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