1#[derive(Clone, Debug)]
3pub struct Frustum {
4 pub planes: [[f32; 4]; 6],
6}
7
8impl Frustum {
9 pub fn from_view_proj(view_proj: &[[f32; 4]; 4]) -> Self {
11 let mut planes = [[0.0f32; 4]; 6];
12 let m = view_proj;
13
14 planes[0] = [
16 m[0][3] + m[0][0],
17 m[1][3] + m[1][0],
18 m[2][3] + m[2][0],
19 m[3][3] + m[3][0],
20 ];
21 planes[1] = [
23 m[0][3] - m[0][0],
24 m[1][3] - m[1][0],
25 m[2][3] - m[2][0],
26 m[3][3] - m[3][0],
27 ];
28 planes[2] = [
30 m[0][3] - m[0][1],
31 m[1][3] - m[1][1],
32 m[2][3] - m[2][1],
33 m[3][3] - m[3][1],
34 ];
35 planes[3] = [
37 m[0][3] + m[0][1],
38 m[1][3] + m[1][1],
39 m[2][3] + m[2][1],
40 m[3][3] + m[3][1],
41 ];
42 planes[4] = [
44 m[0][3] + m[0][2],
45 m[1][3] + m[1][2],
46 m[2][3] + m[2][2],
47 m[3][3] + m[3][2],
48 ];
49 planes[5] = [
51 m[0][3] - m[0][2],
52 m[1][3] - m[1][2],
53 m[2][3] - m[2][2],
54 m[3][3] - m[3][2],
55 ];
56
57 for plane in &mut planes {
59 let len = (plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]).sqrt();
60 if len > 0.0 {
61 plane[0] /= len;
62 plane[1] /= len;
63 plane[2] /= len;
64 plane[3] /= len;
65 }
66 }
67
68 Self { planes }
69 }
70
71 pub fn intersects_aabb(&self, min: &[f32; 3], max: &[f32; 3]) -> bool {
73 for plane in &self.planes {
74 let px = if plane[0] > 0.0 { max[0] } else { min[0] };
76 let py = if plane[1] > 0.0 { max[1] } else { min[1] };
77 let pz = if plane[2] > 0.0 { max[2] } else { min[2] };
78
79 if plane[0] * px + plane[1] * py + plane[2] * pz + plane[3] < 0.0 {
81 return false;
82 }
83 }
84 true
85 }
86
87 pub fn intersects_sphere(&self, center: &[f32; 3], radius: f32) -> bool {
89 for plane in &self.planes {
90 let dist = plane[0] * center[0] + plane[1] * center[1] + plane[2] * center[2] + plane[3];
91 if dist < -radius {
92 return false;
93 }
94 }
95 true
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101pub struct SpatialCell {
102 pub x: i32,
103 pub y: i32,
104 pub z: i32,
105}
106
107#[derive(Clone, Debug)]
109pub struct SpatialHash {
110 cell_size: f32,
111 cells: std::collections::HashMap<SpatialCell, Vec<u64>>,
112}
113
114impl SpatialHash {
115 pub fn new(cell_size: f32) -> Self {
116 Self {
117 cell_size,
118 cells: std::collections::HashMap::new(),
119 }
120 }
121
122 pub fn insert(&mut self, entity_id: u64, position: &[f32; 3]) {
124 let cell = self.world_to_cell(position);
125 self.cells.entry(cell).or_default().push(entity_id);
126 }
127
128 pub fn remove(&mut self, entity_id: u64, position: &[f32; 3]) {
130 let cell = self.world_to_cell(position);
131 if let Some(entities) = self.cells.get_mut(&cell) {
132 entities.retain(|&id| id != entity_id);
133 if entities.is_empty() {
134 self.cells.remove(&cell);
135 }
136 }
137 }
138
139 pub fn query_frustum(&self, frustum: &Frustum) -> Vec<u64> {
141 let mut results = Vec::new();
142 for (cell, entities) in &self.cells {
144 let min = [
146 cell.x as f32 * self.cell_size,
147 cell.y as f32 * self.cell_size,
148 cell.z as f32 * self.cell_size,
149 ];
150 let max = [
151 min[0] + self.cell_size,
152 min[1] + self.cell_size,
153 min[2] + self.cell_size,
154 ];
155 if frustum.intersects_aabb(&min, &max) {
156 results.extend(entities);
157 }
158 }
159 results
160 }
161
162 pub fn query_sphere(&self, center: &[f32; 3], radius: f32) -> Vec<u64> {
164 let mut results = Vec::new();
165 let min_cell = self.world_to_cell(&[
167 center[0] - radius,
168 center[1] - radius,
169 center[2] - radius,
170 ]);
171 let max_cell = self.world_to_cell(&[
172 center[0] + radius,
173 center[1] + radius,
174 center[2] + radius,
175 ]);
176
177 for x in min_cell.x..=max_cell.x {
178 for y in min_cell.y..=max_cell.y {
179 for z in min_cell.z..=max_cell.z {
180 let cell = SpatialCell { x, y, z };
181 if let Some(entities) = self.cells.get(&cell) {
182 results.extend(entities);
183 }
184 }
185 }
186 }
187 results
188 }
189
190 fn world_to_cell(&self, position: &[f32; 3]) -> SpatialCell {
191 SpatialCell {
192 x: (position[0] / self.cell_size).floor() as i32,
193 y: (position[1] / self.cell_size).floor() as i32,
194 z: (position[2] / self.cell_size).floor() as i32,
195 }
196 }
197
198 pub fn clear(&mut self) {
200 self.cells.clear();
201 }
202
203 pub fn len(&self) -> usize {
205 self.cells.len()
206 }
207
208 pub fn is_empty(&self) -> bool {
209 self.cells.is_empty()
210 }
211}
212
213#[cfg(test)]
214mod p2_28_virtualization_tests {
215 use super::*;
216
217 #[test]
218 fn frustum_intersects_aabb_visible() {
219 let identity = [
220 [1.0, 0.0, 0.0, 0.0],
221 [0.0, 1.0, 0.0, 0.0],
222 [0.0, 0.0, 1.0, 0.0],
223 [0.0, 0.0, 0.0, 1.0],
224 ];
225 let frustum = Frustum::from_view_proj(&identity);
226 assert!(frustum.intersects_aabb(&[0.0, 0.0, 0.0], &[1.0, 1.0, 1.0]));
227 }
228
229 #[test]
230 fn frustum_intersects_aabb_outside() {
231 let frustum = Frustum {
232 planes: [
233 [0.0, 0.0, -1.0, -10.0], [0.0, 0.0, 1.0, -10.0], [0.0, 0.0, 0.0, 0.0],
236 [0.0, 0.0, 0.0, 0.0],
237 [0.0, 0.0, 0.0, 0.0],
238 [0.0, 0.0, 0.0, 0.0],
239 ],
240 };
241 assert!(!frustum.intersects_aabb(&[0.0, 0.0, -11.0], &[1.0, 1.0, -10.5]));
242 }
243
244 #[test]
245 fn frustum_intersects_sphere() {
246 let identity = [
247 [1.0, 0.0, 0.0, 0.0],
248 [0.0, 1.0, 0.0, 0.0],
249 [0.0, 0.0, 1.0, 0.0],
250 [0.0, 0.0, 0.0, 1.0],
251 ];
252 let frustum = Frustum::from_view_proj(&identity);
253 assert!(frustum.intersects_sphere(&[0.0, 0.0, 0.0], 1.0));
254 }
255
256 #[test]
257 fn spatial_hash_insert_and_query() {
258 let mut hash = SpatialHash::new(10.0);
259 hash.insert(1, &[5.0, 5.0, 0.0]);
260 hash.insert(2, &[15.0, 5.0, 0.0]);
261 assert_eq!(hash.len(), 2);
262 }
263
264 #[test]
265 fn spatial_hash_query_frustum() {
266 let mut hash = SpatialHash::new(10.0);
267 hash.insert(1, &[5.0, 5.0, 0.0]);
268 hash.insert(2, &[50.0, 50.0, 0.0]);
269
270 let identity = [
271 [1.0, 0.0, 0.0, 0.0],
272 [0.0, 1.0, 0.0, 0.0],
273 [0.0, 0.0, 1.0, 0.0],
274 [0.0, 0.0, 0.0, 1.0],
275 ];
276 let frustum = Frustum::from_view_proj(&identity);
277 let results = hash.query_frustum(&frustum);
278 assert!(!results.is_empty());
279 }
280
281 #[test]
282 fn spatial_hash_remove() {
283 let mut hash = SpatialHash::new(10.0);
284 hash.insert(1, &[5.0, 5.0, 0.0]);
285 hash.remove(1, &[5.0, 5.0, 0.0]);
286 assert_eq!(hash.len(), 0);
287 }
288
289 #[test]
290 fn spatial_hash_clear() {
291 let mut hash = SpatialHash::new(10.0);
292 hash.insert(1, &[5.0, 5.0, 0.0]);
293 hash.insert(2, &[15.0, 5.0, 0.0]);
294 hash.clear();
295 assert!(hash.is_empty());
296 }
297}