use super::*;
use nalgebra::Rotation3;
const HZ_MIN: f64 = 0.0;
const HZ_MAX: f64 = 4.0;
fn host_m() -> Matrix4<f64> {
Matrix4::new_translation(&Vector3::new(3.0, -2.0, 5.0))
* Rotation3::from_axis_angle(&Vector3::z_axis(), 30.0_f64.to_radians()).to_homogeneous()
}
fn hm_inv() -> Matrix4<f64> {
let m = host_m();
m.try_inverse().expect("host placement is invertible")
}
fn host_axis() -> Vector3<f64> {
Vector3::new(0.0, 0.0, 1.0)
}
fn opening(
center: (f64, f64, f64),
axis: Vector3<f64>,
depth: f64,
dir_sign: f64,
) -> ExtrudedSolidLike {
let z = Vector3::new(0.0, 0.0, 1.0);
let rot = Rotation3::rotation_between(&z, &axis)
.unwrap_or_else(Rotation3::identity)
.to_homogeneous();
let m = host_m() * Matrix4::new_translation(&Vector3::new(center.0, center.1, center.2)) * rot;
let profile = Profile2D::new(vec![
Point2::new(-0.5, -0.5),
Point2::new(0.5, -0.5),
Point2::new(0.5, 0.5),
Point2::new(-0.5, 0.5),
]);
ExtrudedSolidLike {
profile,
depth,
dir_sign,
m,
}
}
#[test]
fn parallel_through_opening_is_eligible() {
let op = opening((1.0, 1.0, 0.0), host_axis(), HZ_MAX, 1.0);
let fp = opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04);
assert!(
fp.is_some(),
"a parallel full-depth opening must be eligible"
);
let fp = fp.unwrap();
assert_eq!(fp.len(), 4);
let expected = [(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)];
for (i, (ex, ey)) in expected.iter().enumerate() {
assert!(
(fp[i].x - ex).abs() < 1e-9 && (fp[i].y - ey).abs() < 1e-9,
"footprint vertex {i}: {:?} != ({ex}, {ey})",
fp[i]
);
}
}
#[test]
fn opening_outside_the_host_span_in_host_local_defers() {
let op = opening((1.0, 1.0, -5.0), host_axis(), HZ_MAX, 1.0);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"an opening outside the host's own extrusion span must defer"
);
}
#[test]
fn perpendicular_opening_defers() {
let op = opening((1.0, 1.0, 2.0), Vector3::new(1.0, 0.0, 0.0), 1.0, 1.0);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"a perpendicular opening must defer"
);
}
#[test]
fn partial_depth_opening_defers() {
let op = opening((1.0, 1.0, 0.0), host_axis(), 1.0, 1.0);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"a partial-depth opening must defer"
);
}
#[test]
fn near_parallel_oblique_opening_defers() {
let theta = 1.0_f64.to_radians();
let axis = Vector3::new(theta.sin(), 0.0, theta.cos());
let op = opening((1.0, 1.0, 0.5 * theta.sin()), axis, 4.0, 1.0);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"a near-parallel oblique opening must defer to the exact kernel"
);
}
#[test]
fn slightly_oblique_short_opening_defers_on_parallelism_alone() {
let theta = 3.0_f64.to_radians();
let axis = Vector3::new(theta.sin(), 0.0, theta.cos());
let op = opening((1.0, 1.0, 0.0), axis, 0.005, 1.0);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 10.0).is_none(),
"a 3-degree tilt must be rejected by the parallelism gate even when \
the lateral-drift and through-cut-span gates are too loose to catch it"
);
}
#[test]
fn degenerate_zero_area_footprint_defers() {
let z = Vector3::new(0.0, 0.0, 1.0);
let m = host_m() * Matrix4::new_translation(&Vector3::new(1.0, 1.0, 0.0));
let profile = Profile2D::new(vec![
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(2.0, 0.0),
]);
let op = ExtrudedSolidLike {
profile,
depth: HZ_MAX,
dir_sign: 1.0,
m: m * Matrix4::new_translation(&Vector3::new(0.0, 0.0, 0.0))
* Rotation3::rotation_between(&z, &host_axis())
.unwrap_or_else(Rotation3::identity)
.to_homogeneous(),
};
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"a zero-area (collinear) footprint must defer to the exact kernel"
);
}
#[test]
fn reconcile_solid_rejects_volume_ratio_outside_tolerance() {
let host_profile = Profile2D::new(vec![
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.0, 1.0),
Point2::new(0.0, 1.0),
]);
let host = extrude_profile(&host_profile, 1.0, None).expect("host extrude");
let notched_profile = Profile2D::new(vec![
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.0, 1.0),
Point2::new(0.7, 1.0),
Point2::new(0.7, 0.9),
Point2::new(0.2, 0.9),
Point2::new(0.2, 1.0),
Point2::new(0.0, 1.0),
]);
let solid = extrude_profile(¬ched_profile, 1.0, None).expect("solid extrude");
assert!(
!reconcile_solid(&host, &solid),
"a 5% volume-ratio discrepancy (0.95) must be rejected by the \
0.97..1.03 tolerance even though the AABB matches exactly"
);
}
#[test]
fn annular_opening_defers() {
let mut op = opening((1.0, 1.0, 0.0), host_axis(), HZ_MAX, 1.0);
op.profile.add_hole(vec![
Point2::new(-0.2, -0.2),
Point2::new(-0.2, 0.2),
Point2::new(0.2, 0.2),
Point2::new(0.2, -0.2),
]);
assert!(
opening_solid_footprint(&op, &hm_inv(), &host_axis(), HZ_MIN, HZ_MAX, 0.04).is_none(),
"an annular opening must defer"
);
}
#[test]
fn footprint_interior_gates_boundary_breach() {
let profile = Profile2D::new(vec![
Point2::new(0.0, 0.0),
Point2::new(10.0, 0.0),
Point2::new(10.0, 10.0),
Point2::new(0.0, 10.0),
]);
let interior = vec![
Point2::new(2.0, 2.0),
Point2::new(3.0, 2.0),
Point2::new(3.0, 3.0),
Point2::new(2.0, 3.0),
];
let breaching = vec![
Point2::new(-1.0, 2.0),
Point2::new(3.0, 2.0),
Point2::new(3.0, 3.0),
Point2::new(-1.0, 3.0),
];
assert!(footprint_interior(&interior, &profile));
assert!(!footprint_interior(&breaching, &profile));
}