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