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(¢er).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(¢er).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}