parry3d_f64/shape/
polyline.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Isometry, Point, Real, Vector};
3use crate::partitioning::Qbvh;
4use crate::query::{PointProjection, PointQueryWithLocation};
5use crate::shape::composite_shape::SimdCompositeShape;
6use crate::shape::{FeatureId, Segment, SegmentPointLocation, Shape, TypedSimdCompositeShape};
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    qbvh: Qbvh<u32>,
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 data = 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 as u32, aabb)
35        });
36
37        let mut qbvh = Qbvh::new();
38        // NOTE: we apply no dilation factor because we won't
39        // update this tree dynamically.
40        qbvh.clear_and_rebuild(data, 0.0);
41
42        Self {
43            qbvh,
44            vertices,
45            indices,
46        }
47    }
48
49    /// Compute the axis-aligned bounding box of this polyline.
50    pub fn aabb(&self, pos: &Isometry<Real>) -> Aabb {
51        self.qbvh.root_aabb().transform_by(pos)
52    }
53
54    /// Gets the local axis-aligned bounding box of this polyline.
55    pub fn local_aabb(&self) -> &Aabb {
56        self.qbvh.root_aabb()
57    }
58
59    pub(crate) fn qbvh(&self) -> &Qbvh<u32> {
60        &self.qbvh
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        Self {
125            qbvh: self.qbvh.scaled(scale),
126            vertices: self.vertices,
127            indices: self.indices,
128        }
129    }
130
131    /// Reverse the orientation of this polyline by swapping the indices of all
132    /// its segments and reverting its index buffer.
133    pub fn reverse(&mut self) {
134        for idx in &mut self.indices {
135            idx.swap(0, 1);
136        }
137
138        self.indices.reverse();
139
140        // Because we reversed the indices, we need to
141        // adjust the segment indices stored in the Qbvh.
142        for (_, seg_id) in self.qbvh.iter_data_mut() {
143            *seg_id = self.indices.len() as u32 - *seg_id - 1;
144        }
145    }
146
147    /// Extracts the connected components of this polyline, consuming `self`.
148    ///
149    /// This method is currently quite restrictive on the kind of allowed input. The polyline
150    /// represented by `self` must already have an index buffer sorted such that:
151    /// - Each connected component appears in the index buffer one after the other, i.e., a
152    ///   connected component of this polyline must be a contiguous range of this polyline’s
153    ///   index buffer.
154    /// - Each connected component is closed, i.e., each range of this polyline index buffer
155    ///   `self.indices[i_start..=i_end]` forming a complete connected component, we must have
156    ///   `self.indices[i_start][0] == self.indices[i_end][1]`.
157    /// - The indices for each component must already be in order, i.e., if the segments
158    ///   `self.indices[i]` and `self.indices[i + 1]` are part of the same connected component then
159    ///   we must have `self.indices[i][1] == self.indices[i + 1][0]`.
160    ///
161    /// # Output
162    /// Returns the set of polylines. If the inputs fulfill the constraints mentioned above, each
163    /// polyline will be a closed loop with consistent edge orientations, i.e., for all indices `i`,
164    /// we have `polyline.indices[i][1] == polyline.indices[i + 1][0]`.
165    ///
166    /// The orientation of each closed loop (clockwise or counterclockwise) are identical to their
167    /// original orientation in `self`.
168    pub fn extract_connected_components(&self) -> Vec<Polyline> {
169        let vertices = self.vertices();
170        let indices = self.indices();
171
172        if indices.is_empty() {
173            // Polyline is empty, return empty Vec
174            Vec::new()
175        } else {
176            let mut components = Vec::new();
177
178            let mut start_i = 0; // Start position of component
179            let mut start_node = indices[0][0]; // Start vertex index of component
180
181            let mut component_vertices = Vec::new();
182            let mut component_indices: Vec<[u32; 2]> = Vec::new();
183
184            // Iterate over indices, building polylines as we go
185            for (i, idx) in indices.iter().enumerate() {
186                component_vertices.push(vertices[idx[0] as usize]);
187
188                if idx[1] != start_node {
189                    // Keep scanning and adding data
190                    component_indices.push([(i - start_i) as u32, (i - start_i + 1) as u32]);
191                } else {
192                    // Start node reached: build polyline and start next component
193                    component_indices.push([(i - start_i) as u32, 0]);
194                    components.push(Polyline::new(
195                        core::mem::take(&mut component_vertices),
196                        Some(core::mem::take(&mut component_indices)),
197                    ));
198
199                    if i + 1 < indices.len() {
200                        // More components to find
201                        start_node = indices[i + 1][0];
202                        start_i = i + 1;
203                    }
204                }
205            }
206
207            components
208        }
209    }
210
211    /// Perform a point projection assuming a solid interior based on a counter-clock-wise orientation.
212    ///
213    /// This is similar to `self.project_local_point_and_get_location` except that the resulting
214    /// `PointProjection::is_inside` will be set to true if the point is inside of the area delimited
215    /// by this polyline, assuming that:
216    /// - This polyline isn’t self-crossing.
217    /// - This polyline is closed with `self.indices[i][1] == self.indices[(i + 1) % num_indices][0]` where
218    ///   `num_indices == self.indices.len()`.
219    /// - This polyline is oriented counter-clockwise.
220    /// - In 3D, the polyline is assumed to be fully coplanar, on a plane with normal given by
221    ///   `axis`.
222    ///
223    /// These properties are not checked.
224    pub fn project_local_point_assuming_solid_interior_ccw(
225        &self,
226        point: Point<Real>,
227        #[cfg(feature = "dim3")] axis: u8,
228    ) -> (PointProjection, (u32, SegmentPointLocation)) {
229        let mut proj = self.project_local_point_and_get_location(&point, false);
230        let segment1 = self.segment((proj.1).0);
231
232        #[cfg(feature = "dim2")]
233        let normal1 = segment1.normal();
234        #[cfg(feature = "dim3")]
235        let normal1 = segment1.planar_normal(axis);
236
237        if let Some(normal1) = normal1 {
238            proj.0.is_inside = match proj.1 .1 {
239                SegmentPointLocation::OnVertex(i) => {
240                    let dir2 = if i == 0 {
241                        let adj_seg = if proj.1 .0 == 0 {
242                            self.indices().len() as u32 - 1
243                        } else {
244                            proj.1 .0 - 1
245                        };
246
247                        assert_eq!(segment1.a, self.segment(adj_seg).b);
248                        -self.segment(adj_seg).scaled_direction()
249                    } else {
250                        assert_eq!(i, 1);
251                        let adj_seg = (proj.1 .0 + 1) % self.indices().len() as u32;
252                        assert_eq!(segment1.b, self.segment(adj_seg).a);
253
254                        self.segment(adj_seg).scaled_direction()
255                    };
256
257                    let dot = normal1.dot(&dir2);
258                    // TODO: is this threshold too big? This corresponds to an angle equal to
259                    //       abs(acos(1.0e-3)) = (90 - 0.057) degrees.
260                    //       We did encounter some cases where this was needed, but perhaps the
261                    //       actual problem was an issue with the SegmentPointLocation (which should
262                    //       perhaps have been Edge instead of Vertex)?
263                    let threshold = 1.0e-3 * dir2.norm();
264                    if dot.abs() > threshold {
265                        // If the vertex is a reentrant vertex, then the point is
266                        // inside. Otherwise, it is outside.
267                        dot >= 0.0
268                    } else {
269                        // If the two edges are collinear, we can’t classify the vertex.
270                        // So check against the edge’s normal instead.
271                        (point - proj.0.point).dot(&normal1) <= 0.0
272                    }
273                }
274                SegmentPointLocation::OnEdge(_) => (point - proj.0.point).dot(&normal1) <= 0.0,
275            };
276        }
277
278        proj
279    }
280}
281
282impl SimdCompositeShape for Polyline {
283    fn map_part_at(
284        &self,
285        i: u32,
286        f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
287    ) {
288        let tri = self.segment(i);
289        f(None, &tri, None)
290    }
291
292    fn qbvh(&self) -> &Qbvh<u32> {
293        &self.qbvh
294    }
295}
296
297impl TypedSimdCompositeShape for Polyline {
298    type PartShape = Segment;
299    type PartNormalConstraints = ();
300    type PartId = u32;
301
302    #[inline(always)]
303    fn map_typed_part_at(
304        &self,
305        i: u32,
306        mut f: impl FnMut(
307            Option<&Isometry<Real>>,
308            &Self::PartShape,
309            Option<&Self::PartNormalConstraints>,
310        ),
311    ) {
312        let seg = self.segment(i);
313        f(None, &seg, None)
314    }
315
316    #[inline(always)]
317    fn map_untyped_part_at(
318        &self,
319        i: u32,
320        mut f: impl FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
321    ) {
322        let seg = self.segment(i);
323        f(None, &seg, None)
324    }
325
326    fn typed_qbvh(&self) -> &Qbvh<u32> {
327        &self.qbvh
328    }
329}