Skip to main content

copc_streaming/
types.rs

1/// Octree node key: (level, x, y, z).
2///
3/// All fields are `i32` to match the COPC wire format. `level` is always
4/// non-negative in practice; `x`, `y`, `z` are signed per the spec.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub struct VoxelKey {
7    /// Octree depth (0 = root).
8    pub level: i32,
9    /// X coordinate at this level.
10    pub x: i32,
11    /// Y coordinate at this level.
12    pub y: i32,
13    /// Z coordinate at this level.
14    pub z: i32,
15}
16
17impl VoxelKey {
18    /// The root octree node.
19    pub const ROOT: VoxelKey = VoxelKey {
20        level: 0,
21        x: 0,
22        y: 0,
23        z: 0,
24    };
25
26    /// Return the parent key, or `None` for the root node.
27    pub fn parent(&self) -> Option<VoxelKey> {
28        if self.level == 0 {
29            return None;
30        }
31        Some(VoxelKey {
32            level: self.level - 1,
33            x: self.x >> 1,
34            y: self.y >> 1,
35            z: self.z >> 1,
36        })
37    }
38
39    /// Return the child key in the given octant direction (0–7).
40    pub fn child(&self, dir: u8) -> VoxelKey {
41        debug_assert!(dir < 8, "octant direction must be 0–7");
42        VoxelKey {
43            level: self.level + 1,
44            x: (self.x << 1) | i32::from(dir & 0x1),
45            y: (self.y << 1) | i32::from((dir >> 1) & 0x1),
46            z: (self.z << 1) | i32::from((dir >> 2) & 0x1),
47        }
48    }
49
50    /// Return all eight child keys.
51    pub fn children(&self) -> [VoxelKey; 8] {
52        std::array::from_fn(|i| self.child(i as u8))
53    }
54
55    /// Compute the spatial bounding box of this node given the root octree bounds.
56    pub fn bounds(&self, root_bounds: &Aabb) -> Aabb {
57        let side = (root_bounds.max[0] - root_bounds.min[0]) / 2_u32.pow(self.level as u32) as f64;
58        Aabb {
59            min: [
60                root_bounds.min[0] + self.x as f64 * side,
61                root_bounds.min[1] + self.y as f64 * side,
62                root_bounds.min[2] + self.z as f64 * side,
63            ],
64            max: [
65                root_bounds.min[0] + (self.x + 1) as f64 * side,
66                root_bounds.min[1] + (self.y + 1) as f64 * side,
67                root_bounds.min[2] + (self.z + 1) as f64 * side,
68            ],
69        }
70    }
71}
72
73/// Axis-aligned bounding box.
74#[derive(Debug, Clone, Copy, PartialEq)]
75pub struct Aabb {
76    /// Minimum corner `[x, y, z]`.
77    pub min: [f64; 3],
78    /// Maximum corner `[x, y, z]`.
79    pub max: [f64; 3],
80}
81
82impl Aabb {
83    /// Test whether two bounding boxes overlap.
84    pub fn intersects(&self, other: &Aabb) -> bool {
85        self.min[0] <= other.max[0]
86            && self.max[0] >= other.min[0]
87            && self.min[1] <= other.max[1]
88            && self.max[1] >= other.min[1]
89            && self.min[2] <= other.max[2]
90            && self.max[2] >= other.min[2]
91    }
92}