1use glam::{Vec3, Vec4};
2
3pub const EDGE_PARALLEL_EPSILON: f32 = 1e-6;
5
6pub const SLICE_PERTURBATION_EPSILON: f32 = 1e-5;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct WPlane {
11 pub w_slice: f32,
12}
13
14impl WPlane {
15 pub const fn new(w_slice: f32) -> Self {
16 Self { w_slice }
17 }
18}
19
20impl WPlane {
21 pub fn intersect_edge(&self, p0: Vec4, p1: Vec4) -> Option<(f32, Vec3)> {
23 let dw = p1.w - p0.w;
24 if dw.abs() < EDGE_PARALLEL_EPSILON {
25 return None;
26 }
27 let t = (self.w_slice - p0.w) / dw;
28 if !(0.0..=1.0).contains(&t) {
29 return None;
30 }
31
32 let p = p0 + t * (p1 - p0);
33 Some((t, Vec3::new(p.x, p.y, p.z)))
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn r4_edge_section_midpoint() {
43 let slice = WPlane::new(0.0);
44 let p0 = Vec4::new(1.0, 2.0, 3.0, -1.0);
45 let p1 = Vec4::new(5.0, 6.0, 7.0, 1.0);
46 let (t, p3) = slice.intersect_edge(p0, p1).unwrap();
47 assert!((t - 0.5).abs() < 1e-6);
48 assert_eq!(p3, Vec3::new(3.0, 4.0, 5.0));
49 }
50
51 #[test]
52 fn r4_edge_section_no_crossing_returns_none() {
53 let slice = WPlane::new(0.0);
54 let p0 = Vec4::new(0.0, 0.0, 0.0, 0.5);
55 let p1 = Vec4::new(0.0, 0.0, 0.0, 1.5);
56 assert!(slice.intersect_edge(p0, p1).is_none());
57 }
58
59 #[test]
60 fn r4_edge_section_parallel_edge_returns_none() {
61 let slice = WPlane::new(0.0);
62 let p0 = Vec4::new(0.0, 0.0, 0.0, 0.0);
63 let p1 = Vec4::new(1.0, 1.0, 1.0, 1e-7);
64 assert!(slice.intersect_edge(p0, p1).is_none());
65 }
66
67 #[test]
68 fn r4_edge_section_endpoint_on_slice_returns_t_zero() {
69 let slice = WPlane::new(0.0);
70 let p0 = Vec4::new(2.0, 2.0, 2.0, 0.0);
71 let p1 = Vec4::new(5.0, 5.0, 5.0, 1.0);
72 let (t, p3) = slice.intersect_edge(p0, p1).unwrap();
73 assert!(t.abs() < 1e-6);
74 assert_eq!(p3, Vec3::new(2.0, 2.0, 2.0));
75 }
76}