parry3d/shape/
tetrahedron.rs

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