123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- public:
- 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) {
- #ifdef BVH_VERBOSE_TREE
- VERBOSE_PRINT("\nitem_add BEFORE");
- _debug_recursive_print_tree(p_tree_id);
- VERBOSE_PRINT("\n");
- #endif
- BVHABB_CLASS abb;
- abb.from(p_aabb);
- // NOTE that we do not expand the AABB for the first create even if
- // leaf expansion is switched on. This is for two reasons:
- // (1) We don't know if this object will move in future, in which case a non-expanded
- // bound would be better...
- // (2) We don't yet know how many objects will be paired, which is used to modify
- // the expansion margin.
- // handle to be filled with the new item ref
- BVHHandle handle;
- // ref id easier to pass around than handle
- uint32_t ref_id;
- // this should never fail
- ItemRef *ref = _refs.request(ref_id);
- // the extra data should be parallel list to the references
- uint32_t extra_id;
- ItemExtra *extra = _extra.request(extra_id);
- BVH_ASSERT(extra_id == ref_id);
- // pairs info
- if (USE_PAIRS) {
- uint32_t pairs_id;
- ItemPairs *pairs = _pairs.request(pairs_id);
- pairs->clear();
- BVH_ASSERT(pairs_id == ref_id);
- }
- extra->subindex = p_subindex;
- extra->userdata = p_userdata;
- extra->last_updated_tick = 0;
- // add an active reference to the list for slow incremental optimize
- // this list must be kept in sync with the references as they are added or removed.
- extra->active_ref_id = _active_refs.size();
- _active_refs.push_back(ref_id);
- extra->tree_id = p_tree_id;
- extra->tree_collision_mask = p_tree_collision_mask;
- // assign to handle to return
- handle.set_id(ref_id);
- create_root_node(p_tree_id);
- // we must choose where to add to tree
- if (p_active) {
- ref->tnode_id = _logic_choose_item_add_node(_root_node_id[p_tree_id], abb);
- bool refit = _node_add_item(ref->tnode_id, ref_id, abb);
- if (refit) {
- // only need to refit from the parent
- const TNode &add_node = _nodes[ref->tnode_id];
- if (add_node.parent_id != BVHCommon::INVALID) {
- refit_upward_and_balance(add_node.parent_id, p_tree_id);
- }
- }
- } else {
- ref->set_inactive();
- }
- #ifdef BVH_VERBOSE
- // memory use
- int mem = _refs.estimate_memory_use();
- mem += _nodes.estimate_memory_use();
- String sz = _debug_aabb_to_string(abb);
- VERBOSE_PRINT("\titem_add [" + itos(ref_id) + "] " + itos(_refs.used_size()) + " refs,\t" + itos(_nodes.used_size()) + " nodes " + sz);
- VERBOSE_PRINT("mem use : " + itos(mem) + ", num nodes reserved : " + itos(_nodes.reserved_size()));
- #endif
- return handle;
- }
- void _debug_print_refs() {
- #ifdef BVH_VERBOSE_TREE
- print_line("refs.....");
- for (int n = 0; n < _refs.size(); n++) {
- const ItemRef &ref = _refs[n];
- print_line("tnode_id " + itos(ref.tnode_id) + ", item_id " + itos(ref.item_id));
- }
- #endif
- }
- // returns false if noop
- bool item_move(BVHHandle p_handle, const BOUNDS &p_aabb) {
- uint32_t ref_id = p_handle.id();
- // get the reference
- ItemRef &ref = _refs[ref_id];
- if (!ref.is_active()) {
- return false;
- }
- BVHABB_CLASS abb;
- abb.from(p_aabb);
- #ifdef BVH_EXPAND_LEAF_AABBS
- if (USE_PAIRS) {
- // scale the pairing expansion by the number of pairs.
- abb.expand(_pairs[ref_id].scale_expansion_margin(_pairing_expansion));
- } else {
- abb.expand(_pairing_expansion);
- }
- #endif
- BVH_ASSERT(ref.tnode_id != BVHCommon::INVALID);
- TNode &tnode = _nodes[ref.tnode_id];
- // does it fit within the current leaf aabb?
- if (tnode.aabb.is_other_within(abb)) {
- // do nothing .. fast path .. not moved enough to need refit
- // however we WILL update the exact aabb in the leaf, as this will be needed
- // for accurate collision detection
- TLeaf &leaf = _node_get_leaf(tnode);
- BVHABB_CLASS &leaf_abb = leaf.get_aabb(ref.item_id);
- // no change?
- #ifdef BVH_EXPAND_LEAF_AABBS
- BOUNDS leaf_aabb;
- leaf_abb.to(leaf_aabb);
- // This test should pass in a lot of cases, and by returning false we can avoid
- // collision pairing checks later, which greatly reduces processing.
- if (expanded_aabb_encloses_not_shrink(leaf_aabb, p_aabb)) {
- return false;
- }
- #else
- if (leaf_abb == abb) {
- return false;
- }
- #endif
- #ifdef BVH_VERBOSE_MOVES
- print_line("item_move " + itos(p_handle.id()) + "(within tnode aabb) : " + _debug_aabb_to_string(abb));
- #endif
- leaf_abb = abb;
- _integrity_check_all();
- return true;
- }
- #ifdef BVH_VERBOSE_MOVES
- print_line("item_move " + itos(p_handle.id()) + "(outside tnode aabb) : " + _debug_aabb_to_string(abb));
- #endif
- uint32_t tree_id = _handle_get_tree_id(p_handle);
- // remove and reinsert
- node_remove_item(ref_id, tree_id);
- // we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
- // add to the tree
- bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
- // only need to refit from the PARENT
- if (needs_refit) {
- // only need to refit from the parent
- const TNode &add_node = _nodes[ref.tnode_id];
- if (add_node.parent_id != BVHCommon::INVALID) {
- // not sure we need to rebalance all the time, this can be done less often
- refit_upward(add_node.parent_id);
- }
- //refit_upward_and_balance(add_node.parent_id);
- }
- return true;
- }
- void item_remove(BVHHandle p_handle) {
- uint32_t ref_id = p_handle.id();
- uint32_t tree_id = _handle_get_tree_id(p_handle);
- VERBOSE_PRINT("item_remove [" + itos(ref_id) + "] ");
- ////////////////////////////////////////
- // remove the active reference from the list for slow incremental optimize
- // this list must be kept in sync with the references as they are added or removed.
- uint32_t active_ref_id = _extra[ref_id].active_ref_id;
- uint32_t ref_id_moved_back = _active_refs[_active_refs.size() - 1];
- // swap back and decrement for fast unordered remove
- _active_refs[active_ref_id] = ref_id_moved_back;
- _active_refs.resize(_active_refs.size() - 1);
- // keep the moved active reference up to date
- _extra[ref_id_moved_back].active_ref_id = active_ref_id;
- ////////////////////////////////////////
- // remove the item from the node (only if active)
- if (_refs[ref_id].is_active()) {
- node_remove_item(ref_id, tree_id);
- }
- // remove the item reference
- _refs.free(ref_id);
- _extra.free(ref_id);
- if (USE_PAIRS) {
- _pairs.free(ref_id);
- }
- // don't think refit_all is necessary?
- //refit_all(_tree_id);
- #ifdef BVH_VERBOSE_TREE
- _debug_recursive_print_tree(tree_id);
- #endif
- }
- // returns success
- bool item_activate(BVHHandle p_handle, const BOUNDS &p_aabb) {
- uint32_t ref_id = p_handle.id();
- ItemRef &ref = _refs[ref_id];
- if (ref.is_active()) {
- // noop
- return false;
- }
- // add to tree
- BVHABB_CLASS abb;
- abb.from(p_aabb);
- uint32_t tree_id = _handle_get_tree_id(p_handle);
- // we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
- _node_add_item(ref.tnode_id, ref_id, abb);
- refit_upward_and_balance(ref.tnode_id, tree_id);
- return true;
- }
- // returns success
- bool item_deactivate(BVHHandle p_handle) {
- uint32_t ref_id = p_handle.id();
- ItemRef &ref = _refs[ref_id];
- if (!ref.is_active()) {
- // noop
- return false;
- }
- uint32_t tree_id = _handle_get_tree_id(p_handle);
- // remove from tree
- BVHABB_CLASS abb;
- node_remove_item(ref_id, tree_id, &abb);
- // mark as inactive
- ref.set_inactive();
- return true;
- }
- bool item_get_active(BVHHandle p_handle) const {
- uint32_t ref_id = p_handle.id();
- const ItemRef &ref = _refs[ref_id];
- return ref.is_active();
- }
- // during collision testing, we want to set the mask and whether pairable for the item testing from
- void item_fill_cullparams(BVHHandle p_handle, CullParams &r_params) const {
- uint32_t ref_id = p_handle.id();
- const ItemExtra &extra = _extra[ref_id];
- // which trees does this item want to collide detect against?
- r_params.tree_collision_mask = extra.tree_collision_mask;
- // The testing user defined object is passed to the user defined cull check function
- // for masks etc. This is usually a dummy object of type T with masks set.
- // However, if not using the cull_check callback (i.e. returning true), you can pass
- // a nullptr instead of dummy object, as it will not be used.
- r_params.tester = extra.userdata;
- }
- bool item_is_pairable(const BVHHandle &p_handle) {
- uint32_t ref_id = p_handle.id();
- const ItemExtra &extra = _extra[ref_id];
- return extra.pairable != 0;
- }
- void item_get_ABB(const BVHHandle &p_handle, BVHABB_CLASS &r_abb) {
- // change tree?
- uint32_t ref_id = p_handle.id();
- const ItemRef &ref = _refs[ref_id];
- TNode &tnode = _nodes[ref.tnode_id];
- TLeaf &leaf = _node_get_leaf(tnode);
- r_abb = leaf.get_aabb(ref.item_id);
- }
- bool item_set_tree(const BVHHandle &p_handle, uint32_t p_tree_id, uint32_t p_tree_collision_mask) {
- // change tree?
- uint32_t ref_id = p_handle.id();
- ItemExtra &ex = _extra[ref_id];
- ItemRef &ref = _refs[ref_id];
- bool active = ref.is_active();
- bool tree_changed = ex.tree_id != p_tree_id;
- bool mask_changed = ex.tree_collision_mask != p_tree_collision_mask;
- bool state_changed = tree_changed | mask_changed;
- // Keep an eye on this for bugs of not noticing changes to objects,
- // especially when changing client user masks that will not be detected as a change
- // in the BVH. You may need to force a collision check in this case with recheck_pairs().
- if (active && (tree_changed | mask_changed)) {
- // record abb
- TNode &tnode = _nodes[ref.tnode_id];
- TLeaf &leaf = _node_get_leaf(tnode);
- BVHABB_CLASS abb = leaf.get_aabb(ref.item_id);
- // make sure current tree is correct prior to changing
- uint32_t tree_id = _handle_get_tree_id(p_handle);
- // remove from old tree
- node_remove_item(ref_id, tree_id);
- // we must set the pairable AFTER getting the current tree
- // because the pairable status determines which tree
- ex.tree_id = p_tree_id;
- ex.tree_collision_mask = p_tree_collision_mask;
- // add to new tree
- tree_id = _handle_get_tree_id(p_handle);
- create_root_node(tree_id);
- // we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
- bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
- // only need to refit from the PARENT
- if (needs_refit) {
- // only need to refit from the parent
- const TNode &add_node = _nodes[ref.tnode_id];
- if (add_node.parent_id != BVHCommon::INVALID) {
- refit_upward_and_balance(add_node.parent_id, tree_id);
- }
- }
- } else {
- // always keep this up to date
- ex.tree_id = p_tree_id;
- ex.tree_collision_mask = p_tree_collision_mask;
- }
- return state_changed;
- }
- void incremental_optimize() {
- // first update all aabbs as one off step..
- // this is cheaper than doing it on each move as each leaf may get touched multiple times
- // in a frame.
- for (int n = 0; n < NUM_TREES; n++) {
- if (_root_node_id[n] != BVHCommon::INVALID) {
- refit_branch(_root_node_id[n]);
- }
- }
- // now do small section reinserting to get things moving
- // gradually, and keep items in the right leaf
- if (_current_active_ref >= _active_refs.size()) {
- _current_active_ref = 0;
- }
- // special case
- if (!_active_refs.size()) {
- return;
- }
- uint32_t ref_id = _active_refs[_current_active_ref++];
- _logic_item_remove_and_reinsert(ref_id);
- #ifdef BVH_VERBOSE
- /*
- // memory use
- int mem_refs = _refs.estimate_memory_use();
- int mem_nodes = _nodes.estimate_memory_use();
- int mem_leaves = _leaves.estimate_memory_use();
- String sz;
- sz += "mem_refs : " + itos(mem_refs) + " ";
- sz += "mem_nodes : " + itos(mem_nodes) + " ";
- sz += "mem_leaves : " + itos(mem_leaves) + " ";
- sz += ", num nodes : " + itos(_nodes.size());
- print_line(sz);
- */
- #endif
- }
- void update() {
- incremental_optimize();
- // keep the expansion values up to date with the world bound
- //#define BVH_ALLOW_AUTO_EXPANSION
- #ifdef BVH_ALLOW_AUTO_EXPANSION
- if (_auto_node_expansion || _auto_pairing_expansion) {
- BVHABB_CLASS world_bound;
- world_bound.set_to_max_opposite_extents();
- bool bound_valid = false;
- for (int n = 0; n < NUM_TREES; n++) {
- uint32_t node_id = _root_node_id[n];
- if (node_id != BVHCommon::INVALID) {
- world_bound.merge(_nodes[node_id].aabb);
- bound_valid = true;
- }
- }
- // if there are no nodes, do nothing, but if there are...
- if (bound_valid) {
- BOUNDS bb;
- world_bound.to(bb);
- real_t size = bb.get_longest_axis_size();
- // automatic AI decision for best parameters.
- // These can be overridden in project settings.
- // these magic numbers are determined by experiment
- if (_auto_node_expansion) {
- _node_expansion = size * 0.025;
- }
- if (_auto_pairing_expansion) {
- _pairing_expansion = size * 0.009;
- }
- }
- }
- #endif
- }
- void params_set_pairing_expansion(real_t p_value) {
- if (p_value < 0.0) {
- #ifdef BVH_ALLOW_AUTO_EXPANSION
- _auto_pairing_expansion = true;
- #endif
- return;
- }
- #ifdef BVH_ALLOW_AUTO_EXPANSION
- _auto_pairing_expansion = false;
- #endif
- _pairing_expansion = p_value;
- // calculate shrinking threshold
- const real_t fudge_factor = 1.1;
- _aabb_shrinkage_threshold = _pairing_expansion * POINT::AXIS_COUNT * 2.0 * fudge_factor;
- }
- // This routine is not just an enclose check, it also checks for special case of shrinkage
- bool expanded_aabb_encloses_not_shrink(const BOUNDS &p_expanded_aabb, const BOUNDS &p_aabb) const {
- if (!p_expanded_aabb.encloses(p_aabb)) {
- return false;
- }
- // Check for special case of shrinkage. If the aabb has shrunk
- // significantly we want to create a new expanded bound, because
- // the previous expanded bound will have diverged significantly.
- const POINT &exp_size = p_expanded_aabb.size;
- const POINT &new_size = p_aabb.size;
- real_t exp_l = 0.0;
- real_t new_l = 0.0;
- for (int i = 0; i < POINT::AXIS_COUNT; ++i) {
- exp_l += exp_size[i];
- new_l += new_size[i];
- }
- // is difference above some metric
- real_t diff = exp_l - new_l;
- if (diff < _aabb_shrinkage_threshold) {
- return true;
- }
- return false;
- }
|