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 segment shape.
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
15#[cfg_attr(
16    feature = "rkyv",
17    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
18    archive(as = "Self")
19)]
20#[derive(PartialEq, Debug, Copy, Clone)]
21#[repr(C)]
22pub struct Segment {
23    /// The segment first point.
24    pub a: Point<Real>,
25    /// The segment second point.
26    pub b: Point<Real>,
27}
28
29/// Logical description of the location of a point on a triangle.
30#[derive(PartialEq, Debug, Clone, Copy)]
31pub enum SegmentPointLocation {
32    /// The point lies on a vertex.
33    OnVertex(u32),
34    /// The point lies on the segment interior.
35    OnEdge([Real; 2]),
36}
37
38impl SegmentPointLocation {
39    /// The barycentric coordinates corresponding to this point location.
40    pub fn barycentric_coordinates(&self) -> [Real; 2] {
41        let mut bcoords = [0.0; 2];
42
43        match self {
44            SegmentPointLocation::OnVertex(i) => bcoords[*i as usize] = 1.0,
45            SegmentPointLocation::OnEdge(uv) => {
46                bcoords[0] = uv[0];
47                bcoords[1] = uv[1];
48            }
49        }
50
51        bcoords
52    }
53}
54
55impl Segment {
56    /// Creates a new segment from two points.
57    #[inline]
58    pub fn new(a: Point<Real>, b: Point<Real>) -> Segment {
59        Segment { a, b }
60    }
61
62    /// Creates the reference to a segment from the reference to an array of two points.
63    pub fn from_array(arr: &[Point<Real>; 2]) -> &Segment {
64        unsafe { mem::transmute(arr) }
65    }
66
67    /// Computes a scaled version of this segment.
68    pub fn scaled(self, scale: &Vector<Real>) -> Self {
69        Self::new(
70            na::Scale::from(*scale) * self.a,
71            na::Scale::from(*scale) * self.b,
72        )
73    }
74
75    /// The direction of this segment scaled by its length.
76    ///
77    /// Points from `self.a` toward `self.b`.
78    pub fn scaled_direction(&self) -> Vector<Real> {
79        self.b - self.a
80    }
81
82    /// The length of this segment.
83    pub fn length(&self) -> Real {
84        self.scaled_direction().norm()
85    }
86
87    /// Swaps the two vertices of this segment.
88    pub fn swap(&mut self) {
89        mem::swap(&mut self.a, &mut self.b)
90    }
91
92    /// The unit direction of this segment.
93    ///
94    /// Points from `self.a()` toward `self.b()`.
95    /// Returns `None` is both points are equal.
96    pub fn direction(&self) -> Option<Unit<Vector<Real>>> {
97        Unit::try_new(self.scaled_direction(), crate::math::DEFAULT_EPSILON)
98    }
99
100    /// In 2D, the not-normalized counterclockwise normal of this segment.
101    #[cfg(feature = "dim2")]
102    pub fn scaled_normal(&self) -> Vector<Real> {
103        let dir = self.scaled_direction();
104        Vector::new(dir.y, -dir.x)
105    }
106
107    /// The not-normalized counterclockwise normal of this segment, assuming it lies on the plane
108    /// with the normal collinear to the given axis (0 = X, 1 = Y, 2 = Z).
109    #[cfg(feature = "dim3")]
110    pub fn scaled_planar_normal(&self, plane_axis: u8) -> Vector<Real> {
111        let dir = self.scaled_direction();
112        match plane_axis {
113            0 => Vector::new(0.0, dir.z, -dir.y),
114            1 => Vector::new(-dir.z, 0.0, dir.x),
115            2 => Vector::new(dir.y, -dir.x, 0.0),
116            _ => panic!("Invalid axis given: must be 0 (X axis), 1 (Y axis) or 2 (Z axis)"),
117        }
118    }
119
120    /// In 2D, the normalized counterclockwise normal of this segment.
121    #[cfg(feature = "dim2")]
122    pub fn normal(&self) -> Option<Unit<Vector<Real>>> {
123        Unit::try_new(self.scaled_normal(), crate::math::DEFAULT_EPSILON)
124    }
125
126    /// Returns `None`. Exists only for API similarity with the 2D parry.
127    #[cfg(feature = "dim3")]
128    pub fn normal(&self) -> Option<Unit<Vector<Real>>> {
129        None
130    }
131
132    /// The normalized counterclockwise normal of this segment, assuming it lies on the plane
133    /// with the normal collinear to the given axis (0 = X, 1 = Y, 2 = Z).
134    #[cfg(feature = "dim3")]
135    pub fn planar_normal(&self, plane_axis: u8) -> Option<Unit<Vector<Real>>> {
136        Unit::try_new(
137            self.scaled_planar_normal(plane_axis),
138            crate::math::DEFAULT_EPSILON,
139        )
140    }
141
142    /// Applies the isometry `m` to the vertices of this segment and returns the resulting segment.
143    pub fn transformed(&self, m: &Isometry<Real>) -> Self {
144        Segment::new(m * self.a, m * self.b)
145    }
146
147    /// Computes the point at the given location.
148    pub fn point_at(&self, location: &SegmentPointLocation) -> Point<Real> {
149        match *location {
150            SegmentPointLocation::OnVertex(0) => self.a,
151            SegmentPointLocation::OnVertex(1) => self.b,
152            SegmentPointLocation::OnEdge(bcoords) => {
153                self.a * bcoords[0] + self.b.coords * bcoords[1]
154            }
155            _ => panic!(),
156        }
157    }
158
159    /// The normal of the given feature of this shape.
160    pub fn feature_normal(&self, feature: FeatureId) -> Option<Unit<Vector<Real>>> {
161        if let Some(direction) = self.direction() {
162            match feature {
163                FeatureId::Vertex(id) => {
164                    if id == 0 {
165                        Some(direction)
166                    } else {
167                        Some(-direction)
168                    }
169                }
170                #[cfg(feature = "dim3")]
171                FeatureId::Edge(_) => {
172                    let iamin = direction.iamin();
173                    let mut normal = Vector::zeros();
174                    normal[iamin] = 1.0;
175                    normal -= *direction * direction[iamin];
176                    Some(Unit::new_normalize(normal))
177                }
178                FeatureId::Face(id) => {
179                    let mut dir = Vector::zeros();
180                    if id == 0 {
181                        dir[0] = direction[1];
182                        dir[1] = -direction[0];
183                    } else {
184                        dir[0] = -direction[1];
185                        dir[1] = direction[0];
186                    }
187                    Some(Unit::new_unchecked(dir))
188                }
189                _ => None,
190            }
191        } else {
192            Some(Vector::y_axis())
193        }
194    }
195}
196
197impl SupportMap for Segment {
198    #[inline]
199    fn local_support_point(&self, dir: &Vector<Real>) -> Point<Real> {
200        if self.a.coords.dot(dir) > self.b.coords.dot(dir) {
201            self.a
202        } else {
203            self.b
204        }
205    }
206}
207
208impl From<[Point<Real>; 2]> for Segment {
209    fn from(arr: [Point<Real>; 2]) -> Self {
210        *Self::from_array(&arr)
211    }
212}
213
214/*
215impl ConvexPolyhedron for Segment {
216    fn vertex(&self, id: FeatureId) -> Point<Real> {
217        if id.unwrap_vertex() == 0 {
218            self.a
219        } else {
220            self.b
221        }
222    }
223
224    #[cfg(feature = "dim3")]
225    fn edge(&self, _: FeatureId) -> (Point<Real>, Point<Real>, FeatureId, FeatureId) {
226        (self.a, self.b, FeatureId::Vertex(0), FeatureId::Vertex(1))
227    }
228
229    #[cfg(feature = "dim3")]
230    fn face(&self, _: FeatureId, _: &mut ConvexPolygonalFeature) {
231        panic!("A segment does not have any face in dimensions higher than 2.")
232    }
233
234    #[cfg(feature = "dim2")]
235    fn face(&self, id: FeatureId, face: &mut ConvexPolygonalFeature) {
236        face.clear();
237
238        if let Some(normal) = utils::ccw_face_normal([&self.a, &self.b]) {
239            face.set_feature_id(id);
240
241            match id.unwrap_face() {
242                0 => {
243                    face.push(self.a, FeatureId::Vertex(0));
244                    face.push(self.b, FeatureId::Vertex(1));
245                    face.set_normal(normal);
246                }
247                1 => {
248                    face.push(self.b, FeatureId::Vertex(1));
249                    face.push(self.a, FeatureId::Vertex(0));
250                    face.set_normal(-normal);
251                }
252                _ => unreachable!(),
253            }
254        } else {
255            face.push(self.a, FeatureId::Vertex(0));
256            face.set_feature_id(FeatureId::Vertex(0));
257        }
258    }
259
260    #[cfg(feature = "dim2")]
261    fn support_face_toward(
262        &self,
263        m: &Isometry<Real>,
264        dir: &Unit<Vector<Real>>,
265        face: &mut ConvexPolygonalFeature,
266    ) {
267        let seg_dir = self.scaled_direction();
268
269        if dir.perp(&seg_dir) >= 0.0 {
270            self.face(FeatureId::Face(0), face);
271        } else {
272            self.face(FeatureId::Face(1), face);
273        }
274        face.transform_by(m)
275    }
276
277    #[cfg(feature = "dim3")]
278    fn support_face_toward(
279        &self,
280        m: &Isometry<Real>,
281        _: &Unit<Vector<Real>>,
282        face: &mut ConvexPolygonalFeature,
283    ) {
284        face.clear();
285        face.push(self.a, FeatureId::Vertex(0));
286        face.push(self.b, FeatureId::Vertex(1));
287        face.push_edge_feature_id(FeatureId::Edge(0));
288        face.set_feature_id(FeatureId::Edge(0));
289        face.transform_by(m)
290    }
291
292    fn support_feature_toward(
293        &self,
294        transform: &Isometry<Real>,
295        dir: &Unit<Vector<Real>>,
296        eps: Real,
297        face: &mut ConvexPolygonalFeature,
298    ) {
299        face.clear();
300        let seg = self.transformed(transform);
301        let ceps = ComplexField::sin(eps);
302
303        if let Some(seg_dir) = seg.direction() {
304            let cang = dir.dot(&seg_dir);
305
306            if cang > ceps {
307                face.set_feature_id(FeatureId::Vertex(1));
308                face.push(seg.b, FeatureId::Vertex(1));
309            } else if cang < -ceps {
310                face.set_feature_id(FeatureId::Vertex(0));
311                face.push(seg.a, FeatureId::Vertex(0));
312            } else {
313                #[cfg(feature = "dim3")]
314                {
315                    face.push(seg.a, FeatureId::Vertex(0));
316                    face.push(seg.b, FeatureId::Vertex(1));
317                    face.push_edge_feature_id(FeatureId::Edge(0));
318                    face.set_feature_id(FeatureId::Edge(0));
319                }
320                #[cfg(feature = "dim2")]
321                {
322                    if dir.perp(&seg_dir) >= 0.0 {
323                        seg.face(FeatureId::Face(0), face);
324                    } else {
325                        seg.face(FeatureId::Face(1), face);
326                    }
327                }
328            }
329        }
330    }
331
332    fn support_feature_id_toward(&self, local_dir: &Unit<Vector<Real>>) -> FeatureId {
333        if let Some(seg_dir) = self.direction() {
334            let eps: Real = na::convert::<f64, Real>(f64::consts::PI / 180.0);
335            let seps = ComplexField::sin(eps);
336            let dot = seg_dir.dot(local_dir.as_ref());
337
338            if dot <= seps {
339                #[cfg(feature = "dim2")]
340                {
341                    if local_dir.perp(seg_dir.as_ref()) >= 0.0 {
342                        FeatureId::Face(0)
343                    } else {
344                        FeatureId::Face(1)
345                    }
346                }
347                #[cfg(feature = "dim3")]
348                {
349                    FeatureId::Edge(0)
350                }
351            } else if dot >= 0.0 {
352                FeatureId::Vertex(1)
353            } else {
354                FeatureId::Vertex(0)
355            }
356        } else {
357            FeatureId::Vertex(0)
358        }
359    }
360}
361*/
362
363#[cfg(test)]
364mod test {
365    use crate::query::{Ray, RayCast};
366
367    pub use super::*;
368    #[test]
369    fn segment_intersect_zero_length_issue_31() {
370        // never intersect each other
371        let ray = Ray::new(Point::origin(), Vector::x());
372        let segment = Segment {
373            a: Point::new(
374                10.0,
375                10.0,
376                #[cfg(feature = "dim3")]
377                10.0,
378            ),
379            b: Point::new(
380                10.0,
381                10.0,
382                #[cfg(feature = "dim3")]
383                10.0,
384            ),
385        };
386
387        let hit = segment.intersects_ray(&Isometry::identity(), &ray, Real::MAX);
388        assert_eq!(hit, false);
389    }
390    #[test]
391    fn segment_very_close_points_hit() {
392        let epsilon = 1.1920929e-7;
393        // intersect each other
394        let ray = Ray::new(
395            Point::new(
396                epsilon * 0.5,
397                0.3,
398                #[cfg(feature = "dim3")]
399                0.0,
400            ),
401            -Vector::y(),
402        );
403        let segment = Segment {
404            a: Point::origin(),
405            b: Point::new(
406                // Theoretically, epsilon would suffice but imprecisions force us to add some more offset.
407                epsilon * 1.01,
408                0.0,
409                #[cfg(feature = "dim3")]
410                0.0,
411            ),
412        };
413
414        let hit = segment.intersects_ray(&Isometry::identity(), &ray, Real::MAX);
415        assert_eq!(hit, true);
416    }
417    #[test]
418    fn segment_very_close_points_no_hit() {
419        let epsilon = 1.1920929e-7;
420        // never intersect each other
421        let ray = Ray::new(
422            Point::new(
423                // Theoretically, epsilon would suffice  but imprecisions force us to add some more offset.
424                epsilon * 11.0,
425                0.1,
426                #[cfg(feature = "dim3")]
427                0.0,
428            ),
429            -Vector::y(),
430        );
431        let segment = Segment {
432            a: Point::origin(),
433            b: Point::new(
434                epsilon * 0.9,
435                0.0,
436                #[cfg(feature = "dim3")]
437                0.0,
438            ),
439        };
440
441        let hit = segment.intersects_ray(&Isometry::identity(), &ray, Real::MAX);
442        assert_eq!(hit, false);
443    }
444}