Skip to content

Commit 45ba723

Browse files
committed
Adding helper methods
1 parent 432138f commit 45ba723

File tree

2 files changed

+221
-45
lines changed

2 files changed

+221
-45
lines changed

crates/bevy_geometry/src/lib.rs

Lines changed: 220 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
use bevy_math::*;
22
use bevy_reflect::Reflect;
3-
//use bevy_transform::components::GlobalTransform;
43
use std::error::Error;
54
use std::fmt;
65

76
pub trait Primitive3d {
8-
/*
9-
/// Returns true if this primitive is on the outside (normal direction) of the supplied
10-
fn outside_plane(
11-
&self,
12-
primitive_transform: GlobalTransform,
13-
plane: Plane,
14-
plane_transform: GlobalTransform,
15-
) -> bool;*/
7+
/// Returns true if this primitive is entirely on the outside (in the normal direction) of the
8+
/// supplied plane.
9+
fn outside_plane(&self, plane: Plane) -> bool;
1610
}
1711

1812
#[derive(Debug, Clone)]
@@ -25,7 +19,10 @@ impl fmt::Display for PrimitiveError {
2519
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2620
match self {
2721
PrimitiveError::MinGreaterThanMax => {
28-
write!(f, "AxisAlignedBox minimums must be smaller than maximums")
22+
write!(
23+
f,
24+
"AxisAlignedBox minimums must be smaller or equal to the maximums"
25+
)
2926
}
3027
PrimitiveError::NonPositiveExtents => {
3128
write!(f, "AxisAlignedBox extents must be greater than zero")
@@ -36,76 +33,214 @@ impl fmt::Display for PrimitiveError {
3633

3734
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
3835
pub struct Sphere {
39-
pub origin: Vec3,
40-
pub radius: f32,
36+
origin: Vec3,
37+
radius: f32,
38+
}
39+
40+
impl Sphere {
41+
/// Get a reference to the sphere's origin.
42+
pub fn origin(&self) -> &Vec3 {
43+
&self.origin
44+
}
45+
46+
/// Get a reference to the sphere's radius.
47+
pub fn radius(&self) -> &f32 {
48+
&self.radius
49+
}
50+
51+
/// Set the sphere's origin.
52+
pub fn set_origin(&mut self, origin: Vec3) {
53+
self.origin = origin;
54+
}
55+
56+
/// Set the sphere's radius.
57+
pub fn set_radius(&mut self, radius: f32) {
58+
self.radius = radius;
59+
}
60+
}
61+
impl Primitive3d for Sphere {
62+
/// Use the sphere's position and radius to determin eif it is entirely on the outside of the
63+
/// the supplied plane.
64+
fn outside_plane(&self, plane: Plane) -> bool {
65+
plane.distance_to_point(&self.origin) > self.radius
66+
}
4167
}
42-
impl Primitive3d for Sphere {}
4368

4469
/// An oriented box, unlike an axis aligned box, can be rotated and is not constrained to match the
4570
/// orientation of the coordinate system it is defined in. Internally, this is represented as an
4671
/// axis aligned box with some rotation ([Quat]) applied.
4772
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
4873
pub struct OrientedBox {
49-
pub aab: AxisAlignedBox,
50-
pub orientation: Quat,
74+
aab: AxisAlignedBox,
75+
transform: Mat4,
76+
}
77+
impl Primitive3d for OrientedBox {
78+
fn outside_plane(&self, plane: Plane) -> bool {
79+
for vertex in self.vertices().iter() {
80+
if plane.distance_to_point(vertex) <= 0.0 {
81+
return false;
82+
}
83+
}
84+
true
85+
}
86+
}
87+
impl OrientedBox {
88+
/// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox].
89+
/// ```none
90+
/// (5)------(1)
91+
/// | \ | \
92+
/// | (4)------(0)
93+
/// | | | |
94+
/// (7)--|---(3) |
95+
/// \ | \ |
96+
/// (6)------(2)
97+
/// ```
98+
pub fn vertices(&self) -> [Vec3; 8] {
99+
let mut vertices = [Vec3::ZERO; 8];
100+
let aab_vertices = self.aab.vertices();
101+
for i in 0..vertices.len() {
102+
vertices[i] = self.transform.project_point3(aab_vertices[i])
103+
}
104+
vertices
105+
}
106+
107+
/// Set the oriented box's aab.
108+
pub fn set_aab(&mut self, aab: AxisAlignedBox) {
109+
self.aab = aab;
110+
}
111+
112+
/// Set the oriented box's transform.
113+
pub fn set_transform(&mut self, transform: Mat4) {
114+
self.transform = transform;
115+
}
116+
pub fn fast_aabb(&self) -> AxisAlignedBox {
117+
let vertices = self.vertices();
118+
let mut max = Vec3::splat(f32::MIN);
119+
let mut min = Vec3::splat(f32::MAX);
120+
for vertex in vertices.iter() {
121+
max = vertex.max(max);
122+
min = vertex.min(min);
123+
}
124+
// Unwrap is okay here because min < max
125+
AxisAlignedBox::from_min_max(min, max).unwrap()
126+
}
51127
}
52-
impl Primitive3d for OrientedBox {}
53128

54129
/// An axis aligned box is a box whose axes lie in the x/y/z directions of the coordinate system
55130
/// the box is defined in.
56131
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
57132
pub struct AxisAlignedBox {
58-
minimums: Vec3,
59-
maximums: Vec3,
133+
min: Vec3,
134+
max: Vec3,
135+
}
136+
impl Primitive3d for AxisAlignedBox {
137+
fn outside_plane(&self, plane: Plane) -> bool {
138+
for vertex in self.vertices().iter() {
139+
if plane.distance_to_point(vertex) <= 0.0 {
140+
return false;
141+
}
142+
}
143+
true
144+
}
60145
}
61-
impl Primitive3d for AxisAlignedBox {}
62146
impl AxisAlignedBox {
63-
pub fn from_min_max(minimums: Vec3, maximums: Vec3) -> Result<AxisAlignedBox, PrimitiveError> {
64-
if (maximums - minimums).min_element() > 0.0 {
65-
Ok(AxisAlignedBox { minimums, maximums })
147+
/// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox].
148+
/// ```none
149+
/// (5)------(1) Y
150+
/// | \ | \ |
151+
/// | (4)------(0) MAX o---X
152+
/// | | | | \
153+
/// MIN (7)--|---(3) | Z
154+
/// \ | \ |
155+
/// (6)------(2)
156+
/// ```
157+
pub fn vertices(&self) -> [Vec3; 8] {
158+
let min = self.min;
159+
let max = self.max;
160+
[
161+
Vec3::new(max.x, max.y, max.z),
162+
Vec3::new(max.x, max.y, min.z),
163+
Vec3::new(max.x, min.y, max.z),
164+
Vec3::new(max.x, min.y, min.z),
165+
Vec3::new(min.x, max.y, max.z),
166+
Vec3::new(min.x, max.y, min.z),
167+
Vec3::new(min.x, min.y, max.z),
168+
Vec3::new(min.x, min.y, min.z),
169+
]
170+
}
171+
/// Construct an [AxisAlignedBox] given the coordinates of the minimum and maximum corners.
172+
pub fn from_min_max(min: Vec3, max: Vec3) -> Result<AxisAlignedBox, PrimitiveError> {
173+
if (max - min).min_element() >= 0.0 {
174+
Ok(AxisAlignedBox { min, max })
66175
} else {
67176
Err(PrimitiveError::MinGreaterThanMax)
68177
}
69178
}
179+
/// Construct an [AxisALignedBox] from the origin at the minimum corner, and the extents - the
180+
/// dimensions of the box in each axis.
70181
pub fn from_extents_origin(
71182
extents: Vec3,
72183
origin: Vec3,
73184
) -> Result<AxisAlignedBox, PrimitiveError> {
74185
if extents.min_element() > 0.0 {
75186
Ok(AxisAlignedBox {
76-
minimums: origin,
77-
maximums: extents + origin,
187+
min: origin,
188+
max: extents + origin,
78189
})
79190
} else {
80191
Err(PrimitiveError::NonPositiveExtents)
81192
}
82193
}
194+
/// Computes the AAB that
195+
pub fn from_points(points: Vec<Vec3>) -> AxisAlignedBox {
196+
let mut max = Vec3::splat(f32::MIN);
197+
let mut min = Vec3::splat(f32::MAX);
198+
for &point in points.iter() {
199+
max = point.max(max);
200+
min = point.min(min);
201+
}
202+
// Unwrap is okay here because min < max
203+
AxisAlignedBox::from_min_max(min, max).unwrap()
204+
}
83205
}
84206

85-
/// A frustum is a truncated pyramid that is used to represent the "volume" of world space that is
207+
/// A frustum is a truncated pyramid that is used to represent the volume of world space that is
86208
/// visible to the camera.
87209
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
88210
#[reflect_value(PartialEq)]
89211
pub struct Frustum {
90212
planes: [Plane; 6],
213+
vertices: [Vec3; 8],
214+
}
215+
impl Primitive3d for Frustum {
216+
fn outside_plane(&self, plane: Plane) -> bool {
217+
for vertex in self.vertices().iter() {
218+
if plane.distance_to_point(vertex) <= 0.0 {
219+
return false;
220+
}
221+
}
222+
true
223+
}
91224
}
92-
impl Primitive3d for Frustum {}
93225
impl Frustum {
94-
pub fn from_camera_properties(
95-
&self,
96-
camera_position: Mat4,
97-
projection_matrix: Mat4,
98-
) -> Frustum {
226+
fn compute_vertices(camera_position: Mat4, projection_matrix: Mat4) -> [Vec3; 8] {
99227
let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse();
100-
// Near/Far, Top/Bottom, Left/Right
101-
let nbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, -1.0));
102-
let nbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, -1.0));
103-
let ntl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, -1.0));
104-
let fbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, 1.0));
105-
let ftr_world = ndc_to_world.project_point3(Vec3::new(1.0, 1.0, 1.0));
106-
let ftl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, 1.0));
107-
let fbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, 1.0));
108-
let ntr_world = ndc_to_world.project_point3(Vec3::new(1.0, 1.0, -1.0));
228+
[
229+
ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, -1.0)),
230+
ndc_to_world.project_point3(Vec3::new(1.0, -1.0, -1.0)),
231+
ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, -1.0)),
232+
ndc_to_world.project_point3(Vec3::new(1.0, 1.0, -1.0)),
233+
ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, 1.0)),
234+
ndc_to_world.project_point3(Vec3::new(1.0, -1.0, 1.0)),
235+
ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, 1.0)),
236+
ndc_to_world.project_point3(Vec3::new(1.0, 1.0, 1.0)),
237+
]
238+
}
239+
240+
pub fn from_camera_properties(camera_position: Mat4, projection_matrix: Mat4) -> Frustum {
241+
let vertices = Frustum::compute_vertices(camera_position, projection_matrix);
242+
let [nbl_world, nbr_world, ntl_world, ntr_world, fbl_world, fbr_world, ftl_world, ftr_world] =
243+
vertices;
109244

110245
let near_normal = (nbr_world - nbl_world)
111246
.cross(ntl_world - nbl_world)
@@ -150,9 +285,30 @@ impl Frustum {
150285
point: ftr_world,
151286
normal: far_normal,
152287
};
153-
Frustum {
154-
planes: [left, right, top, bottom, near, far],
155-
}
288+
289+
let planes = [left, right, top, bottom, near, far];
290+
291+
Frustum { planes, vertices }
292+
}
293+
294+
/// Get a reference to the frustum's vertices. These are given as an ordered list of vertices
295+
/// that form the 8 corners of a [Frustum].
296+
/// ```none
297+
/// (6)--------------(7)
298+
/// | \ TOP / |
299+
/// | (2)------(3) |
300+
/// | L | | R |
301+
/// (4) | NEAR | (5)
302+
/// \ | | /
303+
/// (0)------(1)
304+
/// ```
305+
pub fn vertices(&self) -> &[Vec3; 8] {
306+
&self.vertices
307+
}
308+
309+
/// Get a reference to the frustum's planes.
310+
pub fn planes(&self) -> &[Plane; 6] {
311+
&self.planes
156312
}
157313
}
158314

@@ -162,7 +318,11 @@ pub struct Plane {
162318
point: Vec3,
163319
normal: Vec3,
164320
}
165-
impl Primitive3d for Plane {}
321+
impl Primitive3d for Plane {
322+
fn outside_plane(&self, plane: Plane) -> bool {
323+
self.normal == plane.normal && self.distance_to_point(plane.point()) > 0.0
324+
}
325+
}
166326
impl Plane {
167327
/// Generate a plane from three points that lie on the plane.
168328
pub fn from_points(points: [Vec3; 3]) -> Plane {
@@ -180,4 +340,20 @@ impl Plane {
180340
normal: normal.normalize(),
181341
}
182342
}
343+
/// Returns the nearest distance from the supplied point to this plane. Positive values are in
344+
/// the direction of the plane's normal (outside), negative values are opposite the direction
345+
/// of the planes normal (inside).
346+
pub fn distance_to_point(&self, point: &Vec3) -> f32 {
347+
self.normal.dot(*point) + -self.normal.dot(self.point)
348+
}
349+
350+
/// Get a reference to the plane's point.
351+
pub fn point(&self) -> &Vec3 {
352+
&self.point
353+
}
354+
355+
/// Get a reference to the plane's normal.
356+
pub fn normal(&self) -> &Vec3 {
357+
&self.normal
358+
}
183359
}

tools/publish.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ crates=(
33
bevy_utils
44
bevy_derive
55
bevy_math
6+
bevy_geometry
67
bevy_tasks
78
bevy_ecs/macros
89
bevy_ecs
@@ -16,7 +17,6 @@ crates=(
1617
bevy_core
1718
bevy_diagnostic
1819
bevy_transform
19-
bevy_geometry
2020
bevy_window
2121
bevy_render
2222
bevy_input

0 commit comments

Comments
 (0)