use cadrum::{Shape, Solid};
use glam::DVec3;
fn dvec3(x: f64, y: f64, z: f64) -> DVec3 {
DVec3::new(x, y, z)
}
fn test_box() -> Vec<Solid> {
vec![Solid::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0))]
}
#[test]
fn test_clone_preserves_volume() {
let original = test_box();
let copy = original.clone();
drop(original);
assert!((copy.volume() - 1000.0).abs() < 1e-6);
}
#[test]
fn test_clone_is_independent() {
let original = test_box();
let copy = original.clone();
let other: Vec<Solid> = vec![Solid::box_from_corners(dvec3(5.0, 5.0, 5.0), dvec3(15.0, 15.0, 15.0))];
let _: Vec<Solid> = cadrum::Boolean::union(&original, &other).unwrap().into();
assert!((copy.volume() - 1000.0).abs() < 1e-6);
}
#[test]
fn test_translated_preserves_volume() {
let shape = test_box();
let moved = shape.translate(dvec3(100.0, 200.0, -50.0));
assert!((moved.volume() - 1000.0).abs() < 1e-6);
}
#[test]
fn test_translated_preserves_shell_count() {
let shape = test_box();
let moved = shape.translate(dvec3(5.0, 0.0, 0.0));
assert_eq!(moved.shell_count(), 1);
}
#[test]
fn test_union_of_translated_overlapping_solids_has_single_volume() {
let a = vec![Solid::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0))];
let b = vec![Solid::box_from_corners(dvec3(100.0, 0.0, 0.0), dvec3(110.0, 10.0, 10.0))];
let b_moved: Vec<Solid> = b.clone().translate(dvec3(-100.0, 0.0, 0.0));
let result_no_move: Vec<Solid> = cadrum::Boolean::union(&a, &b)
.expect("union should succeed")
.into();
let volume_no_move: f64 = result_no_move.iter().map(|s| s.volume()).sum();
assert!((volume_no_move - 2000.0).abs() < 1e-3, "expected volume ~2000, got {volume_no_move}");
let result: Vec<Solid> = cadrum::Boolean::union(&a, &b_moved)
.expect("union should succeed")
.into();
let volume: f64 = result.iter().map(|s| s.volume()).sum();
assert!((volume - 1000.0).abs() < 1e-3, "expected volume ~1000, got {volume}");
let result_with_b: Vec<Solid> = cadrum::Boolean::union(&result, &b)
.expect("union should succeed")
.into();
let volume_with_b: f64 = result_with_b.iter().map(|s| s.volume()).sum();
assert!((volume_with_b - 2000.0).abs() < 1e-3, "expected volume ~2000, got {volume_with_b}");
}
#[test]
fn test_rotated_preserves_volume() {
let shape = test_box();
let rotated = shape.rotate(DVec3::ZERO, DVec3::Z, std::f64::consts::FRAC_PI_4);
assert!((rotated.volume() - 1000.0).abs() < 1e-3);
}
#[test]
fn test_rotated_full_turn_preserves_volume() {
let shape = test_box();
let rotated = shape.rotate(DVec3::ZERO, DVec3::Z, std::f64::consts::TAU);
assert!((rotated.volume() - 1000.0).abs() < 1e-3);
}
#[test]
fn test_rotated_preserves_shell_count() {
let shape = test_box();
let rotated = shape.rotate(DVec3::ZERO, DVec3::Y, std::f64::consts::FRAC_PI_2);
assert_eq!(rotated.shell_count(), 1);
}
#[test]
fn test_scaled_volume() {
let shape = test_box();
let scaled = shape.scaled(DVec3::ZERO, 2.0);
assert!((scaled.volume() - 8000.0).abs() < 1e-3);
}
#[test]
fn test_scaled_half_volume() {
let shape = test_box();
let scaled = shape.scaled(DVec3::ZERO, 0.5);
assert!((scaled.volume() - 125.0).abs() < 1e-3);
}
#[test]
fn test_scaled_preserves_shell_count() {
let shape = test_box();
let scaled = shape.scaled(DVec3::ZERO, 3.0);
assert_eq!(scaled.shell_count(), 1);
}
#[test]
fn test_preserves_face_ids() {
use cadrum::TShapeId;
fn face_ids(s: &Vec<Solid>) -> Vec<TShapeId> {
s.faces().map(|f| f.tshape_id()).collect()
}
let shape = test_box();
let solid_id = shape[0].tshape_id();
let ids = face_ids(&shape);
let moved = shape.translate(dvec3(10.0, 0.0, 0.0));
assert_eq!(solid_id, moved[0].tshape_id(), "translate should preserve solid TShapeId");
assert_eq!(ids, face_ids(&moved), "translate should preserve face IDs");
let shape = test_box();
let solid_id = shape[0].tshape_id();
let ids = face_ids(&shape);
let rotated = shape.rotate(DVec3::ZERO, DVec3::Z, std::f64::consts::FRAC_PI_4);
assert_eq!(solid_id, rotated[0].tshape_id(), "rotate should preserve solid TShapeId");
assert_eq!(ids, face_ids(&rotated), "rotate should preserve face IDs");
}
#[test]
fn test_mirrored_octants_union_volume_is_eight() {
let b = vec![Solid::box_from_corners(dvec3(1.0, 1.0, 1.0), dvec3(2.0, 2.0, 2.0))];
let bx = b.mirrored(DVec3::ZERO, DVec3::X);
let by = b.mirrored(DVec3::ZERO, DVec3::Y);
let bz = b.mirrored(DVec3::ZERO, DVec3::Z);
let bxy = bx.mirrored(DVec3::ZERO, DVec3::Y);
let bxz = bx.mirrored(DVec3::ZERO, DVec3::Z);
let byz = by.mirrored(DVec3::ZERO, DVec3::Z);
let bxyz = bxy.mirrored(DVec3::ZERO, DVec3::Z);
let octants = [b, bx, by, bz, bxy, bxz, byz, bxyz];
let mut result = octants[0].clone();
for other in &octants[1..] {
result = cadrum::Boolean::union(&result, other).expect("union failed").into();
}
let volume: f64 = result.iter().map(|s| s.volume()).sum();
assert!((volume - 8.0).abs() < 1e-3, "expected volume ~8, got {volume}");
}
#[test]
fn test_scaled_union_with_original_volume_is_nine() {
let b = vec![Solid::box_from_corners(dvec3(1.0, 1.0, 1.0), dvec3(2.0, 2.0, 2.0))];
let b_scaled = b.scaled(DVec3::ZERO, 2.0);
let result: Vec<Solid> = cadrum::Boolean::union(&b, &b_scaled).expect("union failed").into();
let volume: f64 = result.iter().map(|s| s.volume()).sum();
assert!((volume - 9.0).abs() < 1e-3, "expected volume ~9 (1 + 8), got {volume}");
}
#[test]
fn test_vec_solid_roundtrip() {
let a = Solid::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0));
let b = Solid::box_from_corners(dvec3(20.0, 0.0, 0.0), dvec3(30.0, 10.0, 10.0));
let shape: Vec<Solid> = vec![a, b];
let total_volume = shape.volume();
assert_eq!(shape.len(), 2);
let sum: f64 = shape.iter().map(|s| s.volume()).sum();
assert!((sum - total_volume).abs() < 1e-6, "sum={sum}, expected={total_volume}");
}
#[test]
fn test_single_solid() {
let shape = test_box();
assert_eq!(shape.len(), 1);
assert!((shape[0].volume() - 1000.0).abs() < 1e-6);
}
#[test]
fn test_empty_vec() {
let shape: Vec<Solid> = vec![];
assert!(shape.is_empty());
}
#[test]
fn test_new_faces_subtract_b_inside_a() {
let big: Vec<Solid> = vec![Solid::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0))];
let small: Vec<Solid> = vec![Solid::box_from_corners(dvec3(3.0, 3.0, 3.0), dvec3(7.0, 7.0, 7.0))];
let result = cadrum::Boolean::subtract(&big, &small).unwrap();
assert_eq!(result.solids.faces().filter(|f| result.is_tool_face(f)).count(), 6,
"subtract with B fully inside A: tool faces should be all 6 inner walls");
}
#[test]
fn test_new_faces_intersect_b_inside_a() {
let big: Vec<Solid> = vec![Solid::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0))];
let small: Vec<Solid> = vec![Solid::box_from_corners(dvec3(3.0, 3.0, 3.0), dvec3(7.0, 7.0, 7.0))];
let result = cadrum::Boolean::intersect(&big, &small).unwrap();
let tool_count = result.solids.faces().filter(|f| result.is_tool_face(f)).count();
assert_eq!(tool_count, 6,
"intersect with B fully inside A: tool faces should equal all faces of result");
assert_eq!(result.solids.faces().count(), tool_count,
"intersect with B fully inside A: tool faces should cover all result faces");
}
#[test]
fn test_contains() {
let shape = test_box(); assert!(Shape::contains(&shape, dvec3(5.0, 5.0, 5.0))); assert!(Shape::contains(&shape, dvec3(0.1, 0.1, 0.1))); assert!(!Shape::contains(&shape, dvec3(20.0, 5.0, 5.0))); assert!(!Shape::contains(&shape, dvec3(-0.1, 5.0, 5.0))); }