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_PER_MESH: usize = 24;
118const MAX_CSG_POLYGONS: usize = MAX_CSG_POLYGONS_PER_MESH * 2;
120
121pub struct ClippingProcessor {
123 pub epsilon: f64,
125}
126
127fn aabb_to_mesh(min: Point3<f64>, max: Point3<f64>) -> Mesh {
130 let mut mesh = Mesh::with_capacity(8, 36);
131
132 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));
145 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v3, v2));
146
147 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v5, v6));
149 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v6, v7));
150
151 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v4, v7));
153 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v7, v3));
154
155 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v2, v6));
157 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v6, v5));
158
159 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v1, v5));
161 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v5, v4));
162
163 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v7, v6));
165 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v6, v2));
166
167 mesh
168}
169
170impl ClippingProcessor {
171 #[inline]
172 fn can_run_csg_operation(polygons_a: usize, polygons_b: usize) -> bool {
173 if polygons_a < 4 || polygons_b < 4 {
174 return false;
175 }
176
177 if polygons_a > MAX_CSG_POLYGONS_PER_MESH || polygons_b > MAX_CSG_POLYGONS_PER_MESH {
178 return false;
179 }
180
181 polygons_a + polygons_b <= MAX_CSG_POLYGONS
182 }
183
184 pub fn new() -> Self {
186 Self { epsilon: 1e-6 }
187 }
188
189 pub fn clip_triangle(&self, triangle: &Triangle, plane: &Plane) -> ClipResult {
192 let d0 = plane.signed_distance(&triangle.v0);
194 let d1 = plane.signed_distance(&triangle.v1);
195 let d2 = plane.signed_distance(&triangle.v2);
196
197 let mut front_count = 0;
199 if d0 >= -self.epsilon {
200 front_count += 1;
201 }
202 if d1 >= -self.epsilon {
203 front_count += 1;
204 }
205 if d2 >= -self.epsilon {
206 front_count += 1;
207 }
208
209 match front_count {
210 0 => ClipResult::AllBehind,
212
213 3 => ClipResult::AllFront(triangle.clone()),
215
216 1 => {
218 let (front, back1, back2) = if d0 >= -self.epsilon {
219 (triangle.v0, triangle.v1, triangle.v2)
220 } else if d1 >= -self.epsilon {
221 (triangle.v1, triangle.v2, triangle.v0)
222 } else {
223 (triangle.v2, triangle.v0, triangle.v1)
224 };
225
226 let d_front = if d0 >= -self.epsilon {
228 d0
229 } else if d1 >= -self.epsilon {
230 d1
231 } else {
232 d2
233 };
234 let d_back1 = if d0 >= -self.epsilon {
235 d1
236 } else if d1 >= -self.epsilon {
237 d2
238 } else {
239 d0
240 };
241 let d_back2 = if d0 >= -self.epsilon {
242 d2
243 } else if d1 >= -self.epsilon {
244 d0
245 } else {
246 d1
247 };
248
249 let t1 = d_front / (d_front - d_back1);
250 let t2 = d_front / (d_front - d_back2);
251
252 let p1 = front + (back1 - front) * t1;
253 let p2 = front + (back2 - front) * t2;
254
255 ClipResult::Split(smallvec::smallvec![Triangle::new(front, p1, p2)])
256 }
257
258 2 => {
260 let (front1, front2, back) = if d0 < -self.epsilon {
261 (triangle.v1, triangle.v2, triangle.v0)
262 } else if d1 < -self.epsilon {
263 (triangle.v2, triangle.v0, triangle.v1)
264 } else {
265 (triangle.v0, triangle.v1, triangle.v2)
266 };
267
268 let d_back = if d0 < -self.epsilon {
270 d0
271 } else if d1 < -self.epsilon {
272 d1
273 } else {
274 d2
275 };
276 let d_front1 = if d0 < -self.epsilon {
277 d1
278 } else if d1 < -self.epsilon {
279 d2
280 } else {
281 d0
282 };
283 let d_front2 = if d0 < -self.epsilon {
284 d2
285 } else if d1 < -self.epsilon {
286 d0
287 } else {
288 d1
289 };
290
291 let t1 = d_front1 / (d_front1 - d_back);
292 let t2 = d_front2 / (d_front2 - d_back);
293
294 let p1 = front1 + (back - front1) * t1;
295 let p2 = front2 + (back - front2) * t2;
296
297 ClipResult::Split(smallvec::smallvec![
298 Triangle::new(front1, front2, p1),
299 Triangle::new(front2, p2, p1),
300 ])
301 }
302
303 _ => unreachable!(),
304 }
305 }
306
307 pub fn subtract_box(&self, mesh: &Mesh, min: Point3<f64>, max: Point3<f64>) -> Result<Mesh> {
310 if mesh.is_empty() {
312 return Ok(Mesh::new());
313 }
314
315 let box_mesh = aabb_to_mesh(min, max);
317
318 self.subtract_mesh(mesh, &box_mesh)
320 }
321
322 #[allow(dead_code)]
326 fn extract_opening_profile(
327 &self,
328 opening_mesh: &Mesh,
329 ) -> Option<(Vec<Point3<f64>>, Vector3<f64>)> {
330 if opening_mesh.is_empty() {
331 return None;
332 }
333
334 let mut face_groups: FxHashMap<u64, Vec<(Point3<f64>, Point3<f64>, Point3<f64>)>> =
336 FxHashMap::default();
337 let normal_epsilon = 0.01; let vertex_count = opening_mesh.positions.len() / 3;
340 for i in (0..opening_mesh.indices.len()).step_by(3) {
341 if i + 2 >= opening_mesh.indices.len() {
342 break;
343 }
344 let i0 = opening_mesh.indices[i] as usize;
345 let i1 = opening_mesh.indices[i + 1] as usize;
346 let i2 = opening_mesh.indices[i + 2] as usize;
347
348 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
350 continue;
351 }
352
353 let v0 = Point3::new(
354 opening_mesh.positions[i0 * 3] as f64,
355 opening_mesh.positions[i0 * 3 + 1] as f64,
356 opening_mesh.positions[i0 * 3 + 2] as f64,
357 );
358 let v1 = Point3::new(
359 opening_mesh.positions[i1 * 3] as f64,
360 opening_mesh.positions[i1 * 3 + 1] as f64,
361 opening_mesh.positions[i1 * 3 + 2] as f64,
362 );
363 let v2 = Point3::new(
364 opening_mesh.positions[i2 * 3] as f64,
365 opening_mesh.positions[i2 * 3 + 1] as f64,
366 opening_mesh.positions[i2 * 3 + 2] as f64,
367 );
368
369 let edge1 = v1 - v0;
370 let edge2 = v2 - v0;
371 let normal = match edge1.cross(&edge2).try_normalize(1e-10) {
373 Some(n) => n,
374 None => continue, };
376
377 let nx = (normal.x / normal_epsilon).round() as i32;
379 let ny = (normal.y / normal_epsilon).round() as i32;
380 let nz = (normal.z / normal_epsilon).round() as i32;
381 let key = ((nx as u64) << 32) | ((ny as u32 as u64) << 16) | (nz as u32 as u64);
382
383 face_groups.entry(key).or_default().push((v0, v1, v2));
384 }
385
386 let largest_face = face_groups
388 .iter()
389 .max_by_key(|(_, triangles)| triangles.len())?;
390
391 let triangles = largest_face.1;
392 if triangles.is_empty() {
393 return None;
394 }
395
396 let quantize = |p: &Point3<f64>| -> (i64, i64, i64) {
400 let scale = 1e6; (
402 (p.x * scale).round() as i64,
403 (p.y * scale).round() as i64,
404 (p.z * scale).round() as i64,
405 )
406 };
407
408 let make_edge_key =
410 |a: (i64, i64, i64), b: (i64, i64, i64)| -> ((i64, i64, i64), (i64, i64, i64)) {
411 if a < b {
412 (a, b)
413 } else {
414 (b, a)
415 }
416 };
417
418 let mut edge_count: FxHashMap<
420 ((i64, i64, i64), (i64, i64, i64)),
421 (usize, Point3<f64>, Point3<f64>),
422 > = FxHashMap::default();
423
424 for (v0, v1, v2) in triangles {
425 let q0 = quantize(v0);
426 let q1 = quantize(v1);
427 let q2 = quantize(v2);
428
429 for (qa, qb, pa, pb) in [(q0, q1, *v0, *v1), (q1, q2, *v1, *v2), (q2, q0, *v2, *v0)] {
431 let key = make_edge_key(qa, qb);
432 edge_count
433 .entry(key)
434 .and_modify(|(count, _, _)| *count += 1)
435 .or_insert((1, pa, pb));
436 }
437 }
438
439 let mut boundary_edges: Vec<(Point3<f64>, Point3<f64>)> = Vec::new();
441 for (_, (count, pa, pb)) in &edge_count {
442 if *count == 1 {
443 boundary_edges.push((*pa, *pb));
444 }
445 }
446
447 if boundary_edges.is_empty() {
448 return None;
450 }
451
452 let mut adjacency: FxHashMap<(i64, i64, i64), Vec<(i64, i64, i64, Point3<f64>)>> =
454 FxHashMap::default();
455 for (pa, pb) in &boundary_edges {
456 let qa = quantize(pa);
457 let qb = quantize(pb);
458 adjacency
459 .entry(qa)
460 .or_default()
461 .push((qb.0, qb.1, qb.2, *pb));
462 adjacency
463 .entry(qb)
464 .or_default()
465 .push((qa.0, qa.1, qa.2, *pa));
466 }
467
468 let mut contour: Vec<Point3<f64>> = Vec::new();
470 let mut visited: FxHashMap<(i64, i64, i64), bool> = FxHashMap::default();
471
472 if let Some((start_p, _)) = boundary_edges.first() {
474 let start_q = quantize(start_p);
475 contour.push(*start_p);
476 visited.insert(start_q, true);
477
478 let mut current_q = start_q;
479
480 loop {
482 let neighbors = match adjacency.get(¤t_q) {
483 Some(n) => n,
484 None => break,
485 };
486
487 let mut found_next = false;
489 for (nqx, nqy, nqz, np) in neighbors {
490 let nq = (*nqx, *nqy, *nqz);
491 if !visited.get(&nq).unwrap_or(&false) {
492 contour.push(*np);
493 visited.insert(nq, true);
494 current_q = nq;
495 found_next = true;
496 break;
497 }
498 }
499
500 if !found_next {
501 break; }
503 }
504 }
505
506 if contour.len() < 3 {
507 return None;
509 }
510
511 let normal = calculate_polygon_normal(&contour);
513
514 let normalized_normal = match normal.try_normalize(1e-10) {
516 Some(n) => n,
517 None => return None, };
519
520 Some((contour, normalized_normal))
521 }
522
523 fn mesh_to_polygons(mesh: &Mesh) -> Vec<crate::bsp_csg::Polygon> {
525 use crate::bsp_csg::{Polygon, Vertex};
526
527 if mesh.is_empty() || mesh.indices.len() < 3 {
528 return Vec::new();
529 }
530
531 let vertex_count = mesh.positions.len() / 3;
532 let triangle_count = mesh.indices.len() / 3;
533 let mut polygons = Vec::with_capacity(triangle_count);
534
535 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 {
541 continue;
542 }
543
544 let p0 = i0 * 3;
545 let p1 = i1 * 3;
546 let p2 = i2 * 3;
547
548 let v0 = [
549 mesh.positions[p0] as f64,
550 mesh.positions[p0 + 1] as f64,
551 mesh.positions[p0 + 2] as f64,
552 ];
553 let v1 = [
554 mesh.positions[p1] as f64,
555 mesh.positions[p1 + 1] as f64,
556 mesh.positions[p1 + 2] as f64,
557 ];
558 let v2 = [
559 mesh.positions[p2] as f64,
560 mesh.positions[p2 + 1] as f64,
561 mesh.positions[p2 + 2] as f64,
562 ];
563
564 if !v0.iter().all(|x| x.is_finite())
565 || !v1.iter().all(|x| x.is_finite())
566 || !v2.iter().all(|x| x.is_finite())
567 {
568 continue;
569 }
570
571 let edge1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
572 let edge2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
573 let cross = [
574 edge1[1] * edge2[2] - edge1[2] * edge2[1],
575 edge1[2] * edge2[0] - edge1[0] * edge2[2],
576 edge1[0] * edge2[1] - edge1[1] * edge2[0],
577 ];
578 let len = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
579 if len < 1e-10 {
580 continue;
581 }
582 let n = [cross[0] / len, cross[1] / len, cross[2] / len];
583
584 polygons.push(Polygon::new(vec![
585 Vertex::new(v0, n),
586 Vertex::new(v1, n),
587 Vertex::new(v2, n),
588 ]));
589 }
590
591 polygons
592 }
593
594 fn polygons_to_mesh(polygons: &[crate::bsp_csg::Polygon]) -> Result<Mesh> {
596 let mut mesh = Mesh::new();
597
598 for polygon in polygons {
599 let vertices = &polygon.vertices;
600 if vertices.len() < 3 {
601 continue;
602 }
603
604 let points_3d: Vec<Point3<f64>> = vertices
605 .iter()
606 .map(|v| Point3::new(v.pos[0], v.pos[1], v.pos[2]))
607 .collect();
608
609 let raw_normal =
610 Vector3::new(vertices[0].normal[0], vertices[0].normal[1], vertices[0].normal[2]);
611
612 let csg_normal = match raw_normal.try_normalize(1e-10) {
613 Some(n) if n.x.is_finite() && n.y.is_finite() && n.z.is_finite() => n,
614 _ => {
615 let computed = calculate_polygon_normal(&points_3d);
616 match computed.try_normalize(1e-10) {
617 Some(n) => n,
618 None => continue,
619 }
620 }
621 };
622
623 if points_3d.len() == 3 {
624 let base_idx = mesh.vertex_count();
625 for v in vertices {
626 mesh.add_vertex(
627 Point3::new(v.pos[0], v.pos[1], v.pos[2]),
628 Vector3::new(v.normal[0], v.normal[1], v.normal[2]),
629 );
630 }
631 mesh.add_triangle(
632 base_idx as u32,
633 (base_idx + 1) as u32,
634 (base_idx + 2) as u32,
635 );
636 continue;
637 }
638
639 let (points_2d, _, _, _) = project_to_2d(&points_3d, &csg_normal);
640
641 let indices = match triangulate_polygon(&points_2d) {
642 Ok(idx) => idx,
643 Err(_) => continue,
644 };
645
646 let base_idx = mesh.vertex_count();
647 for v in vertices {
648 mesh.add_vertex(
649 Point3::new(v.pos[0], v.pos[1], v.pos[2]),
650 Vector3::new(v.normal[0], v.normal[1], v.normal[2]),
651 );
652 }
653
654 for tri in indices.chunks(3) {
655 if tri.len() == 3 {
656 mesh.add_triangle(
657 (base_idx + tri[0]) as u32,
658 (base_idx + tri[1]) as u32,
659 (base_idx + tri[2]) as u32,
660 );
661 }
662 }
663 }
664
665 Ok(mesh)
666 }
667
668 fn bounds_overlap(host_mesh: &Mesh, opening_mesh: &Mesh) -> bool {
670 let (host_min, host_max) = host_mesh.bounds();
671 let (open_min, open_max) = opening_mesh.bounds();
672
673 let overlap_x = open_min.x < host_max.x && open_max.x > host_min.x;
675 let overlap_y = open_min.y < host_max.y && open_max.y > host_min.y;
676 let overlap_z = open_min.z < host_max.z && open_max.z > host_min.z;
677
678 overlap_x && overlap_y && overlap_z
679 }
680
681 pub fn subtract_mesh(&self, host_mesh: &Mesh, opening_mesh: &Mesh) -> Result<Mesh> {
683 if host_mesh.is_empty() {
684 return Ok(Mesh::new());
685 }
686 if opening_mesh.is_empty() {
687 return Ok(host_mesh.clone());
688 }
689 if !Self::bounds_overlap(host_mesh, opening_mesh) {
690 return Ok(host_mesh.clone());
691 }
692
693 let host_polys = Self::mesh_to_polygons(host_mesh);
694 let opening_polys = Self::mesh_to_polygons(opening_mesh);
695
696 if host_polys.is_empty() || opening_polys.is_empty() {
697 return Ok(host_mesh.clone());
698 }
699
700 if !Self::can_run_csg_operation(host_polys.len(), opening_polys.len()) {
701 return Ok(host_mesh.clone());
702 }
703
704 let result_polys = crate::bsp_csg::difference(host_polys, opening_polys);
705
706 match Self::polygons_to_mesh(&result_polys) {
707 Ok(result) => {
708 let cleaned = Self::remove_degenerate_triangles(&result, host_mesh);
709 Ok(cleaned)
710 }
711 Err(_) => Ok(host_mesh.clone()),
712 }
713 }
714
715 fn remove_degenerate_triangles(mesh: &Mesh, host_mesh: &Mesh) -> Mesh {
722 let (host_min, host_max) = host_mesh.bounds();
723
724 let host_min_x = host_min.x as f64;
726 let host_min_y = host_min.y as f64;
727 let host_min_z = host_min.z as f64;
728 let host_max_x = host_max.x as f64;
729 let host_max_y = host_max.y as f64;
730 let host_max_z = host_max.z as f64;
731
732 let host_size_x = (host_max_x - host_min_x).abs();
734 let host_size_y = (host_max_y - host_min_y).abs();
735 let host_size_z = (host_max_z - host_min_z).abs();
736 let min_dim = host_size_x.min(host_size_y).min(host_size_z);
737
738 let min_area = (min_dim * 0.001).powi(2);
741
742 let epsilon = min_dim * 0.01;
744
745 let mut cleaned = Mesh::new();
746
747 let vert_count = mesh.positions.len() / 3;
749 for i in (0..mesh.indices.len()).step_by(3) {
750 if i + 2 >= mesh.indices.len() {
751 break;
752 }
753 let i0 = mesh.indices[i] as usize;
754 let i1 = mesh.indices[i + 1] as usize;
755 let i2 = mesh.indices[i + 2] as usize;
756
757 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
759 continue;
760 }
761
762 let v0 = Point3::new(
764 mesh.positions[i0 * 3] as f64,
765 mesh.positions[i0 * 3 + 1] as f64,
766 mesh.positions[i0 * 3 + 2] as f64,
767 );
768 let v1 = Point3::new(
769 mesh.positions[i1 * 3] as f64,
770 mesh.positions[i1 * 3 + 1] as f64,
771 mesh.positions[i1 * 3 + 2] as f64,
772 );
773 let v2 = Point3::new(
774 mesh.positions[i2 * 3] as f64,
775 mesh.positions[i2 * 3 + 1] as f64,
776 mesh.positions[i2 * 3 + 2] as f64,
777 );
778
779 let edge1 = v1 - v0;
781 let edge2 = v2 - v0;
782 let cross = edge1.cross(&edge2);
783 let area = cross.norm() / 2.0;
784
785 if area < min_area {
787 continue;
788 }
789
790 let expansion = min_dim.max(1.0); let far_outside = v0.x < (host_min_x - expansion)
794 || v0.x > (host_max_x + expansion)
795 || v0.y < (host_min_y - expansion)
796 || v0.y > (host_max_y + expansion)
797 || v0.z < (host_min_z - expansion)
798 || v0.z > (host_max_z + expansion)
799 || v1.x < (host_min_x - expansion)
800 || v1.x > (host_max_x + expansion)
801 || v1.y < (host_min_y - expansion)
802 || v1.y > (host_max_y + expansion)
803 || v1.z < (host_min_z - expansion)
804 || v1.z > (host_max_z + expansion)
805 || v2.x < (host_min_x - expansion)
806 || v2.x > (host_max_x + expansion)
807 || v2.y < (host_min_y - expansion)
808 || v2.y > (host_max_y + expansion)
809 || v2.z < (host_min_z - expansion)
810 || v2.z > (host_max_z + expansion);
811
812 if far_outside {
813 continue;
814 }
815
816 let center = Point3::new(
819 (v0.x + v1.x + v2.x) / 3.0,
820 (v0.y + v1.y + v2.y) / 3.0,
821 (v0.z + v1.z + v2.z) / 3.0,
822 );
823
824 let inside_x = center.x > (host_min_x + epsilon) && center.x < (host_max_x - epsilon);
826 let inside_y = center.y > (host_min_y + epsilon) && center.y < (host_max_y - epsilon);
827 let inside_z = center.z > (host_min_z + epsilon) && center.z < (host_max_z - epsilon);
828
829 let max_area = min_dim * min_dim * 0.1; if inside_x && inside_y && inside_z && area < max_area {
833 continue;
834 }
835
836 let n0 = Vector3::new(
838 mesh.normals[i0 * 3] as f64,
839 mesh.normals[i0 * 3 + 1] as f64,
840 mesh.normals[i0 * 3 + 2] as f64,
841 );
842 let n1 = Vector3::new(
843 mesh.normals[i1 * 3] as f64,
844 mesh.normals[i1 * 3 + 1] as f64,
845 mesh.normals[i1 * 3 + 2] as f64,
846 );
847 let n2 = Vector3::new(
848 mesh.normals[i2 * 3] as f64,
849 mesh.normals[i2 * 3 + 1] as f64,
850 mesh.normals[i2 * 3 + 2] as f64,
851 );
852
853 let base_idx = cleaned.vertex_count() as u32;
855 cleaned.add_vertex(v0, n0);
856 cleaned.add_vertex(v1, n1);
857 cleaned.add_vertex(v2, n2);
858 cleaned.add_triangle(base_idx, base_idx + 1, base_idx + 2);
859 }
860
861 cleaned
862 }
863
864 #[allow(dead_code)]
870 fn remove_triangles_inside_bounds(
871 mesh: &Mesh,
872 open_min: Point3<f64>,
873 open_max: Point3<f64>,
874 ) -> Mesh {
875 let mut cleaned = Mesh::new();
876
877 let vert_count = mesh.positions.len() / 3;
879 for i in (0..mesh.indices.len()).step_by(3) {
880 if i + 2 >= mesh.indices.len() {
881 break;
882 }
883 let i0 = mesh.indices[i] as usize;
884 let i1 = mesh.indices[i + 1] as usize;
885 let i2 = mesh.indices[i + 2] as usize;
886
887 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
889 continue;
890 }
891
892 let v0 = Point3::new(
894 mesh.positions[i0 * 3] as f64,
895 mesh.positions[i0 * 3 + 1] as f64,
896 mesh.positions[i0 * 3 + 2] as f64,
897 );
898 let v1 = Point3::new(
899 mesh.positions[i1 * 3] as f64,
900 mesh.positions[i1 * 3 + 1] as f64,
901 mesh.positions[i1 * 3 + 2] as f64,
902 );
903 let v2 = Point3::new(
904 mesh.positions[i2 * 3] as f64,
905 mesh.positions[i2 * 3 + 1] as f64,
906 mesh.positions[i2 * 3 + 2] as f64,
907 );
908
909 let tri_min_x = v0.x.min(v1.x).min(v2.x);
911 let tri_max_x = v0.x.max(v1.x).max(v2.x);
912 let tri_min_y = v0.y.min(v1.y).min(v2.y);
913 let tri_max_y = v0.y.max(v1.y).max(v2.y);
914 let tri_min_z = v0.z.min(v1.z).min(v2.z);
915 let tri_max_z = v0.z.max(v1.z).max(v2.z);
916
917 if tri_min_x >= open_min.x
919 && tri_max_x <= open_max.x
920 && tri_min_y >= open_min.y
921 && tri_max_y <= open_max.y
922 && tri_min_z >= open_min.z
923 && tri_max_z <= open_max.z
924 {
925 continue;
927 }
928
929 let n0 = Vector3::new(
931 mesh.normals[i0 * 3] as f64,
932 mesh.normals[i0 * 3 + 1] as f64,
933 mesh.normals[i0 * 3 + 2] as f64,
934 );
935 let n1 = Vector3::new(
936 mesh.normals[i1 * 3] as f64,
937 mesh.normals[i1 * 3 + 1] as f64,
938 mesh.normals[i1 * 3 + 2] as f64,
939 );
940 let n2 = Vector3::new(
941 mesh.normals[i2 * 3] as f64,
942 mesh.normals[i2 * 3 + 1] as f64,
943 mesh.normals[i2 * 3 + 2] as f64,
944 );
945
946 let base_idx = cleaned.vertex_count() as u32;
947 cleaned.add_vertex(v0, n0);
948 cleaned.add_vertex(v1, n1);
949 cleaned.add_vertex(v2, n2);
950 cleaned.add_triangle(base_idx, base_idx + 1, base_idx + 2);
951 }
952
953 cleaned
954 }
955
956 pub fn union_mesh(&self, mesh_a: &Mesh, mesh_b: &Mesh) -> Result<Mesh> {
958 if mesh_a.is_empty() {
959 return Ok(mesh_b.clone());
960 }
961 if mesh_b.is_empty() {
962 return Ok(mesh_a.clone());
963 }
964
965 let polys_a = Self::mesh_to_polygons(mesh_a);
966 let polys_b = Self::mesh_to_polygons(mesh_b);
967
968 if polys_a.is_empty() || polys_b.is_empty() {
969 let mut merged = mesh_a.clone();
970 merged.merge(mesh_b);
971 return Ok(merged);
972 }
973
974 if !Self::can_run_csg_operation(polys_a.len(), polys_b.len()) {
975 let mut merged = mesh_a.clone();
976 merged.merge(mesh_b);
977 return Ok(merged);
978 }
979
980 let result_polys = crate::bsp_csg::union(polys_a, polys_b);
981 Self::polygons_to_mesh(&result_polys)
982 }
983
984 pub fn intersection_mesh(&self, mesh_a: &Mesh, mesh_b: &Mesh) -> Result<Mesh> {
988 if mesh_a.is_empty() || mesh_b.is_empty() {
989 return Ok(Mesh::new());
990 }
991
992 let polys_a = Self::mesh_to_polygons(mesh_a);
993 let polys_b = Self::mesh_to_polygons(mesh_b);
994
995 if polys_a.is_empty() || polys_b.is_empty() {
996 return Ok(Mesh::new());
997 }
998
999 if !Self::can_run_csg_operation(polys_a.len(), polys_b.len()) {
1000 return Ok(Mesh::new());
1001 }
1002
1003 let result_polys = crate::bsp_csg::intersection(polys_a, polys_b);
1004 Self::polygons_to_mesh(&result_polys)
1005 }
1006
1007 pub fn union_meshes(&self, meshes: &[Mesh]) -> Result<Mesh> {
1012 if meshes.is_empty() {
1013 return Ok(Mesh::new());
1014 }
1015
1016 if meshes.len() == 1 {
1017 return Ok(meshes[0].clone());
1018 }
1019
1020 let mut result = Mesh::new();
1022 let mut found_first = false;
1023
1024 for mesh in meshes {
1025 if mesh.is_empty() {
1026 continue;
1027 }
1028
1029 if !found_first {
1030 result = mesh.clone();
1031 found_first = true;
1032 continue;
1033 }
1034
1035 result = self.union_mesh(&result, mesh)?;
1036 }
1037
1038 Ok(result)
1039 }
1040
1041 pub fn subtract_meshes_batched(&self, host: &Mesh, voids: &[Mesh]) -> Result<Mesh> {
1054 let non_empty_voids: Vec<&Mesh> = voids.iter().filter(|m| !m.is_empty()).collect();
1056
1057 if non_empty_voids.is_empty() {
1058 return Ok(host.clone());
1059 }
1060
1061 if non_empty_voids.len() == 1 {
1062 return self.subtract_mesh(host, non_empty_voids[0]);
1063 }
1064
1065 const BATCH_THRESHOLD: usize = 10;
1067
1068 if non_empty_voids.len() > BATCH_THRESHOLD {
1069 let void_refs: Vec<Mesh> = non_empty_voids.iter().map(|m| (*m).clone()).collect();
1071 let combined = self.union_meshes(&void_refs)?;
1072
1073 self.subtract_mesh(host, &combined)
1075 } else {
1076 let mut result = host.clone();
1078
1079 for void in non_empty_voids {
1080 result = self.subtract_mesh(&result, void)?;
1081 }
1082
1083 Ok(result)
1084 }
1085 }
1086
1087 pub fn subtract_meshes_with_fallback(&self, host: &Mesh, voids: &[Mesh]) -> Mesh {
1093 match self.subtract_meshes_batched(host, voids) {
1094 Ok(result) => {
1095 if result.is_empty() || !self.validate_mesh(&result) {
1097 host.clone()
1098 } else {
1099 result
1100 }
1101 }
1102 Err(_) => host.clone(),
1103 }
1104 }
1105
1106 fn validate_mesh(&self, mesh: &Mesh) -> bool {
1108 if mesh.positions.iter().any(|v| !v.is_finite()) {
1110 return false;
1111 }
1112
1113 if mesh.normals.iter().any(|v| !v.is_finite()) {
1115 return false;
1116 }
1117
1118 let vertex_count = mesh.vertex_count();
1120 for idx in &mesh.indices {
1121 if *idx as usize >= vertex_count {
1122 return false;
1123 }
1124 }
1125
1126 true
1127 }
1128
1129 #[deprecated(note = "Use subtract_box() for better performance")]
1132 pub fn clip_mesh_with_box(
1133 &self,
1134 mesh: &Mesh,
1135 min: Point3<f64>,
1136 max: Point3<f64>,
1137 ) -> Result<Mesh> {
1138 self.subtract_box(mesh, min, max)
1139 }
1140
1141 pub fn clip_mesh(&self, mesh: &Mesh, plane: &Plane) -> Result<Mesh> {
1143 let mut result = Mesh::new();
1144
1145 let vert_count = mesh.positions.len() / 3;
1147 for i in (0..mesh.indices.len()).step_by(3) {
1148 if i + 2 >= mesh.indices.len() {
1149 break;
1150 }
1151 let i0 = mesh.indices[i] as usize;
1152 let i1 = mesh.indices[i + 1] as usize;
1153 let i2 = mesh.indices[i + 2] as usize;
1154
1155 if i0 >= vert_count || i1 >= vert_count || i2 >= vert_count {
1157 continue;
1158 }
1159
1160 let v0 = Point3::new(
1162 mesh.positions[i0 * 3] as f64,
1163 mesh.positions[i0 * 3 + 1] as f64,
1164 mesh.positions[i0 * 3 + 2] as f64,
1165 );
1166 let v1 = Point3::new(
1167 mesh.positions[i1 * 3] as f64,
1168 mesh.positions[i1 * 3 + 1] as f64,
1169 mesh.positions[i1 * 3 + 2] as f64,
1170 );
1171 let v2 = Point3::new(
1172 mesh.positions[i2 * 3] as f64,
1173 mesh.positions[i2 * 3 + 1] as f64,
1174 mesh.positions[i2 * 3 + 2] as f64,
1175 );
1176
1177 let triangle = Triangle::new(v0, v1, v2);
1178
1179 match self.clip_triangle(&triangle, plane) {
1181 ClipResult::AllFront(tri) => {
1182 add_triangle_to_mesh(&mut result, &tri);
1184 }
1185 ClipResult::AllBehind => {
1186 }
1188 ClipResult::Split(triangles) => {
1189 for tri in triangles {
1191 add_triangle_to_mesh(&mut result, &tri);
1192 }
1193 }
1194 }
1195 }
1196
1197 Ok(result)
1198 }
1199}
1200
1201impl Default for ClippingProcessor {
1202 fn default() -> Self {
1203 Self::new()
1204 }
1205}
1206
1207fn add_triangle_to_mesh(mesh: &mut Mesh, triangle: &Triangle) {
1209 let base_idx = mesh.vertex_count() as u32;
1210
1211 let normal = triangle.normal();
1213
1214 mesh.add_vertex(triangle.v0, normal);
1216 mesh.add_vertex(triangle.v1, normal);
1217 mesh.add_vertex(triangle.v2, normal);
1218
1219 mesh.add_triangle(base_idx, base_idx + 1, base_idx + 2);
1221}
1222
1223#[cfg(not(target_arch = "wasm32"))]
1228#[inline]
1229pub fn calculate_normals(_mesh: &mut Mesh) {}
1230
1231#[cfg(target_arch = "wasm32")]
1232#[inline]
1233pub fn calculate_normals(mesh: &mut Mesh) {
1234 let vertex_count = mesh.vertex_count();
1235 if vertex_count == 0 {
1236 return;
1237 }
1238
1239 let positions_len = mesh.positions.len();
1240
1241 let mut normals = vec![Vector3::zeros(); vertex_count];
1243
1244 for i in (0..mesh.indices.len()).step_by(3) {
1246 if i + 2 >= mesh.indices.len() {
1248 break;
1249 }
1250
1251 let i0 = mesh.indices[i] as usize;
1252 let i1 = mesh.indices[i + 1] as usize;
1253 let i2 = mesh.indices[i + 2] as usize;
1254
1255 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
1257 continue;
1258 }
1259 if i0 * 3 + 2 >= positions_len || i1 * 3 + 2 >= positions_len || i2 * 3 + 2 >= positions_len
1260 {
1261 continue;
1262 }
1263
1264 let v0 = Point3::new(
1266 mesh.positions[i0 * 3] as f64,
1267 mesh.positions[i0 * 3 + 1] as f64,
1268 mesh.positions[i0 * 3 + 2] as f64,
1269 );
1270 let v1 = Point3::new(
1271 mesh.positions[i1 * 3] as f64,
1272 mesh.positions[i1 * 3 + 1] as f64,
1273 mesh.positions[i1 * 3 + 2] as f64,
1274 );
1275 let v2 = Point3::new(
1276 mesh.positions[i2 * 3] as f64,
1277 mesh.positions[i2 * 3 + 1] as f64,
1278 mesh.positions[i2 * 3 + 2] as f64,
1279 );
1280
1281 let edge1 = v1 - v0;
1283 let edge2 = v2 - v0;
1284 let normal = edge1.cross(&edge2);
1285
1286 normals[i0] += normal;
1288 normals[i1] += normal;
1289 normals[i2] += normal;
1290 }
1291
1292 mesh.normals.clear();
1294 mesh.normals.reserve(vertex_count * 3);
1295
1296 for normal in normals {
1297 let normalized = normal
1298 .try_normalize(1e-6)
1299 .unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0));
1300 mesh.normals.push(normalized.x as f32);
1301 mesh.normals.push(normalized.y as f32);
1302 mesh.normals.push(normalized.z as f32);
1303 }
1304}
1305
1306#[cfg(test)]
1307mod tests {
1308 use super::*;
1309
1310 #[test]
1311 fn test_plane_signed_distance() {
1312 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1313
1314 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, 5.0)), 5.0);
1315 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, -5.0)), -5.0);
1316 assert_eq!(plane.signed_distance(&Point3::new(5.0, 5.0, 0.0)), 0.0);
1317 }
1318
1319 #[test]
1320 fn test_clip_triangle_all_front() {
1321 let processor = ClippingProcessor::new();
1322 let triangle = Triangle::new(
1323 Point3::new(0.0, 0.0, 1.0),
1324 Point3::new(1.0, 0.0, 1.0),
1325 Point3::new(0.5, 1.0, 1.0),
1326 );
1327 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1328
1329 match processor.clip_triangle(&triangle, &plane) {
1330 ClipResult::AllFront(_) => {}
1331 _ => panic!("Expected AllFront"),
1332 }
1333 }
1334
1335 #[test]
1336 fn test_clip_triangle_all_behind() {
1337 let processor = ClippingProcessor::new();
1338 let triangle = Triangle::new(
1339 Point3::new(0.0, 0.0, -1.0),
1340 Point3::new(1.0, 0.0, -1.0),
1341 Point3::new(0.5, 1.0, -1.0),
1342 );
1343 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1344
1345 match processor.clip_triangle(&triangle, &plane) {
1346 ClipResult::AllBehind => {}
1347 _ => panic!("Expected AllBehind"),
1348 }
1349 }
1350
1351 #[test]
1352 fn test_clip_triangle_split_one_front() {
1353 let processor = ClippingProcessor::new();
1354 let triangle = Triangle::new(
1355 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, -1.0), Point3::new(0.5, 1.0, -1.0), );
1359 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1360
1361 match processor.clip_triangle(&triangle, &plane) {
1362 ClipResult::Split(triangles) => {
1363 assert_eq!(triangles.len(), 1);
1364 }
1365 _ => panic!("Expected Split"),
1366 }
1367 }
1368
1369 #[test]
1370 fn test_clip_triangle_split_two_front() {
1371 let processor = ClippingProcessor::new();
1372 let triangle = Triangle::new(
1373 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0), Point3::new(0.5, 1.0, -1.0), );
1377 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
1378
1379 match processor.clip_triangle(&triangle, &plane) {
1380 ClipResult::Split(triangles) => {
1381 assert_eq!(triangles.len(), 2);
1382 }
1383 _ => panic!("Expected Split with 2 triangles"),
1384 }
1385 }
1386
1387 #[test]
1388 fn test_triangle_normal() {
1389 let triangle = Triangle::new(
1390 Point3::new(0.0, 0.0, 0.0),
1391 Point3::new(1.0, 0.0, 0.0),
1392 Point3::new(0.0, 1.0, 0.0),
1393 );
1394
1395 let normal = triangle.normal();
1396 assert!((normal.z - 1.0).abs() < 1e-6);
1397 }
1398
1399 #[test]
1400 fn test_triangle_area() {
1401 let triangle = Triangle::new(
1402 Point3::new(0.0, 0.0, 0.0),
1403 Point3::new(1.0, 0.0, 0.0),
1404 Point3::new(0.0, 1.0, 0.0),
1405 );
1406
1407 let area = triangle.area();
1408 assert!((area - 0.5).abs() < 1e-6);
1409 }
1410
1411 #[test]
1412 fn test_csg_operation_guard_allows_simple_boxes() {
1413 let box_a = aabb_to_mesh(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
1414 let box_b = aabb_to_mesh(Point3::new(0.25, 0.25, 0.25), Point3::new(0.75, 0.75, 0.75));
1415
1416 let polys_a = ClippingProcessor::mesh_to_polygons(&box_a);
1417 let polys_b = ClippingProcessor::mesh_to_polygons(&box_b);
1418
1419 assert!(ClippingProcessor::can_run_csg_operation(polys_a.len(), polys_b.len()));
1420 }
1421
1422 #[test]
1423 fn test_csg_operation_guard_rejects_complex_operands() {
1424 let box_mesh = aabb_to_mesh(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
1425 let mut complex_mesh = Mesh::new();
1426 complex_mesh.merge(&box_mesh);
1427 complex_mesh.merge(&box_mesh);
1428 complex_mesh.merge(&box_mesh);
1429
1430 let polys_complex = ClippingProcessor::mesh_to_polygons(&complex_mesh);
1431 let polys_box = ClippingProcessor::mesh_to_polygons(&box_mesh);
1432
1433 assert!(!ClippingProcessor::can_run_csg_operation(polys_complex.len(), polys_box.len()));
1434 }
1435}