From 0179aea35ee0b7663852857c12e0c205ffeb15c1 Mon Sep 17 00:00:00 2001 From: Vitor Falcao Date: Fri, 22 Mar 2024 17:24:43 -0300 Subject: [PATCH 1/7] Add triangle_math tests and fix Triangle3d::bounding_sphere bug --- .../src/bounding/bounded3d/primitive_impls.rs | 70 ++++++++++- crates/bevy_math/src/primitives/dim3.rs | 119 +++++++++++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 4c82b0f387321..6145b74fe1704 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -326,8 +326,9 @@ impl Bounded3d for Triangle3d { fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere { if self.is_degenerate() { let (p1, p2) = self.largest_side(); + let mid_point = (p1 + p2) / 2.0; let (segment, _) = Segment3d::from_points(p1, p2); - return segment.bounding_sphere(translation, rotation); + return segment.bounding_sphere(mid_point, rotation); } let [a, b, c] = self.vertices; @@ -343,8 +344,9 @@ impl Bounded3d for Triangle3d { }; if let Some((p1, p2)) = side_opposite_to_non_acute { - let (segment, _) = Segment3d::from_points(p1, p2); - segment.bounding_sphere(translation, rotation) + let mid_point = (p1 + p2) / 2.0; + let radius = mid_point.distance(p1); + BoundingSphere::new(mid_point + translation, radius) } else { let circumcenter = self.circumcenter(); let radius = circumcenter.distance(a); @@ -355,13 +357,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, }; @@ -607,4 +610,63 @@ mod tests { assert_eq!(bounding_sphere.center, translation.into()); assert_eq!(bounding_sphere.radius(), 1.5); } + + fn triangle3d() { + // let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY); // DIVISION BY ZERO + // assert_eq!(bs.center, Vec3::ZERO, "incorrect bounding sphere center"); + // assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius"); + + 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 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 common_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); + let bs = common_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 = common_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" + ); + } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 1c2e217ede3f3..8c6fbf4d4d2ff 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1010,7 +1010,7 @@ mod tests { // Reference values were computed by hand and/or with external tools use super::*; - use crate::Quat; + use crate::{InvalidDirectionError, Quat}; use approx::assert_relative_eq; #[test] @@ -1211,7 +1211,7 @@ mod tests { } #[test] - fn triangle_math() { + fn complex_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); @@ -1254,4 +1254,119 @@ mod tests { assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area"); assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume"); } + + #[test] + fn triangle_math() { + // Default triangle tests + let mut default_triangle = Triangle3d::default(); + let reverse_default_triangle = Triangle3d::new( + Vec3::new(0.5, -0.5, 0.0), + Vec3::new(-0.5, -0.5, 0.0), + Vec3::new(0.0, 0.5, 0.0), + ); + assert_eq!(default_triangle.area(), 0.5, "incorrect area"); + assert_relative_eq!( + default_triangle.perimeter(), + 1.0 + 2.0 * 1.25_f32.sqrt(), + epsilon = 10e-9 + ); + assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal"); + assert_eq!( + default_triangle.is_degenerate(), + false, + "incorrect degenerate check" + ); + assert_eq!( + default_triangle.centroid(), + Vec3::new(0.0, -0.16666667, 0.0), + "incorrect centroid" + ); + assert_eq!( + default_triangle.largest_side(), + (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0)) + ); + default_triangle.reverse(); + assert_eq!( + default_triangle, reverse_default_triangle, + "incorrect reverse" + ); + assert_eq!( + default_triangle.circumcenter(), + Vec3::new(0.0, -0.125, 0.0), + "incorrect circumcenter" + ); + + // Custom triangle tests + let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y); + let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0)); + let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0)); + + assert_eq!( + right_triangle.circumcenter(), + Vec3::new(0.5, 0.5, 0.0), + "incorrect circumcenter" + ); + assert_eq!( + obtuse_triangle.circumcenter(), + Vec3::new(0.0, -4.95, 0.0), + "incorrect circumcenter" + ); + assert_eq!( + acute_triangle.circumcenter(), + Vec3::new(0.5, 2.475, 0.0), + "incorrect circumcenter" + ); + + // Degenerate triangle tests + let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); + assert_eq!( + zero_degenerate_triangle.is_degenerate(), + true, + "incorrect degenerate check" + ); + assert_eq!( + zero_degenerate_triangle.normal(), + Err(InvalidDirectionError::Zero), + "incorrect normal" + ); + assert_eq!( + zero_degenerate_triangle.largest_side(), + (Vec3::ZERO, Vec3::ZERO), + "incorrect largest side" + ); + + let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X); + assert_eq!( + dup_degenerate_triangle.is_degenerate(), + true, + "incorrect degenerate check" + ); + assert_eq!( + dup_degenerate_triangle.normal(), + Err(InvalidDirectionError::Zero), + "incorrect normal" + ); + assert_eq!( + dup_degenerate_triangle.largest_side(), + (Vec3::ZERO, Vec3::X), + "incorrect largest side" + ); + + let common_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); + assert_eq!( + common_degenerate_triangle.is_degenerate(), + true, + "incorrect degenerate check" + ); + assert_eq!( + common_degenerate_triangle.normal(), + Err(InvalidDirectionError::Zero), + "incorrect normal" + ); + assert_eq!( + common_degenerate_triangle.largest_side(), + (Vec3::NEG_X, Vec3::X), + "incorrect largest side" + ); + } } From d104d0fb477b564800673e64bb05315748be5266 Mon Sep 17 00:00:00 2001 From: Vitor Falcao Date: Fri, 22 Mar 2024 17:38:54 -0300 Subject: [PATCH 2/7] Refactor degenerate triangle tests in dim3.rs --- crates/bevy_math/src/primitives/dim3.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 8c6fbf4d4d2ff..3e8756b5b201b 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1271,9 +1271,8 @@ mod tests { epsilon = 10e-9 ); assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal"); - assert_eq!( + assert!( default_triangle.is_degenerate(), - false, "incorrect degenerate check" ); assert_eq!( @@ -1319,9 +1318,8 @@ mod tests { // Degenerate triangle tests let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); - assert_eq!( + assert!( zero_degenerate_triangle.is_degenerate(), - true, "incorrect degenerate check" ); assert_eq!( @@ -1336,9 +1334,8 @@ mod tests { ); let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X); - assert_eq!( + assert!( dup_degenerate_triangle.is_degenerate(), - true, "incorrect degenerate check" ); assert_eq!( @@ -1353,9 +1350,8 @@ mod tests { ); let common_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); - assert_eq!( + assert!( common_degenerate_triangle.is_degenerate(), - true, "incorrect degenerate check" ); assert_eq!( From 01bc018153d9ecac730f9eac4fd1cfd752c94c00 Mon Sep 17 00:00:00 2001 From: Vitor Falcao Date: Fri, 22 Mar 2024 17:43:59 -0300 Subject: [PATCH 3/7] Fix degenerate check in default_triangle --- crates/bevy_math/src/primitives/dim3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 3e8756b5b201b..764fcf7a92ff7 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1272,7 +1272,7 @@ mod tests { ); assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal"); assert!( - default_triangle.is_degenerate(), + !default_triangle.is_degenerate(), "incorrect degenerate check" ); assert_eq!( From d1e8990bbf4ff0a20ffb17ef3e8f5dbb48a6aab6 Mon Sep 17 00:00:00 2001 From: Vitor Falcao Date: Mon, 25 Mar 2024 15:12:03 -0300 Subject: [PATCH 4/7] Refactor degenerate triangle name in dim3.rs --- crates/bevy_math/src/primitives/dim3.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 764fcf7a92ff7..cc877b368fa0c 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1349,18 +1349,18 @@ mod tests { "incorrect largest side" ); - let common_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); + let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); assert!( - common_degenerate_triangle.is_degenerate(), + collinear_degenerate_triangle.is_degenerate(), "incorrect degenerate check" ); assert_eq!( - common_degenerate_triangle.normal(), + collinear_degenerate_triangle.normal(), Err(InvalidDirectionError::Zero), "incorrect normal" ); assert_eq!( - common_degenerate_triangle.largest_side(), + collinear_degenerate_triangle.largest_side(), (Vec3::NEG_X, Vec3::X), "incorrect largest side" ); From 37a47530237afe3579fb778f3e35a83df8135ff8 Mon Sep 17 00:00:00 2001 From: Ben Harper Date: Wed, 22 May 2024 12:41:56 +1000 Subject: [PATCH 5/7] update tests --- .../src/bounding/bounded3d/primitive_impls.rs | 25 +++++++--- crates/bevy_math/src/primitives/dim3.rs | 49 +++++++++---------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 6145b74fe1704..d82bd87cec6ef 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -611,12 +611,10 @@ mod tests { assert_eq!(bounding_sphere.radius(), 1.5); } + #[test] fn triangle3d() { - // let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY); // DIVISION BY ZERO - // assert_eq!(bs.center, Vec3::ZERO, "incorrect bounding sphere center"); - // assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius"); - 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(), @@ -649,15 +647,15 @@ mod tests { "incorrect bounding box half extents" ); - let common_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X); - let bs = common_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY); + 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 = common_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY); + let br = collinear_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY); assert_eq!( br.center(), Vec3::ZERO.into(), @@ -669,4 +667,17 @@ mod tests { "incorrect bounding box half extents" ); } + + #[test] + #[should_panic] + fn degenerate_bounding_sphere_should_panic() { + let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); + let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY); // DIVISION BY ZERO + assert_eq!( + bs.center, + Vec3::ZERO.into(), + "incorrect bounding sphere center" + ); + assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius"); + } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index cc877b368fa0c..aff26123bb7ec 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1210,33 +1210,6 @@ mod tests { assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO); } - #[test] - fn complex_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"); - } - #[test] fn extrusion_math() { let circle = Circle::new(0.75); @@ -1316,6 +1289,28 @@ mod tests { "incorrect circumcenter" ); + // Arbitrary triangle tests + 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" + ); + // Degenerate triangle tests let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); assert!( From a3713d0b3883bd3548c104a6947e85c3d1987e0d Mon Sep 17 00:00:00 2001 From: Ben Harper Date: Thu, 23 May 2024 14:35:51 +1000 Subject: [PATCH 6/7] merge obtuse and degenerate code paths --- .../src/bounding/bounded3d/primitive_impls.rs | 45 +++++-------------- crates/bevy_math/src/primitives/dim3.rs | 33 ++++++++++++++ 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index d82bd87cec6ef..076e9ac28bdf9 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -323,31 +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, _: Quat) -> BoundingSphere { + if self.is_degenerate() || self.is_obtuse() { let (p1, p2) = self.largest_side(); - let mid_point = (p1 + p2) / 2.0; - let (segment, _) = Segment3d::from_points(p1, p2); - return segment.bounding_sphere(mid_point, 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 mid_point = (p1 + p2) / 2.0; let radius = mid_point.distance(p1); BoundingSphere::new(mid_point + translation, radius) } else { + let [a, _, _] = self.vertices; + let circumcenter = self.circumcenter(); let radius = circumcenter.distance(a); BoundingSphere::new(circumcenter + translation, radius) @@ -627,6 +611,14 @@ mod tests { "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!( @@ -667,17 +659,4 @@ mod tests { "incorrect bounding box half extents" ); } - - #[test] - #[should_panic] - fn degenerate_bounding_sphere_should_panic() { - let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); - let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY); // DIVISION BY ZERO - assert_eq!( - bs.center, - Vec3::ZERO.into(), - "incorrect bounding sphere center" - ); - assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius"); - } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index aff26123bb7ec..b6d4f1cbcaf7d 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -780,6 +780,34 @@ impl Triangle3d { 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(), bc.length(), ca.length()]; + side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap()); + side_lengths[0].powf(2.) + side_lengths[1].powf(2.) > side_lengths[2].powf(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(), bc.length(), ca.length()]; + side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap()); + side_lengths[0].powf(2.) + side_lengths[1].powf(2.) < side_lengths[2].powf(2.) + } + /// Reverse the triangle by swapping the first and last vertices. #[inline(always)] pub fn reverse(&mut self) { @@ -1289,6 +1317,11 @@ mod tests { "incorrect circumcenter" ); + assert!(acute_triangle.is_acute()); + assert!(!acute_triangle.is_obtuse()); + assert!(!obtuse_triangle.is_acute()); + assert!(obtuse_triangle.is_obtuse()); + // Arbitrary triangle tests 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); From 16d3b7ce2ed45cc65fb737b7ce410dd7cc96ce86 Mon Sep 17 00:00:00 2001 From: Ben Harper Date: Fri, 24 May 2024 00:47:27 +1000 Subject: [PATCH 7/7] implement review feedback --- .../src/bounding/bounded3d/primitive_impls.rs | 2 +- crates/bevy_math/src/primitives/dim2.rs | 62 +++++++++++++++++++ crates/bevy_math/src/primitives/dim3.rs | 16 +++-- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 076e9ac28bdf9..d11f8b9bbe5f8 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -323,7 +323,7 @@ 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, _: Quat) -> BoundingSphere { + fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere { if self.is_degenerate() || self.is_obtuse() { let (p1, p2) = self.largest_side(); let mid_point = (p1 + p2) / 2.0; diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index d7c47e65dc98f..61725ab1b94e3 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -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)] @@ -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] diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index b6d4f1cbcaf7d..2ad71c689d011 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -789,9 +789,13 @@ impl Triangle3d { let ca = a - c; // a^2 + b^2 < c^2 for an acute triangle - let mut side_lengths = [ab.length(), bc.length(), ca.length()]; + 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].powf(2.) + side_lengths[1].powf(2.) > side_lengths[2].powf(2.) + side_lengths[0] + side_lengths[1] > side_lengths[2] } /// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees @@ -803,9 +807,13 @@ impl Triangle3d { let ca = a - c; // a^2 + b^2 > c^2 for an obtuse triangle - let mut side_lengths = [ab.length(), bc.length(), ca.length()]; + 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].powf(2.) + side_lengths[1].powf(2.) < side_lengths[2].powf(2.) + side_lengths[0] + side_lengths[1] < side_lengths[2] } /// Reverse the triangle by swapping the first and last vertices.