Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Math primitives cleanup #13020

Merged
merged 12 commits into from
Apr 19, 2024
55 changes: 54 additions & 1 deletion crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
bounding::{Bounded2d, BoundingCircle},
primitives::{
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d,
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
},
Dir3, Mat3, Quat, Vec2, Vec3,
};
Expand Down Expand Up @@ -303,6 +303,59 @@ impl Bounded3d for Torus {
}
}

impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
let [a, b, c] = self.vertices;

let a = rotation * a;
let b = rotation * b;
let c = rotation * c;

let min = a.min(b).min(c);
let max = a.max(b).max(c);

let bounding_center = (max + min) / 2.0 + translation;
let half_extents = (max - min) / 2.0;

Aabb3d::new(bounding_center, half_extents)
}

/// Get the bounding sphere of the triangle.
///
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
if self.is_degenerate() {
let (p1, p2) = self.largest_side();
let (segment, _) = Segment3d::from_points(p1, p2);
return segment.bounding_sphere(translation, rotation);
}

let [a, b, c] = self.vertices;

let side_opposite_to_non_acute = if (b - a).dot(c - a) <= 0.0 {
Some((b, c))
} else if (c - b).dot(a - b) <= 0.0 {
Some((c, a))
} else if (a - c).dot(b - c) <= 0.0 {
Some((a, b))
} else {
None
};

if let Some((p1, p2)) = side_opposite_to_non_acute {
let (segment, _) = Segment3d::from_points(p1, p2);
segment.bounding_sphere(translation, rotation)
} else {
let circumcenter = self.circumcenter();
let radius = circumcenter.distance(a);
BoundingSphere::new(circumcenter + translation, radius)
}
}
}

#[cfg(test)]
mod tests {
use glam::{Quat, Vec3};
Expand Down
13 changes: 11 additions & 2 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,24 @@ impl Ellipse {
}
}

#[inline(always)]
/// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse. The eccentricity can be thought of as a measure of how "stretched" or elongated the ellipse is.
pub fn eccentricity(&self) -> f32 {
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
let a = self.semi_major();
let b = self.semi_minor();

(a * a - b * b).sqrt() / a
}

/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
#[inline(always)]
pub fn semi_major(self) -> f32 {
pub fn semi_major(&self) -> f32 {
self.half_size.max_element()
}

/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.
#[inline(always)]
pub fn semi_minor(self) -> f32 {
pub fn semi_minor(&self) -> f32 {
self.half_size.min_element()
}

Expand Down
93 changes: 34 additions & 59 deletions crates/bevy_math/src/primitives/dim3.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::f32::consts::{FRAC_PI_3, PI};
use std::f32::{
consts::{FRAC_PI_3, PI},
EPSILON,
};

use super::{Circle, Primitive3d};
use crate::{
bounding::{Aabb3d, Bounded3d, BoundingSphere},
Dir3, InvalidDirectionError, Mat3, Quat, Vec2, Vec3,
};
use crate::{Dir3, InvalidDirectionError, Mat3, Vec2, Vec3};

/// A sphere primitive
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -774,7 +774,7 @@ impl Triangle3d {
let [a, b, c] = self.vertices;
let ab = b - a;
let ac = c - a;
ab.cross(ac).length() < 10e-7
ab.cross(ac).length() < EPSILON
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
}

/// Reverse the triangle by swapping the first and last vertices.
Expand Down Expand Up @@ -838,59 +838,6 @@ impl Triangle3d {
}
}

impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
let [a, b, c] = self.vertices;

let a = rotation * a;
let b = rotation * b;
let c = rotation * c;

let min = a.min(b).min(c);
let max = a.max(b).max(c);

let bounding_center = (max + min) / 2.0 + translation;
let half_extents = (max - min) / 2.0;

Aabb3d::new(bounding_center, half_extents)
}

/// Get the bounding sphere of the triangle.
///
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
if self.is_degenerate() {
let (p1, p2) = self.largest_side();
let (segment, _) = Segment3d::from_points(p1, p2);
return segment.bounding_sphere(translation, rotation);
}

let [a, b, c] = self.vertices;

let side_opposite_to_non_acute = if (b - a).dot(c - a) <= 0.0 {
Some((b, c))
} else if (c - b).dot(a - b) <= 0.0 {
Some((c, a))
} else if (a - c).dot(b - c) <= 0.0 {
Some((a, b))
} else {
None
};

if let Some((p1, p2)) = side_opposite_to_non_acute {
let (segment, _) = Segment3d::from_points(p1, p2);
segment.bounding_sphere(translation, rotation)
} else {
let circumcenter = self.circumcenter();
let radius = circumcenter.distance(a);
BoundingSphere::new(circumcenter + translation, radius)
}
}
}

/// A tetrahedron primitive.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -977,6 +924,7 @@ mod tests {

use super::*;
use approx::assert_relative_eq;
use glam::Quat;
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved

#[test]
fn direction_creation() {
Expand Down Expand Up @@ -1174,4 +1122,31 @@ mod tests {
);
assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
}

#[test]
fn triangle_math() {
let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
let triangle = Triangle3d::new(a, b, c);

assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
assert_eq!(triangle.area(), 3.0233467, "incorrect area");
assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
assert_eq!(
triangle.circumcenter(),
Vec3::new(-1., 1.75, 0.75),
"incorrect circumcenter"
);
assert_eq!(
triangle.normal(),
Ok(Dir3::new_unchecked(Vec3::new(
-0.04134491,
-0.4134491,
0.90958804
))),
"incorrect normal"
);

let degenerate = Triangle3d::new(Vec3::NEG_ONE, Vec3::ZERO, Vec3::ONE);
assert!(degenerate.is_degenerate(), "did not find degenerate");
}
}