From 845ddda6952c097932c1e687816502ea1170295f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 17 Jul 2018 12:41:03 -0700 Subject: [PATCH] parabola-overlay and -avatar intersection, handle case where acceleration == 0 --- interface/src/Application.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 71 ++++++ interface/src/avatar/AvatarManager.h | 4 + interface/src/raypick/ParabolaPick.cpp | 18 +- interface/src/raypick/ParabolaPick.h | 4 +- .../src/scripting/HMDScriptingInterface.cpp | 6 + .../src/scripting/HMDScriptingInterface.h | 2 + interface/src/ui/overlays/Base3DOverlay.cpp | 22 +- interface/src/ui/overlays/Base3DOverlay.h | 16 +- interface/src/ui/overlays/Circle3DOverlay.cpp | 56 ++++- interface/src/ui/overlays/Circle3DOverlay.h | 4 +- .../ui/overlays/ContextOverlayInterface.cpp | 2 +- interface/src/ui/overlays/Cube3DOverlay.cpp | 3 +- interface/src/ui/overlays/Grid3DOverlay.cpp | 3 +- interface/src/ui/overlays/Grid3DOverlay.h | 5 +- interface/src/ui/overlays/Image3DOverlay.cpp | 58 ++++- interface/src/ui/overlays/Image3DOverlay.h | 4 +- interface/src/ui/overlays/Line3DOverlay.cpp | 3 +- interface/src/ui/overlays/ModelOverlay.cpp | 16 +- interface/src/ui/overlays/ModelOverlay.h | 6 +- interface/src/ui/overlays/Overlays.cpp | 53 +++- interface/src/ui/overlays/Overlays.h | 5 + interface/src/ui/overlays/Planar3DOverlay.cpp | 43 +++- interface/src/ui/overlays/Planar3DOverlay.h | 2 + .../src/ui/overlays/Rectangle3DOverlay.cpp | 3 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 3 +- interface/src/ui/overlays/Sphere3DOverlay.cpp | 3 +- interface/src/ui/overlays/Text3DOverlay.cpp | 3 +- interface/src/ui/overlays/Volume3DOverlay.cpp | 17 ++ interface/src/ui/overlays/Volume3DOverlay.h | 2 + interface/src/ui/overlays/Web3DOverlay.cpp | 17 +- interface/src/ui/overlays/Web3DOverlay.h | 3 - .../src/avatars-renderer/Avatar.cpp | 1 + .../src/display-plugins/CompositorHelper.cpp | 25 +- .../src/display-plugins/CompositorHelper.h | 1 + libraries/entities/src/TextEntityItem.cpp | 10 +- libraries/entities/src/WebEntityItem.cpp | 4 +- libraries/shared/src/AABox.cpp | 192 ++++++++++----- libraries/shared/src/AACube.cpp | 165 ++++++++----- libraries/shared/src/GeometryUtil.cpp | 231 +++++++++++++----- libraries/shared/src/GeometryUtil.h | 3 + 41 files changed, 816 insertions(+), 275 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6f95a1afe8..cffed61b37 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5253,7 +5253,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm _keyboardFocusHighlight->setPulseMin(0.5); _keyboardFocusHighlight->setPulseMax(1.0); _keyboardFocusHighlight->setColorPulse(1.0); - _keyboardFocusHighlight->setIgnoreRayIntersection(true); + _keyboardFocusHighlight->setIgnorePickIntersection(true); _keyboardFocusHighlight->setDrawInFront(false); _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c63095a204..ed2eb6a2db 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -573,6 +573,77 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } +ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { + ParabolaToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findParabolaIntersectionVector", + Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result), + Q_ARG(const PickParabola&, pick), + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); + return result; + } + + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float parabolicDistance; + BoxFace face; + glm::vec3 surfaceNormal; + + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + + // It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + // AABox avatarBounds = avatarModel->getRenderableMeshBound(); + // if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) { + // // parabola doesn't intersect avatar's bounding-box + // continue; + // } + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance); + if (!intersects) { + // ray doesn't intersect avatar's capsule + continue; + } + + QVariantMap extraInfo; + intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, + parabolicDistance, face, surfaceNormal, extraInfo, true); + + if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) { + result.intersects = true; + result.avatarID = avatar->getID(); + result.parabolicDistance = parabolicDistance; + result.extraInfo = extraInfo; + } + } + + if (result.intersects) { + result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance; + result.distance = glm::distance(pick.origin, result.intersection); + } + + return result; +} + // HACK float AvatarManager::getAvatarSortCoefficient(const QString& name) { if (name == "size") { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6a3d0355f6..9d9466264b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -141,6 +141,10 @@ public: const QVector& avatarsToInclude, const QVector& avatarsToDiscard); + Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); + /**jsdoc * @function AvatarManager.getAvatarSortCoefficient * @param {string} name diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index d0de9e5939..8dd0ad3aed 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -26,29 +26,29 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) } PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { - /*ParabolaToOverlayIntersectionResult overlayRes = + ParabolaToOverlayIntersectionResult overlayRes = qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (overlayRes.intersects) { return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); - } else {*/ + } else { return std::make_shared(pick.toVariantMap()); - //} + } } PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { - /*ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get()->findParabolaIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); + ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get()->findParabolaIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); if (avatarRes.intersects) { return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo); - } else {*/ + } else { return std::make_shared(pick.toVariantMap()); - //} + } } PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { - return std::make_shared(pick.toVariantMap()); - //glm::vec3 hudRes = DependencyManager::get()->calculateParabolaUICollisionPoint(pick); - //return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick); + float parabolicDistance; + glm::vec3 hudRes = DependencyManager::get()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance); + return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick); } glm::vec3 ParabolaPick::getAcceleration() const { diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 6131d09f1e..289f6e468a 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -58,11 +58,11 @@ public: } bool doesIntersect() const override { return intersects; } - bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; } PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { auto newParabolaRes = std::static_pointer_cast(newRes); - if (newParabolaRes->distance < distance) { + if (newParabolaRes->parabolicDistance < parabolicDistance) { return std::make_shared(*newParabolaRes); } else { return std::make_shared(*this); diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 31b8f74e9e..ad8e265a01 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p return result; } +glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const { + glm::vec3 result; + qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance); + return result; +} + glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const { return qApp->getApplicationCompositor().overlayFromSphereSurface(position); } diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d4dba2f0f5..3d7cc5afb2 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -100,6 +100,8 @@ public: */ Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; + glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const; + /**jsdoc * Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay. * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 551b352952..6bce9d9283 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -23,7 +23,7 @@ Base3DOverlay::Base3DOverlay() : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), - _ignoreRayIntersection(false), + _ignorePickIntersection(false), _drawInFront(false), _drawHUDLayer(false) { @@ -34,7 +34,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(base3DOverlay->_isSolid), _isDashedLine(base3DOverlay->_isDashedLine), - _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), + _ignorePickIntersection(base3DOverlay->_ignorePickIntersection), _drawInFront(base3DOverlay->_drawInFront), _drawHUDLayer(base3DOverlay->_drawHUDLayer), _isGrabbable(base3DOverlay->_isGrabbable), @@ -183,8 +183,10 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { if (properties["dashed"].isValid()) { setIsDashedLine(properties["dashed"].toBool()); } - if (properties["ignoreRayIntersection"].isValid()) { - setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool()); + if (properties["ignorePickIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignorePickIntersection"].toBool()); + } else if (properties["ignoreRayIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool()); } if (properties["parentID"].isValid()) { @@ -224,8 +226,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -260,8 +261,8 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "isDashedLine" || property == "dashed") { return _isDashedLine; } - if (property == "ignoreRayIntersection") { - return _ignoreRayIntersection; + if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") { + return _ignorePickIntersection; } if (property == "drawInFront") { return _drawInFront; @@ -282,11 +283,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) { return Overlay::getProperty(property); } -bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - return false; -} - void Base3DOverlay::locationChanged(bool tellPhysics) { SpatiallyNestable::locationChanged(tellPhysics); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 7c5f551e6a..d44c193055 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -45,7 +45,7 @@ public: bool getIsSolid() const { return _isSolid; } bool getIsDashedLine() const { return _isDashedLine; } bool getIsSolidLine() const { return !_isDashedLine; } - bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } + bool getIgnorePickIntersection() const { return _ignorePickIntersection; } bool getDrawInFront() const { return _drawInFront; } bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } @@ -53,7 +53,7 @@ public: void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } - void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } + void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; } virtual void setDrawInFront(bool value) { _drawInFront = value; } virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } @@ -69,13 +69,21 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false); + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking); } + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } + + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { + return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking); + } + virtual SpatialParentTree* getParentTree() const override; protected: @@ -91,7 +99,7 @@ protected: bool _isSolid; bool _isDashedLine; - bool _ignoreRayIntersection; + bool _ignorePickIntersection; bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 8b04f17269..ed09045ac6 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -397,8 +397,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -520,22 +519,65 @@ QVariant Circle3DOverlay::getProperty(const QString& property) { bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // Scale the dimensions by the diameter glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); - bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance); + glm::quat rotation = getWorldOrientation(); - if (intersects) { + if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) { glm::vec3 hitPosition = origin + (distance * direction); glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition()); localHitPosition.x /= getDimensions().x; localHitPosition.y /= getDimensions().y; float distanceToHit = glm::length(localHitPosition); - intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius(); + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } } - return intersects; + return false; +} + +bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // Scale the dimensions by the diameter + glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance; + localHitPosition.x /= getDimensions().x; + localHitPosition.y /= getDimensions().y; + float distanceToHit = glm::length(localHitPosition); + + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = rotation * Vectors::FRONT; + } else { + face = MAX_Z_FACE; + surfaceNormal = rotation * -Vectors::FRONT; + } + return true; + } + } + + return false; } Circle3DOverlay* Circle3DOverlay::createClone() const { diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 0dc0f8b138..b3fa24fb16 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -54,8 +54,10 @@ public: void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Circle3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index aca186a589..a879dcfada 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -193,7 +193,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN); _contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX); _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); - _contextOverlay->setIgnoreRayIntersection(false); + _contextOverlay->setIgnorePickIntersection(false); _contextOverlay->setDrawInFront(true); _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index c98d9330df..38fff5f26f 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -160,8 +160,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 621c19944b..15eb9eef76 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -145,8 +145,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h index 34fe4dbbb6..64b65b3178 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -35,7 +35,10 @@ public: virtual Grid3DOverlay* createClone() const override; // Grids are UI tools, and may not be intersected (pickable) - virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; } + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, + glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index a4ce7f9e0d..8b79c91bc4 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -216,8 +216,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -260,10 +259,7 @@ void Image3DOverlay::setURL(const QString& url) { bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { if (_texture && _texture->isLoaded()) { - // Make sure position and rotation is updated. Transform transform = getTransform(); - - // Don't call applyTransformTo() or setTransform() here because this code runs too frequently. // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. bool isNull = _fromImage.isNull(); @@ -271,12 +267,54 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec float height = isNull ? _texture->getHeight() : _fromImage.height(); float maxSize = glm::max(width, height); glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); - // FIXME - face and surfaceNormal not being set - return findRayRectangleIntersection(origin, direction, - transform.getRotation(), - transform.getTranslation(), - dimensions, distance); + if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + } + + return false; +} + +bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + if (_texture && _texture->isLoaded()) { + Transform transform = getTransform(); + + // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. + bool isNull = _fromImage.isNull(); + float width = isNull ? _texture->getWidth() : _fromImage.width(); + float height = isNull ? _texture->getHeight() : _fromImage.height(); + float maxSize = glm::max(width, height); + glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = rotation * Vectors::FRONT; + } else { + face = MAX_Z_FACE; + surfaceNormal = rotation * -Vectors::FRONT; + } + return true; + } } return false; diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index 4432e3b07c..1ffa062d45 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -42,8 +42,10 @@ public: QVariant getProperty(const QString& property) override; bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Image3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index c2e5ad1fb4..af6c3c2472 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -288,8 +288,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index f4289b1bf5..b303e7f919 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -373,8 +373,7 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} isGroupCulled=false - If true, the mesh parts of the model are LOD culled as a group. @@ -510,17 +509,26 @@ QVariant ModelOverlay::getProperty(const QString& property) { bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - QVariantMap extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } +bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + QVariantMap extraInfo; + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); +} + +bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); +} + ModelOverlay* ModelOverlay::createClone() const { return new ModelOverlay(this); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index f7a79c5615..9cf45ebfbc 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -47,7 +47,11 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; virtual ModelOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 4f2a8e6fa4..a54bc0795e 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -546,7 +546,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay continue; } - if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) { + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { float thisDistance; BoxFace thisFace; glm::vec3 thisSurfaceNormal; @@ -573,6 +573,54 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay return result; } +ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { + float bestDistance = std::numeric_limits::max(); + bool bestIsFront = false; + + QMutexLocker locker(&_mutex); + ParabolaToOverlayIntersectionResult result; + QMapIterator i(_overlaysWorld); + while (i.hasNext()) { + i.next(); + OverlayID thisID = i.key(); + auto thisOverlay = std::dynamic_pointer_cast(i.value()); + + if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || + (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { + continue; + } + + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { + float thisDistance; + BoxFace thisFace; + glm::vec3 thisSurfaceNormal; + QVariantMap thisExtraInfo; + if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance, + thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { + bool isDrawInFront = thisOverlay->getDrawInFront(); + if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) + || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { + + bestIsFront = isDrawInFront; + bestDistance = thisDistance; + result.intersects = true; + result.parabolicDistance = thisDistance; + result.face = thisFace; + result.surfaceNormal = thisSurfaceNormal; + result.overlayID = thisID; + result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance; + result.distance = glm::distance(result.intersection, parabola.origin); + result.extraInfo = thisExtraInfo; + } + } + } + } + return result; +} + QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { auto obj = engine->newObject(); obj.setProperty("intersects", value.intersects); @@ -1046,7 +1094,8 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { i.next(); OverlayID thisID = i.key(); auto overlay = std::dynamic_pointer_cast(i.value()); - if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) { + // FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong + if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) { // get AABox in frame of overlay glm::vec3 dimensions = overlay->getDimensions(); glm::vec3 low = dimensions * -0.5f; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index c91d28cf72..33768416fe 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -119,6 +119,11 @@ public: const QVector& overlaysToDiscard, bool visibleOnly = false, bool collidableOnly = false); + ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index 9a436c7564..77e1eceb58 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -72,8 +72,47 @@ QVariant Planar3DOverlay::getProperty(const QString& property) { bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // FIXME - face and surfaceNormal not being returned - return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance); + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = rotation * Vectors::FRONT; + } else { + face = MAX_Z_FACE; + surfaceNormal = rotation * -Vectors::FRONT; + } + return true; + } + return false; } Transform Planar3DOverlay::evalRenderTransform() { diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index e2a0e1f896..0054b0baf1 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -32,6 +32,8 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: glm::vec2 _dimensions; diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index e765f3fc18..48d89fab1c 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -140,8 +140,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index c27faf6f0f..b0d3cf32af 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -160,8 +160,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 4743e1ed3a..00a0dd686c 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -60,8 +60,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index b128ce7df7..fc4b8b9010 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -229,8 +229,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index cf1f7f7fcb..ba5dfa20c8 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -91,6 +91,23 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); } +bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 worldToEntityMatrix; + Transform transform = getTransform(); + transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable + transform.getInverseMatrix(worldToEntityMatrix); + + glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame + // and testing intersection there. + return _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal); +} + Transform Volume3DOverlay::evalRenderTransform() { Transform transform = getTransform(); #ifndef USE_SN_SCALE diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index e9b996a6dd..e4060ae335 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -32,6 +32,8 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: // Centered local bounding box diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ee267beb78..08b6927b4b 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -539,8 +539,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -623,20 +622,6 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - glm::vec2 dimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; - return true; - } else { - return false; - } -} - Web3DOverlay* Web3DOverlay::createClone() const { return new Web3DOverlay(this); } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 2cf35c0172..233f4e0d21 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -52,9 +52,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual Web3DOverlay* createClone() const override; enum InputMode { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a99838d810..1e4f33a95e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1563,6 +1563,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { } void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + // FIXME: this doesn't take into account Avatar rotation ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index b78f00fa0e..c35e641ffd 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -29,6 +29,8 @@ #include #include +#include "GeometryUtil.h" + // Used to animate the magnification windows //static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; @@ -357,9 +359,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction)); const float UI_RADIUS = 1.0f; - float instersectionDistance; - if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) { - result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance); + float intersectionDistance; + if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) { + result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance); #ifdef WANT_DEBUG DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); #endif @@ -372,6 +374,23 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c return false; } +bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const { + glm::mat4 uiToWorld = getUiTransform(); + glm::mat4 worldToUi = glm::inverse(uiToWorld); + glm::vec3 localOrigin = transformPoint(worldToUi, origin); + glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity)); + glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration)); + + const float UI_RADIUS = 1.0f; + float intersectionDistance; + if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) { + result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance; + parabolicDistance = intersectionDistance; + return true; + } + return false; +} + glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const { glm::vec2 result = sphericalPos; result.x *= -1.0f; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index fb712c26fa..e25d30109f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -52,6 +52,7 @@ public: void setRenderingWidget(QWidget* widget) { _renderingWidget = widget; } bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; + bool calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const; bool isHMD() const; bool fakeEventActive() const { return _fakeMouseEvent; } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 097097bf6d..3748ba6ec3 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -139,15 +139,14 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3 forward = rotation * Vectors::FRONT; if (glm::dot(forward, direction) > 0.0f) { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } else { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } return true; - } else { - return false; } + return false; } bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, @@ -174,9 +173,8 @@ bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c surfaceNormal = rotation * -Vectors::FRONT; } return true; - } else { - return false; } + return false; } void TextEntityItem::setText(const QString& value) { diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index f705409287..f11a3c041b 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -116,10 +116,10 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g glm::vec3 forward = rotation * Vectors::FRONT; if (glm::dot(forward, direction) > 0.0f) { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } else { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } return true; } else { diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index f364956de4..d8433e7fda 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -303,68 +303,123 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance // that is within the bounds of the other two dimensions for (int i = 0; i < 3; i++) { - // TODO: handle case where a is 0 - a = 0.5f * acceleration[i]; - b = velocity[i]; - if (origin[i] < _corner[i]) { - // If we're below _corner, we only need to check the min face - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + if (fabsf(acceleration[i]) < EPSILON) { + // Handle the degenerate case where we only have a line in this axis + if (origin[i] < _corner[i]) { + { // min + if (velocity[i] > 0.0f) { + float possibleDistance = (_corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } - } - } else if (origin[i] > _corner[i] + _scale[i]) { - // If we're above _corner + _scale, we only need to check the max face - { // max - c = origin[i] - (_corner[i] + _scale[i]); - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i + 1); - minNormal = glm::vec3(0.0f); - minNormal[i] = 1.0f; + } else if (origin[i] > _corner[i] + _scale[i]) { + { // max + if (velocity[i] < 0.0f) { + float possibleDistance = (_corner[i] + _scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + { // min + if (velocity[i] < 0.0f) { + float possibleDistance = (_corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + if (velocity[i] > 0.0f) { + float possibleDistance = (_corner[i] + _scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } } } else { - // If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + a = 0.5f * acceleration[i]; + b = velocity[i]; + if (origin[i] < _corner[i]) { + // If we're below _corner, we only need to check the min face + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } - } - { // max - c = origin[i] - (_corner[i] + _scale[i]); - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i + 1); - minNormal = glm::vec3(0.0f); - minNormal[i] = 1.0f; + } else if (origin[i] > _corner[i] + _scale[i]) { + // If we're above _corner + _scale, we only need to check the max face + { // max + c = origin[i] - (_corner[i] + _scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + // If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + c = origin[i] - (_corner[i] + _scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } } @@ -399,14 +454,29 @@ bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const return true; } - // Get the normal of the plane, the cross product of two vectors on the plane - // Assumes: velocity and acceleration != 0 and normalize(velocity) != normalize(acceleration) - glm::vec3 normal = glm::normalize(glm::cross(velocity, acceleration)); + float velocityLength2 = glm::length2(velocity); + if (glm::length2(acceleration) < EPSILON) { + if (velocityLength2 < EPSILON) { + // No intersection if velocity == acceleration == (0, 0, 0) + return false; + } + // Handle the degenerate case where acceleration == (0, 0, 0) + return rayHitsBoundingSphere(origin, glm::normalize(velocity)); + } else { + glm::vec3 vectorOnPlane = velocity; + if (glm::dot(glm::normalize(velocity), glm::normalize(acceleration)) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); - // Project vector from plane to sphere center onto the normal - float distance = glm::dot(localCenter, normal); - if (distance * distance < radiusSquared) { - return true; + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + if (distance * distance < radiusSquared) { + return true; + } } return false; } diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index f78f86d93e..43e97f9e3e 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -289,7 +289,7 @@ void AACube::checkPossibleParabolicIntersection(float t, int i, float& minDistan } bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { float minDistance = FLT_MAX; BoxFace minFace; glm::vec3 minNormal; @@ -299,68 +299,123 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance // that is within the bounds of the other two dimensions for (int i = 0; i < 3; i++) { - // TODO: handle case where a is 0 - a = 0.5f * acceleration[i]; - b = velocity[i]; - if (origin[i] < _corner[i]) { - // If we're below _corner, we only need to check the min face - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + if (fabsf(acceleration[i]) < EPSILON) { + // Handle the degenerate case where we only have a line in this axis + if (origin[i] < _corner[i]) { + { // min + if (velocity[i] > 0.0f) { + float possibleDistance = (_corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } - } - } else if (origin[i] > _corner[i] + _scale) { - // If we're above _corner + _scale, we only need to check the max face - { // max - c = origin[i] - (_corner[i] + _scale); - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i + 1); - minNormal = glm::vec3(0.0f); - minNormal[i] = 1.0f; + } else if (origin[i] > _corner[i] + _scale) { + { // max + if (velocity[i] < 0.0f) { + float possibleDistance = (_corner[i] + _scale - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + { // min + if (velocity[i] < 0.0f) { + float possibleDistance = (_corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + if (velocity[i] > 0.0f) { + float possibleDistance = (_corner[i] + _scale - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } } } else { - // If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + a = 0.5f * acceleration[i]; + b = velocity[i]; + if (origin[i] < _corner[i]) { + // If we're below _corner, we only need to check the min face + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } - } - { // max - c = origin[i] - (_corner[i] + _scale); - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i + 1); - minNormal = glm::vec3(0.0f); - minNormal[i] = 1.0f; + } else if (origin[i] > _corner[i] + _scale) { + // If we're above _corner + _scale, we only need to check the max face + { // max + c = origin[i] - (_corner[i] + _scale); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + // If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + c = origin[i] - (_corner[i] + _scale); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } } } } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 95a69756aa..750dd464a6 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -734,13 +734,21 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& glm::vec2 localCorner = -0.5f * dimensions; float minDistance = FLT_MAX; - float a = 0.5f * acceleration.z; - float b = velocity.z; - float c = origin.z; - glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - checkPossibleParabolicIntersectionWithZPlane(possibleDistances.x, minDistance, origin, velocity, acceleration, localCorner, dimensions); - checkPossibleParabolicIntersectionWithZPlane(possibleDistances.y, minDistance, origin, velocity, acceleration, localCorner, dimensions); + if (fabsf(acceleration.z) < EPSILON) { + // Handle the degenerate case where we only have a line in the z-axis + if (fabsf(velocity.z) > EPSILON) { + float possibleDistance = -origin.z / velocity.z; + checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + } else { + float a = 0.5f * acceleration.z; + float b = velocity.z; + float c = origin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.x, minDistance, origin, velocity, acceleration, localCorner, dimensions); + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.y, minDistance, origin, velocity, acceleration, localCorner, dimensions); + } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; @@ -754,43 +762,72 @@ bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& ve glm::vec3 localCenter = center - origin; float radiusSquared = radius * radius; - // Get the normal of the plane, the cross product of two vectors on the plane - // Assumes: velocity and acceleration != 0 and normalize(velocity) != normalize(acceleration) - glm::vec3 normal = glm::normalize(glm::cross(velocity, acceleration)); - - // Project vector from plane to sphere center onto the normal - float distance = glm::dot(localCenter, normal); - // Exit early if the sphere doesn't intersect the plane defined by the parabola - if (fabsf(distance) > radius) { - return false; - } - - glm::vec3 circleCenter = center - distance * normal; - float circleRadius = sqrtf(radiusSquared - distance * distance); - glm::vec3 q = glm::normalize(acceleration); - glm::vec3 p = glm::cross(normal, q); - - float a1 = glm::length(acceleration) * 0.5f; - float b1 = glm::dot(velocity, q); - float c1 = glm::dot(origin - circleCenter, q); - float a2 = glm::dot(velocity, p); - float b2 = glm::dot(origin - circleCenter, p); - - float a = a1 * a1; - float b = 2.0f * a1 * b1; - float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2; - float d = 2.0f * b1 * c1 + 2.0f * a2 * b2; - float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius; - + float velocityLength2 = glm::length2(velocity); + float accelerationLength = glm::length(acceleration); float minDistance = FLT_MAX; - glm::vec4 possibleDistances(FLT_MAX); - if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { - for (int i = 0; i < 4; i++) { - if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { - minDistance = possibleDistances[i]; + + if (accelerationLength < EPSILON) { + if (velocityLength2 < EPSILON) { + // No intersection if velocity == acceleration == (0, 0, 0) + return false; + } + // Handle the degenerate case where acceleration == (0, 0, 0) + glm::vec3 offset = origin - center; + float a = glm::dot(velocity, velocity); + float b = 2.0f * glm::dot(velocity, offset); + float c = glm::dot(offset, offset) - radius * radius; + glm::vec2 possibleDistances(FLT_MAX); + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } + } + } + } else { + glm::vec3 vectorOnPlane = velocity; + if (fabsf(glm::dot(glm::normalize(velocity), glm::normalize(acceleration))) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); + + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + // Exit early if the sphere doesn't intersect the plane defined by the parabola + if (fabsf(distance) > radius) { + return false; + } + + glm::vec3 circleCenter = center - distance * normal; + float circleRadius = sqrtf(radiusSquared - distance * distance); + glm::vec3 q = glm::normalize(acceleration); + glm::vec3 p = glm::cross(normal, q); + + float a1 = accelerationLength * 0.5f; + float b1 = glm::dot(velocity, q); + float c1 = glm::dot(origin - circleCenter, q); + float a2 = glm::dot(velocity, p); + float b2 = glm::dot(origin - circleCenter, p); + + float a = a1 * a1; + float b = 2.0f * a1 * b1; + float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2; + float d = 2.0f * b1 * c1 + 2.0f * a2 * b2; + float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius; + + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } } } } + if (minDistance < FLT_MAX) { parabolicDistance = minDistance; return true; @@ -837,15 +874,24 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& glm::vec3 localAcceleration = inverseRot * acceleration; float minDistance = FLT_MAX; - float a = 0.5f * localAcceleration.z; - float b = localVelocity.z; - float c = localOrigin.z; - glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - checkPossibleParabolicIntersectionWithTriangle(possibleDistances.x, minDistance, origin, velocity, acceleration, - localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); - checkPossibleParabolicIntersectionWithTriangle(possibleDistances.y, minDistance, origin, velocity, acceleration, + if (fabsf(localAcceleration.z) < EPSILON) { + if (fabsf(localVelocity.z) < EPSILON) { + return false; + } + float possibleDistance = -localOrigin.z / localVelocity.z; + checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration, localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } else { + float a = 0.5f * localAcceleration.z; + float b = localVelocity.z; + float c = localOrigin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + checkPossibleParabolicIntersectionWithTriangle(possibleDistances.x, minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + checkPossibleParabolicIntersectionWithTriangle(possibleDistances.y, minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; @@ -854,6 +900,85 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& return false; } +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance) { + if (start == end) { + return findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, parabolicDistance); // handle degenerate case + } + if (glm::distance2(origin, start) < radius * radius) { // inside start sphere + float startDistance; + bool intersectsStart = findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, startDistance); + if (glm::distance2(origin, end) < radius * radius) { // also inside end sphere + float endDistance; + bool intersectsEnd = findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, endDistance); + if (endDistance < startDistance) { + parabolicDistance = endDistance; + return intersectsEnd; + } + } + parabolicDistance = startDistance; + return intersectsStart; + } else if (glm::distance2(origin, end) < radius * radius) { // inside end sphere (and not start sphere) + return findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, parabolicDistance); + } + + // We are either inside the middle of the capsule or outside it completely + // Either way, we need to check all three parts of the capsule and find the closest intersection + glm::vec3 results(FLT_MAX); + findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, results[0]); + findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, results[1]); + + // We rotate the infinite cylinder to be aligned with the y-axis and then cap the values at the end + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - start); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + float capsuleLength = glm::length(end - start); + + const float MIN_ACCELERATION_PRODUCT = 0.00001f; + if (fabsf(localAcceleration.x * localAcceleration.z) < MIN_ACCELERATION_PRODUCT) { + // Handle the degenerate case where we only have a line in the XZ plane + float a = localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float b = 2.0f * (localVelocity.x * localOrigin.x + localVelocity.z * localOrigin.z); + float c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } else { + float a = 0.25f * (localAcceleration.x * localAcceleration.x + localAcceleration.z * localAcceleration.z); + float b = localVelocity.x * localAcceleration.x + localVelocity.z * localAcceleration.z; + float c = localOrigin.x * localAcceleration.x + localOrigin.z * localAcceleration.z + localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float d = 2.0f * (localOrigin.x * localVelocity.x + localOrigin.z * localVelocity.z); + float e = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } + + float minDistance = FLT_MAX; + for (int i = 0; i < 3; i++) { + minDistance = glm::min(minDistance, results[i]); + } + parabolicDistance = minDistance; + return minDistance != FLT_MAX; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, @@ -1100,7 +1225,7 @@ bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) { } // The following functions provide an analytical solution to a quartic equation, adapted from the solution here: https://github.com/sasamil/Quartic -unsigned int solveP3(float *x, float a, float b, float c) { +unsigned int solveP3(float* x, float a, float b, float c) { float a2 = a * a; float q = (a2 - 3.0f * b) / 9.0f; float r = (a * (2.0f * a2 - 9.0f * b) + 27.0f * c) / 54.0f; @@ -1221,11 +1346,5 @@ bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { } bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) { - a = 1.0f; - b = b / a; - c = c / a; - d = d / a; - e = e / a; - - return solve_quartic(b, c, d, e, roots); + return solve_quartic(b / a, c / a, d / a, e / a, roots); } \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index fb32ee2e8a..50d8d1f801 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -97,6 +97,9 @@ bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& ve bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface = false); +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance); + /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose /// \param direction[in] normalized axis about which the twist happens (typically original direction before rotation applied)