QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) { EntityItemID entityID(id); // If we have a local entity tree set, then also update it. if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } bool updatedEntity = false; _entityTree->withWriteLock([&] { updatedEntity = _entityTree->updateEntity(entityID, properties); }); 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 (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 a 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_EDIT_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->flagForOwnership(); } } entity->setLastBroadcast(usecTimestampNow()); } }); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; }
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = properties; EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { // This Node is creating a new object. If it's in motion, set this Node as the simulator. auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); // and make note of it now, so we can act on it right away. entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } // queue the packet if (success) { queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); } return id; }
bool EntityScriptingInterface::setAbsoluteJointTranslationsInObjectFrame(const QUuid& entityID, const QVector<glm::vec3>& translations) { if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { auto now = usecTimestampNow(); auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity); bool result = false; for (int index = 0; index < translations.size(); index++) { result |= modelEntity->setAbsoluteJointTranslationInObjectFrame(index, translations[index]); } if (result) { EntityItemProperties properties; _entityTree->withWriteLock([&] { entity->setLastEdited(now); entity->setLastBroadcast(now); properties = entity->getProperties(); }); properties.setJointTranslationsDirty(); properties.setLastEdited(now); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return true; } } return false; }
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); auto newVelocity = propertiesWithSimID.getVelocity().length(); float cost = calculateCost(density * volume, 0, newVelocity); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { return QUuid(); } EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { if (propertiesWithSimID.parentRelatedPropertyChanged()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. bool success; AACube queryAACube = entity->getQueryAACube(success); if (success) { propertiesWithSimID.setQueryAACube(queryAACube); } } if (_bidOnSimulationOwnership) { // This Node is creating a new object. If it's in motion, set this Node as the simulator. auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); // and make note of it now, so we can act on it right away. propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); } entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } // queue the packet if (success) { emit debitEnergySource(cost); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); } return id; }
EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const EntityItemProperties& properties) { EntityItemID actualID = entityID; // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID.id != UNKNOWN_ENTITY_ID) { entityID.id = actualID.id; entityID.isKnownID = true; } } // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); _entityTree->updateEntity(entityID, properties); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include if (properties.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { EntityItemProperties tempProperties = properties; tempProperties.setType(entity->getType()); queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, tempProperties); return entityID; } } // if the properties already includes the type, then use it as is queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); } return entityID; }
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { // The application will keep track of creatorTokenID uint32_t creatorTokenID = EntityItemID::getNextCreatorTokenID(); EntityItemID id(NEW_ENTITY, creatorTokenID, false ); // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); _entityTree->addEntity(id, properties); _entityTree->unlock(); } // queue the packet queueEntityMessage(PacketTypeEntityAddOrEdit, id, properties); return id; }
bool EntityScriptingInterface::actionWorker(const QUuid& entityID, std::function<bool(EntitySimulation*, EntityItemPointer)> actor) { if (!_entityTree) { return false; } EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { EntitySimulation* simulation = _entityTree->getSimulation(); entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { qDebug() << "actionWorker -- unknown entity" << entityID; return; } if (!simulation) { qDebug() << "actionWorker -- no simulation" << entityID; return; } doTransmit = actor(simulation, entity); if (doTransmit) { _entityTree->entityChanged(entity); } }); // transmit the change if (doTransmit) { EntityItemProperties properties; _entityTree->withReadLock([&] { properties = entity->getProperties(); }); properties.setActionDataDirty(); auto now = usecTimestampNow(); properties.setLastEdited(now); queueEntityMessage(PacketType::EntityEdit, entityID, properties); } return doTransmit; }
bool EntityScriptingInterface::setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation) { if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { auto now = usecTimestampNow(); auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity); bool result = modelEntity->setAbsoluteJointRotationInObjectFrame(jointIndex, rotation); if (result) { EntityItemProperties properties; _entityTree->withWriteLock([&] { properties = entity->getProperties(); entity->setLastBroadcast(now); }); properties.setJointRotationsDirty(); properties.setLastEdited(now); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return true; } } return false; }
bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor) { if (!_entityTree) { return false; } EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; } EntityTypes::EntityType entityType = entity->getType(); if (entityType != EntityTypes::Line) { return false; } auto now = usecTimestampNow(); auto lineEntity = std::static_pointer_cast<LineEntityItem>(entity); bool success; _entityTree->withWriteLock([&] { success = actor(*lineEntity); entity->setLastEdited(now); entity->setLastBroadcast(now); }); EntityItemProperties properties; _entityTree->withReadLock([&] { properties = entity->getProperties(); }); properties.setLinePointsDirty(); properties.setLastEdited(now); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return success; }
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; }