1use crate::error::Result;
10use crate::mesh::Mesh;
11use crate::triangulation::{calculate_polygon_normal, project_to_2d, triangulate_polygon};
12use nalgebra::{Point3, Vector3};
13use rustc_hash::FxHashMap;
14use smallvec::SmallVec;
15
16pub type TriangleVec = SmallVec<[Triangle; 4]>;
18
19#[derive(Debug, Clone, Copy)]
21pub struct Plane {
22 pub point: Point3<f64>,
24 pub normal: Vector3<f64>,
26}
27
28impl Plane {
29 pub fn new(point: Point3<f64>, normal: Vector3<f64>) -> Self {
31 Self {
32 point,
33 normal: normal.normalize(),
34 }
35 }
36
37 pub fn signed_distance(&self, point: &Point3<f64>) -> f64 {
40 (point - self.point).dot(&self.normal)
41 }
42
43 pub fn is_front(&self, point: &Point3<f64>) -> bool {
45 self.signed_distance(point) >= 0.0
46 }
47}
48
49#[derive(Debug, Clone)]
51pub enum ClipResult {
52 AllFront(Triangle),
54 AllBehind,
56 Split(TriangleVec),
58}
59
60#[derive(Debug, Clone)]
62pub struct Triangle {
63 pub v0: Point3<f64>,
64 pub v1: Point3<f64>,
65 pub v2: Point3<f64>,
66}
67
68impl Triangle {
69 #[inline]
71 pub fn new(v0: Point3<f64>, v1: Point3<f64>, v2: Point3<f64>) -> Self {
72 Self { v0, v1, v2 }
73 }
74
75 #[inline]
77 pub fn normal(&self) -> Vector3<f64> {
78 let edge1 = self.v1 - self.v0;
79 let edge2 = self.v2 - self.v0;
80 edge1.cross(&edge2).normalize()
81 }
82
83 #[inline]
90 pub fn cross_product(&self) -> Vector3<f64> {
91 let edge1 = self.v1 - self.v0;
92 let edge2 = self.v2 - self.v0;
93 edge1.cross(&edge2)
94 }
95
96 #[inline]
98 pub fn area(&self) -> f64 {
99 self.cross_product().norm() * 0.5
100 }
101
102 #[inline]
107 pub fn is_degenerate(&self, epsilon: f64) -> bool {
108 self.cross_product().try_normalize(epsilon).is_none()
109 }
110}
111
112const MAX_CSG_POLYGONS: usize = 2000;
117
118pub struct ClippingProcessor {
120 pub epsilon: f64,
122}
123
124fn aabb_to_mesh(min: Point3<f64>, max: Point3<f64>) -> Mesh {
127 let mut mesh = Mesh::with_capacity(8, 36);
128
129 let v0 = Point3::new(min.x, min.y, min.z); let v1 = Point3::new(max.x, min.y, min.z); let v2 = Point3::new(max.x, max.y, min.z); let v3 = Point3::new(min.x, max.y, min.z); let v4 = Point3::new(min.x, min.y, max.z); let v5 = Point3::new(max.x, min.y, max.z); let v6 = Point3::new(max.x, max.y, max.z); let v7 = Point3::new(min.x, max.y, max.z); add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v2, v1));
142 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v3, v2));
143
144 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v5, v6));
146 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v6, v7));
147
148 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v4, v7));
150 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v7, v3));
151
152 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v2, v6));
154 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v6, v5));
155
156 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v1, v5));
158 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v5, v4));
159
160 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v7, v6));
162 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v6, v2));
163
164 mesh
165}
166
167impl ClippingProcessor {
168 pub fn new() -> Self {
170 Self { epsilon: 1e-6 }
171 }
172
173 pub fn clip_triangle(&self, triangle: &Triangle, plane: &Plane) -> ClipResult {
176 let d0 = plane.signed_distance(&triangle.v0);
178 let d1 = plane.signed_distance(&triangle.v1);
179 let d2 = plane.signed_distance(&triangle.v2);
180
181 let mut front_count = 0;
183 if d0 >= -self.epsilon {
184 front_count += 1;
185 }
186 if d1 >= -self.epsilon {
187 front_count += 1;
188 }
189 if d2 >= -self.epsilon {
190 front_count += 1;
191 }
192
193 match front_count {
194 0 => ClipResult::AllBehind,
196
197 3 => ClipResult::AllFront(triangle.clone()),
199
200 1 => {
202 let (front, back1, back2) = if d0 >= -self.epsilon {
203 (triangle.v0, triangle.v1, triangle.v2)
204 } else if d1 >= -self.epsilon {
205 (triangle.v1, triangle.v2, triangle.v0)
206 } else {
207 (triangle.v2, triangle.v0, triangle.v1)
208 };
209
210 let d_front = if d0 >= -self.epsilon {
212 d0
213 } else if d1 >= -self.epsilon {
214 d1
215 } else {
216 d2
217 };
218 let d_back1 = if d0 >= -self.epsilon {
219 d1
220 } else if d1 >= -self.epsilon {
221 d2
222 } else {
223 d0
224 };
225 let d_back2 = if d0 >= -self.epsilon {
226 d2
227 } else if d1 >= -self.epsilon {
228 d0
229 } else {
230 d1
231 };
232
233 let t1 = d_front / (d_front - d_back1);
234 let t2 = d_front / (d_front - d_back2);
235
236 let p1 = front + (back1 - front) * t1;
237 let p2 = front + (back2 - front) * t2;
238
239 ClipResult::Split(smallvec::smallvec![Triangle::new(front, p1, p2)])
240 }
241
242 2 => {
244 let (front1, front2, back) = if d0 < -self.epsilon {
245 (triangle.v1, triangle.v2, triangle.v0)
246 } else if d1 < -self.epsilon {
247 (triangle.v2, triangle.v0, triangle.v1)
248 } else {
249 (triangle.v0, triangle.v1, triangle.v2)
250 };
251
252 let d_back = if d0 < -self.epsilon {
254 d0
255 } else if d1 < -self.epsilon {
256 d1
257 } else {
258 d2
259 };
260 let d_front1 = if d0 < -self.epsilon {
261 d1
262 } else if d1 < -self.epsilon {
263 d2
264 } else {
265 d0
266 };
267 let d_front2 = if d0 < -self.epsilon {
268 d2
269 } else if d1 < -self.epsilon {
270 d0
271 } else {
272 d1
273 };
274
275 let t1 = d_front1 / (d_front1 - d_back);
276 let t2 = d_front2 / (d_front2 - d_back);
277
278 let p1 = front1 + (back - front1) * t1;
279 let p2 = front2 + (back - front2) * t2;
280
281 ClipResult::Split(smallvec::smallvec![
282 Triangle::new(front1, front2, p1),
283 Triangle::new(front2, p2, p1),
284 ])
285 }
286
287 _ => unreachable!(),
288 }
289 }
290
291 pub fn subtract_box(&self, mesh: &Mesh, min: Point3<f64>, max: Point3<f64>) -> Result<Mesh> {
294 if mesh.is_empty() {
296 return Ok(Mesh::new());
297 }
298
299 let box_mesh = aabb_to_mesh(min, max);
301
302 self.subtract_mesh(mesh, &box_mesh)
304 }
305
306 #[allow(dead_code)]
310 fn extract_opening_profile(
311 &self,
312 opening_mesh: &Mesh,
313 ) -> Option<(Vec<Point3<f64>>, Vector3<f64>)> {
314 if opening_mesh.is_empty() {
315 return None;
316 }
317
318 let mut face_groups: FxHashMap<u64, Vec<(Point3<f64>, Point3<f64>, Point3<f64>)>> =
320 FxHashMap::default();
321 let normal_epsilon = 0.01; let vertex_count = opening_mesh.positions.len() / 3;
324 for i in (0..opening_mesh.indices.len()).step_by(3) {
325 if i + 2 >= opening_mesh.indices.len() {
326 break;
327 }
328 let i0 = opening_mesh.indices[i] as usize;
329 let i1 = opening_mesh.indices[i + 1] as usize;
330 let i2 = opening_mesh.indices[i + 2] as usize;
331
332 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
334 continue;
335 }
336
337 let v0 = Point3::new(
338 opening_mesh.positions[i0 * 3] as f64,
339 opening_mesh.positions[i0 * 3 + 1] as f64,
340 opening_mesh.positions[i0 * 3 + 2] as f64,
341 );
342 let v1 = Point3::new(
343 opening_mesh.positions[i1 * 3] as f64,
344 opening_mesh.positions[i1 * 3 + 1] as f64,
345 opening_mesh.positions[i1 * 3 + 2] as f64,
346 );
347 let v2 = Point3::new(
348 opening_mesh.positions[i2 * 3] as f64,
349 opening_mesh.positions[i2 * 3 + 1] as f64,
350 opening_mesh.positions[i2 * 3 + 2] as f64,
351 );
352
353 let edge1 = v1 - v0;
354 let edge2 = v2 - v0;
355 let normal = match edge1.cross(&edge2).try_normalize(1e-10) {
357 Some(n) => n,
358 None => continue, };
360
361 let nx = (normal.x / normal_epsilon).round() as i32;
363 let ny = (normal.y / normal_epsilon).round() as i32;
364 let nz = (normal.z / normal_epsilon).round() as i32;
365 let key = ((nx as u64) << 32) | ((ny as u32 as u64) << 16) | (nz as u32 as u64);
366
367 face_groups.entry(key).or_default().push((v0, v1, v2));
368 }
369
370 let largest_face = face_groups
372 .iter()
373 .max_by_key(|(_, triangles)| triangles.len())?;
374
375 let triangles = largest_face.1;
376 if triangles.is_empty() {
377 return None;
378 }
379
380 let quantize = |p: &Point3<f64>| -> (i64, i64, i64) {
384 let scale = 1e6; (
386 (p.x * scale).round() as i64,
387 (p.y * scale).round() as i64,
388 (p.z * scale).round() as i64,
389 )
390 };
391
392 let make_edge_key =
394 |a: (i64, i64, i64), b: (i64, i64, i64)| -> ((i64, i64, i64), (i64, i64, i64)) {
395 if a < b {
396 (a, b)
397 } else {
398 (b, a)
399 }
400 };
401
402 let mut edge_count: FxHashMap<
404 ((i64, i64, i64), (i64, i64, i64)),
405 (usize, Point3<f64>, Point3<f64>),
406 > = FxHashMap::default();
407
408 for (v0, v1, v2) in triangles {
409 let q0 = quantize(v0);
410 let q1 = quantize(v1);
411 let q2 = quantize(v2);
412
413 for (qa, qb, pa, pb) in [
415 (q0, q1, *v0, *v1),
416 (q1, q2, *v1, *v2),
417 (q2, q0, *v2, *v0),
418 ] {
419 let key = make_edge_key(qa, qb);
420 edge_count
421 .entry(key)
422 .and_modify(|(count, _, _)| *count += 1)
423 .or_insert((1, pa, pb));
424 }
425 }
426
427 let mut boundary_edges: Vec<(Point3<f64>, Point3<f64>)> = Vec::new();
429 for (_, (count, pa, pb)) in &edge_count {
430 if *count == 1 {
431 boundary_edges.push((*pa, *pb));
432 }
433 }
434
435 if boundary_edges.is_empty() {
436 return None;
438 }
439
440 let mut adjacency: FxHashMap<(i64, i64, i64), Vec<(i64, i64, i64, Point3<f64>)>> =
442 FxHashMap::default();
443 for (pa, pb) in &boundary_edges {
444 let qa = quantize(pa);
445 let qb = quantize(pb);
446 adjacency.entry(qa).or_default().push((qb.0, qb.1, qb.2, *pb));
447 adjacency.entry(qb).or_default().push((qa.0, qa.1, qa.2, *pa));
448 }
449
450 let mut contour: Vec<Point3<f64>> = Vec::new();
452 let mut visited: FxHashMap<(i64, i64, i64), bool> = FxHashMap::default();
453
454 if let Some((start_p, _)) = boundary_edges.first() {
456 let start_q = quantize(start_p);
457 contour.push(*start_p);
458 visited.insert(start_q, true);
459
460 let mut current_q = start_q;
461
462 loop {
464 let neighbors = match adjacency.get(¤t_q) {
465 Some(n) => n,
466 None => break,
467 };
468
469 let mut found_next = false;
471 for (nqx, nqy, nqz, np) in neighbors {
472 let nq = (*nqx, *nqy, *nqz);
473 if !visited.get(&nq).unwrap_or(&false) {
474 contour.push(*np);
475 visited.insert(nq, true);
476 current_q = nq;
477 found_next = true;
478 break;
479 }
480 }
481
482 if !found_next {
483 break; }
485 }
486 }
487
488 if contour.len() < 3 {
489 return None;
491 }
492
493 let normal = calculate_polygon_normal(&contour);
495
496 let normalized_normal = match normal.try_normalize(1e-10) {
498 Some(n) => n,
499 None => return None, };
501
502 Some((contour, normalized_normal))
503 }
504
505 fn mesh_to_csgrs(mesh: &Mesh) -> Result<csgrs::mesh::Mesh<()>> {
507 use csgrs::mesh::{polygon::Polygon, vertex::Vertex, Mesh as CSGMesh};
508 use std::sync::OnceLock;
509
510 if mesh.is_empty() {
511 return Ok(CSGMesh {
512 polygons: Vec::new(),
513 bounding_box: OnceLock::new(),
514 metadata: None,
515 });
516 }
517
518 if mesh.indices.len() < 3 {
520 return Ok(CSGMesh {
521 polygons: Vec::new(),
522 bounding_box: OnceLock::new(),
523 metadata: None,
524 });
525 }
526
527 let vertex_count = mesh.positions.len() / 3;
528 let triangle_count = mesh.indices.len() / 3;
529
530 let mut polygons = Vec::with_capacity(triangle_count);
532
533 for chunk in mesh.indices.chunks_exact(3) {
536 let i0 = chunk[0] as usize;
537 let i1 = chunk[1] as usize;
538 let i2 = chunk[2] as usize;
539
540 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
542 continue;
543 }
544
545 let p0_idx = i0 * 3;
548 let p1_idx = i1 * 3;
549 let p2_idx = i2 * 3;
550
551 let v0 = Point3::new(
552 mesh.positions[p0_idx] as f64,
553 mesh.positions[p0_idx + 1] as f64,
554 mesh.positions[p0_idx + 2] as f64,
555 );
556 let v1 = Point3::new(
557 mesh.positions[p1_idx] as f64,
558 mesh.positions[p1_idx + 1] as f64,
559 mesh.positions[p1_idx + 2] as f64,
560 );
561 let v2 = Point3::new(
562 mesh.positions[p2_idx] as f64,
563 mesh.positions[p2_idx + 1] as f64,
564 mesh.positions[p2_idx + 2] as f64,
565 );
566
567 if !v0.x.is_finite()
569 || !v0.y.is_finite()
570 || !v0.z.is_finite()
571 || !v1.x.is_finite()
572 || !v1.y.is_finite()
573 || !v1.z.is_finite()
574 || !v2.x.is_finite()
575 || !v2.y.is_finite()
576 || !v2.z.is_finite()
577 {
578 continue;
579 }
580
581 let edge1 = v1 - v0;
584 let edge2 = v2 - v0;
585 let face_normal = match edge1.cross(&edge2).try_normalize(1e-10) {
586 Some(n) => n,
587 None => continue, };
589 let vertices = vec![
593 Vertex::new(v0, face_normal),
594 Vertex::new(v1, face_normal),
595 Vertex::new(v2, face_normal),
596 ];
597
598 polygons.push(Polygon::new(vertices, None));
599 }
600
601 Ok(CSGMesh::from_polygons(&polygons, None))
602 }
603
604 fn csgrs_to_mesh(csg_mesh: &csgrs::mesh::Mesh<()>) -> Result<Mesh> {
606 let mut mesh = Mesh::new();
607
608 for polygon in &csg_mesh.polygons {
609 let vertices = &polygon.vertices;
610 if vertices.len() < 3 {
611 continue;
612 }
613
614 let points_3d: Vec<Point3<f64>> = vertices
616 .iter()
617 .map(|v| Point3::new(v.pos[0], v.pos[1], v.pos[2]))
618 .collect();
619
620 let raw_normal = Vector3::new(
623 vertices[0].normal[0],
624 vertices[0].normal[1],
625 vertices[0].normal[2],
626 );
627
628 let csg_normal = match raw_normal.try_normalize(1e-10) {
630 Some(n) if n.x.is_finite() && n.y.is_finite() && n.z.is_finite() => n,
631 _ => {
632 let computed = calculate_polygon_normal(&points_3d);
634 match computed.try_normalize(1e-10) {
635 Some(n) => n,
636 None => continue, }
638 }
639 };
640
641 if points_3d.len() == 3 {
643 let base_idx = mesh.vertex_count();
644 for v in vertices {
645 mesh.add_vertex(v.pos, v.normal);
646 }
647 mesh.add_triangle(
648 base_idx as u32,
649 (base_idx + 1) as u32,
650 (base_idx + 2) as u32,
651 );
652 continue;
653 }
654
655 let (points_2d, _, _, _) = project_to_2d(&points_3d, &csg_normal);
657
658 let indices = match triangulate_polygon(&points_2d) {
660 Ok(idx) => idx,
661 Err(_) => continue, };
663
664 let base_idx = mesh.vertex_count();
666 for v in vertices {
667 mesh.add_vertex(v.pos, v.normal);
668 }
669
670 for tri in indices.chunks(3) {
671 if tri.len() == 3 {
672 mesh.add_triangle(
673 (base_idx + tri[0]) as u32,
674 (base_idx + tri[1]) as u32,
675 (base_idx + tri[2]) as u32,
676 );
677 }
678 }
679 }
680
681 Ok(mesh)
682 }
683
684 fn bounds_overlap(host_mesh: &Mesh, opening_mesh: &Mesh) -> bool {
686 let (host_min, host_max) = host_mesh.bounds();
687 let (open_min, open_max) = opening_mesh.bounds();
688
689 let overlap_x = open_min.x < host_max.x && open_max.x > host_min.x;
691 let overlap_y = open_min.y < host_max.y && open_max.y > host_min.y;
692 let overlap_z = open_min.z < host_max.z && open_max.z > host_min.z;
693
694 overlap_x && overlap_y && overlap_z
695 }
696
697 pub fn subtract_mesh(&self, host_mesh: &Mesh, opening_mesh: &Mesh) -> Result<Mesh> {
699 use csgrs::traits::CSG;
700
701 if host_mesh.is_empty() {
703 return Ok(Mesh::new());
704 }
705
706 if opening_mesh.is_empty() {
707 return Ok(host_mesh.clone());
708 }
709
710 if !Self::bounds_overlap(host_mesh, opening_mesh) {
712 return Ok(host_mesh.clone());
713 }
714
715 let host_csg = match Self::mesh_to_csgrs(host_mesh) {
717 Ok(csg) => csg,
718 Err(_) => return Ok(host_mesh.clone()),
719 };
720
721 let opening_csg = match Self::mesh_to_csgrs(opening_mesh) {
722 Ok(csg) => csg,
723 Err(_) => return Ok(host_mesh.clone()),
724 };
725
726 if host_csg.polygons.is_empty() || opening_csg.polygons.is_empty() {
729 return Ok(host_mesh.clone());
730 }
731
732 if host_csg.polygons.len() < 4 || opening_csg.polygons.len() < 4 {
735 return Ok(host_mesh.clone());
736 }
737
738 if host_csg.polygons.len() + opening_csg.polygons.len() > MAX_CSG_POLYGONS {
740 return Ok(host_mesh.clone());
741 }
742
743 let result_csg = host_csg.difference(&opening_csg);
745
746 if result_csg.polygons.is_empty() {
748 return Ok(host_mesh.clone());
749 }
750
751 match Self::csgrs_to_mesh(&result_csg) {
753 Ok(result) => {
754 let cleaned = Self::remove_degenerate_triangles(&result, host_mesh);
759 Ok(cleaned)
760 }
761 Err(_) => Ok(host_mesh.clone())
762 }
763 }
764
765 fn remove_degenerate_triangles(mesh: &Mesh, host_mesh: &Mesh) -> Mesh {
772 let (host_min, host_max) = host_mesh.bounds();
773
774 let host_min_x = host_min.x as f64;
776 let host_min_y = host_min.y as f64;
777 let host_min_z = host_min.z as f64;
778 let host_max_x = host_max.x as f64;
779 let host_max_y = host_max.y as f64;
780 let host_max_z = host_max.z as f64;
781
782 let host_size_x = (host_max_x - host_min_x).abs();
784 let host_size_y = (host_max_y - host_min_y).abs();
785 let host_size_z = (host_max_z - host_min_z).abs();
786 let min_dim = host_size_x.min(host_size_y).min(host_size_z);
787
788 let min_area = (min_dim * 0.001).powi(2);
791
792 let epsilon = min_dim * 0.01;
794
795 let mut cleaned = Mesh::new();
796
797 let vert_count = mesh.positions.len() / 3;
799 for i in (0..mesh.indices.len()).step_by(3) {
800 if i + 2 >= mesh.indices.len() {
801 break;
802 }
803 let i0 = mesh.indices[i] as usize;
804 let i1 = mesh.indices[i + 1] as usize;
805 let i2 = mesh.indices[i + 2] as usize;
806
807 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
809 continue;
810 }
811
812 let v0 = Point3::new(
814 mesh.positions[i0 * 3] as f64,
815 mesh.positions[i0 * 3 + 1] as f64,
816 mesh.positions[i0 * 3 + 2] as f64,
817 );
818 let v1 = Point3::new(
819 mesh.positions[i1 * 3] as f64,
820 mesh.positions[i1 * 3 + 1] as f64,
821 mesh.positions[i1 * 3 + 2] as f64,
822 );
823 let v2 = Point3::new(
824 mesh.positions[i2 * 3] as f64,
825 mesh.positions[i2 * 3 + 1] as f64,
826 mesh.positions[i2 * 3 + 2] as f64,
827 );
828
829 let edge1 = v1 - v0;
831 let edge2 = v2 - v0;
832 let cross = edge1.cross(&edge2);
833 let area = cross.norm() / 2.0;
834
835 if area < min_area {
837 continue;
838 }
839
840 let expansion = min_dim.max(1.0); let far_outside =
844 v0.x < (host_min_x - expansion) || v0.x > (host_max_x + expansion) ||
845 v0.y < (host_min_y - expansion) || v0.y > (host_max_y + expansion) ||
846 v0.z < (host_min_z - expansion) || v0.z > (host_max_z + expansion) ||
847 v1.x < (host_min_x - expansion) || v1.x > (host_max_x + expansion) ||
848 v1.y < (host_min_y - expansion) || v1.y > (host_max_y + expansion) ||
849 v1.z < (host_min_z - expansion) || v1.z > (host_max_z + expansion) ||
850 v2.x < (host_min_x - expansion) || v2.x > (host_max_x + expansion) ||
851 v2.y < (host_min_y - expansion) || v2.y > (host_max_y + expansion) ||
852 v2.z < (host_min_z - expansion) || v2.z > (host_max_z + expansion);
853
854 if far_outside {
855 continue;
856 }
857
858 let center = Point3::new(
861 (v0.x + v1.x + v2.x) / 3.0,
862 (v0.y + v1.y + v2.y) / 3.0,
863 (v0.z + v1.z + v2.z) / 3.0,
864 );
865
866 let inside_x = center.x > (host_min_x + epsilon) && center.x < (host_max_x - epsilon);
868 let inside_y = center.y > (host_min_y + epsilon) && center.y < (host_max_y - epsilon);
869 let inside_z = center.z > (host_min_z + epsilon) && center.z < (host_max_z - epsilon);
870
871 let max_area = min_dim * min_dim * 0.1; if inside_x && inside_y && inside_z && area < max_area {
875 continue;
876 }
877
878 let n0 = Vector3::new(
880 mesh.normals[i0 * 3] as f64,
881 mesh.normals[i0 * 3 + 1] as f64,
882 mesh.normals[i0 * 3 + 2] as f64,
883 );
884 let n1 = Vector3::new(
885 mesh.normals[i1 * 3] as f64,
886 mesh.normals[i1 * 3 + 1] as f64,
887 mesh.normals[i1 * 3 + 2] as f64,
888 );
889 let n2 = Vector3::new(
890 mesh.normals[i2 * 3] as f64,
891 mesh.normals[i2 * 3 + 1] as f64,
892 mesh.normals[i2 * 3 + 2] as f64,
893 );
894
895 let base_idx = cleaned.vertex_count() as u32;
897 cleaned.add_vertex(v0, n0);
898 cleaned.add_vertex(v1, n1);
899 cleaned.add_vertex(v2, n2);
900 cleaned.add_triangle(base_idx, base_idx + 1, base_idx + 2);
901 }
902
903 cleaned
904 }
905
906 #[allow(dead_code)]
912 fn remove_triangles_inside_bounds(
913 mesh: &Mesh,
914 open_min: Point3<f64>,
915 open_max: Point3<f64>,
916 ) -> Mesh {
917 let mut cleaned = Mesh::new();
918
919 let vert_count = mesh.positions.len() / 3;
921 for i in (0..mesh.indices.len()).step_by(3) {
922 if i + 2 >= mesh.indices.len() {
923 break;
924 }
925 let i0 = mesh.indices[i] as usize;
926 let i1 = mesh.indices[i + 1] as usize;
927 let i2 = mesh.indices[i + 2] as usize;
928
929 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
931 continue;
932 }
933
934 let v0 = Point3::new(
936 mesh.positions[i0 * 3] as f64,
937 mesh.positions[i0 * 3 + 1] as f64,
938 mesh.positions[i0 * 3 + 2] as f64,
939 );
940 let v1 = Point3::new(
941 mesh.positions[i1 * 3] as f64,
942 mesh.positions[i1 * 3 + 1] as f64,
943 mesh.positions[i1 * 3 + 2] as f64,
944 );
945 let v2 = Point3::new(
946 mesh.positions[i2 * 3] as f64,
947 mesh.positions[i2 * 3 + 1] as f64,
948 mesh.positions[i2 * 3 + 2] as f64,
949 );
950
951 let tri_min_x = v0.x.min(v1.x).min(v2.x);
953 let tri_max_x = v0.x.max(v1.x).max(v2.x);
954 let tri_min_y = v0.y.min(v1.y).min(v2.y);
955 let tri_max_y = v0.y.max(v1.y).max(v2.y);
956 let tri_min_z = v0.z.min(v1.z).min(v2.z);
957 let tri_max_z = v0.z.max(v1.z).max(v2.z);
958
959 if tri_min_x >= open_min.x && tri_max_x <= open_max.x &&
961 tri_min_y >= open_min.y && tri_max_y <= open_max.y &&
962 tri_min_z >= open_min.z && tri_max_z <= open_max.z {
963 continue;
965 }
966
967 let n0 = Vector3::new(
969 mesh.normals[i0 * 3] as f64,
970 mesh.normals[i0 * 3 + 1] as f64,
971 mesh.normals[i0 * 3 + 2] as f64,
972 );
973 let n1 = Vector3::new(
974 mesh.normals[i1 * 3] as f64,
975 mesh.normals[i1 * 3 + 1] as f64,
976 mesh.normals[i1 * 3 + 2] as f64,
977 );
978 let n2 = Vector3::new(
979 mesh.normals[i2 * 3] as f64,
980 mesh.normals[i2 * 3 + 1] as f64,
981 mesh.normals[i2 * 3 + 2] as f64,
982 );
983
984 let base_idx = cleaned.vertex_count() as u32;
985 cleaned.add_vertex(v0, n0);
986 cleaned.add_vertex(v1, n1);
987 cleaned.add_vertex(v2, n2);
988 cleaned.add_triangle(base_idx, base_idx + 1, base_idx + 2);
989 }
990
991 cleaned
992 }
993
994 pub fn union_mesh(&self, mesh_a: &Mesh, mesh_b: &Mesh) -> Result<Mesh> {
996 use csgrs::traits::CSG;
997
998 if mesh_a.is_empty() {
1000 return Ok(mesh_b.clone());
1001 }
1002 if mesh_b.is_empty() {
1003 return Ok(mesh_a.clone());
1004 }
1005
1006 let csg_a = Self::mesh_to_csgrs(mesh_a)?;
1008 let csg_b = Self::mesh_to_csgrs(mesh_b)?;
1009
1010 if csg_a.polygons.is_empty() || csg_b.polygons.is_empty() {
1012 let mut merged = mesh_a.clone();
1013 merged.merge(mesh_b);
1014 return Ok(merged);
1015 }
1016
1017 if csg_a.polygons.len() < 4 || csg_b.polygons.len() < 4 {
1019 let mut merged = mesh_a.clone();
1020 merged.merge(mesh_b);
1021 return Ok(merged);
1022 }
1023
1024 if csg_a.polygons.len() + csg_b.polygons.len() > MAX_CSG_POLYGONS {
1026 let mut merged = mesh_a.clone();
1027 merged.merge(mesh_b);
1028 return Ok(merged);
1029 }
1030
1031 let result_csg = csg_a.union(&csg_b);
1033
1034 Self::csgrs_to_mesh(&result_csg)
1036 }
1037
1038 pub fn intersection_mesh(&self, mesh_a: &Mesh, mesh_b: &Mesh) -> Result<Mesh> {
1042 use csgrs::traits::CSG;
1043
1044 if mesh_a.is_empty() || mesh_b.is_empty() {
1046 return Ok(Mesh::new());
1047 }
1048
1049 let csg_a = Self::mesh_to_csgrs(mesh_a)?;
1051 let csg_b = Self::mesh_to_csgrs(mesh_b)?;
1052
1053 if csg_a.polygons.is_empty() || csg_b.polygons.is_empty() {
1055 return Ok(Mesh::new());
1056 }
1057
1058 if csg_a.polygons.len() < 4 || csg_b.polygons.len() < 4 {
1060 return Ok(Mesh::new());
1061 }
1062
1063 if csg_a.polygons.len() + csg_b.polygons.len() > MAX_CSG_POLYGONS {
1065 return Ok(mesh_a.clone());
1066 }
1067
1068 let result_csg = csg_a.intersection(&csg_b);
1070
1071 Self::csgrs_to_mesh(&result_csg)
1073 }
1074
1075 pub fn union_meshes(&self, meshes: &[Mesh]) -> Result<Mesh> {
1080 if meshes.is_empty() {
1081 return Ok(Mesh::new());
1082 }
1083
1084 if meshes.len() == 1 {
1085 return Ok(meshes[0].clone());
1086 }
1087
1088 let mut result = Mesh::new();
1090 let mut found_first = false;
1091
1092 for mesh in meshes {
1093 if mesh.is_empty() {
1094 continue;
1095 }
1096
1097 if !found_first {
1098 result = mesh.clone();
1099 found_first = true;
1100 continue;
1101 }
1102
1103 result = self.union_mesh(&result, mesh)?;
1104 }
1105
1106 Ok(result)
1107 }
1108
1109 pub fn subtract_meshes_batched(&self, host: &Mesh, voids: &[Mesh]) -> Result<Mesh> {
1122 let non_empty_voids: Vec<&Mesh> = voids.iter().filter(|m| !m.is_empty()).collect();
1124
1125 if non_empty_voids.is_empty() {
1126 return Ok(host.clone());
1127 }
1128
1129 if non_empty_voids.len() == 1 {
1130 return self.subtract_mesh(host, non_empty_voids[0]);
1131 }
1132
1133 const BATCH_THRESHOLD: usize = 10;
1135
1136 if non_empty_voids.len() > BATCH_THRESHOLD {
1137 let void_refs: Vec<Mesh> = non_empty_voids.iter().map(|m| (*m).clone()).collect();
1139 let combined = self.union_meshes(&void_refs)?;
1140
1141 self.subtract_mesh(host, &combined)
1143 } else {
1144 let mut result = host.clone();
1146
1147 for void in non_empty_voids {
1148 result = self.subtract_mesh(&result, void)?;
1149 }
1150
1151 Ok(result)
1152 }
1153 }
1154
1155 pub fn subtract_meshes_with_fallback(&self, host: &Mesh, voids: &[Mesh]) -> Mesh {
1161 match self.subtract_meshes_batched(host, voids) {
1162 Ok(result) => {
1163 if result.is_empty() || !self.validate_mesh(&result) {
1165 host.clone()
1166 } else {
1167 result
1168 }
1169 }
1170 Err(_) => host.clone(),
1171 }
1172 }
1173
1174 fn validate_mesh(&self, mesh: &Mesh) -> bool {
1176 if mesh.positions.iter().any(|v| !v.is_finite()) {
1178 return false;
1179 }
1180
1181 if mesh.normals.iter().any(|v| !v.is_finite()) {
1183 return false;
1184 }
1185
1186 let vertex_count = mesh.vertex_count();
1188 for idx in &mesh.indices {
1189 if *idx as usize >= vertex_count {
1190 return false;
1191 }
1192 }
1193
1194 true
1195 }
1196
1197 #[deprecated(note = "Use subtract_box() for better performance")]
1200 pub fn clip_mesh_with_box(
1201 &self,
1202 mesh: &Mesh,
1203 min: Point3<f64>,
1204 max: Point3<f64>,
1205 ) -> Result<Mesh> {
1206 self.subtract_box(mesh, min, max)
1207 }
1208
1209 pub fn clip_mesh(&self, mesh: &Mesh, plane: &Plane) -> Result<Mesh> {
1211 let mut result = Mesh::new();
1212
1213 let vert_count = mesh.positions.len() / 3;
1215 for i in (0..mesh.indices.len()).step_by(3) {
1216 if i + 2 >= mesh.indices.len() {
1217 break;
1218 }
1219 let i0 = mesh.indices[i] as usize;
1220 let i1 = mesh.indices[i + 1] as usize;
1221 let i2 = mesh.indices[i + 2] as usize;
1222
1223 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
1225 continue;
1226 }
1227
1228 let v0 = Point3::new(
1230 mesh.positions[i0 * 3] as f64,
1231 mesh.positions[i0 * 3 + 1] as f64,
1232 mesh.positions[i0 * 3 + 2] as f64,
1233 );
1234 let v1 = Point3::new(
1235 mesh.positions[i1 * 3] as f64,
1236 mesh.positions[i1 * 3 + 1] as f64,
1237 mesh.positions[i1 * 3 + 2] as f64,
1238 );
1239 let v2 = Point3::new(
1240 mesh.positions[i2 * 3] as f64,
1241 mesh.positions[i2 * 3 + 1] as f64,
1242 mesh.positions[i2 * 3 + 2] as f64,
1243 );
1244
1245 let triangle = Triangle::new(v0, v1, v2);
1246
1247 match self.clip_triangle(&triangle, plane) {
1249 ClipResult::AllFront(tri) => {
1250 add_triangle_to_mesh(&mut result, &tri);
1252 }
1253 ClipResult::AllBehind => {
1254 }
1256 ClipResult::Split(triangles) => {
1257 for tri in triangles {
1259 add_triangle_to_mesh(&mut result, &tri);
1260 }
1261 }
1262 }
1263 }
1264
1265 Ok(result)
1266 }
1267}
1268
1269impl Default for ClippingProcessor {
1270 fn default() -> Self {
1271 Self::new()
1272 }
1273}
1274
1275fn add_triangle_to_mesh(mesh: &mut Mesh, triangle: &Triangle) {
1277 let base_idx = mesh.vertex_count() as u32;
1278
1279 let normal = triangle.normal();
1281
1282 mesh.add_vertex(triangle.v0, normal);
1284 mesh.add_vertex(triangle.v1, normal);
1285 mesh.add_vertex(triangle.v2, normal);
1286
1287 mesh.add_triangle(base_idx, base_idx + 1, base_idx + 2);
1289}
1290
1291#[inline]
1293pub fn calculate_normals(mesh: &mut Mesh) {
1294 let vertex_count = mesh.vertex_count();
1295 if vertex_count == 0 {
1296 return;
1297 }
1298
1299 let positions_len = mesh.positions.len();
1300
1301 let mut normals = vec![Vector3::zeros(); vertex_count];
1303
1304 for i in (0..mesh.indices.len()).step_by(3) {
1306 if i + 2 >= mesh.indices.len() {
1308 break;
1309 }
1310
1311 let i0 = mesh.indices[i] as usize;
1312 let i1 = mesh.indices[i + 1] as usize;
1313 let i2 = mesh.indices[i + 2] as usize;
1314
1315 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
1317 continue;
1318 }
1319 if i0 * 3 + 2 >= positions_len || i1 * 3 + 2 >= positions_len || i2 * 3 + 2 >= positions_len
1320 {
1321 continue;
1322 }
1323
1324 let v0 = Point3::new(
1326 mesh.positions[i0 * 3] as f64,
1327 mesh.positions[i0 * 3 + 1] as f64,
1328 mesh.positions[i0 * 3 + 2] as f64,
1329 );
1330 let v1 = Point3::new(
1331 mesh.positions[i1 * 3] as f64,
1332 mesh.positions[i1 * 3 + 1] as f64,
1333 mesh.positions[i1 * 3 + 2] as f64,
1334 );
1335 let v2 = Point3::new(
1336 mesh.positions[i2 * 3] as f64,
1337 mesh.positions[i2 * 3 + 1] as f64,
1338 mesh.positions[i2 * 3 + 2] as f64,
1339 );
1340
1341 let edge1 = v1 - v0;
1343 let edge2 = v2 - v0;
1344 let normal = edge1.cross(&edge2);
1345
1346 normals[i0] += normal;
1348 normals[i1] += normal;
1349 normals[i2] += normal;
1350 }
1351
1352 mesh.normals.clear();
1354 mesh.normals.reserve(vertex_count * 3);
1355
1356 for normal in normals {
1357 let normalized = normal.try_normalize(1e-6).unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0));
1358 mesh.normals.push(normalized.x as f32);
1359 mesh.normals.push(normalized.y as f32);
1360 mesh.normals.push(normalized.z as f32);
1361 }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366 use super::*;
1367
1368 #[test]
1369 fn test_plane_signed_distance() {
1370 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1371
1372 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, 5.0)), 5.0);
1373 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, -5.0)), -5.0);
1374 assert_eq!(plane.signed_distance(&Point3::new(5.0, 5.0, 0.0)), 0.0);
1375 }
1376
1377 #[test]
1378 fn test_clip_triangle_all_front() {
1379 let processor = ClippingProcessor::new();
1380 let triangle = Triangle::new(
1381 Point3::new(0.0, 0.0, 1.0),
1382 Point3::new(1.0, 0.0, 1.0),
1383 Point3::new(0.5, 1.0, 1.0),
1384 );
1385 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1386
1387 match processor.clip_triangle(&triangle, &plane) {
1388 ClipResult::AllFront(_) => {}
1389 _ => panic!("Expected AllFront"),
1390 }
1391 }
1392
1393 #[test]
1394 fn test_clip_triangle_all_behind() {
1395 let processor = ClippingProcessor::new();
1396 let triangle = Triangle::new(
1397 Point3::new(0.0, 0.0, -1.0),
1398 Point3::new(1.0, 0.0, -1.0),
1399 Point3::new(0.5, 1.0, -1.0),
1400 );
1401 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1402
1403 match processor.clip_triangle(&triangle, &plane) {
1404 ClipResult::AllBehind => {}
1405 _ => panic!("Expected AllBehind"),
1406 }
1407 }
1408
1409 #[test]
1410 fn test_clip_triangle_split_one_front() {
1411 let processor = ClippingProcessor::new();
1412 let triangle = Triangle::new(
1413 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, -1.0), Point3::new(0.5, 1.0, -1.0), );
1417 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1418
1419 match processor.clip_triangle(&triangle, &plane) {
1420 ClipResult::Split(triangles) => {
1421 assert_eq!(triangles.len(), 1);
1422 }
1423 _ => panic!("Expected Split"),
1424 }
1425 }
1426
1427 #[test]
1428 fn test_clip_triangle_split_two_front() {
1429 let processor = ClippingProcessor::new();
1430 let triangle = Triangle::new(
1431 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0), Point3::new(0.5, 1.0, -1.0), );
1435 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1436
1437 match processor.clip_triangle(&triangle, &plane) {
1438 ClipResult::Split(triangles) => {
1439 assert_eq!(triangles.len(), 2);
1440 }
1441 _ => panic!("Expected Split with 2 triangles"),
1442 }
1443 }
1444
1445 #[test]
1446 fn test_triangle_normal() {
1447 let triangle = Triangle::new(
1448 Point3::new(0.0, 0.0, 0.0),
1449 Point3::new(1.0, 0.0, 0.0),
1450 Point3::new(0.0, 1.0, 0.0),
1451 );
1452
1453 let normal = triangle.normal();
1454 assert!((normal.z - 1.0).abs() < 1e-6);
1455 }
1456
1457 #[test]
1458 fn test_triangle_area() {
1459 let triangle = Triangle::new(
1460 Point3::new(0.0, 0.0, 0.0),
1461 Point3::new(1.0, 0.0, 0.0),
1462 Point3::new(0.0, 1.0, 0.0),
1463 );
1464
1465 let area = triangle.area();
1466 assert!((area - 0.5).abs() < 1e-6);
1467 }
1468}