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}