//// 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 <QJsonObject>#include <QJsonDocument>#include <QJsonArray> #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>#include "GrabPropertyGroup.h"#include <ScriptContext.h>#include <ScriptEngineCast.h>#include <ScriptValue.h> const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; void staticEntityScriptInitializer(ScriptManager* manager) { auto scriptEngine = manager->engine().get(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->init(); auto ifacePtr = entityScriptingInterface.data(); // using this when we don't want to leak a reference registerMetaTypes(scriptEngine); scriptRegisterMetaType(scriptEngine, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue); scriptRegisterMetaType(scriptEngine, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); scriptRegisterMetaType(scriptEngine, EntityPropertyInfoToScriptValue, EntityPropertyInfoFromScriptValue); scriptRegisterMetaType(scriptEngine, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); scriptRegisterMetaType(scriptEngine, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Entities", entityScriptingInterface.data()); scriptEngine->registerFunction("Entities", "getMultipleEntityProperties", EntityScriptingInterface::getMultipleEntityProperties); // "The return value of QObject::sender() is not valid when the slot is called via a Qt::DirectConnection from a thread // different from this object's thread. Do not use this function in this type of scenario." // so... yay lambdas everywhere to get the sender manager->connect( manager, &ScriptManager::attachDefaultEventHandlers, entityScriptingInterface.data(), [ifacePtr, manager] { ifacePtr->attachDefaultEventHandlers(manager); }, Qt::DirectConnection); manager->connect(manager, &ScriptManager::releaseEntityPacketSenderMessages, entityScriptingInterface.data(), &EntityScriptingInterface::releaseEntityPacketSenderMessages, Qt::DirectConnection);}STATIC_SCRIPT_INITIALIZER(staticEntityScriptInitializer); 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); connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged); connect(nodeList.data(), &NodeList::canRezAvatarEntitiesChanged, this, &EntityScriptingInterface::canRezAvatarEntitiesChanged); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, PacketReceiver::makeSourcedListenerReference<EntityScriptingInterface>(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket));} void EntityScriptingInterface::releaseEntityPacketSenderMessages(bool wait) { EntityEditPacketSender* entityPacketSender = getEntityPacketSender(); if (entityPacketSender->serversExist()) { // release the queue of edit entity messages. entityPacketSender->releaseQueuedMessages(); // since we're in non-threaded mode, call process so that the packets are sent if (!entityPacketSender->isThreaded()) { if (!wait) { entityPacketSender->process(); } else { // wait here till the edit packet sender is completely done sending while (entityPacketSender->hasPacketsToSend()) { entityPacketSender->process(); QCoreApplication::processEvents(); } } } else { // FIXME - do we need to have a similar "wait here" loop for non-threaded packet senders? } }} void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager) { // Connect up ALL the handlers to the global entities object's signals. // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) // Bug? These handlers are deleted when entityID is deleted, which is nice. // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded? // Also, what about when the entity script is REloaded? // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS connect(this, &EntityScriptingInterface::deletingEntity, manager, [manager](const EntityItemID& entityID) { manager->removeAllEventHandlers(entityID); }); // Two common cases of event handler, differing only in argument signature. /*@jsdoc * Called when an entity event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~entityEventCallback * @param {Uuid} entityID - The ID of the entity the event has occured on. */ using SingleEntityHandler = std::function<void(const EntityItemID&)>; auto makeSingleEntityHandler = [manager](QString eventName) -> SingleEntityHandler { return [manager, eventName](const EntityItemID& entityItemID) { manager->forwardHandlerCall(entityItemID, eventName, { EntityItemIDtoScriptValue(manager->engine().get(), entityItemID) }); }; }; /*@jsdoc * Called when a pointer event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~pointerEventCallback * @param {Uuid} entityID - The ID of the entity the event has occurred on. * @param {PointerEvent} pointerEvent - Details of the event. */ using PointerHandler = std::function<void(const EntityItemID&, const PointerEvent&)>; auto makePointerHandler = [manager](QString eventName) -> PointerHandler { return [manager, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { if (!EntityTree::areEntityClicksCaptured()) { ScriptEngine* engine = manager->engine().get(); manager->forwardHandlerCall(entityItemID, eventName, { EntityItemIDtoScriptValue(engine, entityItemID), event.toScriptValue(engine) }); } }; }; /*@jsdoc * Called when a collision event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~collisionEventCallback * @param {Uuid} entityA - The ID of one entity in the collision. * @param {Uuid} entityB - The ID of the other entity in the collision. * @param {Collision} collisionEvent - Details of the collision. */ using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>; auto makeCollisionHandler = [manager](QString eventName) -> CollisionHandler { return [manager, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { ScriptEngine* engine = manager->engine().get(); manager->forwardHandlerCall(idA, eventName, { EntityItemIDtoScriptValue(engine, idA), EntityItemIDtoScriptValue(engine, idB), collisionToScriptValue(engine, collision) }); }; }; /*@jsdoc * <p>The name of an entity event. When the entity event occurs, any function that has been registered for that event * via {@link Script.addEventHandler} is called with parameters per the entity event.</p> * <table> * <thead> * <tr><th>Event Name</th><th>Callback Type</th><th>Entity Event</th></tr> * </thead> * <tbody> * <tr><td><code>"enterEntity"</code></td><td>{@link Script~entityEventCallback|entityEventCallback}</td> * <td>{@link Entities.enterEntity}</td></tr> * <tr><td><code>"leaveEntity"</code></td><td>{@link Script~entityEventCallback|entityEventCallback}</td> * <td>{@link Entities.leaveEntity}</td></tr> * <tr><td><code>"mousePressOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mousePressOnEntity}</td></tr> * <tr><td><code>"mouseMoveOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mouseMoveOnEntity}</td></tr> * <tr><td><code>"mouseReleaseOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mouseReleaseOnEntity}</td></tr> * <tr><td><code>"clickDownOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.clickDownOnEntity}</td></tr> * <tr><td><code>"holdingClickOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.holdingClickOnEntity}</td></tr> * <tr><td><code>"clickReleaseOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.clickReleaseOnEntity}</td></tr> * <tr><td><code>"hoverEnterEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverEnterEntity}</td></tr> * <tr><td><code>"hoverOverEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverOverEntity}</td></tr> * <tr><td><code>"hoverLeaveEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverLeaveEntity}</td></tr> * <tr><td><code>"collisionWithEntity"</code><td>{@link Script~collisionEventCallback|collisionEventCallback}</td> * </td><td>{@link Entities.collisionWithEntity}</td></tr> * </tbody> * </table> * @typedef {string} Script.EntityEvent */ connect(this, &EntityScriptingInterface::enterEntity, manager, makeSingleEntityHandler("enterEntity")); connect(this, &EntityScriptingInterface::leaveEntity, manager, makeSingleEntityHandler("leaveEntity")); connect(this, &EntityScriptingInterface::mousePressOnEntity, manager, makePointerHandler("mousePressOnEntity")); connect(this, &EntityScriptingInterface::mouseMoveOnEntity, manager, makePointerHandler("mouseMoveOnEntity")); connect(this, &EntityScriptingInterface::mouseReleaseOnEntity, manager, makePointerHandler("mouseReleaseOnEntity")); connect(this, &EntityScriptingInterface::clickDownOnEntity, manager, makePointerHandler("clickDownOnEntity")); connect(this, &EntityScriptingInterface::holdingClickOnEntity, manager, makePointerHandler("holdingClickOnEntity")); connect(this, &EntityScriptingInterface::clickReleaseOnEntity, manager, makePointerHandler("clickReleaseOnEntity")); connect(this, &EntityScriptingInterface::hoverEnterEntity, manager, makePointerHandler("hoverEnterEntity")); connect(this, &EntityScriptingInterface::hoverOverEntity, manager, makePointerHandler("hoverOverEntity")); connect(this, &EntityScriptingInterface::hoverLeaveEntity, manager, makePointerHandler("hoverLeaveEntity")); connect(this, &EntityScriptingInterface::collisionWithEntity, manager, makeCollisionHandler("collisionWithEntity"));} 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();} bool EntityScriptingInterface::canGetAndSetPrivateUserData() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanGetAndSetPrivateUserData();} bool EntityScriptingInterface::canRezAvatarEntities() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezAvatarEntities();} void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity); 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::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity, Qt::DirectConnection); connect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity, Qt::DirectConnection); 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;} // TODO: this method looks expensive and should take properties by reference, update it, and return voidEntityItemProperties 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;} void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) { if (grabProperties.equippableLeftPositionChanged() || grabProperties.equippableRightPositionChanged() || grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() || grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged()) { QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); if (grabProperties.equippableLeftPositionChanged()) { if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) { spatialKey.remove("leftRelativePosition"); } else { spatialKey["leftRelativePosition"] = QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())); } } if (grabProperties.equippableRightPositionChanged()) { if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) { spatialKey.remove("rightRelativePosition"); } else { spatialKey["rightRelativePosition"] = QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())); } } if (grabProperties.equippableLeftRotationChanged()) { spatialKey["relativeRotation"] = QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())); } else if (grabProperties.equippableRightRotationChanged()) { spatialKey["relativeRotation"] = QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())); } grabbableKey["spatialKey"] = spatialKey; userDataChanged = true; }} void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { if (grabProperties.triggerableChanged() || grabProperties.grabbableChanged() || grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() || grabProperties.equippableRightPositionChanged() || grabProperties.equippableRightRotationChanged()) { QJsonObject grabbableKey = userData["grabbableKey"].toObject(); if (grabProperties.triggerableChanged()) { if (grabProperties.getTriggerable()) { grabbableKey["triggerable"] = true; } else { grabbableKey.remove("triggerable"); } } if (grabProperties.grabbableChanged()) { if (grabProperties.getGrabbable()) { grabbableKey.remove("grabbable"); } else { grabbableKey["grabbable"] = false; } } if (grabProperties.grabFollowsControllerChanged()) { if (grabProperties.getGrabFollowsController()) { grabbableKey.remove("ignoreIK"); } else {Lines 1–500 of 2638. 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 <QJsonObject> #include <QJsonDocument> #include <QJsonArray> #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> #include "GrabPropertyGroup.h" #include <ScriptContext.h> #include <ScriptEngineCast.h> #include <ScriptValue.h> const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; void staticEntityScriptInitializer(ScriptManager* manager) { auto scriptEngine = manager->engine().get(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->init(); auto ifacePtr = entityScriptingInterface.data(); // using this when we don't want to leak a reference registerMetaTypes(scriptEngine); scriptRegisterMetaType(scriptEngine, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue); scriptRegisterMetaType(scriptEngine, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); scriptRegisterMetaType(scriptEngine, EntityPropertyInfoToScriptValue, EntityPropertyInfoFromScriptValue); scriptRegisterMetaType(scriptEngine, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); scriptRegisterMetaType(scriptEngine, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Entities", entityScriptingInterface.data()); scriptEngine->registerFunction("Entities", "getMultipleEntityProperties", EntityScriptingInterface::getMultipleEntityProperties); // "The return value of QObject::sender() is not valid when the slot is called via a Qt::DirectConnection from a thread // different from this object's thread. Do not use this function in this type of scenario." // so... yay lambdas everywhere to get the sender manager->connect( manager, &ScriptManager::attachDefaultEventHandlers, entityScriptingInterface.data(), [ifacePtr, manager] { ifacePtr->attachDefaultEventHandlers(manager); }, Qt::DirectConnection); manager->connect(manager, &ScriptManager::releaseEntityPacketSenderMessages, entityScriptingInterface.data(), &EntityScriptingInterface::releaseEntityPacketSenderMessages, Qt::DirectConnection); } STATIC_SCRIPT_INITIALIZER(staticEntityScriptInitializer); 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); connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged); connect(nodeList.data(), &NodeList::canRezAvatarEntitiesChanged, this, &EntityScriptingInterface::canRezAvatarEntitiesChanged); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, PacketReceiver::makeSourcedListenerReference<EntityScriptingInterface>(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket)); } void EntityScriptingInterface::releaseEntityPacketSenderMessages(bool wait) { EntityEditPacketSender* entityPacketSender = getEntityPacketSender(); if (entityPacketSender->serversExist()) { // release the queue of edit entity messages. entityPacketSender->releaseQueuedMessages(); // since we're in non-threaded mode, call process so that the packets are sent if (!entityPacketSender->isThreaded()) { if (!wait) { entityPacketSender->process(); } else { // wait here till the edit packet sender is completely done sending while (entityPacketSender->hasPacketsToSend()) { entityPacketSender->process(); QCoreApplication::processEvents(); } } } else { // FIXME - do we need to have a similar "wait here" loop for non-threaded packet senders? } } } void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager) { // Connect up ALL the handlers to the global entities object's signals. // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) // Bug? These handlers are deleted when entityID is deleted, which is nice. // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded? // Also, what about when the entity script is REloaded? // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS connect(this, &EntityScriptingInterface::deletingEntity, manager, [manager](const EntityItemID& entityID) { manager->removeAllEventHandlers(entityID); }); // Two common cases of event handler, differing only in argument signature. /*@jsdoc * Called when an entity event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~entityEventCallback * @param {Uuid} entityID - The ID of the entity the event has occured on. */ using SingleEntityHandler = std::function<void(const EntityItemID&)>; auto makeSingleEntityHandler = [manager](QString eventName) -> SingleEntityHandler { return [manager, eventName](const EntityItemID& entityItemID) { manager->forwardHandlerCall(entityItemID, eventName, { EntityItemIDtoScriptValue(manager->engine().get(), entityItemID) }); }; }; /*@jsdoc * Called when a pointer event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~pointerEventCallback * @param {Uuid} entityID - The ID of the entity the event has occurred on. * @param {PointerEvent} pointerEvent - Details of the event. */ using PointerHandler = std::function<void(const EntityItemID&, const PointerEvent&)>; auto makePointerHandler = [manager](QString eventName) -> PointerHandler { return [manager, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { if (!EntityTree::areEntityClicksCaptured()) { ScriptEngine* engine = manager->engine().get(); manager->forwardHandlerCall(entityItemID, eventName, { EntityItemIDtoScriptValue(engine, entityItemID), event.toScriptValue(engine) }); } }; }; /*@jsdoc * Called when a collision event occurs on an entity as registered with {@link Script.addEventHandler}. * @callback Script~collisionEventCallback * @param {Uuid} entityA - The ID of one entity in the collision. * @param {Uuid} entityB - The ID of the other entity in the collision. * @param {Collision} collisionEvent - Details of the collision. */ using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>; auto makeCollisionHandler = [manager](QString eventName) -> CollisionHandler { return [manager, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { ScriptEngine* engine = manager->engine().get(); manager->forwardHandlerCall(idA, eventName, { EntityItemIDtoScriptValue(engine, idA), EntityItemIDtoScriptValue(engine, idB), collisionToScriptValue(engine, collision) }); }; }; /*@jsdoc * <p>The name of an entity event. When the entity event occurs, any function that has been registered for that event * via {@link Script.addEventHandler} is called with parameters per the entity event.</p> * <table> * <thead> * <tr><th>Event Name</th><th>Callback Type</th><th>Entity Event</th></tr> * </thead> * <tbody> * <tr><td><code>"enterEntity"</code></td><td>{@link Script~entityEventCallback|entityEventCallback}</td> * <td>{@link Entities.enterEntity}</td></tr> * <tr><td><code>"leaveEntity"</code></td><td>{@link Script~entityEventCallback|entityEventCallback}</td> * <td>{@link Entities.leaveEntity}</td></tr> * <tr><td><code>"mousePressOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mousePressOnEntity}</td></tr> * <tr><td><code>"mouseMoveOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mouseMoveOnEntity}</td></tr> * <tr><td><code>"mouseReleaseOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.mouseReleaseOnEntity}</td></tr> * <tr><td><code>"clickDownOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.clickDownOnEntity}</td></tr> * <tr><td><code>"holdingClickOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.holdingClickOnEntity}</td></tr> * <tr><td><code>"clickReleaseOnEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.clickReleaseOnEntity}</td></tr> * <tr><td><code>"hoverEnterEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverEnterEntity}</td></tr> * <tr><td><code>"hoverOverEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverOverEntity}</td></tr> * <tr><td><code>"hoverLeaveEntity"</code></td><td>{@link Script~pointerEventCallback|pointerEventCallback}</td> * <td>{@link Entities.hoverLeaveEntity}</td></tr> * <tr><td><code>"collisionWithEntity"</code><td>{@link Script~collisionEventCallback|collisionEventCallback}</td> * </td><td>{@link Entities.collisionWithEntity}</td></tr> * </tbody> * </table> * @typedef {string} Script.EntityEvent */ connect(this, &EntityScriptingInterface::enterEntity, manager, makeSingleEntityHandler("enterEntity")); connect(this, &EntityScriptingInterface::leaveEntity, manager, makeSingleEntityHandler("leaveEntity")); connect(this, &EntityScriptingInterface::mousePressOnEntity, manager, makePointerHandler("mousePressOnEntity")); connect(this, &EntityScriptingInterface::mouseMoveOnEntity, manager, makePointerHandler("mouseMoveOnEntity")); connect(this, &EntityScriptingInterface::mouseReleaseOnEntity, manager, makePointerHandler("mouseReleaseOnEntity")); connect(this, &EntityScriptingInterface::clickDownOnEntity, manager, makePointerHandler("clickDownOnEntity")); connect(this, &EntityScriptingInterface::holdingClickOnEntity, manager, makePointerHandler("holdingClickOnEntity")); connect(this, &EntityScriptingInterface::clickReleaseOnEntity, manager, makePointerHandler("clickReleaseOnEntity")); connect(this, &EntityScriptingInterface::hoverEnterEntity, manager, makePointerHandler("hoverEnterEntity")); connect(this, &EntityScriptingInterface::hoverOverEntity, manager, makePointerHandler("hoverOverEntity")); connect(this, &EntityScriptingInterface::hoverLeaveEntity, manager, makePointerHandler("hoverLeaveEntity")); connect(this, &EntityScriptingInterface::collisionWithEntity, manager, makeCollisionHandler("collisionWithEntity")); } 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(); } bool EntityScriptingInterface::canGetAndSetPrivateUserData() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanGetAndSetPrivateUserData(); } bool EntityScriptingInterface::canRezAvatarEntities() { auto nodeList = DependencyManager::get<NodeList>(); return nodeList->getThisNodeCanRezAvatarEntities(); } void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity); 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::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity, Qt::DirectConnection); connect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity, Qt::DirectConnection); 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; } // TODO: this method looks expensive and should take properties by reference, update it, and return void 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; } void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) { if (grabProperties.equippableLeftPositionChanged() || grabProperties.equippableRightPositionChanged() || grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() || grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged()) { QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); if (grabProperties.equippableLeftPositionChanged()) { if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) { spatialKey.remove("leftRelativePosition"); } else { spatialKey["leftRelativePosition"] = QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())); } } if (grabProperties.equippableRightPositionChanged()) { if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) { spatialKey.remove("rightRelativePosition"); } else { spatialKey["rightRelativePosition"] = QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())); } } if (grabProperties.equippableLeftRotationChanged()) { spatialKey["relativeRotation"] = QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())); } else if (grabProperties.equippableRightRotationChanged()) { spatialKey["relativeRotation"] = QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())); } grabbableKey["spatialKey"] = spatialKey; userDataChanged = true; } } void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { if (grabProperties.triggerableChanged() || grabProperties.grabbableChanged() || grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() || grabProperties.equippableRightPositionChanged() || grabProperties.equippableRightRotationChanged()) { QJsonObject grabbableKey = userData["grabbableKey"].toObject(); if (grabProperties.triggerableChanged()) { if (grabProperties.getTriggerable()) { grabbableKey["triggerable"] = true; } else { grabbableKey.remove("triggerable"); } } if (grabProperties.grabbableChanged()) { if (grabProperties.getGrabbable()) { grabbableKey.remove("grabbable"); } else { grabbableKey["grabbable"] = false; } } if (grabProperties.grabFollowsControllerChanged()) { if (grabProperties.getGrabFollowsController()) { grabbableKey.remove("ignoreIK"); } else {