Skip to main content

parry2d/shape/
segment.rs

1//! Definition of the segment shape.
2
3#[cfg(feature = "dim3")]
4use crate::math::VectorExt;
5use crate::math::{Pose, Real, Vector};
6use crate::shape::{FeatureId, SupportMap};
7use core::mem;
8
9/// A line segment shape.
10///
11/// A segment is the simplest 1D shape, defined by two endpoints. It represents
12/// a straight line between two points with no thickness or volume.
13///
14/// # Structure
15///
16/// - **a**: The first endpoint
17/// - **b**: The second endpoint
18/// - **Direction**: Vectors from `a` toward `b`
19///
20/// # Properties
21///
22/// - **1-dimensional**: Has length but no width or volume
23/// - **Convex**: Always convex
24/// - **No volume**: Mass properties are zero
25/// - **Simple**: Very fast collision detection
26///
27/// # Use Cases
28///
29/// Segments are commonly used for:
30/// - **Thin objects**: Ropes, wires, laser beams
31/// - **Skeletal animation**: Bone connections
32/// - **Path representation**: Straight-line paths
33/// - **Geometry building block**: Part of polylines and meshes
34/// - **Testing**: Simple shape for debugging
35///
36/// # Note
37///
38/// For shapes with thickness, consider using [`Capsule`](super::Capsule) instead,
39/// which is a segment with a radius (rounded cylinder).
40///
41/// # Example
42///
43/// ```rust
44/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
45/// use parry3d::shape::Segment;
46/// use parry3d::math::Vector;
47///
48/// // Create a horizontal segment of length 5
49/// let a = Vector::ZERO;
50/// let b = Vector::new(5.0, 0.0, 0.0);
51/// let segment = Segment::new(a, b);
52///
53/// assert_eq!(segment.length(), 5.0);
54/// assert_eq!(segment.a, a);
55/// assert_eq!(segment.b, b);
56/// # }
57/// ```
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
60#[cfg_attr(feature = "encase", derive(encase::ShaderType))]
61#[cfg_attr(
62    feature = "rkyv",
63    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
64)]
65#[derive(PartialEq, Debug, Copy, Clone)]
66#[repr(C)]
67pub struct Segment {
68    /// The first endpoint of the segment.
69    pub a: Vector,
70    /// The second endpoint of the segment.
71    pub b: Vector,
72}
73
74/// Describes where a point is located on a segment.
75///
76/// This enum is used by point projection queries to indicate whether the
77/// projected point is at one of the endpoints or somewhere along the segment.
78///
79/// # Variants
80///
81/// - **OnVertex(id)**: Vector projects to an endpoint (0 = `a`, 1 = `b`)
82/// - **OnEdge(bary)**: Vector projects to the interior with barycentric coordinates
83///
84/// # Example
85///
86/// ```rust
87/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
88/// use parry3d::shape::SegmentPointLocation;
89///
90/// // Vector at first vertex
91/// let loc = SegmentPointLocation::OnVertex(0);
92/// assert_eq!(loc.barycentric_coordinates(), [1.0, 0.0]);
93///
94/// // Vector at second vertex
95/// let loc = SegmentPointLocation::OnVertex(1);
96/// assert_eq!(loc.barycentric_coordinates(), [0.0, 1.0]);
97///
98/// // Vector halfway along the segment
99/// let loc = SegmentPointLocation::OnEdge([0.5, 0.5]);
100/// assert_eq!(loc.barycentric_coordinates(), [0.5, 0.5]);
101/// # }
102/// ```
103#[derive(PartialEq, Debug, Clone, Copy)]
104pub enum SegmentPointLocation {
105    /// The point lies on a vertex (endpoint).
106    ///
107    /// - `0` = Vector is at `segment.a`
108    /// - `1` = Vector is at `segment.b`
109    OnVertex(u32),
110
111    /// The point lies on the segment interior.
112    ///
113    /// Contains barycentric coordinates `[u, v]` where:
114    /// - `u + v = 1.0`
115    /// - Vector = `a * u + b * v`
116    /// - `0.0 < u, v < 1.0` (strictly between endpoints)
117    OnEdge([Real; 2]),
118}
119
120impl SegmentPointLocation {
121    /// Returns the barycentric coordinates corresponding to this location.
122    ///
123    /// Barycentric coordinates `[u, v]` satisfy:
124    /// - `u + v = 1.0`
125    /// - Vector = `a * u + b * v`
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
131    /// use parry3d::shape::{Segment, SegmentPointLocation};
132    /// use parry3d::math::Vector;
133    ///
134    /// let segment = Segment::new(
135    ///     Vector::ZERO,
136    ///     Vector::new(10.0, 0.0, 0.0)
137    /// );
138    ///
139    /// // Vector at endpoint a
140    /// let loc_a = SegmentPointLocation::OnVertex(0);
141    /// assert_eq!(loc_a.barycentric_coordinates(), [1.0, 0.0]);
142    ///
143    /// // Vector at endpoint b
144    /// let loc_b = SegmentPointLocation::OnVertex(1);
145    /// assert_eq!(loc_b.barycentric_coordinates(), [0.0, 1.0]);
146    ///
147    /// // Vector at 30% from a to b
148    /// let loc_mid = SegmentPointLocation::OnEdge([0.7, 0.3]);
149    /// let coords = loc_mid.barycentric_coordinates();
150    /// assert_eq!(coords[0], 0.7);
151    /// assert_eq!(coords[1], 0.3);
152    /// # }
153    /// ```
154    pub fn barycentric_coordinates(&self) -> [Real; 2] {
155        let mut bcoords = [0.0; 2];
156
157        match self {
158            SegmentPointLocation::OnVertex(i) => bcoords[*i as usize] = 1.0,
159            SegmentPointLocation::OnEdge(uv) => {
160                bcoords[0] = uv[0];
161                bcoords[1] = uv[1];
162            }
163        }
164
165        bcoords
166    }
167}
168
169impl Segment {
170    /// Creates a new segment from two endpoints.
171    ///
172    /// # Arguments
173    ///
174    /// * `a` - The first endpoint
175    /// * `b` - The second endpoint
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
181    /// use parry3d::shape::Segment;
182    /// use parry3d::math::Vector;
183    ///
184    /// let segment = Segment::new(
185    ///     Vector::ZERO,
186    ///     Vector::new(5.0, 0.0, 0.0)
187    /// );
188    /// assert_eq!(segment.length(), 5.0);
189    /// # }
190    /// ```
191    #[inline]
192    pub fn new(a: Vector, b: Vector) -> Segment {
193        Segment { a, b }
194    }
195
196    /// Creates a segment reference from an array of two points.
197    ///
198    /// This is a zero-cost conversion using memory transmutation.
199    ///
200    /// # Example
201    ///
202    /// ```
203    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
204    /// use parry3d::shape::Segment;
205    /// use parry3d::math::Vector;
206    ///
207    /// let points = [Vector::ZERO, Vector::new(1.0, 0.0, 0.0)];
208    /// let segment = Segment::from_array(&points);
209    /// assert_eq!(segment.a, points[0]);
210    /// assert_eq!(segment.b, points[1]);
211    /// # }
212    /// ```
213    pub fn from_array(arr: &[Vector; 2]) -> &Segment {
214        unsafe { mem::transmute(arr) }
215    }
216
217    /// Computes a scaled version of this segment.
218    ///
219    /// Each endpoint is scaled component-wise by the scale vector.
220    ///
221    /// # Arguments
222    ///
223    /// * `scale` - The scaling factors for each axis
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
229    /// use parry3d::shape::Segment;
230    /// use parry3d::math::Vector;
231    ///
232    /// let segment = Segment::new(
233    ///     Vector::new(1.0, 2.0, 3.0),
234    ///     Vector::new(4.0, 5.0, 6.0)
235    /// );
236    ///
237    /// let scaled = segment.scaled(Vector::new(2.0, 2.0, 2.0));
238    /// assert_eq!(scaled.a, Vector::new(2.0, 4.0, 6.0));
239    /// assert_eq!(scaled.b, Vector::new(8.0, 10.0, 12.0));
240    /// # }
241    /// ```
242    pub fn scaled(self, scale: Vector) -> Self {
243        Self::new(self.a * scale, self.b * scale)
244    }
245
246    /// Returns the direction vector of this segment scaled by its length.
247    ///
248    /// This is equivalent to `b - a` and points from `a` toward `b`.
249    /// The magnitude equals the segment length.
250    ///
251    /// # Example
252    ///
253    /// ```
254    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
255    /// use parry3d::shape::Segment;
256    /// use parry3d::math::Vector;
257    ///
258    /// let segment = Segment::new(
259    ///     Vector::ZERO,
260    ///     Vector::new(3.0, 4.0, 0.0)
261    /// );
262    ///
263    /// let dir = segment.scaled_direction();
264    /// assert_eq!(dir, Vector::new(3.0, 4.0, 0.0));
265    /// assert_eq!(dir.length(), 5.0); // Length of the segment
266    /// # }
267    /// ```
268    pub fn scaled_direction(&self) -> Vector {
269        self.b - self.a
270    }
271
272    /// Returns the length of this segment.
273    ///
274    /// # Example
275    ///
276    /// ```
277    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
278    /// use parry3d::shape::Segment;
279    /// use parry3d::math::Vector;
280    ///
281    /// // 3-4-5 right triangle
282    /// let segment = Segment::new(
283    ///     Vector::ZERO,
284    ///     Vector::new(3.0, 4.0, 0.0)
285    /// );
286    /// assert_eq!(segment.length(), 5.0);
287    /// # }
288    /// ```
289    pub fn length(&self) -> Real {
290        self.scaled_direction().length()
291    }
292
293    /// Swaps the two endpoints of this segment.
294    ///
295    /// After swapping, `a` becomes `b` and `b` becomes `a`.
296    ///
297    /// # Example
298    ///
299    /// ```
300    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
301    /// use parry3d::shape::Segment;
302    /// use parry3d::math::Vector;
303    ///
304    /// let mut segment = Segment::new(
305    ///     Vector::new(1.0, 0.0, 0.0),
306    ///     Vector::new(5.0, 0.0, 0.0)
307    /// );
308    ///
309    /// segment.swap();
310    /// assert_eq!(segment.a, Vector::new(5.0, 0.0, 0.0));
311    /// assert_eq!(segment.b, Vector::new(1.0, 0.0, 0.0));
312    /// # }
313    /// ```
314    pub fn swap(&mut self) {
315        mem::swap(&mut self.a, &mut self.b)
316    }
317
318    /// Returns the unit direction vector of this segment.
319    ///
320    /// Vectors from `a` toward `b` with length 1.0.
321    ///
322    /// # Returns
323    ///
324    /// * `Some(direction)` - The normalized direction if the segment has non-zero length
325    /// * `None` - If both endpoints are equal (degenerate segment)
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
331    /// use parry3d::shape::Segment;
332    /// use parry3d::math::Vector;
333    ///
334    /// let segment = Segment::new(
335    ///     Vector::ZERO,
336    ///     Vector::new(3.0, 4.0, 0.0)
337    /// );
338    ///
339    /// if let Some(dir) = segment.direction() {
340    ///     // Direction is normalized
341    ///     assert!((dir.length() - 1.0).abs() < 1e-6);
342    ///     // Vectors from a to b
343    ///     assert_eq!(dir, Vector::new(0.6, 0.8, 0.0));
344    /// }
345    ///
346    /// // Degenerate segment (zero length)
347    /// let degenerate = Segment::new(Vector::ZERO, Vector::ZERO);
348    /// assert!(degenerate.direction().is_none());
349    /// # }
350    /// ```
351    pub fn direction(&self) -> Option<Vector> {
352        self.scaled_direction().try_normalize()
353    }
354
355    /// In 2D, the not-normalized counterclockwise normal of this segment.
356    #[cfg(feature = "dim2")]
357    pub fn scaled_normal(&self) -> Vector {
358        let dir = self.scaled_direction();
359        Vector::new(dir.y, -dir.x)
360    }
361
362    /// The not-normalized counterclockwise normal of this segment, assuming it lies on the plane
363    /// with the normal collinear to the given axis (0 = X, 1 = Y, 2 = Z).
364    #[cfg(feature = "dim3")]
365    pub fn scaled_planar_normal(&self, plane_axis: u8) -> Vector {
366        let dir = self.scaled_direction();
367        match plane_axis {
368            0 => Vector::new(0.0, dir.z, -dir.y),
369            1 => Vector::new(-dir.z, 0.0, dir.x),
370            2 => Vector::new(dir.y, -dir.x, 0.0),
371            _ => panic!("Invalid axis given: must be 0 (X axis), 1 (Y axis) or 2 (Z axis)"),
372        }
373    }
374
375    /// In 2D, the normalized counterclockwise normal of this segment.
376    #[cfg(feature = "dim2")]
377    pub fn normal(&self) -> Option<Vector> {
378        let (dir, length) = self.scaled_normal().normalize_and_length();
379        (length > crate::math::DEFAULT_EPSILON).then_some(dir)
380    }
381
382    /// Returns `None`. Exists only for API similarity with the 2D parry.
383    #[cfg(feature = "dim3")]
384    pub fn normal(&self) -> Option<Vector> {
385        None
386    }
387
388    /// The normalized counterclockwise normal of this segment, assuming it lies on the plane
389    /// with the normal collinear to the given axis (0 = X, 1 = Y, 2 = Z).
390    #[cfg(feature = "dim3")]
391    pub fn planar_normal(&self, plane_axis: u8) -> Option<Vector> {
392        self.scaled_planar_normal(plane_axis).try_normalize()
393    }
394
395    /// Applies the isometry `m` to the vertices of this segment and returns the resulting segment.
396    pub fn transformed(&self, m: &Pose) -> Self {
397        Segment::new(m * self.a, m * self.b)
398    }
399
400    /// Computes the point at the given location.
401    pub fn point_at(&self, location: &SegmentPointLocation) -> Vector {
402        match *location {
403            SegmentPointLocation::OnVertex(0) => self.a,
404            SegmentPointLocation::OnVertex(1) => self.b,
405            SegmentPointLocation::OnEdge(bcoords) => self.a * bcoords[0] + self.b * bcoords[1],
406            _ => panic!(),
407        }
408    }
409
410    /// The normal of the given feature of this shape.
411    pub fn feature_normal(&self, feature: FeatureId) -> Option<Vector> {
412        if let Some(direction) = self.direction() {
413            match feature {
414                FeatureId::Vertex(id) => {
415                    if id == 0 {
416                        Some(direction)
417                    } else {
418                        Some(-direction)
419                    }
420                }
421                #[cfg(feature = "dim3")]
422                FeatureId::Edge(_) => {
423                    let imin = direction.abs().min_position();
424                    let mut normal = Vector::ZERO;
425                    normal.vset(imin, 1.0);
426                    normal -= direction * direction.vget(imin);
427                    Some(normal.normalize())
428                }
429                FeatureId::Face(id) => {
430                    let mut dir = Vector::ZERO;
431                    if id == 0 {
432                        dir.x = direction.y;
433                        dir.y = -direction.x;
434                    } else {
435                        dir.x = -direction.y;
436                        dir.y = direction.x;
437                    }
438                    Some(dir)
439                }
440                _ => None,
441            }
442        } else {
443            Some(Vector::Y)
444        }
445    }
446}
447
448impl SupportMap for Segment {
449    #[inline]
450    fn local_support_point(&self, dir: Vector) -> Vector {
451        if self.a.dot(dir) > self.b.dot(dir) {
452            self.a
453        } else {
454            self.b
455        }
456    }
457}
458
459impl From<[Vector; 2]> for Segment {
460    fn from(arr: [Vector; 2]) -> Self {
461        *Self::from_array(&arr)
462    }
463}
464
465/*
466impl ConvexPolyhedron for Segment {
467    fn vertex(&self, id: FeatureId) -> Vector {
468        if id.unwrap_vertex() == 0 {
469            self.a
470        } else {
471            self.b
472        }
473    }
474
475    #[cfg(feature = "dim3")]
476    fn edge(&self, _: FeatureId) -> (Vector, Vector, FeatureId, FeatureId) {
477        (self.a, self.b, FeatureId::Vertex(0), FeatureId::Vertex(1))
478    }
479
480    #[cfg(feature = "dim3")]
481    fn face(&self, _: FeatureId, _: &mut ConvexPolygonalFeature) {
482        panic!("A segment does not have any face in dimensions higher than 2.")
483    }
484
485    #[cfg(feature = "dim2")]
486    fn face(&self, id: FeatureId, face: &mut ConvexPolygonalFeature) {
487        face.clear();
488
489        if let Some(normal) = utils::ccw_face_normal([&self.a, &self.b]) {
490            face.set_feature_id(id);
491
492            match id.unwrap_face() {
493                0 => {
494                    face.push(self.a, FeatureId::Vertex(0));
495                    face.push(self.b, FeatureId::Vertex(1));
496                    face.set_normal(normal);
497                }
498                1 => {
499                    face.push(self.b, FeatureId::Vertex(1));
500                    face.push(self.a, FeatureId::Vertex(0));
501                    face.set_normal(-normal);
502                }
503                _ => unreachable!(),
504            }
505        } else {
506            face.push(self.a, FeatureId::Vertex(0));
507            face.set_feature_id(FeatureId::Vertex(0));
508        }
509    }
510
511    #[cfg(feature = "dim2")]
512    fn support_face_toward(
513        &self,
514        m: &Pose,
515        dir: Vector,
516        face: &mut ConvexPolygonalFeature,
517    ) {
518        let seg_dir = self.scaled_direction();
519
520        if dir.perp(&seg_dir) >= 0.0 {
521            self.face(FeatureId::Face(0), face);
522        } else {
523            self.face(FeatureId::Face(1), face);
524        }
525        face.transform_by(m)
526    }
527
528    #[cfg(feature = "dim3")]
529    fn support_face_toward(
530        &self,
531        m: &Pose,
532        _: Vector,
533        face: &mut ConvexPolygonalFeature,
534    ) {
535        face.clear();
536        face.push(self.a, FeatureId::Vertex(0));
537        face.push(self.b, FeatureId::Vertex(1));
538        face.push_edge_feature_id(FeatureId::Edge(0));
539        face.set_feature_id(FeatureId::Edge(0));
540        face.transform_by(m)
541    }
542
543    fn support_feature_toward(
544        &self,
545        transform: &Pose,
546        dir: Vector,
547        eps: Real,
548        face: &mut ConvexPolygonalFeature,
549    ) {
550        face.clear();
551        let seg = self.transformed(transform);
552        let ceps = <Real as ComplexField>::sin(eps);
553
554        if let Some(seg_dir) = seg.direction() {
555            let cang = dir.dot(seg_dir);
556
557            if cang > ceps {
558                face.set_feature_id(FeatureId::Vertex(1));
559                face.push(seg.b, FeatureId::Vertex(1));
560            } else if cang < -ceps {
561                face.set_feature_id(FeatureId::Vertex(0));
562                face.push(seg.a, FeatureId::Vertex(0));
563            } else {
564                #[cfg(feature = "dim3")]
565                {
566                    face.push(seg.a, FeatureId::Vertex(0));
567                    face.push(seg.b, FeatureId::Vertex(1));
568                    face.push_edge_feature_id(FeatureId::Edge(0));
569                    face.set_feature_id(FeatureId::Edge(0));
570                }
571                #[cfg(feature = "dim2")]
572                {
573                    if dir.perp(&seg_dir) >= 0.0 {
574                        seg.face(FeatureId::Face(0), face);
575                    } else {
576                        seg.face(FeatureId::Face(1), face);
577                    }
578                }
579            }
580        }
581    }
582
583    fn support_feature_id_toward(&self, local_dir: Vector) -> FeatureId {
584        if let Some(seg_dir) = self.direction() {
585            let eps: Real = (f64::consts::PI / 180.0) as Real;
586            let seps = <Real as ComplexField>::sin(eps);
587            let dot = seg_dir.dot(local_dir.as_ref());
588
589            if dot <= seps {
590                #[cfg(feature = "dim2")]
591                {
592                    if local_dir.perp(seg_dir.as_ref()) >= 0.0 {
593                        FeatureId::Face(0)
594                    } else {
595                        FeatureId::Face(1)
596                    }
597                }
598                #[cfg(feature = "dim3")]
599                {
600                    FeatureId::Edge(0)
601                }
602            } else if dot >= 0.0 {
603                FeatureId::Vertex(1)
604            } else {
605                FeatureId::Vertex(0)
606            }
607        } else {
608            FeatureId::Vertex(0)
609        }
610    }
611}
612*/
613
614#[cfg(test)]
615mod test {
616    use crate::query::{Ray, RayCast};
617
618    pub use super::*;
619    #[test]
620    fn segment_intersect_zero_length_issue_31() {
621        // never intersect each other
622        let ray = Ray::new(Vector::ZERO, Vector::X);
623        let segment = Segment {
624            a: Vector::new(
625                10.0,
626                10.0,
627                #[cfg(feature = "dim3")]
628                10.0,
629            ),
630            b: Vector::new(
631                10.0,
632                10.0,
633                #[cfg(feature = "dim3")]
634                10.0,
635            ),
636        };
637
638        let hit = segment.intersects_ray(&Pose::identity(), &ray, Real::MAX);
639        assert_eq!(hit, false);
640    }
641    #[test]
642    fn segment_very_close_points_hit() {
643        let epsilon = 1.1920929e-7;
644        // intersect each other
645        let ray = Ray::new(
646            Vector::new(
647                epsilon * 0.5,
648                0.3,
649                #[cfg(feature = "dim3")]
650                0.0,
651            ),
652            -Vector::Y,
653        );
654        let segment = Segment {
655            a: Vector::ZERO,
656            b: Vector::new(
657                // Theoretically, epsilon would suffice but imprecisions force us to add some more offset.
658                epsilon * 1.01,
659                0.0,
660                #[cfg(feature = "dim3")]
661                0.0,
662            ),
663        };
664
665        let hit = segment.intersects_ray(&Pose::identity(), &ray, Real::MAX);
666        assert_eq!(hit, true);
667    }
668    #[test]
669    fn segment_very_close_points_no_hit() {
670        let epsilon = 1.1920929e-7;
671        // never intersect each other
672        let ray = Ray::new(
673            Vector::new(
674                // Theoretically, epsilon would suffice  but imprecisions force us to add some more offset.
675                epsilon * 11.0,
676                0.1,
677                #[cfg(feature = "dim3")]
678                0.0,
679            ),
680            -Vector::Y,
681        );
682        let segment = Segment {
683            a: Vector::ZERO,
684            b: Vector::new(
685                epsilon * 0.9,
686                0.0,
687                #[cfg(feature = "dim3")]
688                0.0,
689            ),
690        };
691
692        let hit = segment.intersects_ray(&Pose::identity(), &ray, Real::MAX);
693        assert_eq!(hit, false);
694    }
695}