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_key(|a| a.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_key(|a| a.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 =
468 ConstrainedDelaunayTriangulation::try_bulk_load_cdt(planar_points, edges, |_conflict| {})?;
469 debug_assert!(cdt_triangulation.vertices().len() == points.len());
470
471 let points = points.into_iter().map(|p| Vector3::from(p.point)).collect();
472 Ok((cdt_triangulation, points))
473}
474
475#[cfg(feature = "wavefront")]
477fn _points_to_obj(mesh: &[Vector3], path: &PathBuf) {
478 use std::io::Write;
479 let mut file = std::fs::File::create(path).unwrap();
480
481 for p in mesh {
482 writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap();
483 }
484}
485
486#[cfg(feature = "wavefront")]
488fn _points_and_edges_to_obj(mesh: &[Vector3], edges: &[[usize; 2]], path: &PathBuf) {
489 use std::io::Write;
490 let mut file = std::fs::File::create(path).unwrap();
491
492 for p in mesh {
493 writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap();
494 }
495
496 for e in edges {
497 writeln!(file, "l {} {}", e[0] + 1, e[1] + 1).unwrap();
498 }
499}
500
501#[derive(Copy, Clone, PartialEq, Debug, Default)]
502struct TreeVector {
503 point: Vector3,
504 id: usize,
505}
506
507impl rstar::Point for TreeVector {
508 type Scalar = Real;
509 const DIMENSIONS: usize = 3;
510
511 fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self {
512 TreeVector {
513 point: Vector3::new(generator(0), generator(1), generator(2)),
514 id: usize::MAX,
515 }
516 }
517
518 fn nth(&self, index: usize) -> Self::Scalar {
519 self.point[index]
520 }
521
522 fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar {
523 &mut self.point[index]
524 }
525}
526
527fn insert_into_set(position: Vector3, point_set: &mut RTree<TreeVector>, epsilon: Real) -> usize {
528 let point_count = point_set.size();
529 let point_to_insert = TreeVector {
530 point: position,
531 id: point_count,
532 };
533
534 match point_set.nearest_neighbor(point_to_insert) {
535 Some(tree_point) if (tree_point.point - position).length_squared() <= epsilon => {
536 tree_point.id
537 }
538 _ => {
539 point_set.insert(point_to_insert);
540 debug_assert!(point_set.size() == point_count + 1);
541 point_count
542 }
543 }
544}
545
546fn smallest_angle(points: &[Vector3]) -> Real {
547 let n = points.len();
548
549 let mut worst_cos: Real = -2.0;
550 for i in 0..points.len() {
551 let d1 = (points[i] - points[(i + 1) % n]).normalize();
552 let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize();
553
554 let cos = d1.dot(d2);
555 if cos > worst_cos {
556 worst_cos = cos;
557 }
558 }
559
560 worst_cos.acos()
561}
562
563fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) {
564 let u1 = v1;
565 let u2 = v2 - (v2.dot(u1) / u1.length_squared()) * u1;
566
567 let e1 = u1.normalize();
568 let e2 = u2.normalize();
569
570 (e1, e2)
571}
572
573fn project_point_to_segment(point: Vector3, segment: &[Vector3; 2]) -> Vector3 {
574 let dir = segment[1] - segment[0];
575 let local = point - segment[0];
576
577 let norm = dir.length();
578 let coeff = (dir.dot(local) / norm).clamp(0., norm);
580
581 segment[0] + coeff * dir.normalize()
582}
583
584fn is_triangle_degenerate(
588 triangle: &[Vector3; 3],
589 epsilon_angle: Real,
590 epsilon_distance: Real,
591) -> bool {
592 if smallest_angle(triangle) < epsilon_angle {
593 return true;
594 }
595
596 for i in 0..3 {
597 let (dir, dist) = (triangle[(i + 1) % 3] - triangle[(i + 2) % 3]).normalize_and_length();
598 if dist < epsilon_distance {
599 return true;
600 }
601
602 let proj = triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(dir) * dir;
603
604 if (proj - triangle[i]).length() < epsilon_distance {
605 return true;
606 }
607 }
608
609 false
610}
611
612fn merge_triangle_sets(
613 mesh1: &TriMesh,
614 mesh2: &TriMesh,
615 triangle_constraints: &BTreeMap<&u32, Vec<[Vector3; 2]>>,
616 pos1: &Pose,
617 pos2: &Pose,
618 flip1: bool,
619 flip2: bool,
620 metadata: &MeshIntersectionTolerances,
621 point_set: &mut RTree<TreeVector>,
622 topology_indices: &mut HashMap<HashableTriangleIndices, [u32; 3]>,
623) -> Result<(), MeshIntersectionError> {
624 for (triangle_id, constraints) in triangle_constraints.iter() {
630 let tri = mesh1.triangle(**triangle_id).transformed(pos1);
631
632 let (delaunay, points) = triangulate_constraints_and_merge_duplicates(
633 &tri,
634 constraints,
635 metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_scale,
636 )
637 .or(Err(MeshIntersectionError::TriangulationError))?;
638
639 for face in delaunay.inner_faces() {
640 let verts = face.vertices();
641 let p1 = points[verts[0].index()];
642 let p2 = points[verts[1].index()];
643 let p3 = points[verts[2].index()];
644
645 if is_triangle_degenerate(
648 &[p1, p2, p3],
649 metadata.angle_epsilon,
650 metadata.global_insertion_epsilon,
651 ) {
652 continue;
653 }
654
655 let center = Triangle {
656 a: p1,
657 b: p2,
658 c: p3,
659 }
660 .center();
661
662 let epsilon = metadata.global_insertion_epsilon;
663 let projection = mesh2
664 .project_local_point_and_get_location(pos2.inverse_transform_point(center), true)
665 .0;
666
667 if flip2 ^ (projection.is_inside_eps(center, epsilon)) {
668 let mut new_tri_idx = [
669 insert_into_set(p1, point_set, epsilon) as u32,
670 insert_into_set(p2, point_set, epsilon) as u32,
671 insert_into_set(p3, point_set, epsilon) as u32,
672 ];
673
674 if flip1 {
675 new_tri_idx.swap(0, 1)
676 }
677
678 if is_topologically_degenerate(new_tri_idx) {
682 return Err(MeshIntersectionError::DuplicateVertices);
683 }
684
685 insert_topology_indices(topology_indices, new_tri_idx);
686 }
687 }
688 }
689
690 Ok(())
691}
692
693fn insert_topology_indices(
699 topology_indices: &mut HashMap<HashableTriangleIndices, [u32; 3]>,
700 new_tri_idx: [u32; 3],
701) {
702 match topology_indices.entry(new_tri_idx.into()) {
703 Entry::Vacant(e) => {
704 let _ = e.insert(new_tri_idx);
705 }
706 Entry::Occupied(e) => {
707 fn same_orientation(a: &[u32; 3], b: &[u32; 3]) -> bool {
708 let ib = if a[0] == b[0] {
709 0
710 } else if a[0] == b[1] {
711 1
712 } else {
713 2
714 };
715 a[1] == b[(ib + 1) % 3]
716 }
717
718 if !same_orientation(e.get(), &new_tri_idx) {
719 #[cfg(feature = "enhanced-determinism")]
723 let _ = e.swap_remove();
724 #[cfg(not(feature = "enhanced-determinism"))]
725 let _ = e.remove();
726 }
727 }
728 }
729}
730
731fn is_topologically_degenerate(tri_idx: [u32; 3]) -> bool {
732 tri_idx[0] == tri_idx[1] || tri_idx[0] == tri_idx[2] || tri_idx[1] == tri_idx[2]
733}
734
735#[cfg(feature = "wavefront")]
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use crate::math::Vector;
740 use crate::shape::{Ball, Cuboid, TriMeshFlags};
741 use obj::Obj;
742 use obj::ObjData;
743
744 #[test]
745 fn test_same_mesh_intersection() {
746 let Obj {
747 data: ObjData {
748 position, objects, ..
749 },
750 ..
751 } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap();
752
753 let mesh = TriMesh::with_flags(
754 position
755 .iter()
756 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
757 .collect::<Vec<_>>(),
758 objects[0].groups[0]
759 .polys
760 .iter()
761 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
762 .collect::<Vec<_>>(),
763 TriMeshFlags::all(),
764 )
765 .unwrap();
766
767 let _ = intersect_meshes(
768 &Pose::identity(),
769 &mesh,
770 false,
771 &Pose::identity(),
772 &mesh,
773 false,
774 )
775 .unwrap()
776 .unwrap();
777
778 let _ = mesh.to_obj_file(&PathBuf::from("same_test.obj"));
779 }
780
781 #[test]
782 fn test_non_origin_pos1_pos2_intersection() {
783 let ball = Ball::new(2f32 as Real).to_trimesh(10, 10);
784 let cuboid = Cuboid::new(Vector::new(2.0, 1.0, 1.0)).to_trimesh();
785 let mut sphere_mesh = TriMesh::new(ball.0, ball.1).unwrap();
786 sphere_mesh.set_flags(TriMeshFlags::all()).unwrap();
787 let mut cuboid_mesh = TriMesh::new(cuboid.0, cuboid.1).unwrap();
788 cuboid_mesh.set_flags(TriMeshFlags::all()).unwrap();
789
790 let res = intersect_meshes(
791 &Pose::translation(1.0, 0.0, 0.0),
792 &cuboid_mesh,
793 false,
794 &Pose::translation(2.0, 0.0, 0.0),
795 &sphere_mesh,
796 false,
797 )
798 .unwrap()
799 .unwrap();
800
801 let _ = res.to_obj_file(&PathBuf::from("test_non_origin_pos1_pos2_intersection.obj"));
802
803 let bounding_sphere = res.local_bounding_sphere();
804 assert!(bounding_sphere.center == Vector::new(1.5, 0.0, 0.0));
805 assert_relative_eq!(2.0615528, bounding_sphere.radius, epsilon = 1.0e-5);
806 }
807
808 #[test]
809 fn test_offset_cylinder_intersection() {
810 let Obj {
811 data: ObjData {
812 position, objects, ..
813 },
814 ..
815 } = Obj::load("../../assets/tests/offset_cylinder.obj").unwrap();
816
817 let offset_mesh = TriMesh::with_flags(
818 position
819 .iter()
820 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
821 .collect::<Vec<_>>(),
822 objects[0].groups[0]
823 .polys
824 .iter()
825 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
826 .collect::<Vec<_>>(),
827 TriMeshFlags::all(),
828 )
829 .unwrap();
830
831 let Obj {
832 data: ObjData {
833 position, objects, ..
834 },
835 ..
836 } = Obj::load("../../assets/tests/center_cylinder.obj").unwrap();
837
838 let center_mesh = TriMesh::with_flags(
839 position
840 .iter()
841 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
842 .collect::<Vec<_>>(),
843 objects[0].groups[0]
844 .polys
845 .iter()
846 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
847 .collect::<Vec<_>>(),
848 TriMeshFlags::all(),
849 )
850 .unwrap();
851
852 let res = intersect_meshes(
853 &Pose::identity(),
854 ¢er_mesh,
855 false,
856 &Pose::identity(),
857 &offset_mesh,
858 false,
859 )
860 .unwrap()
861 .unwrap();
862
863 let _ = res.to_obj_file(&PathBuf::from("offset_test.obj"));
864 }
865
866 #[test]
867 fn test_stair_bar_intersection() {
868 let Obj {
869 data: ObjData {
870 position, objects, ..
871 },
872 ..
873 } = Obj::load("../../assets/tests/stairs.obj").unwrap();
874
875 let stair_mesh = TriMesh::with_flags(
876 position
877 .iter()
878 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
879 .collect::<Vec<_>>(),
880 objects[0].groups[0]
881 .polys
882 .iter()
883 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
884 .collect::<Vec<_>>(),
885 TriMeshFlags::all(),
886 )
887 .unwrap();
888
889 let Obj {
890 data: ObjData {
891 position, objects, ..
892 },
893 ..
894 } = Obj::load("../../assets/tests/bar.obj").unwrap();
895
896 let bar_mesh = TriMesh::with_flags(
897 position
898 .iter()
899 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
900 .collect::<Vec<_>>(),
901 objects[0].groups[0]
902 .polys
903 .iter()
904 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
905 .collect::<Vec<_>>(),
906 TriMeshFlags::all(),
907 )
908 .unwrap();
909
910 let res = intersect_meshes(
911 &Pose::identity(),
912 &stair_mesh,
913 false,
914 &Pose::identity(),
915 &bar_mesh,
916 false,
917 )
918 .unwrap()
919 .unwrap();
920
921 let _ = res.to_obj_file(&PathBuf::from("stair_test.obj"));
922 }
923
924 #[test]
925 fn test_complex_intersection() {
926 let Obj {
927 data: ObjData {
928 position, objects, ..
929 },
930 ..
931 } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap();
932
933 let bunny_mesh = TriMesh::with_flags(
934 position
935 .iter()
936 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
937 .collect::<Vec<_>>(),
938 objects[0].groups[0]
939 .polys
940 .iter()
941 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
942 .collect::<Vec<_>>(),
943 TriMeshFlags::all(),
944 )
945 .unwrap();
946
947 let Obj {
948 data: ObjData {
949 position, objects, ..
950 },
951 ..
952 } = Obj::load("../../assets/tests/poly_cylinder.obj").unwrap();
953
954 let cylinder_mesh = TriMesh::with_flags(
955 position
956 .iter()
957 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
958 .collect::<Vec<_>>(),
959 objects[0].groups[0]
960 .polys
961 .iter()
962 .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32])
963 .collect::<Vec<_>>(),
964 TriMeshFlags::all(),
965 )
966 .unwrap();
967
968 let res = intersect_meshes(
969 &Pose::identity(),
970 &bunny_mesh,
971 false,
972 &Pose::identity(),
973 &cylinder_mesh,
974 true,
975 )
976 .unwrap()
977 .unwrap();
978
979 let _ = res.to_obj_file(&PathBuf::from("complex_test.obj"));
980 }
981}