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