use axiolid_core::Point3;
use axiolid_mesh::TriMesh;
use axiolid_reference::{assemble_polylines, intersection_segments};
fn cuboid(min: Point3, max: Point3) -> TriMesh {
let positions = vec![
Point3::new(min.x, min.y, min.z),
Point3::new(max.x, min.y, min.z),
Point3::new(max.x, max.y, min.z),
Point3::new(min.x, max.y, min.z),
Point3::new(min.x, min.y, max.z),
Point3::new(max.x, min.y, max.z),
Point3::new(max.x, max.y, max.z),
Point3::new(min.x, max.y, max.z),
];
let indices = vec![
0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 0, 4, 3, 4, 7, ];
TriMesh::new(positions, indices)
}
#[test]
fn disjoint_boxes_produce_no_segments() {
let left = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
let right = cuboid(Point3::new(5.0, 5.0, 5.0), Point3::new(6.0, 6.0, 6.0));
let curve = intersection_segments(&left, &right).expect("disjoint boxes are supported");
assert!(curve.segments.is_empty(), "disjoint solids share no curve");
}
#[test]
fn a_nested_box_produces_no_segments() {
let outer = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(10.0, 10.0, 10.0));
let inner = cuboid(Point3::new(4.0, 4.0, 4.0), Point3::new(6.0, 6.0, 6.0));
let curve = intersection_segments(&outer, &inner).expect("nested boxes are supported");
assert!(curve.segments.is_empty(), "a nested solid crosses nothing");
}
#[test]
fn overlapping_boxes_intersect_in_one_closed_loop() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(4.0, 4.0, 4.0));
let tool = cuboid(Point3::new(2.0, 2.0, 2.0), Point3::new(6.0, 6.0, 6.0));
let curve = intersection_segments(&subject, &tool).expect("crossing boxes are supported");
assert!(
!curve.segments.is_empty(),
"crossing surfaces share a curve"
);
let polylines = assemble_polylines(&curve.segments).expect("the curve is a 1-manifold");
assert_eq!(polylines.len(), 1, "one crossing corner gives one loop");
assert!(
polylines[0].closed,
"the loop must close: an open run is a crack"
);
}
#[test]
fn the_curve_is_independent_of_operand_order() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(4.0, 4.0, 4.0));
let tool = cuboid(Point3::new(2.0, 2.0, 2.0), Point3::new(6.0, 6.0, 6.0));
let forward = intersection_segments(&subject, &tool).expect("supported");
let reverse = intersection_segments(&tool, &subject).expect("supported");
assert_eq!(
forward.segments.len(),
reverse.segments.len(),
"segment count must not depend on which operand is first",
);
}
#[test]
fn the_curve_is_reproducible() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(4.0, 4.0, 4.0));
let tool = cuboid(Point3::new(2.0, 2.0, 2.0), Point3::new(6.0, 6.0, 6.0));
let first = intersection_segments(&subject, &tool).expect("supported");
let second = intersection_segments(&subject, &tool).expect("supported");
assert_eq!(first.segments, second.segments, "segments must be stable");
assert_eq!(
first.positions, second.positions,
"positions must be stable"
);
}
#[test]
fn coplanar_faces_are_refused_rather_than_approximated() {
let lower = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(2.0, 2.0, 2.0));
let upper = cuboid(Point3::new(0.0, 0.0, 2.0), Point3::new(2.0, 2.0, 4.0));
let result = intersection_segments(&lower, &upper);
assert!(
result.is_err(),
"an area of contact must not be reported as a curve"
);
}
#[test]
fn the_loop_has_the_node_count_the_geometry_implies() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(4.0, 4.0, 4.0));
let tool = cuboid(Point3::new(2.0, 2.0, 2.0), Point3::new(6.0, 6.0, 6.0));
let curve = intersection_segments(&subject, &tool).expect("supported");
assert_eq!(curve.segments.len(), 6, "a hexagonal ring has six sides");
assert_eq!(curve.positions.len(), 6, "and six corners");
let polylines = assemble_polylines(&curve.segments).expect("1-manifold");
assert_eq!(polylines.len(), 1);
assert!(polylines[0].closed);
assert_eq!(polylines[0].nodes.len(), 6, "six nodes around the hexagon");
}
#[test]
fn every_node_lies_inside_both_operands_bounds() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(4.0, 4.0, 4.0));
let tool = cuboid(Point3::new(2.0, 2.0, 2.0), Point3::new(6.0, 6.0, 6.0));
let curve = intersection_segments(&subject, &tool).expect("supported");
for point in curve.positions.values() {
for axis in [point.x, point.y, point.z] {
assert!(
(2.0..=4.0).contains(&axis),
"node {point:?} lies outside the shared region [2,4]^3",
);
}
}
}
#[test]
fn a_slab_through_a_box_crosses_in_two_closed_rings() {
let box_solid = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(10.0, 10.0, 10.0));
let slab = cuboid(Point3::new(3.0, -5.0, -5.0), Point3::new(6.0, 15.0, 15.0));
let curve = intersection_segments(&box_solid, &slab).expect("a through-cut is a curve");
let polylines = assemble_polylines(&curve.segments).expect("both rings close");
assert_eq!(polylines.len(), 2, "a through-cut enters and exits");
for line in &polylines {
assert!(line.closed, "a cut through a closed solid closes");
assert_eq!(
line.nodes.len(),
8,
"four box faces, two of them split by a diagonal"
);
}
}
#[test]
fn coplanar_faces_without_shared_area_do_not_block_the_curve() {
let subject = cuboid(Point3::new(0.0, 0.0, 0.0), Point3::new(2.0, 2.0, 2.0));
let tool = cuboid(Point3::new(5.0, 0.0, 0.0), Point3::new(7.0, 2.0, 2.0));
let curve = intersection_segments(&subject, &tool)
.expect("coplanar but non-overlapping faces are not a refusal");
assert!(
curve.segments.is_empty(),
"the solids are disjoint, so there is no curve"
);
}