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}