Skip to content

Commit e2c9b60

Browse files
committed
octree: first implementation
1 parent 43f08e7 commit e2c9b60

File tree

3 files changed

+223
-2
lines changed

3 files changed

+223
-2
lines changed

src/geo.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl Vec3 {
3838
}
3939
}
4040

41-
#[derive(Debug, Clone)]
41+
#[derive(Debug, Clone, PartialEq, Eq)]
4242
pub struct Bbox {
4343
lower: Vec3,
4444
upper: Vec3,
@@ -61,7 +61,7 @@ impl Bbox {
6161
(self.lower + self.upper) / 2
6262
}
6363

64-
pub fn expand(&mut self, p: Vec3) -> Bbox {
64+
pub fn expand(&self, p: Vec3) -> Bbox {
6565
Bbox { lower: self.lower.min(p), upper: self.upper.max(p) }
6666
}
6767

@@ -75,6 +75,11 @@ impl Bbox {
7575
let d = self.upper - self.lower;
7676
d.x * d.y * d.z
7777
}
78+
79+
pub fn dist2(&self, p: Vec3) -> i64 {
80+
// TODO:
81+
unimplemented!()
82+
}
7883
}
7984

8085
impl std::ops::Add for Vec3 {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use rand::Rng;
33

44
pub mod geo;
55
pub use geo::Vec3;
6+
pub mod octree;
67

78
use crate::geo::Bbox;
89

src/octree.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use crate::geo::{Bbox, Vec3};
2+
3+
const MAX_LEAF_SIZE: usize = 64;
4+
5+
#[derive(Debug, PartialEq, Eq)]
6+
pub struct Octree {
7+
root: Option<Node>,
8+
outside: Vec<Vec3>,
9+
}
10+
11+
#[derive(Debug, PartialEq, Eq)]
12+
enum Node {
13+
Branch { children: Box<[Node; 8]>, bbox: Bbox },
14+
Leaf { points: Vec<Vec3>, bbox: Bbox },
15+
//
16+
// TODO: consider adding a Full{bbox: Bbox} variant which could be constructed when we know
17+
// that a bbox is completely full (remember we're living in a finite space since we're using
18+
// integer coordinates). This variant would drastically improve query time and also space
19+
// requirements.
20+
//
21+
}
22+
23+
impl Octree {
24+
pub fn new() -> Self {
25+
Octree { root: None, outside: Vec::with_capacity(MAX_LEAF_SIZE) }
26+
}
27+
28+
pub fn with_hint(bbox: Bbox) -> Self {
29+
Octree { root: Some(Node::new(bbox, vec![])), outside: vec![] }
30+
}
31+
32+
pub fn add(&mut self, p: Vec3) {
33+
if !self.root.as_ref().map_or(false, |n| n.bbox().contains(p)) {
34+
self.outside.push(p);
35+
36+
// TODO: when outside contains more than MAX_LEAF_SIZE elems rebuild the tree
37+
if self.outside.len() > MAX_LEAF_SIZE {
38+
eprintln!("warning: too many points outside bbox, query performance may suffer");
39+
}
40+
41+
return;
42+
}
43+
44+
self.root.as_mut().unwrap().add(p);
45+
}
46+
47+
pub fn nearest(&self, p: Vec3) -> Option<(Vec3, i64)> {
48+
let closest = self.root.as_ref().and_then(|n| n.nearest(p));
49+
50+
let closest_outside =
51+
self.outside.iter().map(|pt| (*pt, pt.dist2(p))).min_by_key(|(_, d)| *d);
52+
53+
match (closest, closest_outside) {
54+
(None, None) => None,
55+
(Some(n), None) | (None, Some(n)) => Some(n),
56+
(Some(n1), Some(n2)) => {
57+
if n1.1 < n2.1 {
58+
Some(n1)
59+
} else {
60+
Some(n2)
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
impl Node {
68+
pub fn new(bbox: Bbox, data: Vec<Vec3>) -> Self {
69+
if data.len() <= MAX_LEAF_SIZE {
70+
return Node::Leaf { points: data, bbox };
71+
}
72+
73+
let c = bbox.center();
74+
let sub_bboxes = split_bbox(&bbox, c);
75+
let mut sub_data = [
76+
Vec::with_capacity(MAX_LEAF_SIZE),
77+
Vec::with_capacity(MAX_LEAF_SIZE),
78+
Vec::with_capacity(MAX_LEAF_SIZE),
79+
Vec::with_capacity(MAX_LEAF_SIZE),
80+
Vec::with_capacity(MAX_LEAF_SIZE),
81+
Vec::with_capacity(MAX_LEAF_SIZE),
82+
Vec::with_capacity(MAX_LEAF_SIZE),
83+
Vec::with_capacity(MAX_LEAF_SIZE),
84+
];
85+
86+
for p in data {
87+
let bbox_id = partition_pt(p, c);
88+
sub_data[bbox_id].push(p);
89+
}
90+
91+
let child = |i: usize, sub_data: &mut [Vec<Vec3>; 8]| {
92+
let mut d = vec![];
93+
std::mem::swap(&mut d, &mut sub_data[i]);
94+
95+
Node::new(sub_bboxes[i].clone(), d)
96+
};
97+
98+
let children = Box::new([
99+
child(0, &mut sub_data),
100+
child(1, &mut sub_data),
101+
child(2, &mut sub_data),
102+
child(3, &mut sub_data),
103+
child(4, &mut sub_data),
104+
child(5, &mut sub_data),
105+
child(6, &mut sub_data),
106+
child(7, &mut sub_data),
107+
]);
108+
109+
Node::Branch { bbox, children }
110+
}
111+
112+
pub fn add(&mut self, p: Vec3) {
113+
match self {
114+
Node::Leaf { points, bbox } => {
115+
points.push(p);
116+
if points.len() >= MAX_LEAF_SIZE {
117+
let mut t = vec![];
118+
std::mem::swap(&mut t, points);
119+
120+
*self = Node::new(bbox.clone(), t);
121+
}
122+
}
123+
Node::Branch { children, bbox } => {
124+
let i = partition_pt(p, bbox.center());
125+
children[i].add(p);
126+
}
127+
}
128+
}
129+
130+
pub fn nearest(&self, p: Vec3) -> Option<(Vec3, i64)> {
131+
match self {
132+
Node::Leaf { points, .. } => {
133+
points.iter().map(|pt| (*pt, pt.dist2(p))).min_by_key(|(_, d)| *d)
134+
}
135+
Node::Branch { children, bbox } => {
136+
let enclosing_bbox_id = partition_pt(p, bbox.center());
137+
138+
let mut nearest = children[enclosing_bbox_id].nearest(p);
139+
140+
for (bbox_id, child) in children.iter().enumerate() {
141+
if bbox_id == enclosing_bbox_id {
142+
continue;
143+
}
144+
145+
match nearest {
146+
None => {
147+
nearest = child.nearest(p);
148+
}
149+
Some((_, min_dist)) => {
150+
if child.bbox().dist2(p) > min_dist {
151+
continue;
152+
}
153+
154+
if let Some((n, d)) = child.nearest(p) {
155+
if d < min_dist {
156+
nearest = Some((n, d));
157+
}
158+
}
159+
}
160+
}
161+
}
162+
163+
nearest
164+
}
165+
}
166+
}
167+
168+
pub fn bbox(&self) -> &Bbox {
169+
match self {
170+
Node::Branch { bbox, .. } | Node::Leaf { bbox, .. } => bbox,
171+
}
172+
}
173+
}
174+
175+
fn split_bbox(bbox: &Bbox, c: Vec3) -> [Bbox; 8] {
176+
debug_assert!(bbox.contains(c));
177+
178+
let u = bbox.upper();
179+
let l = bbox.lower();
180+
181+
[
182+
// top
183+
Bbox::new(c).expand(Vec3::new(l.x, l.y, l.z)),
184+
Bbox::new(c).expand(Vec3::new(l.x, l.y, u.z)),
185+
Bbox::new(c).expand(Vec3::new(u.x, l.y, l.z)),
186+
Bbox::new(c).expand(Vec3::new(u.x, l.y, u.z)),
187+
// bottom
188+
Bbox::new(c).expand(Vec3::new(l.x, u.y, l.z)),
189+
Bbox::new(c).expand(Vec3::new(l.x, u.y, u.z)),
190+
Bbox::new(c).expand(Vec3::new(u.x, u.y, l.z)),
191+
Bbox::new(c).expand(Vec3::new(u.x, u.y, u.z)),
192+
]
193+
}
194+
195+
fn partition_pt(p: Vec3, c: Vec3) -> usize {
196+
if p.x <= c.x && p.y <= c.y && p.z <= c.z {
197+
0
198+
} else if p.x <= c.x && p.y <= c.y && p.z >= c.z {
199+
1
200+
} else if p.x >= c.x && p.y <= c.y && p.z <= c.z {
201+
2
202+
} else if p.x >= c.x && p.y <= c.y && p.z >= c.z {
203+
3
204+
} else if p.x <= c.x && p.y >= c.y && p.z <= c.z {
205+
4
206+
} else if p.x <= c.x && p.y >= c.y && p.z >= c.z {
207+
5
208+
} else if p.x >= c.x && p.y >= c.y && p.z <= c.z {
209+
6
210+
} else if p.x >= c.x && p.y >= c.y && p.z >= c.z {
211+
7
212+
} else {
213+
unreachable!()
214+
}
215+
}

0 commit comments

Comments
 (0)