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}