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}