Add Tetrahedron primitive to bevy_math::primitives (#12688)

# Objective

- #10572

There is no 3D primitive available for the common shape of a tetrahedron
(3-simplex).

## Solution

This PR introduces a new type to the existing math primitives:

- `Tetrahedron`: a polyhedron composed of four triangular faces, six
straight edges, and four vertices

---

## Changelog

### Added

- `Tetrahedron` primitive to the `bevy_math` crate
- `Tetrahedron` tests (`area`, `volume` methods)
- `impl_reflect!` declaration for `Tetrahedron` in the `bevy_reflect`
crate
This commit is contained in:
Jakub Marcowski 2024-04-01 23:53:12 +02:00 committed by GitHub
parent aa477028ef
commit 20ee56e719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 1 deletions

View file

@ -3,7 +3,7 @@ use std::f32::consts::{FRAC_PI_3, PI};
use super::{Circle, Primitive3d}; use super::{Circle, Primitive3d};
use crate::{ use crate::{
bounding::{Aabb3d, Bounded3d, BoundingSphere}, bounding::{Aabb3d, Bounded3d, BoundingSphere},
Dir3, InvalidDirectionError, Quat, Vec3, Dir3, InvalidDirectionError, Mat3, Quat, Vec3,
}; };
/// A sphere primitive /// A sphere primitive
@ -823,6 +823,86 @@ impl Bounded3d for Triangle3d {
} }
} }
/// A tetrahedron primitive.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct Tetrahedron {
/// The vertices of the tetrahedron.
pub vertices: [Vec3; 4],
}
impl Primitive3d for Tetrahedron {}
impl Default for Tetrahedron {
/// Returns the default [`Tetrahedron`] with the vertices
/// `[0.0, 0.5, 0.0]`, `[-0.5, -0.5, 0.0]`, `[0.5, -0.5, 0.0]` and `[0.0, 0.0, 0.5]`.
fn default() -> Self {
Self {
vertices: [
Vec3::new(0.0, 0.5, 0.0),
Vec3::new(-0.5, -0.5, 0.0),
Vec3::new(0.5, -0.5, 0.0),
Vec3::new(0.0, 0.0, 0.5),
],
}
}
}
impl Tetrahedron {
/// Create a new [`Tetrahedron`] from points `a`, `b`, `c` and `d`.
#[inline(always)]
pub fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
Self {
vertices: [a, b, c, d],
}
}
/// Get the surface area of the tetrahedron.
#[inline(always)]
pub fn area(&self) -> f32 {
let [a, b, c, d] = self.vertices;
let ab = b - a;
let ac = c - a;
let ad = d - a;
let bc = c - b;
let bd = d - b;
(ab.cross(ac).length()
+ ab.cross(ad).length()
+ ac.cross(ad).length()
+ bc.cross(bd).length())
/ 2.0
}
/// Get the volume of the tetrahedron.
#[inline(always)]
pub fn volume(&self) -> f32 {
self.signed_volume().abs()
}
/// Get the signed volume of the tetrahedron.
///
/// If it's negative, the normal vector of the face defined by
/// the first three points using the right-hand rule points
/// away from the fourth vertex.
#[inline(always)]
pub fn signed_volume(&self) -> f32 {
let [a, b, c, d] = self.vertices;
let ab = b - a;
let ac = c - a;
let ad = d - a;
Mat3::from_cols(ab, ac, ad).determinant() / 6.0
}
/// Get the centroid of the tetrahedron.
///
/// This function finds the geometric center of the tetrahedron
/// by averaging the vertices: `centroid = (a + b + c + d) / 4`.
#[doc(alias("center", "barycenter", "baricenter"))]
#[inline(always)]
pub fn centroid(&self) -> Vec3 {
(self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// Reference values were computed by hand and/or with external tools // Reference values were computed by hand and/or with external tools
@ -985,4 +1065,40 @@ mod tests {
assert_relative_eq!(torus.area(), 33.16187); assert_relative_eq!(torus.area(), 33.16187);
assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001); assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
} }
#[test]
fn tetrahedron_math() {
let tetrahedron = Tetrahedron {
vertices: [
Vec3::new(0.3, 1.0, 1.7),
Vec3::new(-2.0, -1.0, 0.0),
Vec3::new(1.8, 0.5, 1.0),
Vec3::new(-1.0, -2.0, 3.5),
],
};
assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
assert_eq!(
tetrahedron.signed_volume(),
3.2058334,
"incorrect signed volume"
);
assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
assert_eq!(Tetrahedron::default().area(), 1.4659258, "incorrect area");
assert_eq!(
Tetrahedron::default().volume(),
0.083333336,
"incorrect volume"
);
assert_eq!(
Tetrahedron::default().signed_volume(),
0.083333336,
"incorrect signed volume"
);
assert_relative_eq!(
Tetrahedron::default().centroid(),
Vec3::new(0.0, -0.125, 0.125)
);
}
} }

View file

@ -97,3 +97,11 @@ impl_reflect!(
major_radius: f32, major_radius: f32,
} }
); );
impl_reflect!(
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
#[type_path = "bevy_math::primitives"]
struct Tetrahedron {
vertices: [Vec3; 4],
}
);