parry3d/query/split/
split_trimesh.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Pose, Real, Vector, VectorExt};
3use crate::query::{IntersectResult, PointQuery, SplitResult};
4use crate::shape::{Cuboid, FeatureId, Polyline, Segment, Shape, TriMesh, TriMeshFlags, Triangle};
5use crate::transformation::{intersect_meshes, MeshIntersectionError};
6use crate::utils::{hashmap::HashMap, SortedPair, WBasis};
7use alloc::{vec, vec::Vec};
8use core::cmp::Ordering;
9use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _};
10
11struct Triangulation {
12    delaunay: ConstrainedDelaunayTriangulation<spade::Point2<Real>>,
13    basis: [Vector; 2],
14    basis_origin: Vector,
15    spade2index: HashMap<FixedVertexHandle, u32>,
16    index2spade: HashMap<u32, FixedVertexHandle>,
17}
18
19impl Triangulation {
20    fn new(axis: Vector, basis_origin: Vector) -> Self {
21        Triangulation {
22            delaunay: ConstrainedDelaunayTriangulation::new(),
23            basis: axis.orthonormal_basis(),
24            basis_origin,
25            spade2index: HashMap::default(),
26            index2spade: HashMap::default(),
27        }
28    }
29
30    fn project(&self, pt: Vector) -> spade::Point2<Real> {
31        let dpt = pt - self.basis_origin;
32        spade::Point2::new(dpt.dot(self.basis[0]), dpt.dot(self.basis[1]))
33    }
34
35    fn add_edge(&mut self, id1: u32, id2: u32, points: &[Vector]) {
36        let proj1 = self.project(points[id1 as usize]);
37        let proj2 = self.project(points[id2 as usize]);
38
39        let handle1 = *self.index2spade.entry(id1).or_insert_with(|| {
40            let h = self.delaunay.insert(proj1).unwrap();
41            let _ = self.spade2index.insert(h, id1);
42            h
43        });
44
45        let handle2 = *self.index2spade.entry(id2).or_insert_with(|| {
46            let h = self.delaunay.insert(proj2).unwrap();
47            let _ = self.spade2index.insert(h, id2);
48            h
49        });
50
51        // NOTE: the naming of the `ConstrainedDelaunayTriangulation::can_add_constraint` method is misleading.
52        if !self.delaunay.can_add_constraint(handle1, handle2) {
53            let _ = self.delaunay.add_constraint(handle1, handle2);
54        }
55    }
56}
57
58impl TriMesh {
59    /// Splits this `TriMesh` along the given canonical axis.
60    ///
61    /// This will split the Aabb by a plane with a normal with it’s `axis`-th component set to 1.
62    /// The splitting plane is shifted wrt. the origin by the `bias` (i.e. it passes through the point
63    /// equal to `normal * bias`).
64    ///
65    /// # Result
66    /// Returns the result of the split. The first mesh returned is the piece lying on the negative
67    /// half-space delimited by the splitting plane. The second mesh returned is the piece lying on the
68    /// positive half-space delimited by the splitting plane.
69    pub fn canonical_split(&self, axis: usize, bias: Real, epsilon: Real) -> SplitResult<Self> {
70        // TODO: optimize this.
71        self.local_split(Vector::ith(axis, 1.0), bias, epsilon)
72    }
73
74    /// Splits this mesh, transformed by `position` by a plane identified by its normal `local_axis`
75    /// and the `bias` (i.e. the plane passes through the point equal to `normal * bias`).
76    pub fn split(
77        &self,
78        position: &Pose,
79        axis: Vector,
80        bias: Real,
81        epsilon: Real,
82    ) -> SplitResult<Self> {
83        let local_axis = position.rotation.inverse() * axis;
84        let added_bias = -position.translation.dot(axis);
85        self.local_split(local_axis, bias + added_bias, epsilon)
86    }
87
88    /// Splits this mesh by a plane identified by its normal `local_axis`
89    /// and the `bias` (i.e. the plane passes through the point equal to `normal * bias`).
90    pub fn local_split(&self, local_axis: Vector, bias: Real, epsilon: Real) -> SplitResult<Self> {
91        let mut triangulation = if self.pseudo_normals_if_oriented().is_some() {
92            Some(Triangulation::new(local_axis, self.vertices()[0]))
93        } else {
94            None
95        };
96
97        // 1. Partition the vertices.
98        let vertices = self.vertices();
99        let indices = self.indices();
100        let mut colors = vec![0u8; self.vertices().len()];
101
102        // Color 0 = on plane.
103        //       1 = on negative half-space.
104        //       2 = on positive half-space.
105        let mut found_negative = false;
106        let mut found_positive = false;
107        for (i, pt) in vertices.iter().enumerate() {
108            let dist_to_plane = pt.dot(local_axis) - bias;
109            if dist_to_plane < -epsilon {
110                found_negative = true;
111                colors[i] = 1;
112            } else if dist_to_plane > epsilon {
113                found_positive = true;
114                colors[i] = 2;
115            }
116        }
117
118        // Exit early if `self` isn’t crossed by the plane.
119        if !found_negative {
120            return SplitResult::Positive;
121        }
122
123        if !found_positive {
124            return SplitResult::Negative;
125        }
126
127        // 2. Split the triangles.
128        let mut intersections_found = HashMap::default();
129        let mut new_indices = indices.to_vec();
130        let mut new_vertices = vertices.to_vec();
131
132        for (tri_id, idx) in indices.iter().enumerate() {
133            let mut intersection_features = (FeatureId::Unknown, FeatureId::Unknown);
134
135            // First, find where the plane intersects the triangle.
136            for ia in 0..3 {
137                let ib = (ia + 1) % 3;
138                let idx_a = idx[ia as usize];
139                let idx_b = idx[ib as usize];
140
141                let fid = match (colors[idx_a as usize], colors[idx_b as usize]) {
142                    (1, 2) | (2, 1) => FeatureId::Edge(ia),
143                    // NOTE: the case (_, 0) will be dealt with in the next loop iteration.
144                    (0, _) => FeatureId::Vertex(ia),
145                    _ => continue,
146                };
147
148                if intersection_features.0 == FeatureId::Unknown {
149                    intersection_features.0 = fid;
150                } else {
151                    // TODO: this assertion may fire if the triangle is coplanar with the edge?
152                    // assert_eq!(intersection_features.1, FeatureId::Unknown);
153                    intersection_features.1 = fid;
154                }
155            }
156
157            // Helper that intersects an edge with the plane.
158            let mut intersect_edge = |idx_a, idx_b| {
159                *intersections_found
160                    .entry(SortedPair::new(idx_a, idx_b))
161                    .or_insert_with(|| {
162                        let segment = Segment::new(
163                            new_vertices[idx_a as usize],
164                            new_vertices[idx_b as usize],
165                        );
166                        // Intersect the segment with the plane.
167                        if let Some((intersection, _)) = segment
168                            .local_split_and_get_intersection(local_axis, bias, epsilon)
169                            .1
170                        {
171                            new_vertices.push(intersection);
172                            colors.push(0);
173                            (new_vertices.len() - 1) as u32
174                        } else {
175                            unreachable!()
176                        }
177                    })
178            };
179
180            // Perform the intersection, push new triangles, and update
181            // triangulation constraints if needed.
182            match intersection_features {
183                (_, FeatureId::Unknown) => {
184                    // The plane doesn’t intersect the triangle, or intersects it at
185                    // a single vertex, so we don’t have anything to do.
186                    assert!(
187                        matches!(intersection_features.0, FeatureId::Unknown)
188                            || matches!(intersection_features.0, FeatureId::Vertex(_))
189                    );
190                }
191                (FeatureId::Vertex(v1), FeatureId::Vertex(v2)) => {
192                    // The plane intersects the triangle along one of its edge.
193                    // We don’t have to split the triangle, but we need to add
194                    // a constraint to the triangulation.
195                    if let Some(triangulation) = &mut triangulation {
196                        let id1 = idx[v1 as usize];
197                        let id2 = idx[v2 as usize];
198                        triangulation.add_edge(id1, id2, &new_vertices);
199                    }
200                }
201                (FeatureId::Vertex(iv), FeatureId::Edge(ie))
202                | (FeatureId::Edge(ie), FeatureId::Vertex(iv)) => {
203                    // The plane splits the triangle into exactly two triangles.
204                    let ia = ie;
205                    let ib = (ie + 1) % 3;
206                    let ic = (ie + 2) % 3;
207                    let idx_a = idx[ia as usize];
208                    let idx_b = idx[ib as usize];
209                    let idx_c = idx[ic as usize];
210                    assert_eq!(iv, ic);
211
212                    let intersection_idx = intersect_edge(idx_a, idx_b);
213
214                    // Compute the indices of the two triangles.
215                    let new_tri_a = [idx_c, idx_a, intersection_idx];
216                    let new_tri_b = [idx_b, idx_c, intersection_idx];
217
218                    new_indices[tri_id] = new_tri_a;
219                    new_indices.push(new_tri_b);
220
221                    if let Some(triangulation) = &mut triangulation {
222                        triangulation.add_edge(intersection_idx, idx_c, &new_vertices);
223                    }
224                }
225                (FeatureId::Edge(mut e1), FeatureId::Edge(mut e2)) => {
226                    // The plane splits the triangle into 1 + 2 triangles.
227                    // First, make sure the edge indices are consecutive.
228                    if e2 != (e1 + 1) % 3 {
229                        core::mem::swap(&mut e1, &mut e2);
230                    }
231
232                    let ia = e2; // The first point of the second edge is the vertex shared by both edges.
233                    let ib = (e2 + 1) % 3;
234                    let ic = (e2 + 2) % 3;
235                    let idx_a = idx[ia as usize];
236                    let idx_b = idx[ib as usize];
237                    let idx_c = idx[ic as usize];
238
239                    let intersection1 = intersect_edge(idx_c, idx_a);
240                    let intersection2 = intersect_edge(idx_a, idx_b);
241
242                    let new_tri1 = [idx_a, intersection2, intersection1];
243                    let new_tri2 = [intersection2, idx_b, idx_c];
244                    let new_tri3 = [intersection2, idx_c, intersection1];
245                    new_indices[tri_id] = new_tri1;
246                    new_indices.push(new_tri2);
247                    new_indices.push(new_tri3);
248
249                    if let Some(triangulation) = &mut triangulation {
250                        triangulation.add_edge(intersection1, intersection2, &new_vertices);
251                    }
252                }
253                _ => unreachable!(),
254            }
255        }
256
257        // 3. Partition the new triangles into two trimeshes.
258        let mut vertices_lhs = vec![];
259        let mut vertices_rhs = vec![];
260        let mut indices_lhs = vec![];
261        let mut indices_rhs = vec![];
262        let mut remap = vec![];
263
264        for i in 0..new_vertices.len() {
265            match colors[i] {
266                0 => {
267                    remap.push((vertices_lhs.len() as u32, vertices_rhs.len() as u32));
268                    vertices_lhs.push(new_vertices[i]);
269                    vertices_rhs.push(new_vertices[i]);
270                }
271                1 => {
272                    remap.push((vertices_lhs.len() as u32, u32::MAX));
273                    vertices_lhs.push(new_vertices[i]);
274                }
275                2 => {
276                    remap.push((u32::MAX, vertices_rhs.len() as u32));
277                    vertices_rhs.push(new_vertices[i]);
278                }
279                _ => unreachable!(),
280            }
281        }
282
283        for idx in new_indices {
284            let idx = [idx[0] as usize, idx[1] as usize, idx[2] as usize]; // Convert to usize.
285            let colors = [colors[idx[0]], colors[idx[1]], colors[idx[2]]];
286            let remap = [remap[idx[0]], remap[idx[1]], remap[idx[2]]];
287
288            if colors[0] == 1 || colors[1] == 1 || colors[2] == 1 {
289                assert!(colors[0] != 2 && colors[1] != 2 && colors[2] != 2);
290                indices_lhs.push([remap[0].0, remap[1].0, remap[2].0]);
291            } else if colors[0] == 2 || colors[1] == 2 || colors[2] == 2 {
292                assert!(colors[0] != 1 && colors[1] != 1 && colors[2] != 1);
293                indices_rhs.push([remap[0].1, remap[1].1, remap[2].1]);
294            } else {
295                // The colors are all 0, so push into both trimeshes.
296                indices_lhs.push([remap[0].0, remap[1].0, remap[2].0]);
297                indices_rhs.push([remap[0].1, remap[1].1, remap[2].1]);
298            }
299        }
300
301        // Push the triangulation if there is one.
302        if let Some(triangulation) = triangulation {
303            for face in triangulation.delaunay.inner_faces() {
304                let vtx = face.vertices();
305                let mut idx1 = [0; 3];
306                let mut idx2 = [0; 3];
307                for k in 0..3 {
308                    let vid = triangulation.spade2index[&vtx[k].fix()];
309                    assert_eq!(colors[vid as usize], 0);
310                    idx1[k] = remap[vid as usize].0;
311                    idx2[k] = remap[vid as usize].1;
312                }
313
314                let tri = Triangle::new(
315                    vertices_lhs[idx1[0] as usize],
316                    vertices_lhs[idx1[1] as usize],
317                    vertices_lhs[idx1[2] as usize],
318                );
319
320                if self.contains_local_point(tri.center()) {
321                    indices_lhs.push(idx1);
322
323                    idx2.swap(1, 2); // Flip orientation for the second half of the split.
324                    indices_rhs.push(idx2);
325                }
326            }
327        }
328
329        // TODO: none of the index buffers should be empty at this point unless perhaps
330        //       because of some rounding errors?
331        //       Should we just panic if they are empty?
332        if indices_rhs.is_empty() {
333            SplitResult::Negative
334        } else if indices_lhs.is_empty() {
335            SplitResult::Positive
336        } else {
337            let mesh_lhs = TriMesh::new(vertices_lhs, indices_lhs).unwrap();
338            let mesh_rhs = TriMesh::new(vertices_rhs, indices_rhs).unwrap();
339            SplitResult::Pair(mesh_lhs, mesh_rhs)
340        }
341    }
342
343    /// Computes the intersection [`Polyline`]s between this mesh and the plane identified by
344    /// the given canonical axis.
345    ///
346    /// This will intersect the mesh by a plane with a normal with it’s `axis`-th component set to 1.
347    /// The splitting plane is shifted wrt. the origin by the `bias` (i.e. it passes through the point
348    /// equal to `normal * bias`).
349    ///
350    /// Note that the resultant polyline may have multiple connected components
351    pub fn canonical_intersection_with_plane(
352        &self,
353        axis: usize,
354        bias: Real,
355        epsilon: Real,
356    ) -> IntersectResult<Polyline> {
357        self.intersection_with_local_plane(Vector::ith(axis, 1.0), bias, epsilon)
358    }
359
360    /// Computes the intersection [`Polyline`]s between this mesh, transformed by `position`,
361    /// and a plane identified by its normal `axis` and the `bias`
362    /// (i.e. the plane passes through the point equal to `normal * bias`).
363    pub fn intersection_with_plane(
364        &self,
365        position: &Pose,
366        axis: Vector,
367        bias: Real,
368        epsilon: Real,
369    ) -> IntersectResult<Polyline> {
370        let local_axis = position.rotation.inverse() * axis;
371        let added_bias = -position.translation.dot(axis);
372        self.intersection_with_local_plane(local_axis, bias + added_bias, epsilon)
373    }
374
375    /// Computes the intersection [`Polyline`]s between this mesh
376    /// and a plane identified by its normal `local_axis`
377    /// and the `bias` (i.e. the plane passes through the point equal to `normal * bias`).
378    pub fn intersection_with_local_plane(
379        &self,
380        local_axis: Vector,
381        bias: Real,
382        epsilon: Real,
383    ) -> IntersectResult<Polyline> {
384        // 1. Partition the vertices.
385        let vertices = self.vertices();
386        let indices = self.indices();
387        let mut colors = vec![0u8; self.vertices().len()];
388
389        // Color 0 = on plane.
390        //       1 = on negative half-space.
391        //       2 = on positive half-space.
392        let mut found_negative = false;
393        let mut found_positive = false;
394        for (i, pt) in vertices.iter().enumerate() {
395            let dist_to_plane = pt.dot(local_axis) - bias;
396            if dist_to_plane < -epsilon {
397                found_negative = true;
398                colors[i] = 1;
399            } else if dist_to_plane > epsilon {
400                found_positive = true;
401                colors[i] = 2;
402            }
403        }
404
405        // Exit early if `self` isn’t crossed by the plane.
406        if !found_negative {
407            return IntersectResult::Positive;
408        }
409
410        if !found_positive {
411            return IntersectResult::Negative;
412        }
413
414        // 2. Split the triangles.
415        let mut index_adjacencies: Vec<Vec<usize>> = Vec::new(); // Adjacency list of indices
416
417        // Helper functions for adding polyline segments to the adjacency list
418        let mut add_segment_adjacencies = |idx_a: usize, idx_b| {
419            assert!(idx_a <= index_adjacencies.len());
420
421            match idx_a.cmp(&index_adjacencies.len()) {
422                Ordering::Less => index_adjacencies[idx_a].push(idx_b),
423                Ordering::Equal => index_adjacencies.push(vec![idx_b]),
424                Ordering::Greater => {}
425            }
426        };
427        let mut add_segment_adjacencies_symmetric = |idx_a: usize, idx_b| {
428            if idx_a < idx_b {
429                add_segment_adjacencies(idx_a, idx_b);
430                add_segment_adjacencies(idx_b, idx_a);
431            } else {
432                add_segment_adjacencies(idx_b, idx_a);
433                add_segment_adjacencies(idx_a, idx_b);
434            }
435        };
436
437        let mut intersections_found = HashMap::default();
438        let mut existing_vertices_found = HashMap::default();
439        let mut new_vertices = Vec::new();
440
441        for idx in indices.iter() {
442            let mut intersection_features = (FeatureId::Unknown, FeatureId::Unknown);
443
444            // First, find where the plane intersects the triangle.
445            for ia in 0..3 {
446                let ib = (ia + 1) % 3;
447                let idx_a = idx[ia as usize];
448                let idx_b = idx[ib as usize];
449
450                let fid = match (colors[idx_a as usize], colors[idx_b as usize]) {
451                    (1, 2) | (2, 1) => FeatureId::Edge(ia),
452                    // NOTE: the case (_, 0) will be dealt with in the next loop iteration.
453                    (0, _) => FeatureId::Vertex(ia),
454                    _ => continue,
455                };
456
457                if intersection_features.0 == FeatureId::Unknown {
458                    intersection_features.0 = fid;
459                } else {
460                    // TODO: this assertion may fire if the triangle is coplanar with the edge?
461                    // assert_eq!(intersection_features.1, FeatureId::Unknown);
462                    intersection_features.1 = fid;
463                }
464            }
465
466            // Helper that intersects an edge with the plane.
467            let mut intersect_edge = |idx_a, idx_b| {
468                *intersections_found
469                    .entry(SortedPair::new(idx_a, idx_b))
470                    .or_insert_with(|| {
471                        let segment =
472                            Segment::new(vertices[idx_a as usize], vertices[idx_b as usize]);
473                        // Intersect the segment with the plane.
474                        if let Some((intersection, _)) = segment
475                            .local_split_and_get_intersection(local_axis, bias, epsilon)
476                            .1
477                        {
478                            new_vertices.push(intersection);
479                            colors.push(0);
480                            new_vertices.len() - 1
481                        } else {
482                            unreachable!()
483                        }
484                    })
485            };
486
487            // Perform the intersection, push new triangles, and update
488            // triangulation constraints if needed.
489            match intersection_features {
490                (_, FeatureId::Unknown) => {
491                    // The plane doesn’t intersect the triangle, or intersects it at
492                    // a single vertex, so we don’t have anything to do.
493                    assert!(
494                        matches!(intersection_features.0, FeatureId::Unknown)
495                            || matches!(intersection_features.0, FeatureId::Vertex(_))
496                    );
497                }
498                (FeatureId::Vertex(iv1), FeatureId::Vertex(iv2)) => {
499                    // The plane intersects the triangle along one of its edge.
500                    // We don’t have to split the triangle, but we need to add
501                    // the edge to the polyline indices
502
503                    let id1 = idx[iv1 as usize];
504                    let id2 = idx[iv2 as usize];
505
506                    let out_id1 = *existing_vertices_found.entry(id1).or_insert_with(|| {
507                        let v1 = vertices[id1 as usize];
508
509                        new_vertices.push(v1);
510                        new_vertices.len() - 1
511                    });
512                    let out_id2 = *existing_vertices_found.entry(id2).or_insert_with(|| {
513                        let v2 = vertices[id2 as usize];
514
515                        new_vertices.push(v2);
516                        new_vertices.len() - 1
517                    });
518
519                    add_segment_adjacencies_symmetric(out_id1, out_id2);
520                }
521                (FeatureId::Vertex(iv), FeatureId::Edge(ie))
522                | (FeatureId::Edge(ie), FeatureId::Vertex(iv)) => {
523                    // The plane splits the triangle into exactly two triangles.
524                    let ia = ie;
525                    let ib = (ie + 1) % 3;
526                    let ic = (ie + 2) % 3;
527                    let idx_a = idx[ia as usize];
528                    let idx_b = idx[ib as usize];
529                    let idx_c = idx[ic as usize];
530                    assert_eq!(iv, ic);
531
532                    let intersection_idx = intersect_edge(idx_a, idx_b);
533
534                    let out_idx_c = *existing_vertices_found.entry(idx_c).or_insert_with(|| {
535                        let v2 = vertices[idx_c as usize];
536
537                        new_vertices.push(v2);
538                        new_vertices.len() - 1
539                    });
540
541                    add_segment_adjacencies_symmetric(out_idx_c, intersection_idx);
542                }
543                (FeatureId::Edge(mut e1), FeatureId::Edge(mut e2)) => {
544                    // The plane splits the triangle into 1 + 2 triangles.
545                    // First, make sure the edge indices are consecutive.
546                    if e2 != (e1 + 1) % 3 {
547                        core::mem::swap(&mut e1, &mut e2);
548                    }
549
550                    let ia = e2; // The first point of the second edge is the vertex shared by both edges.
551                    let ib = (e2 + 1) % 3;
552                    let ic = (e2 + 2) % 3;
553                    let idx_a = idx[ia as usize];
554                    let idx_b = idx[ib as usize];
555                    let idx_c = idx[ic as usize];
556
557                    let intersection1 = intersect_edge(idx_c, idx_a);
558                    let intersection2 = intersect_edge(idx_a, idx_b);
559
560                    add_segment_adjacencies_symmetric(intersection1, intersection2);
561                }
562                _ => unreachable!(),
563            }
564        }
565
566        // 3. Ensure consistent edge orientation by traversing the adjacency list
567        let mut polyline_indices: Vec<[u32; 2]> = Vec::with_capacity(index_adjacencies.len() + 1);
568
569        let mut seen = vec![false; index_adjacencies.len()];
570        for (idx, neighbors) in index_adjacencies.iter().enumerate() {
571            if !seen[idx] {
572                // Start a new component
573                // Traverse the adjencies until the loop closes
574
575                let first = idx;
576                let mut prev = first;
577                let mut next = neighbors.first(); // Arbitrary neighbor
578
579                'traversal: while let Some(current) = next {
580                    seen[*current] = true;
581                    polyline_indices.push([prev as u32, *current as u32]);
582
583                    for neighbor in index_adjacencies[*current].iter() {
584                        if *neighbor != prev && *neighbor != first {
585                            prev = *current;
586                            next = Some(neighbor);
587                            continue 'traversal;
588                        } else if *neighbor != prev && *neighbor == first {
589                            // If the next index is same as the first, close the polyline and exit
590                            polyline_indices.push([*current as u32, first as u32]);
591                            next = None;
592                            continue 'traversal;
593                        }
594                    }
595                }
596            }
597        }
598
599        IntersectResult::Intersect(Polyline::new(new_vertices, Some(polyline_indices)))
600    }
601
602    /// Computes the intersection mesh between an Aabb and this mesh.
603    pub fn intersection_with_aabb(
604        &self,
605        position: &Pose,
606        flip_mesh: bool,
607        aabb: &Aabb,
608        flip_cuboid: bool,
609        epsilon: Real,
610    ) -> Result<Option<Self>, MeshIntersectionError> {
611        let cuboid = Cuboid::new(aabb.half_extents());
612        let cuboid_pos = Pose::from_translation(aabb.center());
613        self.intersection_with_cuboid(
614            position,
615            flip_mesh,
616            &cuboid,
617            &cuboid_pos,
618            flip_cuboid,
619            epsilon,
620        )
621    }
622
623    /// Computes the intersection mesh between a cuboid and this mesh transformed by `position`.
624    pub fn intersection_with_cuboid(
625        &self,
626        position: &Pose,
627        flip_mesh: bool,
628        cuboid: &Cuboid,
629        cuboid_position: &Pose,
630        flip_cuboid: bool,
631        epsilon: Real,
632    ) -> Result<Option<Self>, MeshIntersectionError> {
633        self.intersection_with_local_cuboid(
634            flip_mesh,
635            cuboid,
636            &position.inv_mul(cuboid_position),
637            flip_cuboid,
638            epsilon,
639        )
640    }
641
642    /// Computes the intersection mesh between a cuboid and this mesh.
643    pub fn intersection_with_local_cuboid(
644        &self,
645        flip_mesh: bool,
646        cuboid: &Cuboid,
647        cuboid_position: &Pose,
648        flip_cuboid: bool,
649        _epsilon: Real,
650    ) -> Result<Option<Self>, MeshIntersectionError> {
651        if self.topology().is_some() && self.pseudo_normals_if_oriented().is_some() {
652            let (cuboid_vtx, cuboid_idx) = cuboid.to_trimesh();
653            let cuboid_trimesh = TriMesh::with_flags(
654                cuboid_vtx,
655                cuboid_idx,
656                TriMeshFlags::HALF_EDGE_TOPOLOGY | TriMeshFlags::ORIENTED,
657            )
658            // `TriMesh::with_flags` can't fail for `cuboid.to_trimesh()`.
659            .unwrap();
660
661            let intersect_meshes = intersect_meshes(
662                &Pose::IDENTITY,
663                self,
664                flip_mesh,
665                cuboid_position,
666                &cuboid_trimesh,
667                flip_cuboid,
668            );
669            return intersect_meshes;
670        }
671
672        let cuboid_aabb = cuboid.compute_aabb(cuboid_position);
673        let intersecting_tris: Vec<_> = self.bvh().intersect_aabb(&cuboid_aabb).collect();
674
675        if intersecting_tris.is_empty() {
676            return Ok(None);
677        }
678
679        // First, very naive version that outputs a triangle soup without
680        // index buffer (shared vertices are duplicated).
681        let vertices = self.vertices();
682        let indices = self.indices();
683
684        let mut clip_workspace = vec![];
685        let mut new_vertices = vec![];
686        let mut new_indices = vec![];
687        let aabb = cuboid.local_aabb();
688        let inv_pos = cuboid_position.inverse();
689        let mut to_clip = vec![];
690
691        for tri in intersecting_tris {
692            let idx = indices[tri as usize];
693            to_clip.extend_from_slice(&[
694                inv_pos * vertices[idx[0] as usize],
695                inv_pos * vertices[idx[1] as usize],
696                inv_pos * vertices[idx[2] as usize],
697            ]);
698
699            // There is no need to clip if the triangle is fully inside of the Aabb.
700            // Note that we can’t take a shortcut for the case where all the vertices are
701            // outside of the Aabb, because the Aabb can still instersect the edges or face.
702            if !(aabb.contains_local_point(to_clip[0])
703                && aabb.contains_local_point(to_clip[1])
704                && aabb.contains_local_point(to_clip[2]))
705            {
706                aabb.clip_polygon_with_workspace(&mut to_clip, &mut clip_workspace);
707            }
708
709            if to_clip.len() >= 3 {
710                let base_i = new_vertices.len();
711                for i in 1..to_clip.len() - 1 {
712                    new_indices.push([base_i as u32, (base_i + i) as u32, (base_i + i + 1) as u32]);
713                }
714                new_vertices.append(&mut to_clip);
715            }
716        }
717
718        // The clipping outputs points in the local-space of the cuboid.
719        // So we need to transform it back.
720        for pt in &mut new_vertices {
721            *pt = cuboid_position * *pt;
722        }
723
724        Ok(if new_vertices.len() >= 3 {
725            Some(TriMesh::new(new_vertices, new_indices).unwrap())
726        } else {
727            None
728        })
729    }
730}