Skip to content

Commit 2b3e334

Browse files
authored
separating finite and infinite 3d planes (#12426)
# Objective Fixes #12388 ## Solution - Removing the plane3d and adding rect3d primitive mesh
1 parent 158defd commit 2b3e334

File tree

10 files changed

+153
-64
lines changed

10 files changed

+153
-64
lines changed

crates/bevy_gizmos/src/primitives/dim3.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,18 @@ impl<T: GizmoConfigGroup> Drop for SphereBuilder<'_, '_, '_, T> {
134134

135135
// plane 3d
136136

137-
/// Builder for configuring the drawing options of [`Sphere`].
137+
/// Builder for configuring the drawing options of [`Plane3d`].
138138
pub struct Plane3dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
139139
gizmos: &'a mut Gizmos<'w, 's, T>,
140140

141141
// direction of the normal orthogonal to the plane
142142
normal: Dir3,
143143

144-
// Rotation of the sphere around the origin in 3D space
144+
// Rotation of the plane around the origin in 3D space
145145
rotation: Quat,
146-
// Center position of the sphere in 3D space
146+
// Center position of the plane in 3D space
147147
position: Vec3,
148-
// Color of the sphere
148+
// Color of the plane
149149
color: Color,
150150

151151
// Number of axis to hint the plane

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use crate::{
44
bounding::{Bounded2d, BoundingCircle},
55
primitives::{
6-
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d,
7-
Polyline3d, Segment3d, Sphere, Torus, Triangle2d,
6+
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
7+
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d,
88
},
99
Dir3, Mat3, Quat, Vec2, Vec3,
1010
};
@@ -21,7 +21,7 @@ impl Bounded3d for Sphere {
2121
}
2222
}
2323

24-
impl Bounded3d for Plane3d {
24+
impl Bounded3d for InfinitePlane3d {
2525
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
2626
let normal = rotation * *self.normal;
2727
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
@@ -310,7 +310,7 @@ mod tests {
310310
use crate::{
311311
bounding::Bounded3d,
312312
primitives::{
313-
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d,
313+
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
314314
Segment3d, Sphere, Torus,
315315
},
316316
Dir3,
@@ -334,23 +334,24 @@ mod tests {
334334
fn plane() {
335335
let translation = Vec3::new(2.0, 1.0, 0.0);
336336

337-
let aabb1 = Plane3d::new(Vec3::X).aabb_3d(translation, Quat::IDENTITY);
337+
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation, Quat::IDENTITY);
338338
assert_eq!(aabb1.min, Vec3::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
339339
assert_eq!(aabb1.max, Vec3::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
340340

341-
let aabb2 = Plane3d::new(Vec3::Y).aabb_3d(translation, Quat::IDENTITY);
341+
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation, Quat::IDENTITY);
342342
assert_eq!(aabb2.min, Vec3::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
343343
assert_eq!(aabb2.max, Vec3::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
344344

345-
let aabb3 = Plane3d::new(Vec3::Z).aabb_3d(translation, Quat::IDENTITY);
345+
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation, Quat::IDENTITY);
346346
assert_eq!(aabb3.min, Vec3::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
347347
assert_eq!(aabb3.max, Vec3::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
348348

349-
let aabb4 = Plane3d::new(Vec3::ONE).aabb_3d(translation, Quat::IDENTITY);
349+
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation, Quat::IDENTITY);
350350
assert_eq!(aabb4.min, Vec3::splat(-f32::MAX / 2.0));
351351
assert_eq!(aabb4.max, Vec3::splat(f32::MAX / 2.0));
352352

353-
let bounding_sphere = Plane3d::new(Vec3::Y).bounding_sphere(translation, Quat::IDENTITY);
353+
let bounding_sphere =
354+
InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation, Quat::IDENTITY);
354355
assert_eq!(bounding_sphere.center, translation);
355356
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
356357
}

crates/bevy_math/src/primitives/dim3.rs

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::f32::consts::{FRAC_PI_3, PI};
33
use super::{Circle, Primitive3d};
44
use crate::{
55
bounding::{Aabb3d, Bounded3d, BoundingSphere},
6-
Dir3, InvalidDirectionError, Mat3, Quat, Vec3,
6+
Dir3, InvalidDirectionError, Mat3, Quat, Vec2, Vec3,
77
};
88

99
/// A sphere primitive
@@ -67,33 +67,38 @@ impl Sphere {
6767
}
6868
}
6969

70-
/// An unbounded plane in 3D space. It forms a separating surface through the origin,
71-
/// stretching infinitely far
70+
/// A bounded plane in 3D space. It forms a surface starting from the origin with a defined height and width.
7271
#[derive(Clone, Copy, Debug, PartialEq)]
7372
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
7473
pub struct Plane3d {
7574
/// The normal of the plane. The plane will be placed perpendicular to this direction
7675
pub normal: Dir3,
76+
/// Half of the width and height of the plane
77+
pub half_size: Vec2,
7778
}
7879
impl Primitive3d for Plane3d {}
7980

8081
impl Default for Plane3d {
81-
/// Returns the default [`Plane3d`] with a normal pointing in the `+Y` direction.
82+
/// Returns the default [`Plane3d`] with a normal pointing in the `+Y` direction, width and height of `1.0`.
8283
fn default() -> Self {
83-
Self { normal: Dir3::Y }
84+
Self {
85+
normal: Dir3::Y,
86+
half_size: Vec2::splat(0.5),
87+
}
8488
}
8589
}
8690

8791
impl Plane3d {
88-
/// Create a new `Plane3d` from a normal
92+
/// Create a new `Plane3d` from a normal and a half size
8993
///
9094
/// # Panics
9195
///
9296
/// Panics if the given `normal` is zero (or very close to zero), or non-finite.
9397
#[inline(always)]
94-
pub fn new(normal: Vec3) -> Self {
98+
pub fn new(normal: Vec3, half_size: Vec2) -> Self {
9599
Self {
96100
normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
101+
half_size,
97102
}
98103
}
99104

@@ -109,8 +114,71 @@ impl Plane3d {
109114
/// are *collinear* and lie on the same line.
110115
#[inline(always)]
111116
pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
112-
let normal = Dir3::new((b - a).cross(c - a))
113-
.expect("plane must be defined by three finite points that don't lie on the same line");
117+
let normal = Dir3::new((b - a).cross(c - a)).expect(
118+
"finite plane must be defined by three finite points that don't lie on the same line",
119+
);
120+
let translation = (a + b + c) / 3.0;
121+
122+
(
123+
Self {
124+
normal,
125+
..Default::default()
126+
},
127+
translation,
128+
)
129+
}
130+
}
131+
132+
/// An unbounded plane in 3D space. It forms a separating surface through the origin,
133+
/// stretching infinitely far
134+
#[derive(Clone, Copy, Debug, PartialEq)]
135+
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
136+
pub struct InfinitePlane3d {
137+
/// The normal of the plane. The plane will be placed perpendicular to this direction
138+
pub normal: Dir3,
139+
}
140+
impl Primitive3d for InfinitePlane3d {}
141+
142+
impl Default for InfinitePlane3d {
143+
/// Returns the default [`InfinitePlane3d`] with a normal pointing in the `+Y` direction.
144+
fn default() -> Self {
145+
Self { normal: Dir3::Y }
146+
}
147+
}
148+
149+
impl InfinitePlane3d {
150+
/// Create a new `InfinitePlane3d` from a normal
151+
///
152+
/// # Panics
153+
///
154+
/// Panics if the given `normal` is zero (or very close to zero), or non-finite.
155+
#[inline(always)]
156+
pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
157+
where
158+
<T as TryInto<Dir3>>::Error: std::fmt::Debug,
159+
{
160+
Self {
161+
normal: normal
162+
.try_into()
163+
.expect("normal must be nonzero and finite"),
164+
}
165+
}
166+
167+
/// Create a new `InfinitePlane3d` based on three points and compute the geometric center
168+
/// of those points.
169+
///
170+
/// The direction of the plane normal is determined by the winding order
171+
/// of the triangular shape formed by the points.
172+
///
173+
/// # Panics
174+
///
175+
/// Panics if a valid normal can not be computed, for example when the points
176+
/// are *collinear* and lie on the same line.
177+
#[inline(always)]
178+
pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
179+
let normal = Dir3::new((b - a).cross(c - a)).expect(
180+
"infinite plane must be defined by three finite points that don't lie on the same line",
181+
);
114182
let translation = (a + b + c) / 3.0;
115183

116184
(Self { normal }, translation)
@@ -975,6 +1043,14 @@ mod tests {
9751043
fn plane_from_points() {
9761044
let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
9771045
assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1046+
assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1047+
assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1048+
}
1049+
1050+
#[test]
1051+
fn infinite_plane_from_points() {
1052+
let (plane, translation) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1053+
assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
9781054
assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
9791055
}
9801056

crates/bevy_math/src/ray.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
primitives::{Plane2d, Plane3d},
2+
primitives::{InfinitePlane3d, Plane2d},
33
Dir2, Dir3, Vec2, Vec3,
44
};
55

@@ -79,7 +79,7 @@ impl Ray3d {
7979

8080
/// Get the distance to a plane if the ray intersects it
8181
#[inline]
82-
pub fn intersect_plane(&self, plane_origin: Vec3, plane: Plane3d) -> Option<f32> {
82+
pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option<f32> {
8383
let denominator = plane.normal.dot(*self.direction);
8484
if denominator.abs() > f32::EPSILON {
8585
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
@@ -141,37 +141,40 @@ mod tests {
141141

142142
// Orthogonal, and test that an inverse plane_normal has the same result
143143
assert_eq!(
144-
ray.intersect_plane(Vec3::Z, Plane3d::new(Vec3::Z)),
144+
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)),
145145
Some(1.0)
146146
);
147147
assert_eq!(
148-
ray.intersect_plane(Vec3::Z, Plane3d::new(Vec3::NEG_Z)),
148+
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)),
149149
Some(1.0)
150150
);
151151
assert!(ray
152-
.intersect_plane(Vec3::NEG_Z, Plane3d::new(Vec3::Z))
152+
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z))
153153
.is_none());
154154
assert!(ray
155-
.intersect_plane(Vec3::NEG_Z, Plane3d::new(Vec3::NEG_Z))
155+
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::NEG_Z))
156156
.is_none());
157157

158158
// Diagonal
159159
assert_eq!(
160-
ray.intersect_plane(Vec3::Z, Plane3d::new(Vec3::ONE)),
160+
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)),
161161
Some(1.0)
162162
);
163163
assert!(ray
164-
.intersect_plane(Vec3::NEG_Z, Plane3d::new(Vec3::ONE))
164+
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE))
165165
.is_none());
166166

167167
// Parallel
168168
assert!(ray
169-
.intersect_plane(Vec3::X, Plane3d::new(Vec3::X))
169+
.intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
170170
.is_none());
171171

172172
// Parallel with simulated rounding error
173173
assert!(ray
174-
.intersect_plane(Vec3::X, Plane3d::new(Vec3::X + Vec3::Z * f32::EPSILON))
174+
.intersect_plane(
175+
Vec3::X,
176+
InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
177+
)
175178
.is_none());
176179
}
177180
}

crates/bevy_reflect/src/impls/math/primitives3d.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate as bevy_reflect;
22
use crate::{ReflectDeserialize, ReflectSerialize};
3-
use bevy_math::{primitives::*, Dir3, Vec3};
3+
use bevy_math::{primitives::*, Dir3, Vec2, Vec3};
44
use bevy_reflect_derive::impl_reflect;
55

66
impl_reflect!(
@@ -16,6 +16,15 @@ impl_reflect!(
1616
#[type_path = "bevy_math::primitives"]
1717
struct Plane3d {
1818
normal: Dir3,
19+
half_size: Vec2,
20+
}
21+
);
22+
23+
impl_reflect!(
24+
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
25+
#[type_path = "bevy_math::primitives"]
26+
struct InfinitePlane3d {
27+
normal: Dir3,
1928
}
2029
);
2130

0 commit comments

Comments
 (0)