Skip to content

Commit

Permalink
Add triangle_math tests and fix Triangle3d::bounding_sphere bug (#13467)
Browse files Browse the repository at this point in the history
# Objective

Adopted #12659.

Resolved the merge conflicts on #12659;

* I merged the `triangle_tests` added by this PR and by #13020.
* I moved the [commented out
code](#12659 (comment))
from the original PR into a separate test with `#[should_panic]`.

---------

Co-authored-by: Vitor Falcao <vitorfhc@protonmail.com>
Co-authored-by: Ben Harper <ben@tukom.org>
  • Loading branch information
3 people authored May 23, 2024
1 parent 1d950e6 commit bd5148e
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 40 deletions.
94 changes: 73 additions & 21 deletions crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,29 +323,15 @@ impl Bounded3d for Triangle3d {
/// 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() {
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
if self.is_degenerate() || self.is_obtuse() {
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))
let mid_point = (p1 + p2) / 2.0;
let radius = mid_point.distance(p1);
BoundingSphere::new(mid_point + translation, radius)
} else {
None
};
let [a, _, _] = self.vertices;

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)
Expand All @@ -355,13 +341,14 @@ impl Bounded3d for Triangle3d {

#[cfg(test)]
mod tests {
use crate::bounding::BoundingVolume;
use glam::{Quat, Vec3, Vec3A};

use crate::{
bounding::Bounded3d,
primitives::{
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
Segment3d, Sphere, Torus,
Segment3d, Sphere, Torus, Triangle3d,
},
Dir3,
};
Expand Down Expand Up @@ -607,4 +594,69 @@ mod tests {
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}

#[test]
fn triangle3d() {
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);

let br = zero_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::ZERO.into(),
"incorrect bounding box half extents"
);

let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");

let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
let bs = dup_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
bs.center,
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
let br = dup_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
br.center(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);

let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
let bs = collinear_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
let br = collinear_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(1.0, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);
}
}
62 changes: 62 additions & 0 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,54 @@ impl Triangle2d {
(Circle { radius }, center)
}

/// Checks if the triangle is degenerate, meaning it has zero area.
///
/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.
/// This indicates that the three vertices are collinear or nearly collinear.
#[inline(always)]
pub fn is_degenerate(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = (b - a).extend(0.);
let ac = (c - a).extend(0.);
ab.cross(ac).length() < 10e-7
}

/// Checks if the triangle is acute, meaning all angles are less than 90 degrees
#[inline(always)]
pub fn is_acute(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = b - a;
let bc = c - b;
let ca = a - c;

// a^2 + b^2 < c^2 for an acute triangle
let mut side_lengths = [
ab.length_squared(),
bc.length_squared(),
ca.length_squared(),
];
side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
side_lengths[0] + side_lengths[1] > side_lengths[2]
}

/// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees
#[inline(always)]
pub fn is_obtuse(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = b - a;
let bc = c - b;
let ca = a - c;

// a^2 + b^2 > c^2 for an obtuse triangle
let mut side_lengths = [
ab.length_squared(),
bc.length_squared(),
ca.length_squared(),
];
side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
side_lengths[0] + side_lengths[1] < side_lengths[2]
}

/// Reverse the [`WindingOrder`] of the triangle
/// by swapping the first and last vertices.
#[inline(always)]
Expand Down Expand Up @@ -975,6 +1023,20 @@ mod tests {
);
assert_eq!(triangle.area(), 21.0, "incorrect area");
assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");

let degenerate_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));
assert!(degenerate_triangle.is_degenerate());

let acute_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));
let obtuse_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));

assert!(acute_triangle.is_acute());
assert!(!acute_triangle.is_obtuse());
assert!(!obtuse_triangle.is_acute());
assert!(obtuse_triangle.is_obtuse());
}

#[test]
Expand Down
Loading

0 comments on commit bd5148e

Please sign in to comment.