parry3d/shape/triangle.rs
1//! Definition of the triangle shape.
2
3use crate::math::{ComplexField, Pose, Real, Vector};
4use crate::shape::SupportMap;
5use crate::shape::{PolygonalFeature, Segment};
6use crate::utils;
7
8use core::mem;
9use num::Zero;
10
11#[cfg(feature = "dim3")]
12use crate::shape::FeatureId;
13
14#[cfg(feature = "dim2")]
15use crate::shape::PackedFeatureId;
16
17/// A triangle shape defined by three vertices.
18///
19/// A triangle is one of the most fundamental shapes in computational geometry.
20/// It's the simplest 2D polygon and the building block for triangle meshes.
21///
22/// # Structure
23///
24/// - **a, b, c**: The three vertices of the triangle
25/// - **Edges**: AB (from a to b), BC (from b to c), CA (from c to a)
26/// - **Orientation**: Counter-clockwise (CCW) is the standard convention
27///
28/// # Properties
29///
30/// - **Convex**: Always convex
31/// - **2D/3D**: Can be used in both dimensions
32/// - **In 2D**: A filled triangular region with area
33/// - **In 3D**: A flat surface embedded in 3D space (zero volume)
34///
35/// # Orientation Convention
36///
37/// Triangles are typically defined with counter-clockwise vertex order:
38/// - Looking at the triangle from the "front", vertices go a → b → c in CCW order
39/// - The normal vector (3D) points toward the observer
40/// - Right-hand rule: Curl fingers from a→b→c, thumb points along normal
41///
42/// # Use Cases
43///
44/// - **Mesh building block**: Fundamental unit of triangle meshes
45/// - **Simple collision shapes**: Fast collision detection
46/// - **Terrain representation**: Ground planes and surfaces
47/// - **Testing and debugging**: Simple shape for verification
48///
49/// # Example
50///
51/// ```rust
52/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
53/// use parry3d::shape::Triangle;
54/// use parry3d::math::Vector;
55///
56/// // Create a right triangle in the XY plane
57/// let triangle = Triangle::new(
58/// Vector::ZERO, // a: origin
59/// Vector::new(3.0, 0.0, 0.0), // b: along +X
60/// Vector::new(0.0, 4.0, 0.0) // c: along +Y
61/// );
62///
63/// // Area of 3-4-5 right triangle is 6.0
64/// assert_eq!(triangle.area(), 6.0);
65///
66/// // Check if a point is inside
67/// let inside = Vector::new(1.0, 1.0, 0.0);
68/// assert!(triangle.contains_point(inside));
69/// # }
70/// ```
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
73#[cfg_attr(feature = "encase", derive(encase::ShaderType))]
74#[cfg_attr(
75 feature = "rkyv",
76 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
77)]
78#[derive(PartialEq, Debug, Copy, Clone, Default)]
79#[repr(C)]
80pub struct Triangle {
81 /// The first vertex of the triangle.
82 pub a: Vector,
83 /// The second vertex of the triangle.
84 pub b: Vector,
85 /// The third vertex of the triangle.
86 pub c: Vector,
87}
88
89/// Description of the location of a point on a triangle.
90#[derive(Copy, Clone, Debug)]
91pub enum TrianglePointLocation {
92 /// The point lies on a vertex.
93 OnVertex(u32),
94 /// The point lies on an edge.
95 ///
96 /// The 0-st edge is the segment AB.
97 /// The 1-st edge is the segment BC.
98 /// The 2-nd edge is the segment AC.
99 // XXX: it appears the conversion of edge indexing here does not match the
100 // convention of edge indexing for the `fn edge` method (from the ConvexPolyhedron impl).
101 OnEdge(u32, [Real; 2]),
102 /// The point lies on the triangle interior.
103 ///
104 /// The integer indicates on which side of the face the point is. 0 indicates the point
105 /// is on the half-space toward the CW normal of the triangle. 1 indicates the point is on the other
106 /// half-space. This is always set to 0 in 2D.
107 OnFace(u32, [Real; 3]),
108 /// The point lies on the triangle interior (for "solid" point queries).
109 OnSolid,
110}
111
112impl TrianglePointLocation {
113 /// The barycentric coordinates corresponding to this point location.
114 ///
115 /// Returns `None` if the location is `TrianglePointLocation::OnSolid`.
116 pub fn barycentric_coordinates(&self) -> Option<[Real; 3]> {
117 let mut bcoords = [0.0; 3];
118
119 match self {
120 TrianglePointLocation::OnVertex(i) => bcoords[*i as usize] = 1.0,
121 TrianglePointLocation::OnEdge(i, uv) => {
122 let idx = match i {
123 0 => (0, 1),
124 1 => (1, 2),
125 2 => (0, 2),
126 _ => unreachable!(),
127 };
128
129 bcoords[idx.0] = uv[0];
130 bcoords[idx.1] = uv[1];
131 }
132 TrianglePointLocation::OnFace(_, uvw) => {
133 bcoords[0] = uvw[0];
134 bcoords[1] = uvw[1];
135 bcoords[2] = uvw[2];
136 }
137 TrianglePointLocation::OnSolid => {
138 return None;
139 }
140 }
141
142 Some(bcoords)
143 }
144
145 /// Returns `true` if the point is located on the relative interior of the triangle.
146 pub fn is_on_face(&self) -> bool {
147 matches!(*self, TrianglePointLocation::OnFace(..))
148 }
149}
150
151/// Orientation of a triangle.
152#[derive(Copy, Clone, Debug, PartialEq, Eq)]
153pub enum TriangleOrientation {
154 /// Orientation with a clockwise orientation, i.e., with a positive signed area.
155 Clockwise,
156 /// Orientation with a clockwise orientation, i.e., with a negative signed area.
157 CounterClockwise,
158 /// Degenerate triangle.
159 Degenerate,
160}
161
162impl From<[Vector; 3]> for Triangle {
163 fn from(arr: [Vector; 3]) -> Self {
164 *Self::from_array(&arr)
165 }
166}
167
168impl Triangle {
169 /// Creates a triangle from three vertices.
170 ///
171 /// # Arguments
172 ///
173 /// * `a` - The first vertex
174 /// * `b` - The second vertex
175 /// * `c` - The third vertex
176 ///
177 /// # Convention
178 ///
179 /// For proper normal calculation and consistent collision detection, vertices
180 /// should be ordered counter-clockwise when viewed from the "front" side.
181 ///
182 /// # Example
183 ///
184 /// ```
185 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
186 /// use parry3d::shape::Triangle;
187 /// use parry3d::math::Vector;
188 ///
189 /// // Create a triangle in the XY plane
190 /// let tri = Triangle::new(
191 /// Vector::ZERO,
192 /// Vector::new(1.0, 0.0, 0.0),
193 /// Vector::new(0.0, 1.0, 0.0)
194 /// );
195 ///
196 /// assert_eq!(tri.area(), 0.5);
197 /// # }
198 /// ```
199 #[inline]
200 pub fn new(a: Vector, b: Vector, c: Vector) -> Triangle {
201 Triangle { a, b, c }
202 }
203
204 /// Creates the reference to a triangle from the reference to an array of three points.
205 pub fn from_array(arr: &[Vector; 3]) -> &Triangle {
206 unsafe { mem::transmute(arr) }
207 }
208
209 /// Reference to an array containing the three vertices of this triangle.
210 #[inline]
211 pub fn vertices(&self) -> &[Vector; 3] {
212 unsafe { mem::transmute(self) }
213 }
214
215 /// The normal of this triangle assuming it is oriented ccw.
216 ///
217 /// The normal points such that it is collinear to `AB × AC` (where `×` denotes the cross
218 /// product).
219 #[inline]
220 #[cfg(feature = "dim3")]
221 pub fn normal(&self) -> Option<Vector> {
222 self.scaled_normal().try_normalize()
223 }
224
225 /// The three edges of this triangle: [AB, BC, CA].
226 #[inline]
227 pub fn edges(&self) -> [Segment; 3] {
228 [
229 Segment::new(self.a, self.b),
230 Segment::new(self.b, self.c),
231 Segment::new(self.c, self.a),
232 ]
233 }
234
235 /// Computes a scaled version of this triangle.
236 pub fn scaled(self, scale: Vector) -> Self {
237 Self::new(self.a * scale, self.b * scale, self.c * scale)
238 }
239
240 /// Returns a new triangle with vertices transformed by `m`.
241 #[inline]
242 pub fn transformed(&self, m: &Pose) -> Self {
243 Triangle::new(m * self.a, m * self.b, m * self.c)
244 }
245
246 /// The three edges scaled directions of this triangle: [B - A, C - B, A - C].
247 #[inline]
248 pub fn edges_scaled_directions(&self) -> [Vector; 3] {
249 [self.b - self.a, self.c - self.b, self.a - self.c]
250 }
251
252 /// Return the edge segment of this cuboid with a normal cone containing
253 /// a direction that that maximizes the dot product with `local_dir`.
254 pub fn local_support_edge_segment(&self, dir: Vector) -> Segment {
255 let dots = [dir.dot(self.a), dir.dot(self.b), dir.dot(self.c)];
256
257 let imin = if dots[0] <= dots[1] && dots[0] <= dots[2] {
258 0
259 } else if dots[1] <= dots[2] {
260 1
261 } else {
262 2
263 };
264
265 match imin {
266 0 => Segment::new(self.b, self.c),
267 1 => Segment::new(self.c, self.a),
268 _ => Segment::new(self.a, self.b),
269 }
270 }
271
272 /// Return the face of this triangle with a normal that maximizes
273 /// the dot product with `dir`.
274 #[cfg(feature = "dim3")]
275 pub fn support_face(&self, _dir: Vector) -> PolygonalFeature {
276 PolygonalFeature::from(*self)
277 }
278
279 /// Return the face of this triangle with a normal that maximizes
280 /// the dot product with `dir`.
281 #[cfg(feature = "dim2")]
282 pub fn support_face(&self, dir: Vector) -> PolygonalFeature {
283 let mut best = 0;
284 let mut best_dot = -Real::MAX;
285
286 for (i, tangent) in self.edges_scaled_directions().iter().enumerate() {
287 let normal = Vector::new(tangent.y, -tangent.x);
288 if let Some(normal) = normal.try_normalize() {
289 let dot = normal.dot(dir);
290 if normal.dot(dir) > best_dot {
291 best = i;
292 best_dot = dot;
293 }
294 }
295 }
296
297 let pts = self.vertices();
298 let i1 = best;
299 let i2 = (best + 1) % 3;
300
301 PolygonalFeature {
302 vertices: [pts[i1], pts[i2]],
303 vids: PackedFeatureId::vertices([i1 as u32, i2 as u32]),
304 fid: PackedFeatureId::face(i1 as u32),
305 num_vertices: 2,
306 }
307 }
308
309 /// A vector normal of this triangle.
310 ///
311 /// The vector points such that it is collinear to `AB × AC` (where `×` denotes the cross
312 /// product).
313 ///
314 /// Note that on thin triangles the calculated normals can suffer from numerical issues.
315 /// For a more robust (but more computationally expensive) normal calculation, see
316 /// [`Triangle::robust_scaled_normal`].
317 #[inline]
318 #[cfg(feature = "dim3")]
319 pub fn scaled_normal(&self) -> Vector {
320 let ab = self.b - self.a;
321 let ac = self.c - self.a;
322 ab.cross(ac)
323 }
324
325 /// Find a triangle normal more robustly than with [`Triangle::scaled_normal`].
326 ///
327 /// Thin triangles can cause numerical issues when computing its normal. This method accounts
328 /// for these numerical issues more robustly than [`Triangle::scaled_normal`], but is more
329 /// computationally expensive.
330 #[inline]
331 #[cfg(feature = "dim3")]
332 pub fn robust_scaled_normal(&self) -> Vector {
333 let pts = self.vertices();
334 let best_vertex = self.angle_closest_to_90();
335 let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3];
336 let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3];
337
338 d1.cross(d2)
339 }
340
341 /// Similar to [`Triangle::robust_scaled_normal`], but returns the unit length normal.
342 #[inline]
343 #[cfg(feature = "dim3")]
344 pub fn robust_normal(&self) -> Vector {
345 self.robust_scaled_normal().normalize()
346 }
347
348 /// Computes the extents of this triangle on the given direction.
349 ///
350 /// This computes the min and max values of the dot products between each
351 /// vertex of this triangle and `dir`.
352 #[inline]
353 pub fn extents_on_dir(&self, dir: Vector) -> (Real, Real) {
354 let a = self.a.dot(dir);
355 let b = self.b.dot(dir);
356 let c = self.c.dot(dir);
357
358 if a > b {
359 if b > c {
360 (c, a)
361 } else if a > c {
362 (b, a)
363 } else {
364 (b, c)
365 }
366 } else {
367 // b >= a
368 if a > c {
369 (c, b)
370 } else if b > c {
371 (a, b)
372 } else {
373 (a, c)
374 }
375 }
376 }
377 //
378 // #[cfg(feature = "dim3")]
379 // fn support_feature_id_toward(&self, local_dir: Vector, eps: Real) -> FeatureId {
380 // if let Some(normal) = self.normal() {
381 // let (seps, ceps) = ComplexField::sin_cos(eps);
382 //
383 // let normal_dot = local_dir.dot(*normal);
384 // if normal_dot >= ceps {
385 // FeatureId::Face(0)
386 // } else if normal_dot <= -ceps {
387 // FeatureId::Face(1)
388 // } else {
389 // let edges = self.edges();
390 // let mut dots = [0.0; 3];
391 //
392 // let dir1 = edges[0].direction();
393 // if let Some(dir1) = dir1 {
394 // dots[0] = dir1.dot(local_dir);
395 //
396 // if dots[0].abs() < seps {
397 // return FeatureId::Edge(0);
398 // }
399 // }
400 //
401 // let dir2 = edges[1].direction();
402 // if let Some(dir2) = dir2 {
403 // dots[1] = dir2.dot(local_dir);
404 //
405 // if dots[1].abs() < seps {
406 // return FeatureId::Edge(1);
407 // }
408 // }
409 //
410 // let dir3 = edges[2].direction();
411 // if let Some(dir3) = dir3 {
412 // dots[2] = dir3.dot(local_dir);
413 //
414 // if dots[2].abs() < seps {
415 // return FeatureId::Edge(2);
416 // }
417 // }
418 //
419 // if dots[0] > 0.0 && dots[1] < 0.0 {
420 // FeatureId::Vertex(1)
421 // } else if dots[1] > 0.0 && dots[2] < 0.0 {
422 // FeatureId::Vertex(2)
423 // } else {
424 // FeatureId::Vertex(0)
425 // }
426 // }
427 // } else {
428 // FeatureId::Vertex(0)
429 // }
430 // }
431
432 /// The area of this triangle.
433 #[inline]
434 pub fn area(&self) -> Real {
435 // Kahan's formula.
436 let a = self.b.distance(self.a);
437 let b = self.c.distance(self.b);
438 let c = self.a.distance(self.c);
439
440 let (c, b, a) = utils::sort3(&a, &b, &c);
441 let a = *a;
442 let b = *b;
443 let c = *c;
444
445 let sqr = (a + (b + c)) * (c - (a - b)) * (c + (a - b)) * (a + (b - c));
446
447 // We take the max(0.0) because it can be slightly negative
448 // because of numerical errors due to almost-degenerate triangles.
449 <Real as ComplexField>::sqrt(sqr.max(0.0)) * 0.25
450 }
451
452 /// Computes the unit angular inertia of this triangle.
453 #[cfg(feature = "dim2")]
454 pub fn unit_angular_inertia(&self) -> Real {
455 let factor = 1.0 / 6.0;
456
457 // Algorithm adapted from Box2D
458 let e1 = self.b - self.a;
459 let e2 = self.c - self.a;
460
461 let intx2 = e1.x * e1.x + e2.x * e1.x + e2.x * e2.x;
462 let inty2 = e1.y * e1.y + e2.y * e1.y + e2.y * e2.y;
463 factor * (intx2 + inty2)
464 }
465
466 /// The geometric center of this triangle.
467 #[inline]
468 pub fn center(&self) -> Vector {
469 utils::center(&[self.a, self.b, self.c])
470 }
471
472 /// The perimeter of this triangle.
473 #[inline]
474 pub fn perimeter(&self) -> Real {
475 self.b.distance(self.a) + self.c.distance(self.b) + self.a.distance(self.c)
476 }
477
478 /// The circumcircle of this triangle.
479 pub fn circumcircle(&self) -> (Vector, Real) {
480 let a = self.a - self.c;
481 let b = self.b - self.c;
482
483 let na = a.length_squared();
484 let nb = b.length_squared();
485
486 let dab = a.dot(b);
487
488 let denom = 2.0 * (na * nb - dab * dab);
489
490 if denom.is_zero() {
491 // The triangle is degenerate (the three points are collinear).
492 // So we find the longest segment and take its center.
493 let c = self.a - self.b;
494 let nc = c.length_squared();
495
496 if nc >= na && nc >= nb {
497 // Longest segment: [&self.a, &self.b]
498 (
499 self.a.midpoint(self.b),
500 <Real as ComplexField>::sqrt(nc) / 2.0,
501 )
502 } else if na >= nb && na >= nc {
503 // Longest segment: [&self.a, pc]
504 (
505 self.a.midpoint(self.c),
506 <Real as ComplexField>::sqrt(na) / 2.0,
507 )
508 } else {
509 // Longest segment: [&self.b, &self.c]
510 (
511 self.b.midpoint(self.c),
512 <Real as ComplexField>::sqrt(nb) / 2.0,
513 )
514 }
515 } else {
516 let k = b * na - a * nb;
517
518 let center = self.c + (a * k.dot(b) - b * k.dot(a)) / denom;
519 let radius = center.distance(self.a);
520
521 (center, radius)
522 }
523 }
524
525 /// Tests if this triangle is affinely dependent, i.e., its points are almost aligned.
526 #[cfg(feature = "dim3")]
527 pub fn is_affinely_dependent(&self) -> bool {
528 const EPS: Real = crate::math::DEFAULT_EPSILON * 100.0;
529
530 let p1p2 = self.b - self.a;
531 let p1p3 = self.c - self.a;
532 relative_eq!(p1p2.cross(p1p3).length_squared(), 0.0, epsilon = EPS * EPS)
533
534 // relative_eq!(
535 // self.area(),
536 // 0.0,
537 // epsilon = EPS * self.perimeter()
538 // )
539 }
540
541 /// Is this triangle degenerate or almost degenerate?
542 #[cfg(feature = "dim3")]
543 pub fn is_affinely_dependent_eps(&self, eps: Real) -> bool {
544 let p1p2 = self.b - self.a;
545 let p1p3 = self.c - self.a;
546 relative_eq!(
547 p1p2.cross(p1p3).length(),
548 0.0,
549 epsilon = eps * p1p2.length().max(p1p3.length())
550 )
551
552 // relative_eq!(
553 // self.area(),
554 // 0.0,
555 // epsilon = EPS * self.perimeter()
556 // )
557 }
558
559 /// Tests if a point is inside of this triangle.
560 #[cfg(feature = "dim2")]
561 pub fn contains_point(&self, p: Vector) -> bool {
562 let ab = self.b - self.a;
563 let bc = self.c - self.b;
564 let ca = self.a - self.c;
565 let sgn1 = ab.perp_dot(p - self.a);
566 let sgn2 = bc.perp_dot(p - self.b);
567 let sgn3 = ca.perp_dot(p - self.c);
568 sgn1.signum() * sgn2.signum() >= 0.0
569 && sgn1.signum() * sgn3.signum() >= 0.0
570 && sgn2.signum() * sgn3.signum() >= 0.0
571 }
572
573 /// Tests if a point is inside of this triangle.
574 #[cfg(feature = "dim3")]
575 pub fn contains_point(&self, p: Vector) -> bool {
576 const EPS: Real = crate::math::DEFAULT_EPSILON;
577
578 let vb = self.b - self.a;
579 let vc = self.c - self.a;
580 let vp = p - self.a;
581
582 let n = vc.cross(vb);
583 let n_norm = n.length_squared();
584 if n_norm < EPS || vp.dot(n).abs() > EPS * n_norm {
585 // the triangle is degenerate or the
586 // point does not lie on the same plane as the triangle.
587 return false;
588 }
589
590 // We are seeking B, C such that vp = vb * B + vc * C .
591 // If B and C are both in [0, 1] and B + C <= 1 then p is in the triangle.
592 //
593 // We can project this equation along a vector nb coplanar to the triangle
594 // and perpendicular to vb:
595 // vp.dot(nb) = vb.dot(nb) * B + vc.dot(nb) * C
596 // => C = vp.dot(nb) / vc.dot(nb)
597 // and similarly for B.
598 //
599 // In order to avoid divisions and sqrts we scale both B and C - so
600 // b = vb.dot(nc) * B and c = vc.dot(nb) * C - this results in harder-to-follow math but
601 // hopefully fast code.
602
603 let nb = vb.cross(n);
604 let nc = vc.cross(n);
605
606 let signed_blim = vb.dot(nc);
607 let b = vp.dot(nc) * signed_blim.signum();
608 let blim = signed_blim.abs();
609
610 let signed_clim = vc.dot(nb);
611 let c = vp.dot(nb) * signed_clim.signum();
612 let clim = signed_clim.abs();
613
614 c >= 0.0 && c <= clim && b >= 0.0 && b <= blim && c * blim + b * clim <= blim * clim
615 }
616
617 /// The normal of the given feature of this shape.
618 #[cfg(feature = "dim3")]
619 pub fn feature_normal(&self, _: FeatureId) -> Option<Vector> {
620 self.normal()
621 }
622
623 /// The orientation of the triangle, based on its signed area.
624 ///
625 /// Returns `TriangleOrientation::Degenerate` if the triangle’s area is
626 /// smaller than `epsilon`.
627 #[cfg(feature = "dim2")]
628 pub fn orientation(&self, epsilon: Real) -> TriangleOrientation {
629 let area2 = (self.b - self.a).perp_dot(self.c - self.a);
630 // println!("area2: {}", area2);
631 if area2 > epsilon {
632 TriangleOrientation::CounterClockwise
633 } else if area2 < -epsilon {
634 TriangleOrientation::Clockwise
635 } else {
636 TriangleOrientation::Degenerate
637 }
638 }
639
640 /// The orientation of the 2D triangle, based on its signed area.
641 ///
642 /// Returns `TriangleOrientation::Degenerate` if the triangle's area is
643 /// smaller than `epsilon`.
644 pub fn orientation2d(
645 a: crate::math::Vector2,
646 b: crate::math::Vector2,
647 c: crate::math::Vector2,
648 epsilon: Real,
649 ) -> TriangleOrientation {
650 let area2 = (b - a).perp_dot(c - a);
651 // println!("area2: {}", area2);
652 if area2 > epsilon {
653 TriangleOrientation::CounterClockwise
654 } else if area2 < -epsilon {
655 TriangleOrientation::Clockwise
656 } else {
657 TriangleOrientation::Degenerate
658 }
659 }
660
661 /// Find the index of a vertex in this triangle, such that the two
662 /// edges incident in that vertex form the angle closest to 90
663 /// degrees in the triangle.
664 pub fn angle_closest_to_90(&self) -> usize {
665 let points = self.vertices();
666 let mut best_cos = 2.0;
667 let mut selected_i = 0;
668
669 for i in 0..3 {
670 let d1 = (points[i] - points[(i + 1) % 3]).normalize();
671 let d2 = (points[(i + 2) % 3] - points[(i + 1) % 3]).normalize();
672
673 let cos_abs = d1.dot(d2).abs();
674
675 if cos_abs < best_cos {
676 best_cos = cos_abs;
677 selected_i = i;
678 }
679 }
680
681 selected_i
682 }
683
684 /// Reverse the orientation of this triangle by swapping b and c.
685 pub fn reverse(&mut self) {
686 mem::swap(&mut self.b, &mut self.c);
687 }
688}
689
690impl SupportMap for Triangle {
691 #[inline]
692 fn local_support_point(&self, dir: Vector) -> Vector {
693 let d1 = self.a.dot(dir);
694 let d2 = self.b.dot(dir);
695 let d3 = self.c.dot(dir);
696
697 if d1 > d2 {
698 if d1 > d3 {
699 self.a
700 } else {
701 self.c
702 }
703 } else if d2 > d3 {
704 self.b
705 } else {
706 self.c
707 }
708 }
709}
710
711/*
712#[cfg(feature = "dim3")]
713impl ConvexPolyhedron for Triangle {
714 fn vertex(&self, id: FeatureId) -> Vector {
715 match id.unwrap_vertex() {
716 0 => self.a,
717 1 => self.b,
718 2 => self.c,
719 _ => panic!("Triangle vertex index out of bounds."),
720 }
721 }
722 fn edge(&self, id: FeatureId) -> (Vector, Vector, FeatureId, FeatureId) {
723 match id.unwrap_edge() {
724 0 => (self.a, self.b, FeatureId::Vertex(0), FeatureId::Vertex(1)),
725 1 => (self.b, self.c, FeatureId::Vertex(1), FeatureId::Vertex(2)),
726 2 => (self.c, self.a, FeatureId::Vertex(2), FeatureId::Vertex(0)),
727 _ => panic!("Triangle edge index out of bounds."),
728 }
729 }
730
731 fn face(&self, id: FeatureId, face: &mut ConvexPolygonalFeature) {
732 face.clear();
733
734 if let Some(normal) = self.normal() {
735 face.set_feature_id(id);
736
737 match id.unwrap_face() {
738 0 => {
739 face.push(self.a, FeatureId::Vertex(0));
740 face.push(self.b, FeatureId::Vertex(1));
741 face.push(self.c, FeatureId::Vertex(2));
742 face.push_edge_feature_id(FeatureId::Edge(0));
743 face.push_edge_feature_id(FeatureId::Edge(1));
744 face.push_edge_feature_id(FeatureId::Edge(2));
745 face.set_normal(normal);
746 }
747 1 => {
748 face.push(self.a, FeatureId::Vertex(0));
749 face.push(self.c, FeatureId::Vertex(2));
750 face.push(self.b, FeatureId::Vertex(1));
751 face.push_edge_feature_id(FeatureId::Edge(2));
752 face.push_edge_feature_id(FeatureId::Edge(1));
753 face.push_edge_feature_id(FeatureId::Edge(0));
754 face.set_normal(-normal);
755 }
756 _ => unreachable!(),
757 }
758
759 face.recompute_edge_normals();
760 } else {
761 face.push(self.a, FeatureId::Vertex(0));
762 face.set_feature_id(FeatureId::Vertex(0));
763 }
764 }
765
766 fn support_face_toward(
767 &self,
768 m: &Pose,
769 dir: Vector,
770 face: &mut ConvexPolygonalFeature,
771 ) {
772 let normal = self.scaled_normal();
773
774 if normal.dot(*dir) >= 0.0 {
775 ConvexPolyhedron::face(self, FeatureId::Face(0), face);
776 } else {
777 ConvexPolyhedron::face(self, FeatureId::Face(1), face);
778 }
779 face.transform_by(m)
780 }
781
782 fn support_feature_toward(
783 &self,
784 transform: &Pose,
785 dir: Vector,
786 eps: Real,
787 out: &mut ConvexPolygonalFeature,
788 ) {
789 out.clear();
790 let tri = self.transformed(transform);
791 let feature = tri.support_feature_id_toward(dir, eps);
792
793 match feature {
794 FeatureId::Vertex(_) => {
795 let v = tri.vertex(feature);
796 out.push(v, feature);
797 out.set_feature_id(feature);
798 }
799 FeatureId::Edge(_) => {
800 let (a, b, fa, fb) = tri.edge(feature);
801 out.push(a, fa);
802 out.push(b, fb);
803 out.push_edge_feature_id(feature);
804 out.set_feature_id(feature);
805 }
806 FeatureId::Face(_) => tri.face(feature, out),
807 _ => unreachable!(),
808 }
809 }
810
811 fn support_feature_id_toward(&self, local_dir: Vector) -> FeatureId {
812 self.support_feature_id_toward(local_dir, (f64::consts::PI / 180.0) as Real)
813 }
814}
815*/
816
817#[cfg(feature = "dim2")]
818#[cfg(test)]
819mod test {
820 use crate::math::Vector;
821 use crate::shape::Triangle;
822
823 #[test]
824 fn test_triangle_area() {
825 let pa = Vector::new(5.0, 0.0);
826 let pb = Vector::ZERO;
827 let pc = Vector::new(0.0, 4.0);
828
829 assert!(relative_eq!(Triangle::new(pa, pb, pc).area(), 10.0));
830 }
831
832 #[test]
833 fn test_triangle_contains_point() {
834 let tri = Triangle::new(Vector::new(5.0, 0.0), Vector::ZERO, Vector::new(0.0, 4.0));
835
836 assert!(tri.contains_point(Vector::new(1.0, 1.0)));
837 assert!(!tri.contains_point(Vector::new(-1.0, 1.0)));
838 }
839
840 #[test]
841 fn test_obtuse_triangle_contains_point() {
842 let tri = Triangle::new(
843 Vector::new(-10.0, 10.0),
844 Vector::ZERO,
845 Vector::new(20.0, 0.0),
846 );
847
848 assert!(tri.contains_point(Vector::new(-3.0, 5.0)));
849 assert!(tri.contains_point(Vector::new(5.0, 1.0)));
850 assert!(!tri.contains_point(Vector::new(0.0, -1.0)));
851 }
852}
853
854#[cfg(feature = "dim3")]
855#[cfg(test)]
856mod test {
857 use crate::math::{Real, Vector};
858 use crate::shape::Triangle;
859
860 #[test]
861 fn test_triangle_area() {
862 let pa = Vector::new(0.0, 5.0, 0.0);
863 let pb = Vector::ZERO;
864 let pc = Vector::new(0.0, 0.0, 4.0);
865
866 assert!(relative_eq!(Triangle::new(pa, pb, pc).area(), 10.0));
867 }
868
869 #[test]
870 fn test_triangle_contains_point() {
871 let tri = Triangle::new(
872 Vector::new(0.0, 5.0, 0.0),
873 Vector::ZERO,
874 Vector::new(0.0, 0.0, 4.0),
875 );
876
877 assert!(tri.contains_point(Vector::new(0.0, 1.0, 1.0)));
878 assert!(!tri.contains_point(Vector::new(0.0, -1.0, 1.0)));
879 }
880
881 #[test]
882 fn test_obtuse_triangle_contains_point() {
883 let tri = Triangle::new(
884 Vector::new(-10.0, 10.0, 0.0),
885 Vector::ZERO,
886 Vector::new(20.0, 0.0, 0.0),
887 );
888
889 assert!(tri.contains_point(Vector::new(-3.0, 5.0, 0.0)));
890 assert!(tri.contains_point(Vector::new(5.0, 1.0, 0.0)));
891 assert!(!tri.contains_point(Vector::new(0.0, -1.0, 0.0)));
892 }
893
894 #[test]
895 fn test_3dtriangle_contains_point() {
896 let o = Vector::ZERO;
897 let pa = Vector::new(1.2, 1.4, 5.6);
898 let pb = Vector::new(1.5, 6.7, 1.9);
899 let pc = Vector::new(5.0, 2.1, 1.3);
900
901 let tri = Triangle::new(pa, pb, pc);
902
903 let va = pa - o;
904 let vb = pb - o;
905 let vc = pc - o;
906
907 let n = (pa - pb).cross(pb - pc);
908
909 // This is a simple algorithm for generating points that are inside the
910 // triangle: o + (va * alpha + vb * beta + vc * gamma) is always inside the
911 // triangle if:
912 // * each of alpha, beta, gamma is in (0, 1)
913 // * alpha + beta + gamma = 1
914 let contained_p = o + (va * 0.2 + vb * 0.3 + vc * 0.5);
915 let not_contained_coplanar_p = o + (va * -0.5 + vb * 0.8 + vc * 0.7);
916 let not_coplanar_p = o + (va * 0.2 + vb * 0.3 + vc * 0.5) + n * 0.1;
917 let not_coplanar_p2 = o + (va * -0.5 + vb * 0.8 + vc * 0.7) + n * 0.1;
918 assert!(tri.contains_point(contained_p));
919 assert!(!tri.contains_point(not_contained_coplanar_p));
920 assert!(!tri.contains_point(not_coplanar_p));
921 assert!(!tri.contains_point(not_coplanar_p2));
922
923 // Test that points that are clearly within the triangle as seen as such, by testing
924 // a number of points along a line intersecting the triangle.
925 for i in -50i16..150 {
926 let a = 0.15;
927 let b = 0.01 * Real::from(i); // b ranges from -0.5 to 1.5
928 let c = 1.0 - a - b;
929 let p = o + (va * a + vb * b + vc * c);
930
931 match i {
932 ii if ii < 0 || ii > 85 => assert!(
933 !tri.contains_point(p),
934 "Should not contain: i = {}, b = {}",
935 i,
936 b
937 ),
938 ii if ii > 0 && ii < 85 => assert!(
939 tri.contains_point(p),
940 "Should contain: i = {}, b = {}",
941 i,
942 b
943 ),
944 _ => (), // Vectors at the edge may be seen as inside or outside
945 }
946 }
947 }
948}