Skip to content

Commit cd80b10

Browse files
lynn-lumenJondolfNthTensor
authored
Math primitives cleanup (#13020)
# Objective - General clenup of the primitives in `bevy_math` - Add `eccentricity()` to `Ellipse` ## Solution - Moved `Bounded3d` implementation for `Triangle3d` to the `bounded` module - Added `eccentricity()` to `Ellipse` - `Ellipse::semi_major()` and `::semi_minor()` now accept `&self` instead of `self` - `Triangle3d::is_degenerate()` actually uses `f32::EPSILON` as documented - Added tests for `Triangle3d`-maths --------- Co-authored-by: Joona Aalto <[email protected]> Co-authored-by: Miles Silberling-Cook <[email protected]>
1 parent f68bc01 commit cd80b10

File tree

3 files changed

+106
-61
lines changed

3 files changed

+106
-61
lines changed

crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
bounding::{Bounded2d, BoundingCircle},
55
primitives::{
66
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
7-
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d,
7+
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
88
},
99
Dir3, Mat3, Quat, Vec2, Vec3,
1010
};
@@ -303,6 +303,59 @@ impl Bounded3d for Torus {
303303
}
304304
}
305305

306+
impl Bounded3d for Triangle3d {
307+
/// Get the bounding box of the triangle.
308+
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
309+
let [a, b, c] = self.vertices;
310+
311+
let a = rotation * a;
312+
let b = rotation * b;
313+
let c = rotation * c;
314+
315+
let min = a.min(b).min(c);
316+
let max = a.max(b).max(c);
317+
318+
let bounding_center = (max + min) / 2.0 + translation;
319+
let half_extents = (max - min) / 2.0;
320+
321+
Aabb3d::new(bounding_center, half_extents)
322+
}
323+
324+
/// Get the bounding sphere of the triangle.
325+
///
326+
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
327+
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
328+
/// that contains the largest side of the triangle.
329+
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
330+
if self.is_degenerate() {
331+
let (p1, p2) = self.largest_side();
332+
let (segment, _) = Segment3d::from_points(p1, p2);
333+
return segment.bounding_sphere(translation, rotation);
334+
}
335+
336+
let [a, b, c] = self.vertices;
337+
338+
let side_opposite_to_non_acute = if (b - a).dot(c - a) <= 0.0 {
339+
Some((b, c))
340+
} else if (c - b).dot(a - b) <= 0.0 {
341+
Some((c, a))
342+
} else if (a - c).dot(b - c) <= 0.0 {
343+
Some((a, b))
344+
} else {
345+
None
346+
};
347+
348+
if let Some((p1, p2)) = side_opposite_to_non_acute {
349+
let (segment, _) = Segment3d::from_points(p1, p2);
350+
segment.bounding_sphere(translation, rotation)
351+
} else {
352+
let circumcenter = self.circumcenter();
353+
let radius = circumcenter.distance(a);
354+
BoundingSphere::new(circumcenter + translation, radius)
355+
}
356+
}
357+
}
358+
306359
#[cfg(test)]
307360
mod tests {
308361
use glam::{Quat, Vec3};

crates/bevy_math/src/primitives/dim2.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,27 @@ impl Ellipse {
106106
}
107107
}
108108

109+
#[inline(always)]
110+
/// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse.
111+
/// It can be thought of as a measure of how "stretched" or elongated the ellipse is.
112+
///
113+
/// The value should be in the range [0, 1), where 0 represents a circle, and 1 represents a parabola.
114+
pub fn eccentricity(&self) -> f32 {
115+
let a = self.semi_major();
116+
let b = self.semi_minor();
117+
118+
(a * a - b * b).sqrt() / a
119+
}
120+
109121
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
110122
#[inline(always)]
111-
pub fn semi_major(self) -> f32 {
123+
pub fn semi_major(&self) -> f32 {
112124
self.half_size.max_element()
113125
}
114126

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

@@ -839,6 +851,14 @@ mod tests {
839851
fn ellipse_math() {
840852
let ellipse = Ellipse::new(3.0, 1.0);
841853
assert_eq!(ellipse.area(), 9.424778, "incorrect area");
854+
855+
assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");
856+
857+
let line = Ellipse::new(1., 0.);
858+
assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");
859+
860+
let circle = Ellipse::new(2., 2.);
861+
assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");
842862
}
843863

844864
#[test]

crates/bevy_math/src/primitives/dim3.rs

Lines changed: 30 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
use std::f32::consts::{FRAC_PI_3, PI};
22

33
use super::{Circle, Primitive3d};
4-
use crate::{
5-
bounding::{Aabb3d, Bounded3d, BoundingSphere},
6-
Dir3, InvalidDirectionError, Mat3, Quat, Vec2, Vec3,
7-
};
4+
use crate::{Dir3, InvalidDirectionError, Mat3, Vec2, Vec3};
85

96
/// A sphere primitive
107
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -767,7 +764,7 @@ impl Triangle3d {
767764

768765
/// Checks if the triangle is degenerate, meaning it has zero area.
769766
///
770-
/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `f32::EPSILON`.
767+
/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.
771768
/// This indicates that the three vertices are collinear or nearly collinear.
772769
#[inline(always)]
773770
pub fn is_degenerate(&self) -> bool {
@@ -838,59 +835,6 @@ impl Triangle3d {
838835
}
839836
}
840837

841-
impl Bounded3d for Triangle3d {
842-
/// Get the bounding box of the triangle.
843-
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
844-
let [a, b, c] = self.vertices;
845-
846-
let a = rotation * a;
847-
let b = rotation * b;
848-
let c = rotation * c;
849-
850-
let min = a.min(b).min(c);
851-
let max = a.max(b).max(c);
852-
853-
let bounding_center = (max + min) / 2.0 + translation;
854-
let half_extents = (max - min) / 2.0;
855-
856-
Aabb3d::new(bounding_center, half_extents)
857-
}
858-
859-
/// Get the bounding sphere of the triangle.
860-
///
861-
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
862-
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
863-
/// that contains the largest side of the triangle.
864-
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
865-
if self.is_degenerate() {
866-
let (p1, p2) = self.largest_side();
867-
let (segment, _) = Segment3d::from_points(p1, p2);
868-
return segment.bounding_sphere(translation, rotation);
869-
}
870-
871-
let [a, b, c] = self.vertices;
872-
873-
let side_opposite_to_non_acute = if (b - a).dot(c - a) <= 0.0 {
874-
Some((b, c))
875-
} else if (c - b).dot(a - b) <= 0.0 {
876-
Some((c, a))
877-
} else if (a - c).dot(b - c) <= 0.0 {
878-
Some((a, b))
879-
} else {
880-
None
881-
};
882-
883-
if let Some((p1, p2)) = side_opposite_to_non_acute {
884-
let (segment, _) = Segment3d::from_points(p1, p2);
885-
segment.bounding_sphere(translation, rotation)
886-
} else {
887-
let circumcenter = self.circumcenter();
888-
let radius = circumcenter.distance(a);
889-
BoundingSphere::new(circumcenter + translation, radius)
890-
}
891-
}
892-
}
893-
894838
/// A tetrahedron primitive.
895839
#[derive(Clone, Copy, Debug, PartialEq)]
896840
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
@@ -976,6 +920,7 @@ mod tests {
976920
// Reference values were computed by hand and/or with external tools
977921

978922
use super::*;
923+
use crate::Quat;
979924
use approx::assert_relative_eq;
980925

981926
#[test]
@@ -1174,4 +1119,31 @@ mod tests {
11741119
);
11751120
assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
11761121
}
1122+
1123+
#[test]
1124+
fn triangle_math() {
1125+
let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1126+
let triangle = Triangle3d::new(a, b, c);
1127+
1128+
assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1129+
assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1130+
assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1131+
assert_eq!(
1132+
triangle.circumcenter(),
1133+
Vec3::new(-1., 1.75, 0.75),
1134+
"incorrect circumcenter"
1135+
);
1136+
assert_eq!(
1137+
triangle.normal(),
1138+
Ok(Dir3::new_unchecked(Vec3::new(
1139+
-0.04134491,
1140+
-0.4134491,
1141+
0.90958804
1142+
))),
1143+
"incorrect normal"
1144+
);
1145+
1146+
let degenerate = Triangle3d::new(Vec3::NEG_ONE, Vec3::ZERO, Vec3::ONE);
1147+
assert!(degenerate.is_degenerate(), "did not find degenerate");
1148+
}
11771149
}

0 commit comments

Comments
 (0)