Skip to main content

parry3d/shape/
polyline.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Pose, Vector};
3use crate::partitioning::{Bvh, BvhBuildStrategy};
4use crate::query::{PointProjection, PointQueryWithLocation};
5use crate::shape::composite_shape::CompositeShape;
6use crate::shape::{
7    FeatureId, Segment, SegmentPointLocation, SegmentPseudoNormals, Shape, TypedCompositeShape,
8};
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11
12use crate::query::details::NormalConstraints;
13
14#[cfg(feature = "dim2")]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[cfg_attr(
17    feature = "rkyv",
18    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
19)]
20#[repr(C)]
21#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
22/// Controls how a [`Polyline`] is loaded.
23pub struct PolylineFlags(u8);
24
25#[cfg(feature = "dim2")]
26bitflags::bitflags! {
27    impl PolylineFlags: u8 {
28        /// If set, the polyline is treated as one-sided: a pseudo-normal is computed at every
29        /// vertex and contact normals are clamped to the outward side, the *right* of each
30        /// segment's direction. The solid must be wound counter-clockwise, so the outward side is
31        /// on the right. This removes the spurious sideways push a body gets at a convex corner of
32        /// a double-sided polyline. This one flag covers what `TriMesh` splits across
33        /// `TriMeshFlags::ORIENTED` (compute pseudo-normals) and `TriMeshFlags::FIX_INTERNAL_EDGES`
34        /// (use them to clamp contacts).
35        const ORIENTED = 1;
36    }
37}
38
39#[derive(Clone, Debug)]
40#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
41#[cfg_attr(
42    feature = "rkyv",
43    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
44)]
45/// A polyline shape formed by connected line segments.
46///
47/// A polyline is a sequence of line segments (edges) connecting vertices. It can be open
48/// (not forming a closed loop) or closed (where the last vertex connects back to the first).
49/// Polylines are commonly used for paths, boundaries, and 2D/3D curves.
50///
51/// # Structure
52///
53/// A polyline consists of:
54/// - **Vertices**: Vectors in 2D or 3D space
55/// - **Indices**: Pairs of vertex indices defining each segment
56/// - **BVH**: Bounding Volume Hierarchy for fast spatial queries
57///
58/// # Properties
59///
60/// - **Composite shape**: Made up of multiple segments
61/// - **1-dimensional**: Has length but no volume
62/// - **Flexible topology**: Can be open or closed, branching or linear
63/// - **Accelerated queries**: Uses BVH for efficient collision detection
64///
65/// # Use Cases
66///
67/// Polylines are ideal for:
68/// - **Paths and roads**: Navigation paths, road networks
69/// - **Terrain boundaries**: Cliff edges, coastlines, level boundaries
70/// - **Outlines**: 2D shape outlines, contours
71/// - **Wire frames**: Simplified representations of complex shapes
72/// - **Motion paths**: Character movement paths, camera rails
73///
74/// # Example
75///
76/// ```rust
77/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
78/// use parry3d::shape::Polyline;
79/// use parry3d::math::Vector;
80///
81/// // Create a simple L-shaped polyline
82/// let vertices = vec![
83///     Vector::ZERO,
84///     Vector::new(1.0, 0.0, 0.0),
85///     Vector::new(1.0, 1.0, 0.0),
86/// ];
87///
88/// // Indices are automatically generated to connect consecutive vertices
89/// let polyline = Polyline::new(vertices, None);
90///
91/// // The polyline has 2 segments: (0,1) and (1,2)
92/// assert_eq!(polyline.num_segments(), 2);
93/// assert_eq!(polyline.vertices().len(), 3);
94/// # }
95/// ```
96///
97/// # Custom Connectivity
98///
99/// You can provide custom indices to create non-sequential connections:
100///
101/// ```rust
102/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
103/// use parry2d::shape::Polyline;
104/// use parry2d::math::Vector;
105///
106/// // Create a triangle polyline (closed loop)
107/// let vertices = vec![
108///     Vector::ZERO,
109///     Vector::new(1.0, 0.0),
110///     Vector::new(0.5, 1.0),
111/// ];
112///
113/// // Manually specify edges to create a closed triangle
114/// let indices = vec![
115///     [0, 1],  // Bottom edge
116///     [1, 2],  // Right edge
117///     [2, 0],  // Left edge (closes the loop)
118/// ];
119///
120/// let polyline = Polyline::new(vertices, Some(indices));
121/// assert_eq!(polyline.num_segments(), 3);
122/// # }
123/// ```
124pub struct Polyline {
125    bvh: Bvh,
126    vertices: Vec<Vector>,
127    indices: Vec<[u32; 2]>,
128    /// Per-vertex outward pseudo-normals, present when [`PolylineFlags::ORIENTED`] is set; contact
129    /// normals are then clamped to one side so the polyline acts as a one-sided surface.
130    #[cfg(feature = "dim2")]
131    pseudo_normals: Option<Vec<Vector>>,
132    #[cfg(feature = "dim2")]
133    flags: PolylineFlags,
134}
135
136impl Polyline {
137    /// Creates a new polyline from a vertex buffer and an optional index buffer.
138    ///
139    /// This is the main constructor for creating a polyline. If no indices are provided,
140    /// the vertices will be automatically connected in sequence (vertex 0 to 1, 1 to 2, etc.).
141    ///
142    /// # Arguments
143    ///
144    /// * `vertices` - A vector of points defining the polyline vertices
145    /// * `indices` - Optional vector of `[u32; 2]` pairs defining which vertices connect.
146    ///   If `None`, vertices are connected sequentially.
147    ///
148    /// # Returns
149    ///
150    /// A new `Polyline` with an internal BVH for accelerated queries.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
156    /// use parry3d::shape::Polyline;
157    /// use parry3d::math::Vector;
158    ///
159    /// // Create a zigzag path with automatic sequential connections
160    /// let vertices = vec![
161    ///     Vector::ZERO,
162    ///     Vector::new(1.0, 1.0, 0.0),
163    ///     Vector::new(2.0, 0.0, 0.0),
164    ///     Vector::new(3.0, 1.0, 0.0),
165    /// ];
166    /// let polyline = Polyline::new(vertices, None);
167    /// assert_eq!(polyline.num_segments(), 3);
168    /// # }
169    /// ```
170    ///
171    /// # Custom Connectivity Example
172    ///
173    /// ```
174    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
175    /// use parry2d::shape::Polyline;
176    /// use parry2d::math::Vector;
177    ///
178    /// // Create a square with custom indices
179    /// let vertices = vec![
180    ///     Vector::ZERO,
181    ///     Vector::new(1.0, 0.0),
182    ///     Vector::new(1.0, 1.0),
183    ///     Vector::new(0.0, 1.0),
184    /// ];
185    ///
186    /// // Define edges to form a closed square
187    /// let indices = vec![
188    ///     [0, 1], [1, 2], [2, 3], [3, 0]
189    /// ];
190    ///
191    /// let square = Polyline::new(vertices, Some(indices));
192    /// assert_eq!(square.num_segments(), 4);
193    ///
194    /// // Each segment connects the correct vertices
195    /// let first_segment = square.segment(0);
196    /// assert_eq!(first_segment.a, Vector::ZERO);
197    /// assert_eq!(first_segment.b, Vector::new(1.0, 0.0));
198    /// # }
199    /// ```
200    pub fn new(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
201        // Index from 1 so empty input produces no segments instead of underflowing on `len - 1`.
202        let indices =
203            indices.unwrap_or_else(|| (1..vertices.len() as u32).map(|i| [i - 1, i]).collect());
204        let leaves = indices.iter().enumerate().map(|(i, idx)| {
205            let aabb =
206                Segment::new(vertices[idx[0] as usize], vertices[idx[1] as usize]).local_aabb();
207            (i, aabb)
208        });
209
210        // NOTE: we apply no dilation factor because we won't
211        // update this tree dynamically.
212        let bvh = Bvh::from_iter(BvhBuildStrategy::Binned, leaves);
213
214        Self {
215            bvh,
216            vertices,
217            indices,
218            #[cfg(feature = "dim2")]
219            pseudo_normals: None,
220            #[cfg(feature = "dim2")]
221            flags: PolylineFlags::empty(),
222        }
223    }
224
225    /// Creates a new polyline with the given [`PolylineFlags`] controlling its optional associated
226    /// data, e.g. orientation via [`PolylineFlags::ORIENTED`].
227    ///
228    /// # Example
229    ///
230    /// ```
231    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
232    /// use parry2d::shape::{Polyline, PolylineFlags};
233    /// use parry2d::math::Vector;
234    ///
235    /// // A unit square wound counter-clockwise, so the solid is inside and outward points away.
236    /// let vertices = vec![
237    ///     Vector::new(-1.0, -1.0),
238    ///     Vector::new(1.0, -1.0),
239    ///     Vector::new(1.0, 1.0),
240    ///     Vector::new(-1.0, 1.0),
241    /// ];
242    /// let indices = vec![[0, 1], [1, 2], [2, 3], [3, 0]];
243    /// let polyline = Polyline::with_flags(vertices, Some(indices), PolylineFlags::ORIENTED);
244    ///
245    /// // The bottom edge's outward normal points down, away from the interior.
246    /// let bottom = polyline.segment_normal_constraints(0).unwrap();
247    /// assert!(bottom.face.abs_diff_eq(Vector::new(0.0, -1.0), 1.0e-5));
248    /// # }
249    /// ```
250    #[cfg(feature = "dim2")]
251    pub fn with_flags(
252        vertices: Vec<Vector>,
253        indices: Option<Vec<[u32; 2]>>,
254        flags: PolylineFlags,
255    ) -> Self {
256        let mut result = Self::new(vertices, indices);
257        result.set_flags(flags);
258        result
259    }
260
261    /// Sets the [`PolylineFlags`], computing or discarding the polyline's optional associated data.
262    #[cfg(feature = "dim2")]
263    pub fn set_flags(&mut self, flags: PolylineFlags) {
264        self.flags = flags;
265
266        if flags.contains(PolylineFlags::ORIENTED) {
267            self.compute_pseudo_normals();
268        } else {
269            self.pseudo_normals = None;
270        }
271    }
272
273    /// The [`PolylineFlags`] controlling this polyline's optional associated data.
274    #[cfg(feature = "dim2")]
275    pub fn flags(&self) -> PolylineFlags {
276        self.flags
277    }
278
279    /// Computes the outward pseudo-normal at every vertex (the normalized sum of its incident
280    /// segments' outward normals) for the one-sided behavior of [`PolylineFlags::ORIENTED`].
281    #[cfg(feature = "dim2")]
282    fn compute_pseudo_normals(&mut self) {
283        let mut vertex_normals = Vec::new();
284        vertex_normals.resize(self.vertices.len(), Vector::ZERO);
285
286        // A 2D vertex has at most two incident segments, so the normalized sum is their exact
287        // bisector -- no angle weighting (unlike the 3D `TrianglePseudoNormals`).
288        for idx in &self.indices {
289            let a = idx[0] as usize;
290            let b = idx[1] as usize;
291            let normal = crate::utils::ccw_face_normal([self.vertices[a], self.vertices[b]])
292                .unwrap_or(Vector::ZERO);
293            vertex_normals[a] += normal;
294            vertex_normals[b] += normal;
295        }
296
297        for normal in &mut vertex_normals {
298            *normal = normal.normalize_or_zero();
299        }
300
301        self.pseudo_normals = Some(vertex_normals);
302    }
303
304    /// Returns the [`SegmentPseudoNormals`] for the segment with index `i`, or `None` unless this
305    /// polyline was built with [`PolylineFlags::ORIENTED`].
306    ///
307    /// # Example
308    ///
309    /// ```
310    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
311    /// use parry2d::shape::{Polyline, PolylineFlags};
312    /// use parry2d::math::Vector;
313    ///
314    /// let vertices = vec![Vector::new(0.0, 0.0), Vector::new(2.0, 0.0), Vector::new(2.0, 2.0)];
315    /// let mut polyline = Polyline::new(vertices, Some(vec![[0, 1], [1, 2]]));
316    ///
317    /// // A double-sided polyline has no constraints...
318    /// assert!(polyline.segment_normal_constraints(0).is_none());
319    ///
320    /// // ...until it is oriented.
321    /// polyline.set_flags(PolylineFlags::ORIENTED);
322    /// assert!(polyline.segment_normal_constraints(0).is_some());
323    /// # }
324    /// ```
325    #[cfg(feature = "dim2")]
326    pub fn segment_normal_constraints(&self, i: u32) -> Option<SegmentPseudoNormals> {
327        let pseudo_normals = self.pseudo_normals.as_ref()?;
328        let idx = self.indices[i as usize];
329        let a = idx[0] as usize;
330        let b = idx[1] as usize;
331        let face = crate::utils::ccw_face_normal([self.vertices[a], self.vertices[b]])?;
332        Some(SegmentPseudoNormals {
333            face,
334            edges: [pseudo_normals[a], pseudo_normals[b]],
335        })
336    }
337
338    /// Pseudo-normals are a 2D-only feature; in 3D there are no segment normal constraints. 3D stub
339    /// so the composite-shape impls compile; mirrors `TriMesh::triangle_normal_constraints`'s dim2
340    /// stub.
341    #[cfg(feature = "dim3")]
342    #[doc(hidden)]
343    pub fn segment_normal_constraints(&self, _i: u32) -> Option<SegmentPseudoNormals> {
344        None
345    }
346
347    /// Computes the axis-aligned bounding box of this polyline in world space.
348    ///
349    /// The AABB is the smallest box aligned with the world axes that fully contains
350    /// the polyline after applying the given position/rotation transformation.
351    ///
352    /// # Arguments
353    ///
354    /// * `pos` - The position and orientation (isometry) of the polyline in world space
355    ///
356    /// # Returns
357    ///
358    /// An `Aabb` that bounds the transformed polyline
359    ///
360    /// # Example
361    ///
362    /// ```
363    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
364    /// use parry3d::shape::Polyline;
365    /// use parry3d::math::{Vector, Pose};
366    ///
367    /// // Create a polyline along the X axis
368    /// let vertices = vec![
369    ///     Vector::ZERO,
370    ///     Vector::new(2.0, 0.0, 0.0),
371    /// ];
372    /// let polyline = Polyline::new(vertices, None);
373    ///
374    /// // Compute AABB at the origin
375    /// let identity = Pose::identity();
376    /// let aabb = polyline.aabb(&identity);
377    /// assert_eq!(aabb.mins.x, 0.0);
378    /// assert_eq!(aabb.maxs.x, 2.0);
379    ///
380    /// // Compute AABB after translating by (10, 5, 0)
381    /// let translated = Pose::translation(10.0, 5.0, 0.0);
382    /// let aabb_translated = polyline.aabb(&translated);
383    /// assert_eq!(aabb_translated.mins.x, 10.0);
384    /// assert_eq!(aabb_translated.maxs.x, 12.0);
385    /// # }
386    /// ```
387    pub fn aabb(&self, pos: &Pose) -> Aabb {
388        self.bvh.root_aabb().transform_by(pos)
389    }
390
391    /// Gets the local axis-aligned bounding box of this polyline.
392    ///
393    /// This returns the AABB in the polyline's local coordinate system (before any
394    /// transformation is applied). It's more efficient than `aabb()` when you don't
395    /// need to transform the polyline.
396    ///
397    /// # Returns
398    ///
399    /// An `Aabb` that bounds the polyline in local space
400    ///
401    /// # Example
402    ///
403    /// ```
404    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
405    /// use parry2d::shape::Polyline;
406    /// use parry2d::math::Vector;
407    ///
408    /// // Create a rectangular polyline
409    /// let vertices = vec![
410    ///     Vector::new(-1.0, -2.0),
411    ///     Vector::new(3.0, -2.0),
412    ///     Vector::new(3.0, 4.0),
413    ///     Vector::new(-1.0, 4.0),
414    /// ];
415    /// let polyline = Polyline::new(vertices, None);
416    ///
417    /// // Get the local AABB
418    /// let aabb = polyline.local_aabb();
419    ///
420    /// // The AABB should contain all vertices
421    /// assert_eq!(aabb.mins.x, -1.0);
422    /// assert_eq!(aabb.mins.y, -2.0);
423    /// assert_eq!(aabb.maxs.x, 3.0);
424    /// assert_eq!(aabb.maxs.y, 4.0);
425    /// # }
426    /// ```
427    pub fn local_aabb(&self) -> Aabb {
428        self.bvh.root_aabb()
429    }
430
431    /// The BVH acceleration structure for this polyline.
432    pub fn bvh(&self) -> &Bvh {
433        &self.bvh
434    }
435
436    /// Returns the number of segments (edges) in this polyline.
437    ///
438    /// Each segment connects two vertices. For a polyline with `n` vertices and
439    /// sequential connectivity, there are `n-1` segments. For custom connectivity,
440    /// the number of segments equals the number of index pairs.
441    ///
442    /// # Returns
443    ///
444    /// The total number of line segments
445    ///
446    /// # Example
447    ///
448    /// ```
449    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
450    /// use parry3d::shape::Polyline;
451    /// use parry3d::math::Vector;
452    ///
453    /// // Sequential polyline: 5 vertices -> 4 segments
454    /// let vertices = vec![
455    ///     Vector::ZERO,
456    ///     Vector::new(1.0, 0.0, 0.0),
457    ///     Vector::new(2.0, 0.0, 0.0),
458    ///     Vector::new(3.0, 0.0, 0.0),
459    ///     Vector::new(4.0, 0.0, 0.0),
460    /// ];
461    /// let polyline = Polyline::new(vertices.clone(), None);
462    /// assert_eq!(polyline.num_segments(), 4);
463    ///
464    /// // Custom connectivity: can have different number of segments
465    /// let indices = vec![[0, 4], [1, 3]]; // Only 2 segments
466    /// let custom = Polyline::new(vertices, Some(indices));
467    /// assert_eq!(custom.num_segments(), 2);
468    /// # }
469    /// ```
470    pub fn num_segments(&self) -> usize {
471        self.indices.len()
472    }
473
474    /// Returns an iterator over all segments in this polyline.
475    ///
476    /// Each segment is returned as a [`Segment`] object with two endpoints.
477    /// The iterator yields exactly `num_segments()` items.
478    ///
479    /// # Returns
480    ///
481    /// An exact-size iterator that yields `Segment` instances
482    ///
483    /// # Example
484    ///
485    /// ```
486    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
487    /// use parry2d::shape::Polyline;
488    /// use parry2d::math::Vector;
489    ///
490    /// // Create a triangle
491    /// let vertices = vec![
492    ///     Vector::ZERO,
493    ///     Vector::new(1.0, 0.0),
494    ///     Vector::new(0.5, 1.0),
495    /// ];
496    /// let polyline = Polyline::new(vertices, None);
497    ///
498    /// // Iterate over all segments
499    /// let mut total_length = 0.0;
500    /// for segment in polyline.segments() {
501    ///     total_length += segment.length();
502    /// }
503    ///
504    /// // Calculate expected perimeter (not closed, so 2 sides only)
505    /// assert!(total_length > 2.0);
506    ///
507    /// // Collect all segments into a vector
508    /// let segments: Vec<_> = polyline.segments().collect();
509    /// assert_eq!(segments.len(), 2);
510    /// # }
511    /// ```
512    pub fn segments(&self) -> impl ExactSizeIterator<Item = Segment> + '_ {
513        self.indices.iter().map(move |ids| {
514            Segment::new(
515                self.vertices[ids[0] as usize],
516                self.vertices[ids[1] as usize],
517            )
518        })
519    }
520
521    /// Returns the segment at the given index.
522    ///
523    /// This retrieves a specific segment by its index. Indices range from `0` to
524    /// `num_segments() - 1`. If you need to access multiple segments, consider
525    /// using the `segments()` iterator instead.
526    ///
527    /// # Arguments
528    ///
529    /// * `i` - The index of the segment to retrieve (0-based)
530    ///
531    /// # Returns
532    ///
533    /// A `Segment` representing the edge at index `i`
534    ///
535    /// # Panics
536    ///
537    /// Panics if `i >= num_segments()`
538    ///
539    /// # Example
540    ///
541    /// ```
542    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
543    /// use parry3d::shape::Polyline;
544    /// use parry3d::math::Vector;
545    ///
546    /// let vertices = vec![
547    ///     Vector::ZERO,
548    ///     Vector::new(1.0, 0.0, 0.0),
549    ///     Vector::new(2.0, 1.0, 0.0),
550    /// ];
551    /// let polyline = Polyline::new(vertices, None);
552    ///
553    /// // Get the first segment (connects vertex 0 to vertex 1)
554    /// let seg0 = polyline.segment(0);
555    /// assert_eq!(seg0.a, Vector::ZERO);
556    /// assert_eq!(seg0.b, Vector::new(1.0, 0.0, 0.0));
557    /// assert_eq!(seg0.length(), 1.0);
558    ///
559    /// // Get the second segment (connects vertex 1 to vertex 2)
560    /// let seg1 = polyline.segment(1);
561    /// assert_eq!(seg1.a, Vector::new(1.0, 0.0, 0.0));
562    /// assert_eq!(seg1.b, Vector::new(2.0, 1.0, 0.0));
563    /// # }
564    /// ```
565    pub fn segment(&self, i: u32) -> Segment {
566        let idx = self.indices[i as usize];
567        Segment::new(
568            self.vertices[idx[0] as usize],
569            self.vertices[idx[1] as usize],
570        )
571    }
572
573    /// Transforms  the feature-id of a segment to the feature-id of this polyline.
574    pub fn segment_feature_to_polyline_feature(
575        &self,
576        segment: u32,
577        _feature: FeatureId,
578    ) -> FeatureId {
579        // TODO: return a vertex feature when it makes sense.
580        #[cfg(feature = "dim2")]
581        return FeatureId::Face(segment);
582        #[cfg(feature = "dim3")]
583        return FeatureId::Edge(segment);
584    }
585
586    /// Returns a slice containing all vertices of this polyline.
587    ///
588    /// Vertices are the points that define the polyline. Segments connect
589    /// pairs of these vertices according to the index buffer.
590    ///
591    /// # Returns
592    ///
593    /// A slice of all vertex points
594    ///
595    /// # Example
596    ///
597    /// ```
598    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
599    /// use parry2d::shape::Polyline;
600    /// use parry2d::math::Vector;
601    ///
602    /// let vertices = vec![
603    ///     Vector::ZERO,
604    ///     Vector::new(1.0, 0.0),
605    ///     Vector::new(1.0, 1.0),
606    /// ];
607    /// let polyline = Polyline::new(vertices.clone(), None);
608    ///
609    /// // Access all vertices
610    /// let verts = polyline.vertices();
611    /// assert_eq!(verts.len(), 3);
612    /// assert_eq!(verts[0], Vector::ZERO);
613    /// assert_eq!(verts[1], Vector::new(1.0, 0.0));
614    /// assert_eq!(verts[2], Vector::new(1.0, 1.0));
615    ///
616    /// // You can iterate over vertices
617    /// for (i, vertex) in polyline.vertices().iter().enumerate() {
618    ///     println!("Vertex {}: {:?}", i, vertex);
619    /// }
620    /// # }
621    /// ```
622    pub fn vertices(&self) -> &[Vector] {
623        &self.vertices[..]
624    }
625
626    /// Returns a slice containing all segment indices.
627    ///
628    /// Each index is a pair `[u32; 2]` representing a segment connecting two vertices.
629    /// The first element is the index of the segment's start vertex, and the second
630    /// is the index of the end vertex.
631    ///
632    /// # Returns
633    ///
634    /// A slice of all segment index pairs
635    ///
636    /// # Example
637    ///
638    /// ```
639    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
640    /// use parry3d::shape::Polyline;
641    /// use parry3d::math::Vector;
642    ///
643    /// let vertices = vec![
644    ///     Vector::ZERO,
645    ///     Vector::new(1.0, 0.0, 0.0),
646    ///     Vector::new(2.0, 0.0, 0.0),
647    /// ];
648    ///
649    /// // With automatic indices
650    /// let polyline = Polyline::new(vertices.clone(), None);
651    /// let indices = polyline.indices();
652    /// assert_eq!(indices.len(), 2);
653    /// assert_eq!(indices[0], [0, 1]); // First segment: vertex 0 -> 1
654    /// assert_eq!(indices[1], [1, 2]); // Second segment: vertex 1 -> 2
655    ///
656    /// // With custom indices
657    /// let custom_indices = vec![[0, 2], [1, 0]];
658    /// let custom = Polyline::new(vertices, Some(custom_indices));
659    /// assert_eq!(custom.indices()[0], [0, 2]);
660    /// assert_eq!(custom.indices()[1], [1, 0]);
661    /// # }
662    /// ```
663    pub fn indices(&self) -> &[[u32; 2]] {
664        &self.indices
665    }
666
667    /// A flat view of the index buffer of this mesh.
668    pub fn flat_indices(&self) -> &[u32] {
669        unsafe {
670            let len = self.indices.len() * 2;
671            let data = self.indices.as_ptr() as *const u32;
672            core::slice::from_raw_parts(data, len)
673        }
674    }
675
676    /// Computes a scaled version of this polyline.
677    ///
678    /// This consumes the polyline and returns a new one with all vertices scaled
679    /// component-wise by the given scale vector. The connectivity (indices) remains
680    /// unchanged, but the BVH is rebuilt to reflect the new geometry.
681    ///
682    /// # Arguments
683    ///
684    /// * `scale` - The scaling factors for each axis
685    ///
686    /// # Returns
687    ///
688    /// A new polyline with scaled vertices
689    ///
690    /// # Example
691    ///
692    /// ```
693    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
694    /// use parry2d::shape::Polyline;
695    /// use parry2d::math::Vector;
696    ///
697    /// let vertices = vec![
698    ///     Vector::new(1.0, 2.0),
699    ///     Vector::new(3.0, 4.0),
700    /// ];
701    /// let polyline = Polyline::new(vertices, None);
702    ///
703    /// // Scale by 2x in X and 3x in Y
704    /// let scaled = polyline.scaled(Vector::new(2.0, 3.0));
705    ///
706    /// // Check scaled vertices
707    /// assert_eq!(scaled.vertices()[0], Vector::new(2.0, 6.0));
708    /// assert_eq!(scaled.vertices()[1], Vector::new(6.0, 12.0));
709    /// # }
710    /// ```
711    ///
712    /// # Note
713    ///
714    /// This method consumes `self`. If you need to keep the original polyline,
715    /// clone it first:
716    ///
717    /// ```
718    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
719    /// use parry3d::shape::Polyline;
720    /// use parry3d::math::Vector;
721    ///
722    /// let vertices = vec![Vector::ZERO, Vector::new(1.0, 0.0, 0.0)];
723    /// let original = Polyline::new(vertices, None);
724    ///
725    /// // Clone before scaling if you need to keep the original
726    /// let scaled = original.clone().scaled(Vector::new(2.0, 2.0, 2.0));
727    ///
728    /// // Both polylines still exist
729    /// assert_eq!(original.vertices()[1].x, 1.0);
730    /// assert_eq!(scaled.vertices()[1].x, 2.0);
731    /// # }
732    /// ```
733    pub fn scaled(mut self, scale: Vector) -> Self {
734        self.vertices.iter_mut().for_each(|pt| *pt *= scale);
735        let mut bvh = self.bvh.clone();
736        bvh.scale(scale);
737
738        #[cfg(feature = "dim2")]
739        {
740            let mut result = Self {
741                bvh,
742                vertices: self.vertices,
743                indices: self.indices,
744                pseudo_normals: None,
745                flags: PolylineFlags::empty(),
746            };
747            // Recompute the pseudo-normals from the scaled geometry.
748            result.set_flags(self.flags);
749            result
750        }
751        #[cfg(feature = "dim3")]
752        {
753            Self {
754                bvh,
755                vertices: self.vertices,
756                indices: self.indices,
757            }
758        }
759    }
760
761    /// Reverses the orientation of this polyline.
762    ///
763    /// This operation:
764    /// 1. Swaps the start and end vertex of each segment (reversing edge direction)
765    /// 2. Reverses the order of segments in the index buffer
766    /// 3. Rebuilds the BVH to maintain correct acceleration structure
767    ///
768    /// After reversing, traversing the polyline segments in order will visit
769    /// the same geometry but in the opposite direction.
770    ///
771    /// # Example
772    ///
773    /// ```
774    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
775    /// use parry2d::shape::Polyline;
776    /// use parry2d::math::Vector;
777    ///
778    /// let vertices = vec![
779    ///     Vector::ZERO,
780    ///     Vector::new(1.0, 0.0),
781    ///     Vector::new(2.0, 0.0),
782    /// ];
783    /// let mut polyline = Polyline::new(vertices, None);
784    ///
785    /// // Original: segment 0 goes from vertex 0 to 1, segment 1 from 1 to 2
786    /// assert_eq!(polyline.indices()[0], [0, 1]);
787    /// assert_eq!(polyline.indices()[1], [1, 2]);
788    ///
789    /// // Reverse the polyline
790    /// polyline.reverse();
791    ///
792    /// // After reversing: order is flipped and directions are swapped
793    /// // The last segment becomes first, with swapped endpoints
794    /// assert_eq!(polyline.indices()[0], [2, 1]);
795    /// assert_eq!(polyline.indices()[1], [1, 0]);
796    /// # }
797    /// ```
798    ///
799    /// # Use Cases
800    ///
801    /// This is useful for:
802    /// - Correcting winding order for 2D shapes
803    /// - Reversing path direction for navigation
804    /// - Ensuring consistent edge orientation in connected components
805    pub fn reverse(&mut self) {
806        for idx in &mut self.indices {
807            idx.swap(0, 1);
808        }
809
810        self.indices.reverse();
811
812        // Rebuild the bvh since the segment indices no longer map to the correct element.
813        // TODO PERF: should the Bvh have a function for efficient leaf index remapping?
814        //            Probably not worth it unless this function starts showing up as a
815        //            bottleneck for someone.
816        let leaves = self.segments().map(|seg| seg.local_aabb()).enumerate();
817        let bvh = Bvh::from_iter(BvhBuildStrategy::Binned, leaves);
818        self.bvh = bvh;
819
820        // Reversing flips the winding, so recompute the outward side.
821        #[cfg(feature = "dim2")]
822        if self.flags.contains(PolylineFlags::ORIENTED) {
823            self.compute_pseudo_normals();
824        }
825    }
826
827    /// Extracts the connected components of this polyline, consuming `self`.
828    ///
829    /// This method is currently quite restrictive on the kind of allowed input. The polyline
830    /// represented by `self` must already have an index buffer sorted such that:
831    /// - Each connected component appears in the index buffer one after the other, i.e., a
832    ///   connected component of this polyline must be a contiguous range of this polyline’s
833    ///   index buffer.
834    /// - Each connected component is closed, i.e., each range of this polyline index buffer
835    ///   `self.indices[i_start..=i_end]` forming a complete connected component, we must have
836    ///   `self.indices[i_start][0] == self.indices[i_end][1]`.
837    /// - The indices for each component must already be in order, i.e., if the segments
838    ///   `self.indices[i]` and `self.indices[i + 1]` are part of the same connected component then
839    ///   we must have `self.indices[i][1] == self.indices[i + 1][0]`.
840    ///
841    /// # Output
842    /// Returns the set of polylines. If the inputs fulfill the constraints mentioned above, each
843    /// polyline will be a closed loop with consistent edge orientations, i.e., for all indices `i`,
844    /// we have `polyline.indices[i][1] == polyline.indices[i + 1][0]`.
845    ///
846    /// The orientation of each closed loop (clockwise or counterclockwise) are identical to their
847    /// original orientation in `self`.
848    pub fn extract_connected_components(&self) -> Vec<Polyline> {
849        let vertices = self.vertices();
850        let indices = self.indices();
851
852        if indices.is_empty() {
853            // Polyline is empty, return empty Vec
854            Vec::new()
855        } else {
856            let mut components = Vec::new();
857
858            let mut start_i = 0; // Start position of component
859            let mut start_node = indices[0][0]; // Start vertex index of component
860
861            let mut component_vertices = Vec::new();
862            let mut component_indices: Vec<[u32; 2]> = Vec::new();
863
864            // Iterate over indices, building polylines as we go
865            for (i, idx) in indices.iter().enumerate() {
866                component_vertices.push(vertices[idx[0] as usize]);
867
868                if idx[1] != start_node {
869                    // Keep scanning and adding data
870                    component_indices.push([(i - start_i) as u32, (i - start_i + 1) as u32]);
871                } else {
872                    // Start node reached: build polyline and start next component
873                    component_indices.push([(i - start_i) as u32, 0]);
874                    components.push(Polyline::new(
875                        core::mem::take(&mut component_vertices),
876                        Some(core::mem::take(&mut component_indices)),
877                    ));
878
879                    if i + 1 < indices.len() {
880                        // More components to find
881                        start_node = indices[i + 1][0];
882                        start_i = i + 1;
883                    }
884                }
885            }
886
887            components
888        }
889    }
890
891    /// Perform a point projection assuming a solid interior based on a counter-clock-wise orientation.
892    ///
893    /// This is similar to `self.project_local_point_and_get_location` except that the resulting
894    /// `PointProjection::is_inside` will be set to true if the point is inside of the area delimited
895    /// by this polyline, assuming that:
896    /// - This polyline isn’t self-crossing.
897    /// - This polyline is closed with `self.indices[i][1] == self.indices[(i + 1) % num_indices][0]` where
898    ///   `num_indices == self.indices.len()`.
899    /// - This polyline is oriented counter-clockwise.
900    /// - In 3D, the polyline is assumed to be fully coplanar, on a plane with normal given by
901    ///   `axis`.
902    ///
903    /// These properties are not checked.
904    pub fn project_local_point_assuming_solid_interior_ccw(
905        &self,
906        point: Vector,
907        #[cfg(feature = "dim3")] axis: u8,
908    ) -> (PointProjection, (u32, SegmentPointLocation)) {
909        let mut proj = self.project_local_point_and_get_location(point, false);
910        let segment1 = self.segment((proj.1).0);
911
912        #[cfg(feature = "dim2")]
913        let normal1 = segment1.normal();
914        #[cfg(feature = "dim3")]
915        let normal1 = segment1.planar_normal(axis);
916
917        if let Some(normal1) = normal1 {
918            proj.0.is_inside = match proj.1 .1 {
919                SegmentPointLocation::OnVertex(i) => {
920                    let dir2 = if i == 0 {
921                        let adj_seg = if proj.1 .0 == 0 {
922                            self.indices().len() as u32 - 1
923                        } else {
924                            proj.1 .0 - 1
925                        };
926
927                        assert_eq!(segment1.a, self.segment(adj_seg).b);
928                        -self.segment(adj_seg).scaled_direction()
929                    } else {
930                        assert_eq!(i, 1);
931                        let adj_seg = (proj.1 .0 + 1) % self.indices().len() as u32;
932                        assert_eq!(segment1.b, self.segment(adj_seg).a);
933
934                        self.segment(adj_seg).scaled_direction()
935                    };
936
937                    let dot = normal1.dot(dir2);
938                    // TODO: is this threshold too big? This corresponds to an angle equal to
939                    //       abs(acos(1.0e-3)) = (90 - 0.057) degrees.
940                    //       We did encounter some cases where this was needed, but perhaps the
941                    //       actual problem was an issue with the SegmentPointLocation (which should
942                    //       perhaps have been Edge instead of Vertex)?
943                    let threshold = 1.0e-3 * dir2.length();
944                    if dot.abs() > threshold {
945                        // If the vertex is a reentrant vertex, then the point is
946                        // inside. Otherwise, it is outside.
947                        dot >= 0.0
948                    } else {
949                        // If the two edges are collinear, we can’t classify the vertex.
950                        // So check against the edge’s normal instead.
951                        (point - proj.0.point).dot(normal1) <= 0.0
952                    }
953                }
954                SegmentPointLocation::OnEdge(_) => (point - proj.0.point).dot(normal1) <= 0.0,
955            };
956        }
957
958        proj
959    }
960}
961
962impl CompositeShape for Polyline {
963    fn map_part_at(
964        &self,
965        i: u32,
966        f: &mut dyn FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>),
967    ) {
968        let seg = self.segment(i);
969        let normals = self.segment_normal_constraints(i);
970        f(
971            None,
972            &seg,
973            normals.as_ref().map(|n| n as &dyn NormalConstraints),
974        )
975    }
976
977    fn bvh(&self) -> &Bvh {
978        &self.bvh
979    }
980}
981
982impl TypedCompositeShape for Polyline {
983    type PartShape = Segment;
984    type PartNormalConstraints = SegmentPseudoNormals;
985
986    #[inline(always)]
987    fn map_typed_part_at<T>(
988        &self,
989        i: u32,
990        mut f: impl FnMut(Option<&Pose>, &Self::PartShape, Option<&Self::PartNormalConstraints>) -> T,
991    ) -> Option<T> {
992        let seg = self.segment(i);
993        let normals = self.segment_normal_constraints(i);
994        Some(f(None, &seg, normals.as_ref()))
995    }
996
997    #[inline(always)]
998    fn map_untyped_part_at<T>(
999        &self,
1000        i: u32,
1001        mut f: impl FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
1002    ) -> Option<T> {
1003        let seg = self.segment(i);
1004        let normals = self.segment_normal_constraints(i);
1005        Some(f(
1006            None,
1007            &seg,
1008            normals.as_ref().map(|n| n as &dyn NormalConstraints),
1009        ))
1010    }
1011}
1012
1013#[cfg(test)]
1014#[cfg(all(feature = "dim2", feature = "alloc"))]
1015mod pseudo_normal_tests {
1016    use crate::math::Vector;
1017    use crate::shape::{Polyline, PolylineFlags};
1018
1019    fn ccw_square() -> Polyline {
1020        // CCW unit square: solid inside, outward away from the center.
1021        let vertices = vec![
1022            Vector::new(-1.0, -1.0),
1023            Vector::new(1.0, -1.0),
1024            Vector::new(1.0, 1.0),
1025            Vector::new(-1.0, 1.0),
1026        ];
1027        Polyline::new(vertices, Some(vec![[0, 1], [1, 2], [2, 3], [3, 0]]))
1028    }
1029
1030    #[test]
1031    fn not_oriented_by_default() {
1032        assert!(ccw_square().segment_normal_constraints(0).is_none());
1033    }
1034
1035    #[test]
1036    fn face_and_edge_normals_are_unit_and_outward() {
1037        let mut polyline = ccw_square();
1038        polyline.set_flags(PolylineFlags::ORIENTED);
1039
1040        for i in 0..polyline.num_segments() as u32 {
1041            let segment = polyline.segment(i);
1042            let constraints = polyline.segment_normal_constraints(i).unwrap();
1043
1044            assert!((constraints.face.length() - 1.0).abs() < 1.0e-5);
1045            for edge in constraints.edges {
1046                assert!((edge.length() - 1.0).abs() < 1.0e-5);
1047            }
1048
1049            // The square is centered on the origin, so a midpoint doubles as its outward direction.
1050            let midpoint = (segment.a + segment.b) * 0.5;
1051            assert!(constraints.face.dot(midpoint) > 0.0);
1052        }
1053    }
1054
1055    #[test]
1056    fn corner_pseudo_normal_bisects_its_two_faces() {
1057        let mut polyline = ccw_square();
1058        polyline.set_flags(PolylineFlags::ORIENTED);
1059
1060        // Vertex 1's pseudo-normal bisects the bottom edge (-Y) and right edge (+X): (1, -1) normalized.
1061        let bottom = polyline.segment_normal_constraints(0).unwrap();
1062        assert!(bottom.edges[1].abs_diff_eq(Vector::new(1.0, -1.0).normalize(), 1.0e-5));
1063    }
1064
1065    #[test]
1066    fn degenerate_input_yields_no_segments() {
1067        // Auto-generated indices must not underflow `len - 1` on 0- or 1-vertex input.
1068        assert_eq!(Polyline::new(vec![], None).num_segments(), 0);
1069        assert_eq!(Polyline::new(vec![Vector::ZERO], None).num_segments(), 0);
1070    }
1071
1072    #[test]
1073    fn oriented_point_query_treats_interior_as_inside() {
1074        use crate::query::{PointQuery, PointQueryWithLocation};
1075
1076        let mut polyline = ccw_square();
1077        polyline.set_flags(PolylineFlags::ORIENTED);
1078        let inside = Vector::new(0.25, -0.5);
1079
1080        // Solid: an inside point is its own projection.
1081        let solid = polyline.project_local_point(inside, true);
1082        assert!(solid.is_inside);
1083        assert!(solid.point.abs_diff_eq(inside, 1.0e-5));
1084
1085        // Non-solid: still inside, but projected to the boundary.
1086        let hollow = polyline.project_local_point(inside, false);
1087        assert!(hollow.is_inside);
1088        assert!(!hollow.point.abs_diff_eq(inside, 1.0e-5));
1089
1090        assert!(polyline.contains_local_point(inside));
1091        assert!(
1092            polyline
1093                .project_local_point_and_get_location(inside, false)
1094                .0
1095                .is_inside
1096        );
1097        assert!(
1098            polyline
1099                .project_local_point_and_get_feature(inside)
1100                .0
1101                .is_inside
1102        );
1103    }
1104
1105    #[test]
1106    fn oriented_point_query_leaves_exterior_outside() {
1107        use crate::query::PointQuery;
1108
1109        let mut polyline = ccw_square();
1110        polyline.set_flags(PolylineFlags::ORIENTED);
1111        let outside = Vector::new(2.0, 0.0);
1112
1113        let proj = polyline.project_local_point(outside, true);
1114        assert!(!proj.is_inside);
1115        assert!(proj.point.abs_diff_eq(Vector::new(1.0, 0.0), 1.0e-5));
1116        assert!(!polyline.contains_local_point(outside));
1117    }
1118
1119    #[test]
1120    fn unoriented_polyline_has_no_interior() {
1121        use crate::query::PointQuery;
1122
1123        // Unoriented: a hollow wireframe with no interior.
1124        let polyline = ccw_square();
1125        let center = Vector::ZERO;
1126
1127        assert!(!polyline.project_local_point(center, true).is_inside);
1128        assert!(!polyline.contains_local_point(center));
1129    }
1130}