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);