//// EntityScriptingInterface.cpp// libraries/entities/src//// Created by Brad Hefta-Gaub on 12/6/13.// Copyright 2013 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "EntityScriptingInterface.h" #include <glm/gtc/matrix_transform.hpp>#include <glm/gtx/transform.hpp> #include <QFutureWatcher>#include <QtConcurrent/QtConcurrentRun> #include <shared/QtHelpers.h>#include <VariantMapToScriptValue.h>#include <SharedUtil.h>#include <SpatialParentFinder.h>#include <AvatarHashMap.h> #include "EntityItemID.h"#include "EntitiesLogging.h"#include "EntityDynamicFactoryInterface.h"#include "EntityDynamicInterface.h"#include "EntitySimulation.h"#include "EntityTree.h"#include "LightEntityItem.h"#include "ModelEntityItem.h"#include "QVariantGLM.h"#include "SimulationOwner.h"#include "ZoneEntityItem.h"#include "WebEntityItem.h"#include <EntityScriptClient.h>#include <Profile.h> EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(NULL), _bidOnSimulationOwnership(bidOnSimulationOwnership){ auto nodeList = DependencyManager::get<NodeList>(); connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged); connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged); connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged); connect(nodeList.data(), &NodeList::canRezCertifiedChanged, this, &EntityScriptingInterface::canRezCertifiedChanged); connect(nodeList.data(), &NodeList::canRezTmpCertifiedChanged, this, &EntityScriptingInterface::canRezTmpCertifiedChanged); connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged); // If the user clicks somewhere where there is no entity at all, we will release focus connect(this, &EntityScriptingInterface::mousePressOffEntity, [=]() { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");} void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties);} void EntityScriptingInterface::resetActivityTracking() { _activityTracking.addedEntityCount = 0; _activityTracking.deletedEntityCount = 0; _activityTracking.editedEntityCount = 0;} bool EntityScriptingInterface::canAdjustLocks() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->isAllowedEditor();} bool EntityScriptingInterface::canRez() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRez();} bool EntityScriptingInterface::canRezTmp() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezTmp();} bool EntityScriptingInterface::canRezCertified() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezCertified();} bool EntityScriptingInterface::canRezTmpCertified() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezTmpCertified();} bool EntityScriptingInterface::canWriteAssets() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanWriteAssets();} bool EntityScriptingInterface::canReplaceContent() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanReplaceContent();} void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } _entityTree = elementTree; if (_entityTree) { connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); }} EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperties& entitySideProperties, bool scalesWithParent) { // 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()); scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity()); scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(entitySideProperties.getVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(entitySideProperties.getDimensions().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); scriptSideProperties.setPosition(worldPosition); scriptSideProperties.setRotation(worldRotation); scriptSideProperties.setVelocity(worldVelocity); scriptSideProperties.setAngularVelocity(worldAngularVelocity); scriptSideProperties.setDimensions(worldDimensions); return scriptSideProperties;} EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProperties& scriptSideProperties, bool scalesWithParent) { // 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().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, 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(), scalesWithParent, success); entitySideProperties.setRotation(localRotation); } if (scriptSideProperties.localVelocityChanged()) { entitySideProperties.setVelocity(scriptSideProperties.getLocalVelocity()); } else if (scriptSideProperties.velocityChanged()) { glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(entitySideProperties.getVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setVelocity(localVelocity); } if (scriptSideProperties.localAngularVelocityChanged()) { entitySideProperties.setAngularVelocity(scriptSideProperties.getLocalAngularVelocity()); } else if (scriptSideProperties.angularVelocityChanged()) { glm::vec3 localAngularVelocity = SpatiallyNestable::worldToLocalAngularVelocity(entitySideProperties.getAngularVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setAngularVelocity(localAngularVelocity); } if (scriptSideProperties.localDimensionsChanged()) { entitySideProperties.setDimensions(scriptSideProperties.getLocalDimensions()); } else if (scriptSideProperties.dimensionsChanged()) { glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(entitySideProperties.getDimensions().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setDimensions(localDimensions); } return entitySideProperties;} QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.addedEntityCount++; auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { const QUuid myNodeID = sessionID; propertiesWithSimID.setClientOnly(clientOnly); propertiesWithSimID.setOwningAvatarID(myNodeID); } propertiesWithSimID.setLastEditedBy(sessionID); bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); EntityItemID id; // If we have a local entity tree set, then also update it. bool success = addLocalEntityCopy(propertiesWithSimID, id); // queue the packet if (success) { queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); return id; } else { return QUuid(); }} bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id, bool isClone) { bool success = true; id = EntityItemID(QUuid::createUuid()); if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->addEntity(id, properties, isClone); if (entity) { if (properties.queryAACubeRelatedPropertyChanged()) { // 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) { properties.setQueryAACube(queryAACube); } } entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); properties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } return success;} QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, bool collisionless, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(name); properties.setModelURL(modelUrl); properties.setShapeTypeFromString(shapeType); properties.setDynamic(dynamic); properties.setCollisionless(collisionless); properties.setPosition(position); properties.setGravity(gravity); if (!textures.isEmpty()) { properties.setTextures(textures); } auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); properties.setLastEditedBy(sessionID); return addEntity(properties);} QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) { EntityItemID newEntityID; EntityItemProperties properties = getEntityProperties(entityIDToClone); bool cloneAvatarEntity = properties.getCloneAvatarEntity(); properties.convertToCloneProperties(entityIDToClone); if (cloneAvatarEntity) { return addEntity(properties, true); } else { // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties // from the server-created entity, don't change this unless you know what you are doing properties.setLastEdited(0); bool success = addLocalEntityCopy(properties, newEntityID, true); if (success) { getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID); return newEntityID; } else { return QUuid(); } }} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) { EntityPropertyFlags noSpecificProperties; return getEntityProperties(identity, noSpecificProperties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); if (entity) { scalesWithParent = entity->getScalesWithParent(); if (desiredProperties.getHasProperty(PROP_POSITION) || desiredProperties.getHasProperty(PROP_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_POSITION) || desiredProperties.getHasProperty(PROP_LOCAL_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_VELOCITY) || desiredProperties.getHasProperty(PROP_LOCAL_ANGULAR_VELOCITY) || desiredProperties.getHasProperty(PROP_LOCAL_DIMENSIONS)) { // if we are explicitly getting position or rotation, we need parent information to make sense of them. desiredProperties.setHasProperty(PROP_PARENT_ID); desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX); } if (desiredProperties.isEmpty()) { // these are left out of EntityItem::getEntityProperties so that localPosition and localRotation // don't end up in json saves, etc. We still want them here, though. EncodeBitstreamParams params; // unknown desiredProperties = entity->getEntityProperties(params); desiredProperties.setHasProperty(PROP_LOCAL_POSITION); desiredProperties.setHasProperty(PROP_LOCAL_ROTATION); desiredProperties.setHasProperty(PROP_LOCAL_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_ANGULAR_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_DIMENSIONS); } results = entity->getProperties(desiredProperties); } }); } return convertPropertiesToScriptSemantics(results, scalesWithParent);} QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.editedEntityCount++; auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); EntityItemProperties properties = scriptSideProperties; properties.setLastEditedBy(sessionID); EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } // If we have a local entity tree set, then also update it. bool updatedEntity = false; _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { return; } if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { // don't edit other avatar's avatarEntities return; } 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. if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } if (!scriptSideProperties.parentJointIndexChanged()) { properties.setParentJointIndex(entity->getParentJointIndex()); } if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { properties.setPosition(entity->getWorldPosition()); } if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { properties.setRotation(entity->getWorldOrientation()); } if (!scriptSideProperties.localDimensionsChanged() && !scriptSideProperties.dimensionsChanged()) { properties.setDimensions(entity->getScaledDimensions()); } } properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); updatedEntity = _entityTree->updateEntity(entityID, properties); }); // FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially // breaks entities that are parented. // // To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist // in the local entity tree, we need to allow those edits to go through to the server. // if (!updatedEntity) { // return QUuid(); // } bool entityFound { false }; _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { entityFound = true; // 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->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } } if (properties.queryAACubeRelatedPropertyChanged()) {Lines 1–500 of 1889. Load more
// // EntityScriptingInterface.cpp // libraries/entities/src // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "EntityScriptingInterface.h" #include <glm/gtc/matrix_transform.hpp> #include <glm/gtx/transform.hpp> #include <QFutureWatcher> #include <QtConcurrent/QtConcurrentRun> #include <shared/QtHelpers.h> #include <VariantMapToScriptValue.h> #include <SharedUtil.h> #include <SpatialParentFinder.h> #include <AvatarHashMap.h> #include "EntityItemID.h" #include "EntitiesLogging.h" #include "EntityDynamicFactoryInterface.h" #include "EntityDynamicInterface.h" #include "EntitySimulation.h" #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" #include "QVariantGLM.h" #include "SimulationOwner.h" #include "ZoneEntityItem.h" #include "WebEntityItem.h" #include <EntityScriptClient.h> #include <Profile.h> EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(NULL), _bidOnSimulationOwnership(bidOnSimulationOwnership) { auto nodeList = DependencyManager::get<NodeList>(); connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged); connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged); connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged); connect(nodeList.data(), &NodeList::canRezCertifiedChanged, this, &EntityScriptingInterface::canRezCertifiedChanged); connect(nodeList.data(), &NodeList::canRezTmpCertifiedChanged, this, &EntityScriptingInterface::canRezTmpCertifiedChanged); connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged); // If the user clicks somewhere where there is no entity at all, we will release focus connect(this, &EntityScriptingInterface::mousePressOffEntity, [=]() { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket"); } void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); } void EntityScriptingInterface::resetActivityTracking() { _activityTracking.addedEntityCount = 0; _activityTracking.deletedEntityCount = 0; _activityTracking.editedEntityCount = 0; } bool EntityScriptingInterface::canAdjustLocks() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->isAllowedEditor(); } bool EntityScriptingInterface::canRez() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRez(); } bool EntityScriptingInterface::canRezTmp() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezTmp(); } bool EntityScriptingInterface::canRezCertified() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezCertified(); } bool EntityScriptingInterface::canRezTmpCertified() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezTmpCertified(); } bool EntityScriptingInterface::canWriteAssets() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanWriteAssets(); } bool EntityScriptingInterface::canReplaceContent() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanReplaceContent(); } void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } _entityTree = elementTree; if (_entityTree) { connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } } EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperties& entitySideProperties, bool scalesWithParent) { // 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()); scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity()); scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(entitySideProperties.getVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(entitySideProperties.getDimensions().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); scriptSideProperties.setPosition(worldPosition); scriptSideProperties.setRotation(worldRotation); scriptSideProperties.setVelocity(worldVelocity); scriptSideProperties.setAngularVelocity(worldAngularVelocity); scriptSideProperties.setDimensions(worldDimensions); return scriptSideProperties; } EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProperties& scriptSideProperties, bool scalesWithParent) { // 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().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, 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(), scalesWithParent, success); entitySideProperties.setRotation(localRotation); } if (scriptSideProperties.localVelocityChanged()) { entitySideProperties.setVelocity(scriptSideProperties.getLocalVelocity()); } else if (scriptSideProperties.velocityChanged()) { glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(entitySideProperties.getVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setVelocity(localVelocity); } if (scriptSideProperties.localAngularVelocityChanged()) { entitySideProperties.setAngularVelocity(scriptSideProperties.getLocalAngularVelocity()); } else if (scriptSideProperties.angularVelocityChanged()) { glm::vec3 localAngularVelocity = SpatiallyNestable::worldToLocalAngularVelocity(entitySideProperties.getAngularVelocity().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setAngularVelocity(localAngularVelocity); } if (scriptSideProperties.localDimensionsChanged()) { entitySideProperties.setDimensions(scriptSideProperties.getLocalDimensions()); } else if (scriptSideProperties.dimensionsChanged()) { glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(entitySideProperties.getDimensions().toGlm(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); entitySideProperties.setDimensions(localDimensions); } return entitySideProperties; } QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.addedEntityCount++; auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { const QUuid myNodeID = sessionID; propertiesWithSimID.setClientOnly(clientOnly); propertiesWithSimID.setOwningAvatarID(myNodeID); } propertiesWithSimID.setLastEditedBy(sessionID); bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); EntityItemID id; // If we have a local entity tree set, then also update it. bool success = addLocalEntityCopy(propertiesWithSimID, id); // queue the packet if (success) { queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); return id; } else { return QUuid(); } } bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id, bool isClone) { bool success = true; id = EntityItemID(QUuid::createUuid()); if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->addEntity(id, properties, isClone); if (entity) { if (properties.queryAACubeRelatedPropertyChanged()) { // 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) { properties.setQueryAACube(queryAACube); } } entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); properties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } return success; } QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, bool collisionless, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(name); properties.setModelURL(modelUrl); properties.setShapeTypeFromString(shapeType); properties.setDynamic(dynamic); properties.setCollisionless(collisionless); properties.setPosition(position); properties.setGravity(gravity); if (!textures.isEmpty()) { properties.setTextures(textures); } auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); properties.setLastEditedBy(sessionID); return addEntity(properties); } QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) { EntityItemID newEntityID; EntityItemProperties properties = getEntityProperties(entityIDToClone); bool cloneAvatarEntity = properties.getCloneAvatarEntity(); properties.convertToCloneProperties(entityIDToClone); if (cloneAvatarEntity) { return addEntity(properties, true); } else { // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties // from the server-created entity, don't change this unless you know what you are doing properties.setLastEdited(0); bool success = addLocalEntityCopy(properties, newEntityID, true); if (success) { getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID); return newEntityID; } else { return QUuid(); } } } EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) { EntityPropertyFlags noSpecificProperties; return getEntityProperties(identity, noSpecificProperties); } EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); if (entity) { scalesWithParent = entity->getScalesWithParent(); if (desiredProperties.getHasProperty(PROP_POSITION) || desiredProperties.getHasProperty(PROP_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_POSITION) || desiredProperties.getHasProperty(PROP_LOCAL_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_VELOCITY) || desiredProperties.getHasProperty(PROP_LOCAL_ANGULAR_VELOCITY) || desiredProperties.getHasProperty(PROP_LOCAL_DIMENSIONS)) { // if we are explicitly getting position or rotation, we need parent information to make sense of them. desiredProperties.setHasProperty(PROP_PARENT_ID); desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX); } if (desiredProperties.isEmpty()) { // these are left out of EntityItem::getEntityProperties so that localPosition and localRotation // don't end up in json saves, etc. We still want them here, though. EncodeBitstreamParams params; // unknown desiredProperties = entity->getEntityProperties(params); desiredProperties.setHasProperty(PROP_LOCAL_POSITION); desiredProperties.setHasProperty(PROP_LOCAL_ROTATION); desiredProperties.setHasProperty(PROP_LOCAL_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_ANGULAR_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_DIMENSIONS); } results = entity->getProperties(desiredProperties); } }); } return convertPropertiesToScriptSemantics(results, scalesWithParent); } QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.editedEntityCount++; auto nodeList = DependencyManager::get<NodeList>(); auto sessionID = nodeList->getSessionUUID(); EntityItemProperties properties = scriptSideProperties; properties.setLastEditedBy(sessionID); EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } // If we have a local entity tree set, then also update it. bool updatedEntity = false; _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { return; } if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { // don't edit other avatar's avatarEntities return; } 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. if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } if (!scriptSideProperties.parentJointIndexChanged()) { properties.setParentJointIndex(entity->getParentJointIndex()); } if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { properties.setPosition(entity->getWorldPosition()); } if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { properties.setRotation(entity->getWorldOrientation()); } if (!scriptSideProperties.localDimensionsChanged() && !scriptSideProperties.dimensionsChanged()) { properties.setDimensions(entity->getScaledDimensions()); } } properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); updatedEntity = _entityTree->updateEntity(entityID, properties); }); // FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially // breaks entities that are parented. // // To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist // in the local entity tree, we need to allow those edits to go through to the server. // if (!updatedEntity) { // return QUuid(); // } bool entityFound { false }; _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { entityFound = true; // 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->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } } if (properties.queryAACubeRelatedPropertyChanged()) {