1use crate::math::sqrt;
11
12#[derive(Copy, Clone, Debug)]
14pub struct Plane {
15 pub normal: [f32; 3],
17 pub d: f32,
19}
20
21#[derive(Copy, Clone, Debug)]
23pub struct Frustum {
24 pub planes: [Plane; 6],
26}
27
28impl Frustum {
29 pub fn from_view_projection(vp: [[f32; 4]; 4]) -> Self {
32 let row = |r: usize| -> [f32; 4] { [vp[0][r], vp[1][r], vp[2][r], vp[3][r]] };
34 let r0 = row(0);
35 let r1 = row(1);
36 let r2 = row(2);
37 let r3 = row(3);
38
39 let make = |a: [f32; 4], b: [f32; 4], sign: f32| -> Plane {
40 let p = [
41 a[0] * sign + b[0],
42 a[1] * sign + b[1],
43 a[2] * sign + b[2],
44 a[3] * sign + b[3],
45 ];
46 normalise_plane(p)
47 };
48
49 Self {
50 planes: [
51 make(r0, r3, 1.0), make(r0, r3, -1.0), make(r1, r3, 1.0), make(r1, r3, -1.0), make(r2, r3, 1.0), make(r2, r3, -1.0), ],
58 }
59 }
60
61 pub fn intersects_aabb(&self, bb_min: [f32; 3], bb_max: [f32; 3]) -> bool {
63 for plane in &self.planes {
64 let mut farthest = [0.0f32; 3];
68 for (i, n) in plane.normal.iter().enumerate() {
69 farthest[i] = if *n >= 0.0 { bb_max[i] } else { bb_min[i] };
70 }
71 let dist = plane.normal[0] * farthest[0]
72 + plane.normal[1] * farthest[1]
73 + plane.normal[2] * farthest[2]
74 + plane.d;
75 if dist < 0.0 {
76 return false;
77 }
78 }
79 true
80 }
81}
82
83fn normalise_plane(p: [f32; 4]) -> Plane {
84 let len = sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
85 let inv = if len > 1e-6 { 1.0 / len } else { 1.0 };
86 Plane {
87 normal: [p[0] * inv, p[1] * inv, p[2] * inv],
88 d: p[3] * inv,
89 }
90}
91
92pub fn transform_aabb(
96 bb_min: [f32; 3],
97 bb_max: [f32; 3],
98 model: [[f32; 4]; 4],
99) -> ([f32; 3], [f32; 3]) {
100 let corners = [
101 [bb_min[0], bb_min[1], bb_min[2]],
102 [bb_max[0], bb_min[1], bb_min[2]],
103 [bb_min[0], bb_max[1], bb_min[2]],
104 [bb_max[0], bb_max[1], bb_min[2]],
105 [bb_min[0], bb_min[1], bb_max[2]],
106 [bb_max[0], bb_min[1], bb_max[2]],
107 [bb_min[0], bb_max[1], bb_max[2]],
108 [bb_max[0], bb_max[1], bb_max[2]],
109 ];
110 let mut out_min = [f32::INFINITY; 3];
111 let mut out_max = [f32::NEG_INFINITY; 3];
112 for c in &corners {
113 let x = model[0][0] * c[0] + model[1][0] * c[1] + model[2][0] * c[2] + model[3][0];
115 let y = model[0][1] * c[0] + model[1][1] * c[1] + model[2][1] * c[2] + model[3][1];
116 let z = model[0][2] * c[0] + model[1][2] * c[1] + model[2][2] * c[2] + model[3][2];
117 out_min[0] = out_min[0].min(x);
118 out_min[1] = out_min[1].min(y);
119 out_min[2] = out_min[2].min(z);
120 out_max[0] = out_max[0].max(x);
121 out_max[1] = out_max[1].max(y);
122 out_max[2] = out_max[2].max(z);
123 }
124 (out_min, out_max)
125}
126
127pub fn aabb_distance_sq(cam: [f32; 3], bb_min: [f32; 3], bb_max: [f32; 3]) -> f32 {
130 let mut sq = 0.0f32;
131 for i in 0..3 {
132 let v = cam[i];
133 if v < bb_min[i] {
134 let d = bb_min[i] - v;
135 sq += d * d;
136 } else if v > bb_max[i] {
137 let d = v - bb_max[i];
138 sq += d * d;
139 }
140 }
141 sq
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn identity4() -> [[f32; 4]; 4] {
149 [
150 [1.0, 0.0, 0.0, 0.0],
151 [0.0, 1.0, 0.0, 0.0],
152 [0.0, 0.0, 1.0, 0.0],
153 [0.0, 0.0, 0.0, 1.0],
154 ]
155 }
156
157 #[test]
158 fn identity_vp_contains_origin_aabb() {
159 let f = Frustum::from_view_projection(identity4());
161 assert!(f.intersects_aabb([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]));
162 }
163
164 #[test]
165 fn identity_vp_rejects_far_aabb() {
166 let f = Frustum::from_view_projection(identity4());
167 assert!(!f.intersects_aabb([5.0, -0.5, -0.5], [6.0, 0.5, 0.5]));
169 }
170
171 #[test]
172 fn transform_aabb_identity_passthrough() {
173 let (mn, mx) = transform_aabb([0.0, 0.0, 0.0], [1.0, 2.0, 3.0], identity4());
174 assert_eq!(mn, [0.0, 0.0, 0.0]);
175 assert_eq!(mx, [1.0, 2.0, 3.0]);
176 }
177
178 #[test]
179 fn transform_aabb_translates_corners() {
180 let mut model = identity4();
181 model[3][0] = 5.0;
182 model[3][1] = -2.0;
183 let (mn, mx) = transform_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], model);
184 assert_eq!(mn, [5.0, -2.0, 0.0]);
185 assert_eq!(mx, [6.0, -1.0, 1.0]);
186 }
187
188 #[test]
189 fn aabb_distance_inside_is_zero() {
190 let d = aabb_distance_sq([0.5, 0.5, 0.5], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
191 assert_eq!(d, 0.0);
192 }
193
194 #[test]
195 fn aabb_distance_outside_is_squared() {
196 let d = aabb_distance_sq([4.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
198 assert!((d - 9.0).abs() < 1e-5);
199 }
200}