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;
14
15#[derive(Debug, Clone, Copy)]
17pub struct Plane {
18 pub point: Point3<f64>,
20 pub normal: Vector3<f64>,
22}
23
24impl Plane {
25 pub fn new(point: Point3<f64>, normal: Vector3<f64>) -> Self {
27 Self {
28 point,
29 normal: normal.normalize(),
30 }
31 }
32
33 pub fn signed_distance(&self, point: &Point3<f64>) -> f64 {
36 (point - self.point).dot(&self.normal)
37 }
38
39 pub fn is_front(&self, point: &Point3<f64>) -> bool {
41 self.signed_distance(point) >= 0.0
42 }
43}
44
45#[derive(Debug, Clone)]
47pub enum ClipResult {
48 AllFront(Triangle),
50 AllBehind,
52 Split(Vec<Triangle>),
54}
55
56#[derive(Debug, Clone)]
58pub struct Triangle {
59 pub v0: Point3<f64>,
60 pub v1: Point3<f64>,
61 pub v2: Point3<f64>,
62}
63
64impl Triangle {
65 pub fn new(v0: Point3<f64>, v1: Point3<f64>, v2: Point3<f64>) -> Self {
67 Self { v0, v1, v2 }
68 }
69
70 pub fn normal(&self) -> Vector3<f64> {
72 let edge1 = self.v1 - self.v0;
73 let edge2 = self.v2 - self.v0;
74 edge1.cross(&edge2).normalize()
75 }
76
77 pub fn area(&self) -> f64 {
79 let edge1 = self.v1 - self.v0;
80 let edge2 = self.v2 - self.v0;
81 edge1.cross(&edge2).norm() * 0.5
82 }
83}
84
85pub struct ClippingProcessor {
87 pub epsilon: f64,
89}
90
91fn aabb_to_mesh(min: Point3<f64>, max: Point3<f64>) -> Mesh {
94 let mut mesh = Mesh::with_capacity(8, 36);
95
96 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));
109 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v3, v2));
110
111 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v5, v6));
113 add_triangle_to_mesh(&mut mesh, &Triangle::new(v4, v6, v7));
114
115 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v4, v7));
117 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v7, v3));
118
119 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v2, v6));
121 add_triangle_to_mesh(&mut mesh, &Triangle::new(v1, v6, v5));
122
123 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v1, v5));
125 add_triangle_to_mesh(&mut mesh, &Triangle::new(v0, v5, v4));
126
127 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v7, v6));
129 add_triangle_to_mesh(&mut mesh, &Triangle::new(v3, v6, v2));
130
131 mesh
132}
133
134impl ClippingProcessor {
135 pub fn new() -> Self {
137 Self { epsilon: 1e-6 }
138 }
139
140 pub fn clip_triangle(&self, triangle: &Triangle, plane: &Plane) -> ClipResult {
143 let d0 = plane.signed_distance(&triangle.v0);
145 let d1 = plane.signed_distance(&triangle.v1);
146 let d2 = plane.signed_distance(&triangle.v2);
147
148 let mut front_count = 0;
150 if d0 >= -self.epsilon {
151 front_count += 1;
152 }
153 if d1 >= -self.epsilon {
154 front_count += 1;
155 }
156 if d2 >= -self.epsilon {
157 front_count += 1;
158 }
159
160 match front_count {
161 0 => ClipResult::AllBehind,
163
164 3 => ClipResult::AllFront(triangle.clone()),
166
167 1 => {
169 let (front, back1, back2) = if d0 >= -self.epsilon {
170 (triangle.v0, triangle.v1, triangle.v2)
171 } else if d1 >= -self.epsilon {
172 (triangle.v1, triangle.v2, triangle.v0)
173 } else {
174 (triangle.v2, triangle.v0, triangle.v1)
175 };
176
177 let d_front = if d0 >= -self.epsilon {
179 d0
180 } else if d1 >= -self.epsilon {
181 d1
182 } else {
183 d2
184 };
185 let d_back1 = if d0 >= -self.epsilon {
186 d1
187 } else if d1 >= -self.epsilon {
188 d2
189 } else {
190 d0
191 };
192 let d_back2 = if d0 >= -self.epsilon {
193 d2
194 } else if d1 >= -self.epsilon {
195 d0
196 } else {
197 d1
198 };
199
200 let t1 = d_front / (d_front - d_back1);
201 let t2 = d_front / (d_front - d_back2);
202
203 let p1 = front + (back1 - front) * t1;
204 let p2 = front + (back2 - front) * t2;
205
206 ClipResult::Split(vec![Triangle::new(front, p1, p2)])
207 }
208
209 2 => {
211 let (front1, front2, back) = if d0 < -self.epsilon {
212 (triangle.v1, triangle.v2, triangle.v0)
213 } else if d1 < -self.epsilon {
214 (triangle.v2, triangle.v0, triangle.v1)
215 } else {
216 (triangle.v0, triangle.v1, triangle.v2)
217 };
218
219 let d_back = if d0 < -self.epsilon {
221 d0
222 } else if d1 < -self.epsilon {
223 d1
224 } else {
225 d2
226 };
227 let d_front1 = if d0 < -self.epsilon {
228 d1
229 } else if d1 < -self.epsilon {
230 d2
231 } else {
232 d0
233 };
234 let d_front2 = if d0 < -self.epsilon {
235 d2
236 } else if d1 < -self.epsilon {
237 d0
238 } else {
239 d1
240 };
241
242 let t1 = d_front1 / (d_front1 - d_back);
243 let t2 = d_front2 / (d_front2 - d_back);
244
245 let p1 = front1 + (back - front1) * t1;
246 let p2 = front2 + (back - front2) * t2;
247
248 ClipResult::Split(vec![
249 Triangle::new(front1, front2, p1),
250 Triangle::new(front2, p2, p1),
251 ])
252 }
253
254 _ => unreachable!(),
255 }
256 }
257
258 pub fn subtract_box(&self, mesh: &Mesh, min: Point3<f64>, max: Point3<f64>) -> Result<Mesh> {
261 if mesh.is_empty() {
263 return Ok(Mesh::new());
264 }
265
266 let box_mesh = aabb_to_mesh(min, max);
268
269 self.subtract_mesh(mesh, &box_mesh)
271 }
272
273 #[allow(dead_code)]
277 fn extract_opening_profile(
278 &self,
279 opening_mesh: &Mesh,
280 ) -> Option<(Vec<Point3<f64>>, Vector3<f64>)> {
281 if opening_mesh.is_empty() {
282 return None;
283 }
284
285 let mut face_groups: FxHashMap<u64, Vec<(Point3<f64>, Point3<f64>, Point3<f64>)>> =
287 FxHashMap::default();
288 let normal_epsilon = 0.01; for i in (0..opening_mesh.indices.len()).step_by(3) {
291 let i0 = opening_mesh.indices[i] as usize;
292 let i1 = opening_mesh.indices[i + 1] as usize;
293 let i2 = opening_mesh.indices[i + 2] as usize;
294
295 let v0 = Point3::new(
296 opening_mesh.positions[i0 * 3] as f64,
297 opening_mesh.positions[i0 * 3 + 1] as f64,
298 opening_mesh.positions[i0 * 3 + 2] as f64,
299 );
300 let v1 = Point3::new(
301 opening_mesh.positions[i1 * 3] as f64,
302 opening_mesh.positions[i1 * 3 + 1] as f64,
303 opening_mesh.positions[i1 * 3 + 2] as f64,
304 );
305 let v2 = Point3::new(
306 opening_mesh.positions[i2 * 3] as f64,
307 opening_mesh.positions[i2 * 3 + 1] as f64,
308 opening_mesh.positions[i2 * 3 + 2] as f64,
309 );
310
311 let edge1 = v1 - v0;
312 let edge2 = v2 - v0;
313 let normal = match edge1.cross(&edge2).try_normalize(1e-10) {
315 Some(n) => n,
316 None => continue, };
318
319 let nx = (normal.x / normal_epsilon).round() as i32;
321 let ny = (normal.y / normal_epsilon).round() as i32;
322 let nz = (normal.z / normal_epsilon).round() as i32;
323 let key = ((nx as u64) << 32) | ((ny as u32 as u64) << 16) | (nz as u32 as u64);
324
325 face_groups.entry(key).or_default().push((v0, v1, v2));
326 }
327
328 let largest_face = face_groups
330 .iter()
331 .max_by_key(|(_, triangles)| triangles.len())?;
332
333 let triangles = largest_face.1;
334 if triangles.is_empty() {
335 return None;
336 }
337
338 let quantize = |p: &Point3<f64>| -> (i64, i64, i64) {
342 let scale = 1e6; (
344 (p.x * scale).round() as i64,
345 (p.y * scale).round() as i64,
346 (p.z * scale).round() as i64,
347 )
348 };
349
350 let make_edge_key =
352 |a: (i64, i64, i64), b: (i64, i64, i64)| -> ((i64, i64, i64), (i64, i64, i64)) {
353 if a < b {
354 (a, b)
355 } else {
356 (b, a)
357 }
358 };
359
360 let mut edge_count: FxHashMap<
362 ((i64, i64, i64), (i64, i64, i64)),
363 (usize, Point3<f64>, Point3<f64>),
364 > = FxHashMap::default();
365
366 for (v0, v1, v2) in triangles {
367 let q0 = quantize(v0);
368 let q1 = quantize(v1);
369 let q2 = quantize(v2);
370
371 for (qa, qb, pa, pb) in [
373 (q0, q1, *v0, *v1),
374 (q1, q2, *v1, *v2),
375 (q2, q0, *v2, *v0),
376 ] {
377 let key = make_edge_key(qa, qb);
378 edge_count
379 .entry(key)
380 .and_modify(|(count, _, _)| *count += 1)
381 .or_insert((1, pa, pb));
382 }
383 }
384
385 let mut boundary_edges: Vec<(Point3<f64>, Point3<f64>)> = Vec::new();
387 for (_, (count, pa, pb)) in &edge_count {
388 if *count == 1 {
389 boundary_edges.push((*pa, *pb));
390 }
391 }
392
393 if boundary_edges.is_empty() {
394 return None;
396 }
397
398 let mut adjacency: FxHashMap<(i64, i64, i64), Vec<(i64, i64, i64, Point3<f64>)>> =
400 FxHashMap::default();
401 for (pa, pb) in &boundary_edges {
402 let qa = quantize(pa);
403 let qb = quantize(pb);
404 adjacency.entry(qa).or_default().push((qb.0, qb.1, qb.2, *pb));
405 adjacency.entry(qb).or_default().push((qa.0, qa.1, qa.2, *pa));
406 }
407
408 let mut contour: Vec<Point3<f64>> = Vec::new();
410 let mut visited: FxHashMap<(i64, i64, i64), bool> = FxHashMap::default();
411
412 if let Some((start_p, _)) = boundary_edges.first() {
414 let start_q = quantize(start_p);
415 contour.push(*start_p);
416 visited.insert(start_q, true);
417
418 let mut current_q = start_q;
419
420 loop {
422 let neighbors = match adjacency.get(¤t_q) {
423 Some(n) => n,
424 None => break,
425 };
426
427 let mut found_next = false;
429 for (nqx, nqy, nqz, np) in neighbors {
430 let nq = (*nqx, *nqy, *nqz);
431 if !visited.get(&nq).unwrap_or(&false) {
432 contour.push(*np);
433 visited.insert(nq, true);
434 current_q = nq;
435 found_next = true;
436 break;
437 }
438 }
439
440 if !found_next {
441 break; }
443 }
444 }
445
446 if contour.len() < 3 {
447 return None;
449 }
450
451 let normal = calculate_polygon_normal(&contour);
453
454 let normalized_normal = match normal.try_normalize(1e-10) {
456 Some(n) => n,
457 None => return None, };
459
460 Some((contour, normalized_normal))
461 }
462
463 fn mesh_to_csgrs(mesh: &Mesh) -> Result<csgrs::mesh::Mesh<()>> {
465 use csgrs::mesh::{polygon::Polygon, vertex::Vertex, Mesh as CSGMesh};
466 use std::sync::OnceLock;
467
468 if mesh.is_empty() {
469 return Ok(CSGMesh {
470 polygons: Vec::new(),
471 bounding_box: OnceLock::new(),
472 metadata: None,
473 });
474 }
475
476 let mut polygons = Vec::new();
477
478 for i in (0..mesh.indices.len()).step_by(3) {
480 let i0 = mesh.indices[i] as usize;
481 let i1 = mesh.indices[i + 1] as usize;
482 let i2 = mesh.indices[i + 2] as usize;
483
484 let v0 = Point3::new(
486 mesh.positions[i0 * 3] as f64,
487 mesh.positions[i0 * 3 + 1] as f64,
488 mesh.positions[i0 * 3 + 2] as f64,
489 );
490 let v1 = Point3::new(
491 mesh.positions[i1 * 3] as f64,
492 mesh.positions[i1 * 3 + 1] as f64,
493 mesh.positions[i1 * 3 + 2] as f64,
494 );
495 let v2 = Point3::new(
496 mesh.positions[i2 * 3] as f64,
497 mesh.positions[i2 * 3 + 1] as f64,
498 mesh.positions[i2 * 3 + 2] as f64,
499 );
500
501 let edge1 = v1 - v0;
504 let edge2 = v2 - v0;
505 let face_normal = match edge1.cross(&edge2).try_normalize(1e-10) {
506 Some(n) => n,
507 None => continue, };
509
510 let vertices = vec![
512 Vertex::new(v0, face_normal),
513 Vertex::new(v1, face_normal),
514 Vertex::new(v2, face_normal),
515 ];
516
517 polygons.push(Polygon::new(vertices, None));
518 }
519
520 Ok(CSGMesh::from_polygons(&polygons, None))
521 }
522
523 fn csgrs_to_mesh(csg_mesh: &csgrs::mesh::Mesh<()>) -> Result<Mesh> {
525 let mut mesh = Mesh::new();
526
527 for polygon in &csg_mesh.polygons {
528 let vertices = &polygon.vertices;
529 if vertices.len() < 3 {
530 continue;
531 }
532
533 let points_3d: Vec<Point3<f64>> = vertices
535 .iter()
536 .map(|v| Point3::new(v.pos[0], v.pos[1], v.pos[2]))
537 .collect();
538
539 let raw_normal = Vector3::new(
542 vertices[0].normal[0],
543 vertices[0].normal[1],
544 vertices[0].normal[2],
545 );
546
547 let csg_normal = match raw_normal.try_normalize(1e-10) {
549 Some(n) if n.x.is_finite() && n.y.is_finite() && n.z.is_finite() => n,
550 _ => {
551 let computed = calculate_polygon_normal(&points_3d);
553 match computed.try_normalize(1e-10) {
554 Some(n) => n,
555 None => continue, }
557 }
558 };
559
560 if points_3d.len() == 3 {
562 let base_idx = mesh.vertex_count();
563 for v in vertices {
564 mesh.add_vertex(v.pos, v.normal);
565 }
566 mesh.add_triangle(
567 base_idx as u32,
568 (base_idx + 1) as u32,
569 (base_idx + 2) as u32,
570 );
571 continue;
572 }
573
574 let (points_2d, _, _, _) = project_to_2d(&points_3d, &csg_normal);
576
577 let indices = match triangulate_polygon(&points_2d) {
579 Ok(idx) => idx,
580 Err(_) => continue, };
582
583 let base_idx = mesh.vertex_count();
585 for v in vertices {
586 mesh.add_vertex(v.pos, v.normal);
587 }
588
589 for tri in indices.chunks(3) {
590 if tri.len() == 3 {
591 mesh.add_triangle(
592 (base_idx + tri[0]) as u32,
593 (base_idx + tri[1]) as u32,
594 (base_idx + tri[2]) as u32,
595 );
596 }
597 }
598 }
599
600 Ok(mesh)
601 }
602
603 pub fn subtract_mesh(&self, host_mesh: &Mesh, opening_mesh: &Mesh) -> Result<Mesh> {
605 use csgrs::traits::CSG;
606
607 if opening_mesh.is_empty() {
609 return Ok(host_mesh.clone());
610 }
611
612 let host_csg = Self::mesh_to_csgrs(host_mesh)?;
614 let opening_csg = Self::mesh_to_csgrs(opening_mesh)?;
615
616 let result_csg = host_csg.difference(&opening_csg);
618
619 Self::csgrs_to_mesh(&result_csg)
621 }
622
623 pub fn union_mesh(&self, mesh_a: &Mesh, mesh_b: &Mesh) -> Result<Mesh> {
625 use csgrs::traits::CSG;
626
627 if mesh_a.is_empty() {
629 return Ok(mesh_b.clone());
630 }
631 if mesh_b.is_empty() {
632 return Ok(mesh_a.clone());
633 }
634
635 let csg_a = Self::mesh_to_csgrs(mesh_a)?;
637 let csg_b = Self::mesh_to_csgrs(mesh_b)?;
638
639 let result_csg = csg_a.union(&csg_b);
641
642 Self::csgrs_to_mesh(&result_csg)
644 }
645
646 pub fn union_meshes(&self, meshes: &[Mesh]) -> Result<Mesh> {
651 if meshes.is_empty() {
652 return Ok(Mesh::new());
653 }
654
655 if meshes.len() == 1 {
656 return Ok(meshes[0].clone());
657 }
658
659 let mut result = Mesh::new();
661 let mut found_first = false;
662
663 for mesh in meshes {
664 if mesh.is_empty() {
665 continue;
666 }
667
668 if !found_first {
669 result = mesh.clone();
670 found_first = true;
671 continue;
672 }
673
674 result = self.union_mesh(&result, mesh)?;
675 }
676
677 Ok(result)
678 }
679
680 pub fn subtract_meshes_batched(&self, host: &Mesh, voids: &[Mesh]) -> Result<Mesh> {
693 let non_empty_voids: Vec<&Mesh> = voids.iter().filter(|m| !m.is_empty()).collect();
695
696 if non_empty_voids.is_empty() {
697 return Ok(host.clone());
698 }
699
700 if non_empty_voids.len() == 1 {
701 return self.subtract_mesh(host, non_empty_voids[0]);
702 }
703
704 const BATCH_THRESHOLD: usize = 10;
706
707 if non_empty_voids.len() > BATCH_THRESHOLD {
708 let void_refs: Vec<Mesh> = non_empty_voids.iter().map(|m| (*m).clone()).collect();
710 let combined = self.union_meshes(&void_refs)?;
711
712 self.subtract_mesh(host, &combined)
714 } else {
715 let mut result = host.clone();
717
718 for void in non_empty_voids {
719 result = self.subtract_mesh(&result, void)?;
720 }
721
722 Ok(result)
723 }
724 }
725
726 pub fn subtract_meshes_with_fallback(&self, host: &Mesh, voids: &[Mesh]) -> Mesh {
732 match self.subtract_meshes_batched(host, voids) {
733 Ok(result) => {
734 if result.is_empty() || !self.validate_mesh(&result) {
736 host.clone()
737 } else {
738 result
739 }
740 }
741 Err(_) => host.clone(),
742 }
743 }
744
745 fn validate_mesh(&self, mesh: &Mesh) -> bool {
747 if mesh.positions.iter().any(|v| !v.is_finite()) {
749 return false;
750 }
751
752 if mesh.normals.iter().any(|v| !v.is_finite()) {
754 return false;
755 }
756
757 let vertex_count = mesh.vertex_count();
759 for idx in &mesh.indices {
760 if *idx as usize >= vertex_count {
761 return false;
762 }
763 }
764
765 true
766 }
767
768 #[deprecated(note = "Use subtract_box() for better performance")]
771 pub fn clip_mesh_with_box(
772 &self,
773 mesh: &Mesh,
774 min: Point3<f64>,
775 max: Point3<f64>,
776 ) -> Result<Mesh> {
777 self.subtract_box(mesh, min, max)
778 }
779
780 pub fn clip_mesh(&self, mesh: &Mesh, plane: &Plane) -> Result<Mesh> {
782 let mut result = Mesh::new();
783
784 for i in (0..mesh.indices.len()).step_by(3) {
786 let i0 = mesh.indices[i] as usize;
787 let i1 = mesh.indices[i + 1] as usize;
788 let i2 = mesh.indices[i + 2] as usize;
789
790 let v0 = Point3::new(
792 mesh.positions[i0 * 3] as f64,
793 mesh.positions[i0 * 3 + 1] as f64,
794 mesh.positions[i0 * 3 + 2] as f64,
795 );
796 let v1 = Point3::new(
797 mesh.positions[i1 * 3] as f64,
798 mesh.positions[i1 * 3 + 1] as f64,
799 mesh.positions[i1 * 3 + 2] as f64,
800 );
801 let v2 = Point3::new(
802 mesh.positions[i2 * 3] as f64,
803 mesh.positions[i2 * 3 + 1] as f64,
804 mesh.positions[i2 * 3 + 2] as f64,
805 );
806
807 let triangle = Triangle::new(v0, v1, v2);
808
809 match self.clip_triangle(&triangle, plane) {
811 ClipResult::AllFront(tri) => {
812 add_triangle_to_mesh(&mut result, &tri);
814 }
815 ClipResult::AllBehind => {
816 }
818 ClipResult::Split(triangles) => {
819 for tri in triangles {
821 add_triangle_to_mesh(&mut result, &tri);
822 }
823 }
824 }
825 }
826
827 Ok(result)
828 }
829}
830
831impl Default for ClippingProcessor {
832 fn default() -> Self {
833 Self::new()
834 }
835}
836
837fn add_triangle_to_mesh(mesh: &mut Mesh, triangle: &Triangle) {
839 let base_idx = mesh.vertex_count() as u32;
840
841 let normal = triangle.normal();
843
844 mesh.add_vertex(triangle.v0, normal);
846 mesh.add_vertex(triangle.v1, normal);
847 mesh.add_vertex(triangle.v2, normal);
848
849 mesh.add_triangle(base_idx, base_idx + 1, base_idx + 2);
851}
852
853#[inline]
855pub fn calculate_normals(mesh: &mut Mesh) {
856 let vertex_count = mesh.vertex_count();
857 if vertex_count == 0 {
858 return;
859 }
860
861 let mut normals = vec![Vector3::zeros(); vertex_count];
863
864 for i in (0..mesh.indices.len()).step_by(3) {
866 let i0 = mesh.indices[i] as usize;
867 let i1 = mesh.indices[i + 1] as usize;
868 let i2 = mesh.indices[i + 2] as usize;
869
870 let v0 = Point3::new(
872 mesh.positions[i0 * 3] as f64,
873 mesh.positions[i0 * 3 + 1] as f64,
874 mesh.positions[i0 * 3 + 2] as f64,
875 );
876 let v1 = Point3::new(
877 mesh.positions[i1 * 3] as f64,
878 mesh.positions[i1 * 3 + 1] as f64,
879 mesh.positions[i1 * 3 + 2] as f64,
880 );
881 let v2 = Point3::new(
882 mesh.positions[i2 * 3] as f64,
883 mesh.positions[i2 * 3 + 1] as f64,
884 mesh.positions[i2 * 3 + 2] as f64,
885 );
886
887 let edge1 = v1 - v0;
889 let edge2 = v2 - v0;
890 let normal = edge1.cross(&edge2);
891
892 normals[i0] += normal;
894 normals[i1] += normal;
895 normals[i2] += normal;
896 }
897
898 mesh.normals.clear();
900 mesh.normals.reserve(vertex_count * 3);
901
902 for normal in normals {
903 let normalized = normal.try_normalize(1e-6).unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0));
904 mesh.normals.push(normalized.x as f32);
905 mesh.normals.push(normalized.y as f32);
906 mesh.normals.push(normalized.z as f32);
907 }
908}
909
910#[cfg(test)]
911mod tests {
912 use super::*;
913
914 #[test]
915 fn test_plane_signed_distance() {
916 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
917
918 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, 5.0)), 5.0);
919 assert_eq!(plane.signed_distance(&Point3::new(0.0, 0.0, -5.0)), -5.0);
920 assert_eq!(plane.signed_distance(&Point3::new(5.0, 5.0, 0.0)), 0.0);
921 }
922
923 #[test]
924 fn test_clip_triangle_all_front() {
925 let processor = ClippingProcessor::new();
926 let triangle = Triangle::new(
927 Point3::new(0.0, 0.0, 1.0),
928 Point3::new(1.0, 0.0, 1.0),
929 Point3::new(0.5, 1.0, 1.0),
930 );
931 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
932
933 match processor.clip_triangle(&triangle, &plane) {
934 ClipResult::AllFront(_) => {}
935 _ => panic!("Expected AllFront"),
936 }
937 }
938
939 #[test]
940 fn test_clip_triangle_all_behind() {
941 let processor = ClippingProcessor::new();
942 let triangle = Triangle::new(
943 Point3::new(0.0, 0.0, -1.0),
944 Point3::new(1.0, 0.0, -1.0),
945 Point3::new(0.5, 1.0, -1.0),
946 );
947 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
948
949 match processor.clip_triangle(&triangle, &plane) {
950 ClipResult::AllBehind => {}
951 _ => panic!("Expected AllBehind"),
952 }
953 }
954
955 #[test]
956 fn test_clip_triangle_split_one_front() {
957 let processor = ClippingProcessor::new();
958 let triangle = Triangle::new(
959 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, -1.0), Point3::new(0.5, 1.0, -1.0), );
963 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
964
965 match processor.clip_triangle(&triangle, &plane) {
966 ClipResult::Split(triangles) => {
967 assert_eq!(triangles.len(), 1);
968 }
969 _ => panic!("Expected Split"),
970 }
971 }
972
973 #[test]
974 fn test_clip_triangle_split_two_front() {
975 let processor = ClippingProcessor::new();
976 let triangle = Triangle::new(
977 Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0), Point3::new(0.5, 1.0, -1.0), );
981 let plane = Plane::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
982
983 match processor.clip_triangle(&triangle, &plane) {
984 ClipResult::Split(triangles) => {
985 assert_eq!(triangles.len(), 2);
986 }
987 _ => panic!("Expected Split with 2 triangles"),
988 }
989 }
990
991 #[test]
992 fn test_triangle_normal() {
993 let triangle = Triangle::new(
994 Point3::new(0.0, 0.0, 0.0),
995 Point3::new(1.0, 0.0, 0.0),
996 Point3::new(0.0, 1.0, 0.0),
997 );
998
999 let normal = triangle.normal();
1000 assert!((normal.z - 1.0).abs() < 1e-6);
1001 }
1002
1003 #[test]
1004 fn test_triangle_area() {
1005 let triangle = Triangle::new(
1006 Point3::new(0.0, 0.0, 0.0),
1007 Point3::new(1.0, 0.0, 0.0),
1008 Point3::new(0.0, 1.0, 0.0),
1009 );
1010
1011 let area = triangle.area();
1012 assert!((area - 0.5).abs() < 1e-6);
1013 }
1014}