parry2d/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 // Half the cross-product magnitude, which unlike Kahan's formula on the rounded
436 // side lengths is exactly 0.0 for bitwise-collinear vertices.
437 let ab = self.b - self.a;
438 let ac = self.c - self.a;
439
440 #[cfg(feature = "dim2")]
441 return ab.perp_dot(ac).abs() * 0.5;
442
443 #[cfg(feature = "dim3")]
444 return ab.cross(ac).length() * 0.5;
445 }
446
447 /// Computes the unit angular inertia of this triangle.
448 #[cfg(feature = "dim2")]
449 pub fn unit_angular_inertia(&self) -> Real {
450 let factor = 1.0 / 6.0;
451
452 // Algorithm adapted from Box2D
453 let e1 = self.b - self.a;
454 let e2 = self.c - self.a;
455
456 let intx2 = e1.x * e1.x + e2.x * e1.x + e2.x * e2.x;
457 let inty2 = e1.y * e1.y + e2.y * e1.y + e2.y * e2.y;
458 factor * (intx2 + inty2)
459 }
460
461 /// The geometric center of this triangle.
462 #[inline]
463 pub fn center(&self) -> Vector {
464 utils::center(&[self.a, self.b, self.c])
465 }
466
467 /// The perimeter of this triangle.
468 #[inline]
469 pub fn perimeter(&self) -> Real {
470 self.b.distance(self.a) + self.c.distance(self.b) + self.a.distance(self.c)
471 }
472
473 /// The circumcircle of this triangle.
474 pub fn circumcircle(&self) -> (Vector, Real) {
475 let a = self.a - self.c;
476 let b = self.b - self.c;
477
478 let na = a.length_squared();
479 let nb = b.length_squared();
480
481 let dab = a.dot(b);
482
483 let denom = 2.0 * (na * nb - dab * dab);
484
485 if denom.is_zero() {
486 // The triangle is degenerate (the three points are collinear).
487 // So we find the longest segment and take its center.
488 let c = self.a - self.b;
489 let nc = c.length_squared();
490
491 if nc >= na && nc >= nb {
492 // Longest segment: [&self.a, &self.b]
493 (
494 self.a.midpoint(self.b),
495 <Real as ComplexField>::sqrt(nc) / 2.0,
496 )
497 } else if na >= nb && na >= nc {
498 // Longest segment: [&self.a, pc]
499 (
500 self.a.midpoint(self.c),
501 <Real as ComplexField>::sqrt(na) / 2.0,
502 )
503 } else {
504 // Longest segment: [&self.b, &self.c]
505 (
506 self.b.midpoint(self.c),
507 <Real as ComplexField>::sqrt(nb) / 2.0,
508 )
509 }
510 } else {
511 let k = b * na - a * nb;
512
513 let center = self.c + (a * k.dot(b) - b * k.dot(a)) / denom;
514 let radius = center.distance(self.a);
515
516 (center, radius)
517 }
518 }
519
520 /// Tests if this triangle is affinely dependent, i.e., its points are almost aligned.
521 #[cfg(feature = "dim3")]
522 pub fn is_affinely_dependent(&self) -> bool {
523 const EPS: Real = crate::math::DEFAULT_EPSILON * 100.0;
524
525 let p1p2 = self.b - self.a;
526 let p1p3 = self.c - self.a;
527 relative_eq!(p1p2.cross(p1p3).length_squared(), 0.0, epsilon = EPS * EPS)
528
529 // relative_eq!(
530 // self.area(),
531 // 0.0,
532 // epsilon = EPS * self.perimeter()
533 // )
534 }
535
536 /// Is this triangle degenerate or almost degenerate?
537 #[cfg(feature = "dim3")]
538 pub fn is_affinely_dependent_eps(&self, eps: Real) -> bool {
539 let p1p2 = self.b - self.a;
540 let p1p3 = self.c - self.a;
541 relative_eq!(
542 p1p2.cross(p1p3).length(),
543 0.0,
544 epsilon = eps * p1p2.length().max(p1p3.length())
545 )
546
547 // relative_eq!(
548 // self.area(),
549 // 0.0,
550 // epsilon = EPS * self.perimeter()
551 // )
552 }
553
554 /// Tests if a point is inside of this triangle.
555 #[cfg(feature = "dim2")]
556 pub fn contains_point(&self, p: Vector) -> bool {
557 let ab = self.b - self.a;
558 let bc = self.c - self.b;
559 let ca = self.a - self.c;
560 let sgn1 = ab.perp_dot(p - self.a);
561 let sgn2 = bc.perp_dot(p - self.b);
562 let sgn3 = ca.perp_dot(p - self.c);
563 sgn1.signum() * sgn2.signum() >= 0.0
564 && sgn1.signum() * sgn3.signum() >= 0.0
565 && sgn2.signum() * sgn3.signum() >= 0.0
566 }
567
568 /// Tests if a point is inside of this triangle.
569 #[cfg(feature = "dim3")]
570 pub fn contains_point(&self, p: Vector) -> bool {
571 const EPS: Real = crate::math::DEFAULT_EPSILON;
572
573 let vb = self.b - self.a;
574 let vc = self.c - self.a;
575 let vp = p - self.a;
576
577 let n = vc.cross(vb);
578 let n_norm = n.length_squared();
579 if n_norm < EPS || vp.dot(n).abs() > EPS * n_norm {
580 // the triangle is degenerate or the
581 // point does not lie on the same plane as the triangle.
582 return false;
583 }
584
585 // We are seeking B, C such that vp = vb * B + vc * C .
586 // If B and C are both in [0, 1] and B + C <= 1 then p is in the triangle.
587 //
588 // We can project this equation along a vector nb coplanar to the triangle
589 // and perpendicular to vb:
590 // vp.dot(nb) = vb.dot(nb) * B + vc.dot(nb) * C
591 // => C = vp.dot(nb) / vc.dot(nb)
592 // and similarly for B.
593 //
594 // In order to avoid divisions and sqrts we scale both B and C - so
595 // b = vb.dot(nc) * B and c = vc.dot(nb) * C - this results in harder-to-follow math but
596 // hopefully fast code.
597
598 let nb = vb.cross(n);
599 let nc = vc.cross(n);
600
601 let signed_blim = vb.dot(nc);
602 let b = vp.dot(nc) * signed_blim.signum();
603 let blim = signed_blim.abs();
604
605 let signed_clim = vc.dot(nb);
606 let c = vp.dot(nb) * signed_clim.signum();
607 let clim = signed_clim.abs();
608
609 c >= 0.0 && c <= clim && b >= 0.0 && b <= blim && c * blim + b * clim <= blim * clim
610 }
611
612 /// The normal of the given feature of this shape.
613 #[cfg(feature = "dim3")]
614 pub fn feature_normal(&self, _: FeatureId) -> Option<Vector> {
615 self.normal()
616 }
617
618 /// The orientation of the triangle, based on its signed area.
619 ///
620 /// Returns `TriangleOrientation::Degenerate` if the triangle’s area is
621 /// smaller than `epsilon`.
622 #[cfg(feature = "dim2")]
623 pub fn orientation(&self, epsilon: Real) -> TriangleOrientation {
624 let area2 = (self.b - self.a).perp_dot(self.c - self.a);
625 // println!("area2: {}", area2);
626 if area2 > epsilon {
627 TriangleOrientation::CounterClockwise
628 } else if area2 < -epsilon {
629 TriangleOrientation::Clockwise
630 } else {
631 TriangleOrientation::Degenerate
632 }
633 }
634
635 /// The orientation of the 2D triangle, based on its signed area.
636 ///
637 /// Returns `TriangleOrientation::Degenerate` if the triangle's area is
638 /// smaller than `epsilon`.
639 pub fn orientation2d(
640 a: crate::math::Vector2,
641 b: crate::math::Vector2,
642 c: crate::math::Vector2,
643 epsilon: Real,
644 ) -> TriangleOrientation {
645 let area2 = (b - a).perp_dot(c - a);
646 // println!("area2: {}", area2);
647 if area2 > epsilon {
648 TriangleOrientation::CounterClockwise
649 } else if area2 < -epsilon {
650 TriangleOrientation::Clockwise
651 } else {
652 TriangleOrientation::Degenerate
653 }
654 }
655
656 /// Find the index of a vertex in this triangle, such that the two
657 /// edges incident in that vertex form the angle closest to 90
658 /// degrees in the triangle.
659 pub fn angle_closest_to_90(&self) -> usize {
660 let points = self.vertices();
661 let mut best_cos = 2.0;
662 let mut selected_i = 0;
663
664 for i in 0..3 {
665 let d1 = (points[i] - points[(i + 1) % 3]).normalize();
666 let d2 = (points[(i + 2) % 3] - points[(i + 1) % 3]).normalize();
667
668 let cos_abs = d1.dot(d2).abs();
669
670 if cos_abs < best_cos {
671 best_cos = cos_abs;
672 selected_i = i;
673 }
674 }
675
676 selected_i
677 }
678
679 /// Reverse the orientation of this triangle by swapping b and c.
680 pub fn reverse(&mut self) {
681 mem::swap(&mut self.b, &mut self.c);
682 }
683}
684
685impl SupportMap for Triangle {
686 #[inline]
687 fn local_support_point(&self, dir: Vector) -> Vector {
688 let d1 = self.a.dot(dir);
689 let d2 = self.b.dot(dir);
690 let d3 = self.c.dot(dir);
691
692 if d1 > d2 {
693 if d1 > d3 {
694 self.a
695 } else {
696 self.c
697 }
698 } else if d2 > d3 {
699 self.b
700 } else {
701 self.c
702 }
703 }
704}
705
706/*
707#[cfg(feature = "dim3")]
708impl ConvexPolyhedron for Triangle {
709 fn vertex(&self, id: FeatureId) -> Vector {
710 match id.unwrap_vertex() {
711 0 => self.a,
712 1 => self.b,
713 2 => self.c,
714 _ => panic!("Triangle vertex index out of bounds."),
715 }
716 }
717 fn edge(&self, id: FeatureId) -> (Vector, Vector, FeatureId, FeatureId) {
718 match id.unwrap_edge() {
719 0 => (self.a, self.b, FeatureId::Vertex(0), FeatureId::Vertex(1)),
720 1 => (self.b, self.c, FeatureId::Vertex(1), FeatureId::Vertex(2)),
721 2 => (self.c, self.a, FeatureId::Vertex(2), FeatureId::Vertex(0)),
722 _ => panic!("Triangle edge index out of bounds."),
723 }
724 }
725
726 fn face(&self, id: FeatureId, face: &mut ConvexPolygonalFeature) {
727 face.clear();
728
729 if let Some(normal) = self.normal() {
730 face.set_feature_id(id);
731
732 match id.unwrap_face() {
733 0 => {
734 face.push(self.a, FeatureId::Vertex(0));
735 face.push(self.b, FeatureId::Vertex(1));
736 face.push(self.c, FeatureId::Vertex(2));
737 face.push_edge_feature_id(FeatureId::Edge(0));
738 face.push_edge_feature_id(FeatureId::Edge(1));
739 face.push_edge_feature_id(FeatureId::Edge(2));
740 face.set_normal(normal);
741 }
742 1 => {
743 face.push(self.a, FeatureId::Vertex(0));
744 face.push(self.c, FeatureId::Vertex(2));
745 face.push(self.b, FeatureId::Vertex(1));
746 face.push_edge_feature_id(FeatureId::Edge(2));
747 face.push_edge_feature_id(FeatureId::Edge(1));
748 face.push_edge_feature_id(FeatureId::Edge(0));
749 face.set_normal(-normal);
750 }
751 _ => unreachable!(),
752 }
753
754 face.recompute_edge_normals();
755 } else {
756 face.push(self.a, FeatureId::Vertex(0));
757 face.set_feature_id(FeatureId::Vertex(0));
758 }
759 }
760
761 fn support_face_toward(
762 &self,
763 m: &Pose,
764 dir: Vector,
765 face: &mut ConvexPolygonalFeature,
766 ) {
767 let normal = self.scaled_normal();
768
769 if normal.dot(*dir) >= 0.0 {
770 ConvexPolyhedron::face(self, FeatureId::Face(0), face);
771 } else {
772 ConvexPolyhedron::face(self, FeatureId::Face(1), face);
773 }
774 face.transform_by(m)
775 }
776
777 fn support_feature_toward(
778 &self,
779 transform: &Pose,
780 dir: Vector,
781 eps: Real,
782 out: &mut ConvexPolygonalFeature,
783 ) {
784 out.clear();
785 let tri = self.transformed(transform);
786 let feature = tri.support_feature_id_toward(dir, eps);
787
788 match feature {
789 FeatureId::Vertex(_) => {
790 let v = tri.vertex(feature);
791 out.push(v, feature);
792 out.set_feature_id(feature);
793 }
794 FeatureId::Edge(_) => {
795 let (a, b, fa, fb) = tri.edge(feature);
796 out.push(a, fa);
797 out.push(b, fb);
798 out.push_edge_feature_id(feature);
799 out.set_feature_id(feature);
800 }
801 FeatureId::Face(_) => tri.face(feature, out),
802 _ => unreachable!(),
803 }
804 }
805
806 fn support_feature_id_toward(&self, local_dir: Vector) -> FeatureId {
807 self.support_feature_id_toward(local_dir, (f64::consts::PI / 180.0) as Real)
808 }
809}
810*/
811
812#[cfg(feature = "dim2")]
813#[cfg(test)]
814mod test {
815 use crate::math::Vector;
816 use crate::shape::Triangle;
817
818 #[test]
819 fn test_triangle_area() {
820 let pa = Vector::new(5.0, 0.0);
821 let pb = Vector::ZERO;
822 let pc = Vector::new(0.0, 4.0);
823
824 assert!(relative_eq!(Triangle::new(pa, pb, pc).area(), 10.0));
825 }
826
827 #[test]
828 fn test_triangle_contains_point() {
829 let tri = Triangle::new(Vector::new(5.0, 0.0), Vector::ZERO, Vector::new(0.0, 4.0));
830
831 assert!(tri.contains_point(Vector::new(1.0, 1.0)));
832 assert!(!tri.contains_point(Vector::new(-1.0, 1.0)));
833 }
834
835 #[test]
836 fn test_obtuse_triangle_contains_point() {
837 let tri = Triangle::new(
838 Vector::new(-10.0, 10.0),
839 Vector::ZERO,
840 Vector::new(20.0, 0.0),
841 );
842
843 assert!(tri.contains_point(Vector::new(-3.0, 5.0)));
844 assert!(tri.contains_point(Vector::new(5.0, 1.0)));
845 assert!(!tri.contains_point(Vector::new(0.0, -1.0)));
846 }
847}
848
849#[cfg(feature = "dim3")]
850#[cfg(test)]
851mod test {
852 use crate::math::{Real, Vector};
853 use crate::shape::Triangle;
854
855 #[test]
856 fn test_triangle_area() {
857 let pa = Vector::new(0.0, 5.0, 0.0);
858 let pb = Vector::ZERO;
859 let pc = Vector::new(0.0, 0.0, 4.0);
860
861 assert!(relative_eq!(Triangle::new(pa, pb, pc).area(), 10.0));
862 }
863
864 #[test]
865 fn test_triangle_contains_point() {
866 let tri = Triangle::new(
867 Vector::new(0.0, 5.0, 0.0),
868 Vector::ZERO,
869 Vector::new(0.0, 0.0, 4.0),
870 );
871
872 assert!(tri.contains_point(Vector::new(0.0, 1.0, 1.0)));
873 assert!(!tri.contains_point(Vector::new(0.0, -1.0, 1.0)));
874 }
875
876 #[test]
877 fn test_obtuse_triangle_contains_point() {
878 let tri = Triangle::new(
879 Vector::new(-10.0, 10.0, 0.0),
880 Vector::ZERO,
881 Vector::new(20.0, 0.0, 0.0),
882 );
883
884 assert!(tri.contains_point(Vector::new(-3.0, 5.0, 0.0)));
885 assert!(tri.contains_point(Vector::new(5.0, 1.0, 0.0)));
886 assert!(!tri.contains_point(Vector::new(0.0, -1.0, 0.0)));
887 }
888
889 #[test]
890 fn test_3dtriangle_contains_point() {
891 let o = Vector::ZERO;
892 let pa = Vector::new(1.2, 1.4, 5.6);
893 let pb = Vector::new(1.5, 6.7, 1.9);
894 let pc = Vector::new(5.0, 2.1, 1.3);
895
896 let tri = Triangle::new(pa, pb, pc);
897
898 let va = pa - o;
899 let vb = pb - o;
900 let vc = pc - o;
901
902 let n = (pa - pb).cross(pb - pc);
903
904 // This is a simple algorithm for generating points that are inside the
905 // triangle: o + (va * alpha + vb * beta + vc * gamma) is always inside the
906 // triangle if:
907 // * each of alpha, beta, gamma is in (0, 1)
908 // * alpha + beta + gamma = 1
909 let contained_p = o + (va * 0.2 + vb * 0.3 + vc * 0.5);
910 let not_contained_coplanar_p = o + (va * -0.5 + vb * 0.8 + vc * 0.7);
911 let not_coplanar_p = o + (va * 0.2 + vb * 0.3 + vc * 0.5) + n * 0.1;
912 let not_coplanar_p2 = o + (va * -0.5 + vb * 0.8 + vc * 0.7) + n * 0.1;
913 assert!(tri.contains_point(contained_p));
914 assert!(!tri.contains_point(not_contained_coplanar_p));
915 assert!(!tri.contains_point(not_coplanar_p));
916 assert!(!tri.contains_point(not_coplanar_p2));
917
918 // Test that points that are clearly within the triangle as seen as such, by testing
919 // a number of points along a line intersecting the triangle.
920 for i in -50i16..150 {
921 let a = 0.15;
922 let b = 0.01 * Real::from(i); // b ranges from -0.5 to 1.5
923 let c = 1.0 - a - b;
924 let p = o + (va * a + vb * b + vc * c);
925
926 match i {
927 ii if ii < 0 || ii > 85 => assert!(
928 !tri.contains_point(p),
929 "Should not contain: i = {}, b = {}",
930 i,
931 b
932 ),
933 ii if ii > 0 && ii < 85 => assert!(
934 tri.contains_point(p),
935 "Should contain: i = {}, b = {}",
936 i,
937 b
938 ),
939 _ => (), // Vectors at the edge may be seen as inside or outside
940 }
941 }
942 }
943}