parry3d/partitioning/bvh/
bvh_optimize.rs1use super::{Bvh, BvhWorkspace};
2use crate::math::Real;
3use core::cmp::Ordering;
4use ordered_float::OrderedFloat;
5
6#[cfg(not(feature = "std"))]
7use na::ComplexField; impl Bvh {
10 fn optimization_config(&self, frame_index: u32) -> OptimizationConfig {
11 const TARGET_REBUILD_NODE_PERCENTAGE: u32 = 5;
12 let num_leaves = self.nodes[0].leaf_count();
13 let num_optimized_leaves = (num_leaves * TARGET_REBUILD_NODE_PERCENTAGE).div_ceil(100);
14
15 let num_leaves_sqrt = (num_leaves as Real).sqrt();
16 let root_mode = if frame_index % 2 == 0 {
17 RootOptimizationMode::Skip
18 } else if (frame_index / 2) % 16 == 0 {
19 RootOptimizationMode::BreadthFirst
20 } else {
21 RootOptimizationMode::PriorityQueue
22 };
23
24 let target_root_node_count = num_leaves_sqrt.ceil();
25 let target_subtree_leaf_count = (num_leaves_sqrt * 4.0).ceil();
26 let root_refinement_cost = target_root_node_count * target_root_node_count.log2()
27 / (target_subtree_leaf_count * target_subtree_leaf_count.log2());
28 let mut target_optimized_subtree_count =
29 (num_optimized_leaves as Real / target_subtree_leaf_count - root_refinement_cost)
30 .round()
31 .max(0.0) as usize;
32
33 if root_mode == RootOptimizationMode::Skip {
34 target_optimized_subtree_count = target_optimized_subtree_count.max(1)
35 }
36
37 OptimizationConfig {
38 target_root_node_count: target_root_node_count as usize,
39 target_subtree_leaf_count: target_subtree_leaf_count as usize,
40 target_optimized_subtree_count,
41 root_mode,
42 }
43 }
44
45 pub fn optimize_incremental(&mut self, workspace: &mut BvhWorkspace) {
58 if self.nodes.is_empty() {
59 return;
60 }
61
62 workspace.rebuild_leaves.clear();
63 workspace.rebuild_frame_index = workspace.rebuild_frame_index.overflowing_add(1).0;
64 let config = self.optimization_config(workspace.rebuild_frame_index);
65
66 let num_leaves = self.nodes[0].leaf_count();
71 let mut start_index = workspace.rebuild_start_index;
72
73 self.find_optimization_roots(
75 workspace,
76 0,
77 &mut start_index,
78 config.target_optimized_subtree_count as u32,
79 0,
80 config.target_subtree_leaf_count as u32,
81 );
82
83 if start_index >= num_leaves {
84 start_index = 0;
85 }
88
89 workspace.rebuild_start_index = start_index;
90
91 workspace.rebuild_leaves.clear();
101
102 match config.root_mode {
104 RootOptimizationMode::BreadthFirst => self
105 .find_root_optimization_pseudo_leaves_breadth_first(
106 workspace,
107 config.target_root_node_count,
108 ),
109 RootOptimizationMode::PriorityQueue => self
110 .find_root_optimization_pseudo_leaves_pqueue(
111 workspace,
112 config.target_root_node_count,
113 ),
114 RootOptimizationMode::Skip => {}
115 }
116
117 if !workspace.rebuild_leaves.is_empty() {
118 self.rebuild_range_binned(0, &mut workspace.rebuild_leaves);
119 }
120 for i in 0..workspace.optimization_roots.len() {
126 let subtree_root_id = workspace.optimization_roots[i];
127 workspace.rebuild_leaves.clear();
128 self.collect_leaves(workspace, subtree_root_id);
129
130 self.rebuild_range_binned(subtree_root_id, &mut workspace.rebuild_leaves);
132 }
138
139 workspace.optimization_roots.clear();
146 }
147
148 fn collect_leaves(&self, workspace: &mut BvhWorkspace, subtree_root: u32) {
149 let node = &self.nodes[subtree_root as usize];
150 let left = &node.left;
151 let right = &node.right;
152
153 if left.is_leaf() {
154 workspace.rebuild_leaves.push(*left);
155 } else {
156 self.collect_leaves(workspace, left.children);
157 }
158
159 if right.is_leaf() {
160 workspace.rebuild_leaves.push(*right);
161 } else {
162 self.collect_leaves(workspace, right.children);
163 }
164 }
165
166 fn find_root_optimization_pseudo_leaves_breadth_first(
167 &mut self,
168 workspace: &mut BvhWorkspace,
169 target_count: usize,
170 ) {
171 if self.nodes.len() < 2 {
172 return;
173 }
174
175 workspace.dequeue.push_back(0);
176
177 while workspace.dequeue.len() + workspace.rebuild_leaves.len() < target_count {
178 let Some(curr_node) = workspace.dequeue.pop_front() else {
179 break;
180 };
181
182 let node = &self.nodes[curr_node as usize];
183 let left = &node.left;
184 let right = &node.right;
185
186 if left.is_leaf() || left.data.is_change_pending() {
187 workspace.rebuild_leaves.push(*left);
188 } else {
189 workspace.dequeue.push_back(left.children);
190 }
191
192 if right.is_leaf() || right.data.is_change_pending() {
193 workspace.rebuild_leaves.push(*right);
194 } else {
195 workspace.dequeue.push_back(right.children);
196 }
197 }
198
199 for id in workspace.dequeue.drain(..) {
200 let node = &self.nodes[id as usize];
201 workspace.rebuild_leaves.push(node.left);
202 workspace.rebuild_leaves.push(node.right);
203 }
204 }
205
206 fn find_root_optimization_pseudo_leaves_pqueue(
207 &mut self,
208 workspace: &mut BvhWorkspace,
209 target_count: usize,
210 ) {
211 if self.nodes.len() < 2 {
212 return;
213 }
214
215 workspace.queue.push(BvhOptimizationHeapEntry {
216 score: OrderedFloat(Real::MAX),
217 id: 0,
218 });
219
220 while workspace.queue.len() + workspace.rebuild_leaves.len() < target_count {
221 let Some(curr_node) = workspace.queue.pop() else {
222 break;
223 };
224
225 let node = &self.nodes[curr_node.id as usize];
226 let left = &node.left;
227 let right = &node.right;
228
229 if left.is_leaf() || left.data.is_change_pending() {
230 workspace.rebuild_leaves.push(*left);
231 } else {
232 let children = self.nodes[left.children as usize];
233 let left_score = children.left.volume() * children.left.leaf_count() as Real
234 + children.right.volume() * children.right.leaf_count() as Real;
235 workspace.queue.push(BvhOptimizationHeapEntry {
236 score: OrderedFloat(left_score),
237 id: left.children,
238 });
239 }
240
241 if right.is_leaf() || right.data.is_change_pending() {
242 workspace.rebuild_leaves.push(*right);
243 } else {
244 let children = self.nodes[right.children as usize];
245 let right_score = children.left.volume() * children.left.leaf_count() as Real
246 + children.right.volume() * children.right.leaf_count() as Real;
247 workspace.queue.push(BvhOptimizationHeapEntry {
248 score: OrderedFloat(right_score),
249 id: right.children,
250 });
251 }
252 }
253
254 for id in workspace.queue.as_slice() {
255 let node = &self.nodes[id.id as usize];
256 workspace.rebuild_leaves.push(node.left);
257 workspace.rebuild_leaves.push(node.right);
258 }
259 workspace.queue.clear();
260 }
261
262 fn find_optimization_roots(
263 &mut self,
264 workspace: &mut BvhWorkspace,
265 curr_node: u32,
266 start_index: &mut u32,
267 max_optimization_roots: u32,
268 mut leaf_count_before: u32,
269 max_candidate_leaf_count: u32,
270 ) {
271 if workspace.optimization_roots.len() == max_optimization_roots as usize {
272 return;
274 }
275
276 let node = &mut self.nodes[curr_node as usize];
277 let left = &mut node.left;
278 let left_leaf_count = left.leaf_count();
279 let left_children = left.children;
280
281 if leaf_count_before + left_leaf_count > *start_index {
282 if left_leaf_count < max_candidate_leaf_count {
284 if left_leaf_count > 2 {
286 left.data.set_change_pending();
291 workspace.optimization_roots.push(left_children);
292 *start_index += left_leaf_count;
293 }
294 } else {
295 self.find_optimization_roots(
297 workspace,
298 left_children,
299 start_index,
300 max_optimization_roots,
301 leaf_count_before,
302 max_candidate_leaf_count,
303 );
304 }
305 }
306
307 leaf_count_before += left_leaf_count;
308
309 if workspace.optimization_roots.len() == max_optimization_roots as usize {
310 return;
312 }
313
314 let node = &mut self.nodes[curr_node as usize];
315 let right = &mut node.right;
316 let right_leaf_count = right.leaf_count();
317 let right_children = right.children;
318
319 if leaf_count_before + right_leaf_count > *start_index {
320 if right_leaf_count < max_candidate_leaf_count {
322 if right_leaf_count > 2 {
323 right.data.set_change_pending();
328 workspace.optimization_roots.push(right_children);
329 *start_index += right_leaf_count;
330 }
331 } else {
332 self.find_optimization_roots(
333 workspace,
334 right_children,
335 start_index,
336 max_optimization_roots,
337 leaf_count_before,
338 max_candidate_leaf_count,
339 );
340 }
341 }
342 }
343}
344
345#[derive(Copy, Clone, PartialEq, Eq, Debug)]
346enum RootOptimizationMode {
347 PriorityQueue,
348 BreadthFirst,
349 Skip,
350}
351
352#[derive(Copy, Clone, PartialEq, Eq, Debug)]
353struct OptimizationConfig {
354 target_root_node_count: usize,
355 target_subtree_leaf_count: usize,
356 target_optimized_subtree_count: usize,
357 root_mode: RootOptimizationMode,
358}
359
360#[derive(Copy, Clone, Debug)]
361pub struct BvhOptimizationHeapEntry {
362 score: OrderedFloat<Real>,
363 id: u32,
364}
365
366impl PartialOrd for BvhOptimizationHeapEntry {
367 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
368 Some(self.score.cmp(&other.score))
369 }
370}
371
372impl Ord for BvhOptimizationHeapEntry {
373 fn cmp(&self, other: &Self) -> Ordering {
374 self.score.cmp(&other.score)
375 }
376}
377
378impl PartialEq for BvhOptimizationHeapEntry {
379 fn eq(&self, other: &Self) -> bool {
380 self.score == other.score
381 }
382}
383
384impl Eq for BvhOptimizationHeapEntry {}