// A box is inside a sphere if all of its corners are inside the sphere // A box intersects a sphere if any of its edges (as rays) interesect the sphere // A box is outside a sphere if none of its edges (as rays) interesect the sphere ViewFrustum::location ViewFrustum::cubeInKeyhole(const AACube& cube) const { // First check to see if the cube is in the bounding cube for the sphere, if it's not, then we can short circuit // this and not check with sphere penetration which is more expensive if (!_keyholeBoundingCube.contains(cube)) { return OUTSIDE; } glm::vec3 penetration; bool intersects = cube.findSpherePenetration(_position, _keyholeRadius, penetration); ViewFrustum::location result = OUTSIDE; // if the cube intersects the sphere, then it may also be inside... calculate further if (intersects) { result = INTERSECT; // test all the corners, if they are all inside the sphere, the entire cube is in the sphere bool allPointsInside = true; // assume the best for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { glm::vec3 vertex = cube.getVertex((BoxVertex)v); if (!pointInKeyhole(vertex)) { allPointsInside = false; break; } } if (allPointsInside) { result = INSIDE; } } return result; }
ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const { ViewFrustum::location regularResult = INSIDE; ViewFrustum::location keyholeResult = OUTSIDE; // If we have a keyholeRadius, check that first, since it's cheaper if (_keyholeRadius >= 0.0f) { keyholeResult = cubeInKeyhole(cube); } if (keyholeResult == INSIDE) { return keyholeResult; } // TODO: These calculations are expensive, taking up 80% of our time in this function. // This appears to be expensive because we have to test the distance to each plane. // One suggested optimization is to first check against the approximated cone. We might // also be able to test against the cone to the bounding sphere of the box. for(int i=0; i < 6; i++) { const glm::vec3& normal = _planes[i].getNormal(); const glm::vec3& boxVertexP = cube.getVertexP(normal); float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP); const glm::vec3& boxVertexN = cube.getVertexN(normal); float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN); if (planeToBoxVertexPDistance < 0) { // This is outside the regular frustum, so just return the value from checking the keyhole return keyholeResult; } else if (planeToBoxVertexNDistance < 0) { regularResult = INTERSECT; } } return regularResult; }
// Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the // axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for // squares and square-roots. Just compares. void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const { const glm::vec3& bottomNearRight = box.getCorner(); float scale = box.getScale(); float halfScale = scale * 0.5f; if (_position.x < bottomNearRight.x + halfScale) { // we are to the right of the center, so the left edge is furthest furthestPoint.x = bottomNearRight.x + scale; } else { furthestPoint.x = bottomNearRight.x; } if (_position.y < bottomNearRight.y + halfScale) { // we are below of the center, so the top edge is furthest furthestPoint.y = bottomNearRight.y + scale; } else { furthestPoint.y = bottomNearRight.y; } if (_position.z < bottomNearRight.z + halfScale) { // we are to the near side of the center, so the far side edge is furthest furthestPoint.z = bottomNearRight.z + scale; } else { furthestPoint.z = bottomNearRight.z; } }
float ConicalViewFrustum::getAngularSize(const AACube& cube) const { auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere auto position = cube.calcCenter() - _position; // position of bounding sphere in view-frame float distance = glm::length(position); return getAngularSize(distance, radius); }
int OctreeElement::getMyChildContaining(const AACube& cube) const { float ourScale = getScale(); float cubeScale = cube.getScale(); // TODO: consider changing this to assert() if (cubeScale > ourScale) { qCDebug(octree) << "UNEXPECTED -- OctreeElement::getMyChildContaining() -- (cubeScale > ourScale)"; qCDebug(octree) << " cube=" << cube; qCDebug(octree) << " elements AACube=" << _cube; qCDebug(octree) << " cubeScale=" << cubeScale; qCDebug(octree) << " ourScale=" << ourScale; assert(false); } // Determine which of our children the minimum and maximum corners of the cube live in... glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_cube.contains(cubeCornerMinimum) && _cube.contains(cubeCornerMaximum)) { int childIndexCubeMinimum = getMyChildContainingPoint(cubeCornerMinimum); int childIndexCubeMaximum = getMyChildContainingPoint(cubeCornerMaximum); // If the minimum and maximum corners of the cube are in two different children's cubes, then we are the containing element if (childIndexCubeMinimum != childIndexCubeMaximum) { return CHILD_UNKNOWN; } return childIndexCubeMinimum; // either would do, they are the same } return CHILD_UNKNOWN; // since cube is not contained in our element, it can't be in one of our children }
void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, const DiffTraversal::View& lastView) { if (_nextIndex == -1) { // root case is special ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); next.element = element; next.intersection = ViewFrustum::INTERSECT; return; } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement) { AACube cube = nextElement->getAACube(); // check for LOD truncation float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE; float angularDiameter = cube.getScale() / distance; if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { next.element = nextElement; next.intersection = ViewFrustum::OUTSIDE; return; } } } } } } next.element.reset(); next.intersection = ViewFrustum::OUTSIDE; }
bool AABox::touches(const AACube& otherCube) const { glm::vec3 relativeCenter = _corner - otherCube.getCorner() + ((_scale - otherCube.getDimensions()) * 0.5f); glm::vec3 totalHalfScale = (_scale + otherCube.getDimensions()) * 0.5f; return fabsf(relativeCenter.x) <= totalHalfScale.x && fabsf(relativeCenter.y) <= totalHalfScale.y && fabsf(relativeCenter.z) <= totalHalfScale.z; }
QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube) { QScriptValue obj = engine->newObject(); const glm::vec3& corner = aaCube.getCorner(); obj.setProperty("x", corner.x); obj.setProperty("y", corner.y); obj.setProperty("z", corner.z); obj.setProperty("scale", aaCube.getScale()); return obj; }
bool OctreePacketData::appendValue(const AACube& aaCube) { aaCubeData cube { aaCube.getCorner(), aaCube.getScale() }; const unsigned char* data = (const unsigned char*)&cube; int length = sizeof(aaCubeData); bool success = append(data, length); if (success) { _bytesOfValues += length; _totalBytesOfValues += length; } return success; }
// does this entity tree element contain the old entity bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(OctreeElement* element) { bool containsEntity = false; // If we don't have an old entity, then we don't contain the entity, otherwise // check the bounds if (_entitiesToDelete.size() > 0) { AACube elementCube = element->getAACube(); foreach(const EntityToDeleteDetails& details, _entitiesToDelete) { if (elementCube.contains(details.cube)) { containsEntity = true; break; // if it contains at least one, we're good to go } } }
void ParticleTreeElement::getParticles(const AACube& box, QVector<Particle*>& foundParticles) { QList<Particle>::iterator particleItr = _particles->begin(); QList<Particle>::iterator particleEnd = _particles->end(); AACube particleCube; while(particleItr != particleEnd) { Particle* particle = &(*particleItr); float radius = particle->getRadius(); // NOTE: we actually do box-box collision queries here, which is sloppy but good enough for now // TODO: decide whether to replace particleBox-box query with sphere-box (requires a square root // but will be slightly more accurate). particleCube.setBox(particle->getPosition() - glm::vec3(radius), 2.f * radius); if (particleCube.touches(_cube)) { foundParticles.push_back(particle); } ++particleItr; } }
bool AACube::contains(const AACube& otherCube) const { for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { glm::vec3 vertex = otherCube.getVertex((BoxVertex)v); if (!contains(vertex)) { return false; } } return true; }
void aaCubeFromScriptValue(const QScriptValue &object, AACube& aaCube) { glm::vec3 corner; corner.x = object.property("x").toVariant().toFloat(); corner.y = object.property("y").toVariant().toFloat(); corner.z = object.property("z").toVariant().toFloat(); float scale = object.property("scale").toVariant().toFloat(); aaCube.setBox(corner, scale); }
void MovingEntitiesOperator::addEntityToMoveList(EntityItemPointer entity, const AACube& newCube) { EntityTreeElementPointer oldContainingElement = entity->getElement(); AABox newCubeClamped = newCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_wantDebug) { qCDebug(entities) << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; qCDebug(entities) << " newCube:" << newCube; qCDebug(entities) << " newCubeClamped:" << newCubeClamped; if (oldContainingElement) { qCDebug(entities) << " oldContainingElement:" << oldContainingElement->getAACube(); qCDebug(entities) << " oldContainingElement->bestFitBounds(newCubeClamped):" << oldContainingElement->bestFitBounds(newCubeClamped); } else { qCDebug(entities) << " WARNING NO OLD CONTAINING ELEMENT for entity" << entity->getEntityItemID(); } } if (!oldContainingElement) { return; // bail without adding. } // If the original containing element is the best fit for the requested newCube locations then // we don't actually need to add the entity for moving and we can short circuit all this work if (!oldContainingElement->bestFitBounds(newCubeClamped)) { // check our tree, to determine if this entity is known EntityToMoveDetails details; details.oldContainingElement = oldContainingElement; details.oldContainingElementCube = oldContainingElement->getAACube(); details.entity = entity; details.oldFound = false; details.newFound = false; details.newCube = newCube; details.newCubeClamped = newCubeClamped; _entitiesToMove << details; _lookingCount++; if (_wantDebug) { qCDebug(entities) << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; qCDebug(entities) << " details.entity:" << details.entity->getEntityItemID(); qCDebug(entities) << " details.oldContainingElementCube:" << details.oldContainingElementCube; qCDebug(entities) << " details.newCube:" << details.newCube; qCDebug(entities) << " details.newCubeClamped:" << details.newCubeClamped; qCDebug(entities) << " _lookingCount:" << _lookingCount; qCDebug(entities) << "--------------------------------------------------------------------------"; } } else { if (_wantDebug) { qCDebug(entities) << " oldContainingElement->bestFitBounds(newCubeClamped) IS BEST FIT... NOTHING TO DO"; } } if (_wantDebug) { qCDebug(entities) << "--------------------------------------------------------------------------"; } }
void VoxelShapeManager::updateVoxels(const quint64& now, CubeList& cubes) { const quint64 VOXEL_UPDATE_PERIOD = 100000; // usec _updateExpiry = now + VOXEL_UPDATE_PERIOD; PhysicsSimulation* simulation = getSimulation(); if (!simulation) { return; } int numChanges = 0; VoxelPool::iterator voxelItr = _voxels.begin(); while (voxelItr != _voxels.end()) { // look for this voxel in cubes CubeList::iterator cubeItr = cubes.find(voxelItr.key()); if (cubeItr == cubes.end()) { // did not find it --> remove the voxel simulation->removeShape(voxelItr.value()._shape); voxelItr = _voxels.erase(voxelItr); ++numChanges; } else { // found it --> remove the cube cubes.erase(cubeItr); voxelItr++; } } // add remaining cubes to _voxels glm::vec3 simulationOrigin = simulation->getTranslation(); CubeList::const_iterator cubeItr = cubes.constBegin(); while (cubeItr != cubes.constEnd()) { AACube cube = cubeItr.value(); AACubeShape* shape = new AACubeShape(cube.getScale(), cube.calcCenter() - simulationOrigin); shape->setEntity(this); VoxelInfo voxel = {cube, shape }; _voxels.insert(cubeItr.key(), voxel); ++numChanges; ++cubeItr; } if (numChanges > 0) { buildShapes(); } }
AABox::AABox(const AACube& other) : _corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) { }
AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { AACube maxCube = getMaximumAACubeInMeters(); maxCube.scale(1.0f / (float)TREE_SCALE); return maxCube; }
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() << "******************************************************************************************"; } }
AACube< Point >::AACube(unsigned int box, const AACube< Point > &cube) { cube.SubCube(box, this); }
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 {
OctreeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { const glm::vec3& bottomNearRight = box.getCorner(); glm::vec3 topFarLeft = box.calcTopFarLeft(); int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit + ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera + ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining + ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices OctreeProjectedPolygon projectedPolygon(vertexCount); bool pointInView = true; bool allPointsInView = false; // assume the best, but wait till we know we have a vertex bool anyPointsInView = false; // assume the worst! if (vertexCount) { allPointsInView = true; // assume the best! for(int i = 0; i < vertexCount; i++) { int vertexNum = hullVertexLookup[lookUp][i+1]; glm::vec3 point = box.getVertex((BoxVertex)vertexNum); glm::vec2 projectedPoint = projectPoint(point, pointInView); allPointsInView = allPointsInView && pointInView; anyPointsInView = anyPointsInView || pointInView; projectedPolygon.setVertex(i, projectedPoint); } /*** // Now that we've got the polygon, if it extends beyond the clipping window, then let's clip it // NOTE: This clipping does not improve our overall performance. It basically causes more polygons to // end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes() if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) || (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { CoverageRegion::_clippedPolygons++; glm::vec2* clippedVertices; int clippedVertexCount; PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); // Now reset the vertices of our projectedPolygon object projectedPolygon.setVertexCount(clippedVertexCount); for(int i = 0; i < clippedVertexCount; i++) { projectedPolygon.setVertex(i, clippedVertices[i]); } delete[] clippedVertices; lookUp += PROJECTION_CLIPPED; } ***/ } // set the distance from our camera position, to the closest vertex float distance = glm::distance(getPosition(), box.calcCenter()); projectedPolygon.setDistance(distance); projectedPolygon.setAnyInView(anyPointsInView); projectedPolygon.setAllInView(allPointsInView); projectedPolygon.setProjectionType(lookUp); // remember the projection type return projectedPolygon; }
void EntityItem::recalculateCollisionShape() { AACube entityAACube = getMinimumAACube(); entityAACube.scale(TREE_SCALE); // scale to meters _collisionShape.setTranslation(entityAACube.calcCenter()); _collisionShape.setScale(entityAACube.getScale()); }