1use super::{MeshIntersectionError, TriangleTriangleIntersection};
2use crate::bounding_volume::BoundingVolume;
3use crate::math::{Pose, Real, Vector3};
4use crate::partitioning::BvhNode;
5use crate::query::point::point_query::PointQueryWithLocation;
6use crate::query::PointQuery;
7use crate::shape::{TriMesh, Triangle};
8use crate::utils;
9use crate::utils::hashmap::Entry;
10use crate::utils::hashmap::HashMap;
11use crate::utils::hashset::HashSet;
12use alloc::collections::BTreeMap;
13use alloc::{vec, vec::Vec};
14use rstar::RTree;
15use spade::{ConstrainedDelaunayTriangulation, InsertionError, Triangulation as _};
16#[cfg(feature = "wavefront")]
17use std::path::PathBuf;
18
19#[derive(Copy, Clone, PartialEq, Eq, Hash)]
24struct HashableTriangleIndices([u32; 3]);
25
26impl From<[u32; 3]> for HashableTriangleIndices {
27 fn from([a, b, c]: [u32; 3]) -> Self {
28 let (sa, sb, sc) = utils::sort3(&a, &b, &c);
29 HashableTriangleIndices([*sa, *sb, *sc])
30 }
31}
32
33#[derive(Copy, Clone, PartialEq, Debug)]
36pub struct MeshIntersectionTolerances {
37 pub angle_epsilon: Real,
40 pub global_insertion_epsilon: Real,
44 pub local_insertion_epsilon_scale: Real,
50 pub collinearity_epsilon: Real,
52}
53
54impl Default for MeshIntersectionTolerances {
55 fn default() -> Self {
56 Self {
57 #[expect(clippy::unnecessary_cast)]
58 angle_epsilon: (0.005 as Real).to_radians(), global_insertion_epsilon: Real::EPSILON * 100.0,
60 local_insertion_epsilon_scale: 10.,
61 collinearity_epsilon: Real::EPSILON * 100.0,
62 }
63 }
64}
65
66pub fn intersect_meshes(
71 pos1: &Pose,
72 mesh1: &TriMesh,
73 flip1: bool,
74 pos2: &Pose,
75 mesh2: &TriMesh,
76 flip2: bool,
77) -> Result<Option<TriMesh>, MeshIntersectionError> {
78 intersect_meshes_with_tolerances(
79 pos1,
80 mesh1,
81 flip1,
82 pos2,
83 mesh2,
84 flip2,
85 MeshIntersectionTolerances::default(),
86 )
87}
88
89pub fn intersect_meshes_with_tolerances(
94 pos1: &Pose,
95 mesh1: &TriMesh,
96 flip1: bool,
97 pos2: &Pose,
98 mesh2: &TriMesh,
99 flip2: bool,
100 tolerances: MeshIntersectionTolerances,
101) -> Result<Option<TriMesh>, MeshIntersectionError> {
102 if cfg!(debug_assertions) {
103 mesh1.assert_half_edge_topology_is_valid();
104 mesh2.assert_half_edge_topology_is_valid();
105 }
106
107 if mesh1.topology().is_none() || mesh2.topology().is_none() {
108 return Err(MeshIntersectionError::MissingTopology);
109 }
110
111 if mesh1.pseudo_normals_if_oriented().is_none() || mesh2.pseudo_normals_if_oriented().is_none()
112 {
113 return Err(MeshIntersectionError::MissingPseudoNormals);
114 }
115
116 let pos12 = pos1.inv_mul(pos2);
117
118 let intersection_candidates: Vec<_> = mesh1
120 .bvh()
121 .leaf_pairs(mesh2.bvh(), |node1: &BvhNode, node2: &BvhNode| {
122 let aabb1 = node1.aabb();
123 let aabb2 = node2.aabb().transform_by(&pos12);
124 aabb1.intersects(&aabb2)
125 })
126 .collect();
127
128 let mut deleted_faces1: HashSet<u32> = HashSet::default();
129 let mut deleted_faces2: HashSet<u32> = HashSet::default();
130 let mut new_indices1 = vec![];
131 let mut new_indices2 = vec![];
132
133 let mut intersections = vec![];
135 for (fid1, fid2) in &intersection_candidates {
136 let tri1 = mesh1.triangle(*fid1);
137 let tri2 = mesh2.triangle(*fid2).transformed(&pos12);
138
139 if super::triangle_triangle_intersection(&tri1, &tri2, tolerances.collinearity_epsilon)
140 .is_some()
141 {
142 intersections.push((*fid1, *fid2));
143 let _ = deleted_faces1.insert(*fid1);
144 let _ = deleted_faces2.insert(*fid2);
145 }
146 }
147
148 extract_connected_components(
150 &pos12,
151 mesh1,
152 mesh2,
153 flip2,
154 &deleted_faces1,
155 &mut new_indices1,
156 );
157 extract_connected_components(
158 &pos12.inverse(),
159 mesh2,
160 mesh1,
161 flip1,
162 &deleted_faces2,
163 &mut new_indices2,
164 );
165
166 let mut point_set = RTree::<TreeVector, _>::new();
169 let mut topology_indices = HashMap::default();
170 {
171 let mut insert_point = |position: Vector3| {
172 insert_into_set(
173 position,
174 &mut point_set,
175 tolerances.global_insertion_epsilon,
176 ) as u32
177 };
178 for mut face in new_indices1 {
180 if flip1 {
181 face.swap(0, 1);
182 }
183
184 let idx = [
185 insert_point(pos1 * mesh1.vertices()[face[0] as usize]),
186 insert_point(pos1 * mesh1.vertices()[face[1] as usize]),
187 insert_point(pos1 * mesh1.vertices()[face[2] as usize]),
188 ];
189
190 if !is_topologically_degenerate(idx) {
191 insert_topology_indices(&mut topology_indices, idx);
192 }
193 }
194
195 for mut face in new_indices2 {
197 if flip2 {
198 face.swap(0, 1);
199 }
200 let idx = [
201 insert_point(pos2 * mesh2.vertices()[face[0] as usize]),
202 insert_point(pos2 * mesh2.vertices()[face[1] as usize]),
203 insert_point(pos2 * mesh2.vertices()[face[2] as usize]),
204 ];
205
206 if !is_topologically_degenerate(idx) {
207 insert_topology_indices(&mut topology_indices, idx);
208 }
209 }
210 }
211
212 let mut constraints1 = BTreeMap::<_, Vec<_>>::new();
215 let mut constraints2 = BTreeMap::<_, Vec<_>>::new();
216 for (fid1, fid2) in &intersections {
217 let tri1 = mesh1.triangle(*fid1).transformed(pos1);
218 let tri2 = mesh2.triangle(*fid2).transformed(pos2);
219
220 let list1 = constraints1.entry(fid1).or_default();
221 let list2 = constraints2.entry(fid2).or_default();
222
223 let intersection =
224 super::triangle_triangle_intersection(&tri1, &tri2, tolerances.collinearity_epsilon);
225 if let Some(intersection) = intersection {
226 match intersection {
227 TriangleTriangleIntersection::Segment { a, b } => {
228 list1.push([a.p1, b.p1]);
233 list2.push([a.p1, b.p1]);
234 }
235 TriangleTriangleIntersection::Polygon(polygon) => {
236 for i in 0..polygon.len() {
237 let a = polygon[i];
238 let b = polygon[(i + 1) % polygon.len()];
239
240 list1.push([a.p1, b.p1]);
242 }
243 }
244 }
245 }
246 }
247
248 merge_triangle_sets(
252 mesh1,
253 mesh2,
254 &constraints1,
255 pos1,
256 pos2,
257 flip1,
258 flip2,
259 &tolerances,
260 &mut point_set,
261 &mut topology_indices,
262 )?;
263
264 merge_triangle_sets(
265 mesh2,
266 mesh1,
267 &constraints2,
268 pos2,
269 pos1,
270 flip2,
271 flip1,
272 &tolerances,
273 &mut point_set,
274 &mut topology_indices,
275 )?;
276
277 let mut vertices: Vec<_> = point_set.iter().copied().collect();
279 vertices.sort_by(|a, b| a.id.cmp(&b.id));
280 let vertices: Vec<_> = vertices.iter().map(|p| Vector3::from(p.point)).collect();
281
282 if !topology_indices.is_empty() {
283 Ok(Some(TriMesh::new(
284 vertices,
285 topology_indices.into_values().collect(),
286 )?))
287 } else {
288 Ok(None)
289 }
290}
291
292fn extract_connected_components(
293 pos12: &Pose,
294 mesh1: &TriMesh,
295 mesh2: &TriMesh,
296 flip2: bool,
297 deleted_faces1: &HashSet<u32>,
298 new_indices1: &mut Vec<[u32; 3]>,
299) {
300 let topo1 = mesh1.topology().unwrap();
301 let mut visited: HashSet<u32> = HashSet::default();
302 let mut to_visit = vec![];
303 let mut visited_conn_comp = if let Some(cc) = mesh1.connected_components() {
304 vec![false; cc.ranges.len()] } else {
306 vec![]
307 };
308
309 for face in deleted_faces1 {
310 if let Some(cc) = mesh1.connected_components() {
311 visited_conn_comp[cc.face_colors[*face as usize] as usize] = true;
312 }
313
314 let eid = topo1.faces[*face as usize].half_edge;
315 let edge_a = &topo1.half_edges[eid as usize];
316 let edge_b = &topo1.half_edges[edge_a.next as usize];
317 let edge_c = &topo1.half_edges[edge_b.next as usize];
318 let edges = [edge_a, edge_b, edge_c];
319
320 for edge in edges {
321 if let Some(twin) = topo1.half_edges.get(edge.twin as usize) {
322 if !deleted_faces1.contains(&twin.face) {
323 let tri1 = mesh1.triangle(twin.face);
324
325 if flip2
326 ^ mesh2.contains_local_point(pos12.inverse_transform_point(tri1.center()))
327 {
328 to_visit.push(twin.face);
329 }
330 }
331 }
332 }
333 }
334
335 while let Some(face) = to_visit.pop() {
337 if !visited.insert(face) {
338 continue; }
340
341 new_indices1.push(mesh1.indices()[face as usize]);
342
343 let eid = topo1.faces[face as usize].half_edge;
344 let edge_a = &topo1.half_edges[eid as usize];
345 let edge_b = &topo1.half_edges[edge_a.next as usize];
346 let edge_c = &topo1.half_edges[edge_b.next as usize];
347 let edges = [edge_a, edge_b, edge_c];
348
349 for edge in edges {
350 if let Some(twin) = topo1.half_edges.get(edge.twin as usize) {
351 if !deleted_faces1.contains(&twin.face) {
352 to_visit.push(twin.face);
353 }
354 }
355 }
356 }
357
358 if let Some(cc) = mesh1.connected_components() {
362 for (i, range) in cc.ranges.windows(2).enumerate() {
363 if !visited_conn_comp[i] {
364 let repr_face = cc.grouped_faces[range[0]];
369 let repr_pt = mesh1.triangle(repr_face).center();
370 let indices = mesh1.indices();
371
372 if flip2 ^ mesh2.contains_local_point(pos12.inverse_transform_point(repr_pt)) {
373 new_indices1.extend(
374 cc.grouped_faces[range[0]..range[1]]
375 .iter()
376 .map(|fid| indices[*fid as usize]),
377 )
378 }
379 }
380 }
381 } else if deleted_faces1.is_empty() {
382 let repr_pt = mesh1.triangle(0).center();
384
385 if flip2 ^ mesh2.contains_local_point(pos12.inverse_transform_point(repr_pt)) {
386 new_indices1.extend_from_slice(mesh1.indices());
387 }
388 }
389}
390
391fn triangulate_constraints_and_merge_duplicates(
392 tri: &Triangle,
393 constraints: &[[Vector3; 2]],
394 epsilon: Real,
395) -> Result<
396 (
397 ConstrainedDelaunayTriangulation<spade::Point2<Real>>,
398 Vec<Vector3>,
399 ),
400 InsertionError,
401> {
402 let mut constraints = constraints.to_vec();
403 let mut point_set = RTree::<TreeVector, _>::new();
405 let _ = insert_into_set(tri.a, &mut point_set, epsilon);
406 let _ = insert_into_set(tri.b, &mut point_set, epsilon);
407 let _ = insert_into_set(tri.c, &mut point_set, epsilon);
408
409 let tri_vtx = tri.vertices();
413 for point_pair in constraints.iter_mut() {
414 let p1 = point_pair[0];
415 let p2 = point_pair[1];
416
417 for i in 0..3 {
418 let q1 = tri_vtx[i];
419 let q2 = tri_vtx[(i + 1) % 3];
420
421 let proj1 = project_point_to_segment(p1, &[q1, q2]);
422 if (p1 - proj1).length() < epsilon {
423 point_pair[0] = Vector3::from(proj1);
424 }
425
426 let proj2 = project_point_to_segment(p2, &[q1, q2]);
427 if (p2 - proj2).length() < epsilon {
428 point_pair[1] = Vector3::from(proj2);
429 }
430 }
431 }
432
433 let mut edges = Vec::new();
435 for point_pair in constraints {
436 let p1_id = insert_into_set(point_pair[0], &mut point_set, epsilon);
437 let p2_id = insert_into_set(point_pair[1], &mut point_set, epsilon);
438
439 if p1_id != p2_id {
440 edges.push([p1_id, p2_id]);
441 }
442 }
443
444 let mut points: Vec<_> = point_set.iter().cloned().collect();
445 points.sort_by(|a, b| a.id.cmp(&b.id));
446
447 let tri_points = tri.vertices();
448 let best_source = tri.angle_closest_to_90();
449 let d1 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3];
450 let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3];
451 let (e1, e2) = planar_gram_schmidt(d1, d2);
452
453 let project = |p: Vector3| spade::Point2::new(e1.dot(p), e2.dot(p));
454
455 let planar_points: Vec<_> = points
457 .iter()
458 .copied()
459 .map(|point| {
460 let point_proj = project(point.point);
461 utils::sanitize_spade_point(point_proj)
462 })
463 .collect();
464 let cdt_triangulation = ConstrainedDelaunayTriangulation::bulk_load_cdt(planar_points, edges)?;
465 debug_assert!(cdt_triangulation.vertices().len() == points.len());
466
467 let points = points.into_iter().map(|p| Vector3::from(p.point)).collect();
468 Ok((cdt_triangulation, points))
469}
470
471#[cfg(feature = "wavefront")]
473fn _points_to_obj(mesh: &[Vector3], path: &PathBuf) {
474 use std::io::Write;
475 let mut file = std::fs::File::create(path).unwrap();
476
477 for p in mesh {
478 writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap();
479 }
480}
481
482#[cfg(feature = "wavefront")]
484fn _points_and_edges_to_obj(mesh: &[Vector3], edges: &[[usize; 2]], path: &PathBuf) {
485 use std::io::Write;
486 let mut file = std::fs::File::create(path).unwrap();
487
488 for p in mesh {
489 writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap();
490 }
491
492 for e in edges {
493 writeln!(file, "l {} {}", e[0] + 1, e[1] + 1).unwrap();
494 }
495}
496
497#[derive(Copy, Clone, PartialEq, Debug, Default)]
498struct TreeVector {
499 point: Vector3,
500 id: usize,
501}
502
503impl rstar::Point for TreeVector {
504 type Scalar = Real;
505 const DIMENSIONS: usize = 3;
506
507 fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self {
508 TreeVector {
509 point: Vector3::new(generator(0), generator(1), generator(2)),
510 id: usize::MAX,
511 }
512 }
513
514 fn nth(&self, index: usize) -> Self::Scalar {
515 self.point[index]
516 }
517
518 fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar {
519 &mut self.point[index]
520 }
521}
522
523fn insert_into_set(position: Vector3, point_set: &mut RTree<TreeVector>, epsilon: Real) -> usize {
524 let point_count = point_set.size();
525 let point_to_insert = TreeVector {
526 point: position,
527 id: point_count,
528 };
529
530 match point_set.nearest_neighbor(&point_to_insert) {
531 Some(tree_point) => {
532 if (tree_point.point - position).length_squared() <= epsilon {
533 tree_point.id
534 } else {
535 point_set.insert(point_to_insert);
536 debug_assert!(point_set.size() == point_count + 1);
537 point_count
538 }
539 }
540 None => {
541 point_set.insert(point_to_insert);
542 debug_assert!(point_set.size() == point_count + 1);
543 point_count
544 }
545 }
546}
547
548fn smallest_angle(points: &[Vector3]) -> Real {
549 let n = points.len();
550
551 let mut worst_cos: Real = -2.0;
552 for i in 0..points.len() {
553 let d1 = (points[i] - points[(i + 1) % n]).normalize();
554 let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize();
555
556 let cos = d1.dot(d2);
557 if cos > worst_cos {
558 worst_cos = cos;
559 }
560 }
561
562 worst_cos.acos()
563}
564
565fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) {
566 let u1 = v1;
567 let u2 = v2 - (v2.dot(u1) / u1.length_squared()) * u1;
568
569 let e1 = u1.normalize();
570 let e2 = u2.normalize();
571
572 (e1, e2)
573}
574
575fn project_point_to_segment(point: Vector3, segment: &[Vector3; 2]) -> Vector3 {
576 let dir = segment[1] - segment[0];
577 let local = point - segment[0];
578
579 let norm = dir.length();
580 let coeff = (dir.dot(local) / norm).clamp(0., norm);
582
583 segment[0] + coeff * dir.normalize()
584}
585
586fn is_triangle_degenerate(
590 triangle: &[Vector3; 3],
591 epsilon_angle: Real,
592 epsilon_distance: Real,
593) -> bool {
594 if smallest_angle(triangle) < epsilon_angle {
595 return true;
596 }
597
598 for i in 0..3 {
599 let (dir, dist) = (triangle[(i + 1) % 3] - triangle[(i + 2) % 3]).normalize_and_length();
600 if dist < epsilon_distance {
601 return true;
602 }
603
604 let proj = triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(dir) * dir;
605
606 if (proj - triangle[i]).length() < epsilon_distance {
607 return true;
608 }
609 }
610
611 false
612}
613
614fn merge_triangle_sets(
615 mesh1: &TriMesh,
616 mesh2: &TriMesh,
617 triangle_constraints: &BTreeMap<&u32, Vec<[Vector3; 2]>>,
618 pos1: &Pose,
619 pos2: &Pose,
620 flip1: bool,
621 flip2: bool,
622 metadata: &MeshIntersectionTolerances,
623 point_set: &mut RTree<TreeVector>,
624 topology_indices: &mut HashMap<HashableTriangleIndices, [u32; 3]>,
625) -> Result<(), MeshIntersectionError> {
626 for (triangle_id, constraints) in triangle_constraints.iter() {
632 let tri = mesh1.triangle(**triangle_id).transformed(pos1);
633
634 let (delaunay, points) = triangulate_constraints_and_merge_duplicates(
635 &tri,
636 constraints,
637 metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_scale,
638 )
639 .or(Err(MeshIntersectionError::TriangulationError))?;
640
641 for face in delaunay.inner_faces() {
642 let verts = face.vertices();
643 let p1 = points[verts[0].index()];
644 let p2 = points[verts[1].index()];
645 let p3 = points[verts[2].index()];
646
647 if is_triangle_degenerate(
650 &[p1, p2, p3],
651 metadata.angle_epsilon,
652 metadata.global_insertion_epsilon,
653 ) {
654 continue;
655 }
656
657 let center = Triangle {
658 a: p1,
659 b: p2,
660 c: p3,
661 }
662 .center();
663
664 let epsilon = metadata.global_insertion_epsilon;
665 let projection = mesh2
666 .project_local_point_and_get_location(pos2.inverse_transform_point(center), true)
667 .0;
668
669 if flip2 ^ (projection.is_inside_eps(center, epsilon)) {
670 let mut new_tri_idx = [
671 insert_into_set(p1, point_set, epsilon) as u32,
672 insert_into_set(p2, point_set, epsilon) as u32,
673 insert_into_set(p3, point_set, epsilon) as u32,
674 ];
675
676 if flip1 {
677 new_tri_idx.swap(0, 1)
678 }
679
680 if is_topologically_degenerate(new_tri_idx) {
684 return Err(MeshIntersectionError::DuplicateVertices);
685 }
686
687 insert_topology_indices(topology_indices, new_tri_idx);
688 }
689 }
690 }
691
692 Ok(())
693}
694
695fn insert_topology_indices(
701 topology_indices: &mut HashMap<HashableTriangleIndices, [u32; 3]>,
702 new_tri_idx: [u32; 3],
703) {
704 match topology_indices.entry(new_tri_idx.into()) {
705 Entry::Vacant(e) => {
706 let _ = e.insert(new_tri_idx);
707 }
708 Entry::Occupied(e) => {
709 fn same_orientation(a: &[u32; 3], b: &[u32; 3]) -> bool {
710 let ib = if a[0] == b[0] {
711 0
712 } else if a[0] == b[1] {
713 1
714 } else {
715 2
716 };
717 a[1] == b[(ib + 1) % 3]
718 }
719
720 if !same_orientation(e.get(), &new_tri_idx) {
721 #[cfg(feature = "enhanced-determinism")]
725 let _ = e.swap_remove();
726 #[cfg(not(feature = "enhanced-determinism"))]
727 let _ = e.remove();
728 }
729 }
730 }
731}
732
733fn is_topologically_degenerate(tri_idx: [u32; 3]) -> bool {
734 tri_idx[0] == tri_idx[1] || tri_idx[0] == tri_idx[2] || tri_idx[1] == tri_idx[2]
735}
736
737#[cfg(feature = "wavefront")]
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::math::Vector;
742 use crate::shape::{Ball, Cuboid, TriMeshFlags};
743 use obj::Obj;
744 use obj::ObjData;
745
746 #[test]
747 fn test_same_mesh_intersection() {
748 let Obj {
749 data: ObjData {
750 position, objects, ..
751 },
752 ..
753 } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap();
754
755 let mesh = TriMesh::with_flags(
756 position
757 .iter()
758 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
759 .collect::<Vec<_>>(),
760 objects[0].groups[0]
761 .polys
762 .iter()
763 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
764 .collect::<Vec<_>>(),
765 TriMeshFlags::all(),
766 )
767 .unwrap();
768
769 let _ = intersect_meshes(
770 &Pose::identity(),
771 &mesh,
772 false,
773 &Pose::identity(),
774 &mesh,
775 false,
776 )
777 .unwrap()
778 .unwrap();
779
780 let _ = mesh.to_obj_file(&PathBuf::from("same_test.obj"));
781 }
782
783 #[test]
784 fn test_non_origin_pos1_pos2_intersection() {
785 let ball = Ball::new(2f32 as Real).to_trimesh(10, 10);
786 let cuboid = Cuboid::new(Vector::new(2.0, 1.0, 1.0)).to_trimesh();
787 let mut sphere_mesh = TriMesh::new(ball.0, ball.1).unwrap();
788 sphere_mesh.set_flags(TriMeshFlags::all()).unwrap();
789 let mut cuboid_mesh = TriMesh::new(cuboid.0, cuboid.1).unwrap();
790 cuboid_mesh.set_flags(TriMeshFlags::all()).unwrap();
791
792 let res = intersect_meshes(
793 &Pose::translation(1.0, 0.0, 0.0),
794 &cuboid_mesh,
795 false,
796 &Pose::translation(2.0, 0.0, 0.0),
797 &sphere_mesh,
798 false,
799 )
800 .unwrap()
801 .unwrap();
802
803 let _ = res.to_obj_file(&PathBuf::from("test_non_origin_pos1_pos2_intersection.obj"));
804
805 let bounding_sphere = res.local_bounding_sphere();
806 assert!(bounding_sphere.center == Vector::new(1.5, 0.0, 0.0));
807 assert_relative_eq!(2.0615528, bounding_sphere.radius, epsilon = 1.0e-5);
808 }
809
810 #[test]
811 fn test_offset_cylinder_intersection() {
812 let Obj {
813 data: ObjData {
814 position, objects, ..
815 },
816 ..
817 } = Obj::load("../../assets/tests/offset_cylinder.obj").unwrap();
818
819 let offset_mesh = TriMesh::with_flags(
820 position
821 .iter()
822 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
823 .collect::<Vec<_>>(),
824 objects[0].groups[0]
825 .polys
826 .iter()
827 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
828 .collect::<Vec<_>>(),
829 TriMeshFlags::all(),
830 )
831 .unwrap();
832
833 let Obj {
834 data: ObjData {
835 position, objects, ..
836 },
837 ..
838 } = Obj::load("../../assets/tests/center_cylinder.obj").unwrap();
839
840 let center_mesh = TriMesh::with_flags(
841 position
842 .iter()
843 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
844 .collect::<Vec<_>>(),
845 objects[0].groups[0]
846 .polys
847 .iter()
848 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
849 .collect::<Vec<_>>(),
850 TriMeshFlags::all(),
851 )
852 .unwrap();
853
854 let res = intersect_meshes(
855 &Pose::identity(),
856 ¢er_mesh,
857 false,
858 &Pose::identity(),
859 &offset_mesh,
860 false,
861 )
862 .unwrap()
863 .unwrap();
864
865 let _ = res.to_obj_file(&PathBuf::from("offset_test.obj"));
866 }
867
868 #[test]
869 fn test_stair_bar_intersection() {
870 let Obj {
871 data: ObjData {
872 position, objects, ..
873 },
874 ..
875 } = Obj::load("../../assets/tests/stairs.obj").unwrap();
876
877 let stair_mesh = TriMesh::with_flags(
878 position
879 .iter()
880 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
881 .collect::<Vec<_>>(),
882 objects[0].groups[0]
883 .polys
884 .iter()
885 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
886 .collect::<Vec<_>>(),
887 TriMeshFlags::all(),
888 )
889 .unwrap();
890
891 let Obj {
892 data: ObjData {
893 position, objects, ..
894 },
895 ..
896 } = Obj::load("../../assets/tests/bar.obj").unwrap();
897
898 let bar_mesh = TriMesh::with_flags(
899 position
900 .iter()
901 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
902 .collect::<Vec<_>>(),
903 objects[0].groups[0]
904 .polys
905 .iter()
906 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
907 .collect::<Vec<_>>(),
908 TriMeshFlags::all(),
909 )
910 .unwrap();
911
912 let res = intersect_meshes(
913 &Pose::identity(),
914 &stair_mesh,
915 false,
916 &Pose::identity(),
917 &bar_mesh,
918 false,
919 )
920 .unwrap()
921 .unwrap();
922
923 let _ = res.to_obj_file(&PathBuf::from("stair_test.obj"));
924 }
925
926 #[test]
927 fn test_complex_intersection() {
928 let Obj {
929 data: ObjData {
930 position, objects, ..
931 },
932 ..
933 } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap();
934
935 let bunny_mesh = TriMesh::with_flags(
936 position
937 .iter()
938 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
939 .collect::<Vec<_>>(),
940 objects[0].groups[0]
941 .polys
942 .iter()
943 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
944 .collect::<Vec<_>>(),
945 TriMeshFlags::all(),
946 )
947 .unwrap();
948
949 let Obj {
950 data: ObjData {
951 position, objects, ..
952 },
953 ..
954 } = Obj::load("../../assets/tests/poly_cylinder.obj").unwrap();
955
956 let cylinder_mesh = TriMesh::with_flags(
957 position
958 .iter()
959 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
960 .collect::<Vec<_>>(),
961 objects[0].groups[0]
962 .polys
963 .iter()
964 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
965 .collect::<Vec<_>>(),
966 TriMeshFlags::all(),
967 )
968 .unwrap();
969
970 let res = intersect_meshes(
971 &Pose::identity(),
972 &bunny_mesh,
973 false,
974 &Pose::identity(),
975 &cylinder_mesh,
976 true,
977 )
978 .unwrap()
979 .unwrap();
980
981 let _ = res.to_obj_file(&PathBuf::from("complex_test.obj"));
982 }
983}