Files
vectorcraft/crates/three-d/src/tests/lighting.rs
T

118 lines
5.4 KiB
Rust

use crate::*;
#[test]
fn color_combines_with_neutral_ambient_before_channel_clamping() {
let n = V3(0.0, 0.0, 1.0);
let o = Revolve { light_color: [0.0, 0.5, 1.0], ..Default::default() };
assert_eq!(illumination(&o, n, n), [0.25, 0.65, 1.0]);
assert_eq!(illumination(&o, V3(0.0, 0.0, -1.0), n), [0.25; 3]);
assert_eq!(illumination(&Revolve { light_intensity: 0.0, ..o }, n, n), [0.25; 3]);
assert_eq!(illumination(&Revolve { shade: false, ..o }, n, n), [1.0; 3]);
assert_eq!(illumination(&Revolve { light_color: [0.0; 3], ..o }, n, n), [0.25; 3]);
}
#[test]
fn white_light_matches_existing_brightness_with_softness_front_and_back() {
for softness in [0.0, 0.6, 1.0] {
for behind in [false, true] {
let o = Revolve { light_softness: softness, light_behind: behind, ..Default::default() };
let [x, y, z] = o.light_direction();
let l = V3(x, y, z);
for angle in [-180.0f64, -90.0, 0.0, 45.0, 90.0, 180.0] {
let (s, c) = angle.to_radians().sin_cos();
let n = V3(s, 0.0, c);
assert_eq!(illumination(&o, n, l), [brightness(&o, n, l); 3]);
}
}
}
}
#[test]
fn every_geometry_type_produces_finite_rgb_illumination_without_changing_geometry() {
let p = PathData::from_bezpath(&BezPath::from_svg("M0 0L100 0L100 100L0 100Z").unwrap());
let b = p.bounds().unwrap();
let view = View { light_height: 90.0, light_color: [1.0, 0.0, 0.0], ..Default::default() };
let shapes = [
plane(&p, vectorcraft_geom::FillRule::NonZero, b, view).unwrap(),
extrude(&p, vectorcraft_geom::FillRule::NonZero, b, Extrude::default(), view).unwrap(),
inflate(&p, vectorcraft_geom::FillRule::NonZero, b, Inflate::default(), view).unwrap(),
];
for f in shapes.iter().flatten() {
assert!(f.illumination[0] >= f.illumination[1]);
assert_eq!(f.illumination[1], 0.25);
assert_eq!(f.illumination[2], 0.25);
}
assert_eq!(shapes[0][0].illumination, [1.0, 0.25, 0.25]);
let profile = PathData::from_bezpath(&BezPath::from_svg("M0 0L0 100").unwrap());
let o = Revolve { offset: 50.0, light_color: [1.0, 0.0, 0.0], ..Default::default() };
let red = revolve(&profile, profile.bounds().unwrap(), o).unwrap();
let white = revolve(&profile, profile.bounds().unwrap(), Revolve { light_color: [1.0; 3], ..o }).unwrap();
assert_eq!(red.len(), white.len());
for (r, w) in red.iter().zip(&white) {
assert_eq!(r.points, w.points);
assert_eq!(r.depth, w.depth);
assert_eq!(r.illumination[0], w.brightness);
assert_eq!(&r.illumination[1..], &[0.25, 0.25]);
}
let visible = visible_faces(&red).unwrap();
assert!(!visible.is_empty());
for f in visible {
assert_eq!(f.illumination, [f.brightness, 0.25, 0.25]);
}
}
#[test]
fn non_finite_and_out_of_range_light_channels_are_bounded() {
let o = Revolve { light_color: [f64::NAN, -10.0, f64::INFINITY], ..Default::default() }.sanitized();
assert_eq!(o.light_color, [1.0, 0.0, 1.0]);
assert_eq!(illumination(&o, V3(0.0, 0.0, 1.0), V3(0.0, 0.0, 1.0)), [1.0, 0.25, 1.0]);
}
#[test]
fn smooth_color_keeps_channel_saturation_and_shared_edges() {
let a = Point::new(0.0, 0.0);
let b = Point::new(100.0, 0.0);
let c = Point::new(100.0, 100.0);
let d = Point::new(0.0, 100.0);
let make = |points: &[Point], values: &[f64]| LightGradient {
diffuse: ShadeGradient::triangle(points, values).unwrap(),
ambient: 0.25,
color: [1.0, 0.5, 0.0],
};
let g = make(&[a, b, c], &[0.0, 2.0, 1.0]);
let h = make(&[a, c, d], &[0.0, 1.0, 0.5]);
assert_eq!(g.stops().len(), 4, "two distinct channel clipping points");
for (t, rgb) in g.stops() {
assert_eq!(rgb, g.at(g.diffuse.start.lerp(g.diffuse.end, f64::from(t))));
}
for t in [0.0, 0.2, 0.5, 0.9, 1.0] {
let p = a.lerp(c, t);
for (x, y) in g.at(p).into_iter().zip(h.at(p)) {
assert!((x - y).abs() < 1e-12);
}
}
assert_eq!(g.at(b), [1.0, 1.0, 0.25]);
assert_eq!(g.at(a.lerp(b, 0.5)), [1.0, 0.75, 0.25], "clipped red must not flatten green");
}
#[test]
fn colored_smooth_surfaces_keep_caps_curves_and_visibility_placement() {
let p = PathData::from_bezpath(&BezPath::from_svg("M40 0 C70 20 70 60 40 80").unwrap());
let o = Revolve { offset: 20.0, rotation_x: -25.0, light_color: [1.0, 0.4, 0.1], segments: 24, ..Default::default() };
let faces = revolve(&p, p.bounds().unwrap(), o).unwrap();
let white = revolve(&p, p.bounds().unwrap(), Revolve { light_color: [1.0; 3], ..o }).unwrap();
assert_eq!(faces.len(), white.len());
assert!(faces.iter().any(|f| f.light_gradient.is_some()));
let caps: Vec<_> = faces.iter().filter(|f| f.cap).collect();
assert_eq!(caps.len(), 2);
assert!(caps.iter().all(|f| f.outline.is_some() && f.light_gradient.is_none()), "caps remain continuous flat-lit paths");
for (red, white) in faces.iter().zip(&white) {
assert_eq!(red.path(), white.path());
assert_eq!(red.depth, white.depth);
assert_eq!(red.gradient, white.gradient, "the legacy white ramp is unchanged");
}
let visible = visible_faces(&faces).unwrap();
assert!(visible.iter().any(|f| f.light_gradient.is_some()));
assert!(visible.iter().all(|v| faces.iter().any(|f| f.light_gradient == v.light_gradient && f.illumination == v.illumination)));
}