//// 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> const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(nullptr), _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.getVelocity()); scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), 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(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(entitySideProperties.getDimensions(), 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; if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), 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(), 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(), 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(), 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, bool grabbable, 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.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); 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 entityID) { const EntityPropertyFlags noSpecificProperties; return getEntityProperties(entityID, noSpecificProperties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); 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);} struct EntityPropertiesResult { EntityPropertiesResult(const EntityItemProperties& properties, bool scalesWithParent) : properties(properties), scalesWithParent(scalesWithParent) { } EntityPropertiesResult() = default; EntityItemProperties properties; bool scalesWithParent{ false };}; // Static method to make sure that we have the right script engine.// Using sender() or QtScriptable::engine() does not work for classes used by multiple threads (script-engines)QScriptValue EntityScriptingInterface::getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine) { const int ARGUMENT_ENTITY_IDS = 0; const int ARGUMENT_EXTENDED_DESIRED_PROPERTIES = 1; auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); const auto entityIDs = qScriptValueToValue<QVector<QUuid>>(context->argument(ARGUMENT_ENTITY_IDS)); return entityScriptingInterface->getMultipleEntityPropertiesInternal(engine, entityIDs, context->argument(ARGUMENT_EXTENDED_DESIRED_PROPERTIES));} QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector<QUuid> entityIDs, const QScriptValue& extendedDesiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); EntityPsuedoPropertyFlags psuedoPropertyFlags; const auto readExtendedPropertyStringValue = [&](QScriptValue extendedProperty) { const auto extendedPropertyString = extendedProperty.toString(); if (extendedPropertyString == "id") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID); } else if (extendedPropertyString == "type") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Type); } else if (extendedPropertyString == "created") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Created); } else if (extendedPropertyString == "age") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Age); } else if (extendedPropertyString == "ageAsText") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AgeAsText); } else if (extendedPropertyString == "lastEdited") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LastEdited); } else if (extendedPropertyString == "boundingBox") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::BoundingBox); } else if (extendedPropertyString == "originalTextures") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OriginalTextures); } else if (extendedPropertyString == "renderInfo") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::RenderInfo); } else if (extendedPropertyString == "clientOnly") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly); } else if (extendedPropertyString == "owningAvatarID") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID); } }; if (extendedDesiredProperties.isString()) { readExtendedPropertyStringValue(extendedDesiredProperties); psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); } else if (extendedDesiredProperties.isArray()) { const quint32 length = extendedDesiredProperties.property("length").toInt32(); for (quint32 i = 0; i < length; i++) { readExtendedPropertyStringValue(extendedDesiredProperties.property(i)); } psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); } EntityPropertyFlags desiredProperties = qScriptValueToValue<EntityPropertyFlags>(extendedDesiredProperties); bool needsScriptSemantics = 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 (needsScriptSemantics) { // 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); } QVector<EntityPropertiesResult> resultProperties; if (_entityTree) { PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Obtaining Properties"); int i = 0; const int lockAmount = 500; int size = entityIDs.size(); while (i < size) { _entityTree->withReadLock([&] { for (int j = 0; j < lockAmount && i < size; ++i, ++j) { const auto& entityID = entityIDs.at(i); const EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { if (psuedoPropertyFlags.none() && 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); psuedoPropertyFlags.set(); needsScriptSemantics = true;Lines 1–500 of 2023. 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> const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(nullptr), _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.getVelocity()); scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), 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(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), scalesWithParent, success); glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(entitySideProperties.getDimensions(), 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; if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), 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(), 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(), 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(), 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, bool grabbable, 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.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); 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 entityID) { const EntityPropertyFlags noSpecificProperties; return getEntityProperties(entityID, noSpecificProperties); } EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); 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); } struct EntityPropertiesResult { EntityPropertiesResult(const EntityItemProperties& properties, bool scalesWithParent) : properties(properties), scalesWithParent(scalesWithParent) { } EntityPropertiesResult() = default; EntityItemProperties properties; bool scalesWithParent{ false }; }; // Static method to make sure that we have the right script engine. // Using sender() or QtScriptable::engine() does not work for classes used by multiple threads (script-engines) QScriptValue EntityScriptingInterface::getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine) { const int ARGUMENT_ENTITY_IDS = 0; const int ARGUMENT_EXTENDED_DESIRED_PROPERTIES = 1; auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); const auto entityIDs = qScriptValueToValue<QVector<QUuid>>(context->argument(ARGUMENT_ENTITY_IDS)); return entityScriptingInterface->getMultipleEntityPropertiesInternal(engine, entityIDs, context->argument(ARGUMENT_EXTENDED_DESIRED_PROPERTIES)); } QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector<QUuid> entityIDs, const QScriptValue& extendedDesiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); EntityPsuedoPropertyFlags psuedoPropertyFlags; const auto readExtendedPropertyStringValue = [&](QScriptValue extendedProperty) { const auto extendedPropertyString = extendedProperty.toString(); if (extendedPropertyString == "id") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID); } else if (extendedPropertyString == "type") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Type); } else if (extendedPropertyString == "created") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Created); } else if (extendedPropertyString == "age") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Age); } else if (extendedPropertyString == "ageAsText") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AgeAsText); } else if (extendedPropertyString == "lastEdited") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LastEdited); } else if (extendedPropertyString == "boundingBox") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::BoundingBox); } else if (extendedPropertyString == "originalTextures") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OriginalTextures); } else if (extendedPropertyString == "renderInfo") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::RenderInfo); } else if (extendedPropertyString == "clientOnly") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly); } else if (extendedPropertyString == "owningAvatarID") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID); } }; if (extendedDesiredProperties.isString()) { readExtendedPropertyStringValue(extendedDesiredProperties); psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); } else if (extendedDesiredProperties.isArray()) { const quint32 length = extendedDesiredProperties.property("length").toInt32(); for (quint32 i = 0; i < length; i++) { readExtendedPropertyStringValue(extendedDesiredProperties.property(i)); } psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); } EntityPropertyFlags desiredProperties = qScriptValueToValue<EntityPropertyFlags>(extendedDesiredProperties); bool needsScriptSemantics = 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 (needsScriptSemantics) { // 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); } QVector<EntityPropertiesResult> resultProperties; if (_entityTree) { PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Obtaining Properties"); int i = 0; const int lockAmount = 500; int size = entityIDs.size(); while (i < size) { _entityTree->withReadLock([&] { for (int j = 0; j < lockAmount && i < size; ++i, ++j) { const auto& entityID = entityIDs.at(i); const EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { if (psuedoPropertyFlags.none() && 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); psuedoPropertyFlags.set(); needsScriptSemantics = true;