Skip to main content

oxihuman_core/
octree_simple.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Simple octree for 3D spatial queries.
6
7/// Axis-aligned bounding box.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct OctAabb {
10    pub min: [f32; 3],
11    pub max: [f32; 3],
12}
13
14impl OctAabb {
15    pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
16        OctAabb { min, max }
17    }
18
19    pub fn center(&self) -> [f32; 3] {
20        [
21            (self.min[0] + self.max[0]) * 0.5,
22            (self.min[1] + self.max[1]) * 0.5,
23            (self.min[2] + self.max[2]) * 0.5,
24        ]
25    }
26
27    pub fn contains_point(&self, p: &[f32; 3]) -> bool {
28        (0..3).all(|i| p[i] >= self.min[i] && p[i] <= self.max[i])
29    }
30
31    pub fn intersects(&self, other: &OctAabb) -> bool {
32        (0..3).all(|i| self.min[i] <= other.max[i] && self.max[i] >= other.min[i])
33    }
34
35    fn octant(&self, p: &[f32; 3]) -> usize {
36        let c = self.center();
37        let x = if p[0] >= c[0] { 1 } else { 0 };
38        let y = if p[1] >= c[1] { 2 } else { 0 };
39        let z = if p[2] >= c[2] { 4 } else { 0 };
40        x | y | z
41    }
42
43    fn child_aabb(&self, octant: usize) -> OctAabb {
44        let c = self.center();
45        let min_x = if octant & 1 != 0 { c[0] } else { self.min[0] };
46        let max_x = if octant & 1 != 0 { self.max[0] } else { c[0] };
47        let min_y = if octant & 2 != 0 { c[1] } else { self.min[1] };
48        let max_y = if octant & 2 != 0 { self.max[1] } else { c[1] };
49        let min_z = if octant & 4 != 0 { c[2] } else { self.min[2] };
50        let max_z = if octant & 4 != 0 { self.max[2] } else { c[2] };
51        OctAabb {
52            min: [min_x, min_y, min_z],
53            max: [max_x, max_y, max_z],
54        }
55    }
56}
57
58const MAX_POINTS_PER_LEAF: usize = 8;
59const MAX_DEPTH: usize = 8;
60
61/// A simple octree node.
62pub enum SimpleOctreeNode {
63    Leaf {
64        points: Vec<(usize, [f32; 3])>,
65        bounds: OctAabb,
66    },
67    Internal {
68        bounds: OctAabb,
69        children: Box<[Option<SimpleOctreeNode>; 8]>,
70    },
71}
72
73impl SimpleOctreeNode {
74    fn new_leaf(bounds: OctAabb) -> Self {
75        SimpleOctreeNode::Leaf {
76            points: Vec::new(),
77            bounds,
78        }
79    }
80
81    fn bounds(&self) -> &OctAabb {
82        match self {
83            SimpleOctreeNode::Leaf { bounds, .. } => bounds,
84            SimpleOctreeNode::Internal { bounds, .. } => bounds,
85        }
86    }
87
88    fn insert(&mut self, id: usize, p: [f32; 3], depth: usize) {
89        match self {
90            SimpleOctreeNode::Leaf { points, bounds } => {
91                points.push((id, p));
92                if points.len() > MAX_POINTS_PER_LEAF && depth < MAX_DEPTH {
93                    let old_pts = std::mem::take(points);
94                    let old_bounds = *bounds;
95                    let mut children: [Option<SimpleOctreeNode>; 8] = Default::default();
96                    #[allow(clippy::needless_range_loop)]
97                    for oct in 0..8 {
98                        children[oct] =
99                            Some(SimpleOctreeNode::new_leaf(old_bounds.child_aabb(oct)));
100                    }
101                    let mut new_self = SimpleOctreeNode::Internal {
102                        bounds: old_bounds,
103                        children: Box::new(children),
104                    };
105                    for (oid, op) in old_pts {
106                        new_self.insert(oid, op, depth + 1);
107                    }
108                    *self = new_self;
109                }
110            }
111            SimpleOctreeNode::Internal { bounds, children } => {
112                let oct = bounds.octant(&p);
113                if let Some(child) = &mut children[oct] {
114                    child.insert(id, p, depth + 1);
115                }
116            }
117        }
118    }
119
120    fn query_aabb(&self, query: &OctAabb, result: &mut Vec<usize>) {
121        if !self.bounds().intersects(query) {
122            return;
123        }
124        match self {
125            SimpleOctreeNode::Leaf { points, .. } => {
126                for (id, p) in points {
127                    if query.contains_point(p) {
128                        result.push(*id);
129                    }
130                }
131            }
132            SimpleOctreeNode::Internal { children, .. } => {
133                for child in children.iter().flatten() {
134                    child.query_aabb(query, result);
135                }
136            }
137        }
138    }
139
140    fn count(&self) -> usize {
141        match self {
142            SimpleOctreeNode::Leaf { points, .. } => points.len(),
143            SimpleOctreeNode::Internal { children, .. } => {
144                children.iter().flatten().map(|c| c.count()).sum()
145            }
146        }
147    }
148}
149
150/// Simple octree.
151pub struct SimpleOctree {
152    root: Option<SimpleOctreeNode>,
153    bounds: OctAabb,
154    count: usize,
155}
156
157impl SimpleOctree {
158    /// Create a new octree with the given bounds.
159    pub fn new(bounds: OctAabb) -> Self {
160        SimpleOctree {
161            root: Some(SimpleOctreeNode::new_leaf(bounds)),
162            bounds,
163            count: 0,
164        }
165    }
166
167    /// Insert a point with ID.
168    pub fn insert(&mut self, id: usize, p: [f32; 3]) {
169        if !self.bounds.contains_point(&p) {
170            return;
171        }
172        if let Some(root) = &mut self.root {
173            root.insert(id, p, 0);
174            self.count += 1;
175        }
176    }
177
178    /// Query all point IDs within an AABB.
179    pub fn query_aabb(&self, query: &OctAabb) -> Vec<usize> {
180        let mut result = Vec::new();
181        if let Some(root) = &self.root {
182            root.query_aabb(query, &mut result);
183        }
184        result
185    }
186
187    /// Query all point IDs within a sphere (returns AABB candidates; for exact sphere tests use SimpleOctree3).
188    pub fn query_sphere(&self, center: &[f32; 3], r: f32) -> Vec<usize> {
189        let aabb = OctAabb {
190            min: [center[0] - r, center[1] - r, center[2] - r],
191            max: [center[0] + r, center[1] + r, center[2] + r],
192        };
193        self.query_aabb(&aabb)
194    }
195
196    /// Return total number of points inserted.
197    pub fn len(&self) -> usize {
198        self.count
199    }
200
201    /// True if empty.
202    pub fn is_empty(&self) -> bool {
203        self.count == 0
204    }
205}
206
207/// Better simple octree with point storage for sphere queries.
208pub struct SimpleOctree3 {
209    bounds: OctAabb,
210    root: Option<SimpleOctreeNode>,
211    points: Vec<[f32; 3]>,
212}
213
214impl SimpleOctree3 {
215    pub fn new(bounds: OctAabb) -> Self {
216        SimpleOctree3 {
217            root: Some(SimpleOctreeNode::new_leaf(bounds)),
218            bounds,
219            points: Vec::new(),
220        }
221    }
222
223    pub fn insert(&mut self, p: [f32; 3]) -> usize {
224        let id = self.points.len();
225        self.points.push(p);
226        if self.bounds.contains_point(&p) {
227            if let Some(root) = &mut self.root {
228                root.insert(id, p, 0);
229            }
230        }
231        id
232    }
233
234    pub fn query_aabb(&self, query: &OctAabb) -> Vec<usize> {
235        let mut result = Vec::new();
236        if let Some(root) = &self.root {
237            root.query_aabb(query, &mut result);
238        }
239        result
240    }
241
242    pub fn query_sphere(&self, center: &[f32; 3], r: f32) -> Vec<usize> {
243        let aabb = OctAabb {
244            min: [center[0] - r, center[1] - r, center[2] - r],
245            max: [center[0] + r, center[1] + r, center[2] + r],
246        };
247        let r_sq = r * r;
248        let candidates = self.query_aabb(&aabb);
249        candidates
250            .into_iter()
251            .filter(|&id| {
252                let p = &self.points[id];
253                (0..3).map(|i| (p[i] - center[i]).powi(2)).sum::<f32>() <= r_sq
254            })
255            .collect()
256    }
257
258    pub fn len(&self) -> usize {
259        self.points.len()
260    }
261
262    pub fn is_empty(&self) -> bool {
263        self.points.is_empty()
264    }
265
266    pub fn clear(&mut self) {
267        self.root = Some(SimpleOctreeNode::new_leaf(self.bounds));
268        self.points.clear();
269    }
270
271    pub fn bounds(&self) -> &OctAabb {
272        &self.bounds
273    }
274}
275
276/// Create a new simple octree with the given world size.
277pub fn new_simple_octree(half_size: f32) -> SimpleOctree3 {
278    let h = half_size.abs().max(1.0);
279    SimpleOctree3::new(OctAabb {
280        min: [-h, -h, -h],
281        max: [h, h, h],
282    })
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn test_insert_and_query_aabb() {
291        let mut tree = new_simple_octree(100.0);
292        let id = tree.insert([1.0, 2.0, 3.0]);
293        let found = tree.query_aabb(&OctAabb::new([0.0, 0.0, 0.0], [5.0, 5.0, 5.0]));
294        assert!(found.contains(&id));
295    }
296
297    #[test]
298    fn test_query_sphere() {
299        let mut tree = new_simple_octree(100.0);
300        let id = tree.insert([0.0, 0.0, 0.0]);
301        tree.insert([50.0, 50.0, 50.0]);
302        let found = tree.query_sphere(&[0.0, 0.0, 0.0], 1.0);
303        assert!(found.contains(&id));
304        assert_eq!(found.len(), 1);
305    }
306
307    #[test]
308    fn test_empty() {
309        let tree = new_simple_octree(10.0);
310        assert!(tree.is_empty());
311        assert_eq!(tree.len(), 0);
312    }
313
314    #[test]
315    fn test_len() {
316        let mut tree = new_simple_octree(100.0);
317        tree.insert([1.0, 0.0, 0.0]);
318        tree.insert([2.0, 0.0, 0.0]);
319        assert_eq!(tree.len(), 2);
320    }
321
322    #[test]
323    fn test_clear() {
324        let mut tree = new_simple_octree(100.0);
325        tree.insert([1.0, 1.0, 1.0]);
326        tree.clear();
327        assert!(tree.is_empty());
328    }
329
330    #[test]
331    fn test_out_of_bounds_ignored() {
332        let mut tree = new_simple_octree(10.0);
333        tree.insert([1000.0, 0.0, 0.0]);
334        /* ID is assigned but it won't be found in AABB query */
335        let found = tree.query_aabb(&OctAabb::new([-10.0, -10.0, -10.0], [10.0, 10.0, 10.0]));
336        assert!(found.is_empty());
337    }
338
339    #[test]
340    fn test_many_points() {
341        let mut tree = new_simple_octree(100.0);
342        for i in 0..50 {
343            tree.insert([i as f32, 0.0, 0.0]);
344        }
345        assert_eq!(tree.len(), 50);
346        let found = tree.query_sphere(&[25.0, 0.0, 0.0], 3.0);
347        assert!(found.len() >= 5);
348    }
349
350    #[test]
351    fn test_aabb_intersects() {
352        let a = OctAabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
353        let b = OctAabb::new([0.5, 0.5, 0.5], [2.0, 2.0, 2.0]);
354        let c = OctAabb::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]);
355        assert!(a.intersects(&b));
356        assert!(!a.intersects(&c));
357    }
358}