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}