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