1use crate::cancel::{is_cancelled, CancelToken};
32use crate::impl_mesh::ManifoldImpl;
33use crate::linalg::{dot, IVec3, Vec3};
34use crate::types::{Box as BBox, Error, Halfedge, OpType, RayHit, TriRef};
35
36#[path = "boolean3_kernels.rs"]
39mod boolean3_kernels;
40use boolean3_kernels::{intersect12, kernel12, winding03};
41
42#[derive(Clone, Default)]
50pub struct Intersections {
51 pub p1q2: Vec<[i32; 2]>,
53 pub x12: Vec<i32>,
55 pub v12: Vec<Vec3>,
57}
58
59pub struct Boolean3 {
65 pub xv12: Intersections,
66 pub xv21: Intersections,
67 pub w03: Vec<i32>,
68 pub w30: Vec<i32>,
69 pub expand_p: bool,
70 pub valid: bool,
71}
72
73
74impl Boolean3 {
79 pub fn new(in_p: &ManifoldImpl, in_q: &ManifoldImpl, op: OpType) -> Self {
81 match Self::new_with_token(in_p, in_q, op, None) {
82 Some(b3) => b3,
83 None => {
89 debug_assert!(
90 false,
91 "Boolean3::new_with_token returned None for a None token; \
92 only a cancelled token can produce None"
93 );
94 Boolean3 {
95 xv12: Intersections::default(),
96 xv21: Intersections::default(),
97 w03: Vec::new(),
98 w30: Vec::new(),
99 expand_p: op == OpType::Add,
100 valid: false,
101 }
102 }
103 }
104 }
105
106 pub fn new_with_token(
114 in_p: &ManifoldImpl,
115 in_q: &ManifoldImpl,
116 op: OpType,
117 token: Option<&CancelToken>,
118 ) -> Option<Self> {
119 let expand_p = op == OpType::Add;
120
121 if in_p.is_empty() || in_q.is_empty() || !in_p.bbox.does_overlap_box(&in_q.bbox) {
122 return Some(Boolean3 {
123 xv12: Intersections::default(),
124 xv21: Intersections::default(),
125 w03: vec![0; in_p.num_vert()],
126 w30: vec![0; in_q.num_vert()],
127 expand_p,
128 valid: true,
129 });
130 }
131
132 let t_total = crate::timing::start();
134 let t = crate::timing::start();
135 if is_cancelled(token) {
138 return None;
139 }
140 let xv12 = intersect12(in_p, in_q, expand_p, true, token)?;
141 crate::timing::print(" Intersect12 P->Q", t);
142 let t = crate::timing::start();
143 if is_cancelled(token) {
144 return None;
145 }
146 let xv21 = intersect12(in_p, in_q, expand_p, false, token)?;
147 crate::timing::print(" Intersect12 Q->P", t);
148
149 if xv12.x12.len() > i32::MAX as usize || xv21.x12.len() > i32::MAX as usize {
150 return Some(Boolean3 {
151 xv12: Intersections::default(),
152 xv21: Intersections::default(),
153 w03: Vec::new(),
154 w30: Vec::new(),
155 expand_p,
156 valid: false,
157 });
158 }
159
160 let t = crate::timing::start();
162 if is_cancelled(token) {
163 return None;
164 }
165 let w03 = winding03(in_p, in_q, &xv12.p1q2, expand_p, true, token)?;
166 crate::timing::print(" Winding03 P", t);
167 let t = crate::timing::start();
168 if is_cancelled(token) {
169 return None;
170 }
171 let w30 = winding03(in_p, in_q, &xv21.p1q2, expand_p, false, token)?;
172 crate::timing::print(" Winding03 Q", t);
173 crate::timing::print("Intersections (total)", t_total);
174
175 Some(Boolean3 {
176 xv12,
177 xv21,
178 w03,
179 w30,
180 expand_p,
181 valid: true,
182 })
183 }
184}
185
186fn extract_tri_vert(mesh: &ManifoldImpl) -> Vec<IVec3> {
191 (0..mesh.num_tri())
192 .map(|tri| {
193 IVec3::new(
194 mesh.halfedge[3 * tri].start_vert,
195 mesh.halfedge[3 * tri + 1].start_vert,
196 mesh.halfedge[3 * tri + 2].start_vert,
197 )
198 })
199 .collect()
200}
201
202fn extract_tri_prop(mesh: &ManifoldImpl) -> Vec<IVec3> {
203 (0..mesh.num_tri())
204 .map(|tri| {
205 IVec3::new(
206 mesh.halfedge[3 * tri].prop_vert,
207 mesh.halfedge[3 * tri + 1].prop_vert,
208 mesh.halfedge[3 * tri + 2].prop_vert,
209 )
210 })
211 .collect()
212}
213
214fn property_row(mesh: &ManifoldImpl, row: usize, width: usize) -> Vec<f64> {
215 if mesh.num_prop == 0 {
216 vec![0.0; width]
217 } else {
218 let mut out = vec![0.0; width];
219 let src = &mesh.properties[row * mesh.num_prop..(row + 1) * mesh.num_prop];
220 out[..src.len()].copy_from_slice(src);
221 out
222 }
223}
224
225pub fn compose_meshes(meshes: &[ManifoldImpl]) -> ManifoldImpl {
229 if meshes.is_empty() {
230 return ManifoldImpl::new();
231 }
232 if meshes.len() == 1 {
233 return meshes[0].clone();
234 }
235 if meshes.iter().any(|m| m.is_soup) {
240 let mut tris = Vec::new();
241 for m in meshes {
242 tris.extend(crate::robust::soup::impl_to_tris(m));
243 }
244 return crate::robust::assemble_all(&tris);
245 }
246
247 let num_prop = meshes.iter().map(|m| m.num_prop).max().unwrap_or(0);
248 let mut vert_pos = Vec::new();
249 let mut properties = Vec::new();
250 let mut tri_vert = Vec::new();
251 let mut tri_prop = Vec::new();
252 let mut vert_offset = 0i32;
253 let mut prop_offset = 0i32;
254
255 for mesh in meshes {
256 vert_pos.extend_from_slice(&mesh.vert_pos);
257
258 let old_tri_vert = extract_tri_vert(mesh);
259 let old_tri_prop = extract_tri_prop(mesh);
260 tri_vert.extend(old_tri_vert.into_iter().map(|t| {
261 IVec3::new(t.x + vert_offset, t.y + vert_offset, t.z + vert_offset)
262 }));
263 tri_prop.extend(old_tri_prop.into_iter().map(|t| {
264 IVec3::new(t.x + prop_offset, t.y + prop_offset, t.z + prop_offset)
265 }));
266
267 if num_prop > 0 {
268 let prop_rows = mesh.num_prop_vert();
269 for row in 0..prop_rows {
270 properties.extend(property_row(mesh, row, num_prop));
271 }
272 prop_offset += prop_rows as i32;
273 } else {
274 prop_offset += mesh.num_prop_vert() as i32;
275 }
276 vert_offset += mesh.num_vert() as i32;
277 }
278
279 let mut all_tri_refs: Vec<TriRef> = Vec::new();
282 let mut merged_transforms = std::collections::BTreeMap::new();
283 let mut tri_offset = 0i32;
284 for mesh in meshes {
285 let mesh_tri_count = mesh.num_tri() as i32;
286 for tri_ref in &mesh.mesh_relation.tri_ref {
287 all_tri_refs.push(TriRef {
288 mesh_id: tri_ref.mesh_id,
289 original_id: tri_ref.original_id,
290 face_id: tri_ref.face_id,
291 coplanar_id: tri_ref.coplanar_id + tri_offset,
292 });
293 }
294 for (id, rel) in &mesh.mesh_relation.mesh_id_transform {
295 merged_transforms.insert(*id, rel.clone());
296 }
297 tri_offset += mesh_tri_count;
298 }
299
300 let mut out = ManifoldImpl::new();
301 out.vert_pos = vert_pos;
302 out.num_prop = num_prop;
303 out.properties = properties;
304 out.create_halfedges(&tri_prop, &tri_vert);
305 out.mesh_relation.tri_ref = all_tri_refs;
308 out.mesh_relation.mesh_id_transform = merged_transforms;
309 out.mesh_relation.original_id = -1;
310 out.calculate_bbox();
311 out.set_epsilon(-1.0, false);
312 crate::edge_op::remove_degenerates(&mut out, 0);
314 out.sort_geometry();
315 out.increment_mesh_ids();
316 out.set_normals_and_coplanar();
317 out
318}
319
320pub fn boolean(mesh_a: &ManifoldImpl, mesh_b: &ManifoldImpl, op: OpType) -> ManifoldImpl {
329 boolean_with_token(mesh_a, mesh_b, op, None)
330}
331
332pub fn boolean_with_token(
338 mesh_a: &ManifoldImpl,
339 mesh_b: &ManifoldImpl,
340 op: OpType,
341 token: Option<&CancelToken>,
342) -> ManifoldImpl {
343 if is_cancelled(token) {
348 return cancelled_impl();
349 }
350 if mesh_a.is_soup || mesh_b.is_soup {
355 let mut out = ManifoldImpl::new();
356 out.make_empty(Error::NotManifold);
357 return out;
358 }
359 if mesh_a.is_empty() {
360 return match op {
361 OpType::Add => mesh_b.clone(),
362 OpType::Intersect => ManifoldImpl::new(),
363 OpType::Subtract => ManifoldImpl::new(),
364 };
365 }
366 if mesh_b.is_empty() {
367 return match op {
368 OpType::Add | OpType::Subtract => mesh_a.clone(),
369 OpType::Intersect => ManifoldImpl::new(),
370 };
371 }
372
373 if !mesh_a.bbox.does_overlap_box(&mesh_b.bbox) {
374 match op {
377 OpType::Add => return compose_meshes(&[mesh_a.clone(), mesh_b.clone()]),
378 OpType::Intersect => return ManifoldImpl::new(),
379 OpType::Subtract => {} }
381 }
382
383 let Some(bool3) = Boolean3::new_with_token(mesh_a, mesh_b, op, token) else {
385 return cancelled_impl();
386 };
387 if !bool3.valid {
388 return ManifoldImpl::new();
389 }
390
391 crate::boolean_result::boolean_result_with_token(mesh_a, mesh_b, op, &bool3, token)
392}
393
394pub fn boolean_dispatch(
402 mesh_a: &ManifoldImpl,
403 mesh_b: &ManifoldImpl,
404 op: OpType,
405 engine: crate::types::BooleanEngine,
406 token: Option<&CancelToken>,
407) -> ManifoldImpl {
408 use crate::types::BooleanEngine as E;
409 let resolved = match engine {
410 E::Auto => {
411 if mesh_a.is_soup || mesh_b.is_soup {
412 E::Robust
413 } else {
414 E::Exact
415 }
416 }
417 other => other,
418 };
419 match resolved {
420 E::Exact | E::Auto => boolean_with_token(mesh_a, mesh_b, op, token),
421 E::Robust => crate::robust::boolean(mesh_a, mesh_b, op, token),
422 }
423}
424
425pub(crate) fn cancelled_impl() -> ManifoldImpl {
428 let mut out = ManifoldImpl::new();
429 out.make_empty(Error::Cancelled);
430 out
431}
432
433pub fn ray_cast(mesh: &ManifoldImpl, origin: Vec3, endpoint: Vec3) -> Vec<RayHit> {
440 if mesh.is_empty() {
441 return vec![];
442 }
443 let dir = endpoint - origin;
444 if dot(dir, dir) == 0.0 {
445 return vec![];
446 }
447
448 let mut ray_impl = ManifoldImpl::new();
451 ray_impl.vert_pos = vec![origin, endpoint];
452 ray_impl.vert_normal = vec![Vec3::splat(0.0), Vec3::splat(0.0)];
453 ray_impl.halfedge = vec![
454 Halfedge { start_vert: 0, end_vert: 1, paired_halfedge: 1, prop_vert: 0 },
455 Halfedge { start_vert: 1, end_vert: 0, paired_halfedge: 0, prop_vert: 0 },
456 ];
457 ray_impl.face_normal = vec![Vec3::splat(0.0)];
458
459 let collider = &mesh.collider;
461
462 let ray_box = BBox::from_points(
464 Vec3::new(origin.x.min(endpoint.x), origin.y.min(endpoint.y), origin.z.min(endpoint.z)),
465 Vec3::new(origin.x.max(endpoint.x), origin.y.max(endpoint.y), origin.z.max(endpoint.z)),
466 );
467
468 let abs_dir = Vec3::new(dir.x.abs(), dir.y.abs(), dir.z.abs());
470 let t_axis = if abs_dir.x > abs_dir.y && abs_dir.x > abs_dir.z {
471 0usize
472 } else if abs_dir.y > abs_dir.z {
473 1
474 } else {
475 2
476 };
477
478 let mut hits: Vec<RayHit> = Vec::new();
479
480 collider.collisions_with_boxes(std::slice::from_ref(&ray_box), false, |_qi, tri| {
482 let (s, v) = kernel12(0, tri, &ray_impl, mesh, &ray_impl, mesh, false, true);
484 if s != 0 && v.x.is_finite() {
485 let origin_t = [origin.x, origin.y, origin.z][t_axis];
487 let dir_t = [dir.x, dir.y, dir.z][t_axis];
488 let v_t = [v.x, v.y, v.z][t_axis];
489 let t = (v_t - origin_t) / dir_t;
490 if t >= 0.0 && t <= 1.0 {
491 hits.push(RayHit {
492 face_id: tri as u64,
493 distance: t,
494 position: v,
495 normal: mesh.face_normal[tri],
496 });
497 }
498 }
499 });
500
501 hits.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap_or(std::cmp::Ordering::Equal));
502 hits
503}
504
505#[cfg(test)]
506#[path = "boolean3_tests.rs"]
507mod tests;