steel_worldgen/structure/
box_octree.rs1use std::mem;
5
6use glam::IVec3;
7use steel_utils::BoundingBox;
8
9const SUBDIVIDE_THRESHOLD: usize = 10;
10const MAXIMUM_DEPTH: u32 = 3;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub(crate) struct DeflatedQuarters {
18 min_x: i32,
19 min_y: i32,
20 min_z: i32,
21 max_x: i32,
22 max_y: i32,
23 max_z: i32,
24}
25
26impl DeflatedQuarters {
27 #[inline]
28 pub(crate) const fn from_piece(bbox: BoundingBox) -> Self {
29 Self {
30 min_x: bbox.min_x() * 4 + 1,
31 min_y: bbox.min_y() * 4 + 1,
32 min_z: bbox.min_z() * 4 + 1,
33 max_x: bbox.max_x() * 4 + 3,
34 max_y: bbox.max_y() * 4 + 3,
35 max_z: bbox.max_z() * 4 + 3,
36 }
37 }
38
39 #[inline]
41 pub(crate) const fn boundary_from(bbox: BoundingBox) -> Self {
42 Self {
43 min_x: bbox.min_x() * 4,
44 min_y: bbox.min_y() * 4,
45 min_z: bbox.min_z() * 4,
46 max_x: bbox.max_x() * 4 + 4,
47 max_y: bbox.max_y() * 4 + 4,
48 max_z: bbox.max_z() * 4 + 4,
49 }
50 }
51
52 #[inline]
53 pub(crate) const fn is_empty(self) -> bool {
54 self.min_x >= self.max_x || self.min_y >= self.max_y || self.min_z >= self.max_z
55 }
56
57 #[inline]
58 pub(crate) const fn contains(self, inner: Self) -> bool {
59 self.min_x <= inner.min_x
60 && self.min_y <= inner.min_y
61 && self.min_z <= inner.min_z
62 && self.max_x >= inner.max_x
63 && self.max_y >= inner.max_y
64 && self.max_z >= inner.max_z
65 }
66
67 #[inline]
68 pub(crate) const fn intersects(self, other: Self) -> bool {
69 self.min_x < other.max_x
70 && self.max_x > other.min_x
71 && self.min_y < other.max_y
72 && self.max_y > other.min_y
73 && self.min_z < other.max_z
74 && self.max_z > other.min_z
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct BoxOctree {
81 boundary: BoundingBox,
82 boundary_quarters: DeflatedQuarters,
83 size: IVec3,
84 depth: u32,
85 inner_boxes: Vec<StoredBox>,
86 children: Vec<BoxOctree>,
87}
88
89#[derive(Clone, Copy, Debug, Eq, PartialEq)]
90struct StoredBox {
91 bbox: BoundingBox,
92 deflated: DeflatedQuarters,
93}
94
95impl StoredBox {
96 const fn new(bbox: BoundingBox) -> Self {
97 Self {
98 bbox,
99 deflated: DeflatedQuarters::from_piece(bbox),
100 }
101 }
102}
103
104impl BoxOctree {
105 #[must_use]
106 pub fn new(boundary: BoundingBox) -> Self {
107 Self::with_depth(boundary, DeflatedQuarters::boundary_from(boundary), 0)
108 }
109
110 fn with_depth(
111 boundary: BoundingBox,
112 boundary_quarters: DeflatedQuarters,
113 parent_depth: u32,
114 ) -> Self {
115 let size = IVec3::new(
116 round_away_from_zero(boundary.width()),
117 round_away_from_zero(boundary.height()),
118 round_away_from_zero(boundary.depth()),
119 );
120 Self {
121 boundary,
122 boundary_quarters,
123 size,
124 depth: parent_depth + 1,
125 inner_boxes: Vec::new(),
126 children: Vec::new(),
127 }
128 }
129
130 #[inline]
131 pub fn add_box(&mut self, bbox: BoundingBox) {
132 if self.depth < MAXIMUM_DEPTH && self.inner_boxes.len() > SUBDIVIDE_THRESHOLD {
133 self.subdivide();
134 }
135
136 if !self.children.is_empty() {
137 for child in &mut self.children {
138 if child.boundary_intersects(bbox) {
139 child.add_box(bbox);
140 }
141 }
142 return;
143 }
144
145 if self.inner_boxes.iter().any(|stored| stored.bbox == bbox) {
146 return;
147 }
148 self.inner_boxes.push(StoredBox::new(bbox));
149 }
150
151 #[inline]
153 pub fn within_bounds_but_not_intersecting_children(&self, candidate: BoundingBox) -> bool {
154 let deflated = DeflatedQuarters::from_piece(candidate);
155 if deflated.is_empty() {
156 return false;
157 }
158 self.boundary_quarters.contains(deflated) && !self.intersects_deflated(deflated, candidate)
159 }
160
161 #[inline]
162 fn intersects_deflated(&self, deflated: DeflatedQuarters, candidate: BoundingBox) -> bool {
163 if !self.children.is_empty() {
164 return self.children.iter().any(|child| {
165 child.boundary_quarters.intersects(deflated)
166 && child.intersects_deflated(deflated, candidate)
167 });
168 }
169 self.inner_boxes
170 .iter()
171 .any(|stored| candidate.intersects(stored.bbox) && deflated.intersects(stored.deflated))
172 }
173
174 #[inline]
175 fn boundary_intersects(&self, candidate: BoundingBox) -> bool {
176 self.boundary.intersects(candidate)
177 }
178
179 fn subdivide(&mut self) {
180 assert!(
181 self.children.is_empty(),
182 "BoxOctree: tried to subdivide when children already exist"
183 );
184
185 let min = self.boundary.min_corner();
186 let max = self.boundary.max_corner();
187 let half_x = self.size.x / 2;
188 let half_y = self.size.y / 2;
189 let half_z = self.size.z / 2;
190
191 let child_bounds = [
192 BoundingBox::new(
193 IVec3::new(min.x, min.y, min.z),
194 IVec3::new(min.x + half_x, min.y + half_y, min.z + half_z),
195 ),
196 BoundingBox::new(
197 IVec3::new(min.x, min.y, min.z + half_z),
198 IVec3::new(min.x + half_x, min.y + half_y, max.z),
199 ),
200 BoundingBox::new(
201 IVec3::new(min.x + half_x, min.y, min.z),
202 IVec3::new(max.x, min.y + half_y, min.z + half_z),
203 ),
204 BoundingBox::new(
205 IVec3::new(min.x + half_x, min.y, min.z + half_z),
206 IVec3::new(max.x, min.y + half_y, max.z),
207 ),
208 BoundingBox::new(
209 IVec3::new(min.x, min.y + half_y, min.z),
210 IVec3::new(min.x + half_x, max.y, min.z + half_z),
211 ),
212 BoundingBox::new(
213 IVec3::new(min.x, min.y + half_y, min.z + half_z),
214 IVec3::new(min.x + half_x, max.y, max.z),
215 ),
216 BoundingBox::new(
217 IVec3::new(min.x + half_x, min.y + half_y, min.z),
218 IVec3::new(max.x, max.y, min.z + half_z),
219 ),
220 BoundingBox::new(
221 IVec3::new(min.x + half_x, min.y + half_y, min.z + half_z),
222 IVec3::new(max.x, max.y, max.z),
223 ),
224 ];
225
226 self.children = child_bounds
227 .into_iter()
228 .map(|boundary| {
229 Self::with_depth(
230 boundary,
231 DeflatedQuarters::boundary_from(boundary),
232 self.depth,
233 )
234 })
235 .collect();
236
237 let inner_boxes = mem::take(&mut self.inner_boxes);
238 for stored in inner_boxes {
239 for child in &mut self.children {
240 if child.boundary_intersects(stored.bbox) {
241 child.add_box(stored.bbox);
242 }
243 }
244 }
245 }
246}
247
248const fn round_away_from_zero(value: i32) -> i32 {
249 if value >= 0 { value } else { -value }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn distant_boxes_do_not_collide() {
258 let boundary = BoundingBox::new(IVec3::ZERO, IVec3::new(100, 100, 100));
259 let mut tree = BoxOctree::new(boundary);
260 tree.add_box(BoundingBox::new(IVec3::ZERO, IVec3::new(5, 5, 5)));
261 tree.add_box(BoundingBox::new(
262 IVec3::new(50, 50, 50),
263 IVec3::new(55, 55, 55),
264 ));
265
266 let candidate = BoundingBox::new(IVec3::new(10, 10, 10), IVec3::new(15, 15, 15));
267 assert!(tree.within_bounds_but_not_intersecting_children(candidate));
268 }
269
270 #[test]
271 fn nearby_boxes_collide() {
272 let boundary = BoundingBox::new(IVec3::ZERO, IVec3::new(100, 100, 100));
273 let mut tree = BoxOctree::new(boundary);
274 tree.add_box(BoundingBox::new(IVec3::ZERO, IVec3::new(5, 5, 5)));
275
276 let candidate = BoundingBox::new(IVec3::new(4, 4, 4), IVec3::new(8, 8, 8));
277 assert!(!tree.within_bounds_but_not_intersecting_children(candidate));
278 }
279}