1use crate::vec::{Point3, Vec3};
4
5#[must_use]
14pub fn plane_plane_intersection(
15 n1: Vec3,
16 d1: f64,
17 n2: Vec3,
18 d2: f64,
19 tolerance: f64,
20) -> Option<(Point3, Vec3)> {
21 let dir = n1.cross(n2);
22 let det = dir.length_squared();
23
24 if det < tolerance * tolerance {
25 return None;
26 }
27
28 let n1n1 = n1.dot(n1);
36 let n2n2 = n2.dot(n2);
37 let n1n2 = n1.dot(n2);
38
39 let a = d1.mul_add(n2n2, -(d2 * n1n2)) / det;
40 let b = d2.mul_add(n1n1, -(d1 * n1n2)) / det;
41
42 let point = Point3::new(
43 n1.x().mul_add(a, n2.x() * b),
44 n1.y().mul_add(a, n2.y() * b),
45 n1.z().mul_add(a, n2.z() * b),
46 );
47
48 let unit_dir = dir * (1.0 / det.sqrt());
50
51 Some((point, unit_dir))
52}
53
54#[cfg(test)]
55#[allow(clippy::expect_used)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn xy_xz_intersection() {
61 let (pt, dir) = plane_plane_intersection(
63 Vec3::new(0.0, 0.0, 1.0),
64 0.0,
65 Vec3::new(0.0, 1.0, 0.0),
66 0.0,
67 1e-10,
68 )
69 .expect("planes should intersect");
70
71 assert!(pt.z().abs() < 1e-10);
73 assert!(pt.y().abs() < 1e-10);
74
75 assert!(dir.x().abs() > 0.99);
77 assert!(dir.y().abs() < 1e-10);
78 assert!(dir.z().abs() < 1e-10);
79 }
80
81 #[test]
82 fn offset_planes() {
83 let (pt, dir) = plane_plane_intersection(
85 Vec3::new(0.0, 0.0, 1.0),
86 1.0,
87 Vec3::new(0.0, 1.0, 0.0),
88 2.0,
89 1e-10,
90 )
91 .expect("planes should intersect");
92
93 assert!((pt.z() - 1.0).abs() < 1e-10);
94 assert!((pt.y() - 2.0).abs() < 1e-10);
95 assert!(dir.x().abs() > 0.99);
96 }
97
98 #[test]
99 fn parallel_planes_return_none() {
100 let result = plane_plane_intersection(
102 Vec3::new(0.0, 0.0, 1.0),
103 0.0,
104 Vec3::new(0.0, 0.0, 1.0),
105 1.0,
106 1e-10,
107 );
108 assert!(result.is_none());
109 }
110}