void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) const { OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes // Check to see if this element yet has encode data... if it doesn't create it if (!extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElement* child = getChildAtIndex(i); if (!child) { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed } else { if (child->hasEntities()) { entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING } else { entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed } } } for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); } // TODO: some of these inserts might be redundant!!! extraEncodeData->insert(this, entityTreeElementExtraEncodeData); } }
void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID; } return; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. " "entityID=" << entityID; } return; } if (existingEntity->getLocked() && !force) { if (!ignoreWarnings) { qCDebug(entities) << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID; } return; } emit deletingEntity(entityID); // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this, entityID); recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; }
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { EntityTreeElement* childElement = getChildAtIndex(childIndex); if (childElement->alreadyFullyEncoded(params)) { return false; } return true; // if we don't know otherwise than recurse! }
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { return false; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { return false; } return updateEntityWithElement(existingEntity, properties, containingElement, senderNode); }
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; return false; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { qCDebug(entities) << "UNEXPECTED!!!! don't call updateEntity() on entity items that don't exist. entityID=" << entityID; return false; } return updateEntityWithElement(existingEntity, properties, containingElement, senderNode); }
bool DiffTraversal::View::shouldTraverseElement(const EntityTreeElement& element) const { if (!usesViewFrustums()) { return true; } const auto& cube = element.getAACube(); auto center = cube.calcCenter(); // center of bounding sphere auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere return any_of(begin(viewFrustums), end(viewFrustums), [&](const ConicalViewFrustum& frustum) { auto position = center - frustum.getPosition(); // position of bounding sphere in view-frame float distance = glm::length(position); // distance to center of bounding sphere // Check the size of the entity, it's possible that a "too small to see" entity is included in a // larger octree cell because of its position (for example if it crosses the boundary of a cell it // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. float angularSize = frustum.getAngularSize(distance, radius); return angularSize > lodScaleFactor * MIN_ELEMENT_ANGULAR_DIAMETER && frustum.intersects(position, distance, radius); }); }
bool EntityMotionState::entityTreeIsLocked() const { EntityTreeElement* element = _entity ? _entity->getElement() : nullptr; EntityTree* tree = element ? element->getTree() : nullptr; if (tree) { bool readSuccess = tree->tryLockForRead(); if (readSuccess) { tree->unlock(); } bool writeSuccess = tree->tryLockForWrite(); if (writeSuccess) { tree->unlock(); } if (readSuccess && writeSuccess) { return false; // if we can take either kind of lock, there was no tree lock. } return true; // either read or write failed, so there is some lock in place. } else { return true; } }
EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { EntityTreeElement* newElement = new EntityTreeElement(octalCode); newElement->setTree(this); return newElement; }
EntityTreeElement* EntityTreeElement::addChildAtIndex(int index) { EntityTreeElement* newElement = (EntityTreeElement*)OctreeElement::addChildAtIndex(index); newElement->setTree(_myTree); return newElement; }
// This will be called primarily on addChildAt(), which means we're adding a child of our // own type to our own tree. This means we should initialize that child with any tree and type // specific settings that our children must have. One example is out VoxelSystem, which // we know must match ours. OctreeElement* EntityTreeElement::createNewElement(unsigned char* octalCode) { EntityTreeElement* newChild = new EntityTreeElement(octalCode); newChild->setTree(_myTree); return newChild; }
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL; bool hadElementExtraData = false; if (extraEncodeData && extraEncodeData->contains(this)) { entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this)); hadElementExtraData = true; } else { // if there wasn't one already, then create one entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElement* child = getChildAtIndex(i); if (!child) { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed } else { if (child->hasEntities()) { entityTreeElementExtraEncodeData->childCompleted[i] = false; } else { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if the child doesn't have enities, it is completed } } } for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); } } //assert(extraEncodeData); //assert(extraEncodeData->contains(this)); //entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this)); LevelDetails elementLevel = packetData->startLevel(); // write our entities out... first determine which of the entities are in view based on our params uint16_t numberOfEntities = 0; uint16_t actualNumberOfEntities = 0; QVector<uint16_t> indexesOfEntitiesToInclude; // It's possible that our element has been previous completed. In this case we'll simply not include any of our // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we // need to handle the case where our sibling elements need encoding but we don't. if (!entityTreeElementExtraEncodeData->elementCompleted) { for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; bool includeThisEntity = true; if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) { includeThisEntity = false; } if (hadElementExtraData) { includeThisEntity = includeThisEntity && entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); } if (includeThisEntity && params.viewFrustum) { // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of // simulation changing what's visible. consider the case where the entity contains an angular velocity // the entity may not be in view and then in view a frame later, let the client side handle it's view // frustum culling on rendering. AACube entityCube = entity->getMaximumAACube(); entityCube.scale(TREE_SCALE); if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { includeThisEntity = false; // out of view, don't include it } } if (includeThisEntity) { indexesOfEntitiesToInclude << i; numberOfEntities++; } } } int numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); bool successAppendEntityCount = packetData->appendValue(numberOfEntities); if (successAppendEntityCount) { foreach (uint16_t i, indexesOfEntitiesToInclude) { EntityItem* entity = (*_entityItems)[i]; LevelDetails entityLevel = packetData->startLevel(); OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData, params, entityTreeElementExtraEncodeData); // If none of this entity data was able to be appended, then discard it // and don't include it in our entity count if (appendEntityState == OctreeElement::NONE) { packetData->discardLevel(entityLevel); } else { // If either ALL or some of it got appended, then end the level (commit it) // and include the entity in our final count of entities packetData->endLevel(entityLevel); actualNumberOfEntities++; } // If the entity item got completely appended, then we can remove it from the extra encode data if (appendEntityState == OctreeElement::COMPLETED) { entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); } // If any part of the entity items didn't fit, then the element is considered partial // NOTE: if the entity item didn't fit or only partially fit, then the entity item should have // added itself to the extra encode data. if (appendEntityState != OctreeElement::COMPLETED) { appendElementState = OctreeElement::PARTIAL; } } } else {
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const { const bool wantDebug = false; if (wantDebug) { qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << getAACube(); } OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes assert(extraEncodeData->contains(this)); EntityTreeElementExtraEncodeData* thisExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this)); // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() // which means, it's possible that our parent element hasn't finished encoding OUR data... so // in this case, our children may be complete, and we should clean up their encode data... // but not necessarily cleanup our own encode data... // // If we're really complete here's what must be true... // 1) out own data must be complete // 2) the data for all our immediate children must be complete. // However, the following might also be the case... // 1) it's ok for our child trees to not yet be fully encoded/complete... // SO LONG AS... the our child's node is in the bag ready for encoding bool someChildTreeNotComplete = false; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElement* childElement = getChildAtIndex(i); if (childElement) { // why would this ever fail??? // If we've encoding this element before... but we're coming back a second time in an attempt to // encoud our parent... this might happen. if (extraEncodeData->contains(childElement)) { EntityTreeElementExtraEncodeData* childExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement)); if (wantDebug) { qDebug() << "checking child: " << childElement->getAACube(); qDebug() << " childElement->isLeaf():" << childElement->isLeaf(); qDebug() << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; qDebug() << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; } if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) { if (wantDebug) { qDebug() << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!"; } childExtraEncodeData->subtreeCompleted = true; } if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) { someChildTreeNotComplete = true; } } } } if (wantDebug) { qDebug() << "for this element: " << getAACube(); qDebug() << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; qDebug() << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; } thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete; if (wantDebug) { qDebug() << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted; qDebug() << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; if (thisExtraEncodeData->subtreeCompleted) { qDebug() << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; } } }
void EntityTests::entityTreeTests(bool verbose) { bool extraVerbose = false; int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "EntityTests::entityTreeTests()"; // Tree, id, and entity properties used in many tests below... EntityTree tree; QUuid id = QUuid::createUuid(); EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs EntityItemProperties properties; float oneMeter = 1.0f; //float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE; glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE; { testsTaken++; QString testName = "add entity to tree and search"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } properties.setPosition(positionAtCenterInMeters); // TODO: Fix these unit tests. //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); tree.addEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs { testsTaken++; QString testName = "change position of entity in tree"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionNearOriginInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; QString testName = "change position of entity in tree back to center"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionAtCenterInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findClosestEntity() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units quint64 start = usecTimestampNow(); const EntityItem* foundEntityByRadius = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; } bool passed = foundEntityByRadius; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findEntityByID() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } quint64 start = usecTimestampNow(); const EntityItem* foundEntityByID = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { // TODO: does this need to be updated?? foundEntityByID = tree.findEntityByEntityItemID(entityID); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByID=" << foundEntityByID; } bool passed = foundEntityByID; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { // seed the random number generator so that our tests are reproducible srand(0xFEEDBEEF); testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - add entity to tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedAdd = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs float randomX = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomY = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomZ = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); glm::vec3 randomPositionInMeters(randomX,randomY,randomZ); glm::vec3 randomPositionInTreeUnits = randomPositionInMeters / (float)TREE_SCALE; properties.setPosition(randomPositionInMeters); // TODO: fix these unit tests //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); if (extraVerbose) { qDebug() << "iteration:" << i << "ading entity at x/y/z=" << randomX << "," << randomY << "," << randomZ; qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startAdd = usecTimestampNow(); tree.addEntity(entityID, properties); quint64 endAdd = usecTimestampNow(); totalElapsedAdd += (endAdd - startAdd); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(randomPositionInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); bool elementIsBestFit = containingElement->bestFitEntityBounds(foundEntityByID); if (extraVerbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); qDebug() << "elementIsBestFit=" << elementIsBestFit; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID) && elementIsBestFit; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByRadius=" << foundEntityByRadius << "foundEntityByID=" << foundEntityByID << "x/y/z=" << randomX << "," << randomY << "," << randomZ << "elementIsBestFit=" << elementIsBestFit; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsAdd = (float)(totalElapsedAdd) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Add=" << elapsedInMSecsAdd << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - delete entity from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntity(entityID); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 100; const int ENTITIES_PER_ITERATION = 10; QString testName = "Performance - delete " + QString::number(ENTITIES_PER_ITERATION) + " entities from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QSet<EntityItemID> entitiesToDelete; for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entitiesToDelete << entityID; } if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntities(entitiesToDelete); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == (TEST_ITERATIONS * ENTITIES_PER_ITERATION); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken; if (verbose) { qDebug() << "******************************************************************************************"; } }