From 277da5a24f703cd730a71a9d980a5c1b61e42d8a Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 10:23:45 -0700 Subject: [PATCH 01/11] first cut at wiring up new entity specific UI/UX events --- interface/src/Application.cpp | 12 ++ interface/src/entities/EntityTreeRenderer.cpp | 132 ++++++++++++++++++ interface/src/entities/EntityTreeRenderer.h | 29 ++++ libraries/entities/src/EntityItemID.h | 7 + .../entities/src/EntityScriptingInterface.h | 13 ++ 5 files changed, 193 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0752f9fa9e..f3a6899174 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1248,6 +1248,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { if (_lastMouseMoveWasSimulated) { showMouse = false; } + + _entities.mouseMoveEvent(event, deviceID); _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts @@ -1269,6 +1271,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + + _entities.mousePressEvent(event, deviceID); + _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -1306,6 +1311,9 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { + + _entities.mouseReleaseEvent(event, deviceID); + _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -1940,6 +1948,10 @@ void Application::init() { connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity, ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity); + // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing + // of events related clicking, hovering over, and entering entities + _entities.connectSignalsToSlots(ScriptEngine::getEntityScriptingInterface()); + _entityClipboardRenderer.init(); _entityClipboardRenderer.setViewFrustum(getViewFrustum()); _entityClipboardRenderer.setTree(&_entityClipboard); diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index e6b640011d..7335592fa3 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -24,6 +25,8 @@ #include "Menu.h" #include "EntityTreeRenderer.h" +#include "devices/OculusManager.h" + #include "RenderableBoxEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" @@ -42,6 +45,9 @@ EntityTreeRenderer::EntityTreeRenderer() : REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) + + _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID + _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID } EntityTreeRenderer::~EntityTreeRenderer() { @@ -382,4 +388,130 @@ void EntityTreeRenderer::deleteReleasedModels() { } } +PickRay EntityTreeRenderer::computePickRay(float x, float y) { + float screenWidth = Application::getInstance()->getGLWidget()->width(); + float screenHeight = Application::getInstance()->getGLWidget()->height(); + PickRay result; + if (OculusManager::isConnected()) { + Camera* camera = Application::getInstance()->getCamera(); + result.origin = camera->getPosition(); + Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); + } else { + ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum(); + viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + } + return result; +} + + +RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType) { + RayToEntityIntersectionResult result; + if (_tree) { + EntityTree* entityTree = static_cast(_tree); + + OctreeElement* element; + EntityItem* intersectedEntity = NULL; + result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + (void**)&intersectedEntity, lockType, &result.accurate); + if (result.intersects && intersectedEntity) { + result.entityID = intersectedEntity->getEntityItemID(); + result.properties = intersectedEntity->getProperties(); + result.intersection = ray.origin + (ray.direction * result.distance); + } + } + return result; +} + +void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) { + connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + + connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity); + connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity); + connect(this, &EntityTreeRenderer::clickReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickReleaseOnEntity); + + connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity); + connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); +} + +void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + if (rayPickResult.intersects) { + //qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID; + emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + _currentClickingOnEntityID = rayPickResult.entityID; + emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + } +} + +void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + if (rayPickResult.intersects) { + //qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID; + emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + } + + // 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)); + } + + // makes it the unknown ID, we just released so we can't be clicking on anything + _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); +} + +void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); + if (rayPickResult.intersects) { + //qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID; + emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + // handle the hover logic... + + // if we were previously hovering over an entity, and this new entity is not the same as our previous entity + // then we need to send the hover leave. + if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { + emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + } + + // 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)); + } + + // 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)); + + // remember what we're hovering over + _currentHoverOverEntityID = rayPickResult.entityID; + + } else { + // handle the hover logic... + // if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to + // send the hover leave for our previous entity + if (!_currentHoverOverEntityID.isInvalidID()) { + emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID + } + } + + // Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have + // not yet released the hold then this is still considered a holdingClickOnEntity event + if (!_currentClickingOnEntityID.isInvalidID()) { + emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + } +} + diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 09d931541d..054c947272 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -16,6 +16,7 @@ #include #include +#include // for RayToEntityIntersectionResult #include #include #include @@ -75,9 +76,37 @@ public: void releaseModel(Model* model); void deleteReleasedModels(); + + // event handles which may generate entity related events + void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID); + void mousePressEvent(QMouseEvent* event, unsigned int deviceID); + void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID); + + /// connect our signals to anEntityScriptingInterface for firing of events related clicking, + /// hovering over, and entering entities + void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface); + +signals: + void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + private: QList _releasedModels; void renderProxies(const EntityItem* entity, RenderArgs* args); + PickRay computePickRay(float x, float y); + RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType); + + EntityItemID _currentHoverOverEntityID; + EntityItemID _currentClickingOnEntityID; }; #endif // hifi_EntityTreeRenderer_h diff --git a/libraries/entities/src/EntityItemID.h b/libraries/entities/src/EntityItemID.h index 7a3f822c7c..919037f7d4 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/entities/src/EntityItemID.h @@ -49,7 +49,10 @@ public: EntityItemID convertToKnownIDVersion() const; EntityItemID convertToCreatorTokenVersion() const; + bool isInvalidID() const { return id == UNKNOWN_ENTITY_ID && creatorTokenID == UNKNOWN_ENTITY_TOKEN && isKnownID == false; } + // these methods allow you to create models, and later edit them. + static EntityItemID createInvalidEntityID() { return EntityItemID(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); } static EntityItemID getIDfromCreatorTokenID(uint32_t creatorTokenID); static uint32_t getNextCreatorTokenID(); static void handleAddEntityResponse(const QByteArray& packet); @@ -74,6 +77,10 @@ inline bool operator==(const EntityItemID& a, const EntityItemID& b) { return a.id == b.id; } +inline bool operator!=(const EntityItemID& a, const EntityItemID& b) { + return !(a == b); +} + inline uint qHash(const EntityItemID& a, uint seed) { QUuid temp; if (a.id == UNKNOWN_ENTITY_ID) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 1497b2966e..904940c0bc 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -25,6 +25,7 @@ class EntityTree; +class MouseEvent; class RayToEntityIntersectionResult { @@ -101,6 +102,18 @@ signals: void entityCollisionWithVoxel(const EntityItemID& entityID, const VoxelDetail& voxel, const CollisionInfo& collision); void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const CollisionInfo& collision); + void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); From a5088eece6649ec26cd52bc2148757b6270acdab Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 14:03:30 -0700 Subject: [PATCH 02/11] 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) From e7722dc6921f6146fcb3993cd23ddb4c95555e72 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 15:20:16 -0700 Subject: [PATCH 03/11] pass args to object scripts for mouse events --- interface/src/entities/EntityTreeRenderer.cpp | 40 ++++++++++--------- interface/src/entities/EntityTreeRenderer.h | 1 + libraries/entities/src/EntityItemID.cpp | 4 ++ libraries/entities/src/EntityItemID.h | 9 ++++- libraries/script-engine/src/MouseEvent.h | 2 + 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 9ee984354b..46a56e63fe 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -537,33 +537,30 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); } +QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine); + return args; +} + 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) { + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); // load the entity script if needed... QScriptValue entityScript = loadEntityScript(rayPickResult.entity); if (entityScript.property("mouseMoveEvent").isValid()) { - entityScript.property("mouseMoveEvent").call(); + entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs); } //qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID; emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); if (entityScript.property("mouseMoveOnEntity").isValid()) { - entityScript.property("mouseMoveOnEntity").call(); + entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs); } // handle the hover logic... @@ -573,9 +570,11 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { - currentHoverEntity.property("hoverLeaveEntity").call(); + currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); } } @@ -584,7 +583,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI if (rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); if (entityScript.property("hoverEnterEntity").isValid()) { - entityScript.property("hoverEnterEntity").call(); + entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs); } } @@ -592,7 +591,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); if (entityScript.property("hoverOverEntity").isValid()) { - entityScript.property("hoverOverEntity").call(); + entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs); } // remember what we're hovering over @@ -605,9 +604,11 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { - currentHoverEntity.property("hoverLeaveEntity").call(); + currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); } _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID @@ -618,9 +619,12 @@ 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)); + + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { - currentClickingEntity.property("holdingClickOnEntity").call(); + currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); } } } diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 4648db0545..a9a5b96ed9 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -113,6 +113,7 @@ private: QScriptValue loadEntityScript(EntityItem* entity); QScriptValue loadEntityScript(const EntityItemID& entityItemID); + QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); QHash _entityScripts; }; diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index c730e44322..aaf6e33128 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -124,6 +124,10 @@ void EntityItemID::handleAddEntityResponse(const QByteArray& packet) { _tokenIDsToIDs[creatorTokenID] = entityID; } +QScriptValue EntityItemID::toScriptValue(QScriptEngine* engine) const { + return EntityItemIDtoScriptValue(engine, *this); +} + QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) { QScriptValue obj = engine->newObject(); obj.setProperty("id", id.id.toString()); diff --git a/libraries/entities/src/EntityItemID.h b/libraries/entities/src/EntityItemID.h index 919037f7d4..1ac8a67fcd 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/entities/src/EntityItemID.h @@ -57,6 +57,8 @@ public: static uint32_t getNextCreatorTokenID(); static void handleAddEntityResponse(const QByteArray& packet); static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead); + + QScriptValue toScriptValue(QScriptEngine* engine) const; private: friend class EntityTree; @@ -71,6 +73,12 @@ inline bool operator<(const EntityItemID& a, const EntityItemID& b) { } inline bool operator==(const EntityItemID& a, const EntityItemID& b) { + if (a.isInvalidID() && b.isInvalidID()) { + return true; + } + if (a.isInvalidID() != b.isInvalidID()) { + return false; + } if (a.id == UNKNOWN_ENTITY_ID || b.id == UNKNOWN_ENTITY_ID) { return a.creatorTokenID == b.creatorTokenID; } @@ -101,5 +109,4 @@ Q_DECLARE_METATYPE(QVector); QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties); void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties); - #endif // hifi_EntityItemID_h diff --git a/libraries/script-engine/src/MouseEvent.h b/libraries/script-engine/src/MouseEvent.h index 7555f2ea5a..1936e6b58e 100644 --- a/libraries/script-engine/src/MouseEvent.h +++ b/libraries/script-engine/src/MouseEvent.h @@ -21,6 +21,8 @@ public: static QScriptValue toScriptValue(QScriptEngine* engine, const MouseEvent& event); static void fromScriptValue(const QScriptValue& object, MouseEvent& event); + + QScriptValue toScriptValue(QScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); } int x; int y; From e8c694f02e954581fca76a0050b5fb3b171b69f5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 16:38:51 -0700 Subject: [PATCH 04/11] args for more methods --- interface/src/entities/EntityTreeRenderer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 46a56e63fe..c960f1057a 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -495,15 +495,16 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device //qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID; emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); QScriptValue entityScript = loadEntityScript(rayPickResult.entity); if (entityScript.property("mousePressOnEntity").isValid()) { - entityScript.property("mousePressOnEntity").call(); + entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs); } _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); if (entityScript.property("clickDownOnEntity").isValid()) { - entityScript.property("clickDownOnEntity").call(); + entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs); } } } @@ -516,9 +517,10 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi //qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); QScriptValue entityScript = loadEntityScript(rayPickResult.entity); if (entityScript.property("mouseReleaseOnEntity").isValid()) { - entityScript.property("mouseReleaseOnEntity").call(); + entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs); } } @@ -527,9 +529,10 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) { - currentClickingEntity.property("clickReleaseOnEntity").call(); + currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs); } } From 87a5423b35f42cbd87c75ea988291903e359f610 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 1 Nov 2014 10:57:12 -0700 Subject: [PATCH 05/11] support changing scripts on the fly and reloading them accordningly --- interface/src/entities/EntityTreeRenderer.cpp | 32 +++++++++++++------ interface/src/entities/EntityTreeRenderer.h | 8 ++++- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index c960f1057a..fe2db389f6 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -83,8 +83,19 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { if (!entity) { return QScriptValue(); // no entity... } - if (_entityScripts.contains(entity->getEntityItemID())) { - return _entityScripts[entity->getEntityItemID()]; // already loaded + + EntityItemID entityID = entity->getEntityItemID(); + if (_entityScripts.contains(entityID)) { + EntityScriptDetails details = _entityScripts[entityID]; + + // check to make sure our script text hasn't changed on us since we last loaded it + if (details.scriptText == entity->getScript()) { + return details.scriptObject; // previously loaded + } + + // if we got here, then we previously loaded a script, but the entity's script value + // has changed and so we need to reload it. + _entityScripts.remove(entityID); } if (entity->getScript().isEmpty()) { return QScriptValue(); // no script @@ -107,7 +118,8 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { } QScriptValue entityScriptObject = entityScriptConstructor.construct(); - _entityScripts[entity->getEntityItemID()] = entityScriptObject; + EntityScriptDetails newDetails = { entity->getScript(), entityScriptObject }; + _entityScripts[entityID] = newDetails; return entityScriptObject; // newly constructed } @@ -487,6 +499,13 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); } +QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine); + return args; +} + void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); PickRay ray = computePickRay(event->x(), event->y()); @@ -540,13 +559,6 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); } -QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { - QScriptValueList args; - args << entityID.toScriptValue(_entitiesScriptEngine); - args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine); - return args; -} - void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); PickRay ray = computePickRay(event->x(), event->y()); diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index a9a5b96ed9..87d1ed0f8a 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -27,6 +27,12 @@ #include "renderer/Model.h" +class EntityScriptDetails { +public: + QString scriptText; + QScriptValue scriptObject; +}; + // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { Q_OBJECT @@ -115,7 +121,7 @@ private: QScriptValue loadEntityScript(const EntityItemID& entityItemID); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); - QHash _entityScripts; + QHash _entityScripts; }; #endif // hifi_EntityTreeRenderer_h From 4db59866257fda13c8bc384ccccd07b51e5c6083 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 1 Nov 2014 11:40:06 -0700 Subject: [PATCH 06/11] support for url or text based scripts --- interface/src/entities/EntityTreeRenderer.cpp | 62 +++++++++++++++++-- interface/src/entities/EntityTreeRenderer.h | 1 + 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index fe2db389f6..df2c51104b 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -25,6 +25,7 @@ #include "Menu.h" +#include "NetworkAccessManager.h" #include "EntityTreeRenderer.h" #include "devices/OculusManager.h" @@ -79,6 +80,53 @@ QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItem } +QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText) { + QUrl url(scriptMaybeURLorText); + + // If the url is not valid, this must be script text... + if (!url.isValid()) { + return scriptMaybeURLorText; + } + + QString scriptContents; // assume empty + + // if the scheme length is one or lower, maybe they typed in a file, let's try + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + url = QUrl::fromLocalFile(scriptMaybeURLorText); + } + + // ok, let's see if it's valid... and if so, load it + if (url.isValid()) { + if (url.scheme() == "file") { + QString fileName = url.toLocalFile(); + QFile scriptFile(fileName); + if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { + qDebug() << "Loading file:" << fileName; + QTextStream in(&scriptFile); + scriptContents = in.readAll(); + } else { + qDebug() << "ERROR Loading file:" << fileName; + } + } else { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); + qDebug() << "Downloading script at" << url; + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { + scriptContents = reply->readAll(); + } else { + qDebug() << "ERROR Loading file:" << url.toString(); + } + } + } + + return scriptContents; +} + + QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { if (!entity) { return QScriptValue(); // no entity... @@ -101,19 +149,21 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { return QScriptValue(); // no script } - if (QScriptEngine::checkSyntax(entity->getScript()).state() != QScriptSyntaxCheckResult::Valid) { - qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID(); + QString scriptContents = loadScriptContents(entity->getScript()); + + if (QScriptEngine::checkSyntax(scriptContents).state() != QScriptSyntaxCheckResult::Valid) { + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; qDebug() << " INVALID SYNTAX"; - qDebug() << " SCRIPT:" << entity->getScript(); + qDebug() << " SCRIPT:" << scriptContents; return QScriptValue(); // invalid script } - QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(entity->getScript()); + QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); if (!entityScriptConstructor.isFunction()) { - qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID(); + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; qDebug() << " NOT CONSTRUCTOR"; - qDebug() << " SCRIPT:" << entity->getScript(); + qDebug() << " SCRIPT:" << scriptContents; return QScriptValue(); // invalid script } diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 87d1ed0f8a..6c3f948594 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -119,6 +119,7 @@ private: QScriptValue loadEntityScript(EntityItem* entity); QScriptValue loadEntityScript(const EntityItemID& entityItemID); + QString loadScriptContents(const QString& scriptMaybeURLorText); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); QHash _entityScripts; From 2ffe15c833822c4ec09bd1d7d6e6deb7d854ffb7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 1 Nov 2014 11:43:40 -0700 Subject: [PATCH 07/11] add example entity script for change color on hover --- examples/entityScripts/changeColorOnHover.js | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 examples/entityScripts/changeColorOnHover.js diff --git a/examples/entityScripts/changeColorOnHover.js b/examples/entityScripts/changeColorOnHover.js new file mode 100644 index 0000000000..de3f5f3784 --- /dev/null +++ b/examples/entityScripts/changeColorOnHover.js @@ -0,0 +1,38 @@ +// +// changeColorOnHover.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/1/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will +// change the color of the entity when you hover over it. This script uses the JavaScript prototype/class functionality +// to construct an object that has methods for hoverEnterEntity and hoverLeaveEntity; +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function(){ + this.oldColor = {}; + this.oldColorKnown = false; + this.storeOldColor = function(entityID) { + var oldProperties = Entities.getEntityProperties(entityID); + this.oldColor = oldProperties.color; + this.oldColorKnown = true; + print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue); + }; + this.hoverEnterEntity = function(entityID, mouseEvent) { + if (!this.oldColorKnown) { + this.storeOldColor(entityID); + } + Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} }); + }; + this.hoverLeaveEntity = function(entityID, mouseEvent) { + if (this.oldColorKnown) { + print("leave restoring old color... this.oldColor=" + + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue); + Entities.editEntity(entityID, { color: this.oldColor }); + } + }; +}) \ No newline at end of file From 948f3a4c1fef6b056595b425ffdc1a0de7ae8d2a Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 11:38:49 -0800 Subject: [PATCH 08/11] added an example entity script that makes the avatar do crazy legs when you click on an entity --- examples/entityScripts/crazylegsOnClick.js | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/entityScripts/crazylegsOnClick.js diff --git a/examples/entityScripts/crazylegsOnClick.js b/examples/entityScripts/crazylegsOnClick.js new file mode 100644 index 0000000000..57f16b7e7b --- /dev/null +++ b/examples/entityScripts/crazylegsOnClick.js @@ -0,0 +1,57 @@ +// +// crazylegsOnHover.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will make your avatar do the +// crazyLegs dance if you click on it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function(){ + var cumulativeTime = 0.0; + var FREQUENCY = 5.0; + var AMPLITUDE = 45.0; + var jointList = MyAvatar.getJointNames(); + var jointMappings = "\n# Joint list start"; + for (var i = 0; i < jointList.length; i++) { + jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i; + } + print(jointMappings + "\n# Joint list end"); + + this.crazyLegsUpdate = function(deltaTime) { + print("crazyLegsUpdate... deltaTime:" + deltaTime); + cumulativeTime += deltaTime; + print("crazyLegsUpdate... cumulativeTime:" + cumulativeTime); + MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(-AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); + MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); + }; + + this.stopCrazyLegs = function() { + MyAvatar.clearJointData("RightUpLeg"); + MyAvatar.clearJointData("LeftUpLeg"); + MyAvatar.clearJointData("RightLeg"); + MyAvatar.clearJointData("LeftLeg"); + }; + + this.clickDownOnEntity = function(entityID, mouseEvent) { + print("clickDownOnEntity()..."); + cumulativeTime = 0.0; + Script.update.connect(this.crazyLegsUpdate); + }; + + this.clickReleaseOnEntity = function(entityID, mouseEvent) { + print("clickReleaseOnEntity()..."); + this.stopCrazyLegs(); + Script.update.disconnect(this.crazyLegsUpdate); + }; +}) + From f9bd954fbe2f407678c3bbcd203ae59b84864392 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 11:43:30 -0800 Subject: [PATCH 09/11] typo --- examples/entityScripts/crazylegsOnClick.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/entityScripts/crazylegsOnClick.js b/examples/entityScripts/crazylegsOnClick.js index 57f16b7e7b..149c8bf43e 100644 --- a/examples/entityScripts/crazylegsOnClick.js +++ b/examples/entityScripts/crazylegsOnClick.js @@ -1,5 +1,5 @@ // -// crazylegsOnHover.js +// crazylegsOnClick.js // examples/entityScripts // // Created by Brad Hefta-Gaub on 11/3/14. From 30c5451b877b0316649fe56104988c5bdee8e396 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 11:52:16 -0800 Subject: [PATCH 10/11] added example of playing a sound on click --- examples/entityScripts/playSoundOnClick.js | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 examples/entityScripts/playSoundOnClick.js diff --git a/examples/entityScripts/playSoundOnClick.js b/examples/entityScripts/playSoundOnClick.js new file mode 100644 index 0000000000..b261bb269a --- /dev/null +++ b/examples/entityScripts/playSoundOnClick.js @@ -0,0 +1,24 @@ +// +// playSoundOnClick.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when +// you click on it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); + this.clickDownOnEntity = function(entityID, mouseEvent) { + print("clickDownOnEntity()..."); + var options = new AudioInjectionOptions(); + var position = MyAvatar.position; + options.position = position; + options.volume = 0.5; + Audio.playSound(bird, options); + }; +}) From 252d05282b9618580665819743cec35f0c3c8c57 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 12:03:08 -0800 Subject: [PATCH 11/11] added example entity script that teleports your avatar to the entity when you click on it --- examples/entityScripts/teleportOnClick.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 examples/entityScripts/teleportOnClick.js diff --git a/examples/entityScripts/teleportOnClick.js b/examples/entityScripts/teleportOnClick.js new file mode 100644 index 0000000000..11677b12d5 --- /dev/null +++ b/examples/entityScripts/teleportOnClick.js @@ -0,0 +1,18 @@ +// +// teleportOnClick.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will teleport your avatar if you +// click on it the entity. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + this.clickDownOnEntity = function(entityID, mouseEvent) { + MyAvatar.position = Entities.getEntityProperties(entityID).position; + }; +})