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
236 let num_prop = meshes.iter().map(|m| m.num_prop).max().unwrap_or(0);
237 let mut vert_pos = Vec::new();
238 let mut properties = Vec::new();
239 let mut tri_vert = Vec::new();
240 let mut tri_prop = Vec::new();
241 let mut vert_offset = 0i32;
242 let mut prop_offset = 0i32;
243
244 for mesh in meshes {
245 vert_pos.extend_from_slice(&mesh.vert_pos);
246
247 let old_tri_vert = extract_tri_vert(mesh);
248 let old_tri_prop = extract_tri_prop(mesh);
249 tri_vert.extend(old_tri_vert.into_iter().map(|t| {
250 IVec3::new(t.x + vert_offset, t.y + vert_offset, t.z + vert_offset)
251 }));
252 tri_prop.extend(old_tri_prop.into_iter().map(|t| {
253 IVec3::new(t.x + prop_offset, t.y + prop_offset, t.z + prop_offset)
254 }));
255
256 if num_prop > 0 {
257 let prop_rows = mesh.num_prop_vert();
258 for row in 0..prop_rows {
259 properties.extend(property_row(mesh, row, num_prop));
260 }
261 prop_offset += prop_rows as i32;
262 } else {
263 prop_offset += mesh.num_prop_vert() as i32;
264 }
265 vert_offset += mesh.num_vert() as i32;
266 }
267
268 let mut all_tri_refs: Vec<TriRef> = Vec::new();
271 let mut merged_transforms = std::collections::BTreeMap::new();
272 let mut tri_offset = 0i32;
273 for mesh in meshes {
274 let mesh_tri_count = mesh.num_tri() as i32;
275 for tri_ref in &mesh.mesh_relation.tri_ref {
276 all_tri_refs.push(TriRef {
277 mesh_id: tri_ref.mesh_id,
278 original_id: tri_ref.original_id,
279 face_id: tri_ref.face_id,
280 coplanar_id: tri_ref.coplanar_id + tri_offset,
281 });
282 }
283 for (id, rel) in &mesh.mesh_relation.mesh_id_transform {
284 merged_transforms.insert(*id, rel.clone());
285 }
286 tri_offset += mesh_tri_count;
287 }
288
289 let mut out = ManifoldImpl::new();
290 out.vert_pos = vert_pos;
291 out.num_prop = num_prop;
292 out.properties = properties;
293 out.create_halfedges(&tri_prop, &tri_vert);
294 out.mesh_relation.tri_ref = all_tri_refs;
297 out.mesh_relation.mesh_id_transform = merged_transforms;
298 out.mesh_relation.original_id = -1;
299 out.calculate_bbox();
300 out.set_epsilon(-1.0, false);
301 crate::edge_op::remove_degenerates(&mut out, 0);
303 out.sort_geometry();
304 out.increment_mesh_ids();
305 out.set_normals_and_coplanar();
306 out
307}
308
309pub fn boolean(mesh_a: &ManifoldImpl, mesh_b: &ManifoldImpl, op: OpType) -> ManifoldImpl {
318 boolean_with_token(mesh_a, mesh_b, op, None)
319}
320
321pub fn boolean_with_token(
327 mesh_a: &ManifoldImpl,
328 mesh_b: &ManifoldImpl,
329 op: OpType,
330 token: Option<&CancelToken>,
331) -> ManifoldImpl {
332 if is_cancelled(token) {
337 return cancelled_impl();
338 }
339 if mesh_a.is_empty() {
340 return match op {
341 OpType::Add => mesh_b.clone(),
342 OpType::Intersect => ManifoldImpl::new(),
343 OpType::Subtract => ManifoldImpl::new(),
344 };
345 }
346 if mesh_b.is_empty() {
347 return match op {
348 OpType::Add | OpType::Subtract => mesh_a.clone(),
349 OpType::Intersect => ManifoldImpl::new(),
350 };
351 }
352
353 if !mesh_a.bbox.does_overlap_box(&mesh_b.bbox) {
354 match op {
357 OpType::Add => return compose_meshes(&[mesh_a.clone(), mesh_b.clone()]),
358 OpType::Intersect => return ManifoldImpl::new(),
359 OpType::Subtract => {} }
361 }
362
363 let Some(bool3) = Boolean3::new_with_token(mesh_a, mesh_b, op, token) else {
365 return cancelled_impl();
366 };
367 if !bool3.valid {
368 return ManifoldImpl::new();
369 }
370
371 crate::boolean_result::boolean_result_with_token(mesh_a, mesh_b, op, &bool3, token)
372}
373
374pub(crate) fn cancelled_impl() -> ManifoldImpl {
377 let mut out = ManifoldImpl::new();
378 out.make_empty(Error::Cancelled);
379 out
380}
381
382pub fn ray_cast(mesh: &ManifoldImpl, origin: Vec3, endpoint: Vec3) -> Vec<RayHit> {
389 if mesh.is_empty() {
390 return vec![];
391 }
392 let dir = endpoint - origin;
393 if dot(dir, dir) == 0.0 {
394 return vec![];
395 }
396
397 let mut ray_impl = ManifoldImpl::new();
400 ray_impl.vert_pos = vec![origin, endpoint];
401 ray_impl.vert_normal = vec![Vec3::splat(0.0), Vec3::splat(0.0)];
402 ray_impl.halfedge = vec![
403 Halfedge { start_vert: 0, end_vert: 1, paired_halfedge: 1, prop_vert: 0 },
404 Halfedge { start_vert: 1, end_vert: 0, paired_halfedge: 0, prop_vert: 0 },
405 ];
406 ray_impl.face_normal = vec![Vec3::splat(0.0)];
407
408 let collider = &mesh.collider;
410
411 let ray_box = BBox::from_points(
413 Vec3::new(origin.x.min(endpoint.x), origin.y.min(endpoint.y), origin.z.min(endpoint.z)),
414 Vec3::new(origin.x.max(endpoint.x), origin.y.max(endpoint.y), origin.z.max(endpoint.z)),
415 );
416
417 let abs_dir = Vec3::new(dir.x.abs(), dir.y.abs(), dir.z.abs());
419 let t_axis = if abs_dir.x > abs_dir.y && abs_dir.x > abs_dir.z {
420 0usize
421 } else if abs_dir.y > abs_dir.z {
422 1
423 } else {
424 2
425 };
426
427 let mut hits: Vec<RayHit> = Vec::new();
428
429 collider.collisions_with_boxes(std::slice::from_ref(&ray_box), false, |_qi, tri| {
431 let (s, v) = kernel12(0, tri, &ray_impl, mesh, &ray_impl, mesh, false, true);
433 if s != 0 && v.x.is_finite() {
434 let origin_t = [origin.x, origin.y, origin.z][t_axis];
436 let dir_t = [dir.x, dir.y, dir.z][t_axis];
437 let v_t = [v.x, v.y, v.z][t_axis];
438 let t = (v_t - origin_t) / dir_t;
439 if t >= 0.0 && t <= 1.0 {
440 hits.push(RayHit {
441 face_id: tri as u64,
442 distance: t,
443 position: v,
444 normal: mesh.face_normal[tri],
445 });
446 }
447 }
448 });
449
450 hits.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap_or(std::cmp::Ordering::Equal));
451 hits
452}
453
454#[cfg(test)]
455#[path = "boolean3_tests.rs"]
456mod tests;