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