// protected void EntitySimulation::sortEntitiesThatMoved() { // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. MovingEntitiesOperator moveOperator(_entityTree); AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; // check to see if this movement has sent the entity outside of the domain. bool success; AACube newCube = entity->getQueryAACube(success); if (success && !domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; itemItr = _entitiesToSort.erase(itemItr); entity->die(); prepareEntityForDelete(entity); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; } } if (moveOperator.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); _entityTree->recurseTreeWithOperator(&moveOperator); } _entitiesToSort.clear(); }
bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { return false; } return updateEntityWithElement(entity, properties, containingElement, senderNode); }
// protected void EntitySimulation::sortEntitiesThatMoved() { // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; // check to see if this movement has sent the entity outside of the domain. AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; _entitiesToDelete.insert(entity); _mortalEntities.remove(entity); _entitiesToUpdate.remove(entity); _simpleKinematicEntities.remove(entity); removeEntityInternal(entity); _allEntities.remove(entity); entity->_simulated = false; itemItr = _entitiesToSort.erase(itemItr); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; } } if (moveOperator.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); _entityTree->recurseTreeWithOperator(&moveOperator); } _entitiesToSort.clear(); }
UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, EntityItemPointer existingEntity, const AACube newQueryAACube) : _tree(tree), _existingEntity(existingEntity), _containingElement(containingElement), _containingElementCube(containingElement->getAACube()), _entityItemID(existingEntity->getEntityItemID()), _foundOld(false), _foundNew(false), _removeOld(false), _changeTime(usecTimestampNow()), _oldEntityCube(), _newEntityCube(), _wantDebug(false) { // caller must have verified existence of containingElement and oldEntity assert(_containingElement && _existingEntity); if (_wantDebug) { qCDebug(entities) << "UpdateEntityOperator::UpdateEntityOperator() -----------------------------"; } // Here we have a choice to make, do we want to "tight fit" the actual minimum for the // entity into the the element, or do we want to use the entities "relaxed" bounds // which can handle all potential rotations? // the getMaximumAACube is the relaxed form. _oldEntityCube = _existingEntity->getQueryAACube(); _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds _newEntityCube = newQueryAACube; _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds // set oldElementBestFit true if the entity was in the correct element before this operator was run. bool oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. if (!oldElementBestFit) { _oldEntityBox = _existingEntity->getElement()->getAACube(); _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { qCDebug(entities) << " **** UNUSUAL CASE **** not best fit.... **"; } } if (_wantDebug) { qCDebug(entities) << " _entityItemID:" << _entityItemID; qCDebug(entities) << " _containingElementCube:" << _containingElementCube; qCDebug(entities) << " _oldEntityCube:" << _oldEntityCube; qCDebug(entities) << " _oldEntityBox:" << _oldEntityBox; qCDebug(entities) << " _newEntityCube:" << _newEntityCube; qCDebug(entities) << " _newEntityBox:" << _newEntityBox; qCDebug(entities) << "--------------------------------------------------------------------------"; } }
/// Adds a new entity item to the tree void EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); } _isDirty = true; maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); }
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { EntityItemID result; if (_entityTree) { EntityItemPointer closestEntity; _entityTree->withReadLock([&] { closestEntity = _entityTree->findClosestEntity(center, radius); }); if (closestEntity) { result = closestEntity->getEntityItemID(); } } return result; }
void EntitySimulation::changeEntity(EntityItemPointer entity) { assert(entity); if (!entity->_simulated) { // This entity was either never added to the simulation or has been removed // (probably for pending delete), so we don't want to keep a pointer to it // on any internal lists. return; } // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & EntityItem::DIRTY_POSITION) { AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; _entitiesToDelete.insert(entity); _mortalEntities.remove(entity); _entitiesToUpdate.remove(entity); _entitiesToSort.remove(entity); _simpleKinematicEntities.remove(entity); removeEntityInternal(entity); entity->_simulated = false; wasRemoved = true; } } if (!wasRemoved) { if (dirtyFlags & EntityItem::DIRTY_LIFETIME) { if (entity->isMortal()) { _mortalEntities.insert(entity); quint64 expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } } else { _mortalEntities.remove(entity); } entity->clearDirtyFlags(EntityItem::DIRTY_LIFETIME); } if (entity->needsToCallUpdate()) { _entitiesToUpdate.insert(entity); } else { _entitiesToUpdate.remove(entity); } changeEntityInternal(entity); } }
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, bool precisionPicking) { RayToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, result.surfaceNormal, (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); result.intersection = ray.origin + (ray.direction * result.distance); } } return result; }
bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& origProperties, EntityTreeElementPointer containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->getThisNodeCanAdjustLocks(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->getCanAdjustLocks(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; } } } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulationOwnerChanged()) { QUuid submittedID = properties.getSimulationOwner().getID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; properties.clearSimulationOwner(); // clear everything } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { // the sender it taking ownership properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { // the sender is asserting ownership simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator // so we apply the rules for ownership change: // (1) higher priority wins // (2) equal priority wins if ownership filter has expired except... uint8_t oldPriority = entity->getSimulationPriority(); uint8_t newPriority = properties.getSimulationOwner().getPriority(); if (newPriority > oldPriority || (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; } } } else { // the entire update is suspect --> ignore it return false; } } else { simulationBlocked = senderID != entity->getSimulatorID(); } if (simulationBlocked) { // squash ownership and physics-related changes. properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); properties.setVelocityChanged(false); properties.setAngularVelocityChanged(false); properties.setAccelerationChanged(false); if (wantTerseEditLogging()) { qCDebug(entities) << senderNode->getUUID() << "physical edits suppressed"; } } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { if (_simulation) { if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->changeEntity(entity); } } else { // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly entity->clearDirtyFlags(); } } QString entityScriptAfter = entity->getScript(); quint64 entityScriptTimestampAfter = entity->getScriptTimestamp(); bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter; if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entity->getEntityItemID(); return false; } return true; }
bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& origProperties, EntityTreeElement* containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->getThisNodeCanAdjustLocks(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->getCanAdjustLocks(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); UpdateEntityOperator theOperator(this, containingElement, entity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; } } } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulatorIDChanged()) { QUuid submittedID = properties.getSimulatorID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator // so we apply the ownership change filter if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { simulationBlocked = false; } } } else { // the entire update is suspect --> ignore it return false; } } if (simulationBlocked) { // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { if (_simulation) { if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->lock(); _simulation->changeEntity(entity); _simulation->unlock(); } } else { // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly entity->clearDirtyFlags(); } } QString entityScriptAfter = entity->getScript(); if (entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed } maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entity->getEntityItemID(); return false; } return true; }
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 = !hasContent(); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElementPointer 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 { // if the child doesn't have enities, it is completed entityTreeElementExtraEncodeData->childCompleted[i] = true; } } } forEachEntity([&](EntityItemPointer entity) { 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; int numberOfEntitiesOffset = 0; withReadLock([&] { 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++) { EntityItemPointer 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. bool success; AACube entityCube = entity->getQueryAACube(success); if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) { includeThisEntity = false; // out of view, don't include it } else { // 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. success = true; // we can't cull a parent-entity by its dimensions because the child may be larger. we need to // avoid sending details about a child but not the parent. the parent's queryAACube should have // been adjusted to encompass the queryAACube of the child. AABox entityBounds = entity->hasChildren() ? AABox(entityCube) : entity->getAABox(success); if (!success) { // if this entity is a child of an avatar, the entity-server wont be able to determine its // AABox. If this happens, fall back to the queryAACube. entityBounds = AABox(entityCube); } auto renderAccuracy = params.viewFrustum->calculateRenderAccuracy(entityBounds, params.octreeElementSizeScale, params.boundaryLevelAdjust); if (renderAccuracy <= 0.0f) { includeThisEntity = false; // too small, don't include it #ifdef WANT_LOD_DEBUGGING qDebug() << "skipping entity - TOO SMALL - \n" << "......id:" << entity->getID() << "\n" << "....name:" << entity->getName() << "\n" << "..bounds:" << entityBounds << "\n" << "....cell:" << getAACube(); #endif } } } if (includeThisEntity) { #ifdef WANT_LOD_DEBUGGING qDebug() << "including entity - \n" << "......id:" << entity->getID() << "\n" << "....name:" << entity->getName() << "\n" << "....cell:" << getAACube(); #endif indexesOfEntitiesToInclude << i; numberOfEntities++; } else { // if the extra data included this entity, and we've decided to not include the entity, then // we can treat it as if it was completed. entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); } } } numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); bool successAppendEntityCount = packetData->appendValue(numberOfEntities); if (successAppendEntityCount) { foreach(uint16_t i, indexesOfEntitiesToInclude) { EntityItemPointer 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 {
UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, EntityItemPointer existingEntity, const EntityItemProperties& properties) : _tree(tree), _existingEntity(existingEntity), _containingElement(containingElement), _containingElementCube(containingElement->getAACube()), _properties(properties), _entityItemID(existingEntity->getEntityItemID()), _foundOld(false), _foundNew(false), _removeOld(false), _dontMove(false), // assume we'll be moving _changeTime(usecTimestampNow()), _oldEntityCube(), _newEntityCube(), _wantDebug(false) { // caller must have verified existence of containingElement and oldEntity assert(_containingElement && _existingEntity); if (_wantDebug) { qCDebug(entities) << "UpdateEntityOperator::UpdateEntityOperator() -----------------------------"; } // Here we have a choice to make, do we want to "tight fit" the actual minimum for the // entity into the the element, or do we want to use the entities "relaxed" bounds // which can handle all potential rotations? // the getMaximumAACube is the relaxed form. _oldEntityCube = _existingEntity->getMaximumAACube(); _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds // If the old properties doesn't contain the properties required to calculate a bounding box, // get them from the existing entity. Registration point is required to correctly calculate // the bounding box. if (!_properties.registrationPointChanged()) { _properties.setRegistrationPoint(_existingEntity->getRegistrationPoint()); } // If the new properties has position OR dimension changes, but not both, we need to // get the old property value and set it in our properties in order for our bounds // calculations to work. if (_properties.containsPositionChange() && !_properties.containsDimensionsChange()) { glm::vec3 oldDimensions= _existingEntity->getDimensions(); _properties.setDimensions(oldDimensions); if (_wantDebug) { qCDebug(entities) << " ** setting properties dimensions - had position change, no dimension change **"; } } if (!_properties.containsPositionChange() && _properties.containsDimensionsChange()) { glm::vec3 oldPosition= _existingEntity->getPosition(); _properties.setPosition(oldPosition); if (_wantDebug) { qCDebug(entities) << " ** setting properties position - had dimensions change, no position change **"; } } // If our new properties don't have bounds details (no change to position, etc) or if this containing element would // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update bool oldElementBestFit = _containingElement->bestFitBounds(_properties); // if we don't have bounds properties, then use our old clamped box to determine best fit if (!_properties.containsBoundsProperties()) { oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); if (_wantDebug) { qCDebug(entities) << " ** old Element best fit - no dimensions change, no position change **"; } } // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. if (!_properties.containsBoundsProperties() && !oldElementBestFit) { _newEntityCube = _oldEntityCube; _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { qCDebug(entities) << " **** UNUSUAL CASE **** no changes, but not best fit... consider it a move.... **"; } } else if (!_properties.containsBoundsProperties() || oldElementBestFit) { _foundOld = true; _newEntityCube = _oldEntityCube; _dontMove = true; if (_wantDebug) { qCDebug(entities) << " **** TYPICAL NO MOVE CASE ****"; qCDebug(entities) << " _properties.containsBoundsProperties():" << _properties.containsBoundsProperties(); qCDebug(entities) << " oldElementBestFit:" << oldElementBestFit; } } else { _newEntityCube = _properties.getMaximumAACube(); _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { qCDebug(entities) << " **** TYPICAL MOVE CASE ****"; } } _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds if (_wantDebug) { qCDebug(entities) << " _entityItemID:" << _entityItemID; qCDebug(entities) << " _containingElementCube:" << _containingElementCube; qCDebug(entities) << " _oldEntityCube:" << _oldEntityCube; qCDebug(entities) << " _oldEntityBox:" << _oldEntityBox; qCDebug(entities) << " _newEntityCube:" << _newEntityCube; qCDebug(entities) << " _newEntityBox:" << _newEntityBox; qCDebug(entities) << "--------------------------------------------------------------------------"; } }
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) << "--------------------------------------------------------------------------"; } }