diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 38e648ab72..1475eee59a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5919,6 +5919,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); + DependencyManager::get()->registerMetaTypes(scriptEngine.data()); + // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine.data(), &ScriptEngine::printedMessage, DependencyManager::get().data(), &ScriptEngines::onPrintedMessage); diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 32c67be884..988f3023be 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -17,10 +17,10 @@ #include #include "PickScriptingInterface.h" -LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, +LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, const PointerTriggers& triggers, const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled) : - Pointer(DependencyManager::get()->createRayPick(rayProps)), - _renderingEnabled(enabled), + Pointer(DependencyManager::get()->createRayPick(rayProps), enabled), + _triggers(triggers), _renderStates(renderStates), _defaultRenderStates(defaultRenderStates), _faceAvatar(faceAvatar), @@ -49,17 +49,9 @@ LaserPointer::~LaserPointer() { } } -void LaserPointer::enable() { - Pointer::enable(); - withWriteLock([&] { - _renderingEnabled = true; - }); -} - void LaserPointer::disable() { - Pointer::disable(); + Parent::disable(); withWriteLock([&] { - _renderingEnabled = false; if (!_currentRenderState.empty()) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); @@ -199,26 +191,37 @@ void LaserPointer::disableRenderState(const RenderState& renderState) { } } -void LaserPointer::update() { - // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts - withReadLock([&] { - QVariantMap prevRayPickResult = DependencyManager::get()->getPrevPickResult(_pickUID); - IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt()); - PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap()); - QUuid uid = prevRayPickResult["objectID"].toUuid(); - if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { - float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat(); - updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } else if (_renderingEnabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); - } else if (!_currentRenderState.empty()) { - disableRenderState(_renderStates[_currentRenderState]); - disableRenderState(_defaultRenderStates[_currentRenderState].second); +void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { + IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt()); + PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap()); + QUuid uid = prevRayPickResult["objectID"].toUuid(); + if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && + (type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { + float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat(); + updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + disableRenderState(_renderStates[_currentRenderState]); + updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); + } else if (!_currentRenderState.empty()) { + disableRenderState(_renderStates[_currentRenderState]); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } +} + +Pointer::PickedObject LaserPointer::getHoveredObject(const QVariantMap& pickResult) { + return Pointer::PickedObject(pickResult["objectID"].toUuid(), IntersectionType(pickResult["type"].toUInt())); +} + +Pointer::Buttons LaserPointer::getPressedButtons() { + std::unordered_set toReturn; + for (const PointerTrigger& trigger : _triggers) { + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(trigger.getButton()); } - }); + } + return toReturn; } void LaserPointer::setLength(const float length) { @@ -290,4 +293,53 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { } return RenderState(startID, pathID, endID); +} + +PointerEvent LaserPointer::buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const { + uint32_t id = 0; + glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]); + glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]); + glm::vec3 direction = -surfaceNormal; + IntersectionType type = IntersectionType(pickResult["type"].toUInt()); + glm::vec2 pos2D; + if (type == ENTITY) { + pos2D = projectOntoEntityXYPlane(uid, intersection); + } else if (type == OVERLAY) { + pos2D = projectOntoOverlayXYPlane(uid, intersection); + } + return PointerEvent(PointerEvent::Move, id, pos2D, intersection, surfaceNormal, direction, PointerEvent::NoButtons); +} + +glm::vec2 LaserPointer::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint) const { + glm::quat invRot = glm::inverse(rotation); + glm::vec3 localPos = invRot * (worldPos - position); + glm::vec3 invDimensions = glm::vec3(1.0f / dimensions.x, 1.0f / dimensions.y, 1.0f / dimensions.z); + + glm::vec3 normalizedPos = (localPos * invDimensions) + registrationPoint; + return glm::vec2(normalizedPos.x * dimensions.x, (1.0f - normalizedPos.y) * dimensions.y); +} + +glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos) const { + glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); + glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); + glm::vec3 dimensions; + + float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat(); + if (dpi > 0) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. + glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1); + glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); + const float INCHES_TO_METERS = 1.0f / 39.3701f; + dimensions = (resolution * INCHES_TO_METERS / dpi) * scale; + } else { + dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01); + } + + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); +} + +glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entity, const glm::vec3& worldPos) const { + auto props = DependencyManager::get()->getEntityProperties(entity); + return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint()); } \ No newline at end of file diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index dd1ee6de57..fbe293ea75 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -49,16 +49,15 @@ private: }; class LaserPointer : public Pointer { - + using Parent = Pointer; public: typedef std::unordered_map RenderStateMap; typedef std::unordered_map> DefaultRenderStateMap; - LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, const PointerTriggers& triggers, const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled); ~LaserPointer(); - void enable() override; void disable() override; void setRenderState(const std::string& state) override; @@ -68,12 +67,18 @@ public: void setLength(const float length) override; void setLockEndUUID(QUuid objectID, const bool isOverlay) override; - void update() override; + void updateVisuals(const QVariantMap& prevRayPickResult) override; + + PickedObject getHoveredObject(const QVariantMap& pickResult) override; + Pointer::Buttons getPressedButtons() override; static RenderState buildRenderState(const QVariantMap& propMap); +protected: + PointerEvent buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const override; + private: - bool _renderingEnabled; + PointerTriggers _triggers; float _laserLength { 0.0f }; std::string _currentRenderState { "" }; RenderStateMap _renderStates; @@ -88,6 +93,10 @@ private: void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState); void disableRenderState(const RenderState& renderState); + glm::vec2 projectOntoEntityXYPlane(const QUuid& entity, const glm::vec3& worldPos) const; + glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos) const; + glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint) const; + }; #endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 99f7ac2515..40f898e65d 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -17,6 +17,9 @@ #include "JointRayPick.h" #include "MouseRayPick.h" +#include +#include + QUuid PickScriptingInterface::createPick(const PickQuery::PickType type, const QVariant& properties) { switch (type) { case PickQuery::PickType::Ray: @@ -105,3 +108,22 @@ void PickScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue void PickScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptValue& includeItems) { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } + +QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) { + return pickType; +} + +void pickTypesFromScriptValue(const QScriptValue& object, PickQuery::PickType& pickType) { + pickType = static_cast(object.toUInt16()); +} + +void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + QScriptValue pickTypes = engine->newObject(); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < PickQuery::PickType::NUM_PICK_TYPES; ++i) { + pickTypes.setProperty(metaEnum.key(i), metaEnum.value(i)); + } + engine->globalObject().setProperty("PickType", pickTypes); + + qScriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue); +} \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 462b64dbd1..7c2eeb1ffc 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -35,6 +35,8 @@ class PickScriptingInterface : public QObject, public Dependency { public: QUuid createRayPick(const QVariant& properties); + void registerMetaTypes(QScriptEngine* engine); + public slots: Q_INVOKABLE QUuid createPick(const PickQuery::PickType type, const QVariant& properties); Q_INVOKABLE void enablePick(const QUuid& uid); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 8a87721ad9..5ac209d295 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -21,7 +21,7 @@ void PointerScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptV DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } -QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType type, const QVariant& properties) const { +QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) const { switch (type) { case PickQuery::PickType::Ray: return createLaserPointer(properties); @@ -61,7 +61,7 @@ QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) LaserPointer::RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); - for (QVariant& renderStateVariant : renderStateVariants) { + for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid()) { @@ -75,7 +75,7 @@ QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) LaserPointer::DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); - for (QVariant& renderStateVariant : renderStateVariants) { + for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { @@ -87,7 +87,26 @@ QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) } } - return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled)); + PointerTriggers triggers; + auto userInputMapper = DependencyManager::get(); + if (propertyMap["triggers"].isValid()) { + QList triggerVariants = propertyMap["triggers"].toList(); + for (const QVariant& triggerVariant : triggerVariants) { + if (triggerVariant.isValid()) { + QVariantMap triggerMap = triggerVariant.toMap(); + if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { + controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); + if (endpoint) { + std::string button = triggerMap["button"].toString().toStdString(); + triggers.emplace_back(endpoint, button); + } + } + } + } + } + + return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, triggers, + faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled)); } void PointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const { diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index de45826d48..cc2ffbc3cc 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -22,13 +22,13 @@ public: QUuid createLaserPointer(const QVariant& properties) const; public slots: - Q_INVOKABLE QUuid createPointer(const PickQuery::PickType type, const QVariant& properties) const; + Q_INVOKABLE QUuid createPointer(const PickQuery::PickType& type, const QVariant& properties) const; Q_INVOKABLE void enablePointer(const QUuid& uid) const { DependencyManager::get()->enablePointer(uid); } Q_INVOKABLE void disablePointer(const QUuid& uid) const { DependencyManager::get()->disablePointer(uid); } Q_INVOKABLE void removePointer(const QUuid& uid) const { DependencyManager::get()->removePointer(uid); } Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const; Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } - Q_INVOKABLE QVariantMap getPrevPickResult(QUuid uid) const { return DependencyManager::get()->getPrevPickResult(uid); } + Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const { return DependencyManager::get()->getPrevPickResult(uid); } Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); } @@ -37,6 +37,14 @@ public slots: Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay); } +signals: + void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent); + void triggerContinue(const QUuid& id, const PointerEvent& pointerEvent); + void triggerEnd(const QUuid& id, const PointerEvent& pointerEvent); + void hoverBegin(const QUuid& id, const PointerEvent& pointerEvent); + void hoverContinue(const QUuid& id, const PointerEvent& pointerEvent); + void hoverEnd(const QUuid& id, const PointerEvent& pointerEvent); + }; #endif // hifi_PointerScriptingInterface_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5e5b9367a6..2645d3b061 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -37,10 +37,22 @@ #include "Web3DOverlay.h" #include +#include + Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); +Overlays::Overlays() { + auto pointerManager = DependencyManager::get(); + connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterOverlay); + connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverOverlay); + connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeaveOverlay); + connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressOnOverlay); + connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMoveOnOverlay); + connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleaseOnOverlay); +} + void Overlays::cleanupAllOverlays() { QMap overlaysHUD; QMap overlaysWorld; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 732a437eae..67d2537640 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -85,7 +85,7 @@ class Overlays : public QObject { Q_PROPERTY(OverlayID keyboardFocusOverlay READ getKeyboardFocusOverlay WRITE setKeyboardFocusOverlay) public: - Overlays() {}; + Overlays(); void init(); void update(float deltatime); diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 0c8bb51008..5fd21e6299 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -129,6 +129,8 @@ namespace controller { template void withLock(F&& f) { Locker locker(_lock); f(); } + EndpointPointer endpointFor(const Input& endpoint) const; + signals: void actionEvent(int action, float state); void inputEvent(int input, float state); @@ -161,7 +163,6 @@ namespace controller { void disableMapping(const MappingPointer& mapping); EndpointPointer endpointFor(const QJSValue& endpoint); EndpointPointer endpointFor(const QScriptValue& endpoint); - EndpointPointer endpointFor(const Input& endpoint) const; EndpointPointer compositeEndpointFor(EndpointPointer first, EndpointPointer second); ConditionalPointer conditionalFor(const QJSValue& endpoint); ConditionalPointer conditionalFor(const QScriptValue& endpoint); diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 40111e257b..35301e93d6 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -12,6 +12,8 @@ include_hifi_library_headers(animation) include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) +include_hifi_library_headers(pointers) +include_hifi_library_headers(controllers) target_bullet() diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 4238eb4050..c63079b020 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -35,6 +35,8 @@ #include "EntitiesRendererLogging.h" #include "RenderableEntityItem.h" +#include + size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -55,6 +57,14 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf EntityRenderer::initEntityRenderers(); _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; + + auto pointerManager = DependencyManager::get(); + connect(pointerManager.data(), &PointerManager::hoverBeginEntity, this, &EntityTreeRenderer::hoverEnterEntity); + connect(pointerManager.data(), &PointerManager::hoverContinueEntity, this, &EntityTreeRenderer::hoverOverEntity); + connect(pointerManager.data(), &PointerManager::hoverEndEntity, this, &EntityTreeRenderer::hoverLeaveEntity); + connect(pointerManager.data(), &PointerManager::triggerBeginEntity, this, &EntityTreeRenderer::mousePressOnEntity); + connect(pointerManager.data(), &PointerManager::triggerContinueEntity, this, &EntityTreeRenderer::mouseMoveOnEntity); + connect(pointerManager.data(), &PointerManager::triggerEndEntity, this, &EntityTreeRenderer::mouseReleaseOnEntity); } EntityTreeRenderer::~EntityTreeRenderer() { diff --git a/libraries/pointers/CMakeLists.txt b/libraries/pointers/CMakeLists.txt index 504484574c..e33c76e249 100644 --- a/libraries/pointers/CMakeLists.txt +++ b/libraries/pointers/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME pointers) setup_hifi_library() GroupSources(src) -link_hifi_libraries(shared) +link_hifi_libraries(shared controllers) diff --git a/libraries/pointers/src/pointers/Pick.cpp b/libraries/pointers/src/pointers/Pick.cpp index 4e48bc906b..4315409bdb 100644 --- a/libraries/pointers/src/pointers/Pick.cpp +++ b/libraries/pointers/src/pointers/Pick.cpp @@ -9,6 +9,8 @@ const PickFilter PickFilter::NOTHING; +int pickTypeMetaTypeId = qRegisterMetaType("PickType"); + PickQuery::PickQuery(const PickFilter& filter, const float maxDistance, const bool enabled) : _filter(filter), _maxDistance(maxDistance), diff --git a/libraries/pointers/src/pointers/Pick.h b/libraries/pointers/src/pointers/Pick.h index 5dcaba2bb8..9ab17f87d8 100644 --- a/libraries/pointers/src/pointers/Pick.h +++ b/libraries/pointers/src/pointers/Pick.h @@ -136,7 +136,9 @@ public: enum PickType { Ray = 0, - Stylus + Stylus, + + NUM_PICK_TYPES }; Q_ENUM(PickType) @@ -189,6 +191,7 @@ private: QVector _ignoreItems; QVector _includeItems; }; +Q_DECLARE_METATYPE(PickQuery::PickType) template class Pick : public PickQuery { diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 6ba8c6072c..1ee725645f 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -9,6 +9,7 @@ #include #include "PickManager.h" +#include "PointerManager.h" Pointer::~Pointer() { DependencyManager::get()->removePick(_pickUID); @@ -16,10 +17,16 @@ Pointer::~Pointer() { void Pointer::enable() { DependencyManager::get()->enablePick(_pickUID); + withWriteLock([&] { + _enabled = true; + }); } void Pointer::disable() { DependencyManager::get()->disablePick(_pickUID); + withWriteLock([&] { + _enabled = false; + }); } const QVariantMap Pointer::getPrevPickResult() { @@ -36,4 +43,118 @@ void Pointer::setIgnoreItems(const QVector& ignoreItems) const { void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); +} + +void Pointer::update() { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + withReadLock([&] { + QVariantMap pickResult = getPrevPickResult(); + updateVisuals(pickResult); + generatePointerEvents(pickResult); + }); +} + +void Pointer::generatePointerEvents(const QVariantMap& pickResult) { + // TODO: avatars/HUD? + auto pointerManager = DependencyManager::get(); + + // Hover events + Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); + PointerEvent hoveredEvent = buildPointerEvent(hoveredObject.objectID, pickResult); + hoveredEvent.setType(PointerEvent::Move); + hoveredEvent.setButton(PointerEvent::NoButtons); + if (_enabled) { + if (hoveredObject.type == OVERLAY) { + if (_prevHoveredObject.type == OVERLAY) { + if (hoveredObject.objectID == _prevHoveredObject.objectID) { + emit pointerManager->hoverContinueOverlay(hoveredObject.objectID, hoveredEvent); + } else { + PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject.objectID, pickResult); + emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent); + emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); + } + } else { + emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); + if (_prevHoveredObject.type == ENTITY) { + emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); + } + } + } + + // TODO: this is basically repeated code. is there a way to clean it up? + if (hoveredObject.type == ENTITY) { + if (_prevHoveredObject.type == ENTITY) { + if (hoveredObject.objectID == _prevHoveredObject.objectID) { + emit pointerManager->hoverContinueEntity(hoveredObject.objectID, hoveredEvent); + } else { + PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject.objectID, pickResult); + emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent); + emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); + } + } else { + emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); + if (_prevHoveredObject.type == OVERLAY) { + emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); + } + } + } + } + + // Trigger events + Buttons buttons; + Buttons newButtons; + Buttons sameButtons; + // NOTE: After this loop: _prevButtons = buttons that were removed + // If !_enabled, release all buttons + if (_enabled) { + buttons = getPressedButtons(); + for (const std::string& button : buttons) { + if (_prevButtons.find(button) == _prevButtons.end()) { + newButtons.insert(button); + } else { + sameButtons.insert(button); + _prevButtons.erase(button); + } + } + } + + // Trigger begin + for (const std::string& button : newButtons) { + hoveredEvent.setType(PointerEvent::Press); + hoveredEvent.setButton(PointerEvent::PrimaryButton); + if (hoveredObject.type == ENTITY) { + emit pointerManager->triggerBeginEntity(hoveredObject.objectID, hoveredEvent); + } else if (hoveredObject.type == OVERLAY) { + emit pointerManager->triggerBeginOverlay(hoveredObject.objectID, hoveredEvent); + } + _triggeredObjects[button] = hoveredObject; + } + + // Trigger continue + for (const std::string& button : sameButtons) { + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button].objectID, pickResult); + triggeredEvent.setType(PointerEvent::Move); + triggeredEvent.setButton(PointerEvent::PrimaryButton); + if (_triggeredObjects[button].type == ENTITY) { + emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent); + } else if (_triggeredObjects[button].type == OVERLAY) { + emit pointerManager->triggerContinueOverlay(_triggeredObjects[button].objectID, triggeredEvent); + } + } + + // Trigger end + for (const std::string& button : _prevButtons) { + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button].objectID, pickResult); + triggeredEvent.setType(PointerEvent::Release); + triggeredEvent.setButton(PointerEvent::PrimaryButton); + if (_triggeredObjects[button].type == ENTITY) { + emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent); + } else if (_triggeredObjects[button].type == OVERLAY) { + emit pointerManager->triggerEndOverlay(_triggeredObjects[button].objectID, triggeredEvent); + } + _triggeredObjects.erase(button); + } + + _prevHoveredObject = hoveredObject; + _prevButtons = buttons; } \ No newline at end of file diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index 4a105f8a92..4e965cd73c 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -8,15 +8,37 @@ #ifndef hifi_Pointer_h #define hifi_Pointer_h +#include +#include + #include #include #include #include +#include +#include "PointerEvent.h" + +#include "Pick.h" + +class PointerTrigger { +public: + PointerTrigger(controller::Endpoint::Pointer endpoint, const std::string& button) : _endpoint(endpoint), _button(button) {} + + controller::Endpoint::Pointer getEndpoint() const { return _endpoint; } + const std::string& getButton() const { return _button; } + +private: + controller::Endpoint::Pointer _endpoint; + std::string _button { "" }; +}; + +using PointerTriggers = std::vector; + class Pointer : protected ReadWriteLockable { public: - Pointer(const QUuid& uid) : _pickUID(uid) {} + Pointer(const QUuid& uid, bool enabled) : _pickUID(uid), _enabled(enabled) {} virtual ~Pointer(); @@ -35,12 +57,35 @@ public: virtual void setLength(const float length) {} virtual void setLockEndUUID(QUuid objectID, const bool isOverlay) {} - virtual void update() = 0; + void update(); + virtual void updateVisuals(const QVariantMap& pickResult) = 0; + void generatePointerEvents(const QVariantMap& pickResult); + + struct PickedObject { + PickedObject() {} + PickedObject(const QUuid& objectID, IntersectionType type) : objectID(objectID), type(type) {} + + QUuid objectID; + IntersectionType type; + } typedef PickedObject; + + using Buttons = std::unordered_set; + + virtual PickedObject getHoveredObject(const QVariantMap& pickResult) = 0; + virtual Buttons getPressedButtons() = 0; QUuid getRayUID() { return _pickUID; } protected: + bool _enabled; const QUuid _pickUID; + + virtual PointerEvent buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const = 0; + +private: + PickedObject _prevHoveredObject; + Buttons _prevButtons; + std::unordered_map _triggeredObjects; }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index 2ec5921e3c..6e13c09851 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -45,13 +45,19 @@ private: QHash> _pointers; signals: - void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent); - void triggerContinue(const QUuid& id, const PointerEvent& pointerEvent); - void triggerEnd(const QUuid& id, const PointerEvent& pointerEvent); + void triggerBeginOverlay(const QUuid& id, const PointerEvent& pointerEvent); + void triggerContinueOverlay(const QUuid& id, const PointerEvent& pointerEvent); + void triggerEndOverlay(const QUuid& id, const PointerEvent& pointerEvent); + void hoverBeginOverlay(const QUuid& id, const PointerEvent& pointerEvent); + void hoverContinueOverlay(const QUuid& id, const PointerEvent& pointerEvent); + void hoverEndOverlay(const QUuid& id, const PointerEvent& pointerEvent); - void hoverEnter(const QUuid& id, const PointerEvent& pointerEvent); - void hoverOver(const QUuid& id, const PointerEvent& pointerEvent); - void hoverLeave(const QUuid& id, const PointerEvent& pointerEvent); + void triggerBeginEntity(const QUuid& id, const PointerEvent& pointerEvent); + void triggerContinueEntity(const QUuid& id, const PointerEvent& pointerEvent); + void triggerEndEntity(const QUuid& id, const PointerEvent& pointerEvent); + void hoverBeginEntity(const QUuid& id, const PointerEvent& pointerEvent); + void hoverContinueEntity(const QUuid& id, const PointerEvent& pointerEvent); + void hoverEndEntity(const QUuid& id, const PointerEvent& pointerEvent); }; #endif // hifi_pointers_PointerManager_h diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index ab77328fc1..63c063cd01 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -39,7 +39,7 @@ public: PointerEvent(EventType type, uint32_t id, const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec3& normal, const glm::vec3& direction, - Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers); + Button button, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); static void fromScriptValue(const QScriptValue& object, PointerEvent& event); @@ -56,6 +56,9 @@ public: uint32_t getButtons() const { return _buttons; } Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; } + void setType(EventType type) { _type = type; } + void setButton(Button button) { _button = button; } + private: EventType _type; uint32_t _id; // used to identify the pointer. (left vs right hand, for example) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 61e520af21..1243ed28e2 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + //"system/controllers/controllerScripts.js" // "system/chat.js" ];