Skip to main content

copc_streaming/
hierarchy.rs

1//! Incremental COPC hierarchy loading.
2//!
3//! Loads hierarchy pages on demand rather than all at once.
4
5use std::collections::HashMap;
6use std::io::Cursor;
7
8use crate::types::VoxelKey;
9use byteorder::{LittleEndian, ReadBytesExt};
10
11use crate::byte_source::ByteSource;
12use crate::error::CopcError;
13use crate::header::CopcInfo;
14
15/// A single hierarchy entry: metadata for one octree node.
16#[derive(Debug, Clone)]
17pub struct HierarchyEntry {
18    pub key: VoxelKey,
19    /// Absolute file offset to the compressed point data.
20    pub offset: u64,
21    /// Size of compressed data in bytes.
22    pub byte_size: i32,
23    /// Number of points, or -1 if this is a page pointer.
24    pub point_count: i32,
25}
26
27/// Reference to a hierarchy page that hasn't been loaded yet.
28#[derive(Debug, Clone)]
29struct PendingPage {
30    offset: u64,
31    size: u64,
32}
33
34/// Incrementally-loaded hierarchy cache.
35pub struct HierarchyCache {
36    /// All loaded node entries (point_count >= 0).
37    entries: HashMap<VoxelKey, HierarchyEntry>,
38    /// Pages we know about but haven't fetched yet.
39    pending_pages: Vec<PendingPage>,
40    /// Whether the root page has been loaded.
41    root_loaded: bool,
42}
43
44impl Default for HierarchyCache {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl HierarchyCache {
51    pub fn new() -> Self {
52        Self {
53            entries: HashMap::new(),
54            pending_pages: Vec::new(),
55            root_loaded: false,
56        }
57    }
58
59    /// Load the root hierarchy page.
60    pub async fn load_root(
61        &mut self,
62        source: &impl ByteSource,
63        info: &CopcInfo,
64    ) -> Result<(), CopcError> {
65        if self.root_loaded {
66            return Ok(());
67        }
68        let data = source
69            .read_range(info.root_hier_offset, info.root_hier_size)
70            .await?;
71        self.parse_page(&data, info.root_hier_offset)?;
72        self.root_loaded = true;
73        Ok(())
74    }
75
76    /// Load the next batch of pending hierarchy pages.
77    pub async fn load_pending_pages(&mut self, source: &impl ByteSource) -> Result<(), CopcError> {
78        if self.pending_pages.is_empty() {
79            return Ok(());
80        }
81        let pages: Vec<_> = self.pending_pages.drain(..).collect();
82        let ranges: Vec<_> = pages.iter().map(|p| (p.offset, p.size)).collect();
83        let results = source.read_ranges(&ranges).await?;
84
85        for (page, data) in pages.iter().zip(results) {
86            self.parse_page(&data, page.offset)?;
87        }
88
89        Ok(())
90    }
91
92    /// Load all pending pages (breadth-first).
93    pub async fn load_all(
94        &mut self,
95        source: &impl ByteSource,
96        info: &CopcInfo,
97    ) -> Result<(), CopcError> {
98        self.load_root(source, info).await?;
99
100        while !self.pending_pages.is_empty() {
101            self.load_pending_pages(source).await?;
102        }
103
104        Ok(())
105    }
106
107    /// Whether there are unloaded hierarchy pages.
108    pub fn has_pending_pages(&self) -> bool {
109        !self.pending_pages.is_empty()
110    }
111
112    /// Look up a hierarchy entry by key.
113    pub fn get(&self, key: &VoxelKey) -> Option<&HierarchyEntry> {
114        self.entries.get(key)
115    }
116
117    /// Iterate all loaded entries.
118    pub fn iter(&self) -> impl Iterator<Item = (&VoxelKey, &HierarchyEntry)> {
119        self.entries.iter()
120    }
121
122    /// Number of loaded entries.
123    pub fn len(&self) -> usize {
124        self.entries.len()
125    }
126
127    pub fn is_empty(&self) -> bool {
128        self.entries.is_empty()
129    }
130
131    /// Parse a hierarchy page and add entries / page pointers.
132    fn parse_page(&mut self, data: &[u8], _base_offset: u64) -> Result<(), CopcError> {
133        let entry_size = 32; // VoxelKey (16) + offset (8) + byte_size (4) + point_count (4)
134        let mut r = Cursor::new(data);
135
136        while (r.position() as usize + entry_size) <= data.len() {
137            let level = r.read_i32::<LittleEndian>()?;
138            let x = r.read_i32::<LittleEndian>()?;
139            let y = r.read_i32::<LittleEndian>()?;
140            let z = r.read_i32::<LittleEndian>()?;
141            let offset = r.read_u64::<LittleEndian>()?;
142            let byte_size = r.read_i32::<LittleEndian>()?;
143            let point_count = r.read_i32::<LittleEndian>()?;
144
145            let key = VoxelKey { level, x, y, z };
146
147            if point_count == -1 {
148                // Page pointer — register for later loading
149                self.pending_pages.push(PendingPage {
150                    offset,
151                    size: byte_size as u64,
152                });
153            } else {
154                self.entries.insert(
155                    key,
156                    HierarchyEntry {
157                        key,
158                        offset,
159                        byte_size,
160                        point_count,
161                    },
162                );
163            }
164        }
165
166        Ok(())
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use byteorder::WriteBytesExt;
174
175    fn write_hierarchy_entry(
176        buf: &mut Vec<u8>,
177        level: i32,
178        x: i32,
179        y: i32,
180        z: i32,
181        offset: u64,
182        byte_size: i32,
183        point_count: i32,
184    ) {
185        buf.write_i32::<LittleEndian>(level).unwrap();
186        buf.write_i32::<LittleEndian>(x).unwrap();
187        buf.write_i32::<LittleEndian>(y).unwrap();
188        buf.write_i32::<LittleEndian>(z).unwrap();
189        buf.write_u64::<LittleEndian>(offset).unwrap();
190        buf.write_i32::<LittleEndian>(byte_size).unwrap();
191        buf.write_i32::<LittleEndian>(point_count).unwrap();
192    }
193
194    #[tokio::test]
195    async fn test_parse_hierarchy_page() {
196        let mut page_data = Vec::new();
197        write_hierarchy_entry(&mut page_data, 0, 0, 0, 0, 1000, 256, 100);
198        write_hierarchy_entry(&mut page_data, 1, 0, 0, 0, 2000, 512, 50);
199        write_hierarchy_entry(&mut page_data, 1, 1, 0, 0, 5000, 128, -1);
200
201        let mut cache = HierarchyCache::new();
202        cache.parse_page(&page_data, 0).unwrap();
203
204        assert_eq!(cache.len(), 2); // 2 node entries
205        assert_eq!(cache.pending_pages.len(), 1); // 1 page pointer
206
207        let root = cache
208            .get(&VoxelKey {
209                level: 0,
210                x: 0,
211                y: 0,
212                z: 0,
213            })
214            .unwrap();
215        assert_eq!(root.point_count, 100);
216        assert_eq!(root.offset, 1000);
217    }
218}