Skip to main content

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}