use phys_geom::math::*;
use phys_geom::shape::InfinitePlane;
use crate::{Raycast, RaycastHitResult};
pub trait InfinitePlaneExt {
fn distance(&self, p: Point3) -> Real;
}
fn normal() -> UnitVec3 {
Vec3::y_axis()
}
impl InfinitePlaneExt for InfinitePlane {
#[inline]
fn distance(&self, p: Point3) -> Real {
p.coords.dot(&normal().into_inner())
}
}
impl Raycast for InfinitePlane {
fn raycast(
&self,
local_ray: phys_geom::Ray,
max_distance: Real,
discard_inside_hit: bool,
) -> Option<RaycastHitResult> {
let origin_to_plane_distance = self.distance(local_ray.origin);
if origin_to_plane_distance.abs() <= Real::EPSILON && !discard_inside_hit {
return Some(RaycastHitResult {
distance: 0.0,
normal: normal(),
});
}
let dn = local_ray.direction.dot(&normal().into_inner());
if (-Real::EPSILON < dn) && (dn < Real::EPSILON) {
return None;
}
let hit_distance = -origin_to_plane_distance / dn;
if hit_distance < 0.0 {
return None;
}
if hit_distance > 0.0 && hit_distance <= max_distance {
if origin_to_plane_distance > 0.0 {
Some(RaycastHitResult {
distance: hit_distance,
normal: normal(),
})
} else if discard_inside_hit {
None
} else {
Some(RaycastHitResult {
distance: hit_distance,
normal: -normal(),
})
}
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_raycast_infinite_plane() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 2.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, -1.0, 0.0)),
);
let hit = plane.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 2.0);
assert_relative_eq!(hit.normal, Vec3::y_axis());
}
#[test]
fn test_raycast_infinite_plane_from_below() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, -2.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, 1.0, 0.0)),
);
let hit = plane.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 2.0);
assert_relative_eq!(
hit.normal,
UnitVec3::new_normalize(Vec3::new(0.0, -1.0, 0.0))
);
}
#[test]
fn test_raycast_infinite_plane_from_below_discarded() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, -2.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, 1.0, 0.0)),
);
assert_eq!(plane.raycast(ray, 10.0, true), None);
}
#[test]
fn test_raycast_infinite_plane_parallel() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 2.0, 0.0),
UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
);
assert_eq!(plane.raycast(ray, 10.0, false), None);
}
#[test]
fn test_raycast_infinite_plane_on_plane() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, 1.0, 0.0)),
);
let hit = plane.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 0.0);
assert_relative_eq!(hit.normal, Vec3::y_axis());
}
#[test]
fn test_raycast_infinite_plane_on_plane_discarded() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, 1.0, 0.0)),
);
assert_eq!(plane.raycast(ray, 10.0, true), None);
}
#[test]
fn test_raycast_infinite_plane_away_from_plane() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 2.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, 1.0, 0.0)),
);
assert_eq!(plane.raycast(ray, 10.0, false), None);
}
#[test]
fn test_raycast_infinite_plane_max_distance() {
let plane = InfinitePlane::default();
let ray = phys_geom::Ray::new(
Point3::new(0.0, 5.0, 0.0),
UnitVec3::new_normalize(Vec3::new(0.0, -1.0, 0.0)),
);
assert_eq!(plane.raycast(ray, 1.0, false), None);
let hit = plane.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 5.0);
}
#[test]
fn test_infinite_plane_distance() {
let plane = InfinitePlane::default();
assert_relative_eq!(plane.distance(Point3::new(0.0, 3.0, 0.0)), 3.0);
assert_relative_eq!(plane.distance(Point3::new(0.0, 0.0, 0.0)), 0.0);
assert_relative_eq!(plane.distance(Point3::new(0.0, -3.0, 0.0)), -3.0);
}
}