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 2 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
224 changes: 222 additions & 2 deletions crates/bevy_render/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl Sphere {

/// A plane defined by a normal and distance value 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.

Expand Down Expand Up @@ -118,7 +118,12 @@ impl Frustum {

pub fn intersects_sphere(&self, sphere: &Sphere) -> bool {
for plane in &self.planes {
if plane.normal_d.dot(sphere.center.extend(1.0)) + sphere.radius <= 0.0 {
// The formula `normal . center + d + radius <= 0` relies on `normal` being normalized,
// which is not necessarily the case.
let factor = (plane.normal_d.truncate().length_squared()
Copy link
Member

Choose a reason for hiding this comment

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

Please split up this statement. It's hard to read in it's current state.

/ plane.normal_d.length_squared())
.sqrt();
if plane.normal_d.dot(sphere.center.extend(1.0)) + sphere.radius * factor <= 0.0 {
return false;
}
}
Expand Down Expand Up @@ -159,3 +164,218 @@ impl CubemapFrusta {
self.frusta.iter_mut()
}
}

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

fn big_frustum() -> Frustum {
Frustum {
planes: [
Plane {
normal_d: Vec4::new(-0.2425, -0.0606, -0.0000, 1.9403).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, 0.2500, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.0606, -0.2425, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.2500, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.0606, 0.2425, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(0.2425, -0.0606, -0.0000, -0.4851).normalize(),
},
],
}
}

#[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 {
normal_d: Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(0.9701, -0.2425, -0.0000, 0.7276).normalize(),
},
],
}
}

#[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));
}

// These tests are not handled, and it may not be worth handling them.
//#[test]
//fn intersects_sphere_frustum_dodges_2_planes() {
// // Sphere avoids intersecting the frustum by a combination of 2 planes
// let frustum = frustum();
// let sphere = Sphere {
// center: Vec3::new(-1.6048, -1.5564, 0.0000),
// radius: 0.7000,
// };
// assert!(!frustum.intersects_sphere(&sphere));
//}

//#[test]
//fn intersects_sphere_frustum_dodges_3_planes() {
// // Sphere avoids intersecting the frustum by a combination of 3 planes
// let frustum = frustum();
// let sphere = Sphere {
// center: Vec3::new(-1.3059, -1.5564, -1.4264),
// radius: 0.7000,
// };
// assert!(!frustum.intersects_sphere(&sphere));
//}
hayashi-stl marked this conversation as resolved.
Show resolved Hide resolved

// A long frustum.
fn long_frustum() -> Frustum {
Frustum {
planes: [
Plane {
normal_d: Vec4::new(-0.2425, -0.0054, -0.0000, -0.4741).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, 0.0222, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.0054, -0.3202, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.0222, -0.0000, 1.0000).normalize(),
},
Plane {
normal_d: Vec4::new(-0.0000, -0.0054, 0.3202, 0.7276).normalize(),
},
Plane {
normal_d: Vec4::new(0.2425, -0.0054, -0.0000, 1.9293).normalize(),
},
],
}
}

#[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));
}
}