use facett_core::render::camera::{self, Camera, Pos, V3};
use facett_core::render::cpu::sdf::{point_segment, quad_coverage};
use facett_core::render::decor;
use facett_core::render::prim::{shape, MarkerInstance};
use facett_core::render::Frame;
#[test]
fn quad_coverage_dispatches_square_triangle_and_rejects_unknown() {
let mk = |s: u32| MarkerInstance {
center: [0.0; 2],
radius: 10.0,
corner: 0.0,
color: [1.0; 4],
aa: 0.5,
shape: s,
}
.lower();
let sq = mk(shape::SQUARE);
assert!(quad_coverage(&sq, 0.0, 0.0) > 0.99, "square centre lit");
assert!(quad_coverage(&sq, 8.0, 8.0) > 0.9, "square fills its corner");
assert!(quad_coverage(&sq, 14.0, 0.0) < 0.01, "square dark past the edge+aa");
let tri = mk(shape::TRIANGLE);
assert!(quad_coverage(&tri, 0.0, 7.0) > 0.9, "triangle base-centre lit");
assert!(quad_coverage(&tri, 9.0, -9.0) < 0.1, "triangle top-corner dark");
let mut bogus = sq;
bogus.shape = 999;
assert_eq!(quad_coverage(&bogus, 0.0, 0.0), 0.0, "unknown shape draws nothing");
}
#[test]
fn point_segment_gives_distance_and_clamped_parameter() {
let a = [0.0, 0.0];
let b = [10.0, 0.0];
let (d2, t) = point_segment([5.0, 3.0], a, b);
assert!((d2 - 9.0).abs() < 1e-4, "squared distance to the segment, got {d2}");
assert!((t - 0.5).abs() < 1e-6, "foot at the segment midpoint, got {t}");
let (d2on, ton) = point_segment([7.0, 0.0], a, b);
assert!(d2on < 1e-6 && (ton - 0.7).abs() < 1e-6, "on-segment: d=0, t=0.7");
let (d2past, tpast) = point_segment([20.0, 0.0], a, b);
assert_eq!(tpast, 1.0, "t clamps at the far endpoint");
assert!((d2past - 100.0).abs() < 1e-4, "distance measured to endpoint b (10 px away)");
let (_, tbefore) = point_segment([-5.0, 0.0], a, b);
assert_eq!(tbefore, 0.0, "t clamps at the near endpoint");
let (d2deg, tdeg) = point_segment([3.0, 4.0], [1.0, 1.0], [1.0, 1.0]);
assert_eq!(tdeg, 0.0, "zero-length segment → t=0 (no divide-by-zero)");
assert!((d2deg - 13.0).abs() < 1e-4, "distance to the degenerate point, got {d2deg}");
}
#[test]
fn v3_cross_is_right_handed_and_dot_len_normalize_agree() {
let x = V3::new(1.0, 0.0, 0.0);
let y = V3::new(0.0, 1.0, 0.0);
let z = V3::new(0.0, 0.0, 1.0);
let c = x.cross(y);
assert_eq!((c.x, c.y, c.z), (0.0, 0.0, 1.0), "x × y = z (right-handed)");
let c2 = y.cross(x);
assert_eq!((c2.x, c2.y, c2.z), (0.0, 0.0, -1.0), "y × x = -z (anticommutes)");
assert_eq!(x.dot(y), 0.0);
assert_eq!(z.dot(z), 1.0);
let v = V3::new(3.0, 4.0, 0.0);
assert!((v.len() - 5.0).abs() < 1e-6, "3-4-5 length");
let u = v.normalized();
assert!((u.len() - 1.0).abs() < 1e-6, "normalized is unit length");
assert!((u.x - 0.6).abs() < 1e-6 && (u.y - 0.8).abs() < 1e-6, "direction preserved");
let zn = V3::new(0.0, 0.0, 0.0).normalized();
assert!(zn.x.is_finite() && zn.y.is_finite() && zn.z.is_finite(), "no NaN from zero vec");
let a = V3::new(1.0, 2.0, 3.0);
let b = V3::new(4.0, -1.0, 0.5);
let back = a.add(b).sub(b);
assert_eq!((back.x, back.y, back.z), (a.x, a.y, a.z), "add then sub is identity");
let s = a.scale(2.0);
assert_eq!((s.x, s.y, s.z), (2.0, 4.0, 6.0));
}
#[test]
fn camera_2d_affine_is_invertible_for_picking() {
let cam = Camera { pan_x: 30.0, pan_y: -12.0, zoom: 2.5, ..Camera::default() };
let world = Pos::new(10.0, 4.0);
let (sx, sy) = cam.project2d(world);
let (wx, wy) = ((sx - cam.pan_x) / cam.zoom, (sy - cam.pan_y) / cam.zoom);
assert!((wx - world.x).abs() < 1e-4 && (wy - world.y).abs() < 1e-4, "project→unproject round-trips");
let cam2 = Camera { pan_x: -5.0, pan_y: 100.0, zoom: 0.25, ..Camera::default() };
let (sx2, sy2) = cam2.project2d(Pos::new(400.0, -80.0));
let (wx2, wy2) = ((sx2 - cam2.pan_x) / cam2.zoom, (sy2 - cam2.pan_y) / cam2.zoom);
assert!((wx2 - 400.0).abs() < 1e-3 && (wy2 + 80.0).abs() < 1e-3, "fractional-zoom round-trip");
}
#[test]
fn camera_eye_orbits_at_distance_and_far_plane_pads_it() {
for &(az, el) in &[(0.0f32, 0.0f32), (0.7, 0.3), (-1.2, -0.9), (3.0, 1.4)] {
let cam = Camera {
azimuth: az,
elevation: el,
distance: 5.0,
target: V3::new(1.0, -2.0, 0.5),
..Camera::default()
};
let r = cam.eye().sub(cam.target).len();
assert!((r - 5.0).abs() < 1e-3, "eye radius == distance at ({az},{el}), got {r}");
}
let cam = Camera { distance: 6.0, ..Camera::default() };
assert!((cam.far_plane() - 10.0).abs() < 1e-6, "far plane = distance + 4");
assert_eq!(Camera::NEAR_PLANE, 0.02);
let _ = camera::InputFeel::default(); }
#[test]
fn frame_lit_px_counts_only_nonzero_alpha() {
let clear = Frame { width: 2, height: 2, rgba: vec![0; 2 * 2 * 4] };
assert_eq!(clear.lit_px(), 0, "transparent frame → 0 lit");
let mixed = Frame {
width: 2,
height: 2,
rgba: vec![
255, 0, 0, 255, 9, 9, 9, 0, 0, 0, 0, 1, 0, 0, 0, 0, ],
};
assert_eq!(mixed.lit_px(), 2, "only the two non-zero-alpha pixels count");
}
#[test]
fn decor_node_helpers_have_the_right_geometry() {
let halo = decor::glow_halo([40.0, 40.0], 8.0, [0.2, 0.8, 1.0], 1.7 , 2.1);
assert!(halo.radius > 8.0 && halo.inner >= 8.0, "halo blooms outside the marker");
assert!(halo.inner < halo.radius, "valid annulus");
assert_eq!(halo.color[3], 1.0, "intensity clamped to 1.0");
assert!((halo.color[2] - 1.0).abs() < 1e-6, "carries the node blue");
let ao = decor::ao_shadow([40.0, 40.0], 10.0, 1.5 );
assert!(ao.center[0] > 40.0 && ao.center[1] > 40.0, "shadow offset down-right");
assert!(ao.radius > 10.0, "shadow spreads past the marker");
assert!(ao.color[0] < 0.05 && ao.color[3] == 1.0, "black, depth clamped to 1.0");
}
#[test]
fn decor_ramps_and_shockwave_clamp() {
let a = [0.0, 0.0, 0.0, 1.0];
let b = [1.0, 0.5, 0.25, 0.0];
assert_eq!(decor::lerp_rgba(a, b, 0.0), a);
assert_eq!(decor::lerp_rgba(a, b, 1.0), b);
assert_eq!(decor::lerp_rgba(a, b, 2.0), b, "t clamps above 1");
assert_eq!(decor::lerp_rgba(a, b, -1.0), a, "t clamps below 0");
let mid = decor::lerp_rgba(a, b, 0.5);
assert!((mid[0] - 0.5).abs() < 1e-6 && (mid[3] - 0.5).abs() < 1e-6, "midpoint blends");
let tiny = decor::shockwave_ring([50.0, 50.0], 0.0, 5.0, [1.0, 0.4, 0.9], 0.0);
assert!(tiny.inner >= 0.0, "inner radius clamped to >= 0 (valid annulus)");
let gone = decor::shockwave_ring([50.0, 50.0], 10.0, 100.0, [1.0, 0.4, 0.9], 1.0);
assert!(gone.color[3] < 1e-3, "faded out at t=1");
assert!(gone.radius > 100.0, "expanded to r0 + reach at t=1");
}