Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Fixed the frustum-sphere collision and added tests #4035

Closed
wants to merge 5 commits into from
Closed
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 175 additions & 9 deletions crates/bevy_render/src/primitives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::{Mat4, Vec3, Vec3A, Vec4};
use bevy_math::{Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
use bevy_reflect::Reflect;

/// An Axis-Aligned Bounding Box
Expand Down Expand Up @@ -72,14 +72,26 @@ impl Sphere {
}
}

/// A plane defined by a normal and distance value along the normal
/// A plane defined by a normalized normal and distance value along the normal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A plane defined by a normalized normal and distance value along the normal
/// A plane defined by a unit normal and distance from the origin along the normal

/// Any point p is in the plane if n.p = d
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Any point p is in the plane if n.p = d
/// Any point p is in the plane if n.p + d = 0

/// For planes defining half-spaces such as for frusta, if n.p > d then p is on the positive side of the plane.
/// For planes defining half-spaces such as for frusta, if n.p > d then p is on the positive side (inside) of the plane.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// For planes defining half-spaces such as for frusta, if n.p > d then p is on the positive side (inside) of the plane.
/// For planes defining half-spaces such as for frusta, if n.p + d > 0 then p is on
/// the positive side (inside) of the plane.

#[derive(Clone, Copy, Debug, Default)]
pub struct Plane {
pub normal_d: Vec4,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub normal_d: Vec4,
normal_d: Vec4,

And then update other code that uses Plane to use Plane::new for construction.

}

impl Plane {
/// Constructs a `Plane` from a 4D vector whose first 3 components
/// are the normal and whose last component is d.
/// Ensures that the normal is normalized and d is scaled accordingly
/// so it represents the signed distance from the origin.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Constructs a `Plane` from a 4D vector whose first 3 components
/// are the normal and whose last component is d.
/// Ensures that the normal is normalized and d is scaled accordingly
/// so it represents the signed distance from the origin.
/// Constructs a `Plane` from a 4D vector whose first 3 components
/// are the normal and whose last component is the distance along the normal
/// from the origin.
/// This constructor ensures that the normal is normalized and the distance is
/// scaled accordingly so it represents the signed distance from the origin.

fn new(normal_d: Vec4) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn new(normal_d: Vec4) -> Self {
pub fn new(normal_d: Vec4) -> Self {

Self {
normal_d: normal_d * normal_d.xyz().length_recip(),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}
/// `Plane` unit normal
#[inline]
pub fn normal(&self) -> Vec3 {
self.normal_d.xyz()
}
/// Signed distance from the origin along the unit normal such that n.p + d = 0 for point p in
/// the `Plane`
#[inline]
pub fn d(&self) -> f32 {
self.normal_d.w
}
/// `Plane` unit normal and signed distance from the origin such that n.p + d = 0 for point p
/// in the `Plane`
#[inline]
pub fn normal_d(&self) -> Vec4 {
self.normal_d
}

And then use these in the Frustum intersects sphere/obb functions below.

}

#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct Frustum {
Expand All @@ -102,17 +114,14 @@ impl Frustum {
let mut planes = [Plane::default(); 6];
for (i, plane) in planes.iter_mut().enumerate().take(5) {
let row = view_projection.row(i / 2);
plane.normal_d = if (i & 1) == 0 && i != 4 {
*plane = Plane::new(if (i & 1) == 0 && i != 4 {
row3 + row
} else {
row3 - row
}
.normalize();
});
}
let far_center = *view_translation - far * *view_backward;
planes[5].normal_d = view_backward
.extend(-view_backward.dot(far_center))
.normalize();
planes[5] = Plane::new(view_backward.extend(-view_backward.dot(far_center)));
Self { planes }
}

Expand Down Expand Up @@ -159,3 +168,160 @@ impl CubemapFrusta {
self.frusta.iter_mut()
}
}

#[cfg(test)]
mod tests {
use super::*;

// A big, offset frustum
fn big_frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 7.7611)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 2.9104)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)),
],
}
}

#[test]
fn intersects_sphere_big_frustum_outside() {
// Sphere outside frustum
let frustum = big_frustum();
let sphere = Sphere {
center: Vec3::new(0.9167, 0.0000, 0.0000),
radius: 0.7500,
};
assert!(!frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_big_frustum_intersect() {
// Sphere intersects frustum boundary
let frustum = big_frustum();
let sphere = Sphere {
center: Vec3::new(7.9288, 0.0000, 2.9728),
radius: 2.0000,
};
assert!(frustum.intersects_sphere(&sphere));
}

// A frustum
fn frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)),
],
}
}

#[test]
fn intersects_sphere_frustum_surrounding() {
// Sphere surrounds frustum
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(0.0000, 0.0000, 0.0000),
radius: 3.0000,
};
assert!(frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_frustum_contained() {
// Sphere is contained in frustum
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(0.0000, 0.0000, 0.0000),
radius: 0.7000,
};
assert!(frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_frustum_intersects_plane() {
// Sphere intersects a plane
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(0.0000, 0.0000, 0.9695),
radius: 0.7000,
};
assert!(frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_frustum_intersects_2_planes() {
// Sphere intersects 2 planes
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(1.2037, 0.0000, 0.9695),
radius: 0.7000,
};
assert!(frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_frustum_intersects_3_planes() {
// Sphere intersects 3 planes
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(1.2037, -1.0988, 0.9695),
radius: 0.7000,
};
assert!(frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_frustum_dodges_1_plane() {
// Sphere avoids intersecting the frustum by 1 plane
let frustum = frustum();
let sphere = Sphere {
center: Vec3::new(-1.7020, 0.0000, 0.0000),
radius: 0.7000,
};
assert!(!frustum.intersects_sphere(&sphere));
}

// A long frustum.
fn long_frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9998, -0.0222, -0.0000, -1.9543)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, -0.9999, 2.2718)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)),
Plane::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)),
],
}
}

#[test]
fn intersects_sphere_long_frustum_outside() {
// Sphere outside frustum
let frustum = long_frustum();
let sphere = Sphere {
center: Vec3::new(-4.4889, 46.9021, 0.0000),
radius: 0.7500,
};
assert!(!frustum.intersects_sphere(&sphere));
}

#[test]
fn intersects_sphere_long_frustum_intersect() {
// Sphere intersects frustum boundary
let frustum = long_frustum();
let sphere = Sphere {
center: Vec3::new(-4.9957, 0.0000, -0.7396),
radius: 4.4094,
};
assert!(frustum.intersects_sphere(&sphere));
}
}