Skip to main content

rustsim_geometry/
ray.rs

1//! Rays and planes in 3-D.
2
3use crate::vec3::{self, Vec3};
4
5/// 3-D ray with an origin and a (not necessarily unit) direction.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Ray3 {
8    /// Origin.
9    pub origin: Vec3,
10    /// Direction (does not need to be unit length).
11    pub dir: Vec3,
12}
13
14impl Ray3 {
15    /// Point at parameter `t` along the ray.
16    #[inline]
17    pub fn point_at(&self, t: f64) -> Vec3 {
18        vec3::add(self.origin, vec3::scale(self.dir, t))
19    }
20}
21
22/// Infinite plane defined by a point on the plane and a (unit) normal.
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct Plane3 {
25    /// A point on the plane.
26    pub point: Vec3,
27    /// Unit normal.
28    pub normal: Vec3,
29}
30
31impl Plane3 {
32    /// Constructs a horizontal plane at height `z`.
33    #[inline]
34    pub fn horizontal(z: f64) -> Self {
35        Self {
36            point: [0.0, 0.0, z],
37            normal: [0.0, 0.0, 1.0],
38        }
39    }
40
41    /// Signed distance from `p` to the plane (positive on normal side).
42    #[inline]
43    pub fn signed_distance(&self, p: Vec3) -> f64 {
44        vec3::dot(vec3::sub(p, self.point), self.normal)
45    }
46
47    /// Intersect with a ray. Returns the non-negative `t` of intersection
48    /// or `None` if parallel or behind the ray origin.
49    pub fn intersect_ray(&self, ray: &Ray3) -> Option<f64> {
50        let denom = vec3::dot(self.normal, ray.dir);
51        if denom.abs() < 1e-12 {
52            return None;
53        }
54        let t = vec3::dot(vec3::sub(self.point, ray.origin), self.normal) / denom;
55        if t >= 0.0 {
56            Some(t)
57        } else {
58            None
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn ray_hits_horizontal_plane() {
69        let r = Ray3 {
70            origin: [0.0, 0.0, 5.0],
71            dir: [0.0, 0.0, -1.0],
72        };
73        let p = Plane3::horizontal(0.0);
74        let t = p.intersect_ray(&r).unwrap();
75        assert!((t - 5.0).abs() < 1e-12);
76        assert_eq!(r.point_at(t), [0.0, 0.0, 0.0]);
77    }
78
79    #[test]
80    fn parallel_ray_misses_plane() {
81        let r = Ray3 {
82            origin: [0.0, 0.0, 5.0],
83            dir: [1.0, 0.0, 0.0],
84        };
85        let p = Plane3::horizontal(0.0);
86        assert!(p.intersect_ray(&r).is_none());
87    }
88
89    #[test]
90    fn plane_signed_distance() {
91        let p = Plane3::horizontal(0.0);
92        assert!((p.signed_distance([0.0, 0.0, 3.0]) - 3.0).abs() < 1e-12);
93        assert!((p.signed_distance([0.0, 0.0, -2.0]) + 2.0).abs() < 1e-12);
94    }
95}