From 277da5a24f703cd730a71a9d980a5c1b61e42d8a Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 31 Oct 2014 10:23:45 -0700 Subject: [PATCH 01/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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 cecf79e58096515136650cde2e2ac61cb0f91fe2 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 3 Nov 2014 11:22:23 -0800 Subject: [PATCH 08/24] Fix the problem of actual framerate target not beeing true to the value displayed in the menu, same for resolution scale. Removed the value in Application and relies exclusively on the menu state --- interface/src/Application.cpp | 91 +++++++++++++++++++++++++++-------- interface/src/Application.h | 16 +++--- interface/src/Menu.cpp | 50 ++----------------- interface/src/Menu.h | 2 - 4 files changed, 83 insertions(+), 76 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f56885d16b..36591cd5b6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -107,8 +107,6 @@ static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored -const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff - // in the idle loop? (60 FPS is default) static QTimer* idleTimer = NULL; const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; @@ -182,9 +180,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _trayIcon(new QSystemTrayIcon(_window)), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), - _renderTargetFramerate(0), - _isVSyncOn(true), - _renderResolutionScale(1.0f) + _isVSyncOn(true) { // read the ApplicationInfo.ini file for Name/Version/Domain information @@ -601,7 +597,7 @@ void Application::paintGL() { if (OculusManager::isConnected()) { _textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize()); } else { - QSize fbSize = _glWidget->getDeviceSize() * _renderResolutionScale; + QSize fbSize = _glWidget->getDeviceSize() * getRenderResolutionScale(); _textureCache.setFrameBufferSize(fbSize); } @@ -1471,12 +1467,11 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); - // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran + // Only run simulation code if more than the targetFramePeriod have passed since last time we ran double targetFramePeriod = 0.0; - if (_renderTargetFramerate > 0) { - targetFramePeriod = 1000.0 / _renderTargetFramerate; - } else if (_renderTargetFramerate < 0) { - targetFramePeriod = IDLE_SIMULATE_MSECS; + unsigned int targetFramerate = getRenderTargetFramerate(); + if (targetFramerate > 0) { + targetFramePeriod = 1000.0 / targetFramerate; } double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0; if (timeSinceLastUpdate > targetFramePeriod) { @@ -3176,7 +3171,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { } else { // if not rendering the billboard, the region is in device independent coordinates; must convert to device QSize size = getTextureCache()->getFrameBufferSize(); - float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * _renderResolutionScale; + float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale(); int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio; glViewport(x, size.height() - y - height, width, height); glScissor(x, size.height() - y - height, width, height); @@ -4234,13 +4229,11 @@ void Application::takeSnapshot() { _snapshotShareDialog->show(); } -void Application::setRenderTargetFramerate(unsigned int framerate, bool vsyncOn) { - if (vsyncOn != _isVSyncOn) { +void Application::setVSyncEnabled(bool vsyncOn) { #if defined(Q_OS_WIN) if (wglewGetExtension("WGL_EXT_swap_control")) { wglSwapIntervalEXT(vsyncOn); int swapInterval = wglGetSwapIntervalEXT(); - _isVSyncOn = swapInterval; qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); } else { qDebug("V-Sync is FORCED ON on this system\n"); @@ -4260,11 +4253,32 @@ void Application::setRenderTargetFramerate(unsigned int framerate, bool vsyncOn) #else qDebug("V-Sync is FORCED ON on this system\n"); #endif - } - _renderTargetFramerate = framerate; } -bool Application::isVSyncEditable() { +bool Application::isVSyncOn() const { +#if defined(Q_OS_WIN) + if (wglewGetExtension("WGL_EXT_swap_control")) { + int swapInterval = wglGetSwapIntervalEXT(); + return (swapInterval > 0); + } else { + return true; + } +#elif defined(Q_OS_LINUX) + // TODO: write the poper code for linux + /* + if (glQueryExtension.... ("GLX_EXT_swap_control")) { + int swapInterval = xglGetSwapIntervalEXT(); + return (swapInterval > 0); + } else { + return true; + } + */ +#else + return true; +#endif +} + +bool Application::isVSyncEditable() const { #if defined(Q_OS_WIN) if (wglewGetExtension("WGL_EXT_swap_control")) { return true; @@ -4281,6 +4295,43 @@ bool Application::isVSyncEditable() { return false; } -void Application::setRenderResolutionScale(float scale) { - _renderResolutionScale = scale; +unsigned int Application::getRenderTargetFramerate() const { + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) { + return 0; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) { + return 60; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) { + return 50; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) { + return 40; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) { + return 30; + } + return 0; } + +float Application::getRenderResolutionScale() const { + + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { + return 1.f; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) { + return 0.666f; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) { + return 0.5f; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) { + return 0.333f; + } + else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) { + return 0.25f; + } + else { + return 1.f; + } +} \ No newline at end of file diff --git a/interface/src/Application.h b/interface/src/Application.h index 3d2fab07bf..f8a97549f2 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -303,7 +303,12 @@ public: bool isLookingAtMyAvatar(Avatar* avatar); - float getRenderResolutionScale() const { return _renderResolutionScale; } + float getRenderResolutionScale() const; + + unsigned int getRenderTargetFramerate() const; + bool isVSyncOn() const; + bool isVSyncEditable() const; + signals: @@ -367,12 +372,7 @@ public slots: void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void setRenderTargetFramerate(unsigned int framerate, bool vsyncOn = true); - bool isVSyncOn() { return _isVSyncOn; } - bool isVSyncEditable(); - unsigned int getRenderTargetFramerate() const { return _renderTargetFramerate; } - - void setRenderResolutionScale(float scale); + void setVSyncEnabled(bool vsyncOn); void resetSensors(); @@ -624,9 +624,7 @@ private: quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; - int _renderTargetFramerate; bool _isVSyncOn; - float _renderResolutionScale; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f915855758..21c1cded88 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -377,13 +377,12 @@ Menu::Menu() : { QMenu* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate); QActionGroup* framerateGroup = new QActionGroup(framerateMenu); - + framerateGroup->setExclusive(true); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate60, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate50, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate40, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate30, 0, false)); - connect(framerateMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderTargetFramerate(QAction*))); #if defined(Q_OS_MAC) #else @@ -394,12 +393,12 @@ Menu::Menu() : QMenu* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false)); + resolutionGroup->setExclusive(true); + resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false)); - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, true)); + resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - connect(resolutionMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderResolution(QAction*))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, @@ -1261,46 +1260,7 @@ void Menu::muteEnvironment() { } void Menu::changeVSync() { - Application::getInstance()->setRenderTargetFramerate( - Application::getInstance()->getRenderTargetFramerate(), - isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn)); -} -void Menu::changeRenderTargetFramerate(QAction* action) { - bool vsynOn = Application::getInstance()->isVSyncOn(); - - QString text = action->text(); - if (text == MenuOption::RenderTargetFramerateUnlimited) { - Application::getInstance()->setRenderTargetFramerate(0, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate60) { - Application::getInstance()->setRenderTargetFramerate(60, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate50) { - Application::getInstance()->setRenderTargetFramerate(50, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate40) { - Application::getInstance()->setRenderTargetFramerate(40, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate30) { - Application::getInstance()->setRenderTargetFramerate(30, vsynOn); - } -} - -void Menu::changeRenderResolution(QAction* action) { - QString text = action->text(); - if (text == MenuOption::RenderResolutionOne) { - Application::getInstance()->setRenderResolutionScale(1.f); - } else if (text == MenuOption::RenderResolutionTwoThird) { - Application::getInstance()->setRenderResolutionScale(0.666f); - } else if (text == MenuOption::RenderResolutionHalf) { - Application::getInstance()->setRenderResolutionScale(0.5f); - } else if (text == MenuOption::RenderResolutionThird) { - Application::getInstance()->setRenderResolutionScale(0.333f); - } else if (text == MenuOption::RenderResolutionQuarter) { - Application::getInstance()->setRenderResolutionScale(0.25f); - } else { - Application::getInstance()->setRenderResolutionScale(1.f); - } + Application::getInstance()->setVSyncEnabled(isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn)); } void Menu::displayNameLocationResponse(const QString& errorString) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 72b43e09c8..8af91ccae4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -230,9 +230,7 @@ private slots: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); void muteEnvironment(); - void changeRenderTargetFramerate(QAction* action); void changeVSync(); - void changeRenderResolution(QAction* action); private: static Menu* _instance; From 948f3a4c1fef6b056595b425ffdc1a0de7ae8d2a Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 11:38:49 -0800 Subject: [PATCH 09/24] 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 10/24] 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 11/24] 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 12/24] 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; + }; +}) From a4bd0acc9100336d9421e8711cfa51930a9346e8 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 3 Nov 2014 14:36:03 -0800 Subject: [PATCH 13/24] Fix the syntax issue with if blocks --- interface/src/Application.cpp | 66 +++++++++++++++-------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 11dd64f07a..231e535a33 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4233,27 +4233,27 @@ void Application::takeSnapshot() { void Application::setVSyncEnabled(bool vsyncOn) { #if defined(Q_OS_WIN) - if (wglewGetExtension("WGL_EXT_swap_control")) { - wglSwapIntervalEXT(vsyncOn); - int swapInterval = wglGetSwapIntervalEXT(); - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { - qDebug("V-Sync is FORCED ON on this system\n"); - } + if (wglewGetExtension("WGL_EXT_swap_control")) { + wglSwapIntervalEXT(vsyncOn); + int swapInterval = wglGetSwapIntervalEXT(); + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } else { + qDebug("V-Sync is FORCED ON on this system\n"); + } #elif defined(Q_OS_LINUX) - // TODO: write the poper code for linux - /* - if (glQueryExtension.... ("GLX_EXT_swap_control")) { - glxSwapIntervalEXT(vsyncOn); - int swapInterval = xglGetSwapIntervalEXT(); - _isVSyncOn = swapInterval; - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { - qDebug("V-Sync is FORCED ON on this system\n"); - } - */ + // TODO: write the poper code for linux + /* + if (glQueryExtension.... ("GLX_EXT_swap_control")) { + glxSwapIntervalEXT(vsyncOn); + int swapInterval = xglGetSwapIntervalEXT(); + _isVSyncOn = swapInterval; + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } else { + qDebug("V-Sync is FORCED ON on this system\n"); + } + */ #else - qDebug("V-Sync is FORCED ON on this system\n"); + qDebug("V-Sync is FORCED ON on this system\n"); #endif } @@ -4300,40 +4300,30 @@ bool Application::isVSyncEditable() const { unsigned int Application::getRenderTargetFramerate() const { if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) { return 0; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) { return 60; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) { return 50; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) { return 40; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) { return 30; } return 0; } float Application::getRenderResolutionScale() const { - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { return 1.f; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) { return 0.666f; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) { return 0.5f; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) { return 0.333f; - } - else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) { + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) { return 0.25f; - } - else { + } else { return 1.f; } } \ No newline at end of file From 22f4e8ec70de07655644ea44bba6ab77864828c7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 14:37:56 -0800 Subject: [PATCH 14/24] cleanup heartbeat method names --- domain-server/src/DomainServer.cpp | 6 +++--- domain-server/src/DomainServer.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c6ab0285ea..ed0fd60ae5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -388,7 +388,7 @@ void DomainServer::setupAutomaticNetworking() { const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; QTimer* dataHeartbeatTimer = new QTimer(this); - connect(dataHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToDataServer); + connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer())); dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); } @@ -1091,10 +1091,10 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { - updateDomainInDataServer(newPublicSockAddr.getAddress().toString()); + sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString()); } -void DomainServer::updateDomainInDataServer(const QString& networkAddress) { +void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index e5f321639c..de485da5e7 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -66,7 +66,7 @@ private slots: void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void performICEUpdates(); - void sendHeartbeatToDataServer() { updateDomainInDataServer(); } + void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } void sendHeartbeatToIceServer(); void sendICEPingPackets(); private: @@ -77,7 +77,7 @@ private: bool optionallySetupAssignmentPayment(); void setupAutomaticNetworking(); - void updateDomainInDataServer(const QString& networkAddress = QString()); + void sendHeartbeatToDataServer(const QString& networkAddress); void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void processICEHeartbeatResponse(const QByteArray& packet); From 4d1fbc1cf9e1091bce5e64f3fd10a543d83dbf7a Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 3 Nov 2014 14:39:34 -0800 Subject: [PATCH 15/24] Fix end of line in Application.cpp --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 231e535a33..f35336817b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4326,4 +4326,4 @@ float Application::getRenderResolutionScale() const { } else { return 1.f; } -} \ No newline at end of file +} From 2250cdfcc2a750f9778925f7d89234c60997d78f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 14:50:45 -0800 Subject: [PATCH 16/24] have the domain-server unverifiably heartbeat the number of connected users --- domain-server/src/DomainServer.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ed0fd60ae5..198c61bcba 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1109,8 +1109,25 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; + // add the number of currently connected agent users + int numConnectedAuthedUsers = 0; + foreach(const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) { + ++numConnectedAuthedUsers; + } + } + + const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; + const QString HEARTBEAT_NUM_USERS_KEY = "num_users"; + + QJsonObject heartbeatObject; + heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers; + domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject; + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); + qDebug() << domainUpdateJSON; + AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), QNetworkAccessManager::PutOperation, JSONCallbackParameters(), From acef1b868c879277b912584cdf7eeb4bbd43503f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 15:05:40 -0800 Subject: [PATCH 17/24] Revert "Revert "don't allow a HifiSockAddr lookup to block application"" This reverts commit fabc3aea5709960744a72f187c86573bb5351302. --- libraries/networking/src/HifiSockAddr.cpp | 39 ++++++++++++++------ libraries/networking/src/HifiSockAddr.h | 7 +++- libraries/networking/src/LimitedNodeList.cpp | 6 +-- libraries/networking/src/LimitedNodeList.h | 1 + 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 97e9721356..3a200fd392 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -9,9 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include +#include +#include +#include #include "HifiSockAddr.h" @@ -36,14 +36,15 @@ HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) { _port = otherSockAddr._port; } -HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { - // lookup the IP by the hostname - QHostInfo hostInfo = QHostInfo::fromName(hostname); - foreach(const QHostAddress& address, hostInfo.addresses()) { - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - _address = address; - _port = hostOrderPort; - } +HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) : + _address(hostname), + _port(hostOrderPort) +{ + // if we parsed an IPv4 address out of the hostname, don't look it up + if (_address.protocol() != QAbstractSocket::IPv4Protocol) { + // sync lookup the IP by the hostname + int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo))); + qDebug() << "Looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; } } @@ -75,6 +76,22 @@ bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const { return _address == rhsSockAddr._address && _port == rhsSockAddr._port; } +void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) { + if (hostInfo.error() != QHostInfo::NoError) { + qDebug() << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString(); + } + + foreach(const QHostAddress& address, hostInfo.addresses()) { + // just take the first IPv4 address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + _address = address; + qDebug() << "QHostInfo lookup result for" + << hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString(); + break; + } + } +} + QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr) { debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port; return debug.space(); diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 42f815390a..5bbd27437b 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -19,9 +19,10 @@ #include #endif -#include +#include -class HifiSockAddr { +class HifiSockAddr : public QObject { + Q_OBJECT public: HifiSockAddr(); HifiSockAddr(const QHostAddress& address, quint16 port); @@ -51,6 +52,8 @@ public: friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr); friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr); friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr); +private slots: + void handleLookupResult(const QHostInfo& hostInfo); private: QHostAddress _address; quint16 _port; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 5c4dc6cea2..043f0621bb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -73,6 +73,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _dtlsSocket(NULL), _localSockAddr(), _publicSockAddr(), + _stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT), _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer() @@ -583,11 +584,8 @@ void LimitedNodeList::sendSTUNRequest() { QUuid randomUUID = QUuid::createUuid(); memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - // lookup the IP for the STUN server - HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); + _stunSockAddr.getAddress(), _stunSockAddr.getPort()); } void LimitedNodeList::rebindNodeSocket() { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 73381d01a5..64495fbd34 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -163,6 +163,7 @@ protected: QUdpSocket* _dtlsSocket; HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; + HifiSockAddr _stunSockAddr; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; From 74753e5b8bc66c168cb8b432e7d1602dd5766931 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 15:13:41 -0800 Subject: [PATCH 18/24] allow a blocking lookup of IP address in HifiSockAddr --- assignment-client/src/AssignmentClient.cpp | 7 ++----- libraries/networking/src/HifiSockAddr.cpp | 13 +++++++++---- libraries/networking/src/HifiSockAddr.h | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index e7ac7577b9..929d6c76c8 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -96,17 +96,14 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt(); } - - HifiSockAddr assignmentServerSocket(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, assignmentServerPort); // check for an overriden assignment server hostname if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) { - _assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString(); - // change the hostname for our assignment server - assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerSocket.getPort()); + _assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString(); } + HifiSockAddr assignmentServerSocket(_assignmentServerHostname, assignmentServerPort, true); nodeList->setAssignmentServerSocket(assignmentServerSocket); qDebug() << "Assignment server socket is" << assignmentServerSocket; diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 3a200fd392..5e7ad3a2da 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -36,15 +36,20 @@ HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) { _port = otherSockAddr._port; } -HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) : +HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) : _address(hostname), _port(hostOrderPort) { // if we parsed an IPv4 address out of the hostname, don't look it up if (_address.protocol() != QAbstractSocket::IPv4Protocol) { - // sync lookup the IP by the hostname - int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo))); - qDebug() << "Looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; + // lookup the IP by the hostname + if (shouldBlockForLookup) { + QHostInfo result = QHostInfo::fromName(hostname); + handleLookupResult(result); + } else { + int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo))); + qDebug() << "Looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; + } } } diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 5bbd27437b..064f8032ca 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -27,7 +27,7 @@ public: HifiSockAddr(); HifiSockAddr(const QHostAddress& address, quint16 port); HifiSockAddr(const HifiSockAddr& otherSockAddr); - HifiSockAddr(const QString& hostname, quint16 hostOrderPort); + HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false); HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } From 88300f06ad6b775ef21f1e54415ae8c8a51caa30 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 15:14:39 -0800 Subject: [PATCH 19/24] cleanup some debug in HifiSockAddr for hostname lookup --- libraries/networking/src/HifiSockAddr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 5e7ad3a2da..11674a948e 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -44,11 +44,12 @@ HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool if (_address.protocol() != QAbstractSocket::IPv4Protocol) { // lookup the IP by the hostname if (shouldBlockForLookup) { + qDebug() << "Asynchronously looking up IP address for hostname" << hostname; QHostInfo result = QHostInfo::fromName(hostname); handleLookupResult(result); } else { int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo))); - qDebug() << "Looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; + qDebug() << "Synchronously looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; } } } From 0348251cc121d2fd7a50a9c37c13bdec951031df Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Nov 2014 15:32:45 -0800 Subject: [PATCH 20/24] allow case insensitivity for allowed users in domain-server --- domain-server/src/DomainServer.cpp | 6 ++---- libraries/networking/src/DataServerAccountInfo.cpp | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 198c61bcba..131aa3fbe3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -637,7 +637,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, } if (allowedUsers.count() > 0) { - if (allowedUsers.contains(username)) { + if (allowedUsers.contains(username, Qt::CaseInsensitive)) { // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -657,7 +657,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, rsaPublicKey, RSA_PKCS1_PADDING); if (decryptResult != -1) { - if (username == decryptedArray) { + if (username.toLower() == decryptedArray) { qDebug() << "Username signature matches for" << username << "- allowing connection."; // free up the public key before we return @@ -1126,8 +1126,6 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); - qDebug() << domainUpdateJSON; - AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), QNetworkAccessManager::PutOperation, JSONCallbackParameters(), diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 600eb57166..9b513c96f1 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -130,11 +130,11 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() { reinterpret_cast(&privateKeyData), _privateKey.size()); if (rsaPrivateKey) { - QByteArray usernameByteArray = _username.toUtf8(); + QByteArray lowercaseUsername = _username.toLower().toUtf8(); _usernameSignature.resize(RSA_size(rsaPrivateKey)); - int encryptReturn = RSA_private_encrypt(usernameByteArray.size(), - reinterpret_cast(usernameByteArray.constData()), + int encryptReturn = RSA_private_encrypt(lowercaseUsername.size(), + reinterpret_cast(lowercaseUsername.constData()), reinterpret_cast(_usernameSignature.data()), rsaPrivateKey, RSA_PKCS1_PADDING); From e12e3b05e10b72848ed131cfc413cf9ef484f9d1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 15:33:01 -0800 Subject: [PATCH 21/24] add support for enter/leave entity events for when the avatar enters or leaves the bounds of an entity --- .../entityScripts/playSoundOnEnterOrLeave.js | 32 +++++++++ interface/src/entities/EntityTreeRenderer.cpp | 66 +++++++++++++++++++ interface/src/entities/EntityTreeRenderer.h | 9 +++ libraries/entities/src/EntityItem.h | 1 + .../entities/src/EntityScriptingInterface.h | 3 + libraries/entities/src/SphereEntityItem.h | 3 + 6 files changed, 114 insertions(+) create mode 100644 examples/entityScripts/playSoundOnEnterOrLeave.js diff --git a/examples/entityScripts/playSoundOnEnterOrLeave.js b/examples/entityScripts/playSoundOnEnterOrLeave.js new file mode 100644 index 0000000000..6f2ec830c6 --- /dev/null +++ b/examples/entityScripts/playSoundOnEnterOrLeave.js @@ -0,0 +1,32 @@ +// +// playSoundOnEnterOrLeave.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 +// your avatar enters or leaves the bounds of 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(){ + var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); + + function playSound(entityID) { + var options = new AudioInjectionOptions(); + var position = MyAvatar.position; + options.position = position; + options.volume = 0.5; + Audio.playSound(bird, options); + }; + + this.enterEntity = function(entityID) { + playSound(); + }; + + this.leaveEntity = function(entityID) { + playSound(); + }; +}) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index df2c51104b..4963c8baca 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -72,6 +72,11 @@ void EntityTreeRenderer::init() { Application::getInstance()->getControllerScriptingInterface()); Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); } + + // make sure our "last avatar position" is something other than our current position, so that on our + // first chance, we'll check for enter/leave entity events. + glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition(); + _lastAvatarPosition = avatarPosition + glm::vec3(1.f,1.f,1.f); } QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) { @@ -183,6 +188,58 @@ void EntityTreeRenderer::update() { if (_tree) { EntityTree* tree = static_cast(_tree); tree->update(); + + // check to see if the avatar has moved and if we need to handle enter/leave entity logic + checkEnterLeaveEntities(); + } +} + +void EntityTreeRenderer::checkEnterLeaveEntities() { + if (_tree) { + glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float)TREE_SCALE; + + if (avatarPosition != _lastAvatarPosition) { + float radius = 1.0f / (float)TREE_SCALE; // for now, assume 1 meter radius + QVector foundEntities; + QVector entitiesContainingAvatar; + + // find the entities near us + static_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); + + // create a list of entities that actually contain the avatar's position + foreach(const EntityItem* entity, foundEntities) { + if (entity->contains(avatarPosition)) { + entitiesContainingAvatar << entity->getEntityItemID(); + } + } + + // for all of our previous containing entities, if they are no longer containing then send them a leave event + foreach(const EntityItemID& entityID, _currentEntitiesInside) { + if (!entitiesContainingAvatar.contains(entityID)) { + emit leaveEntity(entityID); + QScriptValueList entityArgs = createEntityArgs(entityID); + QScriptValue entityScript = loadEntityScript(entityID); + if (entityScript.property("leaveEntity").isValid()) { + entityScript.property("leaveEntity").call(entityScript, entityArgs); + } + + } + } + + // for all of our new containing entities, if they weren't previously containing then send them an enter event + foreach(const EntityItemID& entityID, entitiesContainingAvatar) { + if (!_currentEntitiesInside.contains(entityID)) { + emit enterEntity(entityID); + QScriptValueList entityArgs = createEntityArgs(entityID); + QScriptValue entityScript = loadEntityScript(entityID); + if (entityScript.property("enterEntity").isValid()) { + entityScript.property("enterEntity").call(entityScript, entityArgs); + } + } + } + _currentEntitiesInside = entitiesContainingAvatar; + _lastAvatarPosition = avatarPosition; + } } } @@ -547,6 +604,9 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity); connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + + connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity); + connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity); } QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { @@ -556,6 +616,12 @@ QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& en return args; } +QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + return args; +} + void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); PickRay ray = computePickRay(event->x(), event->y()); diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 6c3f948594..47a4836738 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -104,6 +104,9 @@ signals: void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event); void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void enterEntity(const EntityItemID& entityItemID); + void leaveEntity(const EntityItemID& entityItemID); private: QList _releasedModels; @@ -113,6 +116,12 @@ private: EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; + + + QScriptValueList createEntityArgs(const EntityItemID& entityID); + void checkEnterLeaveEntities(); + glm::vec3 _lastAvatarPosition; + QVector _currentEntitiesInside; bool _wantScripts; ScriptEngine* _entitiesScriptEngine; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 65da3c964a..2117ec25b0 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -253,6 +253,7 @@ public: void applyHardCollision(const CollisionInfo& collisionInfo); virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } + virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 1152ccbd03..2150fa51da 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -115,6 +115,9 @@ signals: void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void enterEntity(const EntityItemID& entityItemID); + void leaveEntity(const EntityItemID& entityItemID); + private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index d57ada760b..21cb58223b 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -53,6 +53,9 @@ public: virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; } + // TODO: implement proper contains for 3D ellipsoid + //virtual bool contains(const glm::vec3& point) const; + protected: virtual void recalculateCollisionShape(); From bc329a63945ab2ef3775e3fd999354400d09766f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 15:34:30 -0800 Subject: [PATCH 22/24] remove blank line --- interface/src/entities/EntityTreeRenderer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 47a4836738..0a725c8294 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -117,7 +117,6 @@ private: EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; - QScriptValueList createEntityArgs(const EntityItemID& entityID); void checkEnterLeaveEntities(); glm::vec3 _lastAvatarPosition; From 2dae78b82c8e27b01228292ee456f6f137370a0d Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 15:40:09 -0800 Subject: [PATCH 23/24] CR feedback --- interface/src/entities/EntityTreeRenderer.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 4963c8baca..693b367d73 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -196,10 +196,10 @@ void EntityTreeRenderer::update() { void EntityTreeRenderer::checkEnterLeaveEntities() { if (_tree) { - glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float)TREE_SCALE; + glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float) TREE_SCALE; if (avatarPosition != _lastAvatarPosition) { - float radius = 1.0f / (float)TREE_SCALE; // for now, assume 1 meter radius + float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius QVector foundEntities; QVector entitiesContainingAvatar; @@ -276,8 +276,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI } void renderElementProxy(EntityTreeElement* entityTreeElement) { - glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE; - float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE; + glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE; + float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE; glColor3f(1.0f, 0.0f, 0.0f); glPushMatrix(); glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z); @@ -350,9 +350,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg AACube minCube = entity->getMinimumAACube(); AABox entityBox = entity->getAABox(); - maxCube.scale((float)TREE_SCALE); - minCube.scale((float)TREE_SCALE); - entityBox.scale((float)TREE_SCALE); + maxCube.scale((float) TREE_SCALE); + minCube.scale((float) TREE_SCALE); + entityBox.scale((float) TREE_SCALE); glm::vec3 maxCenter = maxCube.calcCenter(); glm::vec3 minCenter = minCube.calcCenter(); @@ -382,9 +382,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg glPopMatrix(); - glm::vec3 position = entity->getPosition() * (float)TREE_SCALE; - glm::vec3 center = entity->getCenter() * (float)TREE_SCALE; - glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE; + glm::vec3 position = entity->getPosition() * (float) TREE_SCALE; + glm::vec3 center = entity->getCenter() * (float) TREE_SCALE; + glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE; glm::quat rotation = entity->getRotation(); glColor4f(1.0f, 0.0f, 1.0f, 1.0f); From 60182a78cb0050cd996c0d61cba396b9ae496b56 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 3 Nov 2014 15:42:40 -0800 Subject: [PATCH 24/24] CR feedback --- examples/entityScripts/playSoundOnEnterOrLeave.js | 10 +++++----- interface/src/entities/EntityTreeRenderer.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/entityScripts/playSoundOnEnterOrLeave.js b/examples/entityScripts/playSoundOnEnterOrLeave.js index 6f2ec830c6..228a8a36d0 100644 --- a/examples/entityScripts/playSoundOnEnterOrLeave.js +++ b/examples/entityScripts/playSoundOnEnterOrLeave.js @@ -15,11 +15,11 @@ var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); function playSound(entityID) { - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 0.5; - Audio.playSound(bird, options); + var options = new AudioInjectionOptions(); + var position = MyAvatar.position; + options.position = position; + options.volume = 0.5; + Audio.playSound(bird, options); }; this.enterEntity = function(entityID) { diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 693b367d73..e447c703fb 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -76,7 +76,7 @@ void EntityTreeRenderer::init() { // make sure our "last avatar position" is something other than our current position, so that on our // first chance, we'll check for enter/leave entity events. glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition(); - _lastAvatarPosition = avatarPosition + glm::vec3(1.f,1.f,1.f); + _lastAvatarPosition = avatarPosition + glm::vec3(1.f, 1.f, 1.f); } QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {