Add triangle_math tests and fix Triangle3d::bounding_sphere bug (#13467)
# 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](https://github.com/bevyengine/bevy/pull/12659#discussion_r1536640427) 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>
This commit is contained in:
parent
1d950e6195
commit
bd5148e0f5
@ -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 [`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
|
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
|
||||||
/// that contains the largest side of the triangle.
|
/// that contains the largest side of the triangle.
|
||||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||||
if self.is_degenerate() {
|
if self.is_degenerate() || self.is_obtuse() {
|
||||||
let (p1, p2) = self.largest_side();
|
let (p1, p2) = self.largest_side();
|
||||||
let (segment, _) = Segment3d::from_points(p1, p2);
|
let mid_point = (p1 + p2) / 2.0;
|
||||||
return segment.bounding_sphere(translation, rotation);
|
let radius = mid_point.distance(p1);
|
||||||
}
|
BoundingSphere::new(mid_point + translation, radius)
|
||||||
|
|
||||||
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 {
|
} 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 circumcenter = self.circumcenter();
|
||||||
let radius = circumcenter.distance(a);
|
let radius = circumcenter.distance(a);
|
||||||
BoundingSphere::new(circumcenter + translation, radius)
|
BoundingSphere::new(circumcenter + translation, radius)
|
||||||
@ -355,13 +341,14 @@ impl Bounded3d for Triangle3d {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::bounding::BoundingVolume;
|
||||||
use glam::{Quat, Vec3, Vec3A};
|
use glam::{Quat, Vec3, Vec3A};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bounding::Bounded3d,
|
bounding::Bounded3d,
|
||||||
primitives::{
|
primitives::{
|
||||||
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
|
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
|
||||||
Segment3d, Sphere, Torus,
|
Segment3d, Sphere, Torus, Triangle3d,
|
||||||
},
|
},
|
||||||
Dir3,
|
Dir3,
|
||||||
};
|
};
|
||||||
@ -607,4 +594,69 @@ mod tests {
|
|||||||
assert_eq!(bounding_sphere.center, translation.into());
|
assert_eq!(bounding_sphere.center, translation.into());
|
||||||
assert_eq!(bounding_sphere.radius(), 1.5);
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -527,6 +527,54 @@ impl Triangle2d {
|
|||||||
(Circle { radius }, center)
|
(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
|
/// Reverse the [`WindingOrder`] of the triangle
|
||||||
/// by swapping the first and last vertices.
|
/// by swapping the first and last vertices.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -975,6 +1023,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(triangle.area(), 21.0, "incorrect area");
|
assert_eq!(triangle.area(), 21.0, "incorrect area");
|
||||||
assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");
|
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]
|
#[test]
|
||||||
|
|||||||
@ -780,6 +780,42 @@ impl Triangle3d {
|
|||||||
ab.cross(ac).length() < 10e-7
|
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 triangle by swapping the first and last vertices.
|
/// Reverse the triangle by swapping the first and last vertices.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn reverse(&mut self) {
|
pub fn reverse(&mut self) {
|
||||||
@ -1010,7 +1046,7 @@ mod tests {
|
|||||||
// Reference values were computed by hand and/or with external tools
|
// Reference values were computed by hand and/or with external tools
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Quat;
|
use crate::{InvalidDirectionError, Quat};
|
||||||
use approx::assert_relative_eq;
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1210,8 +1246,91 @@ mod tests {
|
|||||||
assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
|
assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extrusion_math() {
|
||||||
|
let circle = Circle::new(0.75);
|
||||||
|
let cylinder = Extrusion::new(circle, 2.5);
|
||||||
|
assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
|
||||||
|
assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
|
||||||
|
|
||||||
|
let annulus = crate::primitives::Annulus::new(0.25, 1.375);
|
||||||
|
let tube = Extrusion::new(annulus, 0.333);
|
||||||
|
assert_eq!(tube.area(), 14.886437, "incorrect surface area");
|
||||||
|
assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
|
||||||
|
|
||||||
|
let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
|
||||||
|
let regular_prism = Extrusion::new(polygon, 1.25);
|
||||||
|
assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
|
||||||
|
assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn triangle_math() {
|
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!(
|
||||||
|
!default_triangle.is_degenerate(),
|
||||||
|
"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"
|
||||||
|
);
|
||||||
|
|
||||||
|
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 [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
|
||||||
let triangle = Triangle3d::new(a, b, c);
|
let triangle = Triangle3d::new(a, b, c);
|
||||||
|
|
||||||
@ -1233,25 +1352,53 @@ mod tests {
|
|||||||
"incorrect normal"
|
"incorrect normal"
|
||||||
);
|
);
|
||||||
|
|
||||||
let degenerate = Triangle3d::new(Vec3::NEG_ONE, Vec3::ZERO, Vec3::ONE);
|
// Degenerate triangle tests
|
||||||
assert!(degenerate.is_degenerate(), "did not find degenerate");
|
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
|
||||||
}
|
assert!(
|
||||||
|
zero_degenerate_triangle.is_degenerate(),
|
||||||
|
"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"
|
||||||
|
);
|
||||||
|
|
||||||
#[test]
|
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
|
||||||
fn extrusion_math() {
|
assert!(
|
||||||
let circle = Circle::new(0.75);
|
dup_degenerate_triangle.is_degenerate(),
|
||||||
let cylinder = Extrusion::new(circle, 2.5);
|
"incorrect degenerate check"
|
||||||
assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
|
);
|
||||||
assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
|
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 annulus = crate::primitives::Annulus::new(0.25, 1.375);
|
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
|
||||||
let tube = Extrusion::new(annulus, 0.333);
|
assert!(
|
||||||
assert_eq!(tube.area(), 14.886437, "incorrect surface area");
|
collinear_degenerate_triangle.is_degenerate(),
|
||||||
assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
|
"incorrect degenerate check"
|
||||||
|
);
|
||||||
let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
|
assert_eq!(
|
||||||
let regular_prism = Extrusion::new(polygon, 1.25);
|
collinear_degenerate_triangle.normal(),
|
||||||
assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
|
Err(InvalidDirectionError::Zero),
|
||||||
assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
|
"incorrect normal"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
collinear_degenerate_triangle.largest_side(),
|
||||||
|
(Vec3::NEG_X, Vec3::X),
|
||||||
|
"incorrect largest side"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user