From 5a78c9ebfee6685c32abcef652fb6b3b3f7dc348 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 8 Nov 2017 17:17:00 -0800 Subject: [PATCH] integrated stylus into pointer system, need to update controller module --- interface/src/raypick/JointRayPick.cpp | 2 +- interface/src/raypick/JointRayPick.h | 2 +- interface/src/raypick/LaserPointer.cpp | 70 +- interface/src/raypick/LaserPointer.h | 12 +- interface/src/raypick/MouseRayPick.cpp | 2 +- interface/src/raypick/MouseRayPick.h | 2 +- .../src/raypick/PickScriptingInterface.cpp | 27 + .../src/raypick/PickScriptingInterface.h | 1 + .../src/raypick/PointerScriptingInterface.cpp | 18 +- interface/src/raypick/RayPick.cpp | 59 +- interface/src/raypick/RayPick.h | 14 +- interface/src/raypick/StaticRayPick.cpp | 2 +- interface/src/raypick/StaticRayPick.h | 2 +- interface/src/raypick/StylusPick.cpp | 230 ++++++ interface/src/raypick/StylusPick.h | 78 ++ interface/src/raypick/StylusPointer.cpp | 713 ++++-------------- interface/src/raypick/StylusPointer.h | 143 +--- libraries/pointers/src/pointers/Pointer.cpp | 4 +- libraries/pointers/src/pointers/Pointer.h | 8 +- libraries/shared/src/RegisteredMetaTypes.h | 28 +- .../controllerModules/inEditMode.js | 10 +- 21 files changed, 645 insertions(+), 782 deletions(-) create mode 100644 interface/src/raypick/StylusPick.cpp create mode 100644 interface/src/raypick/StylusPick.h diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index fdffb9796d..45b21f7bcc 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -12,7 +12,7 @@ #include "avatar/AvatarManager.h" -JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance, const bool enabled) : +JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance, bool enabled) : RayPick(filter, maxDistance, enabled), _jointName(jointName), _posOffset(posOffset), diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h index 947eb81ba8..c0031d87ff 100644 --- a/interface/src/raypick/JointRayPick.h +++ b/interface/src/raypick/JointRayPick.h @@ -16,7 +16,7 @@ class JointRayPick : public RayPick { public: - JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickRay getMathematicalPick() const override; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 464de83565..bd54a24e97 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -184,7 +184,7 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && (type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { - PickRay pickRay{ rayPickResult->pickVariant }; + PickRay pickRay(rayPickResult->pickVariant); QUuid uid = rayPickResult->objectID; float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); @@ -292,82 +292,30 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { QUuid pickedID; glm::vec3 intersection, surfaceNormal, direction, origin; - if (target.type != NONE) { - auto rayPickResult = std::static_pointer_cast(pickResult); + auto rayPickResult = std::static_pointer_cast(pickResult); + if (rayPickResult) { intersection = rayPickResult->intersection; surfaceNormal = rayPickResult->surfaceNormal; const QVariantMap& searchRay = rayPickResult->pickVariant; direction = vec3FromVariant(searchRay["direction"]); origin = vec3FromVariant(searchRay["origin"]); - pickedID = rayPickResult->objectID;; + pickedID = rayPickResult->objectID; } glm::vec2 pos2D; if (pickedID != target.objectID) { if (target.type == ENTITY) { - intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); + intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction); } else if (target.type == OVERLAY) { - intersection = intersectRayWithOverlayXYPlane(target.objectID, origin, direction); + intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction); } } if (target.type == ENTITY) { - pos2D = projectOntoEntityXYPlane(target.objectID, intersection); + pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection); } else if (target.type == OVERLAY) { - pos2D = projectOntoOverlayXYPlane(target.objectID, intersection); + pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection); } else if (target.type == HUD) { pos2D = DependencyManager::get()->calculatePos2DFromHUD(intersection); } return PointerEvent(pos2D, intersection, surfaceNormal, direction); -} - -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); - 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& 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 c93404e14d..4874cadf0a 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -75,8 +75,8 @@ protected: PickedObject getHoveredObject(const PickResultPointer& pickResult) override; Pointer::Buttons getPressedButtons() override; - bool shouldHover() override { return _currentRenderState != ""; } - bool shouldTrigger() override { return _currentRenderState != ""; } + bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } private: PointerTriggers _triggers; @@ -94,14 +94,6 @@ private: void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState); void disableRenderState(const RenderState& renderState); - - 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; - }; #endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/MouseRayPick.cpp b/interface/src/raypick/MouseRayPick.cpp index 1f39b61614..2b55c44460 100644 --- a/interface/src/raypick/MouseRayPick.cpp +++ b/interface/src/raypick/MouseRayPick.cpp @@ -13,7 +13,7 @@ #include "Application.h" #include "display-plugins/CompositorHelper.h" -MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, const bool enabled) : +MouseRayPick::MouseRayPick(const PickFilter& filter, float maxDistance, bool enabled) : RayPick(filter, maxDistance, enabled) { } diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h index 79e94ed777..a9070e8b92 100644 --- a/interface/src/raypick/MouseRayPick.h +++ b/interface/src/raypick/MouseRayPick.h @@ -16,7 +16,7 @@ class MouseRayPick : public RayPick { public: - MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + MouseRayPick(const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickRay getMathematicalPick() const override; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 137c3fa6ae..dad4cde3b7 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -16,6 +16,7 @@ #include "StaticRayPick.h" #include "JointRayPick.h" #include "MouseRayPick.h" +#include "StylusPick.h" #include #include @@ -24,6 +25,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, switch (type) { case PickQuery::PickType::Ray: return createRayPick(properties); + case PickQuery::PickType::Stylus: + return createStylusPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -81,6 +84,30 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) { return PickManager::INVALID_PICK_ID; } +unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bilateral::Side side = bilateral::Side::Invalid; + { + QVariant handVar = propMap["hand"]; + if (handVar.isValid()) { + side = bilateral::side(handVar.toInt()); + } + } + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(filter, side, enabled)); +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 613686be9b..108ee99473 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -34,6 +34,7 @@ class PickScriptingInterface : public QObject, public Dependency { public: unsigned int createRayPick(const QVariant& properties); + unsigned int createStylusPick(const QVariant& properties); void registerMetaTypes(QScriptEngine* engine); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 0572434c56..70d60fc467 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -43,19 +43,19 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& } unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const { - bilateral::Side side = bilateral::Side::Invalid; - { - QVariant handVar = properties.toMap()["hand"]; - if (handVar.isValid()) { - side = bilateral::side(handVar.toInt()); - } + QVariantMap propertyMap = properties.toMap(); + + bool hover = false; + if (propertyMap["hover"].isValid()) { + hover = propertyMap["hover"].toBool(); } - if (bilateral::Side::Invalid == side) { - return PointerEvent::INVALID_POINTER_ID; + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); } - return DependencyManager::get()->addPointer(std::make_shared(side)); + return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled)); } unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 4e1e0386f1..7689b295db 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -48,4 +48,61 @@ PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) { glm::vec3 hudRes = DependencyManager::get()->calculateRayUICollisionPoint(pick.origin, pick.direction); return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick); -} \ No newline at end of file +} + +glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration) { + // TODO: take into account registration + glm::vec3 n = rotation * Vectors::FRONT; + float t = glm::dot(n, point - origin) / glm::dot(n, direction); + return origin + t * direction; +} + +glm::vec3 RayPick::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) { + 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 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) { + auto props = DependencyManager::get()->getEntityProperties(entityID); + return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint()); +} + +glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) { + glm::quat invRot = glm::inverse(rotation); + glm::vec3 localPos = invRot * (worldPos - position); + + glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint; + + glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.y)); + if (unNormalized) { + pos2D *= glm::vec2(dimensions.x, dimensions.y); + } + return pos2D; +} + +glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) { + 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, unNormalized); +} + +glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) { + auto props = DependencyManager::get()->getEntityProperties(entityID); + return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized); +} diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 1c2b3fbb80..4a4ac69edd 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -18,7 +18,7 @@ class RayPickResult : public PickResult { public: RayPickResult() {} RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) : + RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) : PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) { } @@ -67,13 +67,23 @@ public: class RayPick : public Pick { public: - RayPick(const PickFilter& filter, const float maxDistance, const bool enabled) : Pick(filter, maxDistance, enabled) {} + RayPick(const PickFilter& filter, float maxDistance, bool enabled) : Pick(filter, maxDistance, enabled) {} PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const PickRay& pick) override; PickResultPointer getOverlayIntersection(const PickRay& pick) override; PickResultPointer getAvatarIntersection(const PickRay& pick) override; PickResultPointer getHUDIntersection(const PickRay& pick) override; + + // These are helper functions for projecting and intersecting rays + static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction); + static glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction); + static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true); + static glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized = true); + +private: + static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration); + static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized); }; #endif // hifi_RayPick_h diff --git a/interface/src/raypick/StaticRayPick.cpp b/interface/src/raypick/StaticRayPick.cpp index c79f87ad0e..b74afd6b5d 100644 --- a/interface/src/raypick/StaticRayPick.cpp +++ b/interface/src/raypick/StaticRayPick.cpp @@ -7,7 +7,7 @@ // #include "StaticRayPick.h" -StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance, const bool enabled) : +StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance, bool enabled) : RayPick(filter, maxDistance, enabled), _pickRay(position, direction) { diff --git a/interface/src/raypick/StaticRayPick.h b/interface/src/raypick/StaticRayPick.h index ded57caf4e..e4da2dbd55 100644 --- a/interface/src/raypick/StaticRayPick.h +++ b/interface/src/raypick/StaticRayPick.h @@ -13,7 +13,7 @@ class StaticRayPick : public RayPick { public: - StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickRay getMathematicalPick() const override; diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp new file mode 100644 index 0000000000..f2b650b5bf --- /dev/null +++ b/interface/src/raypick/StylusPick.cpp @@ -0,0 +1,230 @@ +// +// Created by Bradley Austin Davis on 2017/10/24 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "StylusPick.h" + +#include "RayPick.h" + +#include + +#include "ui/overlays/Base3DOverlay.h" + +#include "Application.h" +#include +#include "avatar/AvatarManager.h" + +#include +#include + +using namespace bilateral; + +// TODO: make these configurable per pick +static Setting::Handle USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false); +static const float WEB_STYLUS_LENGTH = 0.2f; +static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand +static const glm::vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f }; + +struct SideData { + QString avatarJoint; + QString cameraJoint; + controller::StandardPoseChannel channel; + controller::Hand hand; + glm::vec3 grabPointSphereOffset; + + int getJointIndex(bool finger) { + const auto& jointName = finger ? avatarJoint : cameraJoint; + return DependencyManager::get()->getMyAvatar()->getJointIndex(jointName); + } +}; + +static const std::array SIDES{ { { "LeftHandIndex4", + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + controller::StandardPoseChannel::LEFT_HAND, + controller::Hand::LEFT, + { -0.04f, 0.13f, 0.039f } }, + { "RightHandIndex4", + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", + controller::StandardPoseChannel::RIGHT_HAND, + controller::Hand::RIGHT, + { 0.04f, 0.13f, 0.039f } } } }; + +std::shared_ptr StylusPickResult::compareAndProcessNewResult(const std::shared_ptr& newRes) { + auto newStylusResult = std::static_pointer_cast(newRes); + if (newStylusResult && newStylusResult->distance < distance) { + return std::make_shared(*newStylusResult); + } else { + return std::make_shared(*this); + } +} + +bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) { + return distance < maxDistance; +} + +StylusPick::StylusPick(const PickFilter& filter, Side side, bool enabled) : + Pick(filter, 0.0f, enabled), + _side(side) +{ +} + +static StylusTip getFingerWorldLocation(Side side) { + const auto& sideData = SIDES[index(side)]; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint); + + if (fingerJointIndex == -1) { + return StylusTip(); + } + + auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + auto avatarOrientation = myAvatar->getOrientation(); + auto avatarPosition = myAvatar->getPosition(); + StylusTip result; + result.side = side; + result.orientation = avatarOrientation * fingerRotation; + result.position = avatarPosition + (avatarOrientation * fingerPosition); + return result; +} + +// controllerWorldLocation is where the controller would be, in-world, with an added offset +static StylusTip getControllerWorldLocation(Side side) { + static const std::array INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel), + UserInputMapper::makeStandardInput(SIDES[1].channel) } }; + const auto sideIndex = index(side); + const auto& input = INPUTS[sideIndex]; + + const auto pose = DependencyManager::get()->getPose(input); + const auto& valid = pose.valid; + StylusTip result; + if (valid) { + result.side = side; + const auto& sideData = SIDES[sideIndex]; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + float sensorScaleFactor = myAvatar->getSensorToWorldScale(); + auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint); + + const auto avatarOrientation = myAvatar->getOrientation(); + const auto avatarPosition = myAvatar->getPosition(); + result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex); + + result.position = avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)); + // add to the real position so the grab-point is out in front of the hand, a bit + result.position += result.orientation * (sideData.grabPointSphereOffset * sensorScaleFactor); + // move the stylus forward a bit + result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor); + + auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation; + // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. + auto worldControllerLinearVel = avatarOrientation * pose.velocity; + auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity; + result.velocity = worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos); + } + + return result; +} + +StylusTip StylusPick::getMathematicalPick() const { + StylusTip result; + if (USE_FINGER_AS_STYLUS.get()) { + result = getFingerWorldLocation(_side); + } else { + result = getControllerWorldLocation(_side); + } + return result; +} + +PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const { + return std::make_shared(pickVariant); +} + +PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { + std::vector results; + for (const auto& target : getIncludeItems()) { + if (target.isNull()) { + continue; + } + + auto entity = qApp->getEntities()->getTree()->findEntityByEntityItemID(target); + // Don't interact with non-3D or invalid overlays + if (!entity) { + continue; + } + + if (!entity->getVisible() && !getFilter().doesPickInvisible()) { + continue; + } + + const auto entityRotation = entity->getRotation(); + const auto entityPosition = entity->getPosition(); + + glm::vec3 normal = entityRotation * Vectors::UNIT_Z; + float distance = glm::dot(pick.position - entityPosition, normal); + glm::vec3 intersection = pick.position - (normal * distance); + + glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(target, intersection, false); + if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) { + results.push_back(StylusPickResult(IntersectionType::ENTITY, target, distance, intersection, pick, normal)); + } + } + + StylusPickResult nearestTarget(pick.toVariantMap()); + for (const auto& result : results) { + if (result.distance < nearestTarget.distance) { + nearestTarget = result; + } + } + return std::make_shared(nearestTarget); +} + +PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) { + std::vector results; + for (const auto& target : getIncludeItems()) { + if (target.isNull()) { + continue; + } + + auto overlay = qApp->getOverlays().getOverlay(target); + // Don't interact with non-3D or invalid overlays + if (!overlay || !overlay->is3D()) { + continue; + } + + if (!overlay->getVisible() && !getFilter().doesPickInvisible()) { + continue; + } + + auto overlay3D = std::static_pointer_cast(overlay); + const auto overlayRotation = overlay3D->getRotation(); + const auto overlayPosition = overlay3D->getPosition(); + + glm::vec3 normal = overlayRotation * Vectors::UNIT_Z; + float distance = glm::dot(pick.position - overlayPosition, normal); + glm::vec3 intersection = pick.position - (normal * distance); + + glm::vec2 pos2D = RayPick::projectOntoOverlayXYPlane(target, intersection, false); + if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) { + results.push_back(StylusPickResult(IntersectionType::OVERLAY, target, distance, intersection, pick, normal)); + } + } + + StylusPickResult nearestTarget(pick.toVariantMap()); + for (const auto& result : results) { + if (result.distance < nearestTarget.distance) { + nearestTarget = result; + } + } + return std::make_shared(nearestTarget); +} + +PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) { + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) { + return std::make_shared(pick.toVariantMap()); +} \ No newline at end of file diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h new file mode 100644 index 0000000000..9b465b9cc8 --- /dev/null +++ b/interface/src/raypick/StylusPick.h @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2017/10/24 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_StylusPick_h +#define hifi_StylusPick_h + +#include "pointers/Pick.h" +#include "RegisteredMetaTypes.h" + +class StylusPickResult : public PickResult { + using Side = bilateral::Side; + +public: + StylusPickResult() {} + StylusPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + StylusPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const StylusTip& stylusTip, + const glm::vec3& surfaceNormal = glm::vec3(NAN)) : + PickResult(stylusTip.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) { + } + + StylusPickResult(const StylusPickResult& stylusPickResult) : PickResult(stylusPickResult.pickVariant) { + type = stylusPickResult.type; + intersects = stylusPickResult.intersects; + objectID = stylusPickResult.objectID; + distance = stylusPickResult.distance; + intersection = stylusPickResult.intersection; + surfaceNormal = stylusPickResult.surfaceNormal; + } + + IntersectionType type { NONE }; + bool intersects { false }; + QUuid objectID; + float distance { FLT_MAX }; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + + virtual QVariantMap toVariantMap() const override { + QVariantMap toReturn; + toReturn["type"] = type; + toReturn["intersects"] = intersects; + toReturn["objectID"] = objectID; + toReturn["distance"] = distance; + toReturn["intersection"] = vec3toVariant(intersection); + toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal); + toReturn["stylusTip"] = PickResult::toVariantMap(); + return toReturn; + } + + bool doesIntersect() const override { return intersects; } + std::shared_ptr compareAndProcessNewResult(const std::shared_ptr& newRes) override; + bool checkOrFilterAgainstMaxDistance(float maxDistance) override; +}; + +class StylusPick : public Pick { + using Side = bilateral::Side; +public: + StylusPick(const PickFilter& filter, Side side, bool enabled); + + StylusTip getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override; + PickResultPointer getEntityIntersection(const StylusTip& pick) override; + PickResultPointer getOverlayIntersection(const StylusTip& pick) override; + PickResultPointer getAvatarIntersection(const StylusTip& pick) override; + PickResultPointer getHUDIntersection(const StylusTip& pick) override; + +private: + const Side _side; +}; + +#endif // hifi_StylusPick_h + + + + diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 6da755a797..80e783cfe4 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -7,621 +7,200 @@ // #include "StylusPointer.h" -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "RayPick.h" #include "Application.h" #include "avatar/AvatarManager.h" #include "avatar/MyAvatar.h" -#include "scripting/HMDScriptingInterface.h" -#include "ui/overlays/Web3DOverlay.h" -#include "ui/overlays/Sphere3DOverlay.h" -#include "avatar/AvatarManager.h" -#include "InterfaceLogging.h" +#include #include "PickScriptingInterface.h" +#include using namespace controller; -using namespace bilateral; -static Setting::Handle USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false); +// TODO: make these configurable per pointer static const float WEB_STYLUS_LENGTH = 0.2f; -static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand -static const vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f }; -static const float TABLET_MIN_HOVER_DISTANCE = 0.01f; + +static const float TABLET_MIN_HOVER_DISTANCE = -0.1f; static const float TABLET_MAX_HOVER_DISTANCE = 0.1f; -static const float TABLET_MIN_TOUCH_DISTANCE = -0.05f; -static const float TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; -static const float EDGE_BORDER = 0.075f; +static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f; +static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f; static const float HOVER_HYSTERESIS = 0.01f; -static const float NEAR_HYSTERESIS = 0.05f; -static const float TOUCH_HYSTERESIS = 0.002f; +static const float TOUCH_HYSTERESIS = 0.02f; -// triggered when stylus presses a web overlay/entity -static const float HAPTIC_STYLUS_STRENGTH = 1.0f; -static const float HAPTIC_STYLUS_DURATION = 20.0f; -static const float POINTER_PRESS_TO_MOVE_DELAY = 0.33f; // seconds - -static const float WEB_DISPLAY_STYLUS_DISTANCE = 0.5f; -static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; -static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT; - -std::array STYLUSES; - -static OverlayID getHomeButtonID() { - return DependencyManager::get()->getCurrentHomeButtonID(); -} - -static OverlayID getTabletScreenID() { - return DependencyManager::get()->getCurrentTabletScreenID(); -} - -struct SideData { - QString avatarJoint; - QString cameraJoint; - controller::StandardPoseChannel channel; - controller::Hand hand; - vec3 grabPointSphereOffset; - - int getJointIndex(bool finger) { - const auto& jointName = finger ? avatarJoint : cameraJoint; - return DependencyManager::get()->getMyAvatar()->getJointIndex(jointName); - } -}; - -static const std::array SIDES{ { { "LeftHandIndex4", - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - StandardPoseChannel::LEFT_HAND, - Hand::LEFT, - { -0.04f, 0.13f, 0.039f } }, - { "RightHandIndex4", - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", - StandardPoseChannel::RIGHT_HAND, - Hand::RIGHT, - { 0.04f, 0.13f, 0.039f } } } }; - -static StylusTip getFingerWorldLocation(Side side) { - const auto& sideData = SIDES[index(side)]; - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint); - - if (-1 == fingerJointIndex) { - return StylusTip(); - } - - auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); - auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex); - auto avatarOrientation = myAvatar->getOrientation(); - auto avatarPosition = myAvatar->getPosition(); - StylusTip result; - result.side = side; - result.orientation = avatarOrientation * fingerRotation; - result.position = avatarPosition + (avatarOrientation * fingerPosition); - return result; -} - -// controllerWorldLocation is where the controller would be, in-world, with an added offset -static StylusTip getControllerWorldLocation(Side side, float sensorToWorldScale) { - static const std::array INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel), - UserInputMapper::makeStandardInput(SIDES[1].channel) } }; - const auto sideIndex = index(side); - const auto& input = INPUTS[sideIndex]; - - const auto pose = DependencyManager::get()->getPose(input); - const auto& valid = pose.valid; - StylusTip result; - if (valid) { - result.side = side; - const auto& sideData = SIDES[sideIndex]; - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint); - - const auto avatarOrientation = myAvatar->getOrientation(); - const auto avatarPosition = myAvatar->getPosition(); - result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex); - result.position = - avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)); - // add to the real position so the grab-point is out in front of the hand, a bit - result.position += result.orientation * (sideData.grabPointSphereOffset * sensorToWorldScale); - auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation; - // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. - auto worldControllerLinearVel = avatarOrientation * pose.velocity; - auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity; - result.velocity = - worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos); - } - - return result; -} - -bool StylusPickResult::isNormalized() const { - return valid && (normalizedPosition == glm::clamp(normalizedPosition, vec3(0), vec3(1))); -} - -bool StylusPickResult::isNearNormal(float min, float max, float hystersis) const { - return valid && (distance == glm::clamp(distance, min - hystersis, max + hystersis)); -} - -bool StylusPickResult::isNear2D(float border, float hystersis) const { - return valid && position2D == glm::clamp(position2D, vec2(0) - border - hystersis, vec2(dimensions) + border + hystersis); -} - -bool StylusPickResult::isNear(float min, float max, float border, float hystersis) const { - // check to see if the projected stylusTip is within within the 2d border - return isNearNormal(min, max, hystersis) && isNear2D(border, hystersis); -} - -StylusPickResult::operator bool() const { - return valid; -} - -bool StylusPickResult::hasKeyboardFocus() const { - if (!overlayID.isNull()) { - return qApp->getOverlays().getKeyboardFocusOverlay() == overlayID; - } -#if 0 - if (!entityID.isNull()) { - return qApp->getKeyboardFocusEntity() == entityID; - } -#endif - return false; -} - -void StylusPickResult::setKeyboardFocus() const { - if (!overlayID.isNull()) { - qApp->getOverlays().setKeyboardFocusOverlay(overlayID); - qApp->setKeyboardFocusEntity(EntityItemID()); -#if 0 - } else if (!entityID.isNull()) { - qApp->getOverlays().setKeyboardFocusOverlay(OverlayID()); - qApp->setKeyboardFocusEntity(entityID); -#endif - } -} - -void StylusPickResult::sendHoverOverEvent() const { - if (!overlayID.isNull()) { - qApp->getOverlays().hoverOverOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, - normal, -normal }); - } - // FIXME support entity -} - -void StylusPickResult::sendHoverEnterEvent() const { - if (!overlayID.isNull()) { - qApp->getOverlays().hoverEnterOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, - normal, -normal }); - } - // FIXME support entity -} - -void StylusPickResult::sendTouchStartEvent() const { - if (!overlayID.isNull()) { - qApp->getOverlays().sendMousePressOnOverlay(overlayID, PointerEvent{ PointerEvent::Press, deviceId(), position2D, position, - normal, -normal, PointerEvent::PrimaryButton, - PointerEvent::PrimaryButton }); - } - // FIXME support entity -} - -void StylusPickResult::sendTouchEndEvent() const { - if (!overlayID.isNull()) { - qApp->getOverlays().sendMouseReleaseOnOverlay(overlayID, - PointerEvent{ PointerEvent::Release, deviceId(), position2D, position, normal, - -normal, PointerEvent::PrimaryButton }); - } - // FIXME support entity -} - -void StylusPickResult::sendTouchMoveEvent() const { - if (!overlayID.isNull()) { - qApp->getOverlays().sendMouseMoveOnOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, - normal, -normal, PointerEvent::PrimaryButton, - PointerEvent::PrimaryButton }); - } - // FIXME support entity -} - -bool StylusPickResult::doesIntersect() const { - return true; -} - -// for example: if we want the closest result, compare based on distance -// if we want all results, combine them -// must return a new pointer -std::shared_ptr StylusPickResult::compareAndProcessNewResult(const std::shared_ptr& newRes) { - auto newStylusResult = std::static_pointer_cast(newRes); - if (newStylusResult && newStylusResult->distance < distance) { - return std::make_shared(*newStylusResult); - } else { - return std::make_shared(*this); - } -} - -// returns true if this result contains any valid results with distance < maxDistance -// can also filter out results with distance >= maxDistance -bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) { - return distance < maxDistance; -} - -uint32_t StylusPickResult::deviceId() const { - // 0 is reserved for hardware mouse - return index(tip.side) + 1; -} - -StylusPick::StylusPick(Side side) - : Pick(PickFilter(PickScriptingInterface::PICK_OVERLAYS()), FLT_MAX, true) - , _side(side) { -} - -StylusTip StylusPick::getMathematicalPick() const { - StylusTip result; - if (_useFingerInsteadOfStylus) { - result = getFingerWorldLocation(_side); - } else { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - float sensorScaleFactor = myAvatar->getSensorToWorldScale(); - result = getControllerWorldLocation(_side, sensorScaleFactor); - result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor); - } - return result; -} - -PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const { - return std::make_shared(); -} - -PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { - return PickResultPointer(); -} - -PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) { - if (!getFilter().doesPickOverlays()) { - return PickResultPointer(); - } - - std::vector results; - for (const auto& target : getIncludeItems()) { - if (target.isNull()) { - continue; - } - - auto overlay = qApp->getOverlays().getOverlay(target); - // Don't interact with non-3D or invalid overlays - if (!overlay || !overlay->is3D()) { - continue; - } - - if (!overlay->getVisible() && !getFilter().doesPickInvisible()) { - continue; - } - - auto overlayType = overlay->getType(); - auto overlay3D = std::static_pointer_cast(overlay); - const auto overlayRotation = overlay3D->getRotation(); - const auto overlayPosition = overlay3D->getPosition(); - - StylusPickResult result; - result.tip = pick; - result.overlayID = target; - result.normal = overlayRotation * Vectors::UNIT_Z; - result.distance = glm::dot(pick.position - overlayPosition, result.normal); - result.position = pick.position - (result.normal * result.distance); - if (overlayType == Web3DOverlay::TYPE) { - result.dimensions = vec3(std::static_pointer_cast(overlay3D)->getSize(), 0.01f); - } else if (overlayType == Sphere3DOverlay::TYPE) { - result.dimensions = std::static_pointer_cast(overlay3D)->getDimensions(); - } else { - result.dimensions = vec3(0.01f); - } - auto tipRelativePosition = result.position - overlayPosition; - auto localPos = glm::inverse(overlayRotation) * tipRelativePosition; - auto normalizedPosition = localPos / result.dimensions; - result.normalizedPosition = normalizedPosition + 0.5f; - result.position2D = { result.normalizedPosition.x * result.dimensions.x, - (1.0f - result.normalizedPosition.y) * result.dimensions.y }; - result.valid = true; - results.push_back(result); - } - - StylusPickResult nearestTarget; - for (const auto& result : results) { - if (result && result.isNormalized() && result.distance < nearestTarget.distance) { - nearestTarget = result; - } - } - return std::make_shared(nearestTarget); -} - -PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) { - return PickResultPointer(); -} - -PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) { - return PickResultPointer(); -} - -StylusPointer::StylusPointer(Side side) - : Pointer(DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side)), - false, - true) - , _side(side) - , _sideData(SIDES[index(side)]) { - setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } }); - STYLUSES[index(_side)] = this; +StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) : + Pointer(DependencyManager::get()->createStylusPick(props), enabled, hover), + _stylusOverlay(stylusOverlay) +{ } StylusPointer::~StylusPointer() { if (!_stylusOverlay.isNull()) { qApp->getOverlays().deleteOverlay(_stylusOverlay); } - STYLUSES[index(_side)] = nullptr; } -StylusPointer* StylusPointer::getOtherStylus() { - return STYLUSES[((index(_side) + 1) % 2)]; -} - -void StylusPointer::enable() { - Parent::enable(); - withWriteLock([&] { _renderingEnabled = true; }); -} - -void StylusPointer::disable() { - Parent::disable(); - withWriteLock([&] { _renderingEnabled = false; }); -} - -void StylusPointer::updateStylusTarget() { - const float minNearDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor; - const float maxNearDistance = WEB_DISPLAY_STYLUS_DISTANCE * _sensorScaleFactor; - const float edgeBorder = EDGE_BORDER * _sensorScaleFactor; - - auto pickResult = DependencyManager::get()->getPrevPickResultTyped(_pickUID); - - if (pickResult) { - _state.target = *pickResult; - float hystersis = 0.0f; - // If we're already near the target, add hystersis to ensure we don't rapidly toggle between near and not near - // but only for the current near target - if (_previousState.nearTarget && pickResult->overlayID == _previousState.target.overlayID) { - hystersis = _nearHysteresis; - } - _state.nearTarget = pickResult->isNear(minNearDistance, maxNearDistance, edgeBorder, hystersis); - } - - // Not near anything, short circuit the rest - if (!_state.nearTarget) { - relinquishTouchFocus(); - hide(); - return; - } - - show(); - - auto minTouchDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor; - auto maxTouchDistance = TABLET_MAX_TOUCH_DISTANCE * _sensorScaleFactor; - auto maxHoverDistance = TABLET_MAX_HOVER_DISTANCE * _sensorScaleFactor; - - float hystersis = 0.0f; - if (_previousState.nearTarget && _previousState.target.overlayID == _previousState.target.overlayID) { - hystersis = _nearHysteresis; - } - - // If we're in hover distance (calculated as the normal distance from the XY plane of the overlay) - if ((getOtherStylus() && getOtherStylus()->_state.touchingTarget) || !_state.target.isNearNormal(minTouchDistance, maxHoverDistance, hystersis)) { - relinquishTouchFocus(); - return; - } - - requestTouchFocus(_state.target); - - if (!_state.target.hasKeyboardFocus()) { - _state.target.setKeyboardFocus(); - } - - if (hasTouchFocus(_state.target) && !_previousState.touchingTarget) { - _state.target.sendHoverOverEvent(); - } - - hystersis = 0.0f; - if (_previousState.touchingTarget && _previousState.target.overlayID == _state.target.overlayID) { - hystersis = _touchHysteresis; - } - - // If we're in touch distance - if (_state.target.isNearNormal(minTouchDistance, maxTouchDistance, _touchHysteresis) && _state.target.isNormalized()) { - _state.touchingTarget = true; - } -} - -void StylusPointer::update(unsigned int pointerID, float deltaTime) { - // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts - withReadLock([&] { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - // Store and reset the state - { - _previousState = _state; - _state = State(); - } - -#if 0 - // Update finger as stylus setting - { - useFingerInsteadOfStylus = (USE_FINGER_AS_STYLUS.get() && myAvatar->getJointIndex(sideData.avatarJoint) != -1); - } -#endif - - // Update scale factor - { - _sensorScaleFactor = myAvatar->getSensorToWorldScale(); - _hoverHysteresis = HOVER_HYSTERESIS * _sensorScaleFactor; - _nearHysteresis = NEAR_HYSTERESIS * _sensorScaleFactor; - _touchHysteresis = TOUCH_HYSTERESIS * _sensorScaleFactor; - } - - // Identify the current near or touching target - updateStylusTarget(); - - // If we stopped touching, or if the target overlay ID changed, send a touching exit to the previous touch target - if (_previousState.touchingTarget && - (!_state.touchingTarget || _state.target.overlayID != _previousState.target.overlayID)) { - stylusTouchingExit(); - } - - // Handle new or continuing touch - if (_state.touchingTarget) { - // If we were previously not touching, or we were touching a different overlay, add a touch enter - if (!_previousState.touchingTarget || _previousState.target.overlayID != _state.target.overlayID) { - stylusTouchingEnter(); - } else { - _touchingEnterTimer += deltaTime; - } - - stylusTouching(); - } - }); - - setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } }); -} - -void StylusPointer::show() { - if (!_stylusOverlay.isNull()) { - return; - } - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - // FIXME perhaps instantiate a stylus and use show / hide instead of create / destroy - // however, the current design doesn't really allow for this because it assumes that - // hide / show are idempotent and low cost, but constantly querying the visibility +OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) { QVariantMap overlayProperties; + // TODO: make these configurable per pointer overlayProperties["name"] = "stylus"; overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx"; overlayProperties["loadPriority"] = 10.0f; - overlayProperties["dimensions"] = vec3toVariant(_sensorScaleFactor * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH)); overlayProperties["solid"] = true; - overlayProperties["visible"] = true; + overlayProperties["visible"] = false; overlayProperties["ignoreRayIntersection"] = true; overlayProperties["drawInFront"] = false; - overlayProperties["parentID"] = AVATAR_SELF_ID; - overlayProperties["parentJointIndex"] = myAvatar->getJointIndex(_sideData.cameraJoint); - static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; - auto modelOrientation = _state.target.tip.orientation * X_ROT_NEG_90; - auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * _sensorScaleFactor); - overlayProperties["position"] = vec3toVariant(_state.target.tip.position + modelPositionOffset); - overlayProperties["rotation"] = quatToVariant(modelOrientation); - _stylusOverlay = qApp->getOverlays().addOverlay("model", overlayProperties); + return qApp->getOverlays().addOverlay("model", overlayProperties); +} + +void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { + auto stylusPickResult = std::static_pointer_cast(pickResult); + + if (_enabled && _renderState != DISABLED && stylusPickResult) { + StylusTip tip(stylusPickResult->pickVariant); + if (tip.side != bilateral::Side::Invalid) { + show(tip); + return; + } + } + hide(); +} + +void StylusPointer::show(const StylusTip& tip) { + if (!_stylusOverlay.isNull()) { + QVariantMap props; + static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; + auto modelOrientation = tip.orientation * X_ROT_NEG_90; + auto sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * sensorToWorldScale); + props["position"] = vec3toVariant(tip.position + modelPositionOffset); + props["rotation"] = quatToVariant(modelOrientation); + props["dimensions"] = vec3toVariant(sensorToWorldScale * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH)); + props["visible"] = true; + qApp->getOverlays().editOverlay(_stylusOverlay, props); + } } void StylusPointer::hide() { - if (_stylusOverlay.isNull()) { - return; + if (!_stylusOverlay.isNull()) { + QVariantMap props; + props.insert("visible", false); + qApp->getOverlays().editOverlay(_stylusOverlay, props); + } +} + +bool StylusPointer::shouldHover(const PickResultPointer& pickResult) { + auto stylusPickResult = std::static_pointer_cast(pickResult); + if (_renderState == EVENTS_ON && stylusPickResult && stylusPickResult->intersects) { + auto sensorScaleFactor = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float hysteresis = _state.hovering ? HOVER_HYSTERESIS * sensorScaleFactor : 0.0f; + bool hovering = isWithinBounds(stylusPickResult->distance, TABLET_MIN_HOVER_DISTANCE * sensorScaleFactor, + TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor, hysteresis); + + _state.hovering = hovering; + return hovering; } - qApp->getOverlays().deleteOverlay(_stylusOverlay); - _stylusOverlay = OverlayID(); + _state.hovering = false; + return false; } -#if 0 - void pointFinger(bool value) { - static const QString HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; - static const std::array KEYS{ { "pointLeftIndex", "pointLeftIndex" } }; - if (fingerPointing != value) { - QString message = QJsonDocument(QJsonObject{ { KEYS[index(side)], value } }).toJson(); - DependencyManager::get()->sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, message); - fingerPointing = value; + +bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { + auto stylusPickResult = std::static_pointer_cast(pickResult); + if (_renderState == EVENTS_ON && stylusPickResult) { + auto sensorScaleFactor = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float distance = stylusPickResult->distance; + + // If we're triggering on an object, recalculate the distance instead of using the pickResult + if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) { + glm::vec3 intersection; + glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]); + glm::vec3 direction = -_state.surfaceNormal; + if (_state.triggeredObject.type == ENTITY) { + intersection = RayPick::intersectRayWithEntityXYPlane(_state.triggeredObject.objectID, origin, direction); + } else if (_state.triggeredObject.type == OVERLAY) { + intersection = RayPick::intersectRayWithOverlayXYPlane(_state.triggeredObject.objectID, origin, direction); } + distance = glm::dot(intersection - origin, direction); + } + + float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f; + if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor, + TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) { + if (_state.triggeredObject.objectID.isNull()) { + _state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type); + _state.surfaceNormal = stylusPickResult->surfaceNormal; + _state.triggering = true; + } + return true; } -#endif -void StylusPointer::requestTouchFocus(const StylusPickResult& pickResult) { - if (!pickResult) { - return; - } - // send hover events to target if we can. - // record the entity or overlay we are hovering over. - if (!pickResult.overlayID.isNull() && pickResult.overlayID != _hoverOverlay && - getOtherStylus() && pickResult.overlayID != getOtherStylus()->_hoverOverlay) { - _hoverOverlay = pickResult.overlayID; - pickResult.sendHoverEnterEvent(); } + + _state.triggeredObject = PickedObject(); + _state.surfaceNormal = glm::vec3(NAN); + _state.triggering = false; + return false; } -bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) { - return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay); +Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) { + auto stylusPickResult = std::static_pointer_cast(pickResult); + if (!stylusPickResult) { + return PickedObject(); + } + return PickedObject(stylusPickResult->objectID, stylusPickResult->type); } -void StylusPointer::relinquishTouchFocus() { - // send hover leave event. - if (!_hoverOverlay.isNull()) { - PointerEvent pointerEvent{ PointerEvent::Move, (uint32_t)(index(_side) + 1) }; - auto& overlays = qApp->getOverlays(); - overlays.sendMouseMoveOnOverlay(_hoverOverlay, pointerEvent); - overlays.sendHoverOverOverlay(_hoverOverlay, pointerEvent); - overlays.sendHoverLeaveOverlay(_hoverOverlay, pointerEvent); - _hoverOverlay = OverlayID(); - } -}; - -void StylusPointer::stealTouchFocus() { - // send hover events to target - if (getOtherStylus() && _state.target.overlayID == getOtherStylus()->_hoverOverlay) { - getOtherStylus()->relinquishTouchFocus(); - } - requestTouchFocus(_state.target); +Pointer::Buttons StylusPointer::getPressedButtons() { + // TODO: custom buttons for styluses + Pointer::Buttons toReturn({ "Primary", "Focus" }); + return toReturn; } -void StylusPointer::stylusTouchingEnter() { - stealTouchFocus(); - _state.target.sendTouchStartEvent(); - DependencyManager::get()->triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, - _sideData.hand); - _touchingEnterTimer = 0; - _touchingEnterPosition = _state.target.position2D; - _deadspotExpired = false; +PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, direction, origin; + auto stylusPickResult = std::static_pointer_cast(pickResult); + if (stylusPickResult) { + intersection = stylusPickResult->intersection; + surfaceNormal = _state.surfaceNormal; + const QVariantMap& stylusTip = stylusPickResult->pickVariant; + origin = vec3FromVariant(stylusTip["position"]); + direction = -_state.surfaceNormal; + pickedID = stylusPickResult->objectID; + } + + if (pickedID != target.objectID) { + if (target.type == ENTITY) { + intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction); + } else if (target.type == OVERLAY) { + intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction); + } + } + glm::vec2 pos2D; + if (target.type == ENTITY) { + pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, origin); + } else if (target.type == OVERLAY) { + pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, origin); + } else if (target.type == HUD) { + pos2D = DependencyManager::get()->calculatePos2DFromHUD(origin); + } + return PointerEvent(pos2D, intersection, surfaceNormal, direction); } -void StylusPointer::stylusTouchingExit() { - if (!_previousState.target) { - return; - } - // special case to handle home button. - if (_previousState.target.overlayID == getHomeButtonID()) { - DependencyManager::get()->sendLocalMessage("home", _previousState.target.overlayID.toString()); - } - - // send touch end event - _state.target.sendTouchEndEvent(); +bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) { + return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis)); } -void StylusPointer::stylusTouching() { - qDebug() << "QQQ " << __FUNCTION__; - if (_state.target.overlayID.isNull()) { - return; +void StylusPointer::setRenderState(const std::string& state) { + if (state == "events on") { + _renderState = EVENTS_ON; + } else if (state == "events off") { + _renderState = EVENTS_OFF; + } else if (state == "disabled") { + _renderState = DISABLED; } - - if (!_deadspotExpired) { - _deadspotExpired = - (_touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY) || - glm::distance2(_state.target.position2D, _touchingEnterPosition) > TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED; - } - - // Only send moves if the target moves more than the deadspot position or if we've timed out the deadspot - if (_deadspotExpired) { - _state.target.sendTouchMoveEvent(); - } -} +} \ No newline at end of file diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 5308315187..cc7f2814db 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -8,136 +8,65 @@ #ifndef hifi_StylusPointer_h #define hifi_StylusPointer_h -#include -#include - #include -#include #include #include -#include #include "ui/overlays/Overlay.h" - -class StylusPick : public Pick { - using Side = bilateral::Side; -public: - StylusPick(Side side); - - StylusTip getMathematicalPick() const override; - PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override; - PickResultPointer getEntityIntersection(const StylusTip& pick) override; - PickResultPointer getOverlayIntersection(const StylusTip& pick) override; - PickResultPointer getAvatarIntersection(const StylusTip& pick) override; - PickResultPointer getHUDIntersection(const StylusTip& pick) override; - -private: - const Side _side; - const bool _useFingerInsteadOfStylus{ false }; -}; - -struct SideData; - -struct StylusPickResult : public PickResult { - using Side = bilateral::Side; - // FIXME make into a single ID - OverlayID overlayID; - // FIXME restore entity functionality -#if 0 - EntityItemID entityID; -#endif - StylusTip tip; - float distance{ FLT_MAX }; - vec3 position; - vec2 position2D; - vec3 normal; - vec3 normalizedPosition; - vec3 dimensions; - bool valid{ false }; - - virtual bool doesIntersect() const override; - virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr& newRes) override; - virtual bool checkOrFilterAgainstMaxDistance(float maxDistance) override; - - bool isNormalized() const; - bool isNearNormal(float min, float max, float hystersis) const; - bool isNear2D(float border, float hystersis) const; - bool isNear(float min, float max, float border, float hystersis) const; - operator bool() const; - bool hasKeyboardFocus() const; - void setKeyboardFocus() const; - void sendHoverOverEvent() const; - void sendHoverEnterEvent() const; - void sendTouchStartEvent() const; - void sendTouchEndEvent() const; - void sendTouchMoveEvent() const; - -private: - uint32_t deviceId() const; -}; - +#include "StylusPick.h" class StylusPointer : public Pointer { using Parent = Pointer; - using Side = bilateral::Side; using Ptr = std::shared_ptr; public: - StylusPointer(Side side); + StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled); ~StylusPointer(); - void enable() override; - void disable() override; - void update(unsigned int pointerID, float deltaTime) override; + void updateVisuals(const PickResultPointer& pickResult) override; + + // Styluses have three render states: + // default: "events on" -> render and hover/trigger + // "events off" -> render, don't hover/trigger + // "disabled" -> don't render, don't hover/trigger + void setRenderState(const std::string& state) override; + void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {} + + static OverlayID buildStylusOverlay(const QVariantMap& properties); + +protected: + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; + Buttons getPressedButtons() override; + bool shouldHover(const PickResultPointer& pickResult) override; + bool shouldTrigger(const PickResultPointer& pickResult) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override; private: - - virtual void setRenderState(const std::string& state) override {} - virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {} - virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) override { return PickedObject(); } - virtual Buttons getPressedButtons() override { return {}; } - bool shouldHover() override { return true; } - bool shouldTrigger() override { return true; } - virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override { return PointerEvent(); } - - - StylusPointer* getOtherStylus(); - - void updateStylusTarget(); - void requestTouchFocus(const StylusPickResult& pickResult); - bool hasTouchFocus(const StylusPickResult& pickResult); - void relinquishTouchFocus(); - void stealTouchFocus(); - void stylusTouchingEnter(); - void stylusTouchingExit(); - void stylusTouching(); - void show(); + void show(const StylusTip& tip); void hide(); - struct State { - StylusPickResult target; - bool nearTarget{ false }; - bool touchingTarget{ false }; + struct TriggerState { + PickedObject triggeredObject; + glm::vec3 surfaceNormal { NAN }; + bool hovering { false }; + bool triggering { false }; }; - State _state; - State _previousState; + TriggerState _state; - float _nearHysteresis{ 0.0f }; - float _touchHysteresis{ 0.0f }; - float _hoverHysteresis{ 0.0f }; + enum RenderState { + EVENTS_ON = 0, + EVENTS_OFF, + DISABLED + }; - float _sensorScaleFactor{ 1.0f }; - float _touchingEnterTimer{ 0 }; - vec2 _touchingEnterPosition; - bool _deadspotExpired{ false }; + RenderState _renderState { EVENTS_ON }; - bool _renderingEnabled; - OverlayID _stylusOverlay; - OverlayID _hoverOverlay; - const Side _side; - const SideData& _sideData; + const OverlayID _stylusOverlay; + + static bool isWithinBounds(float distance, float min, float max, float hysteresis); }; #endif // hifi_StylusPointer_h diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 767721ba7d..e17fb8ec65 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -78,7 +78,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin Buttons sameButtons; const std::string PRIMARY_BUTTON = "Primary"; bool primaryPressed = false; - if (_enabled && shouldTrigger()) { + if (_enabled && shouldTrigger(pickResult)) { buttons = getPressedButtons(); primaryPressed = buttons.find(PRIMARY_BUTTON) != buttons.end(); for (const std::string& button : buttons) { @@ -92,7 +92,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // Hover events - bool doHover = shouldHover(); + bool doHover = shouldHover(pickResult); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index 9eeeb5ea7e..0415f00d1e 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -62,8 +62,8 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} - virtual void update(unsigned int pointerID, float deltaTime); - virtual void updateVisuals(const PickResultPointer& pickResult) {} + void update(unsigned int pointerID, float deltaTime); + virtual void updateVisuals(const PickResultPointer& pickResult) = 0; void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult); struct PickedObject { @@ -87,8 +87,8 @@ protected: virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0; virtual Buttons getPressedButtons() = 0; - virtual bool shouldHover() = 0; - virtual bool shouldTrigger() = 0; + virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } + virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } private: PickedObject _prevHoveredObject; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index c4ac7aba14..8bb51c88f6 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -154,18 +154,30 @@ public: } }; -struct StylusTip : public MathPick { - bilateral::Side side{ bilateral::Side::Invalid }; +class StylusTip : public MathPick { +public: + StylusTip() : position(NAN), velocity(NAN) {} + StylusTip(const QVariantMap& pickVariant) : side(bilateral::Side(pickVariant["side"].toInt())), position(vec3FromVariant(pickVariant["position"])), + orientation(quatFromVariant(pickVariant["orientation"])), velocity(vec3FromVariant(pickVariant["velocity"])) {} + + bilateral::Side side { bilateral::Side::Invalid }; glm::vec3 position; glm::quat orientation; glm::vec3 velocity; - virtual operator bool() const override { return side != bilateral::Side::Invalid; } + + operator bool() const override { return side != bilateral::Side::Invalid; } + + bool operator==(const StylusTip& other) const { + return (side == other.side && position == other.position && orientation == other.orientation && velocity == other.velocity); + } + QVariantMap toVariantMap() const override { - QVariantMap pickRay; - pickRay["position"] = vec3toVariant(position); - pickRay["orientation"] = quatToVariant(orientation); - pickRay["velocity"] = vec3toVariant(velocity); - return pickRay; + QVariantMap stylusTip; + stylusTip["side"] = (int)side; + stylusTip["position"] = vec3toVariant(position); + stylusTip["orientation"] = quatToVariant(orientation); + stylusTip["velocity"] = vec3toVariant(velocity); + return stylusTip; } }; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 7941ec8d4c..c445895d6c 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -128,16 +128,16 @@ Script.include("/~/system/libraries/utils.js"); LaserPointers.enableLaserPointer(this.laserPointer); LaserPointers.setRenderState(this.laserPointer, this.mode); - if (HMD.tabletID !== this.tabletID || HMD.tabletButtonID !== this.tabletButtonID || HMD.tabletScreenID !== this.tabletScreenID) { + if (HMD.tabletID !== this.tabletID || HMD.homeButtonID !== this.homeButtonID || HMD.tabletScreenID !== this.tabletScreenID) { this.tabletID = HMD.tabletID; - this.tabletButtonID = HMD.tabletButtonID; + this.homeButtonID = HMD.homeButtonID; this.tabletScreenID = HMD.tabletScreenID; - LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]); + LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.homeButtonID, HMD.tabletScreenID]); } }; this.pointingAtTablet = function(objectID) { - if (objectID === HMD.tabletScreenID || objectID === HMD.tabletButtonID) { + if (objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID) { return true; } return false; @@ -240,7 +240,7 @@ Script.include("/~/system/libraries/utils.js"); defaultRenderStates: defaultRenderStates }); - LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]); + LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.homeButtonID, HMD.tabletScreenID]); } var leftHandInEditMode = new InEditMode(LEFT_HAND);