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}