1use crate::vec3::{self, Vec3};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Ray3 {
8 pub origin: Vec3,
10 pub dir: Vec3,
12}
13
14impl Ray3 {
15 #[inline]
17 pub fn point_at(&self, t: f64) -> Vec3 {
18 vec3::add(self.origin, vec3::scale(self.dir, t))
19 }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct Plane3 {
25 pub point: Vec3,
27 pub normal: Vec3,
29}
30
31impl Plane3 {
32 #[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 #[inline]
43 pub fn signed_distance(&self, p: Vec3) -> f64 {
44 vec3::dot(vec3::sub(p, self.point), self.normal)
45 }
46
47 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}