From 0d375110710a70f85e5b0f9bc28993c028df51a5 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 18 Sep 2015 14:06:38 -0700 Subject: [PATCH] add support for scripts to call methods on entity scripts --- examples/controllers/handControllerGrab.js | 38 ++++++++++++-- examples/entityScripts/detectGrabExample.js | 52 ++++--------------- .../src/EntityTreeRenderer.cpp | 1 + .../src/EntitiesScriptEngineProvider.h | 25 +++++++++ .../entities/src/EntityScriptingInterface.cpp | 9 ++++ .../entities/src/EntityScriptingInterface.h | 10 +++- libraries/script-engine/src/ScriptEngine.h | 3 +- 7 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 libraries/entities/src/EntitiesScriptEngineProvider.h diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index ed69d1844d..109a4ab57c 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -69,6 +69,7 @@ var STATE_CLOSE_GRABBING = 2; var STATE_CONTINUE_CLOSE_GRABBING = 3; var STATE_RELEASE = 4; +var GRAB_USER_DATA_KEY = "grabKey"; function controller(hand, triggerAction) { this.hand = hand; @@ -89,6 +90,7 @@ function controller(hand, triggerAction) { this.state = 0; // 0 = searching, 1 = distanceHolding, 2 = closeGrabbing this.pointer = null; // entity-id of line object this.triggerValue = 0; // rolling average of trigger value + this.alreadyDistanceHolding = false; // FIXME - I'll leave it to Seth to potentially make this another state this.update = function() { switch(this.state) { @@ -210,13 +212,20 @@ function controller(hand, triggerAction) { this.distanceHolding = function() { if (!this.triggerSmoothedSqueezed()) { this.state = STATE_RELEASE; + this.alreadyDistanceHolding = false; return; } + if (!this.alreadyDistanceHolding) { + this.activateEntity(this.grabbedEntity); + this.alreadyDistanceHolding = true; + } + var handPosition = this.getHandPosition(); var handControllerPosition = Controller.getSpatialControlPosition(this.palm); var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position","rotation"]); + Entities.callEntityMethod(this.grabbedEntity, "distanceHolding"); this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); @@ -270,7 +279,9 @@ function controller(hand, triggerAction) { this.lineOff(); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + this.activateEntity(this.grabbedEntity); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, "position"); var handRotation = this.getHandRotation(); var handPosition = this.getHandPosition(); @@ -294,6 +305,7 @@ function controller(hand, triggerAction) { } else { this.state = STATE_CONTINUE_CLOSE_GRABBING; } + Entities.callEntityMethod(this.grabbedEntity, "closeGrabbing"); } @@ -324,6 +336,7 @@ function controller(hand, triggerAction) { this.currentObjectPosition = grabbedProperties.position; this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueCloseGrabbing"); } @@ -336,7 +349,10 @@ function controller(hand, triggerAction) { // the action will tend to quickly bring an object's velocity to zero. now that // the action is gone, set the objects velocity to something the holder might expect. - Entities.editEntity(this.grabbedEntity, {velocity: this.grabbedVelocity}); + Entities.editEntity(this.grabbedEntity, { velocity: this.grabbedVelocity }); + + Entities.callEntityMethod(this.grabbedEntity, "release"); + this.deactivateEntity(this.grabbedEntity); this.grabbedVelocity = ZERO_VEC; this.grabbedEntity = null; @@ -348,6 +364,22 @@ function controller(hand, triggerAction) { this.cleanup = function() { release(); } + + this.activateEntity = function(entity) { + var data = { + activated: true, + avatarId: MyAvatar.sessionUUID + }; + setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data); + } + + this.deactivateEntity = function(entity) { + var data = { + activated: false, + avatarId: null + }; + setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data); + } } diff --git a/examples/entityScripts/detectGrabExample.js b/examples/entityScripts/detectGrabExample.js index cdc79e119d..c84d3250cc 100644 --- a/examples/entityScripts/detectGrabExample.js +++ b/examples/entityScripts/detectGrabExample.js @@ -12,7 +12,6 @@ // (function() { - Script.include("../libraries/utils.js"); var _this; @@ -24,39 +23,18 @@ DetectGrabbed.prototype = { - // update() will be called regulary, because we've hooked the update signal in our preload() function - // we will check out userData for the grabData. In the case of the hydraGrab script, it will tell us - // if we're currently being grabbed and if the person grabbing us is the current interfaces avatar. - // we will watch this for state changes and print out if we're being grabbed or released when it changes. - update: function() { - var GRAB_USER_DATA_KEY = "grabKey"; + distanceHolding: function () { + print("I am being distance held... entity:" + this.entityID); + }, - // because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID - var entityID = _this.entityID; - - // we want to assume that if there is no grab data, then we are not being grabbed - var defaultGrabData = { activated: false, avatarId: null }; - - // this handy function getEntityCustomData() is available in utils.js and it will return just the specific section - // of user data we asked for. If it's not available it returns our default data. - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, defaultGrabData); - - // if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface - if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) { - - // remember we're being grabbed so we can detect being released - _this.beingGrabbed = true; - - // print out that we're being grabbed - print("I'm being grabbed..."); - - } else if (_this.beingGrabbed) { - - // if we are not being grabbed, and we previously were, then we were just released, remember that - // and print out a message - _this.beingGrabbed = false; - print("I'm was released..."); - } + closeGrabbing: function () { + print("I was just grabbed... entity:" + this.entityID); + }, + continueCloseGrabbing: function () { + print("I am still being grabbed... entity:" + this.entityID); + }, + release: function () { + print("I was released... entity:" + this.entityID); }, // preload() will be called when the entity has become visible (or known) to the interface @@ -65,14 +43,6 @@ // * connecting to the update signal so we can check our grabbed state preload: function(entityID) { this.entityID = entityID; - Script.update.connect(this.update); - }, - - // unload() will be called when our entity is no longer available. It may be because we were deleted, - // or because we've left the domain or quit the application. In all cases we want to unhook our connection - // to the update signal - unload: function(entityID) { - Script.update.disconnect(this.update); }, }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c55eaaeff9..056aaab1a5 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -112,6 +112,7 @@ void EntityTreeRenderer::init() { _scriptingServices->getControllerScriptingInterface()); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); _entitiesScriptEngine->runInThread(); + DependencyManager::get()->setEntitiesScriptEngine(_entitiesScriptEngine); } // make sure our "last avatar position" is something other than our current position, so that on our diff --git a/libraries/entities/src/EntitiesScriptEngineProvider.h b/libraries/entities/src/EntitiesScriptEngineProvider.h new file mode 100644 index 0000000000..d112a6c0f9 --- /dev/null +++ b/libraries/entities/src/EntitiesScriptEngineProvider.h @@ -0,0 +1,25 @@ +// +// EntitiesScriptEngineProvider.h +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on Sept. 18, 2015 +// Copyright 2015 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 +// +// TODO: How will we handle collision callbacks with Entities +// + +#ifndef hifi_EntitiesScriptEngineProvider_h +#define hifi_EntitiesScriptEngineProvider_h + +#include +#include "EntityItemID.h" + +class EntitiesScriptEngineProvider { +public: + virtual void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) = 0; +}; + +#endif // hifi_EntitiesScriptEngineProvider_h \ No newline at end of file diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4b8b4e2903..1d403e37cd 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -11,6 +11,7 @@ #include "EntityScriptingInterface.h" +#include "EntityItemID.h" #include #include "EntitiesLogging.h" @@ -211,6 +212,14 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { } } +void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method) { + if (_entitiesScriptEngine) { + EntityItemID entityID{ id }; + _entitiesScriptEngine->callEntityScriptMethod(entityID, method); + } +} + + QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { EntityItemID result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 8d2b0b6892..8484b7189a 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -20,18 +20,19 @@ #include #include #include + #include "PolyVoxEntityItem.h" #include "LineEntityItem.h" #include "PolyLineEntityItem.h" #include "EntityTree.h" #include "EntityEditPacketSender.h" +#include "EntitiesScriptEngineProvider.h" class EntityTree; class MouseEvent; - class RayToEntityIntersectionResult { public: RayToEntityIntersectionResult(); @@ -63,6 +64,7 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } + void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; } public slots: @@ -86,6 +88,11 @@ public slots: /// deletes a model Q_INVOKABLE void deleteEntity(QUuid entityID); + /// Allows a script to call a method on an entity's script. The method will execute in the entity script + /// engine. If the entity does not have an entity script or the method does not exist, this call will have + /// no effect. + Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method); + /// finds the closest model to the center point, within the radius /// will return a EntityItemID.isKnownID = false if no models are in the radius /// this function will not find any models in script engine contexts which don't have access to models @@ -180,6 +187,7 @@ private: bool precisionPicking); EntityTreePointer _entityTree; + EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr; }; #endif // hifi_EntityScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 83e65823a5..3cfeb3447e 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "AbstractControllerScriptingInterface.h" #include "ArrayBufferClass.h" @@ -46,7 +47,7 @@ public: QScriptValue scriptObject; }; -class ScriptEngine : public QScriptEngine, public ScriptUser { +class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT,