Skip to content

Add optional backface/frontface culling to Ray3d::intersect_plane #20166

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions crates/bevy_math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub use direction::*;
pub use float_ord::*;
pub use isometry::{Isometry2d, Isometry3d};
pub use ops::FloatPow;
pub use ray::{Ray2d, Ray3d};
pub use ray::{PlaneIntersectionMode, Ray2d, Ray3d};
pub use rects::*;
pub use rotation2d::Rot2;

Expand All @@ -78,8 +78,8 @@ pub mod prelude {
primitives::*,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
Mat4, PlaneIntersectionMode, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect,
UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
};

#[doc(hidden)]
Expand Down
158 changes: 147 additions & 11 deletions crates/bevy_math/src/ray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ impl Ray2d {
}
}

/// Controls which faces of the plane a ray can intersect.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum PlaneIntersectionMode {
/// Intersects only the front face of the plane
/// (the side from which the plane normal points towards the ray).
FrontFaceOnly,
/// Intersects only the back face of the plane
/// (the side opposite to the normal).
BackFaceOnly,
/// Intersects both faces of the plane.
Both,
}

/// An infinite half-line starting at `origin` and going in `direction` in 3D space.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -88,12 +111,24 @@ impl Ray3d {
}

/// Get the distance to a plane if the ray intersects it
/// `plane_hit_mode` specifies which faces of the plane a ray can intersect
#[inline]
pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option<f32> {
pub fn intersect_plane(
&self,
plane_origin: Vec3,
plane: InfinitePlane3d,
plane_hit_mode: PlaneIntersectionMode,
) -> Option<f32> {
let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
if distance > f32::EPSILON {
if distance > f32::EPSILON
&& match plane_hit_mode {
PlaneIntersectionMode::Both => true,
PlaneIntersectionMode::FrontFaceOnly => denominator < 0.0,
PlaneIntersectionMode::BackFaceOnly => denominator > 0.0,
}
{
return Some(distance);
}
}
Expand Down Expand Up @@ -146,44 +181,145 @@ mod tests {
}

#[test]
fn intersect_plane_3d() {
fn intersect_plane_3d_both() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);

// Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)),
ray.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::Both
),
Some(1.0)
);
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)),
ray.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::Both
),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z))
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::Both
)
.is_none());
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::NEG_Z))
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::Both
)
.is_none());

// Diagonal
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)),
ray.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::ONE),
PlaneIntersectionMode::Both
),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE))
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::ONE),
PlaneIntersectionMode::Both
)
.is_none());

// Parallel
assert!(ray
.intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
.intersect_plane(
Vec3::X,
InfinitePlane3d::new(Vec3::X),
PlaneIntersectionMode::Both
)
.is_none());

// Parallel with simulated rounding error
assert!(ray
.intersect_plane(
Vec3::X,
InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON),
PlaneIntersectionMode::Both
)
.is_none());
}

#[test]
fn intersect_plane_3d_only_front() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);

// Orthogonal, and test that ray intersects only the front face
assert!(ray
.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::FrontFaceOnly
)
.is_none());
assert_eq!(
ray.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::FrontFaceOnly
),
Some(1.0)
);
assert!(ray
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::FrontFaceOnly
)
.is_none());
assert!(ray
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::FrontFaceOnly
)
.is_none());
}

#[test]
fn intersect_plane_3d_only_back() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);

// Orthogonal, and test that ray intersects only the back face
assert_eq!(
ray.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::BackFaceOnly
),
Some(1.0)
);
assert!(ray
.intersect_plane(
Vec3::Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::BackFaceOnly
)
.is_none());
assert!(ray
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::BackFaceOnly
)
.is_none());
assert!(ray
.intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::NEG_Z),
PlaneIntersectionMode::BackFaceOnly
)
.is_none());
}
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/3d_viewport_to_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn draw_cursor(
&& let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position)
// Calculate if and at what distance the ray is hitting the ground plane.
&& let Some(distance) =
ray.intersect_plane(ground.translation(), InfinitePlane3d::new(ground.up()))
ray.intersect_plane(ground.translation(), InfinitePlane3d::new(ground.up()), PlaneIntersectionMode::Both)
{
let point = ray.get_point(distance);

Expand Down
8 changes: 6 additions & 2 deletions examples/3d/irradiance_volumes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use bevy::{
color::palettes::css::*,
core_pipeline::Skybox,
math::{uvec3, vec3},
math::{uvec3, vec3, PlaneIntersectionMode},
pbr::{
irradiance_volume::IrradianceVolume, ExtendedMaterial, MaterialExtension, NotShadowCaster,
},
Expand Down Expand Up @@ -466,7 +466,11 @@ fn handle_mouse_clicks(
let Ok(ray) = camera.viewport_to_world(camera_transform, mouse_position) else {
return;
};
let Some(ray_distance) = ray.intersect_plane(Vec3::ZERO, InfinitePlane3d::new(Vec3::Y)) else {
let Some(ray_distance) = ray.intersect_plane(
Vec3::ZERO,
InfinitePlane3d::new(Vec3::Y),
PlaneIntersectionMode::Both,
) else {
return;
};
let plane_intersection = ray.origin + ray.direction.normalize() * ray_distance;
Expand Down
Loading