use crate::vec3::{self, Vec3};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Ray3 {
pub origin: Vec3,
pub dir: Vec3,
}
impl Ray3 {
#[inline]
pub fn point_at(&self, t: f64) -> Vec3 {
vec3::add(self.origin, vec3::scale(self.dir, t))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Plane3 {
pub point: Vec3,
pub normal: Vec3,
}
impl Plane3 {
#[inline]
pub fn horizontal(z: f64) -> Self {
Self {
point: [0.0, 0.0, z],
normal: [0.0, 0.0, 1.0],
}
}
#[inline]
pub fn signed_distance(&self, p: Vec3) -> f64 {
vec3::dot(vec3::sub(p, self.point), self.normal)
}
pub fn intersect_ray(&self, ray: &Ray3) -> Option<f64> {
let denom = vec3::dot(self.normal, ray.dir);
if denom.abs() < 1e-12 {
return None;
}
let t = vec3::dot(vec3::sub(self.point, ray.origin), self.normal) / denom;
if t >= 0.0 {
Some(t)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ray_hits_horizontal_plane() {
let r = Ray3 {
origin: [0.0, 0.0, 5.0],
dir: [0.0, 0.0, -1.0],
};
let p = Plane3::horizontal(0.0);
let t = p.intersect_ray(&r).unwrap();
assert!((t - 5.0).abs() < 1e-12);
assert_eq!(r.point_at(t), [0.0, 0.0, 0.0]);
}
#[test]
fn parallel_ray_misses_plane() {
let r = Ray3 {
origin: [0.0, 0.0, 5.0],
dir: [1.0, 0.0, 0.0],
};
let p = Plane3::horizontal(0.0);
assert!(p.intersect_ray(&r).is_none());
}
#[test]
fn plane_signed_distance() {
let p = Plane3::horizontal(0.0);
assert!((p.signed_distance([0.0, 0.0, 3.0]) - 3.0).abs() < 1e-12);
assert!((p.signed_distance([0.0, 0.0, -2.0]) + 2.0).abs() < 1e-12);
}
}