From 3064647d05e8cdcb6da9ab524a3c6505fdfdc6a2 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 9 Jun 2024 20:00:44 -0700 Subject: [PATCH 1/3] wiggly lasers --- interface/src/Application.cpp | 2 +- interface/src/raypick/LaserPointer.cpp | 74 +++++++++++----- interface/src/raypick/LaserPointer.h | 21 +++-- interface/src/raypick/ParabolaPointer.cpp | 8 +- interface/src/raypick/PathPointer.cpp | 23 +++-- interface/src/raypick/PathPointer.h | 9 +- .../src/raypick/PickScriptingInterface.cpp | 10 ++- .../src/raypick/PointerScriptingInterface.cpp | 85 +++++++++---------- .../src/raypick/PointerScriptingInterface.h | 16 ++-- interface/src/raypick/RayPick.cpp | 35 +++++++- interface/src/raypick/RayPick.h | 9 +- interface/src/raypick/StylusPointer.cpp | 23 ----- libraries/shared/src/RegisteredMetaTypes.h | 18 ++-- .../controllers/controllerDispatcher.js | 13 ++- scripts/system/libraries/pointersUtils.js | 3 + 15 files changed, 205 insertions(+), 144 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8d99e41668..cfd9ff22ed 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2480,7 +2480,7 @@ void Application::initialize(const QCommandLineParser &parser) { // Setup the mouse ray pick and related operators { - auto mouseRayPick = std::make_shared(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, true); + auto mouseRayPick = std::make_shared(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, 0.0f, true); mouseRayPick->parentTransform = std::make_shared(); mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE); auto mouseRayPickID = DependencyManager::get()->addPick(PickQuery::Ray, mouseRayPick); diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 91af721ea6..7c64b6a524 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -33,14 +33,19 @@ PickQuery::PickType LaserPointer::getType() const { } void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { - //V8TODO pathProps are not a thing anymore auto renderState = std::static_pointer_cast(_renderStates[state]); if (renderState) { updateRenderState(renderState->getPathID(), pathProps); - QVariant lineWidth = pathProps.toMap()["lineWidth"]; + QVariantMap pathPropsMap = pathProps.toMap(); + QVariant lineWidth = pathPropsMap["lineWidth"]; if (lineWidth.isValid()) { renderState->setLineWidth(lineWidth.toFloat()); } + + if (pathPropsMap.contains("linePoints")) { + QVariantList linePoints = pathPropsMap["linePoints"].toList(); + renderState->setNumPoints(linePoints.length()); + } } } @@ -133,18 +138,18 @@ LaserPointer::RenderState::RenderState(const QUuid& startID, const QUuid& pathID StartEndRenderState(startID, endID), _pathID(pathID) { if (!getPathID().isNull()) { - auto entityScriptingInterface = DependencyManager::get(); - { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_IGNORE_PICK_INTERSECTION; - _pathIgnorePicks = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties, false).getIgnorePickIntersection(); - } - { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_STROKE_WIDTHS; - auto widths = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties, false).getStrokeWidths(); - _lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0]; - } + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_IGNORE_PICK_INTERSECTION; + desiredProperties += PROP_LINE_POINTS; + desiredProperties += PROP_STROKE_WIDTHS; + auto properties = DependencyManager::get()->getEntityPropertiesInternal(getPathID(), desiredProperties, false); + + auto widths = properties.getStrokeWidths(); + _lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0]; + + setNumPoints(properties.getLinePoints().length()); + + _pathIgnorePicks = properties.getIgnorePickIntersection(); } } @@ -171,28 +176,51 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& if (!getPathID().isNull()) { EntityItemProperties properties; QVector points; + const size_t numPoints = getNumPoints(); points.append(glm::vec3(0.0f)); - points.append(end - origin); + const glm::vec3 endPoint = end - origin; + if (numPoints > 2) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_VISIBLE; + auto oldProperties = DependencyManager::get()->getEntityPropertiesInternal(getPathID(), desiredProperties, false); + + bool hasUnmodifiedEndPoint = false; + glm::vec3 unmodifiedEndPoint; + auto rayPickResult = std::static_pointer_cast(pickResult); + if (rayPickResult && rayPickResult->pickVariant.contains("unmodifiedDirection")) { + unmodifiedEndPoint = glm::length(endPoint) * vec3FromVariant(rayPickResult->pickVariant["unmodifiedDirection"]); + hasUnmodifiedEndPoint = true; + } + + // Segment points are evenly spaced between origin and end + for (size_t i = 1; i < numPoints - 1; i++) { + const float frac = ((float)i / (numPoints - 1)); + if (!oldProperties.getVisible() || !_hasSetLinePoints || !hasUnmodifiedEndPoint) { + points.append(frac * endPoint); + } else { + points.append(frac * mix(unmodifiedEndPoint, endPoint, frac)); + } + } + _hasSetLinePoints = true; + } + points.append(endPoint); properties.setPosition(origin); - properties.setRotation(glm::quat(1.0f, 0.0f ,0.0f ,0.0f)); + properties.setRotation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)); properties.setLinePoints(points); properties.setVisible(true); properties.setIgnorePickIntersection(doesPathIgnorePicks()); QVector normals; - normals.append(glm::vec3(0.0f, 0.0f, 1.0f)); - normals.append(glm::vec3(0.0f, 0.0f, 1.0f)); + normals.fill(glm::vec3(0.0f, 0.0f, 1.0f), (int)numPoints); properties.setNormals(normals); QVector widths; float width = getLineWidth() * parentScale; - widths.append(width); - widths.append(width); + widths.fill(width, (int)numPoints); properties.setStrokeWidths(widths); DependencyManager::get()->editEntity(getPathID(), properties); } } std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap, const QList &entityProperties) { - // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers QUuid startID; if (propMap["startPropertyIndex"].isValid()) { int startPropertyIndex = propMap["startPropertyIndex"].toInt(); @@ -205,11 +233,11 @@ std::shared_ptr LaserPointer::buildRenderState(const QVaria QUuid pathID; if (propMap["pathPropertyIndex"].isValid()) { - // laser paths must be PolyLine int pathPropertyIndex = propMap["pathPropertyIndex"].toInt(); if (pathPropertyIndex >= 0 && pathPropertyIndex < entityProperties.length()) { - //pathMap["type"].toString() == "PolyLine" EntityItemProperties pathProperties(entityProperties[pathPropertyIndex]); + // laser paths must be PolyLine + pathProperties.setType(EntityTypes::EntityType::PolyLine); pathProperties.getGrab().setGrabbable(false); pathID = DependencyManager::get()->addEntityInternal(pathProperties, entity::HostType::LOCAL); } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 74f0fd88d3..c826a548e9 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -13,7 +13,7 @@ #include "PathPointer.h" -#include +#include class LaserPointer : public PathPointer { using Parent = PathPointer; @@ -24,21 +24,29 @@ public: RenderState(const QUuid& startID, const QUuid& pathID, const QUuid& endID); const QUuid& getPathID() const { return _pathID; } - const bool& doesPathIgnorePicks() const { return _pathIgnorePicks; } void setLineWidth(float width) { _lineWidth = width; } float getLineWidth() const { return _lineWidth; } + void setNumPoints(size_t numPoints) { _numPoints = std::max(numPoints, (size_t)2); } + size_t getNumPoints() const { return _numPoints; } + + const bool& doesPathIgnorePicks() const { return _pathIgnorePicks; } + void cleanup() override; void disable() override; - void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, + bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, + const PickResultPointer& pickResult) override; private: QUuid _pathID; - bool _pathIgnorePicks; - float _lineWidth; + float _lineWidth { 0.0f }; + size_t _numPoints { 0 }; + bool _pathIgnorePicks { false }; + + bool _hasSetLinePoints { false }; }; LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, @@ -67,7 +75,6 @@ protected: private: static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); - }; #endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 5f045c5504..a047519373 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -207,9 +207,11 @@ void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float al } } -void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { - StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); +void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, + bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, + const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, + distance, pickResult); auto parabolaPickResult = std::static_pointer_cast(pickResult); if (parabolaPickResult && render::Item::isValidID(_pathID)) { render::Transaction transaction; diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 9333e0f03b..64488b200e 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -144,13 +144,11 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { auto renderState = _renderStates.find(_currentRenderState); auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); float parentScale = 1.0f; - //if (_scaleWithParent) { if (_enabled && _scaleWithParent) { glm::vec3 dimensions = DependencyManager::get()->getParentTransform(_pickUID).getScale(); parentScale = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z); } - //if (!_currentRenderState.empty() && renderState != _renderStates.end() && if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() && (type != IntersectionType::NONE || _pathLength > 0.0f)) { glm::vec3 origin = getPickOrigin(pickResult); @@ -162,14 +160,14 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { defaultRenderState->second.second->disable(); } } else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { - //} else if (!_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { if (renderState != _renderStates.end() && renderState->second->isEnabled()) { renderState->second->disable(); } glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first); defaultRenderState->second.second->update(origin, end, Vectors::UP, parentScale, _distanceScaleEnd, _centerEndY, - _faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, pickResult); + _faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, + pickResult); } else if (!_currentRenderState.empty()) { if (renderState != _renderStates.end() && renderState->second->isEnabled()) { renderState->second->disable(); @@ -205,12 +203,12 @@ void PathPointer::editRenderState(const std::string& state, const QVariant& star } void PathPointer::updateRenderState(const QUuid& id, const QVariant& props) { - // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); - } + // FIXME: updating render state props no longer works since removing 3D Overlays + //if (!id.isNull() && props.isValid()) { + // QVariantMap propMap = props.toMap(); + // propMap.remove("visible"); + // qApp->getOverlays().editOverlay(id, propMap); + //} } Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) { @@ -300,8 +298,9 @@ void StartEndRenderState::disable() { _enabled = false; } -void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, + bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, + const PickResultPointer& pickResult) { auto entityScriptingInterface = DependencyManager::get(); if (!getStartID().isNull()) { EntityItemProperties properties; diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index 4b0625ef0f..0a66133aa3 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -43,16 +43,17 @@ public: virtual void cleanup(); virtual void disable(); - virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult); + virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, + bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, + const PickResultPointer& pickResult); bool isEnabled() const { return _enabled; } protected: QUuid _startID; QUuid _endID; - bool _startIgnorePicks; - bool _endIgnorePicks; + bool _startIgnorePicks { false }; + bool _endIgnorePicks { false }; glm::vec3 _startDim; glm::vec3 _endDim; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 71a13b096a..5538252d57 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -121,6 +121,7 @@ PickFilter getPickFilter(unsigned int filter) { * @property {Vec3} [dirOffset] - Synonym for direction. * @property {Quat} [orientation] - Alternative property for specifying direction. The value is applied to the * default direction value. + * @property {number} [delay=0.0] - The delay, in seconds, to apply to the ray direction. * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or * {@link Picks.getPickScriptParameters}. A ray pick's type is {@link PickType.Ray}. * @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale @@ -173,7 +174,14 @@ std::shared_ptr PickScriptingInterface::buildRayPick(const QVariantMa direction = vec3FromVariant(propMap["dirOffset"]); } - auto rayPick = std::make_shared(position, direction, filter, maxDistance, enabled); + float delayHalf = 0.0f; + if (propMap["delay"].isValid()) { + // We want to be within 0.1% of the target in seconds + // https://twitter.com/FreyaHolmer/status/1757836988495847568 + delayHalf = -std::max(propMap["delay"].toFloat(), 0.0f) / log2(0.001); + } + + auto rayPick = std::make_shared(position, direction, filter, maxDistance, delayHalf, enabled); setParentTransform(rayPick, propMap); return rayPick; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 6ea3272572..1eec708c3b 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -25,7 +25,6 @@ #include STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ - qDebug() << "STATIC_SCRIPT_TYPES_INITIALIZER PointerScriptingInterface"; auto scriptEngine = manager->engine().get(); scriptRegisterMetaType(scriptEngine); scriptRegisterMetaType(scriptEngine); @@ -92,7 +91,6 @@ unsigned int PointerScriptingInterface::createParabolaPointer(ParabolaPointerPro return createPointerInternal(PickQuery::PickType::Parabola, properties); } - bool PointerScriptingInterface::isPointerEnabled(unsigned int uid) const { return DependencyManager::get()->isPointerEnabled(uid); } @@ -178,45 +176,45 @@ std::shared_ptr PointerScriptingInterface::buildStylus(const PointerPro * Properties that define the visual appearance of a ray pointer when the pointer is intersecting something. * @typedef {object} Pointers.RayPointerRenderState * @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state. - * @property {Overlays.OverlayProperties|Uuid} [start] + * @property {Entities.EntityProperties|Uuid} [start] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of - * an overlay to render at the start of the ray pointer. The type property must be specified.

- *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the ray; - * null if there is no overlay. + * an entity to render at the start of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the start of the ray; + * null if there is no entity. * - * @property {Overlays.OverlayProperties|Uuid} [path] + * @property {Entities.EntityProperties|Uuid} [path] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of - * the overlay rendered for the path of the ray pointer. The type property must be specified and be - * "line3d".

- *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered for the path of the ray; - * null if there is no overlay. + * the entity rendered for the path of the ray pointer. The type property must be specified and be + * "PolyLine".

Specify linePoints to control how many segments make up the path. + *

When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered for the path of the ray; + * null if there is no entity. * - * @property {Overlays.OverlayProperties|Uuid} [end] + * @property {Entities.EntityProperties|Uuid} [end] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of - * an overlay to render at the end of the ray pointer. The type property must be specified.

- *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the ray; - * null if there is no overlay. + * an entity to render at the end of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the end of the ray; + * null if there is no entity. */ /*@jsdoc * The properties of a ray pointer. These include the properties from the underlying ray pick that the pointer uses. * @typedef {object} Pointers.RayPointerProperties - * @property {boolean} [faceAvatar=false] - true if the overlay rendered at the end of the ray rotates about the + * @property {boolean} [faceAvatar=false] - true if the entity rendered at the end of the ray rotates about the * world y-axis to always face the avatar; false if it maintains its world orientation. - * @property {boolean} [centerEndY=true] - true if the overlay rendered at the end of the ray is centered on - * the ray end; false if the overlay is positioned against the surface if followNormal is + * @property {boolean} [centerEndY=true] - true if the entity rendered at the end of the ray is centered on + * the ray end; false if the entity is positioned against the surface if followNormal is * true, or above the ray end if followNormal is false. * @property {boolean} [lockEnd=false] - true if the end of the ray is locked to the center of the object at * which the ray is pointing; false if the end of the ray is at the intersected surface. - * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the overlay at the end of the ray + * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the entity at the end of the ray * scale linearly with distance; false if they aren't. * @property {boolean} [scaleWithParent=false] - true if the width of the ray's path and the size of the - * start and end overlays scale linearly with the pointer parent's scale; false if they don't scale. + * start and end entities scale linearly with the pointer parent's scale; false if they don't scale. * @property {boolean} [scaleWithAvatar=false] - A synonym for scalewithParent. *

Deprecated: This property is deprecated and will be removed.

- * @property {boolean} [followNormal=false] - true if the overlay rendered at the end of the ray rotates to + * @property {boolean} [followNormal=false] - true if the entity rendered at the end of the ray rotates to * follow the normal of the surface if one is intersected; false if it doesn't. - * @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow - * the normal of an intersected surface. If 0 or 1, the overlay rotation follows instantaneously; + * @property {number} [followNormalStrength=0.0] - How quickly the entity rendered at the end of the ray rotates to follow + * the normal of an intersected surface. If 0 or 1, the entity rotation follows instantaneously; * for other values, the larger the value the more quickly the rotation follows. * @property {Pointers.RayPointerRenderState[]|Object.} [renderStates] *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual @@ -236,7 +234,7 @@ std::shared_ptr PointerScriptingInterface::buildStylus(const PointerPro * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, * false if it doesn't. * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger - * events on the entity or overlay currently intersected. + * events on the object currently intersected. * @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or * {@link Pointers.getPointerScriptParameters}. A laser pointer's type is {@link PickType(0)|PickType.Ray}. * @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from @@ -378,41 +376,41 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const Poin * Properties that define the visual appearance of a parabola pointer when the pointer is intersecting something. * @typedef {object} Pointers.ParabolaPointerRenderState * @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state. - * @property {Overlays.OverlayProperties|Uuid} [start] + * @property {Entities.EntityProperties|Uuid} [start] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of - * an overlay to render at the start of the parabola pointer. The type property must be specified.

- *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the - * parabola; null if there is no overlay. + * an entity to render at the start of the parabola pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the start of the + * parabola; null if there is no entity. * @property {Pointers.ParabolaPointerPath|Uuid} [path] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of * the rendered path of the parabola pointer.

*

This property is not provided when getting using {@link Pointers.getPointerProperties}. - * @property {Overlays.OverlayProperties|Uuid} [end] + * @property {Entities.EntityProperties|Uuid} [end] *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of - * an overlay to render at the end of the ray pointer. The type property must be specified.

- *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the parabola; - * null if there is no overlay. + * an entity to render at the end of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the end of the parabola; + * null if there is no entity. */ /*@jsdoc * The properties of a parabola pointer. These include the properties from the underlying parabola pick that the pointer uses. * @typedef {object} Pointers.ParabolaPointerProperties - * @property {boolean} [faceAvatar=false] - true if the overlay rendered at the end of the ray rotates about the + * @property {boolean} [faceAvatar=false] - true if the entity rendered at the end of the ray rotates about the * world y-axis to always face the avatar; false if it maintains its world orientation. - * @property {boolean} [centerEndY=true] - true if the overlay rendered at the end of the ray is centered on - * the ray end; false if the overlay is positioned against the surface if followNormal is + * @property {boolean} [centerEndY=true] - true if the entity rendered at the end of the ray is centered on + * the ray end; false if the entity is positioned against the surface if followNormal is * true, or above the ray end if followNormal is false. * @property {boolean} [lockEnd=false] - true if the end of the ray is locked to the center of the object at * which the ray is pointing; false if the end of the ray is at the intersected surface. - * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the overlay at the end of the ray + * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the entity at the end of the ray * scale linearly with distance; false if they aren't. * @property {boolean} [scaleWithParent=false] - true if the width of the ray's path and the size of the - * start and end overlays scale linearly with the pointer parent's scale; false if they don't scale. + * start and end entities scale linearly with the pointer parent's scale; false if they don't scale. * @property {boolean} [scaleWithAvatar=false] - A synonym for scalewithParent. *

Deprecated: This property is deprecated and will be removed.

- * @property {boolean} [followNormal=false] - true if the overlay rendered at the end of the ray rotates to + * @property {boolean} [followNormal=false] - true if the entity rendered at the end of the ray rotates to * follow the normal of the surface if one is intersected; false if it doesn't. - * @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow - * the normal of an intersected surface. If 0 or 1, the overlay rotation follows instantaneously; + * @property {number} [followNormalStrength=0.0] - How quickly the entity rendered at the end of the ray rotates to follow + * the normal of an intersected surface. If 0 or 1, the entity rotation follows instantaneously; * for other values, the larger the value the more quickly the rotation follows. * @property {Pointers.ParabolaPointerRenderState[]|Object.} [renderStates] *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual @@ -432,7 +430,7 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const Poin * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, * false if it doesn't. * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger - * events on the entity or overlay currently intersected. + * events on the object currently intersected. * @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or * {@link Pointers.getPointerScriptParameters}. A parabola pointer's type is {@link PickType(0)|PickType.Parabola}. * @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from @@ -622,7 +620,6 @@ bool rayPointerPropertiesFromScriptValue(const ScriptValue& value, RayPointerPro out.properties[*renderStatesName].setValue(renderStates); } } - qDebug() << "rayPointerPropertiesFromScriptValue" << out.properties; return true; } @@ -675,7 +672,6 @@ bool stylusPointerPropertiesFromScriptValue(const ScriptValue& value, StylusPoin out.properties[*renderStatesName].setValue(renderStates); } } - qDebug() << "stylusPointerPropertiesFromScriptValue" << out.properties; return true; } @@ -714,7 +710,6 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola pathProperties.copyFromScriptValue(path, false); stateMap.insert("pathPropertyIndex", QVariant(out.entityProperties.length())); out.entityProperties.append(pathProperties); - qDebug() << "parabolaPointerPropertiesFromScriptValue : added path entity"; } if (stateMap["end"].isValid()) { @@ -723,7 +718,6 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola endProperties.copyFromScriptValue(end, false); stateMap.insert("endPropertyIndex", QVariant(out.entityProperties.length())); out.entityProperties.append(endProperties); - qDebug() << "parabolaPointerPropertiesFromScriptValue : added end entity"; } renderStates[i].setValue(stateMap); } @@ -731,6 +725,5 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola out.properties[*renderStatesName].setValue(renderStates); } } - qDebug() << "parabolaPointerPropertiesFromScriptValue" << out.properties; return true; } diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 033ef27488..080067e9ae 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -103,7 +103,7 @@ public: * ignorePickIntersection: true * }; * var intersectedPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 0, green: 255, blue: 0 }, * }; * var searchEnd = { @@ -114,7 +114,7 @@ public: * ignorePickIntersection: true * }; * var searchPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 255, green: 0, blue: 0 }, * }; * @@ -224,8 +224,8 @@ public: * @param {number} id - The ID of the pointer. * @param {string} renderState - The name of the render state to edit. * @param {Pointers.RayPointerRenderState|Pointers.ParabolaPointerRenderState} properties - The new properties for the - * render state. Only the overlay properties to change need be specified. - * @example Change the dimensions of a ray pointer's intersecting end overlay. + * render state. Only the properties to change need be specified. + * @example Change the dimensions of a ray pointer's intersecting end entity. * var intersectEnd = { * type: "sphere", * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, @@ -234,7 +234,7 @@ public: * ignorePickIntersection: true * }; * var intersectedPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 0, green: 255, blue: 0 }, * }; * var searchEnd = { @@ -245,7 +245,7 @@ public: * ignorePickIntersection: true * }; * var searchPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 255, green: 0, blue: 0 }, * }; * @@ -310,7 +310,7 @@ public: * ignorePickIntersection: true * }; * var intersectedPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 0, green: 255, blue: 0 }, * }; * var searchEnd = { @@ -321,7 +321,7 @@ public: * ignorePickIntersection: true * }; * var searchPath = { - * type: "line3d", + * type: "PolyLine", * color: { red: 255, green: 0, blue: 0 }, * }; * diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 4cb7232095..274e341ae8 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -19,10 +19,39 @@ PickRay RayPick::getMathematicalPick() const { return _mathPick; } + const bool hasDelay = _prevUpdate != 0 && _delayHalf > 0.0f && !isNaN(_prevDirection.x); + const float now = secTimestampNow(); + const float dt = now - _prevUpdate; + float alpha = 0.0f; + if (hasDelay) { + // This equation gives a framerate-independent lerp for a moving target + // https://twitter.com/FreyaHolmer/status/1757836988495847568 + alpha = 1 - exp2(-dt / _delayHalf); + } + Transform currentParentTransform = parentTransform->getTransform(); glm::vec3 origin = currentParentTransform.transform(_mathPick.origin); - glm::vec3 direction = glm::normalize(currentParentTransform.transformDirection(_mathPick.direction)); - return PickRay(origin, direction); + + glm::vec3 direction; + glm::vec3 newDirection = glm::normalize(currentParentTransform.transformDirection(_mathPick.direction)); + if (hasDelay) { + PickResultPointer result = getPrevPickResult(); + if (!result || !result->doesIntersect()) { + direction = glm::normalize(glm::mix(_prevDirection, newDirection, alpha)); + } else { + auto rayResult = std::static_pointer_cast(result); + glm::vec3 oldDirection = glm::normalize(rayResult->intersection - origin); + direction = glm::normalize(glm::mix(oldDirection, newDirection, alpha)); + } + } else { + direction = newDirection; + } + + _prevUpdate = now; + if (!isNaN(direction.x)) { + _prevDirection = direction; + } + return PickRay(origin, direction, newDirection); } PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { @@ -89,8 +118,6 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint()); } - - glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) { glm::quat invRot = glm::inverse(rotation); glm::vec3 localPos = invRot * (worldPos - position); diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index d5e604c4df..966bc1d11d 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -84,9 +84,8 @@ public: class RayPick : public Pick { public: - RayPick(glm::vec3 position, glm::vec3 direction, const PickFilter& filter, float maxDistance, bool enabled) : - Pick(PickRay(position, direction), filter, maxDistance, enabled) { - } + RayPick(glm::vec3 position, glm::vec3 direction, const PickFilter& filter, float maxDistance, float delayHalf, bool enabled) : + Pick(PickRay(position, direction), filter, maxDistance, enabled), _delayHalf(delayHalf) {} PickType getType() const override { return PickType::Ray; } @@ -107,6 +106,10 @@ public: private: static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration); + + float _delayHalf { 0.0f }; + mutable float _prevUpdate { 0.0f }; + mutable glm::vec3 _prevDirection { NAN }; }; #endif // hifi_RayPick_h diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 8a6097816e..19a48f3c48 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -48,29 +48,6 @@ PickQuery::PickType StylusPointer::getType() const { } QUuid StylusPointer::buildStylus(const QVariantMap& properties) { - // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers - /*QVariantMap propertiesMap; - - QString modelUrl = DEFAULT_STYLUS_MODEL_URL; - - if (properties["model"].isValid()) { - QVariantMap modelData = properties["model"].toMap(); - - if (modelData["url"].isValid()) { - modelUrl = modelData["url"].toString(); - } - } - // TODO: make these configurable per pointer - propertiesMap["name"] = "stylus"; - propertiesMap["url"] = modelUrl; - propertiesMap["loadPriority"] = 10.0f; - propertiesMap["solid"] = true; - propertiesMap["visible"] = false; - propertiesMap["ignorePickIntersection"] = true; - propertiesMap["drawInFront"] = false; - - return qApp->getOverlays().addOverlay("model", propertiesMap);*/ - EntityItemProperties entityProperties; QString modelURL = DEFAULT_STYLUS_MODEL_URL; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 96f88abf42..b3b742e1ba 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -205,25 +205,33 @@ public: * @typedef {object} PickRay * @property {Vec3} origin - The starting position of the ray. * @property {Vec3} direction - The direction that the ray travels. + * @property {Vec3} unmodifiedDirection - The direction that the ray would travel, if not for applied effects like delays. */ class PickRay : public MathPick { public: - PickRay() : origin(NAN), direction(NAN) { } - PickRay(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), direction(vec3FromVariant(pickVariant["direction"])) {} - PickRay(const glm::vec3& origin, const glm::vec3 direction) : origin(origin), direction(direction) {} + PickRay() : origin(NAN), direction(NAN), unmodifiedDirection(NAN) { } + PickRay(const QVariantMap& pickVariant) : + origin(vec3FromVariant(pickVariant["origin"])), direction(vec3FromVariant(pickVariant["direction"])), + unmodifiedDirection(vec3FromVariant(pickVariant["unmodifiedDirection"])) {} + PickRay(const glm::vec3& origin, const glm::vec3& direction) : + origin(origin), direction(direction), unmodifiedDirection(direction) {} + PickRay(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& unmodifiedDirection) : + origin(origin), direction(direction), unmodifiedDirection(unmodifiedDirection) {} glm::vec3 origin; glm::vec3 direction; + glm::vec3 unmodifiedDirection; operator bool() const override { - return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(direction))); + return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(direction)) || glm::any(glm::isnan(unmodifiedDirection))); } bool operator==(const PickRay& other) const { - return (origin == other.origin && direction == other.direction); + return (origin == other.origin && direction == other.direction && unmodifiedDirection == other.unmodifiedDirection); } QVariantMap toVariantMap() const override { QVariantMap pickRay; pickRay["origin"] = vec3toVariant(origin); pickRay["direction"] = vec3toVariant(direction); + pickRay["unmodifiedDirection"] = vec3toVariant(unmodifiedDirection); return pickRay; } }; diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index beee66221e..f098e5325a 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -577,6 +577,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Controller.enableMapping(MAPPING_NAME); + const POINTER_DELAY = 0.5; this.leftPointer = this.pointerManager.createPointer(false, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, @@ -585,7 +586,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); hover: true, scaleWithParent: true, distanceScaleEnd: true, - hand: LEFT_HAND + hand: LEFT_HAND, + delay: POINTER_DELAY }); Keyboard.setLeftHandLaser(this.leftPointer); this.rightPointer = this.pointerManager.createPointer(false, PickType.Ray, { @@ -596,7 +598,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); hover: true, scaleWithParent: true, distanceScaleEnd: true, - hand: RIGHT_HAND + hand: RIGHT_HAND, + delay: POINTER_DELAY }); Keyboard.setRightHandLaser(this.rightPointer); this.leftHudPointer = this.pointerManager.createPointer(true, PickType.Ray, { @@ -608,7 +611,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); hover: true, scaleWithParent: true, distanceScaleEnd: true, - hand: LEFT_HAND + hand: LEFT_HAND, + delay: POINTER_DELAY }); this.rightHudPointer = this.pointerManager.createPointer(true, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", @@ -619,7 +623,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); hover: true, scaleWithParent: true, distanceScaleEnd: true, - hand: RIGHT_HAND + hand: RIGHT_HAND, + delay: POINTER_DELAY }); this.mouseRayPointer = Pointers.createRayPointer({ diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 0633f9bf38..e854c2c3c4 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -36,6 +36,7 @@ var Pointer = function(hudLayer, pickType, pointerData) { faceCamera: true, ignorePickIntersection: true, // always ignore this renderLayer: this.renderLayer, + linePoints: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }; this.halfEnd = { type: "Sphere", @@ -57,6 +58,7 @@ var Pointer = function(hudLayer, pickType, pointerData) { faceCamera: true, ignorePickIntersection: true, // always ignore this renderLayer: this.renderLayer, + linePoints: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }; this.fullEnd = { type: "Sphere", @@ -78,6 +80,7 @@ var Pointer = function(hudLayer, pickType, pointerData) { faceCamera: true, ignorePickIntersection: true, // always ignore this renderLayer: this.renderLayer, + linePoints: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }; this.renderStates = [ From a5b32a64069f629dbc07a1dcfc5916895d5262cb Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 14 Jun 2024 20:15:24 -0700 Subject: [PATCH 2/3] allow configurable delay --- .../src/raypick/PickScriptingInterface.cpp | 13 ++++++++++ .../src/raypick/PickScriptingInterface.h | 21 ++++++++++++++++ .../src/raypick/PointerScriptingInterface.h | 8 +++++++ interface/src/raypick/RayPick.cpp | 16 +++++++++++-- interface/src/raypick/RayPick.h | 2 ++ interface/src/ui/PreferencesDialog.cpp | 24 ++++++++++++++----- libraries/pointers/src/Pick.h | 2 ++ libraries/pointers/src/PickManager.cpp | 7 ++++++ libraries/pointers/src/PickManager.h | 1 + libraries/pointers/src/Pointer.cpp | 4 ++++ libraries/pointers/src/Pointer.h | 1 + libraries/pointers/src/PointerManager.cpp | 7 ++++++ libraries/pointers/src/PointerManager.h | 1 + .../controllers/controllerDispatcher.js | 21 ++++++++++------ 14 files changed, 113 insertions(+), 15 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 5538252d57..5323c52faf 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -463,6 +463,10 @@ void PickScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } +void PickScriptingInterface::setDelay(unsigned int uid, float delay) { + DependencyManager::get()->setDelay(uid, delay); +} + bool PickScriptingInterface::isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } @@ -505,6 +509,15 @@ void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) { DependencyManager::get()->setPerFrameTimeBudget(numUsecs); } +float PickScriptingInterface::getHandLaserDelay() const { + return _handLaserDelaySetting.get(); +} + +void PickScriptingInterface::setHandLaserDelay(float delay) { + _handLaserDelaySetting.set(delay); + emit handLaserDelayChanged(delay); +} + void PickScriptingInterface::setParentTransform(std::shared_ptr pick, const QVariantMap& propMap) { QUuid parentUuid; int parentJointIndex = 0; diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index e6af97e662..00d5420f86 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -16,6 +16,7 @@ #include #include #include +#include class ScriptEngine; class ScriptValue; @@ -72,6 +73,7 @@ class ScriptValue; * @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD surface. Read-only. * * @property {number} perFrameTimeBudget - The maximum time, in microseconds, to spend per frame updating pick results. + * @property {number} handLaserDelay - The delay, in seconds, applied to the hand lasers to smooth their movement. */ class PickScriptingInterface : public QObject, public Dependency { @@ -105,6 +107,7 @@ class PickScriptingInterface : public QObject, public Dependency { Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ getIntersectedAvatar CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_HUD READ getIntersectedHud CONSTANT) Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget) + Q_PROPERTY(float handLaserDelay READ getHandLaserDelay WRITE setHandLaserDelay) SINGLETON_DEPENDENCY public: @@ -263,6 +266,15 @@ public: */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeItems); + /*@jsdoc + * Sets the delay of a Ray pick. + *

Note: Not used by other pick types.

+ * @function Picks.setDelay + * @param {number} id - The ID of the pointer. + * @param {number} delay - The desired delay in seconds. + */ + Q_INVOKABLE void setDelay(unsigned int uid, float delay); + /*@jsdoc * Checks if a pick is associated with the left hand: a ray or parabola pick with joint property set to * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with @@ -295,6 +307,9 @@ public: unsigned int getPerFrameTimeBudget() const; void setPerFrameTimeBudget(unsigned int numUsecs); + float getHandLaserDelay() const; + void setHandLaserDelay(float delay); + public slots: static constexpr unsigned int getPickBypassIgnore() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); } @@ -461,6 +476,9 @@ public slots: */ static constexpr unsigned int getIntersectedHud() { return IntersectionType::HUD; } +signals: + void handLaserDelayChanged(float delay); + protected: static std::shared_ptr buildRayPick(const QVariantMap& properties); static std::shared_ptr buildStylusPick(const QVariantMap& properties); @@ -468,6 +486,9 @@ protected: static std::shared_ptr buildParabolaPick(const QVariantMap& properties); static void setParentTransform(std::shared_ptr pick, const QVariantMap& propMap); + +private: + Setting::Handle _handLaserDelaySetting { "handLaserDelay", 0.0f }; }; #endif // hifi_PickScriptingInterface_h diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 080067e9ae..b45bd46dcf 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -423,6 +423,14 @@ public: */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } + /*@jsdoc + * Sets the delay of a Ray pointer. + *

Note: Not used by stylus or parabola pointers.

+ * @function Pointers.setDelay + * @param {number} id - The ID of the pointer. + * @param {number} delay - The desired delay in seconds. + */ + Q_INVOKABLE void setDelay(unsigned int uid, float delay) const { DependencyManager::get()->setDelay(uid, delay); } /*@jsdoc * Checks if a pointer is associated with the left hand: a ray or parabola pointer with joint property set to diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 274e341ae8..6237dcba90 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -19,14 +19,18 @@ PickRay RayPick::getMathematicalPick() const { return _mathPick; } - const bool hasDelay = _prevUpdate != 0 && _delayHalf > 0.0f && !isNaN(_prevDirection.x); + float delayHalf = 0.0f; + withReadLock([&] { + delayHalf = _delayHalf; + }); + const bool hasDelay = _prevUpdate != 0 && delayHalf > 0.0f && !isNaN(_prevDirection.x); const float now = secTimestampNow(); const float dt = now - _prevUpdate; float alpha = 0.0f; if (hasDelay) { // This equation gives a framerate-independent lerp for a moving target // https://twitter.com/FreyaHolmer/status/1757836988495847568 - alpha = 1 - exp2(-dt / _delayHalf); + alpha = 1 - exp2(-dt / delayHalf); } Transform currentParentTransform = parentTransform->getTransform(); @@ -94,6 +98,14 @@ PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) { return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick); } +void RayPick::setDelay(float delay) { + withWriteLock([&] { + // We want to be within 0.1% of the target in seconds + // https://twitter.com/FreyaHolmer/status/1757836988495847568 + _delayHalf = -std::max(delay, 0.0f) / log2(0.001); + }); +} + Transform RayPick::getResultTransform() const { PickResultPointer result = getPrevPickResult(); if (!result) { diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 966bc1d11d..c11f2ce835 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -97,6 +97,8 @@ public: PickResultPointer getHUDIntersection(const PickRay& pick) override; Transform getResultTransform() const override; + void setDelay(float delay) override; + // These are helper functions for projecting and intersecting rays static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction); static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index ce5a588776..f44b105e0f 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "scripting/RenderScriptingInterface.h" #include "Application.h" #include "DialogsManager.h" @@ -207,6 +208,23 @@ void setupPreferences() { preferences->addPreference(preference); } + { + auto getter = []() -> bool { return qApp->getPreferAvatarFingerOverStylus(); }; + auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); + } + + { + auto getter = []() -> float { return DependencyManager::get()->getHandLaserDelay(); }; + auto setter = [](float value) { DependencyManager::get()->setHandLaserDelay(value); }; + auto delaySlider = new SpinnerSliderPreference(UI_CATEGORY, "Laser Delay (seconds)", getter, setter); + delaySlider->setMin(0.0f); + delaySlider->setMax(2.0f); + delaySlider->setStep(0.02f); + delaySlider->setDecimals(2.0f); + preferences->addPreference(delaySlider); + } + static const QString VIEW_CATEGORY{ "View" }; { auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); }; @@ -226,12 +244,6 @@ void setupPreferences() { preferences->addPreference(preference); } - { - auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); }; - auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; - preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); - } - // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 557fc93750..bb65953fe4 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -170,6 +170,8 @@ public: void setIgnoreItems(const QVector& items); void setIncludeItems(const QVector& items); + virtual void setDelay(float delay) {} + virtual QVariantMap toVariantMap() const { QVariantMap properties; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index cad44b1864..dc3a482150 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -129,6 +129,13 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector& includ } } +void PickManager::setDelay(unsigned int uid, float delay) const { + auto pick = findPick(uid); + if (pick) { + pick->setDelay(delay); + } +} + Transform PickManager::getParentTransform(unsigned int uid) const { auto pick = findPick(uid); if (pick) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 79ab6ca2c8..b8e3f8d97d 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -48,6 +48,7 @@ public: void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setIgnoreItems(unsigned int uid, const QVector& ignore) const; void setIncludeItems(unsigned int uid, const QVector& include) const; + void setDelay(unsigned int uid, float delay) const; Transform getParentTransform(unsigned int uid) const; Transform getResultTransform(unsigned int uid) const; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index f435482865..6e9ed88508 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -74,6 +74,10 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } +void Pointer::setDelay(float delay) const { + DependencyManager::get()->setDelay(_pickUID, delay); +} + bool Pointer::isLeftHand() const { return DependencyManager::get()->isLeftHand(_pickUID); } diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 7a7c51477a..681ceb10c9 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -59,6 +59,7 @@ public: virtual void setPrecisionPicking(bool precisionPicking); virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; + virtual void setDelay(float delay) const; bool isLeftHand() const; bool isRightHand() const; diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index de403a46e8..31e8e0d814 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -157,6 +157,13 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo } } +void PointerManager::setDelay(unsigned int uid, float delay) const { + auto pointer = find(uid); + if (pointer) { + pointer->setDelay(delay); + } +} + bool PointerManager::isLeftHand(unsigned int uid) { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index 609f944101..cb39870c84 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -45,6 +45,7 @@ public: void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const; + void setDelay(unsigned int uid, float delay) const; void update(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index f098e5325a..f2b0efcfa1 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -17,8 +17,7 @@ controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, - PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, - PointerManager, print, Keyboard + PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, print, Keyboard */ var controllerDispatcherPlugins = {}; @@ -577,7 +576,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Controller.enableMapping(MAPPING_NAME); - const POINTER_DELAY = 0.5; this.leftPointer = this.pointerManager.createPointer(false, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, @@ -587,7 +585,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); scaleWithParent: true, distanceScaleEnd: true, hand: LEFT_HAND, - delay: POINTER_DELAY + delay: Picks.handLaserDelay }); Keyboard.setLeftHandLaser(this.leftPointer); this.rightPointer = this.pointerManager.createPointer(false, PickType.Ray, { @@ -599,7 +597,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); scaleWithParent: true, distanceScaleEnd: true, hand: RIGHT_HAND, - delay: POINTER_DELAY + delay: Picks.handLaserDelay }); Keyboard.setRightHandLaser(this.rightPointer); this.leftHudPointer = this.pointerManager.createPointer(true, PickType.Ray, { @@ -612,7 +610,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); scaleWithParent: true, distanceScaleEnd: true, hand: LEFT_HAND, - delay: POINTER_DELAY + delay: Picks.handLaserDelay }); this.rightHudPointer = this.pointerManager.createPointer(true, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", @@ -624,7 +622,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); scaleWithParent: true, distanceScaleEnd: true, hand: RIGHT_HAND, - delay: POINTER_DELAY + delay: Picks.handLaserDelay }); this.mouseRayPointer = Pointers.createRayPointer({ @@ -673,6 +671,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } }; + this.handLaserDelayChanged = function (delay) { + Pointers.setDelay(_this.leftPointer, delay); + Pointers.setDelay(_this.rightPointer, delay); + Pointers.setDelay(_this.leftHudPointer, delay); + Pointers.setDelay(_this.rightHudPointer, delay); + }; + this.cleanup = function () { Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); @@ -735,6 +740,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.messageReceived.connect(controllerDispatcher.handleMessage); + Picks.handLaserDelayChanged.connect(controllerDispatcher.handLaserDelayChanged); + Script.scriptEnding.connect(function () { controllerDispatcher.cleanup(); }); From d3111011d8709e59c009fe1fcc46187e9dd08260 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Tue, 18 Jun 2024 17:20:16 -0700 Subject: [PATCH 3/3] set default to 0.35, change steps --- interface/src/raypick/LaserPointer.cpp | 5 +++-- interface/src/raypick/PickScriptingInterface.h | 2 +- interface/src/ui/PreferencesDialog.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 7c64b6a524..c7fd44fb82 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -186,9 +186,10 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& bool hasUnmodifiedEndPoint = false; glm::vec3 unmodifiedEndPoint; + float directionLength = glm::length(endPoint); auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult && rayPickResult->pickVariant.contains("unmodifiedDirection")) { - unmodifiedEndPoint = glm::length(endPoint) * vec3FromVariant(rayPickResult->pickVariant["unmodifiedDirection"]); + unmodifiedEndPoint = directionLength * vec3FromVariant(rayPickResult->pickVariant["unmodifiedDirection"]); hasUnmodifiedEndPoint = true; } @@ -198,7 +199,7 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& if (!oldProperties.getVisible() || !_hasSetLinePoints || !hasUnmodifiedEndPoint) { points.append(frac * endPoint); } else { - points.append(frac * mix(unmodifiedEndPoint, endPoint, frac)); + points.append(frac * directionLength * glm::normalize(glm::mix(unmodifiedEndPoint, endPoint, frac))); } } _hasSetLinePoints = true; diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 00d5420f86..d429ff5ca9 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -488,7 +488,7 @@ protected: static void setParentTransform(std::shared_ptr pick, const QVariantMap& propMap); private: - Setting::Handle _handLaserDelaySetting { "handLaserDelay", 0.0f }; + Setting::Handle _handLaserDelaySetting { "handLaserDelay", 0.35f }; }; #endif // hifi_PickScriptingInterface_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index f44b105e0f..65e4daa5e0 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -219,8 +219,8 @@ void setupPreferences() { auto setter = [](float value) { DependencyManager::get()->setHandLaserDelay(value); }; auto delaySlider = new SpinnerSliderPreference(UI_CATEGORY, "Laser Delay (seconds)", getter, setter); delaySlider->setMin(0.0f); - delaySlider->setMax(2.0f); - delaySlider->setStep(0.02f); + delaySlider->setMax(0.7f); + delaySlider->setStep(0.05f); delaySlider->setDecimals(2.0f); preferences->addPreference(delaySlider); }