use std::time::Instant;
use manifold_rust::linalg::Vec3;
use manifold_rust::manifold::Manifold;
use manifold_rust::robust::{cells, intersection_graph, soup};
use manifold_rust::types::{BooleanEngine, MeshGL};
fn make_spiky_dodecahedron(spike_height: f64) -> Manifold {
let phi: f64 = (1.0 + 5.0_f64.sqrt()) / 2.0;
let inv_phi = 1.0 / phi;
let scale = 0.5;
let raw_verts: [(f64, f64, f64); 20] = [
(1.0, 1.0, 1.0), (1.0, 1.0, -1.0), (1.0, -1.0, 1.0), (1.0, -1.0, -1.0),
(-1.0, 1.0, 1.0), (-1.0, 1.0, -1.0), (-1.0, -1.0, 1.0), (-1.0, -1.0, -1.0),
(0.0, inv_phi, phi), (0.0, inv_phi, -phi), (0.0, -inv_phi, phi), (0.0, -inv_phi, -phi),
(inv_phi, phi, 0.0), (-inv_phi, phi, 0.0), (inv_phi, -phi, 0.0), (-inv_phi, -phi, 0.0),
(phi, 0.0, inv_phi), (phi, 0.0, -inv_phi), (-phi, 0.0, inv_phi), (-phi, 0.0, -inv_phi),
];
let faces: [[usize; 5]; 12] = [
[0, 8, 10, 2, 16], [0, 16, 17, 1, 12], [0, 12, 13, 4, 8],
[1, 17, 3, 11, 9], [1, 9, 5, 13, 12], [2, 10, 6, 15, 14],
[2, 14, 3, 17, 16], [4, 13, 5, 19, 18], [4, 18, 6, 10, 8],
[5, 9, 11, 7, 19], [6, 18, 19, 7, 15], [3, 14, 15, 7, 11],
];
let verts: Vec<(f64, f64, f64)> =
raw_verts.iter().map(|&(x, y, z)| (x * scale, y * scale, z * scale)).collect();
let mut positions: Vec<f32> = Vec::with_capacity(32 * 3);
let mut tri_verts: Vec<u32> = Vec::with_capacity(60 * 3);
for &(x, y, z) in &verts {
positions.extend([x as f32, y as f32, z as f32]);
}
for face in &faces {
let cx: f64 = face.iter().map(|&i| verts[i].0).sum::<f64>() / 5.0;
let cy: f64 = face.iter().map(|&i| verts[i].1).sum::<f64>() / 5.0;
let cz: f64 = face.iter().map(|&i| verts[i].2).sum::<f64>() / 5.0;
let len = (cx * cx + cy * cy + cz * cz).sqrt();
let (nx, ny, nz) = (cx / len, cy / len, cz / len);
let spike_idx = (positions.len() / 3) as u32;
positions.extend([
(cx + nx * spike_height) as f32,
(cy + ny * spike_height) as f32,
(cz + nz * spike_height) as f32,
]);
for j in 0..5 {
tri_verts.extend([spike_idx, face[j] as u32, face[(j + 1) % 5] as u32]);
}
}
let mut mesh = MeshGL::default();
mesh.num_prop = 3;
mesh.vert_properties = positions;
mesh.tri_verts = tri_verts;
Manifold::from_mesh_gl(&mesh)
}
fn import_stl(path: &str) -> Manifold {
let data = std::fs::read(path).expect("fixture STL");
let head = String::from_utf8_lossy(&data[..data.len().min(512)]).to_string();
let mut positions: Vec<f32> = Vec::new();
if head.trim_start().starts_with("solid") && head.contains("facet") {
for line in String::from_utf8_lossy(&data).lines() {
if let Some(rest) = line.trim_start().strip_prefix("vertex") {
for tok in rest.split_whitespace().take(3) {
positions.push(tok.parse::<f64>().unwrap() as f32);
}
}
}
} else {
let n = u32::from_le_bytes(data[80..84].try_into().unwrap()) as usize;
let n = n.min((data.len() - 84) / 50);
for f in 0..n {
let base = 84 + f * 50 + 12;
for v in 0..9 {
let o = base + v * 4;
positions.push(f32::from_le_bytes(data[o..o + 4].try_into().unwrap()));
}
}
}
let nv = positions.len() / 3;
let (mut min, mut max) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
for i in 0..nv {
for k in 0..3 {
let v = positions[i * 3 + k] as f64;
min[k] = min[k].min(v);
max[k] = max[k].max(v);
}
}
let c = [(min[0] + max[0]) / 2.0, (min[1] + max[1]) / 2.0, (min[2] + max[2]) / 2.0];
let side = (max[0] - min[0]).max(max[1] - min[1]).max(max[2] - min[2]);
let s = if side > 0.0 { 2.0 / side } else { 1.0 };
for i in 0..nv {
for k in 0..3 {
positions[i * 3 + k] = ((positions[i * 3 + k] as f64 - c[k]) * s) as f32;
}
}
let mut mesh = MeshGL::default();
mesh.num_prop = 3;
mesh.tri_verts = (0..nv as u32).collect();
mesh.vert_properties = positions;
mesh.merge();
Manifold::from_mesh_gl_robust(&mesh)
}
fn main() {
let args: Vec<f64> = std::env::args()
.skip(1)
.filter_map(|a| a.parse().ok())
.collect();
let (rx, ry, rz) = if args.len() == 3 {
(args[0], args[1], args[2])
} else {
(30.0, 45.0, 60.0)
};
let a = make_spiky_dodecahedron(0.4);
let b = make_spiky_dodecahedron(0.4)
.rotate(rx, ry, rz)
.translate(Vec3::new(0.3, 0.0, 0.0));
println!(
"spiky vs spiky: {} + {} tris, rot=({rx},{ry},{rz})",
a.num_tri(),
b.num_tri()
);
for (name, engine) in [("exact ", BooleanEngine::Exact), ("robust", BooleanEngine::Robust)] {
let mut best = f64::INFINITY;
let mut out_tris = 0;
for _ in 0..3 {
let t0 = Instant::now();
let out = a.union_with_engine(&b, engine);
let dt = t0.elapsed().as_secs_f64();
best = best.min(dt);
out_tris = out.num_tri();
}
println!("{name} union: {:8.2} ms ({out_tris} tris out)", best * 1e3);
}
let s1 = Manifold::sphere(1.0, 64);
let s2 = Manifold::sphere(1.0, 64).translate(Vec3::new(1.7, 0.0, 0.0));
for (name, engine) in [("exact ", BooleanEngine::Exact), ("robust", BooleanEngine::Robust)] {
let t0 = Instant::now();
let out = s1.union_with_engine(&s2, engine);
println!(
"{name} sphere64 union: {:8.2} ms ({} tris in, {} out)",
t0.elapsed().as_secs_f64() * 1e3,
s1.num_tri() + s2.num_tri(),
out.num_tri()
);
}
if std::env::var("ROBUST_PERF_THINGI").is_ok() {
let pairs: [(&str, &str, &str, [f64; 3]); 3] = [
(
"939888∪93557",
"src/robust/testdata/939888.stl",
"src/robust/testdata/93557.stl",
[356.0, 140.0, 322.0],
),
(
"91946∪61459",
"src/robust/testdata/91946.stl",
"src/robust/testdata/61459.stl",
[236.0, 231.0, 42.0],
),
(
"1663774∪51334",
"src/robust/testdata/1663774.stl",
"src/robust/testdata/51334.stl",
[231.39999999999753, 124.0, 273.6000000000049],
),
];
for (name, file_a, file_b, rot) in pairs {
let a = import_stl(file_a);
let b = import_stl(file_b)
.rotate(rot[0], rot[1], rot[2])
.translate(Vec3::new(0.3, 0.0, 0.0));
let t0 = Instant::now();
let out = a.union_with_engine(&b, BooleanEngine::Robust);
println!(
"robust thingi {name}: {:8.0} ms ({} + {} tris in, {} tris / {} verts out, \
volume {:.7}, status {:?})",
t0.elapsed().as_secs_f64() * 1e3,
a.num_tri(),
b.num_tri(),
out.num_tri(),
out.num_vert(),
out.volume(),
out.status()
);
}
}
let p_tris = soup::impl_to_tris(a.as_impl());
let q_tris = soup::impl_to_tris(b.as_impl());
let t0 = Instant::now();
let graph = intersection_graph::build_graph(&p_tris, &q_tris);
let t_graph = t0.elapsed().as_secs_f64();
let t0 = Instant::now();
let complex = cells::build_cells(&graph);
let t_cells = t0.elapsed().as_secs_f64();
let t0 = Instant::now();
let wind = cells::windings(&graph, &complex, [&p_tris, &q_tris]);
let t_wind = t0.elapsed().as_secs_f64();
let t0 = Instant::now();
let pieces = cells::extract(&graph, &complex, &wind, manifold_rust::types::OpType::Add, manifold_rust::types::WindingRule::Positive);
let t_extract = t0.elapsed().as_secs_f64();
let t0 = Instant::now();
let out = manifold_rust::robust::assemble::assemble(
&pieces,
&graph.verts,
&graph.verts_f64,
|_| true,
None,
);
let t_assemble = t0.elapsed().as_secs_f64();
let _ = out;
println!("robust stages:");
println!(" build_graph {:8.2} ms ({} pieces)", t_graph * 1e3, graph.pieces.len());
println!(" build_cells {:8.2} ms ({} cells)", t_cells * 1e3, complex.num_cells);
println!(" windings {:8.2} ms", t_wind * 1e3);
println!(" extract {:8.2} ms ({} walls kept)", t_extract * 1e3, pieces.len());
println!(" assemble {:8.2} ms", t_assemble * 1e3);
}