This commit is contained in:
Mateusz Stulczewski 2025-07-18 17:32:10 +02:00 committed by GitHub
commit 030d34fd00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 157 additions and 17 deletions

View File

@ -55,7 +55,7 @@ pub use direction::*;
pub use float_ord::*; pub use float_ord::*;
pub use isometry::{Isometry2d, Isometry3d}; pub use isometry::{Isometry2d, Isometry3d};
pub use ops::FloatPow; pub use ops::FloatPow;
pub use ray::{Ray2d, Ray3d}; pub use ray::{PlaneIntersectionMode, Ray2d, Ray3d};
pub use rects::*; pub use rects::*;
pub use rotation2d::Rot2; pub use rotation2d::Rot2;
@ -78,8 +78,8 @@ pub mod prelude {
primitives::*, primitives::*,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A, quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2, Mat4, PlaneIntersectionMode, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
}; };
#[doc(hidden)] #[doc(hidden)]

View File

@ -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. /// An infinite half-line starting at `origin` and going in `direction` in 3D space.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
@ -88,12 +111,24 @@ impl Ray3d {
} }
/// Get the distance to a plane if the ray intersects it /// 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] #[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); let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON { if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator; 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); return Some(distance);
} }
} }
@ -146,44 +181,145 @@ mod tests {
} }
#[test] #[test]
fn intersect_plane_3d() { fn intersect_plane_3d_both() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z); let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);
// Orthogonal, and test that an inverse plane_normal has the same result // Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!( 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) Some(1.0)
); );
assert_eq!( 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) Some(1.0)
); );
assert!(ray assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z)) .intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::Z),
PlaneIntersectionMode::Both
)
.is_none()); .is_none());
assert!(ray 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()); .is_none());
// Diagonal // Diagonal
assert_eq!( 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) Some(1.0)
); );
assert!(ray assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE)) .intersect_plane(
Vec3::NEG_Z,
InfinitePlane3d::new(Vec3::ONE),
PlaneIntersectionMode::Both
)
.is_none()); .is_none());
// Parallel // Parallel
assert!(ray assert!(ray
.intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X)) .intersect_plane(
Vec3::X,
InfinitePlane3d::new(Vec3::X),
PlaneIntersectionMode::Both
)
.is_none()); .is_none());
// Parallel with simulated rounding error // Parallel with simulated rounding error
assert!(ray assert!(ray
.intersect_plane( .intersect_plane(
Vec3::X, 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()); .is_none());
} }

View File

@ -23,7 +23,7 @@ fn draw_cursor(
&& let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) && let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position)
// Calculate if and at what distance the ray is hitting the ground plane. // Calculate if and at what distance the ray is hitting the ground plane.
&& let Some(distance) = && 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); let point = ray.get_point(distance);

View File

@ -16,7 +16,7 @@
use bevy::{ use bevy::{
color::palettes::css::*, color::palettes::css::*,
core_pipeline::Skybox, core_pipeline::Skybox,
math::{uvec3, vec3}, math::{uvec3, vec3, PlaneIntersectionMode},
pbr::{ pbr::{
irradiance_volume::IrradianceVolume, ExtendedMaterial, MaterialExtension, NotShadowCaster, irradiance_volume::IrradianceVolume, ExtendedMaterial, MaterialExtension, NotShadowCaster,
}, },
@ -466,7 +466,11 @@ fn handle_mouse_clicks(
let Ok(ray) = camera.viewport_to_world(camera_transform, mouse_position) else { let Ok(ray) = camera.viewport_to_world(camera_transform, mouse_position) else {
return; 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; return;
}; };
let plane_intersection = ray.origin + ray.direction.normalize() * ray_distance; let plane_intersection = ray.origin + ray.direction.normalize() * ray_distance;