parry3d/shape/
tetrahedron.rs

1//! Definition of the tetrahedron shape.
2
3use crate::math::{Matrix, Point, Real};
4use crate::shape::{Segment, Triangle};
5use crate::utils;
6use core::mem;
7use na::Matrix3;
8
9#[cfg(all(feature = "dim2", not(feature = "std")))]
10use na::ComplexField; // for .abs()
11
12#[cfg(feature = "rkyv")]
13use rkyv::{bytecheck, CheckBytes};
14
15/// A tetrahedron with 4 vertices.
16///
17/// # What is a Tetrahedron?
18///
19/// A tetrahedron is the simplest 3D polyhedron, consisting of exactly **4 vertices**,
20/// **6 edges**, and **4 triangular faces**. It's the 3D equivalent of a triangle in 2D.
21/// Think of it as a pyramid with a triangular base.
22///
23/// # Structure
24///
25/// A tetrahedron has:
26/// - **4 vertices** labeled `a`, `b`, `c`, and `d`
27/// - **4 triangular faces**: ABC, ABD, ACD, and BCD
28/// - **6 edges**: AB, AC, AD, BC, BD, and CD
29///
30/// # Common Use Cases
31///
32/// Tetrahedra are fundamental building blocks in many applications:
33/// - **Mesh generation**: Tetrahedral meshes are used for finite element analysis (FEA)
34/// - **Collision detection**: Simple convex shape for physics simulations
35/// - **Volume computation**: Computing volumes of complex 3D shapes
36/// - **Spatial partitioning**: Subdividing 3D space for games and simulations
37/// - **Computer graphics**: Rendering and ray tracing acceleration structures
38///
39/// # Examples
40///
41/// ## Creating a Simple Tetrahedron
42///
43/// ```
44/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
45/// use parry3d::shape::Tetrahedron;
46/// use parry3d::math::Point;
47///
48/// // Create a tetrahedron with vertices at unit positions
49/// let tetra = Tetrahedron::new(
50///     Point::new(0.0, 0.0, 0.0),  // vertex a
51///     Point::new(1.0, 0.0, 0.0),  // vertex b
52///     Point::new(0.0, 1.0, 0.0),  // vertex c
53///     Point::new(0.0, 0.0, 1.0),  // vertex d
54/// );
55///
56/// println!("First vertex: {:?}", tetra.a);
57/// # }
58/// ```
59///
60/// ## Computing Volume
61///
62/// ```
63/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
64/// use parry3d::shape::Tetrahedron;
65/// use parry3d::math::Point;
66///
67/// // Create a regular tetrahedron
68/// let tetra = Tetrahedron::new(
69///     Point::new(0.0, 0.0, 0.0),
70///     Point::new(1.0, 0.0, 0.0),
71///     Point::new(0.5, 0.866, 0.0),
72///     Point::new(0.5, 0.433, 0.816),
73/// );
74///
75/// let volume = tetra.volume();
76/// assert!(volume > 0.0);
77/// println!("Volume: {}", volume);
78/// # }
79/// ```
80///
81/// ## Accessing Faces and Edges
82///
83/// ```
84/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
85/// use parry3d::shape::Tetrahedron;
86/// use parry3d::math::Point;
87///
88/// let tetra = Tetrahedron::new(
89///     Point::new(0.0, 0.0, 0.0),
90///     Point::new(1.0, 0.0, 0.0),
91///     Point::new(0.0, 1.0, 0.0),
92///     Point::new(0.0, 0.0, 1.0),
93/// );
94///
95/// // Get the first face (triangle ABC)
96/// let face = tetra.face(0);
97/// println!("First face: {:?}", face);
98///
99/// // Get the first edge (segment AB)
100/// let edge = tetra.edge(0);
101/// println!("First edge: {:?}", edge);
102/// # }
103/// ```
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
106#[cfg_attr(
107    feature = "rkyv",
108    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
109    archive(as = "Self")
110)]
111#[derive(Copy, Clone, Debug)]
112#[repr(C)]
113pub struct Tetrahedron {
114    /// The tetrahedron's first vertex.
115    pub a: Point<Real>,
116    /// The tetrahedron's second vertex.
117    pub b: Point<Real>,
118    /// The tetrahedron's third vertex.
119    pub c: Point<Real>,
120    /// The tetrahedron's fourth vertex.
121    pub d: Point<Real>,
122}
123
124/// Logical description of the location of a point on a tetrahedron.
125///
126/// This enum describes where a point is located relative to a tetrahedron's features
127/// (vertices, edges, faces, or interior). It's commonly used in collision detection
128/// and geometric queries to understand spatial relationships.
129///
130/// # Variants
131///
132/// - `OnVertex`: Point is at one of the four vertices (0=a, 1=b, 2=c, 3=d)
133/// - `OnEdge`: Point lies on one of the six edges with barycentric coordinates
134/// - `OnFace`: Point lies on one of the four triangular faces with barycentric coordinates
135/// - `OnSolid`: Point is inside the tetrahedron volume
136///
137/// # Examples
138///
139/// ```
140/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
141/// use parry3d::shape::{Tetrahedron, TetrahedronPointLocation};
142/// use parry3d::math::Point;
143///
144/// let tetra = Tetrahedron::new(
145///     Point::new(0.0, 0.0, 0.0),
146///     Point::new(1.0, 0.0, 0.0),
147///     Point::new(0.0, 1.0, 0.0),
148///     Point::new(0.0, 0.0, 1.0),
149/// );
150///
151/// // Check location of vertex
152/// let location = TetrahedronPointLocation::OnVertex(0);
153/// if let Some(bcoords) = location.barycentric_coordinates() {
154///     println!("Barycentric coordinates: {:?}", bcoords);
155///     // For vertex 0, this will be [1.0, 0.0, 0.0, 0.0]
156/// }
157/// # }
158/// ```
159#[derive(Copy, Clone, Debug)]
160pub enum TetrahedronPointLocation {
161    /// The point lies on a vertex.
162    ///
163    /// The vertex index maps to: 0=a, 1=b, 2=c, 3=d
164    OnVertex(u32),
165    /// The point lies on an edge.
166    ///
167    /// Contains the edge index and barycentric coordinates `[u, v]` where:
168    /// - `u` is the weight of the first vertex
169    /// - `v` is the weight of the second vertex
170    /// - `u + v = 1.0`
171    ///
172    /// Edge indices:
173    /// - 0: segment AB
174    /// - 1: segment AC
175    /// - 2: segment AD
176    /// - 3: segment BC
177    /// - 4: segment BD
178    /// - 5: segment CD
179    OnEdge(u32, [Real; 2]),
180    /// The point lies on a triangular face interior.
181    ///
182    /// Contains the face index and barycentric coordinates `[u, v, w]` where:
183    /// - `u`, `v`, `w` are the weights of the three vertices
184    /// - `u + v + w = 1.0`
185    ///
186    /// Face indices:
187    /// - 0: triangle ABC
188    /// - 1: triangle ABD
189    /// - 2: triangle ACD
190    /// - 3: triangle BCD
191    OnFace(u32, [Real; 3]),
192    /// The point lies inside of the tetrahedron.
193    OnSolid,
194}
195
196impl TetrahedronPointLocation {
197    /// The barycentric coordinates corresponding to this point location.
198    ///
199    /// Barycentric coordinates represent a point as a weighted combination of the
200    /// tetrahedron's four vertices. The returned array `[wa, wb, wc, wd]` contains
201    /// weights such that: `point = wa*a + wb*b + wc*c + wd*d` where `wa + wb + wc + wd = 1.0`.
202    ///
203    /// # Returns
204    ///
205    /// - `Some([wa, wb, wc, wd])`: The barycentric coordinates for points on features
206    /// - `None`: If the location is `OnSolid` (point is in the interior)
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
212    /// use parry3d::shape::TetrahedronPointLocation;
213    ///
214    /// // A point on vertex 0 (vertex a)
215    /// let location = TetrahedronPointLocation::OnVertex(0);
216    /// let bcoords = location.barycentric_coordinates().unwrap();
217    /// assert_eq!(bcoords, [1.0, 0.0, 0.0, 0.0]);
218    ///
219    /// // A point on edge 0 (segment AB) at midpoint
220    /// let location = TetrahedronPointLocation::OnEdge(0, [0.5, 0.5]);
221    /// let bcoords = location.barycentric_coordinates().unwrap();
222    /// assert_eq!(bcoords, [0.5, 0.5, 0.0, 0.0]);
223    ///
224    /// // A point inside the tetrahedron
225    /// let location = TetrahedronPointLocation::OnSolid;
226    /// assert!(location.barycentric_coordinates().is_none());
227    /// # }
228    /// ```
229    pub fn barycentric_coordinates(&self) -> Option<[Real; 4]> {
230        let mut bcoords = [0.0; 4];
231
232        match self {
233            TetrahedronPointLocation::OnVertex(i) => bcoords[*i as usize] = 1.0,
234            TetrahedronPointLocation::OnEdge(i, uv) => {
235                let idx = Tetrahedron::edge_ids(*i);
236                bcoords[idx.0 as usize] = uv[0];
237                bcoords[idx.1 as usize] = uv[1];
238            }
239            TetrahedronPointLocation::OnFace(i, uvw) => {
240                let idx = Tetrahedron::face_ids(*i);
241                bcoords[idx.0 as usize] = uvw[0];
242                bcoords[idx.1 as usize] = uvw[1];
243                bcoords[idx.2 as usize] = uvw[2];
244            }
245            TetrahedronPointLocation::OnSolid => {
246                return None;
247            }
248        }
249
250        Some(bcoords)
251    }
252
253    /// Returns `true` if both `self` and `other` correspond to points on the same feature of a tetrahedron.
254    ///
255    /// Two point locations are considered to be on the same feature if they're both:
256    /// - On the same vertex (same vertex index)
257    /// - On the same edge (same edge index)
258    /// - On the same face (same face index)
259    /// - Both inside the tetrahedron (`OnSolid`)
260    ///
261    /// This is useful for determining if two points share a common geometric feature,
262    /// which is important in collision detection and contact point generation.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
268    /// use parry3d::shape::TetrahedronPointLocation;
269    ///
270    /// let loc1 = TetrahedronPointLocation::OnVertex(0);
271    /// let loc2 = TetrahedronPointLocation::OnVertex(0);
272    /// let loc3 = TetrahedronPointLocation::OnVertex(1);
273    ///
274    /// assert!(loc1.same_feature_as(&loc2));  // Same vertex
275    /// assert!(!loc1.same_feature_as(&loc3)); // Different vertices
276    ///
277    /// let edge1 = TetrahedronPointLocation::OnEdge(0, [0.5, 0.5]);
278    /// let edge2 = TetrahedronPointLocation::OnEdge(0, [0.3, 0.7]);
279    /// let edge3 = TetrahedronPointLocation::OnEdge(1, [0.5, 0.5]);
280    ///
281    /// assert!(edge1.same_feature_as(&edge2));  // Same edge, different coords
282    /// assert!(!edge1.same_feature_as(&edge3)); // Different edges
283    /// # }
284    /// ```
285    pub fn same_feature_as(&self, other: &TetrahedronPointLocation) -> bool {
286        match (*self, *other) {
287            (TetrahedronPointLocation::OnVertex(i), TetrahedronPointLocation::OnVertex(j)) => {
288                i == j
289            }
290            (TetrahedronPointLocation::OnEdge(i, _), TetrahedronPointLocation::OnEdge(j, _)) => {
291                i == j
292            }
293            (TetrahedronPointLocation::OnFace(i, _), TetrahedronPointLocation::OnFace(j, _)) => {
294                i == j
295            }
296            (TetrahedronPointLocation::OnSolid, TetrahedronPointLocation::OnSolid) => true,
297            _ => false,
298        }
299    }
300}
301
302impl Tetrahedron {
303    /// Creates a tetrahedron from four points.
304    ///
305    /// The vertices can be specified in any order, but the order affects the
306    /// orientation and signed volume. For a tetrahedron with positive volume,
307    /// vertex `d` should be on the positive side of the plane defined by the
308    /// counter-clockwise triangle `(a, b, c)`.
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
314    /// use parry3d::shape::Tetrahedron;
315    /// use parry3d::math::Point;
316    ///
317    /// // Create a simple tetrahedron
318    /// let tetra = Tetrahedron::new(
319    ///     Point::new(0.0, 0.0, 0.0),
320    ///     Point::new(1.0, 0.0, 0.0),
321    ///     Point::new(0.0, 1.0, 0.0),
322    ///     Point::new(0.0, 0.0, 1.0),
323    /// );
324    ///
325    /// assert!(tetra.volume() > 0.0);
326    /// # }
327    /// ```
328    #[inline]
329    pub fn new(a: Point<Real>, b: Point<Real>, c: Point<Real>, d: Point<Real>) -> Tetrahedron {
330        Tetrahedron { a, b, c, d }
331    }
332
333    /// Creates the reference to a tetrahedron from the reference to an array of four points.
334    ///
335    /// This is a zero-cost conversion that reinterprets the array as a tetrahedron.
336    /// The array elements correspond to vertices `[a, b, c, d]`.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
342    /// use parry3d::shape::Tetrahedron;
343    /// use parry3d::math::Point;
344    ///
345    /// let points = [
346    ///     Point::new(0.0, 0.0, 0.0),
347    ///     Point::new(1.0, 0.0, 0.0),
348    ///     Point::new(0.0, 1.0, 0.0),
349    ///     Point::new(0.0, 0.0, 1.0),
350    /// ];
351    ///
352    /// let tetra = Tetrahedron::from_array(&points);
353    /// assert_eq!(tetra.a, points[0]);
354    /// assert_eq!(tetra.b, points[1]);
355    /// # }
356    /// ```
357    pub fn from_array(arr: &[Point<Real>; 4]) -> &Tetrahedron {
358        unsafe { mem::transmute(arr) }
359    }
360
361    /// Returns the i-th face of this tetrahedron.
362    ///
363    /// A tetrahedron has 4 triangular faces indexed from 0 to 3:
364    /// - Face 0: triangle ABC
365    /// - Face 1: triangle ABD
366    /// - Face 2: triangle ACD
367    /// - Face 3: triangle BCD
368    ///
369    /// # Panics
370    ///
371    /// Panics if `i >= 4`.
372    ///
373    /// # Examples
374    ///
375    /// ```
376    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
377    /// use parry3d::shape::Tetrahedron;
378    /// use parry3d::math::Point;
379    ///
380    /// let tetra = Tetrahedron::new(
381    ///     Point::new(0.0, 0.0, 0.0),
382    ///     Point::new(1.0, 0.0, 0.0),
383    ///     Point::new(0.0, 1.0, 0.0),
384    ///     Point::new(0.0, 0.0, 1.0),
385    /// );
386    ///
387    /// // Get the first face (triangle ABC)
388    /// let face = tetra.face(0);
389    /// assert_eq!(face.a, tetra.a);
390    /// assert_eq!(face.b, tetra.b);
391    /// assert_eq!(face.c, tetra.c);
392    ///
393    /// // Get the last face (triangle BCD)
394    /// let face = tetra.face(3);
395    /// assert_eq!(face.a, tetra.b);
396    /// assert_eq!(face.b, tetra.c);
397    /// assert_eq!(face.c, tetra.d);
398    /// # }
399    /// ```
400    pub fn face(&self, i: usize) -> Triangle {
401        match i {
402            0 => Triangle::new(self.a, self.b, self.c),
403            1 => Triangle::new(self.a, self.b, self.d),
404            2 => Triangle::new(self.a, self.c, self.d),
405            3 => Triangle::new(self.b, self.c, self.d),
406            _ => panic!("Tetrahedron face index out of bounds (must be < 4."),
407        }
408    }
409
410    /// Returns the indices of the vertices of the i-th face of this tetrahedron.
411    ///
412    /// Returns a tuple `(v1, v2, v3)` where each value is a vertex index (0=a, 1=b, 2=c, 3=d).
413    ///
414    /// Face to vertex index mapping:
415    /// - Face 0: (0, 1, 2) = triangle ABC
416    /// - Face 1: (0, 1, 3) = triangle ABD
417    /// - Face 2: (0, 2, 3) = triangle ACD
418    /// - Face 3: (1, 2, 3) = triangle BCD
419    ///
420    /// # Panics
421    ///
422    /// Panics if `i >= 4`.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
428    /// use parry3d::shape::Tetrahedron;
429    ///
430    /// // Get indices for face 0 (triangle ABC)
431    /// let (v1, v2, v3) = Tetrahedron::face_ids(0);
432    /// assert_eq!((v1, v2, v3), (0, 1, 2));  // vertices a, b, c
433    ///
434    /// // Get indices for face 3 (triangle BCD)
435    /// let (v1, v2, v3) = Tetrahedron::face_ids(3);
436    /// assert_eq!((v1, v2, v3), (1, 2, 3));  // vertices b, c, d
437    /// # }
438    /// ```
439    pub fn face_ids(i: u32) -> (u32, u32, u32) {
440        match i {
441            0 => (0, 1, 2),
442            1 => (0, 1, 3),
443            2 => (0, 2, 3),
444            3 => (1, 2, 3),
445            _ => panic!("Tetrahedron face index out of bounds (must be < 4."),
446        }
447    }
448
449    /// Returns the i-th edge of this tetrahedron.
450    ///
451    /// A tetrahedron has 6 edges indexed from 0 to 5:
452    /// - Edge 0: segment AB
453    /// - Edge 1: segment AC
454    /// - Edge 2: segment AD
455    /// - Edge 3: segment BC
456    /// - Edge 4: segment BD
457    /// - Edge 5: segment CD
458    ///
459    /// # Panics
460    ///
461    /// Panics if `i >= 6`.
462    ///
463    /// # Examples
464    ///
465    /// ```
466    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
467    /// use parry3d::shape::Tetrahedron;
468    /// use parry3d::math::Point;
469    ///
470    /// let tetra = Tetrahedron::new(
471    ///     Point::new(0.0, 0.0, 0.0),
472    ///     Point::new(1.0, 0.0, 0.0),
473    ///     Point::new(0.0, 1.0, 0.0),
474    ///     Point::new(0.0, 0.0, 1.0),
475    /// );
476    ///
477    /// // Get edge 0 (segment AB)
478    /// let edge = tetra.edge(0);
479    /// assert_eq!(edge.a, tetra.a);
480    /// assert_eq!(edge.b, tetra.b);
481    ///
482    /// // Get edge 5 (segment CD)
483    /// let edge = tetra.edge(5);
484    /// assert_eq!(edge.a, tetra.c);
485    /// assert_eq!(edge.b, tetra.d);
486    /// # }
487    /// ```
488    pub fn edge(&self, i: u32) -> Segment {
489        match i {
490            0 => Segment::new(self.a, self.b),
491            1 => Segment::new(self.a, self.c),
492            2 => Segment::new(self.a, self.d),
493            3 => Segment::new(self.b, self.c),
494            4 => Segment::new(self.b, self.d),
495            5 => Segment::new(self.c, self.d),
496            _ => panic!("Tetrahedron edge index out of bounds (must be < 6)."),
497        }
498    }
499
500    /// Returns the indices of the vertices of the i-th edge of this tetrahedron.
501    ///
502    /// Returns a tuple `(v1, v2)` where each value is a vertex index (0=a, 1=b, 2=c, 3=d).
503    ///
504    /// Edge to vertex index mapping:
505    /// - Edge 0: (0, 1) = segment AB
506    /// - Edge 1: (0, 2) = segment AC
507    /// - Edge 2: (0, 3) = segment AD
508    /// - Edge 3: (1, 2) = segment BC
509    /// - Edge 4: (1, 3) = segment BD
510    /// - Edge 5: (2, 3) = segment CD
511    ///
512    /// # Panics
513    ///
514    /// Panics if `i >= 6`.
515    ///
516    /// # Examples
517    ///
518    /// ```
519    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
520    /// use parry3d::shape::Tetrahedron;
521    ///
522    /// // Get indices for edge 0 (segment AB)
523    /// let (v1, v2) = Tetrahedron::edge_ids(0);
524    /// assert_eq!((v1, v2), (0, 1));  // vertices a, b
525    ///
526    /// // Get indices for edge 5 (segment CD)
527    /// let (v1, v2) = Tetrahedron::edge_ids(5);
528    /// assert_eq!((v1, v2), (2, 3));  // vertices c, d
529    /// # }
530    /// ```
531    pub fn edge_ids(i: u32) -> (u32, u32) {
532        match i {
533            0 => (0, 1),
534            1 => (0, 2),
535            2 => (0, 3),
536            3 => (1, 2),
537            4 => (1, 3),
538            5 => (2, 3),
539            _ => panic!("Tetrahedron edge index out of bounds (must be < 6)."),
540        }
541    }
542
543    /// Computes the barycentric coordinates of the given point in the coordinate system of this tetrahedron.
544    ///
545    /// Barycentric coordinates express a point as a weighted combination of the tetrahedron's
546    /// vertices. For point `p`, the returned array `[wa, wb, wc, wd]` satisfies:
547    /// `p = wa*a + wb*b + wc*c + wd*d` where `wa + wb + wc + wd = 1.0`.
548    ///
549    /// These coordinates are useful for:
550    /// - Interpolating values defined at vertices
551    /// - Determining if a point is inside the tetrahedron (all weights are non-negative)
552    /// - Computing distances and projections
553    ///
554    /// # Returns
555    ///
556    /// - `Some([wa, wb, wc, wd])`: The barycentric coordinates
557    /// - `None`: If the tetrahedron is degenerate (zero volume, coplanar vertices)
558    ///
559    /// # Examples
560    ///
561    /// ```
562    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
563    /// use parry3d::shape::Tetrahedron;
564    /// use parry3d::math::Point;
565    ///
566    /// let tetra = Tetrahedron::new(
567    ///     Point::new(0.0, 0.0, 0.0),
568    ///     Point::new(1.0, 0.0, 0.0),
569    ///     Point::new(0.0, 1.0, 0.0),
570    ///     Point::new(0.0, 0.0, 1.0),
571    /// );
572    ///
573    /// // Point at vertex a
574    /// let bcoords = tetra.barycentric_coordinates(&tetra.a).unwrap();
575    /// assert!((bcoords[0] - 1.0).abs() < 1e-6);
576    /// assert!(bcoords[1].abs() < 1e-6);
577    ///
578    /// // Point at center
579    /// let center = tetra.center();
580    /// let bcoords = tetra.barycentric_coordinates(&center).unwrap();
581    /// // All coordinates should be approximately 0.25
582    /// for coord in &bcoords {
583    ///     assert!((coord - 0.25).abs() < 1e-6);
584    /// }
585    /// # }
586    /// ```
587    pub fn barycentric_coordinates(&self, p: &Point<Real>) -> Option<[Real; 4]> {
588        let ab = self.b - self.a;
589        let ac = self.c - self.a;
590        let ad = self.d - self.a;
591        let m = Matrix::new(ab.x, ac.x, ad.x, ab.y, ac.y, ad.y, ab.z, ac.z, ad.z);
592
593        m.try_inverse().map(|im| {
594            let bcoords = im * (p - self.a);
595            [
596                1.0 - bcoords.x - bcoords.y - bcoords.z,
597                bcoords.x,
598                bcoords.y,
599                bcoords.z,
600            ]
601        })
602    }
603
604    /// Computes the volume of this tetrahedron.
605    ///
606    /// The volume is always non-negative, regardless of vertex ordering.
607    /// For the signed volume (which can be negative), use [`signed_volume`](Self::signed_volume).
608    ///
609    /// # Formula
610    ///
611    /// Volume = |det(b-a, c-a, d-a)| / 6
612    ///
613    /// # Examples
614    ///
615    /// ```
616    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
617    /// use parry3d::shape::Tetrahedron;
618    /// use parry3d::math::Point;
619    ///
620    /// let tetra = Tetrahedron::new(
621    ///     Point::new(0.0, 0.0, 0.0),
622    ///     Point::new(1.0, 0.0, 0.0),
623    ///     Point::new(0.0, 1.0, 0.0),
624    ///     Point::new(0.0, 0.0, 1.0),
625    /// );
626    ///
627    /// let volume = tetra.volume();
628    /// assert!((volume - 1.0/6.0).abs() < 1e-6);
629    /// # }
630    /// ```
631    #[inline]
632    pub fn volume(&self) -> Real {
633        self.signed_volume().abs()
634    }
635
636    /// Computes the signed volume of this tetrahedron.
637    ///
638    /// The sign of the volume depends on the vertex ordering:
639    /// - **Positive**: Vertex `d` is on the positive side of the plane defined by
640    ///   the counter-clockwise triangle `(a, b, c)`
641    /// - **Negative**: Vertex `d` is on the negative side (opposite orientation)
642    /// - **Zero**: The tetrahedron is degenerate (all vertices are coplanar)
643    ///
644    /// # Formula
645    ///
646    /// Signed Volume = det(b-a, c-a, d-a) / 6
647    ///
648    /// # Examples
649    ///
650    /// ```
651    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
652    /// use parry3d::shape::Tetrahedron;
653    /// use parry3d::math::Point;
654    ///
655    /// let tetra = Tetrahedron::new(
656    ///     Point::new(0.0, 0.0, 0.0),
657    ///     Point::new(1.0, 0.0, 0.0),
658    ///     Point::new(0.0, 1.0, 0.0),
659    ///     Point::new(0.0, 0.0, 1.0),
660    /// );
661    ///
662    /// let signed_vol = tetra.signed_volume();
663    /// assert!(signed_vol > 0.0);  // Positive orientation
664    ///
665    /// // Swap two vertices to flip orientation
666    /// let tetra_flipped = Tetrahedron::new(
667    ///     tetra.a, tetra.c, tetra.b, tetra.d
668    /// );
669    /// let signed_vol_flipped = tetra_flipped.signed_volume();
670    /// assert!(signed_vol_flipped < 0.0);  // Negative orientation
671    /// assert!((signed_vol + signed_vol_flipped).abs() < 1e-6);  // Same magnitude
672    /// # }
673    /// ```
674    #[inline]
675    pub fn signed_volume(&self) -> Real {
676        let p1p2 = self.b - self.a;
677        let p1p3 = self.c - self.a;
678        let p1p4 = self.d - self.a;
679
680        let mat = Matrix3::new(
681            p1p2[0], p1p3[0], p1p4[0], p1p2[1], p1p3[1], p1p4[1], p1p2[2], p1p3[2], p1p4[2],
682        );
683
684        mat.determinant() / na::convert::<f64, Real>(6.0f64)
685    }
686
687    /// Computes the center of this tetrahedron.
688    ///
689    /// The center (also called centroid or barycenter) is the average of all four vertices.
690    /// It's the point where all barycentric coordinates are equal to 0.25.
691    ///
692    /// # Formula
693    ///
694    /// Center = (a + b + c + d) / 4
695    ///
696    /// # Examples
697    ///
698    /// ```
699    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
700    /// use parry3d::shape::Tetrahedron;
701    /// use parry3d::math::Point;
702    ///
703    /// let tetra = Tetrahedron::new(
704    ///     Point::new(0.0, 0.0, 0.0),
705    ///     Point::new(1.0, 0.0, 0.0),
706    ///     Point::new(0.0, 1.0, 0.0),
707    ///     Point::new(0.0, 0.0, 1.0),
708    /// );
709    ///
710    /// let center = tetra.center();
711    /// assert!((center.x - 0.25).abs() < 1e-6);
712    /// assert!((center.y - 0.25).abs() < 1e-6);
713    /// assert!((center.z - 0.25).abs() < 1e-6);
714    ///
715    /// // The center has equal barycentric coordinates
716    /// let bcoords = tetra.barycentric_coordinates(&center).unwrap();
717    /// for coord in &bcoords {
718    ///     assert!((coord - 0.25).abs() < 1e-6);
719    /// }
720    /// # }
721    /// ```
722    #[inline]
723    pub fn center(&self) -> Point<Real> {
724        utils::center(&[self.a, self.b, self.c, self.d])
725    }
726}