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