parry3d/shape/
segment.rs

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