diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 070483e7776ea..2f7c9390c06d7 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -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::{HitSide, Ray2d, Ray3d}; pub use rects::*; pub use rotation2d::Rot2; @@ -77,9 +77,9 @@ pub mod prelude { ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops, 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, + EulerRot, FloatExt, HitSide, 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, }; #[doc(hidden)] diff --git a/crates/bevy_math/src/ray.rs b/crates/bevy_math/src/ray.rs index 5fe9c3740a846..5a63c6f832b49 100644 --- a/crates/bevy_math/src/ray.rs +++ b/crates/bevy_math/src/ray.rs @@ -55,6 +55,27 @@ impl Ray2d { } } +/// Represents the side of the plane intersected by a ray. +#[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 HitSide { + /// The ray intersected the front side of the plane. + /// (the side from which the plane normal points towards the ray). + Front, + /// The ray intersected the back side of the plane. + /// (the side opposite to the normal). + Back, +} + /// 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))] @@ -87,14 +108,23 @@ impl Ray3d { self.origin + *self.direction * distance } - /// Get the distance to a plane if the ray intersects it + /// Get the distance to a plane and intersection side if the ray intersects it #[inline] - pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option { + pub fn intersect_plane( + &self, + plane_origin: Vec3, + plane: InfinitePlane3d, + ) -> Option<(f32, HitSide)> { 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 { - return Some(distance); + let side = match denominator < 0.0 { + true => HitSide::Front, + false => HitSide::Back, + }; + + return Some((distance, side)); } } None @@ -152,11 +182,11 @@ mod tests { // Orthogonal, and test that an inverse plane_normal has the same result assert_eq!( ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)), - Some(1.0) + Some((1.0, HitSide::Back)) ); assert_eq!( ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)), - Some(1.0) + Some((1.0, HitSide::Front)) ); assert!(ray .intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z)) @@ -168,7 +198,7 @@ mod tests { // Diagonal assert_eq!( ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)), - Some(1.0) + Some((1.0, HitSide::Back)) ); assert!(ray .intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE)) diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs index 883121779a635..4cc5e1c539e0e 100644 --- a/examples/3d/3d_viewport_to_world.rs +++ b/examples/3d/3d_viewport_to_world.rs @@ -22,7 +22,7 @@ fn draw_cursor( // Calculate a ray pointing from the camera into the world based on the cursor's 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. - && let Some(distance) = + && let Some((distance, _)) = ray.intersect_plane(ground.translation(), InfinitePlane3d::new(ground.up())) { let point = ray.get_point(distance); diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index 80373512db984..40cf9a6953126 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -466,7 +466,8 @@ 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)) + else { return; }; let plane_intersection = ray.origin + ray.direction.normalize() * ray_distance;