1#[cfg(feature = "parallel")]
9use rayon::{
10 iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator},
11 slice::ParallelSliceMut,
12};
13
14use rdst::{RadixKey, RadixSort};
15
16use crate::{
17 bvh2::{Bvh2, Bvh2Node},
18 fast_stack,
19 faststack::FastStack,
20};
21
22#[derive(Default)]
24pub struct ReinsertionOptimizer {
25 candidates: Vec<Candidate>,
26 reinsertions: Vec<Reinsertion>,
27 touched: Vec<bool>,
28 batch_size_ratio: f32,
29}
30
31impl ReinsertionOptimizer {
32 pub fn run(&mut self, bvh: &mut Bvh2, batch_size_ratio: f32, ratio_sequence: Option<Vec<f32>>) {
39 crate::scope!("reinsertion_optimize");
40
41 if bvh.nodes.is_empty() || bvh.nodes[0].is_leaf() || batch_size_ratio <= 0.0 {
42 return;
43 }
44
45 bvh.init_parents_if_uninit();
46
47 let cap = (bvh.nodes.len() as f32 * batch_size_ratio.min(1.0)).ceil() as usize;
48
49 self.candidates.reserve(cap);
50 self.reinsertions.reserve(cap);
51 self.touched.clear();
52 self.touched.resize(bvh.nodes.len(), false);
53 self.batch_size_ratio = batch_size_ratio;
54 self.optimize_impl(bvh, ratio_sequence);
55 }
56
57 pub fn run_with_candidates(&mut self, bvh: &mut Bvh2, candidates: &[u32], iterations: u32) {
64 crate::scope!("reinsertion_optimize_candidates");
65
66 if bvh.nodes.is_empty() || bvh.nodes[0].is_leaf() {
67 return;
68 }
69
70 bvh.init_parents_if_uninit();
71
72 let cap = candidates.len();
73
74 self.candidates = candidates
75 .iter()
76 .map(|node_id| {
77 let cost = bvh.nodes[*node_id as usize].aabb().half_area();
78 Candidate {
79 cost,
80 node_id: *node_id,
81 }
82 })
83 .collect::<Vec<_>>();
84 self.reinsertions.reserve(cap);
85 self.touched.clear();
86 self.touched.resize(bvh.nodes.len(), false);
87 self.optimize_specific_candidates(bvh, iterations);
88 }
89
90 pub fn optimize_impl(&mut self, bvh: &mut Bvh2, ratio_sequence: Option<Vec<f32>>) {
91 bvh.children_are_ordered_after_parents = false;
92 let ratio_sequence = ratio_sequence.unwrap_or(
96 (1..32)
97 .step_by(2)
98 .map(|n| 1.0 / n as f32)
99 .collect::<Vec<_>>(),
100 );
101
102 ratio_sequence.iter().for_each(|ratio| {
103 let batch_size =
104 (((bvh.nodes.len() as f32 * self.batch_size_ratio) * ratio) as usize).max(1);
105 let node_count = bvh.nodes.len().min(batch_size + 1);
106 self.find_candidates(bvh, node_count);
107 self.optimize_candidates(bvh, node_count - 1);
108 });
109 }
110
111 pub fn optimize_specific_candidates(&mut self, bvh: &mut Bvh2, iterations: u32) {
112 bvh.children_are_ordered_after_parents = false;
113 for _ in 0..iterations {
114 self.optimize_candidates(bvh, self.candidates.len());
115 }
116 }
117
118 fn find_candidates(&mut self, bvh: &mut Bvh2, node_count: usize) {
120 self.candidates.clear();
125 bvh.nodes
126 .iter()
127 .take(node_count * 2)
128 .enumerate()
129 .skip(1)
130 .for_each(|(i, node)| {
131 self.candidates.push(Candidate {
132 cost: node.aabb().half_area(),
133 node_id: i as u32,
134 });
135 });
136 self.candidates.radix_sort_unstable();
137 }
138
139 #[allow(unused_variables)]
140 fn optimize_candidates(&mut self, bvh: &mut Bvh2, count: usize) {
141 self.touched.fill(false);
142
143 #[cfg(feature = "parallel")]
144 {
145 self.reinsertions.resize(count, Default::default());
146 self.reinsertions
147 .par_iter_mut()
148 .enumerate()
149 .for_each(|(i, reinsertion)| {
150 *reinsertion = find_reinsertion(bvh, self.candidates[i].node_id as usize)
151 });
152 }
153 #[cfg(not(feature = "parallel"))]
154 {
155 self.reinsertions.clear();
156 assert!(count <= self.candidates.len());
157 (0..count).for_each(|i| {
158 let r = find_reinsertion(bvh, self.candidates[i].node_id as usize);
159 if r.area_diff > 0.0 {
160 self.reinsertions.push(r)
161 }
162 });
163 }
164
165 #[cfg(feature = "parallel")]
166 self.reinsertions
167 .par_sort_unstable_by(|a, b| b.area_diff.partial_cmp(&a.area_diff).unwrap());
168
169 #[cfg(not(feature = "parallel"))]
170 self.reinsertions
171 .sort_unstable_by(|a, b| b.area_diff.partial_cmp(&a.area_diff).unwrap());
172
173 assert!(self.reinsertions.len() <= self.touched.len());
174 (0..self.reinsertions.len()).for_each(|i| {
175 let reinsertion = &self.reinsertions[i];
176
177 #[cfg(feature = "parallel")]
178 if reinsertion.area_diff <= 0.0 {
179 return;
180 }
181
182 let conflicts = self.get_conflicts(bvh, reinsertion.from, reinsertion.to);
183
184 if conflicts.iter().any(|&i| self.touched[i]) {
185 return;
186 }
187
188 conflicts.iter().for_each(|&conflict| {
189 self.touched[conflict] = true;
190 });
191
192 reinsert_node(bvh, reinsertion.from as usize, reinsertion.to as usize);
193 });
194 }
195
196 #[inline(always)]
197 fn get_conflicts(&self, bvh: &mut Bvh2, from: u32, to: u32) -> [usize; 5] {
198 [
199 to as usize,
200 from as usize,
201 Bvh2Node::get_sibling_id(from as usize),
202 bvh.parents[to as usize] as usize,
204 bvh.parents[from as usize] as usize,
205 ]
206 }
207}
208
209#[derive(Default, Clone, Copy)]
210pub struct Reinsertion {
211 pub from: u32,
212 pub to: u32,
213 pub area_diff: f32,
214}
215
216#[derive(Clone, Copy, Debug)]
217struct Candidate {
218 node_id: u32,
219 cost: f32,
220}
221
222impl RadixKey for Candidate {
223 const LEVELS: usize = 4;
224
225 #[inline]
226 fn get_level(&self, level: usize) -> u8 {
227 (-self.cost).get_level(level)
228 }
229}
230
231pub fn find_reinsertion(bvh: &Bvh2, node_id: usize) -> Reinsertion {
232 if bvh.parents.is_empty() {
233 panic!("Parents mapping required. Please run Bvh2::init_parents() before reinsert_node()")
234 }
235
236 debug_assert_ne!(node_id, 0);
237 assert!(node_id < bvh.nodes.len());
239 assert!(node_id < bvh.parents.len());
240
241 let mut best_reinsertion = Reinsertion {
270 from: node_id as u32,
271 to: 0,
272 area_diff: 0.0,
273 };
274 let node_area = bvh.nodes[node_id].aabb().half_area();
275
276 let parent_area = bvh.nodes[bvh.parents[node_id] as usize].aabb().half_area();
277 let mut area_diff = parent_area;
278 let mut sibling_id = Bvh2Node::get_sibling_id(node_id);
279 let mut pivot_bbox = *bvh.nodes[sibling_id].aabb();
280 let parent_id = bvh.parents[node_id] as usize;
281 let mut pivot_id = parent_id;
282 let aabb = bvh.nodes[node_id].aabb();
283 let mut longest = 0;
284 fast_stack!((f32, u32), (96, 192), bvh.max_depth * 2, stack, {
286 stack.clear();
287 loop {
288 stack.push((area_diff, sibling_id as u32));
289 while !stack.is_empty() {
290 longest = stack.len().max(longest);
291 let (top_area_diff, top_sibling_id) = stack.pop_fast();
292 if top_area_diff - node_area <= best_reinsertion.area_diff {
293 continue;
294 }
295
296 let dst_node = &bvh.nodes[top_sibling_id as usize];
297 let merged_area = dst_node.aabb().union(aabb).half_area();
298 let reinsert_area = top_area_diff - merged_area;
299 if reinsert_area > best_reinsertion.area_diff {
300 best_reinsertion.to = top_sibling_id;
301 best_reinsertion.area_diff = reinsert_area;
302 }
303
304 if !dst_node.is_leaf() {
305 let child_area = reinsert_area + dst_node.aabb().half_area();
306 stack.push((child_area, dst_node.first_index));
307 stack.push((child_area, dst_node.first_index + 1));
308 }
309 }
310
311 if pivot_id != parent_id {
312 pivot_bbox = pivot_bbox.union(bvh.nodes[sibling_id].aabb());
313 area_diff += bvh.nodes[pivot_id].aabb().half_area() - pivot_bbox.half_area();
314 }
315
316 if pivot_id == 0 {
317 break;
318 }
319
320 sibling_id = Bvh2Node::get_sibling_id(pivot_id);
321 pivot_id = bvh.parents[pivot_id] as usize;
322 }
323 });
324
325 if best_reinsertion.to == Bvh2Node::get_sibling_id32(best_reinsertion.from)
326 || best_reinsertion.to == bvh.parents[best_reinsertion.from as usize]
327 {
328 best_reinsertion = Reinsertion::default();
329 }
330
331 best_reinsertion
332}
333
334pub fn reinsert_node(bvh: &mut Bvh2, from: usize, to: usize) {
335 if bvh.parents.is_empty() {
336 panic!("Parents mapping required. Please run Bvh2::init_parents() before reinsert_node()")
337 }
338
339 let sibling_id = Bvh2Node::get_sibling_id(from);
340 let parent_id = bvh.parents[from] as usize;
341 let sibling_node = bvh.nodes[sibling_id];
342 let dst_node = bvh.nodes[to];
343
344 bvh.nodes[to].make_inner(Bvh2Node::get_left_sibling_id(from) as u32);
345 bvh.nodes[sibling_id] = dst_node;
346 bvh.nodes[parent_id] = sibling_node;
347
348 bvh.relink_node(&dst_node, sibling_id);
350 bvh.relink_node(&sibling_node, parent_id);
351
352 bvh.parents[sibling_id] = to as u32;
353 bvh.parents[from] = to as u32;
354 bvh.refit_from_fast(to);
355 bvh.refit_from_fast(parent_id);
356}
357
358#[cfg(test)]
359mod tests {
360
361 use crate::{
362 ploc::{PlocBuilder, PlocSearchDistance, SortPrecision},
363 test_util::geometry::demoscene,
364 };
365
366 use super::*;
367
368 #[test]
369 fn test_reinsertion() {
370 let tris = demoscene(32, 0);
371 let mut aabbs = Vec::with_capacity(tris.len());
372 let mut indices = Vec::with_capacity(tris.len());
373 for (i, primitive) in tris.iter().enumerate() {
374 indices.push(i as u32);
375 aabbs.push(primitive.aabb());
376 }
377 {
378 let mut bvh = PlocBuilder::new().build(
380 PlocSearchDistance::VeryLow,
381 &aabbs,
382 indices.clone(),
383 SortPrecision::U64,
384 1,
385 );
386 bvh.validate(&tris, false, false);
387 ReinsertionOptimizer::default().run(&mut bvh, 0.25, None);
388 bvh.validate(&tris, false, false);
389 bvh.reorder_in_stack_traversal_order();
390 ReinsertionOptimizer::default().run(&mut bvh, 0.5, None);
391 bvh.validate(&tris, false, false);
392 }
393 {
394 let mut bvh = PlocBuilder::new().build(
396 PlocSearchDistance::VeryLow,
397 &aabbs,
398 indices,
399 SortPrecision::U64,
400 1,
401 );
402 bvh.validate(&tris, false, false);
403 bvh.init_primitives_to_nodes_if_uninit();
404 bvh.init_parents_if_uninit();
405 bvh.validate(&tris, false, false);
406 ReinsertionOptimizer::default().run(&mut bvh, 0.25, None);
407 bvh.validate(&tris, false, false);
408 bvh.reorder_in_stack_traversal_order();
409 ReinsertionOptimizer::default().run(&mut bvh, 0.5, None);
410 bvh.validate(&tris, false, false);
411 }
412 }
413}