use chijin::{
utils::{helix_section, revolve_section, stretch_vector},
Error, Shape,
};
use glam::DVec3;
use std::panic::{self, AssertUnwindSafe};
use std::path::Path;
fn dvec3(x: f64, y: f64, z: f64) -> DVec3 {
DVec3::new(x, y, z)
}
fn test_box() -> Shape {
Shape::box_from_corners(dvec3(0.0, 0.0, 0.0), dvec3(10.0, 10.0, 10.0))
}
fn lambda360box() -> Shape {
let mut file = std::fs::File::open(
"steps/LAMBDA360-BOX-d6cb2eb2d6e0d802095ea1eda691cf9a3e9bf3394301a0d148f53e55f0f97951.step",
)
.expect("Failed to open step file");
Shape::read_step(&mut file).expect("Failed to read step file")
}
fn stretch(shape: &Shape, cx: f64, cy: f64, cz: f64, dx: f64, dy: f64, dz: f64) -> Result<Shape, Error> {
let eps = 1e-10;
let origin = DVec3::new(cx, cy, cz);
let x;
let after_x: &Shape = if dx > eps {
x = stretch_vector(shape, origin, DVec3::new(dx, 0.0, 0.0))?;
&x
} else {
shape
};
let y;
let after_y: &Shape = if dy > eps {
y = stretch_vector(after_x, origin, DVec3::new(0.0, dy, 0.0))?;
&y
} else {
after_x
};
let z;
let after_z: &Shape = if dz > eps {
z = stretch_vector(after_y, origin, DVec3::new(0.0, 0.0, dz))?;
&z
} else {
after_y
};
after_z.clean()
}
fn stretch_ok(
shape: &Shape,
cx: f64,
cy: f64,
cz: f64,
dx: f64,
dy: f64,
dz: f64,
) -> Result<Shape, String> {
let result = panic::catch_unwind(AssertUnwindSafe(|| stretch(shape, cx, cy, cz, dx, dy, dz)));
match result {
Ok(Ok(s)) => Ok(s),
Ok(Err(e)) => Err(e.to_string()),
Err(payload) => {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic in shape operations".to_string()
};
Err(msg)
}
}
}
struct Lcg {
state: u32,
}
impl Lcg {
fn new(seed: u32) -> Self {
Self { state: seed }
}
fn next_f64(&mut self) -> f64 {
self.state = self.state.wrapping_mul(1664525).wrapping_add(1013904223);
(self.state as f64) / (u32::MAX as f64)
}
fn gen_range(&mut self, min: f64, max: f64) -> f64 {
min + self.next_f64() * (max - min)
}
}
fn write_step(shape: &Shape, name: &str) {
std::fs::create_dir_all("out").unwrap();
let path = format!("out/{name}.step");
let mut file = std::fs::File::create(&path).unwrap();
shape.write_step(&mut file).expect("STEP write failed");
let mesh = shape.mesh_with_tolerance(0.1).expect("meshing failed");
assert!(!mesh.vertices.is_empty(), "result shape has no vertices");
println!(
"{name}: {} vertices, {} triangles → {path}",
mesh.vertices.len(),
mesh.indices.len() / 3,
);
}
#[test]
fn test_stretch_vector_volume() {
let shape = test_box();
let result = stretch_vector(&shape, dvec3(5.0, 0.0, 0.0), dvec3(1.0, 0.0, 0.0)).unwrap();
let v = result.volume();
assert!((v - 1100.0).abs() < 1e-3, "expected volume ≈ 1100, got {v}");
}
#[test]
fn test_revolve_section_volume() {
let shape = test_box();
let result = revolve_section(
&shape,
dvec3(0.0, 5.0, 0.0),
dvec3(0.0, 0.0, 1.0),
dvec3(0.0, 1.0, 0.0),
std::f64::consts::TAU,
)
.unwrap();
let v = result.volume();
std::fs::create_dir_all("out").unwrap();
let mut file = std::fs::File::create("out/revolve_section.step").unwrap();
result.write_step(&mut file).expect("STEP write failed");
let expected = std::f64::consts::PI * 10.0f64.powi(2) * 10.0;
assert!((v - expected).abs() < 1.0, "expected volume ≈ {expected:.2}, got {v}");
}
#[test]
fn test_helix_section_volume() {
let shape = test_box();
let result = helix_section(
&shape,
dvec3(0.0, 5.0, 0.0),
dvec3(0.0, 0.0, 1.0),
dvec3(0.0, 1.0, 0.0),
20.0,
1.0,
)
.unwrap();
let v = result.volume();
std::fs::create_dir_all("out").unwrap();
let mut file = std::fs::File::create("out/helix_section.step").unwrap();
result.write_step(&mut file).expect("STEP write failed");
let radius = 5.0;
let area = 100.0;
let expected = 2.0 * std::f64::consts::PI * radius * area;
let tolerance = expected * 0.01;
println!("helix_section volume: {v:.2}, expected (Pappus revolve): {expected:.2}");
assert!(
(v - expected).abs() < tolerance,
"volume check: expected ≈ {expected:.2}, got {v:.2}"
);
}
#[test]
fn diagnose_new_faces() {
let shape = lambda360box();
println!("input: face_count={}, shell_count={}", shape.faces().count(), shape.shell_count());
let origin = DVec3::new(1.0, 0.0, 1.0);
let delta = DVec3::new(1.0, 0.0, 0.0);
let half = Shape::half_space(origin, -delta.normalize());
let r_half = shape.intersect(&half).expect("intersect(half_space) failed");
println!(" intersect result: tool_face count={}", r_half.shape.faces().filter(|f| r_half.is_tool_face(f)).count());
let big_box = Shape::box_from_corners(
DVec3::new(-1000.0, -1000.0, -1000.0),
DVec3::new(1.0, 1000.0, 1000.0),
);
let r_box = shape.intersect(&big_box).expect("intersect(big_box) failed");
println!(" intersect result: tool_face count={}", r_box.shape.faces().filter(|f| r_box.is_tool_face(f)).count());
for (i, face) in r_box.shape.faces().filter(|f| r_box.is_tool_face(f)).enumerate() {
let n = face.normal_at_center();
let c = face.center_of_mass();
println!(" face[{i}]: normal=({:.3},{:.3},{:.3}) center=({:.3},{:.3},{:.3})", n.x, n.y, n.z, c.x, c.y, c.z);
}
}
#[test]
fn stretch_box_known_error_case_1_0_1() {
let shape = lambda360box();
assert_eq!(shape.shell_count(), 1, "input shape must have exactly one shell");
let result = stretch_ok(&shape, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0);
match &result {
Ok(s) => {
assert_eq!(s.shell_count(), 1, "stretched shape must have exactly one shell");
write_step(s, "stretch_box_known_error_case_1_0_1");
}
Err(e) => println!("Error: {e}"),
}
if let Err(e) = result {
panic!("stretch failed: {e}");
}
}
#[test]
#[ignore = "3000 試行で約 8 分かかるため通常の cargo test からは除外。実行: cargo test stretch_box_random_survey -- --ignored"]
fn stretch_box_random_survey() {
use std::io::Write;
let out_dir = Path::new("out");
if !out_dir.exists() {
std::fs::create_dir_all(out_dir).unwrap();
}
let mut file = std::fs::File::create("out/stretch_box_random_survey.csv").unwrap();
writeln!(file, "cx,cy,cz,dx,dy,dz,success,error_msg").unwrap();
let base_shape = lambda360box();
let mut rng = Lcg::new(42);
let mut success_count = 0;
let total_attempts = 1000;
for _i in 0..total_attempts {
let cx = rng.gen_range(-15.0, 15.0);
let cy = rng.gen_range(-15.0, 15.0);
let cz = rng.gen_range(-15.0, 15.0);
for (dx, dy, dz) in [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)] {
match stretch_ok(&base_shape, cx, cy, cz, dx, dy, dz) {
Ok(_) => {
success_count += 1;
writeln!(file, "{},{},{},{},{},{},1,", cx, cy, cz, dx, dy, dz).unwrap();
}
Err(e) => {
writeln!(file, "{},{},{},{},{},{},0,{}", cx, cy, cz, dx, dy, dz, e).unwrap();
}
}
}
}
println!("Out of {} total tries, {} succeeded.", total_attempts * 3, success_count);
}