pub mod arrangement;
pub mod assemble;
pub mod cdt;
pub mod classify;
pub mod exact;
pub mod intersection_graph;
pub mod propagate;
pub mod ray_shoot;
pub mod soup;
pub mod tri_tri;
use crate::cancel::CancelToken;
use crate::impl_mesh::ManifoldImpl;
use crate::linalg::Vec3;
use crate::types::{Error, OpType};
use classify::Tag;
use exact::rational::R3;
use ray_shoot::piece_centroid;
fn is_cancelled(token: Option<&CancelToken>) -> bool {
token.is_some_and(|t| t.is_cancelled())
}
fn cancelled_impl() -> ManifoldImpl {
let mut out = ManifoldImpl::new();
out.make_empty(Error::Cancelled);
out
}
pub fn boolean(
a: &ManifoldImpl,
b: &ManifoldImpl,
op: OpType,
token: Option<&CancelToken>,
) -> ManifoldImpl {
if is_cancelled(token) {
return cancelled_impl();
}
if a.is_empty() {
return match op {
OpType::Add => b.clone(),
OpType::Intersect | OpType::Subtract => ManifoldImpl::new(),
};
}
if b.is_empty() {
return match op {
OpType::Add | OpType::Subtract => a.clone(),
OpType::Intersect => ManifoldImpl::new(),
};
}
let p_tris = soup::impl_to_tris(a);
let mut q_tris = soup::impl_to_tris(b);
let p_props = soup::impl_to_corner_props(a);
let mut q_props = soup::impl_to_corner_props(b);
if !a.bbox.does_overlap_box(&b.bbox) {
match op {
OpType::Add => {
let mut tris = p_tris.clone();
tris.extend(q_tris.iter().cloned());
let mut interner = intersection_graph::VertInterner::default();
let pieces: Vec<intersection_graph::Piece> = tris
.iter()
.enumerate()
.map(|(i, t)| intersection_graph::Piece {
mesh: if i < p_tris.len() { 0 } else { 1 },
tri: if i < p_tris.len() { i } else { i - p_tris.len() },
vi: [
interner.intern_f64(t[0]),
interner.intern_f64(t[1]),
interner.intern_f64(t[2]),
],
})
.collect();
let ctx = assemble::PropCtx {
num_prop: [a.num_prop, b.num_prop],
tris: [&p_tris, &q_tris],
props: [&p_props, &q_props],
};
let props = (ctx.out_num_prop() > 0).then_some(&ctx);
return assemble::assemble(
&pieces,
&interner.verts,
&interner.verts_f64,
|_| true,
props,
)
.into_impl();
}
OpType::Intersect => return ManifoldImpl::new(),
OpType::Subtract => return a.clone(),
}
}
let complement = op == OpType::Subtract;
if complement {
let nq = b.num_prop;
for (ti, t) in q_tris.iter_mut().enumerate() {
t.swap(1, 2);
if nq > 0 {
let base = 3 * ti * nq;
for k in 0..nq {
q_props.swap(base + nq + k, base + 2 * nq + k);
}
}
}
}
let graph = intersection_graph::build_graph(&p_tris, &q_tris);
if is_cancelled(token) {
return cancelled_impl();
}
let t_cls = crate::timing::start();
let cls = classify::classify_rings(&graph);
crate::timing::print("robust: classify_rings", t_cls);
if is_cancelled(token) {
return cancelled_impl();
}
let t_prop = crate::timing::start();
let prop = propagate::propagate(&graph, &cls);
crate::timing::print("robust: propagate", t_prop);
let mut tags = prop.tags;
let to_rational = |tris: &[[Vec3; 3]]| -> Vec<[R3; 3]> {
tris.iter()
.map(|t| [R3::from_vec3(t[0]), R3::from_vec3(t[1]), R3::from_vec3(t[2])])
.collect()
};
let need_windings = !prop.untagged.is_empty();
let own_rational: [Vec<[R3; 3]>; 2] = if need_windings {
[to_rational(&p_tris), to_rational(&q_tris)]
} else {
[Vec::new(), Vec::new()]
};
let tri_boxes = |tris: &[[Vec3; 3]]| -> Vec<crate::types::Box> {
tris.iter()
.map(|t| {
let mut b = crate::types::Box::from_points(t[0], t[1]);
b.union_point(t[2]);
b
})
.collect()
};
let own_boxes: [Vec<crate::types::Box>; 2] = if need_windings {
[tri_boxes(&p_tris), tri_boxes(&q_tris)]
} else {
[Vec::new(), Vec::new()]
};
let t_winding = crate::timing::start();
let winding_indexes: Option<[ray_shoot::WindingIndex; 2]> = (!prop.untagged.is_empty())
.then(|| [ray_shoot::WindingIndex::new(&p_tris), ray_shoot::WindingIndex::new(&q_tris)]);
for &(root, rep) in &prop.untagged {
if is_cancelled(token) {
return cancelled_impl();
}
let piece = &graph.pieces[rep];
let mesh = piece.mesh as usize;
let indexes = winding_indexes.as_ref().expect("built when untagged is non-empty");
let (other, other_index, other_is_complement): (&[[Vec3; 3]], _, bool) = if mesh == 0 {
(&q_tris, &indexes[1], complement)
} else {
(&p_tris, &indexes[0], false)
};
let pv = graph.piece_verts(rep);
let w = ray_shoot::winding_number_indexed(&piece_centroid(pv), other, other_index);
let inside = if other_is_complement { w == 0 } else { w != 0 };
let tag = if inside { Tag::Inter } else { Tag::Union };
let own_f64: &[[Vec3; 3]] = if mesh == 0 { &p_tris } else { &q_tris };
let component_tag =
on_own_boundary(pv, &own_rational[mesh], own_f64, &own_boxes[mesh]).then_some(tag);
for pi in 0..graph.pieces.len() {
if !cls.discarded[pi] && prop.component[pi] == root {
tags[pi] = component_tag;
}
}
}
crate::timing::print(
&format!("robust: winding queries ({} components)", prop.untagged.len()),
t_winding,
);
let want = match op {
OpType::Add => Tag::Union,
OpType::Subtract | OpType::Intersect => Tag::Inter,
};
let ctx = assemble::PropCtx {
num_prop: [a.num_prop, b.num_prop],
tris: [&p_tris, &q_tris],
props: [&p_props, &q_props],
};
let props = (ctx.out_num_prop() > 0).then_some(&ctx);
let t_asm = crate::timing::start();
let out = assemble::assemble(
&graph.pieces,
&graph.verts,
&graph.verts_f64,
|pi| !cls.discarded[pi] && tags[pi] == Some(want),
props,
);
crate::timing::print("robust: assemble+import", t_asm);
out.into_impl()
}
fn on_own_boundary(
pv: [&R3; 3],
own: &[[R3; 3]],
own_f64: &[[Vec3; 3]],
own_boxes: &[crate::types::Box],
) -> bool {
let normal = pv[1].sub(pv[0]).cross(&pv[2].sub(pv[0]));
let w = ray_shoot::winding_off_surface(
&piece_centroid(pv),
&normal,
own,
own_f64,
own_boxes,
);
w == 0 || w == -1
}
pub(crate) fn assemble_all(tris: &[[Vec3; 3]]) -> ManifoldImpl {
let mut interner = intersection_graph::VertInterner::default();
let pieces: Vec<intersection_graph::Piece> = tris
.iter()
.enumerate()
.map(|(i, t)| intersection_graph::Piece {
mesh: 0,
tri: i,
vi: [
interner.intern_f64(t[0]),
interner.intern_f64(t[1]),
interner.intern_f64(t[2]),
],
})
.collect();
assemble::assemble(&pieces, &interner.verts, &interner.verts_f64, |_| true, None).into_impl()
}
#[cfg(test)]
#[path = "engine_tests.rs"]
mod engine_tests;
#[cfg(test)]
#[path = "cross_validation_tests.rs"]
mod cross_validation_tests;
#[cfg(test)]
#[path = "nonmanifold_tests.rs"]
mod nonmanifold_tests;
#[cfg(test)]
#[path = "property_tests.rs"]
mod property_tests;
#[cfg(test)]
#[path = "thingi_tests.rs"]
mod thingi_tests;