From 3064647d05e8cdcb6da9ab524a3c6505fdfdc6a2 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 9 Jun 2024 20:00:44 -0700 Subject: [PATCH] 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 = [