parry3d/shape/
polyline.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Isometry, Point, Real, Vector};
3use crate::partitioning::{Bvh, BvhBuildStrategy};
4use crate::query::{PointProjection, PointQueryWithLocation};
5use crate::shape::composite_shape::CompositeShape;
6use crate::shape::{FeatureId, Segment, SegmentPointLocation, Shape, TypedCompositeShape};
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9
10use crate::query::details::NormalConstraints;
11
12#[derive(Clone, Debug)]
13#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
14#[cfg_attr(
15    feature = "rkyv",
16    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize),
17    archive(check_bytes)
18)]
19/// A polyline.
20pub struct Polyline {
21    bvh: Bvh,
22    vertices: Vec<Point<Real>>,
23    indices: Vec<[u32; 2]>,
24}
25
26impl Polyline {
27    /// Creates a new polyline from a vertex buffer and an index buffer.
28    pub fn new(vertices: Vec<Point<Real>>, indices: Option<Vec<[u32; 2]>>) -> Self {
29        let indices =
30            indices.unwrap_or_else(|| (0..vertices.len() as u32 - 1).map(|i| [i, i + 1]).collect());
31        let leaves = indices.iter().enumerate().map(|(i, idx)| {
32            let aabb =
33                Segment::new(vertices[idx[0] as usize], vertices[idx[1] as usize]).local_aabb();
34            (i, aabb)
35        });
36
37        // NOTE: we apply no dilation factor because we won't
38        // update this tree dynamically.
39        let bvh = Bvh::from_iter(BvhBuildStrategy::Binned, leaves);
40
41        Self {
42            bvh,
43            vertices,
44            indices,
45        }
46    }
47
48    /// Compute the axis-aligned bounding box of this polyline.
49    pub fn aabb(&self, pos: &Isometry<Real>) -> Aabb {
50        self.bvh.root_aabb().transform_by(pos)
51    }
52
53    /// Gets the local axis-aligned bounding box of this polyline.
54    pub fn local_aabb(&self) -> Aabb {
55        self.bvh.root_aabb()
56    }
57
58    /// The BVH acceleration structure for this polyline.
59    pub fn bvh(&self) -> &Bvh {
60        &self.bvh
61    }
62
63    /// The number of segments forming this polyline.
64    pub fn num_segments(&self) -> usize {
65        self.indices.len()
66    }
67
68    /// An iterator through all the segments of this mesh.
69    pub fn segments(&self) -> impl ExactSizeIterator<Item = Segment> + '_ {
70        self.indices.iter().map(move |ids| {
71            Segment::new(
72                self.vertices[ids[0] as usize],
73                self.vertices[ids[1] as usize],
74            )
75        })
76    }
77
78    /// Get the `i`-th segment of this mesh.
79    pub fn segment(&self, i: u32) -> Segment {
80        let idx = self.indices[i as usize];
81        Segment::new(
82            self.vertices[idx[0] as usize],
83            self.vertices[idx[1] as usize],
84        )
85    }
86
87    /// Transforms  the feature-id of a segment to the feature-id of this polyline.
88    pub fn segment_feature_to_polyline_feature(
89        &self,
90        segment: u32,
91        _feature: FeatureId,
92    ) -> FeatureId {
93        // TODO: return a vertex feature when it makes sense.
94        #[cfg(feature = "dim2")]
95        return FeatureId::Face(segment);
96        #[cfg(feature = "dim3")]
97        return FeatureId::Edge(segment);
98    }
99
100    /// The vertex buffer of this mesh.
101    pub fn vertices(&self) -> &[Point<Real>] {
102        &self.vertices[..]
103    }
104
105    /// The index buffer of this mesh.
106    pub fn indices(&self) -> &[[u32; 2]] {
107        &self.indices
108    }
109
110    /// A flat view of the index buffer of this mesh.
111    pub fn flat_indices(&self) -> &[u32] {
112        unsafe {
113            let len = self.indices.len() * 2;
114            let data = self.indices.as_ptr() as *const u32;
115            core::slice::from_raw_parts(data, len)
116        }
117    }
118
119    /// Computes a scaled version of this polyline.
120    pub fn scaled(mut self, scale: &Vector<Real>) -> Self {
121        self.vertices
122            .iter_mut()
123            .for_each(|pt| pt.coords.component_mul_assign(scale));
124        let mut bvh = self.bvh.clone();
125        bvh.scale(scale);
126        Self {
127            bvh,
128            vertices: self.vertices,
129            indices: self.indices,
130        }
131    }
132
133    /// Reverse the orientation of this polyline by swapping the indices of all
134    /// its segments and reverting its index buffer.
135    pub fn reverse(&mut self) {
136        for idx in &mut self.indices {
137            idx.swap(0, 1);
138        }
139
140        self.indices.reverse();
141
142        // Rebuild the bvh since the segment indices no longer map to the correct element.
143        // TODO PERF: should the Bvh have a function for efficient leaf index remapping?
144        //            Probably not worth it unless this function starts showing up as a
145        //            bottleneck for someone.
146        let leaves = self.segments().map(|seg| seg.local_aabb()).enumerate();
147        let bvh = Bvh::from_iter(BvhBuildStrategy::Binned, leaves);
148        self.bvh = bvh;
149    }
150
151    /// Extracts the connected components of this polyline, consuming `self`.
152    ///
153    /// This method is currently quite restrictive on the kind of allowed input. The polyline
154    /// represented by `self` must already have an index buffer sorted such that:
155    /// - Each connected component appears in the index buffer one after the other, i.e., a
156    ///   connected component of this polyline must be a contiguous range of this polyline’s
157    ///   index buffer.
158    /// - Each connected component is closed, i.e., each range of this polyline index buffer
159    ///   `self.indices[i_start..=i_end]` forming a complete connected component, we must have
160    ///   `self.indices[i_start][0] == self.indices[i_end][1]`.
161    /// - The indices for each component must already be in order, i.e., if the segments
162    ///   `self.indices[i]` and `self.indices[i + 1]` are part of the same connected component then
163    ///   we must have `self.indices[i][1] == self.indices[i + 1][0]`.
164    ///
165    /// # Output
166    /// Returns the set of polylines. If the inputs fulfill the constraints mentioned above, each
167    /// polyline will be a closed loop with consistent edge orientations, i.e., for all indices `i`,
168    /// we have `polyline.indices[i][1] == polyline.indices[i + 1][0]`.
169    ///
170    /// The orientation of each closed loop (clockwise or counterclockwise) are identical to their
171    /// original orientation in `self`.
172    pub fn extract_connected_components(&self) -> Vec<Polyline> {
173        let vertices = self.vertices();
174        let indices = self.indices();
175
176        if indices.is_empty() {
177            // Polyline is empty, return empty Vec
178            Vec::new()
179        } else {
180            let mut components = Vec::new();
181
182            let mut start_i = 0; // Start position of component
183            let mut start_node = indices[0][0]; // Start vertex index of component
184
185            let mut component_vertices = Vec::new();
186            let mut component_indices: Vec<[u32; 2]> = Vec::new();
187
188            // Iterate over indices, building polylines as we go
189            for (i, idx) in indices.iter().enumerate() {
190                component_vertices.push(vertices[idx[0] as usize]);
191
192                if idx[1] != start_node {
193                    // Keep scanning and adding data
194                    component_indices.push([(i - start_i) as u32, (i - start_i + 1) as u32]);
195                } else {
196                    // Start node reached: build polyline and start next component
197                    component_indices.push([(i - start_i) as u32, 0]);
198                    components.push(Polyline::new(
199                        core::mem::take(&mut component_vertices),
200                        Some(core::mem::take(&mut component_indices)),
201                    ));
202
203                    if i + 1 < indices.len() {
204                        // More components to find
205                        start_node = indices[i + 1][0];
206                        start_i = i + 1;
207                    }
208                }
209            }
210
211            components
212        }
213    }
214
215    /// Perform a point projection assuming a solid interior based on a counter-clock-wise orientation.
216    ///
217    /// This is similar to `self.project_local_point_and_get_location` except that the resulting
218    /// `PointProjection::is_inside` will be set to true if the point is inside of the area delimited
219    /// by this polyline, assuming that:
220    /// - This polyline isn’t self-crossing.
221    /// - This polyline is closed with `self.indices[i][1] == self.indices[(i + 1) % num_indices][0]` where
222    ///   `num_indices == self.indices.len()`.
223    /// - This polyline is oriented counter-clockwise.
224    /// - In 3D, the polyline is assumed to be fully coplanar, on a plane with normal given by
225    ///   `axis`.
226    ///
227    /// These properties are not checked.
228    pub fn project_local_point_assuming_solid_interior_ccw(
229        &self,
230        point: Point<Real>,
231        #[cfg(feature = "dim3")] axis: u8,
232    ) -> (PointProjection, (u32, SegmentPointLocation)) {
233        let mut proj = self.project_local_point_and_get_location(&point, false);
234        let segment1 = self.segment((proj.1).0);
235
236        #[cfg(feature = "dim2")]
237        let normal1 = segment1.normal();
238        #[cfg(feature = "dim3")]
239        let normal1 = segment1.planar_normal(axis);
240
241        if let Some(normal1) = normal1 {
242            proj.0.is_inside = match proj.1 .1 {
243                SegmentPointLocation::OnVertex(i) => {
244                    let dir2 = if i == 0 {
245                        let adj_seg = if proj.1 .0 == 0 {
246                            self.indices().len() as u32 - 1
247                        } else {
248                            proj.1 .0 - 1
249                        };
250
251                        assert_eq!(segment1.a, self.segment(adj_seg).b);
252                        -self.segment(adj_seg).scaled_direction()
253                    } else {
254                        assert_eq!(i, 1);
255                        let adj_seg = (proj.1 .0 + 1) % self.indices().len() as u32;
256                        assert_eq!(segment1.b, self.segment(adj_seg).a);
257
258                        self.segment(adj_seg).scaled_direction()
259                    };
260
261                    let dot = normal1.dot(&dir2);
262                    // TODO: is this threshold too big? This corresponds to an angle equal to
263                    //       abs(acos(1.0e-3)) = (90 - 0.057) degrees.
264                    //       We did encounter some cases where this was needed, but perhaps the
265                    //       actual problem was an issue with the SegmentPointLocation (which should
266                    //       perhaps have been Edge instead of Vertex)?
267                    let threshold = 1.0e-3 * dir2.norm();
268                    if dot.abs() > threshold {
269                        // If the vertex is a reentrant vertex, then the point is
270                        // inside. Otherwise, it is outside.
271                        dot >= 0.0
272                    } else {
273                        // If the two edges are collinear, we can’t classify the vertex.
274                        // So check against the edge’s normal instead.
275                        (point - proj.0.point).dot(&normal1) <= 0.0
276                    }
277                }
278                SegmentPointLocation::OnEdge(_) => (point - proj.0.point).dot(&normal1) <= 0.0,
279            };
280        }
281
282        proj
283    }
284}
285
286impl CompositeShape for Polyline {
287    fn map_part_at(
288        &self,
289        i: u32,
290        f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
291    ) {
292        let tri = self.segment(i);
293        f(None, &tri, None)
294    }
295
296    fn bvh(&self) -> &Bvh {
297        &self.bvh
298    }
299}
300
301impl TypedCompositeShape for Polyline {
302    type PartShape = Segment;
303    type PartNormalConstraints = ();
304
305    #[inline(always)]
306    fn map_typed_part_at<T>(
307        &self,
308        i: u32,
309        mut f: impl FnMut(
310            Option<&Isometry<Real>>,
311            &Self::PartShape,
312            Option<&Self::PartNormalConstraints>,
313        ) -> T,
314    ) -> Option<T> {
315        let seg = self.segment(i);
316        Some(f(None, &seg, None))
317    }
318
319    #[inline(always)]
320    fn map_untyped_part_at<T>(
321        &self,
322        i: u32,
323        mut f: impl FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
324    ) -> Option<T> {
325        let seg = self.segment(i);
326        Some(f(None, &seg, None))
327    }
328}