parry3d/shape/
segment.rs

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