From f061b9c2da92d9a8f85376b8c272644ab63c8209 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 25 Oct 2017 15:11:18 -0700 Subject: [PATCH 1/5] Add stylus functionality to pointer manager --- interface/src/Application.cpp | 16 +- interface/src/raypick/JointRayPick.cpp | 2 +- interface/src/raypick/JointRayPick.h | 2 +- interface/src/raypick/LaserPointer.cpp | 44 +- interface/src/raypick/LaserPointer.h | 6 +- .../LaserPointerScriptingInterface.cpp | 6 +- .../raypick/LaserPointerScriptingInterface.h | 2 +- interface/src/raypick/MouseRayPick.cpp | 2 +- interface/src/raypick/MouseRayPick.h | 2 +- .../src/raypick/PickScriptingInterface.cpp | 7 +- .../src/raypick/PointerScriptingInterface.cpp | 41 +- .../src/raypick/PointerScriptingInterface.h | 5 +- interface/src/raypick/RayPick.h | 2 +- .../src/raypick/RayPickScriptingInterface.cpp | 7 +- interface/src/raypick/StaticRayPick.cpp | 2 +- interface/src/raypick/StaticRayPick.h | 2 +- interface/src/raypick/StylusPointer.cpp | 627 ++++++++++++++++++ interface/src/raypick/StylusPointer.h | 145 ++++ libraries/pointers/src/pointers/Pick.h | 4 +- .../src/pointers/PickCacheOptimizer.h | 16 +- .../pointers/src/pointers/PickManager.cpp | 9 +- libraries/pointers/src/pointers/PickManager.h | 8 +- libraries/pointers/src/pointers/Pointer.cpp | 10 +- libraries/pointers/src/pointers/Pointer.h | 16 +- .../pointers/src/pointers/PointerManager.cpp | 11 +- .../pointers/src/pointers/PointerManager.h | 4 +- libraries/script-engine/src/ScriptCache.cpp | 1 + libraries/shared/src/PointerEvent.h | 6 +- libraries/shared/src/RegisteredMetaTypes.h | 67 +- libraries/shared/src/shared/Bilateral.h | 21 +- scripts/defaultScripts.js | 2 +- .../controllerModules/tabletStylusInput.js | 454 +------------ 32 files changed, 1019 insertions(+), 530 deletions(-) create mode 100644 interface/src/raypick/StylusPointer.cpp create mode 100644 interface/src/raypick/StylusPointer.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 07962d838b..420c4e7ce6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1819,14 +1819,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setMouseRayPickResultOperator([&](QUuid rayPickID) { RayToEntityIntersectionResult entityResult; entityResult.intersects = false; - QVariantMap result = DependencyManager::get()->getPrevPickResult(rayPickID); - if (result["type"].isValid()) { - entityResult.intersects = result["type"] != PickScriptingInterface::INTERSECTED_NONE(); + auto pickResult = DependencyManager::get()->getPrevPickResultTyped(rayPickID); + if (pickResult) { + entityResult.intersects = pickResult->type != IntersectionType::NONE; if (entityResult.intersects) { - entityResult.intersection = vec3FromVariant(result["intersection"]); - entityResult.distance = result["distance"].toFloat(); - entityResult.surfaceNormal = vec3FromVariant(result["surfaceNormal"]); - entityResult.entityID = result["objectID"].toUuid(); + entityResult.intersection = pickResult->intersection; + entityResult.distance = pickResult->distance; + entityResult.surfaceNormal = pickResult->surfaceNormal; + entityResult.entityID = pickResult->objectID; entityResult.entity = DependencyManager::get()->getTree()->findEntityByID(entityResult.entityID); } } @@ -4957,7 +4957,7 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(app, "PointerManager"); - DependencyManager::get()->update(); + DependencyManager::get()->update(deltaTime); } { diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index c2a7fb11e5..fdffb9796d 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -20,7 +20,7 @@ JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOff { } -const PickRay JointRayPick::getMathematicalPick() const { +PickRay JointRayPick::getMathematicalPick() const { auto myAvatar = DependencyManager::get()->getMyAvatar(); int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); bool useAvatarHead = _jointName == "Avatar"; diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h index ab44bf67c8..0ef39d6336 100644 --- a/interface/src/raypick/JointRayPick.h +++ b/interface/src/raypick/JointRayPick.h @@ -18,7 +18,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); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; private: std::string _jointName; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 83e3757514..bf1847e3f6 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -16,6 +16,7 @@ #include #include #include "PickScriptingInterface.h" +#include "RayPick.h" 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) : @@ -177,17 +178,20 @@ void LaserPointer::disableRenderState(const RenderState& renderState) { } } -void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { - IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt()); - PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap()); - QUuid uid = prevRayPickResult["objectID"].toUuid(); +void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(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())) { - float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat(); + PickRay pickRay{ rayPickResult->pickVariant }; + QUuid uid = rayPickResult->objectID; + float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; 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]); + PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); } else if (!_currentRenderState.empty()) { disableRenderState(_renderStates[_currentRenderState]); @@ -195,8 +199,12 @@ void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { } } -Pointer::PickedObject LaserPointer::getHoveredObject(const QVariantMap& pickResult) { - return Pointer::PickedObject(pickResult["objectID"].toUuid(), IntersectionType(pickResult["type"].toUInt())); +Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + if (!rayPickResult) { + return PickedObject(); + } + return PickedObject(rayPickResult->objectID, rayPickResult->type); } Pointer::Buttons LaserPointer::getPressedButtons() { @@ -281,16 +289,22 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { return RenderState(startID, pathID, endID); } -PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const { +PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { uint32_t id = 0; - glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]); - glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]); - QVariantMap searchRay = pickResult["searchRay"].toMap(); - glm::vec3 direction = vec3FromVariant(searchRay["direction"]); - QUuid pickedID = pickResult["objectID"].toUuid(); + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, direction, origin; + if (target.type != NONE) { + auto rayPickResult = std::static_pointer_cast(pickResult); + intersection = rayPickResult->intersection; + surfaceNormal = rayPickResult->surfaceNormal; + const QVariantMap& searchRay = rayPickResult->pickVariant; + direction = vec3FromVariant(searchRay["direction"]); + origin = vec3FromVariant(searchRay["origin"]); + pickedID = rayPickResult->objectID;; + } + glm::vec2 pos2D; if (pickedID != target.objectID) { - glm::vec3 origin = vec3FromVariant(searchRay["origin"]); if (target.type == ENTITY) { intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); } else if (target.type == OVERLAY) { @@ -355,4 +369,4 @@ glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const 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 bdd3f2ffa0..9fbbadb475 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -65,15 +65,15 @@ public: void setLength(float length) override; void setLockEndUUID(const QUuid& objectID, bool isOverlay) override; - void updateVisuals(const QVariantMap& prevRayPickResult) override; + void updateVisuals(const PickResultPointer& prevRayPickResult) override; - PickedObject getHoveredObject(const QVariantMap& pickResult) override; + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; Pointer::Buttons getPressedButtons() override; static RenderState buildRenderState(const QVariantMap& propMap); protected: - PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override; private: PointerTriggers _triggers; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp index 533dffafb9..92ad837e7a 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.cpp +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -27,4 +27,8 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert void LaserPointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const { DependencyManager::get()->editRenderState(uid, renderState, properties); -} \ No newline at end of file +} + +QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(const QUuid& uid) const { + return DependencyManager::get()->getPrevPickResult(uid); +} diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 1116da1528..d1dd5499f4 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -27,7 +27,7 @@ public slots: Q_INVOKABLE void removeLaserPointer(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 getPrevRayPickResult(QUuid uid) const { return DependencyManager::get()->getPrevPickResult(uid); } + Q_INVOKABLE QVariantMap getPrevRayPickResult(const QUuid& uid) const; 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); } diff --git a/interface/src/raypick/MouseRayPick.cpp b/interface/src/raypick/MouseRayPick.cpp index f691dafc01..1f39b61614 100644 --- a/interface/src/raypick/MouseRayPick.cpp +++ b/interface/src/raypick/MouseRayPick.cpp @@ -18,7 +18,7 @@ MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, co { } -const PickRay MouseRayPick::getMathematicalPick() const { +PickRay MouseRayPick::getMathematicalPick() const { QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); if (position.isValid()) { QVariantMap posMap = position.toMap(); diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h index e9eb3ccabf..81561d7459 100644 --- a/interface/src/raypick/MouseRayPick.h +++ b/interface/src/raypick/MouseRayPick.h @@ -18,7 +18,7 @@ class MouseRayPick : public RayPick { public: MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; }; #endif // hifi_MouseRayPick_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 40f898e65d..ac81bcf72f 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -94,7 +94,12 @@ void PickScriptingInterface::removePick(const QUuid& uid) { } QVariantMap PickScriptingInterface::getPrevPickResult(const QUuid& uid) { - return DependencyManager::get()->getPrevPickResult(uid); + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; } void PickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) { diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index c3a4ac164a..74f207f793 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -10,9 +10,11 @@ #include #include +#include #include "Application.h" #include "LaserPointer.h" +#include "StylusPointer.h" void PointerScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -21,15 +23,40 @@ 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) { + // Interaction with managers should always happen ont he main thread + if (QThread::currentThread() != qApp->thread()) { + QUuid result; + BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(QUuid, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties)); + return result; + } + switch (type) { case PickQuery::PickType::Ray: return createLaserPointer(properties); + case PickQuery::PickType::Stylus: + return createStylus(properties); default: return QUuid(); } } +QUuid 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()); + } + } + + if (bilateral::Side::Invalid == side) { + return QUuid(); + } + + return DependencyManager::get()->addPointer(std::make_shared(side)); +} + QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -133,4 +160,14 @@ void PointerScriptingInterface::editRenderState(const QUuid& uid, const QString& } DependencyManager::get()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps); -} \ No newline at end of file +} + +QVariantMap PointerScriptingInterface::getPrevPickResult(const QUuid& uid) const { + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; +} + diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index cc2ffbc3cc..0cba7e2a6d 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -20,15 +20,16 @@ class PointerScriptingInterface : public QObject, public Dependency { public: QUuid createLaserPointer(const QVariant& properties) const; + QUuid createStylus(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); 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(const QUuid& uid) const { return DependencyManager::get()->getPrevPickResult(uid); } + Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const; 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); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index b5d7ea7c3e..1c2b3fbb80 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -53,7 +53,7 @@ public: bool doesIntersect() const override { return intersects; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; } - PickResultPointer compareAndProcessNewResult(const PickResultPointer newRes) override { + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { auto newRayRes = std::static_pointer_cast(newRes); if (newRayRes->distance < distance) { return std::make_shared(*newRayRes); diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index 92bf3ec521..8cee02270d 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -37,7 +37,12 @@ void RayPickScriptingInterface::removeRayPick(const QUuid& uid) { } QVariantMap RayPickScriptingInterface::getPrevRayPickResult(const QUuid& uid) { - return DependencyManager::get()->getPrevPickResult(uid); + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; } void RayPickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) { diff --git a/interface/src/raypick/StaticRayPick.cpp b/interface/src/raypick/StaticRayPick.cpp index f7803aade6..c79f87ad0e 100644 --- a/interface/src/raypick/StaticRayPick.cpp +++ b/interface/src/raypick/StaticRayPick.cpp @@ -13,6 +13,6 @@ StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& directi { } -const PickRay StaticRayPick::getMathematicalPick() const { +PickRay StaticRayPick::getMathematicalPick() const { return _pickRay; } \ No newline at end of file diff --git a/interface/src/raypick/StaticRayPick.h b/interface/src/raypick/StaticRayPick.h index 6dc0a809ae..ded57caf4e 100644 --- a/interface/src/raypick/StaticRayPick.h +++ b/interface/src/raypick/StaticRayPick.h @@ -15,7 +15,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); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; private: PickRay _pickRay; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp new file mode 100644 index 0000000000..4cc34f36d9 --- /dev/null +++ b/interface/src/raypick/StylusPointer.cpp @@ -0,0 +1,627 @@ +// +// 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 "StylusPointer.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "PickScriptingInterface.h" + +using namespace controller; +using namespace bilateral; + +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 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_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 HOVER_HYSTERESIS = 0.01f; +static const float NEAR_HYSTERESIS = 0.05f; +static const float TOUCH_HYSTERESIS = 0.002f; + +// 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() { + 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(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 + QVariantMap overlayProperties; + 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["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); +} + +void StylusPointer::hide() { + if (_stylusOverlay.isNull()) { + return; + } + + qApp->getOverlays().deleteOverlay(_stylusOverlay); + _stylusOverlay = OverlayID(); +} +#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; + } + } +#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(); + } +} + +bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) { + return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay); +} + +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); +} + +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; +} + +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(); +} + +void StylusPointer::stylusTouching() { + qDebug() << "QQQ " << __FUNCTION__; + if (_state.target.overlayID.isNull()) { + return; + } + + 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(); + } +} diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h new file mode 100644 index 0000000000..7e67a7398e --- /dev/null +++ b/interface/src/raypick/StylusPointer.h @@ -0,0 +1,145 @@ +// +// 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_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; +}; + + +class StylusPointer : public Pointer { + using Parent = Pointer; + using Side = bilateral::Side; + using Ptr = std::shared_ptr; + +public: + StylusPointer(Side side); + ~StylusPointer(); + + void enable() override; + void disable() override; + void update(float deltaTime) 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 {}; } + 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 hide(); + + struct State { + StylusPickResult target; + bool nearTarget{ false }; + bool touchingTarget{ false }; + }; + + State _state; + State _previousState; + + float _nearHysteresis{ 0.0f }; + float _touchHysteresis{ 0.0f }; + float _hoverHysteresis{ 0.0f }; + + float _sensorScaleFactor{ 1.0f }; + float _touchingEnterTimer{ 0 }; + vec2 _touchingEnterPosition; + bool _deadspotExpired{ false }; + + bool _renderingEnabled; + OverlayID _stylusOverlay; + OverlayID _hoverOverlay; + const Side _side; + const SideData& _sideData; +}; + +#endif // hifi_StylusPointer_h + + + + diff --git a/libraries/pointers/src/pointers/Pick.h b/libraries/pointers/src/pointers/Pick.h index 9ab17f87d8..791238d601 100644 --- a/libraries/pointers/src/pointers/Pick.h +++ b/libraries/pointers/src/pointers/Pick.h @@ -118,7 +118,7 @@ public: // for example: if we want the closest result, compare based on distance // if we want all results, combine them // must return a new pointer - virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr newRes) = 0; + virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr& newRes) = 0; // returns true if this result contains any valid results with distance < maxDistance // can also filter out results with distance >= maxDistance @@ -198,7 +198,7 @@ class Pick : public PickQuery { public: Pick(const PickFilter& filter, const float maxDistance, const bool enabled) : PickQuery(filter, maxDistance, enabled) {} - virtual const T getMathematicalPick() const = 0; + virtual T getMathematicalPick() const = 0; virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0; virtual PickResultPointer getEntityIntersection(const T& pick) = 0; virtual PickResultPointer getOverlayIntersection(const T& pick) = 0; diff --git a/libraries/pointers/src/pointers/PickCacheOptimizer.h b/libraries/pointers/src/pointers/PickCacheOptimizer.h index c4bf96ab51..58c3ac9098 100644 --- a/libraries/pointers/src/pointers/PickCacheOptimizer.h +++ b/libraries/pointers/src/pointers/PickCacheOptimizer.h @@ -87,7 +87,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); - cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + if (entityRes) { + cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + } } } @@ -95,7 +97,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); - cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + if (overlayRes) { + cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + } } } @@ -103,7 +107,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); - cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + if (avatarRes) { + cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + } } } @@ -112,7 +118,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector(), QVector() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); - cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); + if (hudRes) { + cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); + } } } diff --git a/libraries/pointers/src/pointers/PickManager.cpp b/libraries/pointers/src/pointers/PickManager.cpp index 571f9f04cd..385115d732 100644 --- a/libraries/pointers/src/pointers/PickManager.cpp +++ b/libraries/pointers/src/pointers/PickManager.cpp @@ -40,12 +40,12 @@ void PickManager::removePick(const QUuid& uid) { }); } -QVariantMap PickManager::getPrevPickResult(const QUuid& uid) const { +PickResultPointer PickManager::getPrevPickResult(const QUuid& uid) const { auto pick = findPick(uid); - if (pick && pick->getPrevPickResult()) { - return pick->getPrevPickResult()->toVariantMap(); + if (pick) { + return pick->getPrevPickResult(); } - return QVariantMap(); + return PickResultPointer(); } void PickManager::enablePick(const QUuid& uid) const { @@ -91,4 +91,5 @@ void PickManager::update() { bool shouldPickHUD = _shouldPickHUDOperator(); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD); + _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false); } \ No newline at end of file diff --git a/libraries/pointers/src/pointers/PickManager.h b/libraries/pointers/src/pointers/PickManager.h index b8abb077c7..5c3ed6ef25 100644 --- a/libraries/pointers/src/pointers/PickManager.h +++ b/libraries/pointers/src/pointers/PickManager.h @@ -27,7 +27,12 @@ public: void enablePick(const QUuid& uid) const; void disablePick(const QUuid& uid) const; - QVariantMap getPrevPickResult(const QUuid& uid) const; + PickResultPointer getPrevPickResult(const QUuid& uid) const; + + template + std::shared_ptr getPrevPickResultTyped(const QUuid& uid) const { + return std::static_pointer_cast(getPrevPickResult(uid)); + } void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const; void setIgnoreItems(const QUuid& uid, const QVector& ignore) const; @@ -43,6 +48,7 @@ protected: QHash _typeMap; PickCacheOptimizer _rayPickCacheOptimizer; + PickCacheOptimizer _stylusPickCacheOptimizer; }; #endif // hifi_PickManager_h \ No newline at end of file diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 8796b1e47d..8551f29dd9 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -30,7 +30,7 @@ void Pointer::disable() { DependencyManager::get()->disablePick(_pickUID); } -const QVariantMap Pointer::getPrevPickResult() { +PickResultPointer Pointer::getPrevPickResult() { return DependencyManager::get()->getPrevPickResult(_pickUID); } @@ -46,16 +46,16 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } -void Pointer::update() { +void Pointer::update(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([&] { - QVariantMap pickResult = getPrevPickResult(); + auto pickResult = getPrevPickResult(); updateVisuals(pickResult); generatePointerEvents(pickResult); }); } -void Pointer::generatePointerEvents(const QVariantMap& pickResult) { +void Pointer::generatePointerEvents(const PickResultPointer& pickResult) { // TODO: avatars/HUD? auto pointerManager = DependencyManager::get(); @@ -176,4 +176,4 @@ PointerEvent::Button Pointer::chooseButton(const std::string& button) { } 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 ca35c38c7a..fa09442c83 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -16,6 +16,7 @@ #include #include +#include "Pick.h" #include #include "PointerEvent.h" @@ -44,7 +45,7 @@ public: virtual void enable(); virtual void disable(); - virtual const QVariantMap getPrevPickResult(); + virtual PickResultPointer getPrevPickResult(); 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; @@ -57,13 +58,12 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} - void update(); - virtual void updateVisuals(const QVariantMap& pickResult) = 0; - void generatePointerEvents(const QVariantMap& pickResult); + virtual void update(float deltaTime); + virtual void updateVisuals(const PickResultPointer& pickResult) {} + void generatePointerEvents(const PickResultPointer& pickResult); struct PickedObject { - PickedObject() {} - PickedObject(const QUuid& objectID, IntersectionType type) : objectID(objectID), type(type) {} + PickedObject(const QUuid& objectID = QUuid(), IntersectionType type = IntersectionType::NONE) : objectID(objectID), type(type) {} QUuid objectID; IntersectionType type; @@ -71,7 +71,7 @@ public: using Buttons = std::unordered_set; - virtual PickedObject getHoveredObject(const QVariantMap& pickResult) = 0; + virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0; virtual Buttons getPressedButtons() = 0; QUuid getRayUID() { return _pickUID; } @@ -81,7 +81,7 @@ protected: bool _enabled; bool _hover; - virtual PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const = 0; + virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const = 0; private: PickedObject _prevHoveredObject; diff --git a/libraries/pointers/src/pointers/PointerManager.cpp b/libraries/pointers/src/pointers/PointerManager.cpp index 2d41543b6b..cd1273900d 100644 --- a/libraries/pointers/src/pointers/PointerManager.cpp +++ b/libraries/pointers/src/pointers/PointerManager.cpp @@ -61,21 +61,22 @@ void PointerManager::editRenderState(const QUuid& uid, const std::string& state, } } -const QVariantMap PointerManager::getPrevPickResult(const QUuid& uid) const { +PickResultPointer PointerManager::getPrevPickResult(const QUuid& uid) const { + PickResultPointer result; auto pointer = find(uid); if (pointer) { - return pointer->getPrevPickResult(); + result = pointer->getPrevPickResult(); } - return QVariantMap(); + return result; } -void PointerManager::update() { +void PointerManager::update(float deltaTime) { auto cachedPointers = resultWithReadLock>>([&] { return _pointers.values(); }); for (const auto& pointer : cachedPointers) { - pointer->update(); + pointer->update(deltaTime); } } diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index 9f477d9eb2..a9c0a47b17 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -29,7 +29,7 @@ public: void disablePointer(const QUuid& uid) const; void setRenderState(const QUuid& uid, const std::string& renderState) const; 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; + PickResultPointer getPrevPickResult(const QUuid& uid) const; void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const; void setIgnoreItems(const QUuid& uid, const QVector& ignoreEntities) const; @@ -38,7 +38,7 @@ public: void setLength(const QUuid& uid, float length) const; void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const; - void update(); + void update(float deltaTime); private: std::shared_ptr find(const QUuid& uid) const; diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index dba2db0458..8e3cebfbf2 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -69,6 +69,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif + forceDownload = true; QUrl unnormalizedURL(scriptOrURL); QUrl url = DependencyManager::get()->normalizeURL(unnormalizedURL); diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index 074e5ab79b..73e37c7509 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -37,9 +37,9 @@ public: PointerEvent(); 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 = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); + const glm::vec2& pos2D = glm::vec2(), const glm::vec3& pos3D = glm::vec3(), + const glm::vec3& normal = glm::vec3(), const glm::vec3& direction = glm::vec3(), + Button button = NoButtons, 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); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a716c9231e..c4ac7aba14 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -21,6 +21,7 @@ #include "AACube.h" #include "SharedUtil.h" +#include "shared/Bilateral.h" class QColor; class QUrl; @@ -152,17 +153,73 @@ public: return pickRay; } }; + +struct StylusTip : public MathPick { + 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; } + QVariantMap toVariantMap() const override { + QVariantMap pickRay; + pickRay["position"] = vec3toVariant(position); + pickRay["orientation"] = quatToVariant(orientation); + pickRay["velocity"] = vec3toVariant(velocity); + return pickRay; + } +}; + + namespace std { + inline void hash_combine(std::size_t& seed) { } + + template + inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest...); + } + template <> - struct hash { - size_t operator()(const glm::vec3& a) const { - return ((hash()(a.x) ^ (hash()(a.y) << 1)) >> 1) ^ (hash()(a.z) << 1); + struct hash { + size_t operator()(const bilateral::Side& a) const { + return std::hash()((int)a); } }; - template <> + + template <> + struct hash { + size_t operator()(const glm::vec3& a) const { + size_t result = 0; + hash_combine(result, a.x, a.y, a.z); + return result; + } + }; + + template <> + struct hash { + size_t operator()(const glm::quat& a) const { + size_t result = 0; + hash_combine(result, a.x, a.y, a.z, a.w); + return result; + } + }; + + template <> struct hash { size_t operator()(const PickRay& a) const { - return (hash()(a.origin) ^ (hash()(a.direction) << 1)); + size_t result = 0; + hash_combine(result, a.origin, a.direction); + return result; + } + }; + + template <> + struct hash { + size_t operator()(const StylusTip& a) const { + size_t result = 0; + hash_combine(result, a.side, a.position, a.orientation, a.velocity); + return result; } }; } diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h index edcaa49540..1ecc7f3d48 100644 --- a/libraries/shared/src/shared/Bilateral.h +++ b/libraries/shared/src/shared/Bilateral.h @@ -11,7 +11,8 @@ namespace bilateral { enum class Side { Left = 0, - Right = 1 + Right = 1, + Invalid = -1 }; using Indices = Side; @@ -27,8 +28,10 @@ namespace bilateral { return 0x01; case Side::Right: return 0x02; + default: + break; } - return std::numeric_limits::max(); + return 0x00; } inline uint8_t index(Side side) { @@ -37,10 +40,24 @@ namespace bilateral { return 0; case Side::Right: return 1; + default: + break; } return std::numeric_limits::max(); } + inline Side side(int index) { + switch (index) { + case 0: + return Side::Left; + case 1: + return Side::Right; + default: + break; + } + return Side::Invalid; + } + template void for_each_side(F f) { f(Side::Left); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 1243ed28e2..61e520af21 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" ]; diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 46b630d023..dc41a5453e 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -16,256 +16,16 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { - var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js"); - // triggered when stylus presses a web overlay/entity - var HAPTIC_STYLUS_STRENGTH = 1.0; - var HAPTIC_STYLUS_DURATION = 20.0; - - var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; - var WEB_STYLUS_LENGTH = 0.2; - var WEB_TOUCH_Y_OFFSET = 0.105; // how far forward (or back with a negative number) to slide stylus in hand - - function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - // check to see if the projected stylusTip is within within the 2d border - var borderMin = {x: -edgeBorder, y: -edgeBorder}; - var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; - if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && - stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && - stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { - return true; - } - } - return false; - } - - function calculateNearestStylusTarget(stylusTargets) { - var nearestStylusTarget; - - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && - stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && - stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { - nearestStylusTarget = stylusTarget; - } - } - - return nearestStylusTarget; - } - - function getFingerWorldLocation(hand) { - var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; - - var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); - var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); - var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); - var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); - var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); - - return { - position: worldFingerPosition, - orientation: worldFingerRotation, - rotation: worldFingerRotation, - valid: true - }; - } - - function distance2D(a, b) { - var dx = (a.x - b.x); - var dy = (a.y - b.y); - return Math.sqrt(dx * dx + dy * dy); - } - - function TabletStylusInput(hand) { + function TabletStylusInput(hand) { this.hand = hand; - this.previousStylusTouchingTarget = false; - this.stylusTouchingTarget = false; - - this.useFingerInsteadOfStylus = false; - this.fingerPointing = false; - - // initialize stylus tip - var DEFAULT_STYLUS_TIP = { - position: {x: 0, y: 0, z: 0}, - orientation: {x: 0, y: 0, z: 0, w: 0}, - rotation: {x: 0, y: 0, z: 0, w: 0}, - velocity: {x: 0, y: 0, z: 0}, - valid: false - }; - this.stylusTip = DEFAULT_STYLUS_TIP; - this.parameters = makeDispatcherModuleParameters( 100, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); - - this.getOtherHandController = function() { - return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput; - }; - - this.handToController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.updateFingerAsStylusSetting = function () { - var DEFAULT_USE_FINGER_AS_STYLUS = false; - var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); - if (USE_FINGER_AS_STYLUS === "") { - USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS; - } - if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) { - this.useFingerInsteadOfStylus = true; - } else { - this.useFingerInsteadOfStylus = false; - } - }; - - this.updateStylusTip = function() { - if (this.useFingerInsteadOfStylus) { - this.stylusTip = getFingerWorldLocation(this.hand); - } else { - this.stylusTip = getControllerWorldLocation(this.handToController(), true); - - // translate tip forward according to constant. - var TIP_OFFSET = Vec3.multiply(MyAvatar.sensorToWorldScale, {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0}); - this.stylusTip.position = Vec3.sum(this.stylusTip.position, - Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); - } - - // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. - var pose = Controller.getPoseValue(this.handToController()); - if (pose.valid) { - var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation)); - var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); - var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity); - var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel, - Vec3.subtract(this.stylusTip.position, worldControllerPos))); - this.stylusTip.velocity = tipVelocity; - } else { - this.stylusTip.velocity = {x: 0, y: 0, z: 0}; - } - }; - - this.showStylus = function() { - if (this.stylus) { - var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 }; - var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90); - var modelOrientationAngles = Quat.safeEulerAngles(modelOrientation); - - var rotation = Overlays.getProperty(this.stylus, "rotation"); - var rotationAngles = Quat.safeEulerAngles(rotation); - - if(!Vec3.withinEpsilon(modelOrientationAngles, rotationAngles, 1)) { - var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 }); - - var updatedStylusProperties = { - position: Vec3.sum(this.stylusTip.position, modelPositionOffset), - rotation: modelOrientation, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }), - }; - - Overlays.editOverlay(this.stylus, updatedStylusProperties); - } - - return; - } - - var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 }; - var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90); - var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 }); - - var stylusProperties = { - name: "stylus", - url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", - loadPriority: 10.0, - position: Vec3.sum(this.stylusTip.position, modelPositionOffset), - rotation: modelOrientation, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }), - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND") - }; - this.stylus = Overlays.addOverlay("model", stylusProperties); - }; - - this.hideStylus = function() { - if (!this.stylus) { - return; - } - Overlays.deleteOverlay(this.stylus); - this.stylus = null; - }; - - this.stealTouchFocus = function(stylusTarget) { - // send hover events to target - // record the entity or overlay we are hovering over. - if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || - (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { - this.getOtherHandController().relinquishTouchFocus(); - } - this.requestTouchFocus(stylusTarget); - }; - - this.requestTouchFocus = function(stylusTarget) { - - // send hover events to target if we can. - // record the entity or overlay we are hovering over. - if (stylusTarget.entityID && - stylusTarget.entityID !== this.hoverEntity && - stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { - this.hoverEntity = stylusTarget.entityID; - TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); - } else if (stylusTarget.overlayID && - stylusTarget.overlayID !== this.hoverOverlay && - stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { - this.hoverOverlay = stylusTarget.overlayID; - TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); - } - }; - - this.hasTouchFocus = function(stylusTarget) { - return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || - (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); - }; - - this.relinquishTouchFocus = function() { - // send hover leave event. - var pointerEvent = { type: "Move", id: this.hand + 1 }; - if (this.hoverEntity) { - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = null; - } else if (this.hoverOverlay) { - Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent); - Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = null; - } - }; - - this.pointFinger = function(value) { - var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; - if (this.fingerPointing !== value) { - var message; - if (this.hand === RIGHT_HAND) { - message = { pointRightIndex: value }; - } else { - message = { pointLeftIndex: value }; - } - Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); - this.fingerPointing = value; - } - }; + + this.pointer = Pointers.createPointer(PickType.Stylus, { hand: this.hand }); this.otherModuleNeedsToRun = function(controllerData) { var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; @@ -277,189 +37,6 @@ Script.include("/~/system/libraries/controllers.js"); return grabOverlayModuleReady.active || farGrabModuleReady.active; }; - this.processStylus = function(controllerData) { - this.updateStylusTip(); - - if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) { - this.pointFinger(false); - this.hideStylus(); - this.stylusTouchingTarget = false; - this.relinquishTouchFocus(); - return false; - } - - if (this.useFingerInsteadOfStylus) { - this.hideStylus(); - } - - // build list of stylus targets, near the stylusTip - var stylusTargets = []; - var candidateEntities = controllerData.nearbyEntityProperties; - var i, props, stylusTarget; - for (i = 0; i < candidateEntities.length; i++) { - props = candidateEntities[i]; - if (props && props.type === "Web") { - stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - } - - // add the tabletScreen, if it is valid - if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL && - Overlays.getProperty(HMD.tabletScreenID, "visible")) { - stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - - // add the tablet home button. - if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL && - Overlays.getProperty(HMD.homeButtonID, "visible")) { - stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - - var TABLET_MIN_HOVER_DISTANCE = 0.01; - var TABLET_MAX_HOVER_DISTANCE = 0.1; - var TABLET_MIN_TOUCH_DISTANCE = -0.05; - var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; - var EDGE_BORDER = 0.075; - - var hysteresisOffset = 0.0; - if (this.isNearStylusTarget) { - hysteresisOffset = 0.05; - } - - var sensorScaleFactor = MyAvatar.sensorToWorldScale; - this.isNearStylusTarget = isNearStylusTarget(stylusTargets, - (EDGE_BORDER + hysteresisOffset) * sensorScaleFactor, - (TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor, - (WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor); - - if (this.isNearStylusTarget) { - if (!this.useFingerInsteadOfStylus) { - this.showStylus(); - } else { - this.pointFinger(true); - } - } else { - this.hideStylus(); - this.pointFinger(false); - } - - var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets); - - var SCALED_TABLET_MIN_TOUCH_DISTANCE = TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor; - var SCALED_TABLET_MAX_TOUCH_DISTANCE = TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor; - var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor; - - if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE && - nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) { - - this.requestTouchFocus(nearestStylusTarget); - - if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) { - TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget); - } - - if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) { - TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget); - } - - // filter out presses when tip is moving away from tablet. - // ensure that stylus is within bounding box by checking normalizedPosition - if (nearestStylusTarget.valid && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE && - nearestStylusTarget.distance < SCALED_TABLET_MAX_TOUCH_DISTANCE && - Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 && - nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 && - nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) { - - this.stylusTarget = nearestStylusTarget; - this.stylusTouchingTarget = true; - } - } else { - this.relinquishTouchFocus(); - } - - this.homeButtonTouched = false; - - if (this.isNearStylusTarget) { - return true; - } else { - this.pointFinger(false); - this.hideStylus(); - return false; - } - }; - - this.stylusTouchingEnter = function () { - this.stealTouchFocus(this.stylusTarget); - TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.stylusTarget); - Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - - this.touchingEnterTimer = 0; - this.touchingEnterStylusTarget = this.stylusTarget; - this.deadspotExpired = false; - - var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481; - this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; - }; - - this.stylusTouchingExit = function () { - - if (this.stylusTarget === undefined) { - return; - } - - // special case to handle home button. - if (this.stylusTarget.overlayID === HMD.homeButtonID) { - Messages.sendLocalMessage("home", this.stylusTarget.overlayID); - } - - // send press event - if (this.deadspotExpired) { - TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget); - } else { - TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.touchingEnterStylusTarget); - } - }; - - this.stylusTouching = function (controllerData, dt) { - - this.touchingEnterTimer += dt; - - if (this.stylusTarget.entityID) { - this.stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); - } else if (this.stylusTarget.overlayID) { - this.stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); - } - - var TABLET_MIN_TOUCH_DISTANCE = -0.1; - var TABLET_MAX_TOUCH_DISTANCE = 0.01; - - if (this.stylusTarget) { - if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && - this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { - var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - distance2D(this.stylusTarget.position2D, - this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { - TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget); - this.deadspotExpired = true; - } - } else { - this.stylusTouchingTarget = false; - } - } else { - this.stylusTouchingTarget = false; - } - }; - this.overlayLaserActive = function(controllerData) { var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput"); var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput"); @@ -469,7 +46,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - if (this.processStylus(controllerData)) { + if (!this.overlayLaserActive(controllerData) && !this.otherModuleNeedsToRun(controllerData)) { return makeRunningValues(true, [], []); } else { return makeRunningValues(false, [], []); @@ -477,28 +54,11 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData, deltaTime) { - this.updateFingerAsStylusSetting(); - - if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { - this.stylusTouchingEnter(); - } - if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { - this.stylusTouchingExit(); - } - this.previousStylusTouchingTarget = this.stylusTouchingTarget; - - if (this.stylusTouchingTarget) { - this.stylusTouching(controllerData, deltaTime); - } - if (this.processStylus(controllerData)) { - return makeRunningValues(true, [], []); - } else { - return makeRunningValues(false, [], []); - } + return this.isReady(controllerData); }; - + this.cleanup = function () { - this.hideStylus(); + Pointers.createPointer(this.pointer); }; } From babaef8399012346342f2fb5651d7ac7a1aa223c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 3 Nov 2017 05:59:59 -0700 Subject: [PATCH 2/5] Fix the audio input device peak meters --- interface/resources/qml/hifi/audio/Audio.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 12c2ec1835..87ddce49ca 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -213,8 +213,8 @@ Rectangle { anchors.right: parent.right peak: model.peak; anchors.verticalCenter: parent.verticalCenter - visible: (bar.currentIndex === 1 && selectedHMD && isVR) || - (bar.currentIndex === 0 && selectedDesktop && !isVR) && + visible: ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) && Audio.devices.input.peakValuesAvailable; } } From c496429a85cad2616f6c9f696ccbae19dbad3d25 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 3 Nov 2017 10:40:25 -0700 Subject: [PATCH 3/5] Add Machine Fingerprint to receive_at endpoint txn' --- interface/src/commerce/Ledger.cpp | 10 ++++++++-- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/Wallet.cpp | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 904847cb5f..f607c923ee 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -90,7 +90,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); } -bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; @@ -99,7 +99,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { return false; // We know right away that we will fail, so tell the caller. } - signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + QJsonObject transaction; + transaction["hfc_key"] = hfc_key; + transaction["machine_fingerprint"] = machine_fingerprint; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + + signedSend("transaction", transactionString, old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 42eb0ffc49..54f3f780f3 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); - bool receiveAt(const QString& hfc_key, const QString& old_key); + bool receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint); void balance(const QStringList& keys); void inventory(const QStringList& keys); void history(const QStringList& keys); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 85632ff8f1..9bc8dc9e43 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -16,6 +16,7 @@ #include "ui/ImageProvider.h" #include "scripting/HMDScriptingInterface.h" +#include #include #include #include @@ -541,7 +542,8 @@ bool Wallet::generateKeyPair() { // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key, oldKey); + QString machineFingerprint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + return ledger->receiveAt(key, oldKey, machineFingerprint); } QStringList Wallet::listPublicKeys() { From 6ca9cc02399f30d1034f7d945b47992573a2e99c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 3 Nov 2017 18:40:55 -0700 Subject: [PATCH 4/5] Update WASAPI audio plugin --- cmake/externals/wasapi/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 4437024962..4c0ffaf88f 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -6,8 +6,8 @@ if (WIN32) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip - URL_MD5 94f4765bdbcd53cd099f349ae031e769 + URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip + URL_MD5 4f40e49715a420fb67b45b9cee19052c CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From d4b4d188ed28bdf6875dd66ef34598aa0ad2005b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 4 Nov 2017 13:40:41 -0700 Subject: [PATCH 5/5] allow splitting of edit or add entity packets across multiple edit packets when property list is larger than MTU --- .../entities/src/EntityEditPacketSender.cpp | 42 ++++++++++++------- libraries/entities/src/EntityItem.cpp | 4 +- .../entities/src/EntityItemProperties.cpp | 34 ++++++--------- libraries/entities/src/EntityItemProperties.h | 4 +- libraries/entities/src/EntityPropertyFlags.h | 29 +++++++------ libraries/octree/src/OctreePacketData.cpp | 12 +++++- libraries/octree/src/OctreePacketData.h | 1 + tests/octree/src/OctreeTests.cpp | 2 +- 8 files changed, 71 insertions(+), 57 deletions(-) diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index e82ed82093..168b0cd446 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - bool success; + OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send auto nodeList = DependencyManager::get(); + + EntityPropertyFlags didntFitProperties; + EntityItemProperties propertiesCopy = properties; + if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) { - EntityItemProperties propertiesCopy = properties; const QUuid myNodeID = nodeList->getSessionUUID(); propertiesCopy.setParentID(myNodeID); - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut); - } else { - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut); } - if (success) { - #ifdef WANT_DEBUG - qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << entityItemID; - qCDebug(entities) << " properties:" << properties; - #endif - queueOctreeEditMessage(type, bufferOut); - if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { - emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties(); + + while (encodeResult == OctreeElement::PARTIAL) { + encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties); + + if (encodeResult != OctreeElement::NONE) { + #ifdef WANT_DEBUG + qCDebug(entities) << "calling queueOctreeEditMessage()..."; + qCDebug(entities) << " id:" << entityItemID; + qCDebug(entities) << " properties:" << properties; + #endif + queueOctreeEditMessage(type, bufferOut); + if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { + emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + } } + + // if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit + if (encodeResult != OctreeElement::COMPLETED) { + type = PacketType::EntityEdit; + requestedProperties = didntFitProperties; + } + + bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index f6f4e48a73..3f054e1ccb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -83,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ACCELERATION; - requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete + requestedProperties += PROP_DIMENSIONS; requestedProperties += PROP_DENSITY; requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; @@ -241,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f774b208c4..108fc14e30 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1221,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue // // TODO: Implement support for script and visible properties. // -bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer) { +OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) { + OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro @@ -1264,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); - EntityPropertyFlags requestedProperties = properties.getChangedProperties(); EntityPropertyFlags propertiesDidntFit = requestedProperties; - // TODO: we need to handle the multi-pass form of this, similar to how we handle entity data - // - // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, - // then our modelTreeElementExtraEncodeData should include data about which properties we need to append. - //if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) { - // requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID()); - //} - LevelDetails entityLevel = packetData->startLevel(); // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this @@ -1302,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && successLastEditedFits - && successLastUpdatedFits && successPropertyFlagsFits; + && successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -1316,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); @@ -1472,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem properties.getType() == EntityTypes::Sphere) { APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -1522,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { - // TODO: handle mechanism for handling partial fitting data! - // add this item into our list for the next appendElementData() pass - //modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit); - - // for now, if it's not complete, it's not successful - success = false; + didntFitProperties = propertiesDidntFit; } } @@ -1543,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; success = false; + appendState = OctreeElement::NONE; // if we got here, then we didn't include the item + // maybe we should assert!!! } } else { packetData->discardSubTree(); } - return success; + + + return appendState; } QByteArray EntityItemProperties::getPackedNormals() const { @@ -1673,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index a8bb063934..732dbdf69f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -262,8 +262,8 @@ public: float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } - static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer); + static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties); static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index f0f22b0091..35d40b669a 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -21,8 +21,7 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, PROP_POSITION, - PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams - PROP_DIMENSIONS = PROP_RADIUS, + PROP_DIMENSIONS, PROP_ROTATION, PROP_DENSITY, PROP_VELOCITY, @@ -47,13 +46,13 @@ enum EntityPropertyList { PROP_ANGULAR_VELOCITY, PROP_ANGULAR_DAMPING, PROP_COLLISIONLESS, - PROP_DYNAMIC, + PROP_DYNAMIC, // 24 // property used by Light entity PROP_IS_SPOTLIGHT, PROP_DIFFUSE_COLOR, - PROP_AMBIENT_COLOR_UNUSED, - PROP_SPECULAR_COLOR_UNUSED, + PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION PROP_LINEAR_ATTENUATION_UNUSED, PROP_QUADRATIC_ATTENUATION_UNUSED, @@ -61,30 +60,30 @@ enum EntityPropertyList { PROP_CUTOFF, // available to all entities - PROP_LOCKED, + PROP_LOCKED, // 34 PROP_TEXTURES, // used by Model entities - PROP_ANIMATION_SETTINGS, // used by Model entities - PROP_USER_DATA, // all entities + PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_USER_DATA, // all entities -- 37 PROP_SHAPE_TYPE, // used by Model + zones entities // used by ParticleEffect entities - PROP_MAX_PARTICLES, - PROP_LIFESPAN, + PROP_MAX_PARTICLES, // 39 + PROP_LIFESPAN, // 40 -- used by all entities PROP_EMIT_RATE, PROP_EMIT_SPEED, PROP_EMIT_STRENGTH, - PROP_EMIT_ACCELERATION, - PROP_PARTICLE_RADIUS, + PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed???? + PROP_PARTICLE_RADIUS, // 45!! PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_MARKETPLACE_ID, // all entities PROP_ACCELERATION, // all entities PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID - PROP_NAME, // all entities + PROP_NAME, // all entities -- 50 PROP_COLLISION_SOUND_URL, PROP_RESTITUTION, - PROP_FRICTION, + PROP_FRICTION, // 53 PROP_VOXEL_VOLUME_SIZE, PROP_VOXEL_DATA, @@ -96,7 +95,7 @@ enum EntityPropertyList { // used by hyperlinks PROP_HREF, - PROP_DESCRIPTION, + PROP_DESCRIPTION, // 61 PROP_FACE_CAMERA, PROP_SCRIPT_TIMESTAMP, diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 493dfdcff5..b5b4a161ef 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) { _dirty = true; } - const bool wantDebug = false; - if (wantDebug && !success) { + #ifdef WANT_DEBUG + if (!success) { qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING...."; qCDebug(octree) << " length=" << length; qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; @@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) { qCDebug(octree) << " _targetSize=" << _targetSize; qCDebug(octree) << " _bytesReserved=" << _bytesReserved; } + #endif return success; } @@ -647,6 +648,13 @@ void OctreePacketData::debugContent() { printf("\n"); } +void OctreePacketData::debugBytes() { + qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; + qCDebug(octree) << " _bytesInUse=" << _bytesInUse; + qCDebug(octree) << " _targetSize=" << _targetSize; + qCDebug(octree) << " _bytesReserved=" << _bytesReserved; +} + int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index ed6a49941b..37c171504b 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -240,6 +240,7 @@ public: /// displays contents for debugging void debugContent(); + void debugBytes(); static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index 64d68e8e13..81300a1293 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() { EntityPropertyFlags props; props.setHasProperty(PROP_VISIBLE); props.setHasProperty(PROP_POSITION); - props.setHasProperty(PROP_RADIUS); + props.setHasProperty(PROP_DIMENSIONS); props.setHasProperty(PROP_MODEL_URL); props.setHasProperty(PROP_COMPOUND_SHAPE_URL); props.setHasProperty(PROP_ROTATION);