use cadrum::{BSplineEnd, Edge, Wire};
use glam::DVec3;
const TOL: f64 = 1e-6;
fn approx_eq(a: DVec3, b: DVec3, tol: f64) -> bool {
(a - b).length() < tol
}
#[test]
fn project_on_line_midpoint() {
let e = Edge::line(DVec3::new(-1.0, 0.0, 0.0), DVec3::new(1.0, 0.0, 0.0)).unwrap();
let (cp, tg) = e.project(DVec3::new(0.0, 1.0, 0.0));
assert!(approx_eq(cp, DVec3::ZERO, TOL), "cp={cp:?}");
assert!((tg.x.abs() - 1.0).abs() < TOL && tg.y.abs() < TOL && tg.z.abs() < TOL, "tg={tg:?}");
}
#[test]
fn project_on_circle_returns_radius() {
let e = Edge::circle(1.0, DVec3::Z).unwrap();
let (cp, tg) = e.project(DVec3::new(2.0, 0.0, 0.0));
assert!(approx_eq(cp, DVec3::new(1.0, 0.0, 0.0), TOL), "cp={cp:?}");
assert!(tg.x.abs() < TOL && (tg.y.abs() - 1.0).abs() < TOL && tg.z.abs() < TOL, "tg={tg:?}");
let (cp2, _) = e.project(DVec3::new(3.0, 0.0, 5.0));
assert!(approx_eq(cp2, DVec3::new(1.0, 0.0, 0.0), TOL), "cp2={cp2:?}");
}
#[test]
fn project_on_polygon_picks_nearest_edge() {
let square = Edge::polygon([
DVec3::new(1.0, 1.0, 0.0),
DVec3::new(-1.0, 1.0, 0.0),
DVec3::new(-1.0, -1.0, 0.0),
DVec3::new(1.0, -1.0, 0.0),
].iter()).unwrap();
let (cp, _) = square.project(DVec3::new(2.0, 0.0, 0.0));
assert!(approx_eq(cp, DVec3::new(1.0, 0.0, 0.0), TOL), "cp={cp:?}");
let (cp2, _) = square.project(DVec3::new(-2.0, -3.0, 0.0));
assert!(approx_eq(cp2, DVec3::new(-1.0, -1.0, 0.0), TOL), "cp2={cp2:?}");
}
#[test]
fn project_on_bspline_converges_to_interpolant() {
let pts = [
DVec3::new(1.0, 0.0, 0.0),
DVec3::new(0.0, 1.0, 0.0),
DVec3::new(-1.0, 0.0, 0.0),
DVec3::new(0.0, -1.0, 0.0),
];
let e = Edge::bspline(pts.iter(), BSplineEnd::Periodic).unwrap();
let (cp, tg) = e.project(DVec3::ZERO);
let r = cp.length();
assert!((0.7..=1.0).contains(&r), "radius out of envelope: {r}");
assert!((tg.length() - 1.0).abs() < TOL, "|tg|={}", tg.length());
}
#[test]
fn project_empty_wire_returns_zero() {
let empty: Vec<Edge> = Vec::new();
let (cp, tg) = empty.project(DVec3::ONE);
assert_eq!(cp, DVec3::ZERO);
assert_eq!(tg, DVec3::ZERO);
}