1pub mod arrangement;
36pub mod assemble;
37pub mod cdt;
38pub mod classify;
39pub mod exact;
40pub mod intersection_graph;
41pub mod propagate;
42pub mod ray_shoot;
43pub mod soup;
44pub mod tri_tri;
45
46use crate::cancel::CancelToken;
47use crate::impl_mesh::ManifoldImpl;
48use crate::linalg::Vec3;
49use crate::types::{Error, OpType};
50
51use classify::Tag;
52use exact::rational::R3;
53use ray_shoot::piece_centroid;
54
55fn is_cancelled(token: Option<&CancelToken>) -> bool {
56 token.is_some_and(|t| t.is_cancelled())
57}
58
59fn cancelled_impl() -> ManifoldImpl {
60 let mut out = ManifoldImpl::new();
61 out.make_empty(Error::Cancelled);
62 out
63}
64
65pub fn boolean(
75 a: &ManifoldImpl,
76 b: &ManifoldImpl,
77 op: OpType,
78 token: Option<&CancelToken>,
79) -> ManifoldImpl {
80 if is_cancelled(token) {
81 return cancelled_impl();
82 }
83 if a.is_empty() {
85 return match op {
86 OpType::Add => b.clone(),
87 OpType::Intersect | OpType::Subtract => ManifoldImpl::new(),
88 };
89 }
90 if b.is_empty() {
91 return match op {
92 OpType::Add | OpType::Subtract => a.clone(),
93 OpType::Intersect => ManifoldImpl::new(),
94 };
95 }
96 let p_tris = soup::impl_to_tris(a);
97 let mut q_tris = soup::impl_to_tris(b);
98 let p_props = soup::impl_to_corner_props(a);
99 let mut q_props = soup::impl_to_corner_props(b);
100
101 if !a.bbox.does_overlap_box(&b.bbox) {
102 match op {
103 OpType::Add => {
104 let mut tris = p_tris.clone();
108 tris.extend(q_tris.iter().cloned());
109 let mut interner = intersection_graph::VertInterner::default();
110 let pieces: Vec<intersection_graph::Piece> = tris
111 .iter()
112 .enumerate()
113 .map(|(i, t)| intersection_graph::Piece {
114 mesh: if i < p_tris.len() { 0 } else { 1 },
115 tri: if i < p_tris.len() { i } else { i - p_tris.len() },
116 vi: [
117 interner.intern_f64(t[0]),
118 interner.intern_f64(t[1]),
119 interner.intern_f64(t[2]),
120 ],
121 })
122 .collect();
123 let ctx = assemble::PropCtx {
124 num_prop: [a.num_prop, b.num_prop],
125 tris: [&p_tris, &q_tris],
126 props: [&p_props, &q_props],
127 };
128 let props = (ctx.out_num_prop() > 0).then_some(&ctx);
129 return assemble::assemble(
130 &pieces,
131 &interner.verts,
132 &interner.verts_f64,
133 |_| true,
134 props,
135 )
136 .into_impl();
137 }
138 OpType::Intersect => return ManifoldImpl::new(),
139 OpType::Subtract => return a.clone(),
140 }
141 }
142
143 let complement = op == OpType::Subtract;
144 if complement {
145 let nq = b.num_prop;
146 for (ti, t) in q_tris.iter_mut().enumerate() {
147 t.swap(1, 2);
148 if nq > 0 {
149 let base = 3 * ti * nq;
151 for k in 0..nq {
152 q_props.swap(base + nq + k, base + 2 * nq + k);
153 }
154 }
155 }
156 }
157
158 let graph = intersection_graph::build_graph(&p_tris, &q_tris);
159 if is_cancelled(token) {
160 return cancelled_impl();
161 }
162 let t_cls = crate::timing::start();
163 let cls = classify::classify_rings(&graph);
164 crate::timing::print("robust: classify_rings", t_cls);
165 if is_cancelled(token) {
166 return cancelled_impl();
167 }
168 let t_prop = crate::timing::start();
169 let prop = propagate::propagate(&graph, &cls);
170 crate::timing::print("robust: propagate", t_prop);
171 let mut tags = prop.tags;
172
173 let to_rational = |tris: &[[Vec3; 3]]| -> Vec<[R3; 3]> {
184 tris.iter()
185 .map(|t| [R3::from_vec3(t[0]), R3::from_vec3(t[1]), R3::from_vec3(t[2])])
186 .collect()
187 };
188 let need_windings = !prop.untagged.is_empty();
189 let own_rational: [Vec<[R3; 3]>; 2] = if need_windings {
190 [to_rational(&p_tris), to_rational(&q_tris)]
191 } else {
192 [Vec::new(), Vec::new()]
193 };
194 let tri_boxes = |tris: &[[Vec3; 3]]| -> Vec<crate::types::Box> {
195 tris.iter()
196 .map(|t| {
197 let mut b = crate::types::Box::from_points(t[0], t[1]);
198 b.union_point(t[2]);
199 b
200 })
201 .collect()
202 };
203 let own_boxes: [Vec<crate::types::Box>; 2] = if need_windings {
204 [tri_boxes(&p_tris), tri_boxes(&q_tris)]
205 } else {
206 [Vec::new(), Vec::new()]
207 };
208
209 let t_winding = crate::timing::start();
210 let winding_indexes: Option<[ray_shoot::WindingIndex; 2]> = (!prop.untagged.is_empty())
213 .then(|| [ray_shoot::WindingIndex::new(&p_tris), ray_shoot::WindingIndex::new(&q_tris)]);
214 for &(root, rep) in &prop.untagged {
215 if is_cancelled(token) {
216 return cancelled_impl();
217 }
218 let piece = &graph.pieces[rep];
219 let mesh = piece.mesh as usize;
220 let indexes = winding_indexes.as_ref().expect("built when untagged is non-empty");
221 let (other, other_index, other_is_complement): (&[[Vec3; 3]], _, bool) = if mesh == 0 {
222 (&q_tris, &indexes[1], complement)
223 } else {
224 (&p_tris, &indexes[0], false)
225 };
226 let pv = graph.piece_verts(rep);
227 let w = ray_shoot::winding_number_indexed(&piece_centroid(pv), other, other_index);
228 let inside = if other_is_complement { w == 0 } else { w != 0 };
229 let tag = if inside { Tag::Inter } else { Tag::Union };
230 let own_f64: &[[Vec3; 3]] = if mesh == 0 { &p_tris } else { &q_tris };
231 let component_tag =
232 on_own_boundary(pv, &own_rational[mesh], own_f64, &own_boxes[mesh]).then_some(tag);
233 for pi in 0..graph.pieces.len() {
234 if !cls.discarded[pi] && prop.component[pi] == root {
235 tags[pi] = component_tag;
236 }
237 }
238 }
239
240 crate::timing::print(
241 &format!("robust: winding queries ({} components)", prop.untagged.len()),
242 t_winding,
243 );
244
245 let want = match op {
246 OpType::Add => Tag::Union,
247 OpType::Subtract | OpType::Intersect => Tag::Inter,
248 };
249 let ctx = assemble::PropCtx {
250 num_prop: [a.num_prop, b.num_prop],
251 tris: [&p_tris, &q_tris],
252 props: [&p_props, &q_props],
253 };
254 let props = (ctx.out_num_prop() > 0).then_some(&ctx);
255 let t_asm = crate::timing::start();
256 let out = assemble::assemble(
257 &graph.pieces,
258 &graph.verts,
259 &graph.verts_f64,
260 |pi| !cls.discarded[pi] && tags[pi] == Some(want),
261 props,
262 );
263 crate::timing::print("robust: assemble+import", t_asm);
264 out.into_impl()
265}
266
267fn on_own_boundary(
279 pv: [&R3; 3],
280 own: &[[R3; 3]],
281 own_f64: &[[Vec3; 3]],
282 own_boxes: &[crate::types::Box],
283) -> bool {
284 let normal = pv[1].sub(pv[0]).cross(&pv[2].sub(pv[0]));
285 let w = ray_shoot::winding_off_surface(
286 &piece_centroid(pv),
287 &normal,
288 own,
289 own_f64,
290 own_boxes,
291 );
292 w == 0 || w == -1
293}
294
295pub(crate) fn assemble_all(tris: &[[Vec3; 3]]) -> ManifoldImpl {
300 let mut interner = intersection_graph::VertInterner::default();
301 let pieces: Vec<intersection_graph::Piece> = tris
302 .iter()
303 .enumerate()
304 .map(|(i, t)| intersection_graph::Piece {
305 mesh: 0,
306 tri: i,
307 vi: [
308 interner.intern_f64(t[0]),
309 interner.intern_f64(t[1]),
310 interner.intern_f64(t[2]),
311 ],
312 })
313 .collect();
314 assemble::assemble(&pieces, &interner.verts, &interner.verts_f64, |_| true, None).into_impl()
315}
316
317#[cfg(test)]
318#[path = "engine_tests.rs"]
319mod engine_tests;
320
321#[cfg(test)]
322#[path = "cross_validation_tests.rs"]
323mod cross_validation_tests;
324
325#[cfg(test)]
326#[path = "nonmanifold_tests.rs"]
327mod nonmanifold_tests;
328
329#[cfg(test)]
330#[path = "property_tests.rs"]
331mod property_tests;
332
333#[cfg(test)]
334#[path = "thingi_tests.rs"]
335mod thingi_tests;