use nalgebra::{one, zero, Point3, RealField, Vector3};
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Plane<T>
where
T: RealField,
{
normal: Vector3<T>,
bias: T,
}
impl<T> Plane<T>
where
T: RealField,
{
pub fn new(normal: Vector3<T>, bias: T) -> Self {
Plane { normal, bias }
}
pub fn from_point_normal(point: &Point3<T>, normal: &Vector3<T>) -> Self {
let normalized = normal.normalize();
Self {
normal: Vector3::new(normalized.x, normalized.y, normalized.z),
bias: point.coords.dot(&normalized),
}
}
pub fn from_point_vectors(point: &Point3<T>, v1: &Vector3<T>, v2: &Vector3<T>) -> Self {
Self::from_point_normal(point, &v1.cross(&v2))
}
pub fn with_x(x: T) -> Self {
Self::from_point_normal(
&Point3::new(x, zero(), zero()),
&Vector3::new(one(), zero(), zero()),
)
}
pub fn with_y(y: T) -> Self {
Self::from_point_normal(
&Point3::new(zero(), y, zero()),
&Vector3::new(zero(), one(), zero()),
)
}
pub fn with_z(z: T) -> Self {
Self::from_point_normal(
&Point3::new(zero(), zero(), z),
&Vector3::new(zero(), zero(), one()),
)
}
pub fn normal(&self) -> &Vector3<T> {
&self.normal
}
pub fn normalize(&self) -> Self {
let distance = self.normal.magnitude();
Self {
normal: self.normal / distance,
bias: self.bias / distance,
}
}
pub fn dot_point(&self, point: &Point3<T>) -> T {
self.normal.x * point.x + self.normal.y * point.y + self.normal.z * point.z + self.bias
}
pub fn dot(&self, point: &Vector3<T>) -> T {
self.normal.x * point.x + self.normal.y * point.y + self.normal.z * point.z
}
pub fn dot_plane(&self, plane: &Plane<T>) -> T {
self.normal.x * plane.normal.x
+ self.normal.y * plane.normal.y
+ self.normal.z * plane.normal.z
+ self.bias * plane.bias
}
pub fn intersect_line(&self, point: &Point3<T>, direction: &Vector3<T>) -> Option<T> {
let fv = self.dot(direction);
let distance = self.dot_point(point) / fv;
if fv.abs() > T::min_value() {
Some(distance)
} else {
None
}
}
pub fn intersect_ray(&self, ray: &Ray<T>) -> Option<T> {
self.intersect_line(&ray.origin, &ray.direction)
}
}
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Ray<T>
where
T: RealField,
{
pub origin: Point3<T>,
pub direction: Vector3<T>,
}
impl<T> Ray<T>
where
T: RealField,
{
pub fn intersect_plane(&self, plane: &Plane<T>) -> Option<T> {
plane.intersect_ray(self)
}
pub fn at_distance(&self, z: T) -> Point3<T> {
self.origin - (self.direction * z)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use approx::{assert_ulps_eq, relative_eq};
#[test]
#[allow(clippy::mistyped_literal_suffixes)]
fn ray_intersect_plane() {
let plane = Plane::<f32>::with_z(0.0);
let ray = Ray {
origin: Point3::new(0.020_277_506, -0.033_236_53, 51.794),
direction: Vector3::new(0.179_559_51, -0.294_313_04, -0.938_689_65),
};
let distance = ray.intersect_plane(&plane).unwrap();
let point = ray.at_distance(distance);
assert_ulps_eq!(point, Point3::new(9.927_818, -16.272_524, 0.0));
let ray = Ray {
origin: Point3::new(-0.003_106_177, 0.034_074_64, 0.799_999_95),
direction: Vector3::new(-0.029_389_05, 0.322_396_73, -0.946_148_3),
};
let distance = ray.intersect_plane(&plane).unwrap();
let point = ray.at_distance(distance);
assert_ulps_eq!(point, Point3::new(-0.027_955_6, 0.306_671_83, 0.0));
}
#[test]
fn at_distance() {
relative_eq!(
Ray {
origin: Point3::new(0.020_277_506, -0.033_236_53, 51.794),
direction: Vector3::new(0.179_559_51, -0.294_313_04, -0.938_689_65),
}
.at_distance(5.0),
Point3::new(0.918_075_1, -1.504_801_8, 47.100_55)
);
}
}