From cf34fe334567c15114394047e2331eb0d2a094b8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 25 Oct 2017 16:04:21 -0700 Subject: [PATCH 1/4] laserpointers generate pointerevents --- interface/src/Application.cpp | 2 + interface/src/raypick/LaserPointer.cpp | 114 ++++++++++++----- interface/src/raypick/LaserPointer.h | 19 ++- .../src/raypick/PickScriptingInterface.cpp | 22 ++++ .../src/raypick/PickScriptingInterface.h | 2 + .../src/raypick/PointerScriptingInterface.cpp | 27 +++- .../src/raypick/PointerScriptingInterface.h | 12 +- interface/src/ui/overlays/Overlays.cpp | 12 ++ interface/src/ui/overlays/Overlays.h | 2 +- .../src/controllers/UserInputMapper.h | 3 +- libraries/entities-renderer/CMakeLists.txt | 2 + .../src/EntityTreeRenderer.cpp | 10 ++ libraries/pointers/CMakeLists.txt | 2 +- libraries/pointers/src/pointers/Pick.cpp | 2 + libraries/pointers/src/pointers/Pick.h | 5 +- libraries/pointers/src/pointers/Pointer.cpp | 121 ++++++++++++++++++ libraries/pointers/src/pointers/Pointer.h | 49 ++++++- .../pointers/src/pointers/PointerManager.h | 18 ++- libraries/shared/src/PointerEvent.h | 5 +- scripts/defaultScripts.js | 2 +- 20 files changed, 375 insertions(+), 56 deletions(-) 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" ]; From 7951826e4961896274e2aa12d045eea1898be864 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 26 Oct 2017 16:10:27 -0700 Subject: [PATCH 2/4] more advanced laser focusing --- interface/src/Application.cpp | 14 +++++---- interface/src/raypick/LaserPointer.cpp | 14 --------- interface/src/raypick/LaserPointer.h | 2 -- .../src/raypick/RayPickScriptingInterface.cpp | 1 - .../src/raypick/RayPickScriptingInterface.h | 31 +++++++++++++++++++ interface/src/ui/overlays/Overlays.cpp | 8 +++-- libraries/pointers/src/pointers/Pointer.cpp | 2 ++ libraries/pointers/src/pointers/Pointer.h | 2 +- libraries/shared/src/PointerEvent.h | 4 +++ 9 files changed, 51 insertions(+), 27 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 991d9eaecd..0db422953f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1472,13 +1472,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, [this](const EntityItemID& entityItemID, const PointerEvent& event) { - if (getEntities()->wantsKeyboardFocus(entityItemID)) { - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); - setKeyboardFocusEntity(entityItemID); - } else { - setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); + if (event.shouldFocus()) { + if (getEntities()->wantsKeyboardFocus(entityItemID)) { + setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); + setKeyboardFocusEntity(entityItemID); + } else { + setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); + } } }); diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 988f3023be..b8122a2388 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -49,20 +49,6 @@ LaserPointer::~LaserPointer() { } } -void LaserPointer::disable() { - Parent::disable(); - withWriteLock([&] { - if (!_currentRenderState.empty()) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - } - if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } - } - }); -} - void LaserPointer::setRenderState(const std::string& state) { withWriteLock([&] { if (!_currentRenderState.empty() && state != _currentRenderState) { diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index fbe293ea75..167de88385 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -58,8 +58,6 @@ public: const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled); ~LaserPointer(); - void disable() override; - void setRenderState(const std::string& state) override; // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index b89f89aab5..92bf3ec521 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -14,7 +14,6 @@ #include #include "GLMHelpers.h" -#include "PickScriptingInterface.h" #include #include "StaticRayPick.h" diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index e7c5dfd5dd..b0d36f482c 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -16,8 +16,24 @@ #include "RegisteredMetaTypes.h" #include +#include "PickScriptingInterface.h" + class RayPickScriptingInterface : public QObject, public Dependency { Q_OBJECT + Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT) + Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT) + Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT) + Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT) + Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT) + Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) SINGLETON_DEPENDENCY public slots: @@ -30,6 +46,21 @@ public slots: Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, const bool precisionPicking); Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities); + + static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); } + static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); } + static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); } + static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); } + static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); } + static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); } + static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); } + static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); } + static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); } + static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); } + static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); } + static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_OVERLAY(); } + static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); } + static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); } }; #endif // hifi_RayPickScriptingInterface_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 05dcc1148a..b37a376de3 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -908,9 +908,11 @@ void Overlays::mousePressPointerEvent(const OverlayID& overlayID, const PointerE thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); } if (thisOverlay) { - // Focus keyboard on web overlays - DependencyManager::get()->setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); - setKeyboardFocusOverlay(overlayID); + if (event.shouldFocus()) { + // Focus keyboard on web overlays + DependencyManager::get()->setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); + setKeyboardFocusOverlay(overlayID); + } // Send to web overlay QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 1ee725645f..0075762f02 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -119,9 +119,11 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { } // Trigger begin + const std::string SHOULD_FOCUS_BUTTON = "Focus"; for (const std::string& button : newButtons) { hoveredEvent.setType(PointerEvent::Press); hoveredEvent.setButton(PointerEvent::PrimaryButton); + hoveredEvent.setShouldFocus(button == SHOULD_FOCUS_BUTTON); if (hoveredObject.type == ENTITY) { emit pointerManager->triggerBeginEntity(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == OVERLAY) { diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index 4e965cd73c..595daebc33 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -77,8 +77,8 @@ public: QUuid getRayUID() { return _pickUID; } protected: - bool _enabled; const QUuid _pickUID; + bool _enabled; virtual PointerEvent buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const = 0; diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index 63c063cd01..074e5ab79b 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -55,9 +55,11 @@ public: Button getButton() const { return _button; } uint32_t getButtons() const { return _buttons; } Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; } + bool shouldFocus() const { return _shouldFocus; } void setType(EventType type) { _type = type; } void setButton(Button button) { _button = button; } + void setShouldFocus(bool focus) { _shouldFocus = focus; } private: EventType _type; @@ -70,6 +72,8 @@ private: Button _button { NoButtons }; // button associated with this event, (if type is Press, this will be the button that is pressed) uint32_t _buttons { NoButtons }; // the current state of all the buttons. Qt::KeyboardModifiers _keyboardModifiers; // set of keys held when event was generated + + bool _shouldFocus { true }; }; QDebug& operator<<(QDebug& dbg, const PointerEvent& p); From e9a5acda06373ac98a8a7c70c53f67df29b222c0 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 27 Oct 2017 10:39:37 -0700 Subject: [PATCH 3/4] control hovering, preset buttons for clicking, constness, scroll off surface --- interface/src/raypick/LaserPointer.cpp | 56 ++++++++++++++----- interface/src/raypick/LaserPointer.h | 18 +++--- .../src/raypick/PointerScriptingInterface.cpp | 7 ++- libraries/pointers/src/pointers/Pointer.cpp | 36 ++++++++---- libraries/pointers/src/pointers/Pointer.h | 14 +++-- .../pointers/src/pointers/PointerManager.cpp | 6 +- .../pointers/src/pointers/PointerManager.h | 6 +- 7 files changed, 100 insertions(+), 43 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index b8122a2388..92a404e765 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -17,9 +17,9 @@ #include #include "PickScriptingInterface.h" -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), enabled), +LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, + const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled) : + Pointer(DependencyManager::get()->createRayPick(rayProps), enabled, hover), _triggers(triggers), _renderStates(renderStates), _defaultRenderStates(defaultRenderStates), @@ -83,7 +83,7 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState) { +void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState) { if (!renderState.getStartID().isNull()) { QVariantMap startProps; startProps.insert("position", vec3toVariant(pickRay.origin)); @@ -210,13 +210,13 @@ Pointer::Buttons LaserPointer::getPressedButtons() { return toReturn; } -void LaserPointer::setLength(const float length) { +void LaserPointer::setLength(float length) { withWriteLock([&] { _laserLength = length; }); } -void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) { +void LaserPointer::setLockEndUUID(const QUuid& objectID, bool isOverlay) { withWriteLock([&] { _objectLockEnd = std::pair(objectID, isOverlay); }); @@ -281,21 +281,49 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { return RenderState(startID, pathID, endID); } -PointerEvent LaserPointer::buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const { +PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, 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()); + QUuid pickedID = pickResult["objectID"].toUuid(); glm::vec2 pos2D; - if (type == ENTITY) { - pos2D = projectOntoEntityXYPlane(uid, intersection); - } else if (type == OVERLAY) { - pos2D = projectOntoOverlayXYPlane(uid, intersection); + if (pickedID != target.objectID) { + QVariantMap searchRay = pickResult["searchRay"].toMap(); + glm::vec3 origin = vec3FromVariant(searchRay["origin"]); + glm::vec3 direction = vec3FromVariant(searchRay["direction"]); + if (target.type == ENTITY) { + intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); + } else if (target.type == OVERLAY) { + intersection = intersectRayWithOverlayXYPlane(target.objectID, origin, direction); + } + } + if (target.type == ENTITY) { + pos2D = projectOntoEntityXYPlane(target.objectID, intersection); + } else if (target.type == OVERLAY) { + pos2D = projectOntoOverlayXYPlane(target.objectID, intersection); } return PointerEvent(PointerEvent::Move, id, pos2D, intersection, surfaceNormal, direction, PointerEvent::NoButtons); } +glm::vec3 LaserPointer::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const { + glm::vec3 n = rotation * Vectors::FRONT; + float t = glm::dot(n, point - origin) / glm::dot(n, direction); + return origin + t * direction; +} + +glm::vec3 LaserPointer::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) const { + glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); + glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + return intersectRayWithXYPlane(origin, direction, position, rotation, DEFAULT_REGISTRATION_POINT); +} + +glm::vec3 LaserPointer::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) const { + auto props = DependencyManager::get()->getEntityProperties(entityID); + return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint()); +} + 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); @@ -325,7 +353,7 @@ glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const 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); +glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const { + auto props = DependencyManager::get()->getEntityProperties(entityID); 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 167de88385..bdd3f2ffa0 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -54,16 +54,16 @@ public: typedef std::unordered_map RenderStateMap; typedef std::unordered_map> DefaultRenderStateMap; - 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(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled); ~LaserPointer(); void setRenderState(const std::string& state) override; // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; - void setLength(const float length) override; - void setLockEndUUID(QUuid objectID, const bool isOverlay) override; + void setLength(float length) override; + void setLockEndUUID(const QUuid& objectID, bool isOverlay) override; void updateVisuals(const QVariantMap& prevRayPickResult) override; @@ -73,7 +73,7 @@ public: static RenderState buildRenderState(const QVariantMap& propMap); protected: - PointerEvent buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const override; private: PointerTriggers _triggers; @@ -88,10 +88,14 @@ private: std::pair _objectLockEnd { std::pair(QUuid(), false)}; void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState); + void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState); void disableRenderState(const RenderState& renderState); - glm::vec2 projectOntoEntityXYPlane(const QUuid& entity, const glm::vec3& worldPos) const; + + glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) const; + glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) const; + glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const; + glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, 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; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 5ac209d295..c3a4ac164a 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -87,6 +87,11 @@ QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) } } + bool hover = false; + if (propertyMap["hover"].isValid()) { + hover = propertyMap["hover"].toBool(); + } + PointerTriggers triggers; auto userInputMapper = DependencyManager::get(); if (propertyMap["triggers"].isValid()) { @@ -105,7 +110,7 @@ QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) } } - return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, triggers, + return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled)); } diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 0075762f02..af560a45ab 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -33,7 +33,7 @@ const QVariantMap Pointer::getPrevPickResult() { return DependencyManager::get()->getPrevPickResult(_pickUID); } -void Pointer::setPrecisionPicking(const bool precisionPicking) { +void Pointer::setPrecisionPicking(bool precisionPicking) { DependencyManager::get()->setPrecisionPicking(_pickUID, precisionPicking); } @@ -60,16 +60,17 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { // Hover events Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); - PointerEvent hoveredEvent = buildPointerEvent(hoveredObject.objectID, pickResult); + PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); + // TODO: set buttons on hover events hoveredEvent.setButton(PointerEvent::NoButtons); - if (_enabled) { + if (_enabled && _hover) { 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); + PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); } @@ -87,7 +88,7 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { emit pointerManager->hoverContinueEntity(hoveredObject.objectID, hoveredEvent); } else { - PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject.objectID, pickResult); + PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } @@ -122,7 +123,7 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { const std::string SHOULD_FOCUS_BUTTON = "Focus"; for (const std::string& button : newButtons) { hoveredEvent.setType(PointerEvent::Press); - hoveredEvent.setButton(PointerEvent::PrimaryButton); + hoveredEvent.setButton(chooseButton(button)); hoveredEvent.setShouldFocus(button == SHOULD_FOCUS_BUTTON); if (hoveredObject.type == ENTITY) { emit pointerManager->triggerBeginEntity(hoveredObject.objectID, hoveredEvent); @@ -134,9 +135,9 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { // Trigger continue for (const std::string& button : sameButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button].objectID, pickResult); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); triggeredEvent.setType(PointerEvent::Move); - triggeredEvent.setButton(PointerEvent::PrimaryButton); + hoveredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == OVERLAY) { @@ -146,9 +147,9 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { // Trigger end for (const std::string& button : _prevButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button].objectID, pickResult); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); triggeredEvent.setType(PointerEvent::Release); - triggeredEvent.setButton(PointerEvent::PrimaryButton); + hoveredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == OVERLAY) { @@ -159,4 +160,19 @@ void Pointer::generatePointerEvents(const QVariantMap& pickResult) { _prevHoveredObject = hoveredObject; _prevButtons = buttons; +} + +PointerEvent::Button Pointer::chooseButton(const std::string& button) { + const std::string PRIMARY_BUTTON = "Primary"; + const std::string SECONDARY_BUTTON = "Secondary"; + const std::string TERTIARY_BUTTON = "Tertiary"; + if (button == PRIMARY_BUTTON) { + return PointerEvent::PrimaryButton; + } else if (button == SECONDARY_BUTTON) { + return PointerEvent::SecondaryButton; + } else if (button == TERTIARY_BUTTON) { + return PointerEvent::TertiaryButton; + } else { + return PointerEvent::NoButtons; + } } \ No newline at end of file diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index 595daebc33..ca35c38c7a 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -38,7 +38,7 @@ using PointerTriggers = std::vector; class Pointer : protected ReadWriteLockable { public: - Pointer(const QUuid& uid, bool enabled) : _pickUID(uid), _enabled(enabled) {} + Pointer(const QUuid& uid, bool enabled, bool hover) : _pickUID(uid), _enabled(enabled), _hover(hover) {} virtual ~Pointer(); @@ -49,13 +49,13 @@ public: virtual void setRenderState(const std::string& state) = 0; virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0; - virtual void setPrecisionPicking(const bool precisionPicking); + virtual void setPrecisionPicking(bool precisionPicking); virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; // Pointers can choose to implement these - virtual void setLength(const float length) {} - virtual void setLockEndUUID(QUuid objectID, const bool isOverlay) {} + virtual void setLength(float length) {} + virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} void update(); virtual void updateVisuals(const QVariantMap& pickResult) = 0; @@ -79,13 +79,17 @@ public: protected: const QUuid _pickUID; bool _enabled; + bool _hover; - virtual PointerEvent buildPointerEvent(const QUuid& uid, const QVariantMap& pickResult) const = 0; + virtual PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const = 0; private: PickedObject _prevHoveredObject; Buttons _prevButtons; std::unordered_map _triggeredObjects; + + PointerEvent::Button chooseButton(const std::string& button); + }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/pointers/PointerManager.cpp b/libraries/pointers/src/pointers/PointerManager.cpp index a475ba4d83..2d41543b6b 100644 --- a/libraries/pointers/src/pointers/PointerManager.cpp +++ b/libraries/pointers/src/pointers/PointerManager.cpp @@ -79,7 +79,7 @@ void PointerManager::update() { } } -void PointerManager::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) const { +void PointerManager::setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { auto pointer = find(uid); if (pointer) { pointer->setPrecisionPicking(precisionPicking); @@ -100,14 +100,14 @@ void PointerManager::setIncludeItems(const QUuid& uid, const QVector& inc } } -void PointerManager::setLength(const QUuid& uid, const float length) const { +void PointerManager::setLength(const QUuid& uid, float length) const { auto pointer = find(uid); if (pointer) { pointer->setLength(length); } } -void PointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const { +void PointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { auto pointer = find(uid); if (pointer) { pointer->setLockEndUUID(objectID, isOverlay); diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index 6e13c09851..9f477d9eb2 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -31,12 +31,12 @@ public: void editRenderState(const QUuid& uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const; const QVariantMap getPrevPickResult(const QUuid& uid) const; - void setPrecisionPicking(const QUuid& uid, const bool precisionPicking) const; + void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const; void setIgnoreItems(const QUuid& uid, const QVector& ignoreEntities) const; void setIncludeItems(const QUuid& uid, const QVector& includeEntities) const; - void setLength(const QUuid& uid, const float length) const; - void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const; + void setLength(const QUuid& uid, float length) const; + void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const; void update(); From 76eb4c656ee8a51a4812faa1a5eb45a1b62464f1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 30 Oct 2017 16:34:02 -0700 Subject: [PATCH 4/4] fix multi-touch and keyboard on web entities --- interface/src/raypick/LaserPointer.cpp | 5 +- interface/src/ui/overlays/Web3DOverlay.cpp | 32 ++-- .../src/RenderableWebEntityItem.cpp | 140 +++++++++++------- .../src/RenderableWebEntityItem.h | 7 +- libraries/pointers/src/pointers/Pointer.cpp | 3 +- 5 files changed, 104 insertions(+), 83 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 92a404e765..83e3757514 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -285,13 +285,12 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const Q uint32_t id = 0; glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]); glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]); - glm::vec3 direction = -surfaceNormal; + QVariantMap searchRay = pickResult["searchRay"].toMap(); + glm::vec3 direction = vec3FromVariant(searchRay["direction"]); QUuid pickedID = pickResult["objectID"].toUuid(); glm::vec2 pos2D; if (pickedID != target.objectID) { - QVariantMap searchRay = pickResult["searchRay"].toMap(); glm::vec3 origin = vec3FromVariant(searchRay["origin"]); - glm::vec3 direction = vec3FromVariant(searchRay["direction"]); if (target.type == ENTITY) { intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); } else if (target.type == OVERLAY) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 0c75803d35..e32246b666 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -66,7 +66,7 @@ const QString Web3DOverlay::QML = "Web3DOverlay.qml"; Web3DOverlay::Web3DOverlay() : _dpi(DPI) { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); - _touchDevice.setName("RenderableWebEntityItemTouchDevice"); + _touchDevice.setName("Web3DOverlayTouchDevice"); _touchDevice.setMaximumTouchPoints(4); _geometryId = DependencyManager::get()->allocateID(); @@ -332,6 +332,12 @@ void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) { } void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { + if (event.getType() == PointerEvent::Press) { + _pressed = true; + } else if (event.getType() == PointerEvent::Release) { + _pressed = false; + } + if (_inputMode == Touch) { handlePointerEventAsTouch(event); } else { @@ -344,19 +350,8 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { return; } - //do not send secondary button events to tablet - if (event.getButton() == PointerEvent::SecondaryButton || - //do not block composed events - event.getButtons() == PointerEvent::SecondaryButton) { - return; - } - - - QPointF windowPoint; - { - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); - windowPoint = QPointF(windowPos.x, windowPos.y); - } + glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); + QPointF windowPoint(windowPos.x, windowPos.y); Qt::TouchPointState state = Qt::TouchPointStationary; if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { @@ -395,14 +390,13 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { } touchEvent.setWindow(_webSurface->getWindow()); + touchEvent.setDevice(&_touchDevice); touchEvent.setTarget(_webSurface->getRootItem()); touchEvent.setTouchPoints(touchPoints); touchEvent.setTouchPointStates(touchPointStates); } // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. - // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times). - // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery". // // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will // receive mouse events @@ -449,12 +443,6 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); - if (event.getType() == PointerEvent::Press) { - this->_pressed = true; - } else if (event.getType() == PointerEvent::Release) { - this->_pressed = false; - } - Qt::MouseButtons buttons = Qt::NoButton; if (event.getButtons() & PointerEvent::PrimaryButton) { buttons |= Qt::LeftButton; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index a2e574a829..ad4230c55b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -227,6 +227,8 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. _webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant()); + // Let us interact with the keyboard + _webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get().data()); _fadeStartTime = usecTimestampNow(); loadSourceURL(); _webSurface->resume(); @@ -315,21 +317,9 @@ void WebEntityRenderer::loadSourceURL() { void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { if (!_lastLocked && _webSurface && _pressed) { // If the user mouses off the entity while the button is down, simulate a touch end. - QTouchEvent::TouchPoint point; - point.setId(event.getID()); - point.setState(Qt::TouchPointReleased); - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _lastDPI); - QPointF windowPoint(windowPos.x, windowPos.y); - point.setScenePos(windowPoint); - point.setPos(windowPoint); - QList touchPoints; - touchPoints.push_back(point); - QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, - Qt::NoModifier, Qt::TouchPointReleased, touchPoints); - touchEvent->setWindow(_webSurface->getWindow()); - touchEvent->setDevice(&_touchDevice); - touchEvent->setTarget(_webSurface->getRootItem()); - QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); + PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), + event.getButton(), event.getButtons(), event.getKeyboardModifiers()); + handlePointerEvent(endEvent); } } @@ -339,57 +329,95 @@ void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { return; } + if (event.getType() == PointerEvent::Press) { + _pressed = true; + } else if (event.getType() == PointerEvent::Release) { + _pressed = false; + } + glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _lastDPI); QPointF windowPoint(windowPos.x, windowPos.y); - if (event.getType() == PointerEvent::Move) { - // Forward a mouse move event to webSurface - QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier); - QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent); + + Qt::TouchPointState state = Qt::TouchPointStationary; + if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { + state = Qt::TouchPointPressed; + } else if (event.getType() == PointerEvent::Release) { + state = Qt::TouchPointReleased; + } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { + state = Qt::TouchPointMoved; + } + + QEvent::Type touchType = QEvent::TouchUpdate; + if (_activeTouchPoints.empty()) { + // If the first active touch point is being created, send a begin + touchType = QEvent::TouchBegin; + } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { + // If the last active touch point is being released, send an end + touchType = QEvent::TouchEnd; } { - // Forward a touch update event to webSurface - if (event.getType() == PointerEvent::Press) { - this->_pressed = true; - } else if (event.getType() == PointerEvent::Release) { - this->_pressed = false; - } - - QEvent::Type type; - Qt::TouchPointState touchPointState; - switch (event.getType()) { - case PointerEvent::Press: - type = QEvent::TouchBegin; - touchPointState = Qt::TouchPointPressed; - break; - case PointerEvent::Release: - type = QEvent::TouchEnd; - touchPointState = Qt::TouchPointReleased; - break; - case PointerEvent::Move: - default: - type = QEvent::TouchUpdate; - touchPointState = Qt::TouchPointMoved; - break; - } - QTouchEvent::TouchPoint point; point.setId(event.getID()); - point.setState(touchPointState); + point.setState(state); point.setPos(windowPoint); point.setScreenPos(windowPoint); - QList touchPoints; - touchPoints.push_back(point); - - QTouchEvent* touchEvent = new QTouchEvent(type); - touchEvent->setWindow(_webSurface->getWindow()); - touchEvent->setDevice(&_touchDevice); - touchEvent->setTarget(_webSurface->getRootItem()); - touchEvent->setTouchPoints(touchPoints); - touchEvent->setTouchPointStates(touchPointState); - - QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); + _activeTouchPoints[event.getID()] = point; } + + QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers()); + { + QList touchPoints; + Qt::TouchPointStates touchPointStates; + for (const auto& entry : _activeTouchPoints) { + touchPointStates |= entry.second.state(); + touchPoints.push_back(entry.second); + } + + touchEvent.setWindow(_webSurface->getWindow()); + touchEvent.setDevice(&_touchDevice); + touchEvent.setTarget(_webSurface->getRootItem()); + touchEvent.setTouchPoints(touchPoints); + touchEvent.setTouchPointStates(touchPointStates); + } + + // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. + // + // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will + // receive mouse events + Qt::MouseButton button = Qt::NoButton; + Qt::MouseButtons buttons = Qt::NoButton; + if (event.getButton() == PointerEvent::PrimaryButton) { + button = Qt::LeftButton; + } + if (event.getButtons() & PointerEvent::PrimaryButton) { + buttons |= Qt::LeftButton; + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + if (event.getType() == PointerEvent::Move) { + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); + } +#endif + + if (touchType == QEvent::TouchBegin) { + _touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); + } else if (_touchBeginAccepted) { + QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); + } + + // If this was a release event, remove the point from the active touch points + if (state == Qt::TouchPointReleased) { + _activeTouchPoints.erase(event.getID()); + } + +#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) + if (event.getType() == PointerEvent::Move) { + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); + } +#endif } void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 2d162e57fe..8adbc17a75 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -12,6 +12,8 @@ #include #include "RenderableEntityItem.h" +#include + class OffscreenQmlSurface; class PointerEvent; @@ -58,13 +60,16 @@ private: QSharedPointer _webSurface; glm::vec3 _contextPosition; gpu::TexturePointer _texture; - bool _pressed{ false }; QString _lastSourceUrl; uint16_t _lastDPI; bool _lastLocked; QTimer _timer; uint64_t _lastRenderTime { 0 }; Transform _renderTransform; + + bool _pressed{ false }; + bool _touchBeginAccepted{ false }; + std::map _activeTouchPoints; }; } } // namespace diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index af560a45ab..8796b1e47d 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -23,10 +23,11 @@ void Pointer::enable() { } void Pointer::disable() { - DependencyManager::get()->disablePick(_pickUID); + // Disable the pointer first, then the pick, so someone can't try to use it while it's in a bad state withWriteLock([&] { _enabled = false; }); + DependencyManager::get()->disablePick(_pickUID); } const QVariantMap Pointer::getPrevPickResult() {