1use bytemuck::{bytes_of, cast_slice, from_bytes, Pod, Zeroable};
18
19use crate::specialized_libs::computational_geometry::{
20 BvhNode, KdNode, BVH_NODE_SIZE, KD_NODE_SIZE,
21};
22
23pub const SPATIAL_INDEX_MINI_HEADER_SIZE: usize = 32;
25
26pub const MAX_BVH_NODE_COUNT: usize = 1_048_576; pub const MAX_KD_NODE_COUNT: usize = 1_048_576; #[repr(C)]
44#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
45pub struct SpatialIndexMiniHeader {
46 pub bvh_node_count: u32,
47 pub kd_node_count: u32,
48 pub bvh_root: u32,
49 pub kd_root: u32,
50 pub bvh_prim_count: u32,
51 pub kd_point_count: u32,
52 pub reserved_u32: u32,
53 pub reserved_u32_2: u32,
54}
55
56impl Default for SpatialIndexMiniHeader {
57 fn default() -> Self {
58 Self {
59 bvh_node_count: 0,
60 kd_node_count: 0,
61 bvh_root: 0,
62 kd_root: 0,
63 bvh_prim_count: 0,
64 kd_point_count: 0,
65 reserved_u32: 0,
66 reserved_u32_2: 0,
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum SpatialIndexSectionError {
74 PayloadTooShort { got: usize, need: usize },
76 NonZeroReserved,
78 NodeCountTooLarge { got: u32, max: usize },
80 PayloadTruncated { expected: usize, got: usize },
82 OutputBufferTooSmall { needed: usize, have: usize },
84}
85
86impl std::fmt::Display for SpatialIndexSectionError {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 match self {
89 Self::PayloadTooShort { got, need } => write!(
90 f,
91 "10d SPATIAL_INDEX payload too short: got {got}, need {need}"
92 ),
93 Self::NonZeroReserved => write!(f, "10d SPATIAL_INDEX non-zero reserved field"),
94 Self::NodeCountTooLarge { got, max } => {
95 write!(f, "10d SPATIAL_INDEX node_count {got} exceeds max {max}")
96 }
97 Self::PayloadTruncated { expected, got } => write!(
98 f,
99 "10d SPATIAL_INDEX payload truncated: expected {expected}, got {got}"
100 ),
101 Self::OutputBufferTooSmall { needed, have } => write!(
102 f,
103 "10d SPATIAL_INDEX output buffer too small: need {needed}, have {have}"
104 ),
105 }
106 }
107}
108
109impl std::error::Error for SpatialIndexSectionError {}
110
111#[inline]
113pub fn encoded_len(
114 bvh_node_count: u32,
115 kd_node_count: u32,
116 bvh_prim_count: u32,
117 kd_point_count: u32,
118) -> usize {
119 let bc = bvh_node_count as usize;
120 let kc = kd_node_count as usize;
121 let pc = bvh_prim_count as usize;
122 let qc = kd_point_count as usize;
123 SPATIAL_INDEX_MINI_HEADER_SIZE
124 + bc * BVH_NODE_SIZE + pc * 4 + kc * KD_NODE_SIZE + qc * 4 }
129
130pub fn encode_spatial_index_section(
134 bvh_nodes: &[BvhNode],
135 bvh_prim_indices: &[u32],
136 bvh_root: u32,
137 kd_nodes: &[KdNode],
138 kd_point_indices: &[u32],
139 kd_root: u32,
140 out: &mut [u8],
141) -> Result<usize, SpatialIndexSectionError> {
142 let bc = bvh_nodes.len();
143 let kc = kd_nodes.len();
144 let pc = bvh_prim_indices.len();
145 let qc = kd_point_indices.len();
146
147 if bc > MAX_BVH_NODE_COUNT {
148 return Err(SpatialIndexSectionError::NodeCountTooLarge {
149 got: bc as u32,
150 max: MAX_BVH_NODE_COUNT,
151 });
152 }
153 if kc > MAX_KD_NODE_COUNT {
154 return Err(SpatialIndexSectionError::NodeCountTooLarge {
155 got: kc as u32,
156 max: MAX_KD_NODE_COUNT,
157 });
158 }
159 if bvh_prim_indices.len() < pc {
160 return Err(SpatialIndexSectionError::PayloadTruncated {
161 expected: pc,
162 got: bvh_prim_indices.len(),
163 });
164 }
165 if kd_point_indices.len() < qc {
166 return Err(SpatialIndexSectionError::PayloadTruncated {
167 expected: qc,
168 got: kd_point_indices.len(),
169 });
170 }
171
172 let need = encoded_len(bc as u32, kc as u32, pc as u32, qc as u32);
173 if out.len() < need {
174 return Err(SpatialIndexSectionError::OutputBufferTooSmall {
175 needed: need,
176 have: out.len(),
177 });
178 }
179
180 let header = SpatialIndexMiniHeader {
181 bvh_node_count: bc as u32,
182 kd_node_count: kc as u32,
183 bvh_root,
184 kd_root,
185 bvh_prim_count: pc as u32,
186 kd_point_count: qc as u32,
187 reserved_u32: 0,
188 reserved_u32_2: 0,
189 };
190
191 let mut off = 0usize;
192 out[off..off + SPATIAL_INDEX_MINI_HEADER_SIZE].copy_from_slice(bytes_of(&header));
193 off += SPATIAL_INDEX_MINI_HEADER_SIZE;
194
195 let bvh_bytes = cast_slice(bvh_nodes);
197 out[off..off + bvh_bytes.len()].copy_from_slice(bvh_bytes);
198 off += bc * BVH_NODE_SIZE;
199
200 let bvh_idx_bytes = cast_slice(&bvh_prim_indices[..pc]);
202 out[off..off + bvh_idx_bytes.len()].copy_from_slice(bvh_idx_bytes);
203 off += pc * 4;
204
205 let kd_bytes = cast_slice(kd_nodes);
207 out[off..off + kd_bytes.len()].copy_from_slice(kd_bytes);
208 off += kc * KD_NODE_SIZE;
209
210 let kd_idx_bytes = cast_slice(&kd_point_indices[..qc]);
212 out[off..off + kd_idx_bytes.len()].copy_from_slice(kd_idx_bytes);
213 off += qc * 4;
214
215 debug_assert_eq!(off, need);
216 Ok(off)
217}
218
219#[derive(Debug)]
221pub struct DecodedSpatialIndex<'a> {
222 pub header: SpatialIndexMiniHeader,
223 pub bvh_nodes: &'a [BvhNode],
224 pub bvh_prim_indices: &'a [u32],
225 pub kd_nodes: &'a [KdNode],
226 pub kd_point_indices: &'a [u32],
227}
228
229pub fn decode_spatial_index_section(
231 payload: &[u8],
232) -> Result<DecodedSpatialIndex<'_>, SpatialIndexSectionError> {
233 if payload.len() < SPATIAL_INDEX_MINI_HEADER_SIZE {
234 return Err(SpatialIndexSectionError::PayloadTooShort {
235 got: payload.len(),
236 need: SPATIAL_INDEX_MINI_HEADER_SIZE,
237 });
238 }
239
240 let header: SpatialIndexMiniHeader = *from_bytes(&payload[..SPATIAL_INDEX_MINI_HEADER_SIZE]);
241
242 if header.reserved_u32 != 0 || header.reserved_u32_2 != 0 {
243 return Err(SpatialIndexSectionError::NonZeroReserved);
244 }
245
246 let bc = header.bvh_node_count as usize;
247 let kc = header.kd_node_count as usize;
248 let pc = header.bvh_prim_count as usize;
249 let qc = header.kd_point_count as usize;
250
251 if bc > MAX_BVH_NODE_COUNT {
252 return Err(SpatialIndexSectionError::NodeCountTooLarge {
253 got: header.bvh_node_count,
254 max: MAX_BVH_NODE_COUNT,
255 });
256 }
257 if kc > MAX_KD_NODE_COUNT {
258 return Err(SpatialIndexSectionError::NodeCountTooLarge {
259 got: header.kd_node_count,
260 max: MAX_KD_NODE_COUNT,
261 });
262 }
263
264 let need = encoded_len(
265 header.bvh_node_count,
266 header.kd_node_count,
267 header.bvh_prim_count,
268 header.kd_point_count,
269 );
270 if payload.len() < need {
271 return Err(SpatialIndexSectionError::PayloadTruncated {
272 expected: need,
273 got: payload.len(),
274 });
275 }
276
277 let mut off = SPATIAL_INDEX_MINI_HEADER_SIZE;
278
279 let bvh_nodes: &[BvhNode] = cast_slice(&payload[off..off + bc * BVH_NODE_SIZE]);
280 off += bc * BVH_NODE_SIZE;
281
282 let bvh_prim_indices: &[u32] = cast_slice(&payload[off..off + pc * 4]);
283 off += pc * 4;
284
285 let kd_nodes: &[KdNode] = cast_slice(&payload[off..off + kc * KD_NODE_SIZE]);
286 off += kc * KD_NODE_SIZE;
287
288 let kd_point_indices: &[u32] = cast_slice(&payload[off..off + qc * 4]);
289 off += qc * 4;
290
291 debug_assert_eq!(off, need);
292
293 Ok(DecodedSpatialIndex {
294 header,
295 bvh_nodes,
296 bvh_prim_indices,
297 kd_nodes,
298 kd_point_indices,
299 })
300}
301
302#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::specialized_libs::computational_geometry::{
310 build_bvh_recursive, build_kd_tree_3d, Aabb, Point3,
311 };
312
313 fn test_aabbs() -> Vec<Aabb> {
314 (0..8)
315 .map(|i| {
316 let x = (i % 2) as f64;
317 let y = ((i / 2) % 2) as f64;
318 let z = (i / 4) as f64;
319 Aabb::new(Point3::new(x, y, z), Point3::new(x + 1.0, y + 1.0, z + 1.0))
320 })
321 .collect()
322 }
323
324 fn test_points() -> Vec<[f64; 3]> {
325 vec![
326 [0.0, 0.0, 0.0],
327 [1.0, 0.0, 0.0],
328 [0.0, 1.0, 0.0],
329 [0.0, 0.0, 1.0],
330 [1.0, 1.0, 1.0],
331 ]
332 }
333
334 #[test]
335 fn round_trip_encode_decode() {
336 let aabbs = test_aabbs();
337 let n = aabbs.len();
338 let mut bvh_nodes = vec![BvhNode::default(); 2 * n];
339 let mut bvh_indices = vec![0u32; n];
340 let mut bvh_codes = vec![0u64; n];
341 let mut bvh_sort = vec![0u32; n];
342 let (bvh_count, bvh_root) = build_bvh_recursive(
343 &aabbs,
344 &mut bvh_nodes,
345 &mut bvh_indices,
346 &mut bvh_codes,
347 &mut bvh_sort,
348 )
349 .unwrap();
350
351 let points = test_points();
352 let np = points.len();
353 let mut kd_nodes = vec![KdNode::default(); np];
354 let mut kd_indices = vec![0u32; np];
355 let mut kd_codes = vec![0u64; np];
356 let mut kd_sort = vec![0u32; np];
357 let (kd_count, kd_root) = build_kd_tree_3d(
358 &points,
359 &mut kd_nodes,
360 &mut kd_indices,
361 &mut kd_codes,
362 &mut kd_sort,
363 )
364 .unwrap();
365
366 let need = encoded_len(bvh_count as u32, kd_count as u32, n as u32, np as u32);
367 let mut buf = vec![0u8; need];
368
369 let written = encode_spatial_index_section(
370 &bvh_nodes[..bvh_count],
371 &bvh_indices,
372 bvh_root as u32,
373 &kd_nodes[..kd_count],
374 &kd_indices,
375 kd_root as u32,
376 &mut buf,
377 )
378 .unwrap();
379 assert_eq!(written, need);
380
381 let decoded = decode_spatial_index_section(&buf).unwrap();
382 assert_eq!(decoded.header.bvh_node_count, bvh_count as u32);
383 assert_eq!(decoded.header.kd_node_count, kd_count as u32);
384 assert_eq!(decoded.header.bvh_root, bvh_root as u32);
385 assert_eq!(decoded.header.kd_root, kd_root as u32);
386 assert_eq!(decoded.header.bvh_prim_count, n as u32);
387 assert_eq!(decoded.header.kd_point_count, np as u32);
388 assert_eq!(decoded.bvh_nodes.len(), bvh_count);
389 assert_eq!(decoded.kd_nodes.len(), kd_count);
390 assert_eq!(decoded.bvh_nodes, &bvh_nodes[..bvh_count]);
391 assert_eq!(decoded.kd_nodes, &kd_nodes[..kd_count]);
392 assert_eq!(decoded.bvh_prim_indices, &bvh_indices);
393 assert_eq!(decoded.kd_point_indices, &kd_indices);
394 }
395
396 #[test]
397 fn encode_twice_is_byte_identical() {
398 let aabbs = test_aabbs();
399 let n = aabbs.len();
400 let mut bvh_nodes = vec![BvhNode::default(); 2 * n];
401 let mut bvh_indices = vec![0u32; n];
402 let mut bvh_codes = vec![0u64; n];
403 let mut bvh_sort = vec![0u32; n];
404 let (bvh_count, bvh_root) = build_bvh_recursive(
405 &aabbs,
406 &mut bvh_nodes,
407 &mut bvh_indices,
408 &mut bvh_codes,
409 &mut bvh_sort,
410 )
411 .unwrap();
412
413 let need = encoded_len(bvh_count as u32, 0, n as u32, 0);
414 let mut buf_a = vec![0u8; need];
415 let mut buf_b = vec![0u8; need];
416
417 encode_spatial_index_section(
418 &bvh_nodes[..bvh_count],
419 &bvh_indices,
420 bvh_root as u32,
421 &[],
422 &[],
423 0,
424 &mut buf_a,
425 )
426 .unwrap();
427 encode_spatial_index_section(
428 &bvh_nodes[..bvh_count],
429 &bvh_indices,
430 bvh_root as u32,
431 &[],
432 &[],
433 0,
434 &mut buf_b,
435 )
436 .unwrap();
437
438 assert_eq!(buf_a, buf_b);
439 }
440
441 #[test]
442 fn decode_rejects_nonzero_reserved() {
443 let mut buf = vec![0u8; SPATIAL_INDEX_MINI_HEADER_SIZE];
444 buf[28] = 1;
446 let result = decode_spatial_index_section(&buf);
447 assert!(result.is_err());
448 assert_eq!(
449 result.unwrap_err(),
450 SpatialIndexSectionError::NonZeroReserved
451 );
452 }
453
454 #[test]
455 fn decode_rejects_truncated_payload() {
456 let buf = vec![0u8; SPATIAL_INDEX_MINI_HEADER_SIZE - 1];
457 let result = decode_spatial_index_section(&buf);
458 assert!(result.is_err());
459 }
460
461 #[test]
462 fn decode_rejects_node_count_too_large() {
463 let mut buf = vec![0u8; SPATIAL_INDEX_MINI_HEADER_SIZE];
464 let header = SpatialIndexMiniHeader {
466 bvh_node_count: MAX_BVH_NODE_COUNT as u32 + 1,
467 kd_node_count: 0,
468 bvh_root: 0,
469 kd_root: 0,
470 bvh_prim_count: 0,
471 kd_point_count: 0,
472 reserved_u32: 0,
473 reserved_u32_2: 0,
474 };
475 buf.copy_from_slice(bytes_of(&header));
476 let result = decode_spatial_index_section(&buf);
477 assert!(result.is_err());
478 }
479
480 #[test]
481 fn empty_section_round_trip() {
482 let need = encoded_len(0, 0, 0, 0);
483 let mut buf = vec![0u8; need];
484 encode_spatial_index_section(&[], &[], 0, &[], &[], 0, &mut buf).unwrap();
485
486 let decoded = decode_spatial_index_section(&buf).unwrap();
487 assert_eq!(decoded.header.bvh_node_count, 0);
488 assert_eq!(decoded.header.kd_node_count, 0);
489 assert!(decoded.bvh_nodes.is_empty());
490 assert!(decoded.kd_nodes.is_empty());
491 }
492
493 #[test]
494 fn header_is_pod() {
495 assert_eq!(
496 std::mem::size_of::<SpatialIndexMiniHeader>(),
497 SPATIAL_INDEX_MINI_HEADER_SIZE
498 );
499 }
500}