1 | public: |
2 | BVHHandle item_add(T *p_userdata, bool p_active, const BOUNDS &p_aabb, int32_t p_subindex, uint32_t p_tree_id, uint32_t p_tree_collision_mask, bool p_invisible = false) { |
3 | #ifdef BVH_VERBOSE_TREE |
4 | VERBOSE_PRINT("\nitem_add BEFORE" ); |
5 | _debug_recursive_print_tree(p_tree_id); |
6 | VERBOSE_PRINT("\n" ); |
7 | #endif |
8 | |
9 | BVHABB_CLASS abb; |
10 | abb.from(p_aabb); |
11 | |
12 | // NOTE that we do not expand the AABB for the first create even if |
13 | // leaf expansion is switched on. This is for two reasons: |
14 | // (1) We don't know if this object will move in future, in which case a non-expanded |
15 | // bound would be better... |
16 | // (2) We don't yet know how many objects will be paired, which is used to modify |
17 | // the expansion margin. |
18 | |
19 | // handle to be filled with the new item ref |
20 | BVHHandle handle; |
21 | |
22 | // ref id easier to pass around than handle |
23 | uint32_t ref_id; |
24 | |
25 | // this should never fail |
26 | ItemRef *ref = _refs.request(ref_id); |
27 | |
28 | // the extra data should be parallel list to the references |
29 | uint32_t ; |
30 | ItemExtra * = _extra.request(extra_id); |
31 | BVH_ASSERT(extra_id == ref_id); |
32 | |
33 | // pairs info |
34 | if (USE_PAIRS) { |
35 | uint32_t pairs_id; |
36 | ItemPairs *pairs = _pairs.request(pairs_id); |
37 | pairs->clear(); |
38 | BVH_ASSERT(pairs_id == ref_id); |
39 | } |
40 | |
41 | extra->subindex = p_subindex; |
42 | extra->userdata = p_userdata; |
43 | extra->last_updated_tick = 0; |
44 | |
45 | // add an active reference to the list for slow incremental optimize |
46 | // this list must be kept in sync with the references as they are added or removed. |
47 | extra->active_ref_id = _active_refs.size(); |
48 | _active_refs.push_back(ref_id); |
49 | |
50 | extra->tree_id = p_tree_id; |
51 | extra->tree_collision_mask = p_tree_collision_mask; |
52 | |
53 | // assign to handle to return |
54 | handle.set_id(ref_id); |
55 | |
56 | create_root_node(p_tree_id); |
57 | |
58 | // we must choose where to add to tree |
59 | if (p_active) { |
60 | ref->tnode_id = _logic_choose_item_add_node(_root_node_id[p_tree_id], abb); |
61 | |
62 | bool refit = _node_add_item(ref->tnode_id, ref_id, abb); |
63 | |
64 | if (refit) { |
65 | // only need to refit from the parent |
66 | const TNode &add_node = _nodes[ref->tnode_id]; |
67 | if (add_node.parent_id != BVHCommon::INVALID) { |
68 | refit_upward_and_balance(add_node.parent_id, p_tree_id); |
69 | } |
70 | } |
71 | } else { |
72 | ref->set_inactive(); |
73 | } |
74 | |
75 | #ifdef BVH_VERBOSE |
76 | // memory use |
77 | int mem = _refs.estimate_memory_use(); |
78 | mem += _nodes.estimate_memory_use(); |
79 | |
80 | String sz = _debug_aabb_to_string(abb); |
81 | VERBOSE_PRINT("\titem_add [" + itos(ref_id) + "] " + itos(_refs.used_size()) + " refs,\t" + itos(_nodes.used_size()) + " nodes " + sz); |
82 | VERBOSE_PRINT("mem use : " + itos(mem) + ", num nodes reserved : " + itos(_nodes.reserved_size())); |
83 | |
84 | #endif |
85 | |
86 | return handle; |
87 | } |
88 | |
89 | void _debug_print_refs() { |
90 | #ifdef BVH_VERBOSE_TREE |
91 | print_line("refs....." ); |
92 | for (int n = 0; n < _refs.size(); n++) { |
93 | const ItemRef &ref = _refs[n]; |
94 | print_line("tnode_id " + itos(ref.tnode_id) + ", item_id " + itos(ref.item_id)); |
95 | } |
96 | |
97 | #endif |
98 | } |
99 | |
100 | // returns false if noop |
101 | bool item_move(BVHHandle p_handle, const BOUNDS &p_aabb) { |
102 | uint32_t ref_id = p_handle.id(); |
103 | |
104 | // get the reference |
105 | ItemRef &ref = _refs[ref_id]; |
106 | if (!ref.is_active()) { |
107 | return false; |
108 | } |
109 | |
110 | BVHABB_CLASS abb; |
111 | abb.from(p_aabb); |
112 | |
113 | #ifdef BVH_EXPAND_LEAF_AABBS |
114 | if (USE_PAIRS) { |
115 | // scale the pairing expansion by the number of pairs. |
116 | abb.expand(_pairs[ref_id].scale_expansion_margin(_pairing_expansion)); |
117 | } else { |
118 | abb.expand(_pairing_expansion); |
119 | } |
120 | #endif |
121 | |
122 | BVH_ASSERT(ref.tnode_id != BVHCommon::INVALID); |
123 | TNode &tnode = _nodes[ref.tnode_id]; |
124 | |
125 | // does it fit within the current leaf aabb? |
126 | if (tnode.aabb.is_other_within(abb)) { |
127 | // do nothing .. fast path .. not moved enough to need refit |
128 | |
129 | // however we WILL update the exact aabb in the leaf, as this will be needed |
130 | // for accurate collision detection |
131 | TLeaf &leaf = _node_get_leaf(tnode); |
132 | |
133 | BVHABB_CLASS &leaf_abb = leaf.get_aabb(ref.item_id); |
134 | |
135 | // no change? |
136 | #ifdef BVH_EXPAND_LEAF_AABBS |
137 | BOUNDS leaf_aabb; |
138 | leaf_abb.to(leaf_aabb); |
139 | |
140 | // This test should pass in a lot of cases, and by returning false we can avoid |
141 | // collision pairing checks later, which greatly reduces processing. |
142 | if (expanded_aabb_encloses_not_shrink(leaf_aabb, p_aabb)) { |
143 | return false; |
144 | } |
145 | #else |
146 | if (leaf_abb == abb) { |
147 | return false; |
148 | } |
149 | #endif |
150 | |
151 | #ifdef BVH_VERBOSE_MOVES |
152 | print_line("item_move " + itos(p_handle.id()) + "(within tnode aabb) : " + _debug_aabb_to_string(abb)); |
153 | #endif |
154 | |
155 | leaf_abb = abb; |
156 | _integrity_check_all(); |
157 | |
158 | return true; |
159 | } |
160 | |
161 | #ifdef BVH_VERBOSE_MOVES |
162 | print_line("item_move " + itos(p_handle.id()) + "(outside tnode aabb) : " + _debug_aabb_to_string(abb)); |
163 | #endif |
164 | |
165 | uint32_t tree_id = _handle_get_tree_id(p_handle); |
166 | |
167 | // remove and reinsert |
168 | node_remove_item(ref_id, tree_id); |
169 | |
170 | // we must choose where to add to tree |
171 | ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb); |
172 | |
173 | // add to the tree |
174 | bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb); |
175 | |
176 | // only need to refit from the PARENT |
177 | if (needs_refit) { |
178 | // only need to refit from the parent |
179 | const TNode &add_node = _nodes[ref.tnode_id]; |
180 | if (add_node.parent_id != BVHCommon::INVALID) { |
181 | // not sure we need to rebalance all the time, this can be done less often |
182 | refit_upward(add_node.parent_id); |
183 | } |
184 | //refit_upward_and_balance(add_node.parent_id); |
185 | } |
186 | |
187 | return true; |
188 | } |
189 | |
190 | void item_remove(BVHHandle p_handle) { |
191 | uint32_t ref_id = p_handle.id(); |
192 | |
193 | uint32_t tree_id = _handle_get_tree_id(p_handle); |
194 | |
195 | VERBOSE_PRINT("item_remove [" + itos(ref_id) + "] " ); |
196 | |
197 | //////////////////////////////////////// |
198 | // remove the active reference from the list for slow incremental optimize |
199 | // this list must be kept in sync with the references as they are added or removed. |
200 | uint32_t active_ref_id = _extra[ref_id].active_ref_id; |
201 | uint32_t ref_id_moved_back = _active_refs[_active_refs.size() - 1]; |
202 | |
203 | // swap back and decrement for fast unordered remove |
204 | _active_refs[active_ref_id] = ref_id_moved_back; |
205 | _active_refs.resize(_active_refs.size() - 1); |
206 | |
207 | // keep the moved active reference up to date |
208 | _extra[ref_id_moved_back].active_ref_id = active_ref_id; |
209 | //////////////////////////////////////// |
210 | |
211 | // remove the item from the node (only if active) |
212 | if (_refs[ref_id].is_active()) { |
213 | node_remove_item(ref_id, tree_id); |
214 | } |
215 | |
216 | // remove the item reference |
217 | _refs.free(ref_id); |
218 | _extra.free(ref_id); |
219 | if (USE_PAIRS) { |
220 | _pairs.free(ref_id); |
221 | } |
222 | |
223 | // don't think refit_all is necessary? |
224 | //refit_all(_tree_id); |
225 | |
226 | #ifdef BVH_VERBOSE_TREE |
227 | _debug_recursive_print_tree(tree_id); |
228 | #endif |
229 | } |
230 | |
231 | // returns success |
232 | bool item_activate(BVHHandle p_handle, const BOUNDS &p_aabb) { |
233 | uint32_t ref_id = p_handle.id(); |
234 | ItemRef &ref = _refs[ref_id]; |
235 | if (ref.is_active()) { |
236 | // noop |
237 | return false; |
238 | } |
239 | |
240 | // add to tree |
241 | BVHABB_CLASS abb; |
242 | abb.from(p_aabb); |
243 | |
244 | uint32_t tree_id = _handle_get_tree_id(p_handle); |
245 | |
246 | // we must choose where to add to tree |
247 | ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb); |
248 | _node_add_item(ref.tnode_id, ref_id, abb); |
249 | |
250 | refit_upward_and_balance(ref.tnode_id, tree_id); |
251 | |
252 | return true; |
253 | } |
254 | |
255 | // returns success |
256 | bool item_deactivate(BVHHandle p_handle) { |
257 | uint32_t ref_id = p_handle.id(); |
258 | ItemRef &ref = _refs[ref_id]; |
259 | if (!ref.is_active()) { |
260 | // noop |
261 | return false; |
262 | } |
263 | |
264 | uint32_t tree_id = _handle_get_tree_id(p_handle); |
265 | |
266 | // remove from tree |
267 | BVHABB_CLASS abb; |
268 | node_remove_item(ref_id, tree_id, &abb); |
269 | |
270 | // mark as inactive |
271 | ref.set_inactive(); |
272 | return true; |
273 | } |
274 | |
275 | bool item_get_active(BVHHandle p_handle) const { |
276 | uint32_t ref_id = p_handle.id(); |
277 | const ItemRef &ref = _refs[ref_id]; |
278 | return ref.is_active(); |
279 | } |
280 | |
281 | // during collision testing, we want to set the mask and whether pairable for the item testing from |
282 | void item_fill_cullparams(BVHHandle p_handle, CullParams &r_params) const { |
283 | uint32_t ref_id = p_handle.id(); |
284 | const ItemExtra & = _extra[ref_id]; |
285 | |
286 | // which trees does this item want to collide detect against? |
287 | r_params.tree_collision_mask = extra.tree_collision_mask; |
288 | |
289 | // The testing user defined object is passed to the user defined cull check function |
290 | // for masks etc. This is usually a dummy object of type T with masks set. |
291 | // However, if not using the cull_check callback (i.e. returning true), you can pass |
292 | // a nullptr instead of dummy object, as it will not be used. |
293 | r_params.tester = extra.userdata; |
294 | } |
295 | |
296 | bool item_is_pairable(const BVHHandle &p_handle) { |
297 | uint32_t ref_id = p_handle.id(); |
298 | const ItemExtra & = _extra[ref_id]; |
299 | return extra.pairable != 0; |
300 | } |
301 | |
302 | void item_get_ABB(const BVHHandle &p_handle, BVHABB_CLASS &r_abb) { |
303 | // change tree? |
304 | uint32_t ref_id = p_handle.id(); |
305 | const ItemRef &ref = _refs[ref_id]; |
306 | |
307 | TNode &tnode = _nodes[ref.tnode_id]; |
308 | TLeaf &leaf = _node_get_leaf(tnode); |
309 | |
310 | r_abb = leaf.get_aabb(ref.item_id); |
311 | } |
312 | |
313 | bool item_set_tree(const BVHHandle &p_handle, uint32_t p_tree_id, uint32_t p_tree_collision_mask) { |
314 | // change tree? |
315 | uint32_t ref_id = p_handle.id(); |
316 | |
317 | ItemExtra &ex = _extra[ref_id]; |
318 | ItemRef &ref = _refs[ref_id]; |
319 | |
320 | bool active = ref.is_active(); |
321 | bool tree_changed = ex.tree_id != p_tree_id; |
322 | bool mask_changed = ex.tree_collision_mask != p_tree_collision_mask; |
323 | bool state_changed = tree_changed | mask_changed; |
324 | |
325 | // Keep an eye on this for bugs of not noticing changes to objects, |
326 | // especially when changing client user masks that will not be detected as a change |
327 | // in the BVH. You may need to force a collision check in this case with recheck_pairs(). |
328 | |
329 | if (active && (tree_changed | mask_changed)) { |
330 | // record abb |
331 | TNode &tnode = _nodes[ref.tnode_id]; |
332 | TLeaf &leaf = _node_get_leaf(tnode); |
333 | BVHABB_CLASS abb = leaf.get_aabb(ref.item_id); |
334 | |
335 | // make sure current tree is correct prior to changing |
336 | uint32_t tree_id = _handle_get_tree_id(p_handle); |
337 | |
338 | // remove from old tree |
339 | node_remove_item(ref_id, tree_id); |
340 | |
341 | // we must set the pairable AFTER getting the current tree |
342 | // because the pairable status determines which tree |
343 | ex.tree_id = p_tree_id; |
344 | ex.tree_collision_mask = p_tree_collision_mask; |
345 | |
346 | // add to new tree |
347 | tree_id = _handle_get_tree_id(p_handle); |
348 | create_root_node(tree_id); |
349 | |
350 | // we must choose where to add to tree |
351 | ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb); |
352 | bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb); |
353 | |
354 | // only need to refit from the PARENT |
355 | if (needs_refit) { |
356 | // only need to refit from the parent |
357 | const TNode &add_node = _nodes[ref.tnode_id]; |
358 | if (add_node.parent_id != BVHCommon::INVALID) { |
359 | refit_upward_and_balance(add_node.parent_id, tree_id); |
360 | } |
361 | } |
362 | } else { |
363 | // always keep this up to date |
364 | ex.tree_id = p_tree_id; |
365 | ex.tree_collision_mask = p_tree_collision_mask; |
366 | } |
367 | |
368 | return state_changed; |
369 | } |
370 | |
371 | void incremental_optimize() { |
372 | // first update all aabbs as one off step.. |
373 | // this is cheaper than doing it on each move as each leaf may get touched multiple times |
374 | // in a frame. |
375 | for (int n = 0; n < NUM_TREES; n++) { |
376 | if (_root_node_id[n] != BVHCommon::INVALID) { |
377 | refit_branch(_root_node_id[n]); |
378 | } |
379 | } |
380 | |
381 | // now do small section reinserting to get things moving |
382 | // gradually, and keep items in the right leaf |
383 | if (_current_active_ref >= _active_refs.size()) { |
384 | _current_active_ref = 0; |
385 | } |
386 | |
387 | // special case |
388 | if (!_active_refs.size()) { |
389 | return; |
390 | } |
391 | |
392 | uint32_t ref_id = _active_refs[_current_active_ref++]; |
393 | |
394 | _logic_item_remove_and_reinsert(ref_id); |
395 | |
396 | #ifdef BVH_VERBOSE |
397 | /* |
398 | // memory use |
399 | int mem_refs = _refs.estimate_memory_use(); |
400 | int mem_nodes = _nodes.estimate_memory_use(); |
401 | int mem_leaves = _leaves.estimate_memory_use(); |
402 | |
403 | String sz; |
404 | sz += "mem_refs : " + itos(mem_refs) + " "; |
405 | sz += "mem_nodes : " + itos(mem_nodes) + " "; |
406 | sz += "mem_leaves : " + itos(mem_leaves) + " "; |
407 | sz += ", num nodes : " + itos(_nodes.size()); |
408 | print_line(sz); |
409 | */ |
410 | #endif |
411 | } |
412 | |
413 | void update() { |
414 | incremental_optimize(); |
415 | |
416 | // keep the expansion values up to date with the world bound |
417 | //#define BVH_ALLOW_AUTO_EXPANSION |
418 | #ifdef BVH_ALLOW_AUTO_EXPANSION |
419 | if (_auto_node_expansion || _auto_pairing_expansion) { |
420 | BVHABB_CLASS world_bound; |
421 | world_bound.set_to_max_opposite_extents(); |
422 | |
423 | bool bound_valid = false; |
424 | |
425 | for (int n = 0; n < NUM_TREES; n++) { |
426 | uint32_t node_id = _root_node_id[n]; |
427 | if (node_id != BVHCommon::INVALID) { |
428 | world_bound.merge(_nodes[node_id].aabb); |
429 | bound_valid = true; |
430 | } |
431 | } |
432 | |
433 | // if there are no nodes, do nothing, but if there are... |
434 | if (bound_valid) { |
435 | BOUNDS bb; |
436 | world_bound.to(bb); |
437 | real_t size = bb.get_longest_axis_size(); |
438 | |
439 | // automatic AI decision for best parameters. |
440 | // These can be overridden in project settings. |
441 | |
442 | // these magic numbers are determined by experiment |
443 | if (_auto_node_expansion) { |
444 | _node_expansion = size * 0.025; |
445 | } |
446 | if (_auto_pairing_expansion) { |
447 | _pairing_expansion = size * 0.009; |
448 | } |
449 | } |
450 | } |
451 | #endif |
452 | } |
453 | |
454 | void params_set_pairing_expansion(real_t p_value) { |
455 | if (p_value < 0.0) { |
456 | #ifdef BVH_ALLOW_AUTO_EXPANSION |
457 | _auto_pairing_expansion = true; |
458 | #endif |
459 | return; |
460 | } |
461 | #ifdef BVH_ALLOW_AUTO_EXPANSION |
462 | _auto_pairing_expansion = false; |
463 | #endif |
464 | |
465 | _pairing_expansion = p_value; |
466 | |
467 | // calculate shrinking threshold |
468 | const real_t fudge_factor = 1.1; |
469 | _aabb_shrinkage_threshold = _pairing_expansion * POINT::AXIS_COUNT * 2.0 * fudge_factor; |
470 | } |
471 | |
472 | // This routine is not just an enclose check, it also checks for special case of shrinkage |
473 | bool expanded_aabb_encloses_not_shrink(const BOUNDS &p_expanded_aabb, const BOUNDS &p_aabb) const { |
474 | if (!p_expanded_aabb.encloses(p_aabb)) { |
475 | return false; |
476 | } |
477 | |
478 | // Check for special case of shrinkage. If the aabb has shrunk |
479 | // significantly we want to create a new expanded bound, because |
480 | // the previous expanded bound will have diverged significantly. |
481 | const POINT &exp_size = p_expanded_aabb.size; |
482 | const POINT &new_size = p_aabb.size; |
483 | |
484 | real_t exp_l = 0.0; |
485 | real_t new_l = 0.0; |
486 | |
487 | for (int i = 0; i < POINT::AXIS_COUNT; ++i) { |
488 | exp_l += exp_size[i]; |
489 | new_l += new_size[i]; |
490 | } |
491 | |
492 | // is difference above some metric |
493 | real_t diff = exp_l - new_l; |
494 | if (diff < _aabb_shrinkage_threshold) { |
495 | return true; |
496 | } |
497 | |
498 | return false; |
499 | } |
500 | |