EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperties& scriptSideProperties) { // convert position and rotation properties from world-space to local, unless localPosition and localRotation // are set. If they are set, they overwrite position and rotation. EntityItemProperties entitySideProperties = scriptSideProperties; bool success; // TODO -- handle velocity and angularVelocity if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), success); entitySideProperties.setPosition(localPosition); } if (scriptSideProperties.localRotationChanged()) { entitySideProperties.setRotation(scriptSideProperties.getLocalRotation()); } else if (scriptSideProperties.rotationChanged()) { glm::quat localRotation = SpatiallyNestable::worldToLocal(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), success); entitySideProperties.setRotation(localRotation); } return entitySideProperties; }
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position) { EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(name); properties.setModelURL(modelUrl); properties.setPosition(position); return addEntity(properties); }
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) { DETAILED_PROFILE_RANGE(simulation_physics, "Bid"); assert(entityTreeIsLocked()); updateSendVelocities(); EntityItemProperties properties; Transform localTransform; glm::vec3 linearVelocity; glm::vec3 angularVelocity; _entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity); properties.setPosition(localTransform.getTranslation()); properties.setRotation(localTransform.getRotation()); properties.setVelocity(linearVelocity); properties.setAcceleration(_entity->getAcceleration()); properties.setAngularVelocity(angularVelocity); // we don't own the simulation for this entity yet, but we're sending a bid for it quint64 now = usecTimestampNow(); uint8_t finalBidPriority = computeFinalBidPriority(); _entity->prepareForSimulationOwnershipBid(properties, now, finalBidPriority); EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. _lastStep = step; _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; // after sending a bid/update we clear _bumpedPriority // which might get promoted again next frame (after local script or simulation interaction) // or we might win the bid _bumpedPriority = 0; }
EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties& entitySideProperties) { // In EntityTree code, properties.position and properties.rotation are relative to the parent. In javascript, // they are in world-space. The local versions are put into localPosition and localRotation and position and // rotation are converted from local to world space. EntityItemProperties scriptSideProperties = entitySideProperties; scriptSideProperties.setLocalPosition(entitySideProperties.getPosition()); scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), success); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), success); // TODO -- handle velocity and angularVelocity scriptSideProperties.setPosition(worldPosition); scriptSideProperties.setRotation(worldRotation); return scriptSideProperties; }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { assert(_entity); assert(entityTreeIsLocked()); bool active = _body->isActive(); if (!active) { // make sure all derivatives are zero glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); _entity->setAcceleration(zero); _sentInactive = true; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { // acceleration measured during the most recent simulation step was close to gravity. if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { // only increment this if we haven't reached the threshold yet. this is to avoid // overflowing the counter. incrementAccelerationNearlyGravityCount(); } } else { // acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter resetAccelerationNearlyGravityCount(); } // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let // the entity server's estimates include gravity. if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(_entity->getGravity()); } else { _entity->setAcceleration(glm::vec3(0.0f)); } const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec bool movingSlowlyLinear = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD); bool movingSlowlyAngular = glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == glm::vec3(0.0f); if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } _sentInactive = false; } // remember properties for local server prediction _serverPosition = _entity->getPosition(); _serverRotation = _entity->getRotation(); _serverVelocity = _entity->getVelocity(); _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); _serverActionData = _entity->getActionData(); EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); if (_entity->actionDataNeedsTransmit()) { _entity->setActionDataNeedsTransmit(false); properties.setActionData(_serverActionData); } if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); #ifdef WANT_DEBUG quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG if (sessionID == _entity->getSimulatorID()) { // we think we own the simulation if (!active) { // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't properties.clearSimulationOwner(); _outgoingPriority = NO_PRORITY; } // else the ownership is not changing so we don't bother to pack it } else { // we don't own the simulation for this entity yet, but we're sending a bid for it properties.setSimulationOwner(sessionID, glm::max<quint8>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); _entity->setLastBroadcast(usecTimestampNow()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); if (descendant->computePuffedQueryAACube()) { EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(usecTimestampNow()); } } }); _lastStep = step; }
QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { EntityItemProperties properties = scriptSideProperties; auto dimensions = properties.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = properties.getDensity(); auto newVelocity = properties.getVelocity().length(); float oldVelocity = { 0.0f }; EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); //if there is no local entity entity tree, no existing velocity, use 0. float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { return QUuid(); } else { //debit the avatar energy and continue emit debitEnergySource(cost); } return id; } // If we have a local entity tree set, then also update it. bool updatedEntity = false; _entityTree->withWriteLock([&] { if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { return; } //existing entity, retrieve old velocity for check down below oldVelocity = entity->getVelocity().length(); if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } if (!scriptSideProperties.parentJointIndexChanged()) { properties.setParentJointIndex(entity->getParentJointIndex()); } if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { properties.setPosition(entity->getPosition()); } if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { properties.setRotation(entity->getOrientation()); } } properties = convertLocationFromScriptSemantics(properties); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { updatedEntity = false; } else { //debit the avatar energy and continue updatedEntity = _entityTree->updateEntity(entityID, properties); if (updatedEntity) { emit debitEnergySource(cost); } } }); if (!updatedEntity) { return QUuid(); } _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; if (_bidOnSimulationOwnership && hasPhysicsChanges) { auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); if (entity->getSimulatorID() == myNodeID) { // we think we already own the simulation, so make sure to send ALL TerseUpdate properties if (hasTerseUpdateChanges) { entity->getAllTerseUpdateProperties(properties); } // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical double-update) that we see during the "poke rolling // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's // simulation. if (entity->getSimulationPriority() < SCRIPT_POKE_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->pokeSimulationOwnership(); } } if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) { properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { if (descendant->computePuffedQueryAACube()) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(usecTimestampNow()); } } }); } }); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { DETAILED_PROFILE_RANGE(simulation_physics, "Send"); assert(entityTreeIsLocked()); assert(isLocallyOwned()); updateSendVelocities(); // remember _serverFoo data for local prediction of server state Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getDynamicData(); EntityItemProperties properties; properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); if (_entity->dynamicDataNeedsTransmit()) { _entity->setDynamicDataNeedsTransmit(false); properties.setActionData(_serverActionData); } if (_entity->updateQueryAACube()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); _entity->setSimulationOwnershipExpiry(now + MAX_OUTGOING_SIMULATION_UPDATE_PERIOD); if (_numInactiveUpdates > 0 && _entity->getScriptSimulationPriority() == 0) { // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); _entity->setPendingOwnershipPriority(0); } else { uint8_t newPriority = computeFinalBidPriority(); _entity->clearScriptSimulationPriority(); // if we get here then we own the simulation and the object is NOT going inactive // if newPriority is zero, then it must be outside of R1, which means we should really set it to YIELD // which we achive by just setting it to the max of the two newPriority = glm::max(newPriority, YIELD_SIMULATION_PRIORITY); if (newPriority != _entity->getSimulationPriority() && !(newPriority == VOLUNTEER_SIMULATION_PRIORITY && _entity->getSimulationPriority() == RECRUIT_SIMULATION_PRIORITY)) { // our desired priority has changed if (newPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to inform the entity-server properties.setSimulationOwner(Physics::getSessionUUID(), newPriority); } _entity->setPendingOwnershipPriority(newPriority); } } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; properties.setEntityHostType(_entity->getEntityHostType()); properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); _entity->setLastBroadcast(now); // for debug/physics status icons // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); if (descendant->updateQueryAACube()) { EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); newQueryCubeProperties.setEntityHostType(entityDescendant->getEntityHostType()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(now); // for debug/physics status icons } } }); _lastStep = step; // after sending a bid/update we clear _bumpedPriority // which might get promoted again next frame (after local script or simulation interaction) // or we might win the bid _bumpedPriority = 0; }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { if (!_entity->isKnownID()) { return; // never update entities that are unknown } if (_outgoingPacketFlags) { EntityItemProperties properties = _entity->getProperties(); if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) { btTransform worldTrans = _body->getWorldTransform(); _sentPosition = bulletToGLM(worldTrans.getOrigin()); properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset()); _sentRotation = bulletToGLM(worldTrans.getRotation()); properties.setRotation(_sentRotation); } if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) { if (_body->isActive()) { _sentVelocity = bulletToGLM(_body->getLinearVelocity()); _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); // if the speeds are very small we zero them out const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec bool zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); if (zeroSpeed) { _sentVelocity = glm::vec3(0.0f); } const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec bool zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; if (zeroSpin) { _sentAngularVelocity = glm::vec3(0.0f); } _sentMoving = ! (zeroSpeed && zeroSpin); } else { _sentVelocity = _sentAngularVelocity = glm::vec3(0.0f); _sentMoving = false; } properties.setVelocity(_sentVelocity); _sentAcceleration = bulletToGLM(_body->getGravity()); properties.setGravity(_sentAcceleration); // DANGER! EntityItem stores angularVelocity in degrees/sec!!! properties.setAngularVelocity(glm::degrees(_sentAngularVelocity)); } // RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit. if (_sentMoving) { _numNonMovingUpdates = 0; } else { _numNonMovingUpdates++; } if (_numNonMovingUpdates <= 1) { // we only update lastEdited when we're sending new physics data // (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data) // NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly quint64 lastSimulated = _entity->getLastSimulated(); _entity->setLastEdited(lastSimulated); properties.setLastEdited(lastSimulated); } else { properties.setLastEdited(_entity->getLastEdited()); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); // The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them // to the full set. These flags may be momentarily cleared by incoming external changes. _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; _sentFrame = frame; } }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { assert(_entity); assert(_entity->isKnownID()); bool active = _body->isActive(); if (!active) { // make sure all derivatives are zero glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); _entity->setAcceleration(zero); _sentActive = false; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { // acceleration measured during the most recent simulation step was close to gravity. if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { // only increment this if we haven't reached the threshold yet. this is to avoid // overflowing the counter. incrementAccelerationNearlyGravityCount(); } } else { // acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter resetAccelerationNearlyGravityCount(); } // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let // the entity server's estimates include gravity. if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(_entity->getGravity()); } else { _entity->setAcceleration(glm::vec3(0.0f)); } const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD) && glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD) && _entity->getAcceleration() == glm::vec3(0.0f); if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } _sentActive = true; } // remember properties for local server prediction _serverPosition = _entity->getPosition(); _serverRotation = _entity->getRotation(); _serverVelocity = _entity->getVelocity(); _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); EntityItemProperties properties = _entity->getProperties(); // explicitly set the properties that changed so that they will be packed properties.setPosition(_serverPosition); properties.setRotation(_serverRotation); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); // we only update lastEdited when we're sending new physics data quint64 lastSimulated = _entity->getLastSimulated(); _entity->setLastEdited(lastSimulated); properties.setLastEdited(lastSimulated); #ifdef WANT_DEBUG quint64 now = usecTimestampNow(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG if (sessionID == _entity->getSimulatorID()) { // we think we own the simulation if (!active) { // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't properties.setSimulatorID(QUuid()); } else { // explicitly set the property's simulatorID so that it is flagged as changed and will be packed properties.setSimulatorID(sessionID); } } else { // we don't own the simulation for this entity yet, but we're sending a bid for it properties.setSimulatorID(sessionID); } if (EntityItem::getSendPhysicsUpdates()) { EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); _entity->setLastBroadcast(usecTimestampNow()); } else { #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested."; #endif } _lastStep = step; }
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() << "******************************************************************************************"; } }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { assert(_entity); assert(entityTreeIsLocked()); if (!_body->isActive()) { // make sure all derivatives are zero _entity->setVelocity(Vectors::ZERO); _entity->setAngularVelocity(Vectors::ZERO); _entity->setAcceleration(Vectors::ZERO); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let // the entity server's estimates include gravity. const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(gravity); } else { _entity->setAcceleration(Vectors::ZERO); } if (!_body->isStaticOrKinematicObject()) { const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec bool movingSlowlyLinear = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD); bool movingSlowlyAngular = glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == Vectors::ZERO; if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } } _numInactiveUpdates = 0; } // remember properties for local server prediction Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getActionData(); EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); if (_entity->actionDataNeedsTransmit()) { _entity->setActionDataNeedsTransmit(false); properties.setActionData(_serverActionData); } if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); #ifdef WANT_DEBUG quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG if (_numInactiveUpdates > 0) { // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); _outgoingPriority = 0; _entity->setPendingOwnershipPriority(_outgoingPriority, now); } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { // we don't own the simulation for this entity yet, but we're sending a bid for it quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; // copy _outgoingPriority into pendingPriority... _entity->setPendingOwnershipPriority(_outgoingPriority, now); // ...then reset _outgoingPriority in preparation for the next frame _outgoingPriority = 0; } else if (_outgoingPriority != _entity->getSimulationPriority()) { // we own the simulation but our desired priority has changed if (_outgoingPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); } _entity->setPendingOwnershipPriority(_outgoingPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; properties.setClientOnly(_entity->getClientOnly()); properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); _entity->setLastBroadcast(now); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); if (descendant->computePuffedQueryAACube()) { EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(now); } } }); _lastStep = step; }