From 20ee56e719c669a5012a4810e04d1610d4e0149d Mon Sep 17 00:00:00 2001 From: Jakub Marcowski <37378746+Chubercik@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:53:12 +0200 Subject: [PATCH] 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 --- crates/bevy_math/src/primitives/dim3.rs | 118 +++++++++++++++++- .../src/impls/math/primitives3d.rs | 8 ++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 47d83b8b5e..887e31c0d7 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -3,7 +3,7 @@ use std::f32::consts::{FRAC_PI_3, PI}; use super::{Circle, Primitive3d}; use crate::{ bounding::{Aabb3d, Bounded3d, BoundingSphere}, - Dir3, InvalidDirectionError, Quat, Vec3, + Dir3, InvalidDirectionError, Mat3, Quat, Vec3, }; /// 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)] mod tests { // 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.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) + ); + } } diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index e9b025529c..e46f1ef78f 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -97,3 +97,11 @@ impl_reflect!( major_radius: f32, } ); + +impl_reflect!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Tetrahedron { + vertices: [Vec3; 4], + } +);