From a5088eece6649ec26cd52bc2148757b6270acdab Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 14:03:30 -0700 Subject: [PATCH] first cut at running scripts from entities --- examples/libraries/entityPropertyDialogBox.js | 4 + interface/src/Application.cpp | 66 +++++----- interface/src/Application.h | 2 + interface/src/entities/EntityTreeRenderer.cpp | 115 +++++++++++++++++- interface/src/entities/EntityTreeRenderer.h | 10 +- .../entities/src/EntityScriptingInterface.cpp | 3 +- .../entities/src/EntityScriptingInterface.h | 1 + 7 files changed, 167 insertions(+), 34 deletions(-) diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index 2c4bf5329e..01e0c76e0d 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -140,6 +140,9 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Visible:", value: properties.visible }); index++; + + array.push({ label: "Script:", value: properties.script }); + index++; if (properties.type == "Box" || properties.type == "Sphere") { array.push({ label: "Color:", type: "header" }); @@ -282,6 +285,7 @@ EntityPropertyDialogBox = (function () { properties.lifetime = array[index++].value; properties.visible = array[index++].value; + properties.script = array[index++].value; if (properties.type == "Box" || properties.type == "Sphere") { index++; // skip header diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3a6899174..a215905362 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -148,7 +148,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _voxelImporter(), _importSucceded(false), _sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard), - _entityClipboardRenderer(), + _entities(true), + _entityCollisionSystem(), + _entityClipboardRenderer(false), _entityClipboard(), _wantToKillLocalVoxels(false), _viewFrustum(), @@ -3831,35 +3833,7 @@ void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) { out = qobject_cast(object.toQObject()); } -ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, - bool loadScriptFromEditor, bool activateMainWindow) { - QUrl scriptUrl(scriptFilename); - const QString& scriptURLString = scriptUrl.toString(); - if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor - && !_scriptEnginesHash[scriptURLString]->isFinished()) { - - return _scriptEnginesHash[scriptURLString]; - } - - ScriptEngine* scriptEngine; - if (scriptFilename.isNull()) { - scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); - } else { - // start the script on a new thread... - scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); - - if (!scriptEngine->hasScript()) { - qDebug() << "Application::loadScript(), script failed to load..."; - QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load."); - return NULL; - } - - _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); - UserActivityLogger::getInstance().loadedScript(scriptURLString); - } - scriptEngine->setUserLoaded(isUserLoaded); - +void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender); @@ -3941,6 +3915,38 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser // Starts an event loop, and emits workerThread->started() workerThread->start(); +} + +ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, + bool loadScriptFromEditor, bool activateMainWindow) { + QUrl scriptUrl(scriptFilename); + const QString& scriptURLString = scriptUrl.toString(); + if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor + && !_scriptEnginesHash[scriptURLString]->isFinished()) { + + return _scriptEnginesHash[scriptURLString]; + } + + ScriptEngine* scriptEngine; + if (scriptFilename.isNull()) { + scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); + } else { + // start the script on a new thread... + scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); + + if (!scriptEngine->hasScript()) { + qDebug() << "Application::loadScript(), script failed to load..."; + QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load."); + return NULL; + } + + _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + UserActivityLogger::getInstance().loadedScript(scriptURLString); + } + scriptEngine->setUserLoaded(isUserLoaded); + + registerScriptEngineWithApplicationServices(scriptEngine); // restore the main window's active state if (activateMainWindow && !loadScriptFromEditor) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 3d2fab07bf..58a7a58bf3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -305,6 +305,8 @@ public: float getRenderResolutionScale() const { return _renderResolutionScale; } + void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine); + signals: /// Fired when we're simulating; allows external parties to hook in. diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 7335592fa3..9ee984354b 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include "InterfaceConfig.h" @@ -39,8 +41,10 @@ QThread* EntityTreeRenderer::getMainThread() { -EntityTreeRenderer::EntityTreeRenderer() : - OctreeRenderer() { +EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) : + OctreeRenderer(), + _wantScripts(wantScripts), + _entitiesScriptEngine(NULL) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) @@ -51,6 +55,7 @@ EntityTreeRenderer::EntityTreeRenderer() : } EntityTreeRenderer::~EntityTreeRenderer() { + // do we need to delete the _entitiesScriptEngine?? or is it deleted by default } void EntityTreeRenderer::clear() { @@ -60,6 +65,51 @@ void EntityTreeRenderer::clear() { void EntityTreeRenderer::init() { OctreeRenderer::init(); static_cast(_tree)->setFBXService(this); + + if (_wantScripts) { + _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities", + Application::getInstance()->getControllerScriptingInterface()); + Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); + } +} + +QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) { + EntityItem* entity = static_cast(_tree)->findEntityByEntityItemID(entityItemID); + return loadEntityScript(entity); +} + + +QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { + if (!entity) { + return QScriptValue(); // no entity... + } + if (_entityScripts.contains(entity->getEntityItemID())) { + return _entityScripts[entity->getEntityItemID()]; // already loaded + } + if (entity->getScript().isEmpty()) { + return QScriptValue(); // no script + } + + if (QScriptEngine::checkSyntax(entity->getScript()).state() != QScriptSyntaxCheckResult::Valid) { + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID(); + qDebug() << " INVALID SYNTAX"; + qDebug() << " SCRIPT:" << entity->getScript(); + return QScriptValue(); // invalid script + } + + QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(entity->getScript()); + + if (!entityScriptConstructor.isFunction()) { + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID(); + qDebug() << " NOT CONSTRUCTOR"; + qDebug() << " SCRIPT:" << entity->getScript(); + return QScriptValue(); // invalid script + } + + QScriptValue entityScriptObject = entityScriptConstructor.construct(); + _entityScripts[entity->getEntityItemID()] = entityScriptObject; + + return entityScriptObject; // newly constructed } void EntityTreeRenderer::setTree(Octree* newTree) { @@ -417,6 +467,7 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); result.intersection = ray.origin + (ray.direction * result.distance); + result.entity = intersectedEntity; } } return result; @@ -443,9 +494,17 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device if (rayPickResult.intersects) { //qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID; emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mousePressOnEntity").isValid()) { + entityScript.property("mousePressOnEntity").call(); + } _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + if (entityScript.property("clickDownOnEntity").isValid()) { + entityScript.property("clickDownOnEntity").call(); + } } } @@ -456,12 +515,22 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi if (rayPickResult.intersects) { //qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mouseReleaseOnEntity").isValid()) { + entityScript.property("mouseReleaseOnEntity").call(); + } } // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickOn event if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) { + currentClickingEntity.property("clickReleaseOnEntity").call(); + } } // makes it the unknown ID, we just released so we can't be clicking on anything @@ -469,12 +538,33 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi } void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { + + // experimental... + /* + qDebug() << "TESTING - "; + qDebug() << " _experimentalScriptValue.isValid():" << _experimentalScriptValue.isValid(); + qDebug() << " _experimentalConstructed.isValid():" << _experimentalConstructed.isValid(); + qDebug() << " _experimentalConstructed.property(MethodA).isValid():" << _experimentalConstructed.property("MethodA").isValid(); + _experimentalConstructed.property("MethodA").call(); + */ + + PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); PickRay ray = computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); if (rayPickResult.intersects) { + + // load the entity script if needed... + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mouseMoveEvent").isValid()) { + entityScript.property("mouseMoveEvent").call(); + } + //qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID; emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("mouseMoveOnEntity").isValid()) { + entityScript.property("mouseMoveOnEntity").call(); + } // handle the hover logic... @@ -482,17 +572,28 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); + if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { + currentHoverEntity.property("hoverLeaveEntity").call(); + } } // If the new hover entity does not match the previous hover entity then we are entering the new one // this is true if the _currentHoverOverEntityID is known or unknown if (rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("hoverEnterEntity").isValid()) { + entityScript.property("hoverEnterEntity").call(); + } } // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("hoverOverEntity").isValid()) { + entityScript.property("hoverOverEntity").call(); + } // remember what we're hovering over _currentHoverOverEntityID = rayPickResult.entityID; @@ -503,6 +604,12 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); + if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { + currentHoverEntity.property("hoverLeaveEntity").call(); + } + _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID } } @@ -511,6 +618,10 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // not yet released the hold then this is still considered a holdingClickOnEntity event if (!_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { + currentClickingEntity.property("holdingClickOnEntity").call(); + } } } diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 054c947272..4648db0545 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -31,7 +31,7 @@ class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { Q_OBJECT public: - EntityTreeRenderer(); + EntityTreeRenderer(bool wantScripts); virtual ~EntityTreeRenderer(); virtual Octree* createTree() { return new EntityTree(true); } @@ -107,6 +107,14 @@ private: EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; + + bool _wantScripts; + ScriptEngine* _entitiesScriptEngine; + + QScriptValue loadEntityScript(EntityItem* entity); + QScriptValue loadEntityScript(const EntityItemID& entityItemID); + + QHash _entityScripts; }; #endif // hifi_EntityTreeRenderer_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8113168655..1658764c71 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -217,7 +217,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() : entityID(), properties(), distance(0), - face() + face(), + entity(NULL) { } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 904940c0bc..1152ccbd03 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -38,6 +38,7 @@ public: float distance; BoxFace face; glm::vec3 intersection; + EntityItem* entity; }; Q_DECLARE_METATYPE(RayToEntityIntersectionResult)