#include "V8World/World.h" #include "V8World/ContactManager.h" #include "Util/Math.h" #include "rbx/Debug.h" #include "RbxAssert.h" #include "G3D/CollisionDetection.h" #include namespace RBX { float SpatialHashStatic::hashGridSize(int level) {return (float)(cellMinSize << level);} float SpatialHashStatic::hashGridRecip(int level) {return 1.0f / SpatialHashStatic::hashGridSize(level);} size_t SpatialHashStatic::numBuckets(int level) {return 65536;} Vector3int32 SpatialHashStatic::realToHashGrid(int level, const Vector3& realPoint) { Vector3 gridPoint = realPoint * SpatialHashStatic::hashGridRecip(level); Vector3int32 hashGrid = Vector3int32::floor(gridPoint); // 4 grids per hash bucket return hashGrid; } ExtentsInt32 SpatialHashStatic::scaleExtents(int smallLevel, int bigLevel, const ExtentsInt32& smallExtents) { RBXASSERT_SLOW(smallLevel < bigLevel); int delta = bigLevel - smallLevel; return smallExtents.shiftRight(delta); } Extents SpatialHashStatic::hashGridToRealExtents(int level, const Vector3int32& hashGrid) { Extents answer( hashGridToReal(level, hashGrid), hashGridToReal(level, hashGrid + Vector3int32::one()) ); RBXASSERT_VERY_FAST(Math::isIntegerVector3(answer.min())); RBXASSERT_VERY_FAST(Math::isIntegerVector3(answer.max())); return answer; } Vector3 SpatialHashStatic::hashGridToReal(int level, const Vector3int32& hashGrid) { return hashGrid.toVector3() * SpatialHashStatic::hashGridSize(level); } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// #define SHP template // statics - templated SpatialHash SHP const int SpatialHash::rootLevel = MAX_LEVELS-1; SHP SpatialHash::SpatialHash(World* world, ContactManager* contactManager, int maxCellsPerPrimitive) : world(world) , contactManager(contactManager) , maxCellsPerPrimitive(maxCellsPerPrimitive) , nodesOut(0) , maxBucket(0) , numTreeNodesTotal(0) { setup(); } SHP void SpatialHash::setup() { for (int i=0; i::~SpatialHash() { cleanup(); } SHP void SpatialHash::cleanup() { // nb: make all checks match with fastClear as well. RBXASSERT_SPATIAL_HASH(validateTallyTreeNodes()); RBXASSERT_SPATIAL_HASH(validateNoNodesOut()); RBXASSERT(nodesOut == 0); } SHP const Extents SpatialHash::calcNewExtents(Primitive* p) { return SpatialHashStatic::safeExtents(p->getFastFuzzyExtents()); } SHP typename SpatialHash::SpatialNode* SpatialHash::newNode(int level, int hash, const Vector3int32& grid) { SpatialNode* answer; answer = new SpatialNode(level, hash, grid); if (level == 0) { answer->treeNode = NULL; } else { answer->treeNode = findTreeNode(level, hash, grid); if (! answer->treeNode) answer->treeNode = createTreeNode(level, hash, grid); answer->treeNode->refByPrimitives++; RBXASSERT(level == answer->treeNode->level); } return answer; } SHP typename SpatialHash::TreeNode *SpatialHash::findTreeNode( int level, int hash, const Vector3int32 &gridCoord) { TreeNode * tn = hashTables[level][hash].treeNodes; while (tn) { RBXASSERT(tn->hashId == hash); RBXASSERT(tn->level == level); if (tn->gridId == gridCoord) { return tn; } tn = tn->next; } return NULL; } SHP typename SpatialHash::TreeNode *SpatialHash::createTreeNode( int level, int hash, const Vector3int32 &gridCoord) { TreeNode *tn; tn = new TreeNode(); numTreeNodesTotal ++; tn->refByPrimitives = 0; tn->level = level; tn->hashId = hash; tn->gridId = gridCoord; RBXASSERT_SPATIAL_HASH(validateTreeNodeNotHere(tn, level, hash)); tn->next = hashTables[level][hash].treeNodes; hashTables[level][hash].treeNodes = tn; return tn; } SHP void SpatialHash::_retireTreeNode(TreeNode* tn) { TreeNode** p = &hashTables[tn->level][tn->hashId].treeNodes; while (*p != tn) { p = &((*p)->next); } *p = tn->next; tn->next = NULL; delete tn; numTreeNodesTotal --; } SHP void SpatialHash::retireTreeNode(TreeNode* tn) { if (--tn->refByPrimitives) { // still being used, do nothing return; } if (tn->childMask) { // This tree node still has children, i.e. it's part of the tree. // Therefore, even though no SpatialNode is using this treenode (which // in turn means this treenode corresponds to no primitives), it // needs to be kept. return; } // this treenode's refcount is 0 AND has no children //retire it and adjust hierarchy int tnlevel = tn->level; Vector3int32 tngridId(tn->gridId); _retireTreeNode(tn); // notify ancestors that this has been removed removeTreeNodeChild(tnlevel, tngridId); } SHP void SpatialHash::insertNodeToPrimitive(SpatialNode* node, Primitive* p, const Vector3int32& grid, int hash) { RBXASSERT(node->level == p->getSpatialNodeLevel()); node->primitive = p; node->gridId = grid; node->hashId = hash; RBXASSERT_SPATIAL_HASH(validateInsertNodeToPrimitive(node, p, grid, hash)); } SHP void SpatialHash::returnNode(SpatialNode* node) { delete node; } SHP void SpatialHash::removeNodeFromHash(SpatialNode* remove) { SpatialNode** nodePtr = &hashTables[remove->getLevel()][remove->hashId].nodes; while (*nodePtr != remove) { nodePtr = &((*nodePtr)->nextHashLink); } *nodePtr = remove->nextHashLink; } SHP typename SpatialHash::SpatialNode* SpatialHash::findNode(Primitive* p, const Vector3int32& grid) { const int l = p->getSpatialNodeLevel(); RBXASSERT_SPATIAL_HASH(static_cast(p->spatialNodes)->getLevel() == l); RBXASSERT(SpatialHashStatic::numBuckets(l) == hashTables[l].size()); int hash = SpatialHashStatic::getHash(l, grid); SpatialNode* node = hashTables[l][hash].nodes; if (node == NULL) return NULL; while ((node->primitive != p) || (node->gridId != grid)) { node = node->nextHashLink; if (! node) { break; } } return node; } SHP bool SpatialHash::oldExtentsOverlap(Primitive* me, Primitive* other) { const int myLevel = me->getSpatialNodeLevel(); const int otherLevel = other->getSpatialNodeLevel(); RBXASSERT(myLevel >= 0); RBXASSERT(otherLevel >= 0); bool answer = false; if (myLevel == otherLevel) { answer = ExtentsInt32::overlapsOrTouches(me->getOldSpatialExtents(), other->getOldSpatialExtents()); } else { Primitive* big = (myLevel > otherLevel) ? me : other; Primitive* smallPrim = (big == me) ? other : me; ExtentsInt32 smallInBig = SpatialHashStatic::scaleExtents(smallPrim->getSpatialNodeLevel(), big->getSpatialNodeLevel(), smallPrim->getOldSpatialExtents()); answer = ExtentsInt32::overlapsOrTouches(big->getOldSpatialExtents(), smallInBig); } return answer; } SHP bool SpatialHash::findOtherNodesInLevel0Cell(SpatialNode* destroy) { RBXASSERT(destroy->getLevel() == 0); Vector3int32 g = destroy->gridId; int hash = destroy->hashId; SpatialNode* node_sameHash = hashTables[0][hash].nodes; while (node_sameHash) { if (node_sameHash->gridId == g) { #ifdef _DEBUG Primitive* other = node_sameHash->primitive; RBXASSERT(other != destroy->primitive); #endif return true; } node_sameHash = node_sameHash->nextHashLink; } return false; } SHP void SpatialHash::destroyNode(SpatialNode* destroy) { RBXASSERT_SPATIAL_HASH(validateTallyTreeNodes()); RBXASSERT_SPATIAL_HASH(validateRemoveNodeFromPrimitive(destroy)); RBXASSERT_SPATIAL_HASH(destroy->getLevel() == destroy->primitive->getSpatialNodeLevel()); // This is insignificant for timing removeNodeFromHash(destroy); // for all lower level nodes if (destroy->getLevel() > 0) { RBXASSERT(destroy->treeNode); retireTreeNode(destroy->treeNode); } else { if (!findOtherNodesInLevel0Cell(destroy)) { // this (conceptual) leaf cell is empty; maintain hierachy removeTreeNodeChild(0, destroy->gridId); } } returnNode(destroy); nodesOut--; RBXASSERT_SPATIAL_HASH(validateTallyTreeNodes()); } SHP void SpatialHash::removeTreeNodeChild(int childLevel, Vector3int32 &childGridCoord) { int childHash; RBXASSERT((childHash = SpatialHashStatic::getHash(childLevel, childGridCoord), 1)); for (int l=childLevel+1; l < MAX_LEVELS; l++) { Vector3int32 g; g.x = childGridCoord.x>>(l-childLevel); g.y = childGridCoord.y>>(l-childLevel); g.z = childGridCoord.z>>(l-childLevel); int hash = SpatialHashStatic::getHash(l, g); TreeNode *tn = findTreeNode(l, hash, g); RBXASSERT(tn); int offset = ((childGridCoord.x>>(l-childLevel-1)) & 1)+ (((childGridCoord.y>>(l-childLevel-1)) & 1)<<1) + (((childGridCoord.z>>(l-childLevel-1)) & 1) << 2); RBXASSERT(tn->children[offset] == childHash); // remove child tn->removeChild(offset); // if there's still children if (tn->refByPrimitives==0 && !tn->childMask) { // this means the child that we've just removed is the only // reason why this treenode existed. Now it's removed _retireTreeNode(tn); } else { // if this treenode is not deleted, no need to check parent break; } childHash = hash; } } SHP bool SpatialHash::hashHasPrimitive(int level, Primitive* p, int hash, const Vector3int32& grid) { SpatialNode* test = hashTables[level][hash].nodes; while (test) { if ((test->primitive == p) && (test->gridId == grid)) { return true; } test = test->nextHashLink; } return false; } SHP void SpatialHash::addContactFromChildren(TreeNode *tn, Primitive *p) { unsigned short *children = tn->children; Vector3int32 baseGrid; baseGrid.x = tn->gridId.x << 1; baseGrid.y = tn->gridId.y << 1; baseGrid.z = tn->gridId.z << 1; for (int i=0; i<8; i++) { if (tn->hasChild(i)) { Vector3int32 g; g.x = baseGrid.x + (i & 1); g.y = baseGrid.y + ((i & 2)>>1); g.z = baseGrid.z + ((i & 4)>>2); RBXASSERT(tn->level - 1 >= 0); SpatialNode *tryNode = hashTables[ tn->level - 1 ][ children[i] ].nodes; while (tryNode) { Primitive* other = tryNode->primitive; if ((other != p) && (tryNode->gridId == g )) { if (Primitive::getContact(p, other) == NULL) { contactManager->onNewPair(p, other); } } else { RBXASSERT(g != tryNode->gridId); } tryNode = tryNode->nextHashLink; } //recursive if (tn->level > 1) { TreeNode *c = findTreeNode(tn->level - 1, children[i], g); RBXASSERT(c); addContactFromChildren(c, p); } } } } SHP void SpatialHash::addNode(Primitive* p, const Vector3int32& grid, bool addContact) { RBXASSERT(p->getSpatialNodeLevel() != -1); int level = p->getSpatialNodeLevel(); int hash = SpatialHashStatic::getHash(level, grid); RBXASSERT_SPATIAL_HASH(validateTallyTreeNodes()); RBXASSERT_VERY_FAST(!hashHasPrimitive(level, p, hash, grid)); nodesOut++; SpatialNode* addedNode = newNode(level, hash, grid); #ifdef _DEBUG if (level > 0) { TreeNode *tn_save = addedNode->treeNode; RBXASSERT(addedNode->getLevel() == tn_save->level); } #endif // 1. Put in the primitive's linked list insertNodeToPrimitive(addedNode, p, grid, hash); // 2. This hash's linked list of nodes SpatialNode* tryNode = hashTables[level][hash].nodes; addedNode->nextHashLink = tryNode; hashTables[level][hash].nodes = addedNode; // 3. Cycle through the pre-existing nodes - see if any are hit int numNodes = 1; // start with 1 - this one // For nodes at the same level, and higher levels nodes containing this node Vector3int32 g = grid; int prevHash = -1; bool needHiearchyUpdate = true; for (int l=level; l < MAX_LEVELS; l++) { if (l > level) { g.x >>= 1; g.y >>= 1; g.z >>= 1; hash = SpatialHashStatic::getHash(l, g); tryNode = hashTables[l][hash].nodes; } if (Primitive::hasGetFirstContact && addContact) { while (tryNode) { RBXASSERT( l < MAX_LEVELS); numNodes++; Primitive* other = tryNode->primitive; if ((other != p) && (tryNode->gridId == g)) { if (Primitive::getContact(p, other) == NULL) { contactManager->onNewPair(p, other); } } else { RBXASSERT(g != tryNode->gridId); } tryNode = tryNode->nextHashLink; } } // maintain hiearchy information if (l > level && needHiearchyUpdate) { TreeNode *tn = findTreeNode(l, hash, g); if (!tn) tn = createTreeNode(l, hash, g); int offset = ((grid.x>>(l-level-1)) & 1)+ (((grid.y>>(l-level-1)) & 1)<<1) + (((grid.z>>(l-level-1)) & 1) << 2); RBXASSERT(0<=offset && offset<=7 && prevHash >= 0); // establish child "pointer" RBXASSERT(!tn->hasChild(offset) || tn->children[offset]==prevHash); if (tn->hasChild(offset)) needHiearchyUpdate = false; // this and upper levels tree-nodes has already been set else tn->setChild(offset, prevHash); if (level > 0) { RBXASSERT(addedNode->treeNode && addedNode->getLevel() == addedNode->treeNode->level); } } // save immediately-lower-level hashId prevHash = hash; } // add contacts from children if (Primitive::hasGetFirstContact && addContact && addedNode->getLevel() > 0) { RBXASSERT(addedNode && addedNode->treeNode->level == addedNode->getLevel()); addContactFromChildren(addedNode->treeNode, p); } maxBucket = std::max(maxBucket, numNodes); RBXASSERT_SPATIAL_HASH(validateTallyTreeNodes()); } SHP int SpatialHash::computeLevel(const Primitive* p, const Extents& extents) { const float extra = static_cast(SpatialHashStatic::cellMinSize * 2.0); // extra buffer for thin objects Vector3 size = extents.size() + Vector3(extra, extra, extra); float volume = size.x * size.y * size.z; int maxLevel = p->requestFixed() ? SpatialHashStatic::maxLevelForAnchored : MAX_LEVELS - 1; float maxVolumeThisLevel = static_cast((SpatialHashStatic::cellMinSize * SpatialHashStatic::cellMinSize * SpatialHashStatic::cellMinSize)* maxCellsPerPrimitive); for (int answerLevel = 0; answerLevel < maxLevel; ++answerLevel) { if (volume < maxVolumeThisLevel) { return answerLevel; } maxVolumeThisLevel *= 8; } return maxLevel; } /* SHP void SpatialHash::computeMinMax(const Primitive* p, const Extents& extents, Vector3int32& min, Vector3int32& max) { RBXASSERT(p->getSpatialNodeLevel() != -1); computeMinMax(p->getSpatialNodeLevel(), extents, min, max); } */ SHP void SpatialHash::onPrimitiveAdded(Primitive* p, bool addContact) { if( contactManager->primitiveIsExcludedFromSpatialHash(p) ) return; primitiveAdded(p, addContact); if (!coarseMovementCallbacks.empty()) { typename CoarseMovementCallback::UpdateInfo info; info.updateType = CoarseMovementCallback::UpdateInfo::UPDATE_TYPE_Insert; info.newLevel = p->getSpatialNodeLevel(); info.newSpatialExtents = p->getOldSpatialExtents(); for (size_t i = 0; i < coarseMovementCallbacks.size(); ++i) { coarseMovementCallbacks[i]->coarsePrimitiveMovement(p, info); } } } // Note -use FastFuzzyExtents when not in the middle of a simulation step // Use FastFuzzyExtentsNoCompute when in a step SHP void SpatialHash::primitiveAdded(Primitive* p, bool addContact) { WriteValidator writeValidator(concurrencyValidator); RBXASSERT_SPATIAL_HASH(validateContacts(p)); RBXASSERT_SPATIAL_HASH(p->spatialNodes == NULL); RBXASSERT(p->getSpatialNodeLevel() == -1); Extents newExtentsFloat = calcNewExtents(p); int level = computeLevel(p, newExtentsFloat); Vector3int32 newMin, newMax; SpatialHashStatic::computeMinMax(level, newExtentsFloat, newMin, newMax); p->setSpatialNodeLevel(level); ExtentsInt32 newExtents(newMin, newMax); p->setOldSpatialExtents(newExtents); changeMinMax(p, &newExtents, NULL, &newExtents, addContact); RBXASSERT(p->getSpatialNodeLevel() == level); RBXASSERT_SPATIAL_HASH(validateContacts(p)); } SHP void SpatialHash::changeMinMax( Primitive* p, const ExtentsInt32* change, const ExtentsInt32* oldBox, const ExtentsInt32* newBox, bool addContact) { bool newEqualsChange = (newBox == change); bool oldEqualsChange = (oldBox == change); for (int i = change->low.x; i <= change->high.x; ++i) { for (int j = change->low.y; j <= change->high.y; ++j) { for (int k = change->low.z; k <= change->high.z; ++k) { Vector3int32 v(i, j, k); const bool inNew = newEqualsChange || (newBox && newBox->contains(v)); const bool inOld = oldEqualsChange || (oldBox && oldBox->contains(v)); if (inNew && !inOld) { addNode(p, v, addContact); // only update bucket counts when moving to avoid } // big counts around 0,0,0 else if (inOld && !inNew) { SpatialNode* destroyMe = findNode(p, v); RBXASSERT(destroyMe); destroyNode(destroyMe); } } } } } SHP void SpatialHash::fastClear() { FastClearSpatialNode fastClearSpatialNode(this); FastClearTreeNode fastClearTreeNode(this); for (int level = 0; level < MAX_LEVELS; level ++) { for (int hashId=0; hashId<(int)SpatialHashStatic::numBuckets(level); hashId++) { SpatialNode *sn = hashTables[level][hashId].nodes; while (sn) { SpatialNode *nodeToFree = sn; sn = nodeToFree->nextHashLink; fastClearSpatialNode(nodeToFree); delete nodeToFree; } TreeNode *tn = hashTables[level][hashId].treeNodes; while (tn) { TreeNode *nodeToFree = tn; tn = nodeToFree->next; RBXASSERT((fastClearTreeNode(nodeToFree), true)); // only run this if asserts are on. delete nodeToFree; }; } } Allocator::releaseMemory(); Allocator::releaseMemory(); maxBucket = 0; cleanup(); setup(); } SHP void SpatialHash::checkAndReleaseContacts(Primitive *p) { if (Primitive::hasGetFirstContact ) { outOfContact.clear(); Contact * contact = p->getFirstContact(); while (contact) { Primitive *other = contact->otherPrimitive(p); contact = p->getNextContact(contact); if( contactManager->primitiveIsExcludedFromSpatialHash(other) ) continue; bool overlap = oldExtentsOverlap(p, other); RBXASSERT_SPATIAL_HASH(overlap == validateNodesOverlap(p, other)); if (!overlap) { outOfContact.append(other); } } for (int i=0; i<(int)outOfContact.size(); i++) contactManager->releasePair(p, outOfContact[i]); } } SHP void SpatialHash::primitiveExtentsChanged(Primitive* p, const Extents& extents) { WriteValidator writeValidator(concurrencyValidator); RBXASSERT_SPATIAL_HASH(validateContacts(p)); RBXASSERT_SPATIAL_HASH(p->spatialNodes != NULL); RBXASSERT(p->getSpatialNodeLevel() != -1); Vector3int32 newMin, newMax; // For now, never change a primitive's level in the hierarchy, once it // has been determined at creation time SpatialHashStatic::computeMinMax(p->getSpatialNodeLevel(), extents, newMin, newMax); if ( (newMin == p->getOldSpatialMin()) && (newMax == p->getOldSpatialMax())) { return; } ExtentsInt32 oldBox(p->getOldSpatialExtents()); ExtentsInt32 newBox(newMin, newMax); p->setOldSpatialExtents(newBox); if (ExtentsInt32::overlapsOrTouches(oldBox, newBox)) { ExtentsInt32 unionBox = ExtentsInt32::unionExtents(oldBox, newBox); changeMinMax(p, &unionBox, &oldBox, &newBox); } else { changeMinMax(p, &oldBox, &oldBox, NULL); RBXASSERT_SPATIAL_HASH(p->spatialNodeCount == 0); changeMinMax(p, &newBox, NULL, &newBox); } checkAndReleaseContacts(p); RBXASSERT_SPATIAL_HASH(validateContacts(p)); } SHP void SpatialHash::onPrimitiveRemoved(Primitive* p) { primitiveRemoved(p); } SHP void SpatialHash::primitiveRemoved(Primitive* p) { WriteValidator writeValidator(concurrencyValidator); RBXASSERT_SPATIAL_HASH(validateContacts(p)); RBXASSERT(p->getSpatialNodeLevel() != -1 || contactManager->primitiveIsExcludedFromSpatialHash(p)); changeMinMax(p, &p->getOldSpatialExtents(), &p->getOldSpatialExtents(), NULL); p->setSpatialNodeLevel(-1); p->setOldSpatialExtents(ExtentsInt32::empty()); RBXASSERT_SPATIAL_HASH(p->spatialNodeCount == 0); if (Primitive::hasGetFirstContact ) { outOfContact.clear(); for (int i = 0; i < p->getNumContacts(); i++) outOfContact.append(p->getContactOther(i)); for (int i = 0; i < (int)outOfContact.size(); i++) contactManager->releasePair(p, outOfContact[i]); } RBXASSERT_SPATIAL_HASH(validateContacts(p)); } // Note -use FastFuzzyExtents when not in the middle of a simulation step // Use FastFuzzyExtentsNoCompute when in a step SHP void SpatialHash::onPrimitiveExtentsChanged(Primitive* p) { if( contactManager->primitiveIsExcludedFromSpatialHash(p) ) return; contactManager->checkTerrainContact(p); Extents newExtentsFloat = calcNewExtents(p); int newLevel = computeLevel(p, newExtentsFloat); int oldLevel = p->getSpatialNodeLevel(); int delta = newLevel - oldLevel; ExtentsInt32 preUpdateSpatialExtents = p->getOldSpatialExtents(); if ((delta > 0) || (delta < -1)) // grow always, shrink only if 2 steps down { primitiveRemoved(p); primitiveAdded(p, true); } else { primitiveExtentsChanged(p, newExtentsFloat); } ExtentsInt32 postUpdateSpatialExtents = p->getOldSpatialExtents(); if (!coarseMovementCallbacks.empty() && (newLevel != oldLevel || preUpdateSpatialExtents != postUpdateSpatialExtents)) { typename CoarseMovementCallback::UpdateInfo info; info.updateType = CoarseMovementCallback::UpdateInfo::UPDATE_TYPE_Change; info.oldLevel = oldLevel; info.oldSpatialExtents = preUpdateSpatialExtents; info.newLevel = newLevel; info.newSpatialExtents = postUpdateSpatialExtents; for (size_t i = 0; i < coarseMovementCallbacks.size(); ++i) { coarseMovementCallbacks[i]->coarsePrimitiveMovement(p, info); } } } // Now that primitives are assembled into mechanisms we know the full topology to filter // internal contacts. So let's query the spatial hash to create the contacts and rely on // onNewPair() to do the filtering SHP void SpatialHash::onPrimitiveAssembled(Primitive* p) { if( contactManager->primitiveIsExcludedFromSpatialHash(p) ) return; RBXASSERT_SPATIAL_HASH(validateContacts(p)); RBXASSERT_SPATIAL_HASH(p->spatialNodes != NULL); RBXASSERT(p->getSpatialNodeLevel() != -1); WriteValidator writeValidator(concurrencyValidator); Extents newExtents = calcNewExtents(p); int level = computeLevel(p, newExtents); Vector3int32 newMin, newMax; SpatialHashStatic::computeMinMax(level, newExtents, newMin, newMax); for (int i = newMin.x; i <= newMax.x; ++i) { for (int j = newMin.y; j <= newMax.y; ++j) { for (int k = newMin.z; k <= newMax.z; ++k) { Vector3int32 grid(i, j, k); SpatialNode* thisNode = findNode(p, grid); if (!thisNode) continue; int hash = SpatialHashStatic::getHash(level, grid); // This hash's linked list of nodes SpatialNode* tryNode = hashTables[level][hash].nodes; // For nodes at the same level, and higher levels nodes containing this node Vector3int32 g = grid; for (int l=level; l < MAX_LEVELS; l++) { if (l > level) { g.x >>= 1; g.y >>= 1; g.z >>= 1; hash = SpatialHashStatic::getHash(l, g); tryNode = hashTables[l][hash].nodes; } while (tryNode) { RBXASSERT( l < MAX_LEVELS); Primitive* other = tryNode->primitive; if ((other != p) && (tryNode->gridId == g)) { if (Primitive::getContact(p, other) == NULL) { contactManager->onNewPair(p, other); } } tryNode = tryNode->nextHashLink; } } // add contacts from children if (thisNode->getLevel() > 0) { RBXASSERT(thisNode && thisNode->treeNode->level == thisNode->getLevel()); addContactFromChildren(thisNode->treeNode, p); } } } } } SHP void SpatialHash::getPrimitivesTouchingGrids(const Extents& extents, const Primitive* ignore, std::size_t maxCount, boost::unordered_set& answer) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); RBXASSERT(answer.size() == 0); Vector3int32 min, max; G3D::Array foundThisGrid; // prevent allocations for (int level=0; level 0 && answer.size() >= maxCount) return; } } } } } } RBXASSERT(foundThisGrid.size() < 200); } // same as above function, but we use a set of all primitives to-be-ignored for use with an ancestor check SHP void SpatialHash::getPrimitivesTouchingGrids(const Extents& extents, const boost::unordered_set& ignoreSet, std::size_t maxCount, boost::unordered_set& answer) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); RBXASSERT(answer.size() == 0); Vector3int32 min, max; G3D::Array foundThisGrid; // prevent allocations for (int level=0; level 0 && answer.size() >= maxCount) return; } } } } } } } RBXASSERT(foundThisGrid.size() < 200); } SHP template void SpatialHash::getPrimitivesOverlapping(const Extents& extents, Set& answer) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); for (int level=0; levelgridId == grid) { Primitive* p = node->primitive; if (skipOverlapTest || extents.overlapsOrTouches(calcNewExtents(p))) { answer.insert(p); } } node = node->nextHashLink; } } } } } } SHP template void SpatialHash::getPrimitivesOverlappingRec(const Extents& extents, Set& answer) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); // A small negative offset is added to extentsMax before computeMinMax to prevent querying extra layer of cells for perfectly aligned/sized extents Extents adjustedExtents(extents.min(), (extents.max() - Vector3(0.01f, 0.01f, 0.01f)).max(extents.min())); int level = MAX_LEVELS - 1; Vector3int32 min, max; SpatialHashStatic::computeMinMax(level, adjustedExtents, min, max); // If the tested region is contained within the extents we don't have to perform precise overlap tests // This is very important since tests require reading Primitive memory which leads to extra cache misses. Vector3 minReal = SpatialHashStatic::hashGridToReal(level, min); Vector3 maxReal = SpatialHashStatic::hashGridToReal(level, max + Vector3int32(1, 1, 1)); bool skipOverlapTest = extents.contains(minReal) && extents.contains(maxReal); for (int i = min.x; i <= max.x; ++i) { for (int j = min.y; j <= max.y; ++j) { for (int k = min.z; k <= max.z; ++k) { Vector3int32 grid(i, j, k); int hash = SpatialHashStatic::getHash(level, grid); getPrimitivesOverlappingRec(skipOverlapTest ? NULL : &extents, answer, level, hash, grid); } } } } SHP template void SpatialHash::getPrimitivesOverlappingRec(const Extents* extents, Set& answer, int level, int hash, const Vector3int32& gridCoord) { // Look for nodes at current level SpatialNode* node = hashTables[level][hash].nodes; while (node) { if (node->gridId == gridCoord) { Primitive* p = node->primitive; if (!extents || extents->overlapsOrTouches(calcNewExtents(p))) answer.insert(p); } node = node->nextHashLink; } // Look for nodes at the next level with smaller cells if (level > 0) { TreeNode* treeNode = hashTables[level][hash].treeNodes; while (treeNode) { if (treeNode->gridId == gridCoord) { for (int child = 0; child < 8; ++child) if (treeNode->hasChild(child)) { int childHash = treeNode->children[child]; Vector3int32 childGrid = getChildGrid(gridCoord, child); getPrimitivesOverlappingRec(extents, answer, level - 1, childHash, childGrid); } // Just one tree node for every cell, no need to look further return; } treeNode = treeNode->next; } } } SHP void SpatialHash::getPrimitivesInGrid(int level, const Vector3int32& grid, G3D::Array& found) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); int hash = SpatialHashStatic::getHash(level, grid); SpatialNode* node = hashTables[level][hash].nodes; while (node) { if (node->gridId == grid) { RBXASSERT_IF_VALIDATING(!found.contains(node->primitive)); found.append(node->primitive); } node = node->nextHashLink; } } // find primitives at all levels--the input grid coord is for level 0 SHP void SpatialHash::getPrimitivesInGrid(const Vector3int32& grid, G3D::Array& found) { RBXASSERT(found.size() == 0); Vector3int32 g = grid; for (int level = 0; level < MAX_LEVELS; level++, g.x>>=1, g.y>>=1, g.z>>=1) { getPrimitivesInGrid(level, g, found); } } SHP bool SpatialHash::getNextGrid(Vector3int32& grid, const RbxRay& unitRay, float maxDistance) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); RBXASSERT_VERY_FAST(unitRay.direction().isUnit()); int low[3], high[3]; for (int i = 0; i < 3; ++i) { low[i] = (unitRay.direction()[i] < 0.0) ? -1 : 0; high[i] = (unitRay.direction()[i] > 0.0) ? 1 : 0; } maxDistance += SpatialHashStatic::hashGridSize(0) * 2.0f; // collision detection returns the first hit in a grid box - this could // be farther away than the actual ultimate hit point float maxDistanceSquared = maxDistance * maxDistance; for (int nz = 1; nz <= 3; ++ nz) { // number of non zeros - 1: adjacent face (6), edge (12), corner (8) for (int i = low[0]; i <= high[0]; ++i) { for (int j = low[1]; j <= high[1]; ++j) { for (int k = low[2]; k <= high[2]; ++k) { if ((std::abs(i) + std::abs(j) + std::abs(k)) == nz) { // start with this number of nonzeros Vector3int32 offset(i, j, k); Extents extents = SpatialHashStatic::hashGridToRealExtents(0, grid + offset); AABox box(extents.min(), extents.max()); Vector3 location; bool inside; bool result = G3D::CollisionDetection::collisionLocationForMovingPointFixedAABox( unitRay.origin(), unitRay.direction(), box, location, inside ); if( inside || result ) { if ((location - unitRay.origin()).squaredMagnitude() < maxDistanceSquared) { grid = grid + offset; return true; } } } }}}} return false; } SHP typename SpatialHash::TreeNode* SpatialHash::getFirstRoot() { TreeNode * tn = NULL; for(size_t id = 0; id < SpatialHashStatic::numBuckets(rootLevel) && tn == NULL; id++) { tn = hashTables[rootLevel][id].treeNodes; } return tn; } SHP typename SpatialHash::TreeNode* SpatialHash::getNextRoot(TreeNode* prevRoot) { if(prevRoot) { if(prevRoot->next) { return prevRoot->next; } else { // go to next non-empty hash grid TreeNode * tn = NULL; for(size_t id = prevRoot->hashId+1; id < SpatialHashStatic::numBuckets(rootLevel) && tn == NULL; id++) { tn = hashTables[rootLevel][id].treeNodes; } return tn; } } else { return NULL; } } SHP typename SpatialHash::TreeNode* SpatialHash::getChild(TreeNode* parent, int octant) { RBXASSERT(parent->hasChild(octant)); if(parent->level > 0) { return findTreeNode(parent->level-1, parent->children[octant], getChildGrid(parent->gridId, octant)); } else { return NULL; } } SHP void SpatialHash::getPrimitivesInTreeNode(TreeNode* tn, G3D::Array& primitives) { SpatialNode* node = hashTables[tn->level][tn->hashId].nodes; while (node) { if (node->gridId == tn->gridId) { RBXASSERT_IF_VALIDATING(!primitives.contains(node->primitive)); primitives.append(node->primitive); } node = node->nextHashLink; } } SHP void SpatialHash::visitPrimitivesInSpace(SpaceFilter* filter, const Vector3& visitDir) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); int visitOrder[8]; SpatialHashStatic::makeVisitOrder(visitOrder, visitDir); typedef std::pair TreeNodePair; std::vector roots; // get all the root nodes, sort by visitDir. (after testing for intersect) for(TreeNode* root = getFirstRoot(); root; root = getNextRoot(root)) { IntersectResult childIntersect = filter->Intersects(SpatialHashStatic::hashGridToRealExtents(root->level, root->gridId)); if(childIntersect == irNone) { continue; // no interesct at all from this node and all child nodes. } roots.push_back(TreeNodePair(root, childIntersect)); } if(visitDir != Vector3::zero()) { SortOffsetByVisitDir sortPred(visitDir); std::sort(roots.begin(), roots.end(), sortPred); } for(size_t i = 0; i < roots.size(); ++i) { TreeNode* tn = roots[i].first; visitPrimitivesInSpaceWorker(tn, tn->level, tn->hashId, tn->gridId, visitOrder, roots[i].second /*childintersect*/, filter, visitDir); } } SHP void SpatialHash::visitPrimitivesInSpace(SpaceFilter* filter) { ReadOnlyValidator readOnlyValidator(concurrencyValidator); std::priority_queue nodestovisit; bool bContinueIterating = true; // becomes false when onPrimitives returns false // get all the root nodes, sort by visitDir. (after testing for intersect) for(TreeNode* root = getFirstRoot(); root; root = getNextRoot(root)) { Extents extents = SpatialHashStatic::hashGridToRealExtents(root->level, root->gridId); IntersectResult childIntersect = filter->Intersects(extents); if(childIntersect == irNone) { continue; // no interesct at all from this node and all child nodes. } nodestovisit.push(NodeInfo(root, aRecurseTreeNode, childIntersect, filter->Distance(extents))); } while(!nodestovisit.empty() && bContinueIterating) { NodeInfo nodeinfo(nodestovisit.top()); nodestovisit.pop(); NodeBase* node = nodeinfo.node; int level = node->level; int hashId = node->hashId; const RBX::Vector3int32& gridId = node->gridId; // check the children if(nodeinfo.action == aRecurseTreeNode) { // all pushed NodeBases are TreeNode if they are not level 0. TreeNode* tn = static_cast(node); int childLevel = level-1; for(int childoffset = 0; childoffset < 8; ++childoffset) { if(!tn->hasChild(childoffset)) { continue; // no child, skip. } Vector3int32 childGridId = getChildGrid(tn->gridId, childoffset); int childHashId = tn->children[childoffset]; IntersectResult childIntersect = irFull; Extents childExtents = SpatialHashStatic::hashGridToRealExtents(childLevel, childGridId); if(nodeinfo.intersectResult == irPartial) // must keep checking bounds { childIntersect = filter->Intersects(childExtents); if(childIntersect == irNone) { continue; // no interesct at all from this node and all child nodes. } } TreeNode* childTreeNode = NULL; if(childLevel > 0) { childTreeNode = findTreeNode(childLevel, childHashId, childGridId); RBXASSERT(childTreeNode); RBXASSERT(childTreeNode->gridId == childGridId); RBXASSERT(childTreeNode->level == childLevel); RBXASSERT(tn->children[childoffset] == childTreeNode->hashId); // we have childTreeNode; nodestovisit.push(NodeInfo(childTreeNode, aRecurseTreeNode, childIntersect, filter->Distance(childExtents))); } else // since we don't have treenodes at the leaf level, we must "reach down" from the above level, instead of "recursing". { // no childTreeNode, must pass a spatialNode; SpatialNode* snode = hashTables[childLevel][childHashId].nodes; bool extentsNeedCalc = true; float extentsDistance = 0; while (snode) { if (snode->gridId == childGridId) { if(extentsNeedCalc) // only calculate distance for this extents 0 or 1 times. { extentsDistance = filter->Distance(childExtents); } #ifndef PRECISE_SORTING nodestovisit.push(NodeInfo(snode, aVisitAllSiblingsSpatialNodes, childIntersect, extentsDistance)); break; #else // precise sorting. nodestovisit.push(NodeInfo(snode, aVisitSingleSpatialNode, childIntersect, extentsDistance))); #endif } snode = snode->nextHashLink; } } } } if (nodeinfo.action != aVisitSingleSpatialNode ) { // this treenode could have sibling spatial nodes. SpatialNode* snode = hashTables[level][hashId].nodes; while (snode && bContinueIterating) { if (snode->gridId == gridId) { #ifndef PRECISE_SORTING bContinueIterating = filter->onPrimitive(snode->primitive, nodeinfo.intersectResult, nodeinfo.distance); #else nodestovisit.push(NodeInfo(snode, aVisitSingleSpatialNode, nodeinfo.intersectResult, filter->Distance(snode->primitive->getFastFuzzyExtents()))); #endif } snode = snode->nextHashLink; } } else //if (nodeinfo.action == aVisitSingleSpatialNode) { bContinueIterating = filter->onPrimitive(static_cast(node)->primitive, nodeinfo.intersectResult, nodeinfo.distance); } } } SHP void SpatialHash::visitPrimitivesInSpaceWorker(TreeNode* tn, int level, int hashId, const RBX::Vector3int32& gridId, int* visitOrder, IntersectResult intersectResult, SpaceFilter* filter, const Vector3& visitDir) { RBXASSERT(intersectResult == irFull || intersectResult == irPartial); // check the children if(tn) { int childLevel = level-1; for(int offseti = 0; offseti < 8; ++offseti) { int childoffset = visitOrder[offseti]; if(!tn->hasChild(childoffset)) { continue; // no child, skip. } Vector3int32 childGridId = getChildGrid(tn->gridId, childoffset); int childHashId = tn->children[childoffset]; IntersectResult childIntersect = irFull; if(intersectResult == irPartial) // must keep checking bounds { childIntersect = filter->Intersects(SpatialHashStatic::hashGridToRealExtents(childLevel, childGridId)); if(childIntersect == irNone) { continue; // no interesct at all from this node and all child nodes. } } TreeNode* childTreeNode = NULL; if(childLevel > 0) { childTreeNode = findTreeNode(childLevel, childHashId, childGridId); RBXASSERT(childTreeNode); RBXASSERT(childTreeNode->gridId == childGridId); RBXASSERT(childTreeNode->level == childLevel); RBXASSERT(tn->children[childoffset] == childTreeNode->hashId); } visitPrimitivesInSpaceWorker(childTreeNode, childLevel, childHashId, childGridId, visitOrder, childIntersect, filter, visitDir); } } // visit the primitives SpatialNode* node = hashTables[level][hashId].nodes; bool bContinue = true; while (node && bContinue) { if (node->gridId == gridId) { bContinue = filter->onPrimitive(node->primitive, intersectResult, 0); } node = node->nextHashLink; } } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // // Debugging - all of these should RBXASSERT(assertingSpatialHash) // SHP bool SpatialHash::validateInsertNodeToPrimitive(SpatialNode* node, Primitive* p, const Vector3int32& grid, int hash) { RBXASSERT(assertingSpatialHash); #ifdef _RBX_DEBUGGING_SPATIAL_HASH // Primitive P's linked list of nodes SpatialNode* oldFirst = static_cast(p->spatialNodes); p->spatialNodes = node; node->nextPrimitiveLink = oldFirst; node->prevPrimitiveLink = NULL; if (oldFirst) { oldFirst->prevPrimitiveLink = node; } p->spatialNodeCount++; #endif return true; } SHP bool SpatialHash::validateRemoveNodeFromPrimitive(SpatialNode* node) { RBXASSERT(assertingSpatialHash); #ifdef _RBX_DEBUGGING_SPATIAL_HASH SpatialNode* prev = node->prevPrimitiveLink; SpatialNode* next = node->nextPrimitiveLink; if (next) { next->prevPrimitiveLink = prev; } if (prev) { prev->nextPrimitiveLink = next; } else { // !prev node->primitive->spatialNodes = next; } --node->primitive->spatialNodeCount; #endif return true; } SHP bool SpatialHash::validateNodesOverlap(Primitive* me, Primitive* other) { RBXASSERT(assertingSpatialHash); #ifdef _RBX_DEBUGGING_SPATIAL_HASH SpatialNode* myNode = static_cast(me->spatialNodes); SpatialNode* otherNode = static_cast(other->spatialNodes); if (!myNode || !otherNode) return false; if (myNode->getLevel() == otherNode->getLevel()) { while (myNode) { SpatialNode* hashNode = hashTables[myNode->getLevel()][myNode->hashId].nodes; while (hashNode) { if ((hashNode->primitive == other) && (hashNode->gridId == myNode->gridId)) { return true; } hashNode = hashNode->nextHashLink; } myNode = myNode->nextPrimitiveLink; } } else { SpatialNode *lower, *higher; Primitive *primHigher; if (myNode->getLevel() > otherNode->getLevel()) { lower = otherNode; higher = myNode; primHigher = me; } else { lower = myNode; higher = otherNode; primHigher = other; } int levelDiff = higher->getLevel() - lower->getLevel(); while (lower) { Vector3int32 gridCoordHigher; gridCoordHigher.x = (lower->gridId.x >> levelDiff); gridCoordHigher.y = (lower->gridId.y >> levelDiff); gridCoordHigher.z = (lower->gridId.z >> levelDiff); int hashHigher = getHash(higher->getLevel(), gridCoordHigher); SpatialNode* node = hashTables[higher->getLevel()][hashHigher].nodes; while (node) { if ((node->primitive == primHigher) && (node->gridId == gridCoordHigher)) { return true; } node = node->nextHashLink; } lower = lower->nextPrimitiveLink; } } #endif return false; } SHP bool SpatialHash::validateTallyTreeNodes() { RBXASSERT(assertingSpatialHash); int numTreeNodesInUse=0, numTreeNodesInPool=0; for (int level = 0; level < MAX_LEVELS; level ++) { for (int hashId=0; hashId<(int)SpatialHashStatic::numBuckets(level); hashId++) { TreeNode *tn = hashTables[level][hashId].treeNodes; while (tn) { numTreeNodesInUse ++; RBXASSERT(tn->childMask || tn->refByPrimitives); tn = tn->next; } } } return numTreeNodesInUse + numTreeNodesInPool == numTreeNodesTotal; } SHP bool SpatialHash::validateTreeNodeNotHere(TreeNode* tn, int level, int hash) { RBXASSERT(assertingSpatialHash); TreeNode *p = hashTables[level][hash].treeNodes; while (p) { RBXASSERT(p != tn); p = p->next; } return true; } SHP bool SpatialHash::validateNoNodesOut() { RBXASSERT(assertingSpatialHash); for (int l=0; l < MAX_LEVELS; l++) { RBXASSERT(hashTables[l].size() == SpatialHashStatic::numBuckets(l)); for (size_t i = 0; i < SpatialHashStatic::numBuckets(l); ++i) { RBXASSERT(hashTables[l][i].nodes == NULL); } } return true; } SHP bool SpatialHash::validateContacts(Primitive* p) { RBXASSERT(assertingSpatialHash); #ifdef _RBX_DEBUGGING_SPATIAL_HASH __if_exists(Primitive::getFirstContact) { Contact* c = p->getFirstContact(); // 1. For each contact, confirm there is a hash collision while (c) { Primitive* other = c->otherPrimitive(p); RBXASSERT_SPATIAL_HASH(validateNodesOverlap(p, other)); RBXASSERT_SPATIAL_HASH(validateNodesOverlap(p, other)); c = p->getNextContact(c); } // 2. For each node with other, confirm there is a contact SpatialNode* myNode = static_cast(p->spatialNodes); while (myNode) { SpatialNode* hashNode = hashTables[myNode->getLevel()][myNode->hashId].nodes; while (hashNode) { if ((hashNode->primitive != p) && (hashNode->gridId == myNode->gridId)) { RBXASSERT_SPATIAL_HASH(Primitive::getContact(p, hashNode->primitive)); } hashNode = hashNode->nextHashLink; } myNode = myNode->nextPrimitiveLink; } } #endif return true; } SHP void SpatialHash::doStats() const { #if 0 bool operator<(const Vector3int32& a, const Vector3int32& b) { for (int i = 0; i < 3; ++i) { if (a[i] < b[i]) { return true; } else if (a[i] > b[i]) { return false; } } return false; } size_t computeNumNodes(SpatialNode* node) { int answer = 0; while (node) { answer++; node = node->nextHashLink; } return answer; } size_t computeNumGrids(SpatialNode* node) { std::set grids; while (node) { grids.insert(node->gridId); node = node->nextHashLink; } return grids.size(); } // little test of hash function, not normally run std::set num_list1; std::set num_list2; std::set num_list3; std::set num_list4; /* for(int n = 0; n < 100000; n++) { // come up with some vectors using the full sample space Vector3int32 v; v.x = (int)((rand() << 16) ^ rand()); v.y = (int)((rand() << 16) ^ rand()); v.z = (int)((rand() << 16) ^ rand()); int key = getHash(v); if(num_list1.find(key) == num_list1.end()) { num_list1.insert(key); continue; } if(num_list2.find(key) == num_list2.end()) { num_list2.insert(key); continue; } if(num_list3.find(key) == num_list3.end()) { num_list3.insert(key); continue; } if(num_list4.find(key) == num_list4.end()) { num_list4.insert(key); continue; } } int u1 = num_list1.size(); int u2 = num_list2.size(); int u3 = num_list3.size(); int u4 = num_list4.size(); RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Random Hash distribution 1: %d 2: %d 3: %d 4: %d", u1, u2, u3, u4); */ std::vector counts(numBuckets(), 0); std::vector differentGrids(numBuckets(), 0); for (size_t i = 0; i < numBuckets(); ++i) { counts[i] = computeNumNodes(nodes[i]); differentGrids[i] = computeNumGrids(nodes[i]); } sort(counts.begin(), counts.end()); sort(differentGrids.begin(), differentGrids.end()); RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "CURRENT HASH DISTRIBUTION"); size_t numSlots = 100; for (size_t i = 0; i < numSlots; ++i) { size_t index = ( (i + 1) * numBuckets() / numSlots ) - 1; RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Slot: %d Count: %d", i, counts[index]); } RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "TOP HASH DISTRIBUTION"); for (size_t i = 0; i < numSlots; ++i) { size_t index = numBuckets() - 1 - i; RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Slot: %d Count: %d", i, counts[index]); } RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "TOP GRID COLLISIONS DISTRIBUTION"); for (size_t i = 0; i < numSlots; ++i) { size_t index = numBuckets() - 1 - i; RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Slot: %d Count: %d", i, differentGrids[index]); } #endif } SHP void SpatialHash::registerCoarseMovementCallback( CoarseMovementCallback* callback) { coarseMovementCallbacks.push_back(callback); } SHP void SpatialHash::unregisterCoarseMovementCallback( CoarseMovementCallback* callback) { typename std::vector::iterator itr = std::find(coarseMovementCallbacks.begin(), coarseMovementCallbacks.end(), callback); if (itr != coarseMovementCallbacks.end()) { coarseMovementCallbacks.erase(itr); } } } // namespace