From 90091d11e4c8fa8a647c569e3af85bedbfc69e7a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 3 Jul 2018 09:47:31 -0700 Subject: [PATCH 01/22] parabola picks, started implementing math --- interface/src/raypick/JointParabolaPick.cpp | 43 ++++++ interface/src/raypick/JointParabolaPick.h | 31 ++++ interface/src/raypick/LaserPointer.cpp | 19 ++- interface/src/raypick/LaserPointer.h | 9 +- interface/src/raypick/MouseParabolaPick.cpp | 27 ++++ interface/src/raypick/MouseParabolaPick.h | 23 +++ interface/src/raypick/ParabolaPick.cpp | 57 ++++++++ interface/src/raypick/ParabolaPick.h | 94 ++++++++++++ .../src/raypick/PickScriptingInterface.cpp | 90 ++++++++++++ .../src/raypick/PickScriptingInterface.h | 24 +++- .../src/raypick/PointerScriptingInterface.cpp | 8 +- interface/src/raypick/StaticParabolaPick.cpp | 19 +++ interface/src/raypick/StaticParabolaPick.h | 26 ++++ interface/src/ui/overlays/Overlays.h | 15 +- libraries/avatars/src/AvatarData.h | 17 ++- .../src/RenderableModelEntityItem.cpp | 2 +- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderablePolyVoxEntityItem.h | 2 +- libraries/entities/src/EntityItem.h | 6 +- .../entities/src/EntityScriptingInterface.cpp | 33 +++-- .../entities/src/EntityScriptingInterface.h | 30 +++- libraries/entities/src/EntityTree.cpp | 65 ++++++++- libraries/entities/src/EntityTree.h | 9 +- libraries/entities/src/EntityTreeElement.cpp | 134 +++++++++++++++++- libraries/entities/src/EntityTreeElement.h | 14 +- libraries/entities/src/LightEntityItem.cpp | 11 ++ libraries/entities/src/LightEntityItem.h | 6 +- libraries/entities/src/LineEntityItem.h | 7 +- .../entities/src/ParticleEffectEntityItem.h | 2 +- libraries/entities/src/PolyLineEntityItem.h | 6 +- libraries/entities/src/PolyVoxEntityItem.h | 6 +- libraries/entities/src/ShapeEntityItem.cpp | 10 +- libraries/entities/src/ShapeEntityItem.h | 6 +- libraries/entities/src/TextEntityItem.cpp | 8 ++ libraries/entities/src/TextEntityItem.h | 6 +- libraries/entities/src/WebEntityItem.cpp | 8 ++ libraries/entities/src/WebEntityItem.h | 6 +- libraries/entities/src/ZoneEntityItem.cpp | 8 +- libraries/entities/src/ZoneEntityItem.h | 6 +- libraries/octree/src/OctreeElement.h | 2 +- libraries/pointers/src/Pick.h | 1 + libraries/pointers/src/PickManager.cpp | 1 + libraries/pointers/src/PickManager.h | 3 +- libraries/shared/src/AABox.cpp | 84 +++++++++++ libraries/shared/src/AABox.h | 8 +- libraries/shared/src/AACube.cpp | 62 ++++++++ libraries/shared/src/AACube.h | 5 + libraries/shared/src/GeometryUtil.cpp | 14 ++ libraries/shared/src/GeometryUtil.h | 2 + libraries/shared/src/RegisteredMetaTypes.h | 40 ++++++ 50 files changed, 1057 insertions(+), 60 deletions(-) create mode 100644 interface/src/raypick/JointParabolaPick.cpp create mode 100644 interface/src/raypick/JointParabolaPick.h create mode 100644 interface/src/raypick/MouseParabolaPick.cpp create mode 100644 interface/src/raypick/MouseParabolaPick.h create mode 100644 interface/src/raypick/ParabolaPick.cpp create mode 100644 interface/src/raypick/ParabolaPick.h create mode 100644 interface/src/raypick/StaticParabolaPick.cpp create mode 100644 interface/src/raypick/StaticParabolaPick.h diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp new file mode 100644 index 0000000000..771cbe6185 --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -0,0 +1,43 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "JointParabolaPick.h" + +#include "avatar/AvatarManager.h" + +JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled), + _jointName(jointName), + _posOffset(posOffset), + _dirOffset(dirOffset) +{ +} + +PickParabola JointParabolaPick::getMathematicalPick() const { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); + bool useAvatarHead = _jointName == "Avatar"; + const int INVALID_JOINT = -1; + if (jointIndex != INVALID_JOINT || useAvatarHead) { + glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex); + glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex); + glm::vec3 avatarPos = myAvatar->getWorldPosition(); + glm::quat avatarRot = myAvatar->getWorldOrientation(); + + glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos); + glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot; + + // Apply offset + pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); + glm::vec3 dir = rot * glm::normalize(_dirOffset); + + return PickParabola(pos, _speed * dir, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/JointParabolaPick.h b/interface/src/raypick/JointParabolaPick.h new file mode 100644 index 0000000000..d8b4457d7f --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.h @@ -0,0 +1,31 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_JointParabolaPick_h +#define hifi_JointParabolaPick_h + +#include "ParabolaPick.h" + +class JointParabolaPick : public ParabolaPick { + +public: + JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } + bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); } + +private: + std::string _jointName; + glm::vec3 _posOffset; + glm::vec3 _dirOffset; + +}; + +#endif // hifi_JointParabolaPick_h diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index bd71e47cf0..7860d6facf 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -19,12 +19,13 @@ #include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, - const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : Pointer(DependencyManager::get()->createRayPick(rayProps), enabled, hover), _triggers(triggers), _renderStates(renderStates), _defaultRenderStates(defaultRenderStates), _faceAvatar(faceAvatar), + _followNormal(followNormal), _centerEndY(centerEndY), _lockEnd(lockEnd), _distanceScaleEnd(distanceScaleEnd), @@ -78,6 +79,10 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta if (lineWidth.isValid()) { _renderStates[state].setLineWidth(lineWidth.toFloat()); } + QVariant rotation = pathProps.toMap()["rotation"]; + if (rotation.isValid()) { + _renderStates[state].setEndRot(quatFromVariant(rotation)); + } }); } @@ -148,7 +153,7 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) { +void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay) { if (!renderState.getStartID().isNull()) { QVariantMap startProps; startProps.insert("position", vec3toVariant(pickRay.origin)); @@ -182,10 +187,11 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter endProps.insert("position", end); } else { glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; - endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y))); + endProps.insert("position", vec3toVariant(endVec + currentUpVector * vec3(0.0f, 0.5f * dim.y, 0.0f))); } if (_faceAvatar) { - endProps.insert("rotation", quatToVariant(faceAvatarRotation)); + glm::quat rotation = _faceAvatar ? faceAvatarRotation : renderState.getEndRot(); + endProps.insert("rotation", quatToVariant(rotation)); } endProps.insert("visible", true); endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); @@ -223,12 +229,12 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay(); QUuid uid = rayPickResult->objectID; float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; - updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay); + updateRenderState(_renderStates[_currentRenderState], type, distance, rayPickResult->surfaceNormal, uid, pickRay); disableRenderState(_defaultRenderStates[_currentRenderState].second); } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay); + updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, Vectors::UP, QUuid(), pickRay); } else if (!_currentRenderState.empty()) { disableRenderState(_renderStates[_currentRenderState]); disableRenderState(_defaultRenderStates[_currentRenderState].second); @@ -306,6 +312,7 @@ RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, cons } if (!_endID.isNull()) { _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); + _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); } } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 964881be42..557bc60195 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -41,6 +41,9 @@ public: void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } const glm::vec3& getEndDim() const { return _endDim; } + void setEndRot(const glm::quat& endRot) { _endRot = endRot; } + const glm::quat& getEndRot() const { return _endRot; } + void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } const float& getLineWidth() const { return _lineWidth; } @@ -55,6 +58,7 @@ private: bool _endIgnoreRays; glm::vec3 _endDim; + glm::quat _endRot; float _lineWidth; }; @@ -65,7 +69,7 @@ public: typedef std::unordered_map> DefaultRenderStateMap; LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, - bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); ~LaserPointer(); void setRenderState(const std::string& state) override; @@ -96,6 +100,7 @@ private: RenderStateMap _renderStates; DefaultRenderStateMap _defaultRenderStates; bool _faceAvatar; + bool _followNormal; bool _centerEndY; bool _lockEnd; bool _distanceScaleEnd; @@ -103,7 +108,7 @@ private: LockEndObject _lockEndObject; void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay); + void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay); void disableRenderState(const RenderState& renderState); struct TriggerState { diff --git a/interface/src/raypick/MouseParabolaPick.cpp b/interface/src/raypick/MouseParabolaPick.cpp new file mode 100644 index 0000000000..450c792913 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.cpp @@ -0,0 +1,27 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "MouseParabolaPick.h" + +#include "Application.h" +#include "display-plugins/CompositorHelper.h" + +MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled) +{ +} + +PickParabola MouseParabolaPick::getMathematicalPick() const { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + QVariantMap posMap = position.toMap(); + PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + return PickParabola(pickRay.origin, _speed * pickRay.direction, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/MouseParabolaPick.h b/interface/src/raypick/MouseParabolaPick.h new file mode 100644 index 0000000000..ad03997c09 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.h @@ -0,0 +1,23 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MouseParabolaPick_h +#define hifi_MouseParabolaPick_h + +#include "ParabolaPick.h" + +class MouseParabolaPick : public ParabolaPick { + +public: + MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isMouse() const override { return true; } +}; + +#endif // hifi_MouseParabolaPick_h diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp new file mode 100644 index 0000000000..d0de9e5939 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.cpp @@ -0,0 +1,57 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ParabolaPick.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" + +PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { + ParabolaToEntityIntersectionResult entityRes = + DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (entityRes.intersects) { + return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); + } else { + return std::make_shared(pick.toVariantMap()); + } +} + +PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { + /*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 {*/ + return std::make_shared(pick.toVariantMap()); + //} +} + +PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { + /*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 {*/ + 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); +} + +glm::vec3 ParabolaPick::getAcceleration() const { + // TODO: use rotateWithAvatar + return _accelerationAxis; +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h new file mode 100644 index 0000000000..6131d09f1e --- /dev/null +++ b/interface/src/raypick/ParabolaPick.h @@ -0,0 +1,94 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_ParabolaPick_h +#define hifi_ParabolaPick_h + +#include +#include + +class EntityItemID; +class OverlayID; + +class ParabolaPickResult : public PickResult { +public: + ParabolaPickResult() {} + ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola, + const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : + PickResult(parabola.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), parabolicDistance(parabolicDistance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { + } + + ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) { + type = parabolaPickResult.type; + intersects = parabolaPickResult.intersects; + objectID = parabolaPickResult.objectID; + distance = parabolaPickResult.distance; + parabolicDistance = parabolaPickResult.parabolicDistance; + intersection = parabolaPickResult.intersection; + surfaceNormal = parabolaPickResult.surfaceNormal; + extraInfo = parabolaPickResult.extraInfo; + } + + IntersectionType type { NONE }; + bool intersects { false }; + QUuid objectID; + float distance { FLT_MAX }; + float parabolicDistance { FLT_MAX }; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + QVariantMap extraInfo; + + virtual QVariantMap toVariantMap() const override { + QVariantMap toReturn; + toReturn["type"] = type; + toReturn["intersects"] = intersects; + toReturn["objectID"] = objectID; + toReturn["distance"] = distance; + toReturn["parabolicDistance"] = parabolicDistance; + toReturn["intersection"] = vec3toVariant(intersection); + toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal); + toReturn["parabola"] = PickResult::toVariantMap(); + toReturn["extraInfo"] = extraInfo; + return toReturn; + } + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { + auto newParabolaRes = std::static_pointer_cast(newRes); + if (newParabolaRes->distance < distance) { + return std::make_shared(*newParabolaRes); + } else { + return std::make_shared(*this); + } + } + +}; + +class ParabolaPick : public Pick { + +public: + ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateWithAvatar(rotateWithAvatar) {} + + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } + PickResultPointer getEntityIntersection(const PickParabola& pick) override; + PickResultPointer getOverlayIntersection(const PickParabola& pick) override; + PickResultPointer getAvatarIntersection(const PickParabola& pick) override; + PickResultPointer getHUDIntersection(const PickParabola& pick) override; + +protected: + float _speed; + glm::vec3 _accelerationAxis; + bool _rotateWithAvatar; + + glm::vec3 getAcceleration() const; +}; + +#endif // hifi_ParabolaPick_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 74459ca624..8475126f28 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -17,6 +17,9 @@ #include "JointRayPick.h" #include "MouseRayPick.h" #include "StylusPick.h" +#include "StaticParabolaPick.h" +#include "JointParabolaPick.h" +#include "MouseParabolaPick.h" #include @@ -26,6 +29,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, return createRayPick(properties); case PickQuery::PickType::Stylus: return createStylusPick(properties); + case PickQuery::PickType::Parabola: + return createParabolaPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -134,6 +139,91 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +/**jsdoc + * A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick. + * @typedef {object} Picks.ParabolaPickProperties + * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. + * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. + * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. + * @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD. + * If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it + * exists on your current avatar. + * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral + * @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral + * @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment. + * @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment. + * @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola. + * @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction. + * @property {boolean} [rotateWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis. + */ +unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + float speed = 1.0f; + if (propMap["speed"].isValid()) { + speed = propMap["speed"].toFloat(); + } + + glm::vec3 accelerationAxis = -Vectors::UP; + if (propMap["accelerationAxis"].isValid()) { + accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]); + } + + bool rotateWithAvatar = true; + if (propMap["rotateWithAvatar"].isValid()) { + rotateWithAvatar = propMap["rotateWithAvatar"].toBool(); + } + + if (propMap["joint"].isValid()) { + std::string jointName = propMap["joint"].toString().toStdString(); + + if (jointName != "Mouse") { + // x = upward, y = forward, z = lateral + glm::vec3 posOffset = Vectors::ZERO; + if (propMap["posOffset"].isValid()) { + posOffset = vec3FromVariant(propMap["posOffset"]); + } + + glm::vec3 dirOffset = Vectors::UP; + if (propMap["dirOffset"].isValid()) { + dirOffset = vec3FromVariant(propMap["dirOffset"]); + } + + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(jointName, posOffset, dirOffset, + speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + + } else { + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + } + } else if (propMap["position"].isValid()) { + glm::vec3 position = vec3FromVariant(propMap["position"]); + + glm::vec3 direction = -Vectors::FRONT; + if (propMap["direction"].isValid()) { + direction = vec3FromVariant(propMap["direction"]); + } + + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + } + + return PickManager::INVALID_PICK_ID; +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..131f14d168 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -62,6 +62,7 @@ class PickScriptingInterface : public QObject, public Dependency { public: unsigned int createRayPick(const QVariant& properties); unsigned int createStylusPick(const QVariant& properties); + unsigned int createParabolaPick(const QVariant& properties); void registerMetaTypes(QScriptEngine* engine); @@ -71,7 +72,7 @@ public: * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick. * @function Picks.createPick * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick + * @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick * @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid. */ Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); @@ -125,6 +126,21 @@ public: * @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection. */ + /**jsdoc + * An intersection result for a Parabola Pick. + * + * @typedef {object} ParabolaPickResult + * @property {number} type The intersection type. + * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) + * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. + * @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola. + * @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola. + * @property {Vec3} intersection The intersection point in world-space. + * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. + * @property {Variant} extraInfo Additional intersection details when available for Model objects. + * @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection. + */ + /**jsdoc * Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled. * @function Picks.getPrevPickResult @@ -162,7 +178,7 @@ public: * Check if a Pick is associated with the left hand. * @function Picks.isLeftHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. */ Q_INVOKABLE bool isLeftHand(unsigned int uid); @@ -170,7 +186,7 @@ public: * Check if a Pick is associated with the right hand. * @function Picks.isRightHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. */ Q_INVOKABLE bool isRightHand(unsigned int uid); @@ -178,7 +194,7 @@ public: * Check if a Pick is associated with the system mouse. * @function Picks.isMouse * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise. + * @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise. */ Q_INVOKABLE bool isMouse(unsigned int uid); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 4e953a5cb8..8efc7f2199 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -134,6 +134,11 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } + bool followNormal = true; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); @@ -192,7 +197,8 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, - faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); + faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd, + scaleWithAvatar, enabled)); } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { diff --git a/interface/src/raypick/StaticParabolaPick.cpp b/interface/src/raypick/StaticParabolaPick.cpp new file mode 100644 index 0000000000..57b6aeccc7 --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.cpp @@ -0,0 +1,19 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "StaticParabolaPick.h" + +StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, + const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled), + _position(position), _velocity(speed * direction) +{ +} + +PickParabola StaticParabolaPick::getMathematicalPick() const { + return PickParabola(_position, _velocity, getAcceleration()); +} \ No newline at end of file diff --git a/interface/src/raypick/StaticParabolaPick.h b/interface/src/raypick/StaticParabolaPick.h new file mode 100644 index 0000000000..676b012b41 --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.h @@ -0,0 +1,26 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_StaticParabolaPick_h +#define hifi_StaticParabolaPick_h + +#include "ParabolaPick.h" + +class StaticParabolaPick : public ParabolaPick { + +public: + StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, + const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + +private: + glm::vec3 _position; + glm::vec3 _velocity; +}; + +#endif // hifi_StaticParabolaPick_h diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 3debf74f26..c91d28cf72 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -65,13 +65,22 @@ public: glm::vec3 intersection; QVariantMap extraInfo; }; - - Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); - QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); +class ParabolaToOverlayIntersectionResult { +public: + bool intersects { false }; + OverlayID overlayID { UNKNOWN_OVERLAY_ID }; + float distance { 0 }; + float parabolicDistance { 0 }; + BoxFace face { UNKNOWN_FACE }; + glm::vec3 surfaceNormal; + glm::vec3 intersection; + QVariantMap extraInfo; +}; + /**jsdoc * The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to * yourself and that aren't persisted to the domain. They are used for UI. diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 51b3257ba2..e5ae7ec999 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1525,19 +1525,26 @@ void registerAvatarTypes(QScriptEngine* engine); class RayToAvatarIntersectionResult { public: -RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} - bool intersects; + bool intersects { false }; QUuid avatarID; - float distance; + float distance { 0.0f }; glm::vec3 intersection; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +class ParabolaToAvatarIntersectionResult { +public: + bool intersects { false }; + QUuid avatarID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + glm::vec3 intersection; + QVariantMap extraInfo; +}; + Q_DECLARE_METATYPE(AvatarEntityMap) QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d8ac3dc63e..c15cf84ad0 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -278,7 +278,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag return properties; } -bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { +bool RenderableModelEntityItem::supportsDetailedIntersection() const { return true; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 91e5496b97..90c5a9c250 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -66,7 +66,7 @@ public: void doInitialModelSimulation(); void updateModelBounds(); - virtual bool supportsDetailedRayIntersection() const override; + virtual bool supportsDetailedIntersection() const override; virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7077ae799b..d7b9868fbc 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -51,7 +51,7 @@ public: int getOnCount() const override { return _onCount; } - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3a11fd821a..97106fcda6 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -159,11 +159,15 @@ public: virtual void debugDump() const; - virtual bool supportsDetailedRayIntersection() const { return false; } + virtual bool supportsDetailedIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return true; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return true; } // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 378ce541d7..14c42a1ac2 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -871,6 +871,30 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke return result; } +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); +} + +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola, + Octree::lockType lockType, bool precisionPicking, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + + + ParabolaToEntityIntersectionResult result; + if (_entityTree) { + OctreeElementPointer element; + result.entityID = _entityTree->findParabolaIntersection(parabola, + entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, + element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal, + result.extraInfo, lockType, &result.accurate); + result.intersects = !result.entityID.isNull(); + } + return result; +} + bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { auto client = DependencyManager::get(); return client->reloadServerScript(entityID); @@ -1025,15 +1049,6 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } -RayToEntityIntersectionResult::RayToEntityIntersectionResult() : - intersects(false), - accurate(true), // assume it's accurate - entityID(), - distance(0), - face() -{ -} - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { PROFILE_RANGE(script_entities, __FUNCTION__); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 50df825e5f..d7d86fc489 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -71,11 +71,10 @@ private: // "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: - RayToEntityIntersectionResult(); - bool intersects; - bool accurate; + bool intersects { false }; + bool accurate { true }; QUuid entityID; - float distance; + float distance { 0.0f }; BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; @@ -87,6 +86,18 @@ Q_DECLARE_METATYPE(RayToEntityIntersectionResult) QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results); void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results); +class ParabolaToEntityIntersectionResult { +public: + bool intersects { false }; + bool accurate { true }; + QUuid entityID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face; + glm::vec3 intersection; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; +}; /**jsdoc * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible @@ -131,6 +142,12 @@ public: void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } + + // TODO: expose to script? + ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly); + public slots: /**jsdoc @@ -1895,6 +1912,11 @@ private: bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false); + /// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode + ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType, + bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + EntityTreePointer _entityTree; std::recursive_mutex _entitiesScriptEngineLock; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index a58f01a83b..25bbe70abf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -63,6 +63,27 @@ public: EntityItemID entityID; }; +class ParabolaArgs { +public: + // Inputs + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + const QVector& entityIdsToInclude; + const QVector& entityIdsToDiscard; + bool visibleOnly; + bool collidableOnly; + bool precisionPicking; + + // Outputs + OctreeElementPointer& element; + float& parabolicDistance; + BoxFace& face; + glm::vec3& surfaceNormal; + QVariantMap& extraInfo; + EntityItemID entityID; +}; + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -820,8 +841,7 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard, - visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; + visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; @@ -836,6 +856,47 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: return args.entityID; } +bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) { + ParabolaArgs* args = static_cast(extraData); + bool keepSearching = true; + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching, + args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, + args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + if (!entityID.isNull()) { + args->entityID = entityID; + } + return keepSearching; +} + +EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType, bool* accurateResult) { + ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard, + visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; + parabolicDistance = FLT_MAX; + distance = FLT_MAX; + + bool requireLock = lockType == Octree::Lock; + bool lockResult = withReadLock([&] { + recurseTreeWithOperation(findParabolaIntersectionOp, &args); + }, requireLock); + + if (accurateResult) { + *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate + } + + if (!args.entityID.isNull()) { + intersection = parabola.origin + parabola.velocity * parabolicDistance + 0.5f * parabola.acceleration * parabolicDistance * parabolicDistance; + distance = glm::distance(intersection, parabola.origin); + } + + return args.entityID; +} + EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) { FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 22b468cf4e..2f971b8566 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -95,7 +95,14 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& node, float& distance, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); + + virtual EntityItemID findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index bc5bb1e81d..d13ae4ef03 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -159,7 +159,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con } // by default, we only allow intersections with leaves with content - if (!canRayIntersect()) { + if (!canPickIntersect()) { return result; // we don't intersect with non-leaves, and we keep searching } @@ -232,7 +232,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori localFace, localSurfaceNormal)) { if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) { // now ask the entity if we actually intersect - if (entity->supportsDetailedRayIntersection()) { + if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { @@ -250,7 +250,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) { distance = localDistance; face = localFace; - surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f)); + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); entityID = entity->getEntityItemID(); } } @@ -287,6 +287,134 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad return result; } +EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking) { + + EntityItemID result; + float distanceToElementCube = std::numeric_limits::max(); + BoxFace localFace; + glm::vec3 localSurfaceNormal; + + // if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance + // we can stop searching! + bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal); + if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) { + keepSearching = false; // no point in continuing to search + return result; // we did not intersect + } + + // by default, we only allow intersections with leaves with content + if (!canPickIntersect()) { + return result; // we don't intersect with non-leaves, and we keep searching + } + + // if the distance to the element cube is not less than the current best distance, then it's not possible + // for any details inside the cube to be closer so we don't need to consider them. + QVariantMap localExtraInfo; + float distanceToElementDetails = parabolicDistance; + EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, element, distanceToElementDetails, + face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localExtraInfo, precisionPicking); + if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { + parabolicDistance = distanceToElementDetails; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + result = entityID; + } + return result; +} + +EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { + + // only called if we do intersect our bounding cube, but find if we actually intersect with entities... + int entityNumber = 0; + EntityItemID entityID; + forEachEntity([&](EntityItemPointer entity) { + // use simple line-sphere for broadphase check + // (this is faster and more likely to cull results than the filter check below so we do it first) + bool success; + AABox entityBox = entity->getAABox(success); + if (!success) { + return; + } + + // Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane + // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, + // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection + // below + if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration)) { + return; + } + + // check RayPick filter settings + if ((visibleOnly && !entity->isVisible()) + || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) + || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) + || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { + return; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); + glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame + // and testing intersection there. + float localDistance; + BoxFace localFace; + glm::vec3 localSurfaceNormal; + if (entityFrameBox.findParabolaIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, localDistance, + localFace, localSurfaceNormal)) { + if (entityFrameBox.contains(entityFrameOrigin) || localDistance < parabolicDistance) { + // now ask the entity if we actually intersect + if (entity->supportsDetailedIntersection()) { + QVariantMap localExtraInfo; + if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance, + localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { + if (localDistance < parabolicDistance) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + entityID = entity->getEntityItemID(); + } + } + } else { + // if the entity type doesn't support a detailed intersection, then just return the non-AABox results + // Never intersect with particle entities + if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + entityID = entity->getEntityItemID(); + } + } + } + } + entityNumber++; + }); + return entityID; +} + EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const { EntityItemPointer closestEntity = NULL; float closestEntityDistance = FLT_MAX; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 76e1e40812..5bb8d4326e 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -134,9 +134,9 @@ public: virtual bool isRendered() const override { return getShouldRender(); } virtual bool deleteApproved() const override { return !hasEntities(); } - virtual bool canRayIntersect() const override { return hasEntities(); } + virtual bool canPickIntersect() const override { return hasEntities(); } virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& node, float& distance, + bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); @@ -148,6 +148,16 @@ public: virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; + virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking = false); + virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking); template void forEachEntity(F f) const { diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index e95af7ebf9..1db67fc0b6 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -309,3 +309,14 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _lightsArePickable; } +bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state + // this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could + // be on the clipboard and someone might be trying to use the parabola intersection API there. Anyway... if you ever try to + // do parabola intersection testing off of trees other than the main tree of the main entity renderer, then we'll need to + // fix this mechanism. + return _lightsArePickable; +} diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 4d0bde3718..518cb18de2 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -84,11 +84,15 @@ public: bool lightPropertiesChanged() const { return _lightPropertiesChanged; } void resetLightPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; private: // properties of a light diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 84f9acf5f5..7c21b5c9d2 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -58,12 +58,17 @@ class LineEntityItem : public EntityItem { virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a LineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, + bool precisionPicking) const override { return false; } bool pointsChanged() const { return _pointsChanged; } void resetPointsChanged(); virtual void debugDump() const override; diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 7e507ab46a..c7ceec1803 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -314,7 +314,7 @@ public: bool getEmitterShouldTrail() const { return _particleProperties.emission.shouldTrail; } void setEmitterShouldTrail(bool emitterShouldTrail); - virtual bool supportsDetailedRayIntersection() const override { return false; } + virtual bool supportsDetailedIntersection() const override { return false; } particle::Properties getParticleProperties() const; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index c76419af02..0a48ed584e 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -89,11 +89,15 @@ class PolyLineEntityItem : public EntityItem { // never have a ray intersection pick a PolyLineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return false; } // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious! diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 4dfe7b9535..e3f8c48dd1 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -42,11 +42,15 @@ class PolyVoxEntityItem : public EntityItem { bool& somethingChanged) override; // never have a ray intersection pick a PolyVoxEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return false; } virtual void debugDump() const override; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 943ae2e462..0a0b5a381a 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -250,7 +250,7 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -bool ShapeEntityItem::supportsDetailedRayIntersection() const { +bool ShapeEntityItem::supportsDetailedIntersection() const { return _shape == entity::Sphere; } @@ -282,6 +282,14 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return false; } +bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO + return false; +} + void ShapeEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index adc33b764b..ded5df15fe 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -90,11 +90,15 @@ public: bool shouldBePhysical() const override { return !isDead(); } - bool supportsDetailedRayIntersection() const override; + bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; void debugDump() const override; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 56e12e66d9..ca790d8c1c 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -140,6 +140,14 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance); } +bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO + return false; +} + void TextEntityItem::setText(const QString& value) { withWriteLock([&] { _text = value; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index efdc84bcd8..4ce5ef3297 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -45,11 +45,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; static const QString DEFAULT_TEXT; void setText(const QString& value); diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index f3159ba3f8..0fd730a86a 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -121,6 +121,14 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g } } +bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO + return false; +} + void WebEntityItem::setSourceUrl(const QString& value) { withWriteLock([&] { if (_sourceUrl != value) { diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 1179f22ded..2fa2033445 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -44,11 +44,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setSourceUrl(const QString& value); QString getSourceUrl() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3a6095b89f..f2550e5d3c 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -294,10 +294,16 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + return _zonesArePickable; +} +bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3a9c7cb1e6..0aaa32a57a 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,11 +102,15 @@ public: void resetRenderingPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void debugDump() const override; diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index b7857c3e6c..e9e7504e24 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -99,7 +99,7 @@ public: virtual bool deleteApproved() const { return true; } - virtual bool canRayIntersect() const { return isLeaf(); } + virtual bool canPickIntersect() const { return isLeaf(); } /// \param center center of sphere in meters /// \param radius radius of sphere in meters /// \param[out] penetration pointing into cube from sphere diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..dd59b50cc4 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -161,6 +161,7 @@ public: enum PickType { Ray = 0, Stylus, + Parabola, NUM_PICK_TYPES }; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index ba8fa814f0..38e86572d5 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -100,6 +100,7 @@ void PickManager::update() { // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..3bafd2186c 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -59,12 +59,13 @@ protected: std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; - unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + PickCacheOptimizer _parabolaPickCacheOptimizer; static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index cbf3c1b785..3287c0ce88 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -287,6 +287,68 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct return false; } +void AABox::checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const { + if (t < minDistance && t > 0.0f && + isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, _corner[(i + 1) % 3], _scale[(i + 1) % 3]) && + isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, _corner[(i + 2) % 3], _scale[(i + 2) % 3])) { + minDistance = t; + hit = true; + } +} + +bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + float minDistance = FLT_MAX; + BoxFace minFace; + glm::vec3 minNormal; + std::pair possibleDistances; + float a, b, c; + + // 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++) { + a = 0.5f * acceleration[i]; + b = velocity[i]; + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + face = minFace; + surfaceNormal = minNormal; + return true; + } + return false; +} + bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { glm::vec3 localCenter = calcCenter() - origin; float distance = glm::dot(localCenter, direction); @@ -296,6 +358,28 @@ bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& dire || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); } +bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) const { + glm::vec3 localCenter = calcCenter() - origin; + const float ONE_OVER_TWO_SQUARED = 0.25f; + float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); + + // origin is inside the sphere + if (glm::length2(localCenter) < radiusSquared) { + 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)); + + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + if (distance * distance < radiusSquared) { + return true; + } + return false; +} + bool AABox::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index cf79cf9d04..43976b7481 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -70,8 +70,11 @@ public: bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const; + BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const; + bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) const; bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; @@ -136,6 +139,9 @@ private: static BoxFace getOppositeFace(BoxFace face); + void checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const; + glm::vec3 _corner; glm::vec3 _scale; }; diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 7dd2f8cb5b..d1a138fcdb 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -283,6 +283,68 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc return false; } +void AACube::checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const { + if (t < minDistance && t > 0.0f && + isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, _corner[(i + 1) % 3], _scale) && + isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, _corner[(i + 2) % 3], _scale)) { + minDistance = t; + hit = true; + } +} + +bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + float minDistance = FLT_MAX; + BoxFace minFace; + glm::vec3 minNormal; + std::pair possibleDistances; + float a, b, c; + + // 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++) { + a = 0.5f * acceleration[i]; + b = velocity[i]; + { // min + c = origin[i] - _corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + face = minFace; + surfaceNormal = minNormal; + return true; + } + return false; +} + bool AACube::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - glm::vec3(_scale), Vectors::ZERO); diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 87a38cb304..df00b8aefc 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -58,6 +58,8 @@ public: bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool touchesSphere(const glm::vec3& center, float radius) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; @@ -76,6 +78,9 @@ private: static BoxFace getOppositeFace(BoxFace face); + void checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const; + glm::vec3 _corner; float _scale; }; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 0742a5625b..62772fb5d6 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -941,3 +941,17 @@ void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec } } } + +bool computeRealQuadraticRoots(float a, float b, float c, std::pair& roots) { + float discriminant = b * b - 4.0f * a * c; + if (discriminant < 0.0f) { + return false; + } else if (discriminant == 0.0f) { + roots.first = (-b + sqrtf(discriminant)) / (2.0f * a); + } else { + float discriminantRoot = sqrtf(discriminant); + roots.first = (-b + discriminantRoot) / (2.0f * a); + roots.second = (-b - discriminantRoot) / (2.0f * a); + } + return true; +} \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 4832616fbd..80d55856b3 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -178,4 +178,6 @@ bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& pla void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); +bool computeRealQuadraticRoots(float a, float b, float c, std::pair& roots); + #endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..db63237c73 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -219,6 +219,37 @@ public: } }; +/**jsdoc +* A PickParabola defines a parabola with a starting point, intitial velocity, and acceleration. +* +* @typedef {object} PickParabola +* @property {Vec3} origin - The starting position of the PickParabola. +* @property {Vec3} velocity - The starting velocity of the parabola. +* @property {Vec3} acceleration - The acceleration that the parabola experiences. +*/ +class PickParabola : public MathPick { +public: + PickParabola() : origin(NAN), velocity(NAN), acceleration(NAN) { } + PickParabola(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), velocity(vec3FromVariant(pickVariant["velocity"])), acceleration(vec3FromVariant(pickVariant["acceleration"])) {} + PickParabola(const glm::vec3& origin, const glm::vec3 velocity, const glm::vec3 acceleration) : origin(origin), velocity(velocity), acceleration(acceleration) {} + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + + operator bool() const override { + return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(velocity)) || glm::any(glm::isnan(acceleration))); + } + bool operator==(const PickParabola& other) const { + return (origin == other.origin && velocity == other.velocity && acceleration == other.acceleration); + } + QVariantMap toVariantMap() const override { + QVariantMap pickParabola; + pickParabola["origin"] = vec3toVariant(origin); + pickParabola["velocity"] = vec3toVariant(velocity); + pickParabola["acceleration"] = vec3toVariant(acceleration); + return pickParabola; + } +}; namespace std { inline void hash_combine(std::size_t& seed) { } @@ -273,6 +304,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const PickParabola& a) const { + size_t result = 0; + hash_combine(result, a.origin, a.velocity, a.acceleration); + return result; + } + }; + template <> struct hash { size_t operator()(const QString& a) const { From bfcf4f78657f50b010b5c40ff95b3aa18dc5027d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 3 Jul 2018 15:14:43 -0700 Subject: [PATCH 02/22] add parabola test, performance optimization --- libraries/shared/src/AABox.cpp | 81 +++++++++++++++++++++++--------- libraries/shared/src/AACube.cpp | 81 +++++++++++++++++++++++--------- tests/shared/src/AACubeTests.cpp | 44 +++++++++++++++++ tests/shared/src/AACubeTests.h | 2 + 4 files changed, 162 insertions(+), 46 deletions(-) diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 3287c0ce88..9aa0f21aa8 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -310,31 +310,66 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v for (int i = 0; i < 3; i++) { a = 0.5f * acceleration[i]; b = velocity[i]; - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } } } } diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index d1a138fcdb..24af2855de 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -306,31 +306,66 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& for (int i = 0; i < 3; i++) { a = 0.5f * acceleration[i]; b = velocity[i]; - { // min - c = origin[i] - _corner[i]; - possibleDistances = { FLT_MAX, FLT_MAX }; - if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { - bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); - if (hit) { - minFace = BoxFace(2 * i); - minNormal = glm::vec3(0.0f); - minNormal[i] = -1.0f; + 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, 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.first, i, minDistance, origin, velocity, acceleration, hit); + checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } } } } diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index 469dcfa981..95a4d7f9f0 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -152,3 +152,47 @@ void AACubeTests::touchesSphere() { } } +void AACubeTests::rayVsParabolaPerformance() { + // Test performance of findRayIntersection vs. findParabolaIntersection + // 100000 cubes with scale 500 in the +x +y +z quadrant + const int NUM_CUBES = 100000; + const float MAX_POS = 1000.0f; + const float MAX_SCALE = 500.0f; + int numRayHits = 0; + int numParabolaHits = 0; + std::vector cubes; + cubes.reserve(NUM_CUBES); + for (int i = 0; i < NUM_CUBES; i++) { + cubes.emplace_back(glm::vec3(randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS)), MAX_SCALE); + } + + glm::vec3 origin(0.0f); + glm::vec3 direction = glm::normalize(glm::vec3(1.0f)); + float distance; + BoxFace face; + glm::vec3 normal; + auto start = std::chrono::high_resolution_clock::now(); + for (auto& cube : cubes) { + if (cube.findRayIntersection(origin, direction, distance, face, normal)) { + numRayHits++; + } + } + + auto rayTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + direction = 10.0f * direction; + glm::vec3 acceleration = glm::vec3(-0.0001f, -0.0001f, -0.0001f); + for (auto& cube : cubes) { + if (cube.findParabolaIntersection(origin, direction, acceleration, distance, face, normal)) { + numParabolaHits++; + } + } + auto parabolaTime = std::chrono::high_resolution_clock::now() - start; + + qDebug() << "Ray vs. Parabola perfomance: rayHit%:" << numRayHits / ((float)NUM_CUBES) * 100.0f << ", rayTime:" << rayTime.count() << + ", parabolaHit%:" << numParabolaHits / ((float)NUM_CUBES) * 100.0f << ", parabolaTime:" << parabolaTime.count() << ", parabolaTime/rayTime: " << (float)parabolaTime.count()/(float)rayTime.count(); +} + +void AACubeTests::cleanupTestCase() { + +} \ No newline at end of file diff --git a/tests/shared/src/AACubeTests.h b/tests/shared/src/AACubeTests.h index a2b2e08cc5..569c978929 100644 --- a/tests/shared/src/AACubeTests.h +++ b/tests/shared/src/AACubeTests.h @@ -23,6 +23,8 @@ private slots: void ctorsAndSetters(); void containsPoint(); void touchesSphere(); + void rayVsParabolaPerformance(); + void cleanupTestCase(); }; #endif // hifi_AACubeTests_h From 7130b5aa7f69203e77d8fad518312ed96f195750 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Jul 2018 10:27:12 -0700 Subject: [PATCH 03/22] fixing findRayIntersection bugs --- .../src/RenderablePolyVoxEntityItem.cpp | 1 - libraries/entities/src/EntityTreeElement.cpp | 6 ++- libraries/entities/src/TextEntityItem.cpp | 37 ++++++++++++++----- libraries/entities/src/WebEntityItem.cpp | 23 ++++++++++-- libraries/shared/src/GeometryUtil.cpp | 5 +++ libraries/shared/src/GeometryUtil.h | 3 ++ 6 files changed, 59 insertions(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 0211daff1e..f9fed5ae8b 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -605,7 +605,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o voxelBox += result3 + Vectors::HALF; float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index d13ae4ef03..5a56f51847 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -168,7 +168,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con QVariantMap localExtraInfo; float distanceToElementDetails = distance; EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, - face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, localExtraInfo, precisionPicking); if (!entityID.isNull() && distanceToElementDetails < distance) { distance = distanceToElementDetails; @@ -251,6 +251,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori distance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); entityID = entity->getEntityItemID(); } } @@ -316,7 +317,7 @@ EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin QVariantMap localExtraInfo; float distanceToElementDetails = parabolicDistance; EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, element, distanceToElementDetails, - face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, localExtraInfo, precisionPicking); if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { parabolicDistance = distanceToElementDetails; @@ -405,6 +406,7 @@ EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3 parabolicDistance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); entityID = entity->getEntityItemID(); } } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index ca790d8c1c..32cc82524b 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -127,25 +127,44 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits } bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * - (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - // FIXME - should set face and surfaceNormal - return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance); + 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 = rotation * -Vectors::FRONT; + } else { + face = MIN_Z_FACE; + surfaceNormal = rotation * Vectors::FRONT; + } + return true; + } else { + return false; + } } bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { - // TODO - return false; + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { + // get face and surfaceNormal + return true; + } else { + return false; + } } void TextEntityItem::setText(const QString& value) { diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 0fd730a86a..a591493370 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -113,8 +113,14 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = rotation * -Vectors::FRONT; + } else { + face = MIN_Z_FACE; + surfaceNormal = rotation * Vectors::FRONT; + } return true; } else { return false; @@ -125,8 +131,17 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { - // TODO - return false; + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { + // get face and surfaceNormal + return true; + } else { + return false; + } } void WebEntityItem::setSourceUrl(const QString& value) { diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 62772fb5d6..5d511f6209 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -711,6 +711,11 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire return false; } +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions, float& parabolicDistance) { + return false; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 80d55856b3..bc531c8a36 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -88,6 +88,9 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::quat& rotation, + const glm::vec3& position, const glm::vec2& dimensions, 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) From bbe9a0005db86392337af75576024159f2e208fa Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Jul 2018 17:02:55 -0700 Subject: [PATCH 04/22] parabola vs. web and text --- libraries/entities/src/TextEntityItem.cpp | 12 +++++++- libraries/entities/src/WebEntityItem.cpp | 12 +++++++- libraries/shared/src/AABox.cpp | 6 +--- libraries/shared/src/AACube.cpp | 6 +--- libraries/shared/src/GeometryUtil.cpp | 34 +++++++++++++++++++++++ libraries/shared/src/GeometryUtil.h | 5 ++++ 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 32cc82524b..6dc5b350bb 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -160,7 +160,17 @@ bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { - // get face and surfaceNormal + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + 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; } else { return false; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index a591493370..31b32ef04e 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -137,7 +137,17 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { - // get face and surfaceNormal + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + 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; } else { return false; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 9aa0f21aa8..a2f1f3d016 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -109,11 +109,6 @@ glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AABox::contains(const Triangle& triangle) const { return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); } @@ -308,6 +303,7 @@ 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]) { diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 24af2855de..2a14723836 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -110,11 +110,6 @@ glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const { return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AACube::contains(const glm::vec3& point) const { return isWithin(point.x, _corner.x, _scale) && isWithin(point.y, _corner.y, _scale) && @@ -304,6 +299,7 @@ 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]) { diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 5d511f6209..2e5c5983a9 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -711,8 +711,42 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire return false; } +// determines whether a value is within the extents +bool isWithin(float value, float corner, float size) { + return value >= corner && value <= corner + size; +} + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) { + if (t < minDistance && t > 0.0f && + isWithin(origin.x + velocity.x * t + 0.5f * acceleration.x * t * t, corner.x, scale.x) && + isWithin(origin.y + velocity.y * t + 0.5f * acceleration.y * t * t, corner.y, scale.y)) { + minDistance = t; + } +} + bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions, float& parabolicDistance) { + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + glm::vec2 localCorner = -0.5f * dimensions; + + float minDistance = FLT_MAX; + float a = 0.5f * localAcceleration.z; + float b = localVelocity.z; + float c = localOrigin.z; + std::pair possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.first, minDistance, localOrigin, localVelocity, localAcceleration, localCorner, dimensions); + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.second, minDistance, localOrigin, localVelocity, localAcceleration, localCorner, dimensions); + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } return false; } diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index bc531c8a36..1225d89d95 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -183,4 +183,9 @@ void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec bool computeRealQuadraticRoots(float a, float b, float c, std::pair& roots); +bool isWithin(float value, float corner, float size); + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale); + #endif // hifi_GeometryUtil_h From fcc523fbef7282d29381d48aecd195324633dad1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 9 Jul 2018 15:15:36 -0700 Subject: [PATCH 05/22] parabola sphere intersection --- libraries/entities/src/ShapeEntityItem.cpp | 19 +- libraries/entities/src/TextEntityItem.cpp | 10 +- libraries/entities/src/WebEntityItem.cpp | 10 +- libraries/shared/src/AABox.cpp | 18 +- libraries/shared/src/AACube.cpp | 18 +- libraries/shared/src/GeometryUtil.cpp | 212 +++++++++++++++++++-- libraries/shared/src/GeometryUtil.h | 13 +- 7 files changed, 253 insertions(+), 47 deletions(-) diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 0a0b5a381a..e4ea1470c1 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -273,6 +273,7 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); distance = glm::distance(origin, hitAt); bool success; + // FIXME: this is only correct for uniformly scaled spheres surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); if (!success) { return false; @@ -286,7 +287,23 @@ bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { - // TODO + // determine the parabola in the frame of the entity transformed from a unit sphere + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) { + bool success; + // FIXME: this is only correct for uniformly scaled spheres + surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success)); + if (!success) { + return false; + } + return true; + } return false; } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 6dc5b350bb..097097bf6d 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -159,10 +159,12 @@ bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; + 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; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 31b32ef04e..f705409287 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -136,10 +136,12 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - if (findParabolaRectangleIntersection(origin, velocity, acceleration, rotation, position, xyDimensions, parabolicDistance)) { - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; + 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; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index a2f1f3d016..f364956de4 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -297,7 +297,7 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v float minDistance = FLT_MAX; BoxFace minFace; glm::vec3 minNormal; - std::pair possibleDistances; + glm::vec2 possibleDistances; float a, b, c; // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance @@ -313,8 +313,8 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -329,8 +329,8 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -345,8 +345,8 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -359,8 +359,8 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 2a14723836..f78f86d93e 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -293,7 +293,7 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& float minDistance = FLT_MAX; BoxFace minFace; glm::vec3 minNormal; - std::pair possibleDistances; + glm::vec2 possibleDistances; float a, b, c; // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance @@ -309,8 +309,8 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -325,8 +325,8 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -341,8 +341,8 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); @@ -355,8 +355,8 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; - checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit); - checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit); + 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); diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 2e5c5983a9..d015ab5478 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -1,4 +1,4 @@ -// +// // GeometryUtil.cpp // libraries/shared/src // @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include "NumericalConstants.h" @@ -725,23 +727,68 @@ void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, } } +// Intersect with the plane z = 0 and make sure the intersection is within dimensions bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions, float& parabolicDistance) { - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - + const glm::vec2& dimensions, float& parabolicDistance) { glm::vec2 localCorner = -0.5f * dimensions; float minDistance = FLT_MAX; - float a = 0.5f * localAcceleration.z; - float b = localVelocity.z; - float c = localOrigin.z; - std::pair possibleDistances = { FLT_MAX, 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.first, minDistance, localOrigin, localVelocity, localAcceleration, localCorner, dimensions); - checkPossibleParabolicIntersectionWithZPlane(possibleDistances.second, minDistance, localOrigin, localVelocity, localAcceleration, localCorner, dimensions); + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.x, minDistance, origin, velocity, acceleration, localCorner, dimensions); + checkPossibleParabolicIntersectionWithZPlane(possibleDistances.y, minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& parabolicDistance) { + 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 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 (minDistance < FLT_MAX) { parabolicDistance = minDistance; @@ -981,16 +1028,147 @@ void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec } } -bool computeRealQuadraticRoots(float a, float b, float c, std::pair& roots) { +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) { float discriminant = b * b - 4.0f * a * c; if (discriminant < 0.0f) { return false; } else if (discriminant == 0.0f) { - roots.first = (-b + sqrtf(discriminant)) / (2.0f * a); + roots.x = (-b + sqrtf(discriminant)) / (2.0f * a); } else { float discriminantRoot = sqrtf(discriminant); - roots.first = (-b + discriminantRoot) / (2.0f * a); - roots.second = (-b - discriminantRoot) / (2.0f * a); + roots.x = (-b + discriminantRoot) / (2.0f * a); + roots.y = (-b - discriminantRoot) / (2.0f * a); } return true; +} + +// 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) { + 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; + float r2 = r * r; + float q3 = q * q * q; + float A, B; + if (r2 < q3) { + float t = r / sqrtf(q3); + t = glm::clamp(t, -1.0f, 1.0f); + t = acosf(t); + a /= 3.0f; + q = -2.0f * sqrtf(q); + x[0] = q * cosf(t / 3.0f) - a; + x[1] = q * cosf((t + 2.0f * M_PI) / 3.0f) - a; + x[2] = q * cosf((t - 2.0f * M_PI) / 3.0f) - a; + return 3; + } else { + A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f); + if (r < 0) { + A = -A; + } + B = (A == 0.0f ? 0.0f : q / A); + + a /= 3.0f; + x[0] = (A + B) - a; + x[1] = -0.5f * (A + B) - a; + x[2] = 0.5f * sqrtf(3.0f) * (A - B); + if (fabsf(x[2]) < EPSILON) { + x[2] = x[1]; + return 2; + } + + return 1; + } +} + +bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { + float a3 = -b; + float b3 = a * c - 4.0f *d; + float c3 = -a * a * d - c * c + 4.0f * b * d; + + float px3[3]; + unsigned int iZeroes = solveP3(px3, a3, b3, c3); + + float q1, q2, p1, p2, D, sqD, y; + + y = px3[0]; + if (iZeroes != 1) { + if (fabs(px3[1]) > fabs(y)) { + y = px3[1]; + } + if (fabs(px3[2]) > fabs(y)) { + y = px3[2]; + } + } + + D = y * y - 4.0f * d; + if (fabs(D) < EPSILON) { + q1 = q2 = 0.5f * y; + D = a * a - 4.0f * (b - y); + if (fabs(D) < EPSILON) { + p1 = p2 = 0.5f * a; + } else { + sqD = sqrt(D); + p1 = 0.5f * (a + sqD); + p2 = 0.5f * (a - sqD); + } + } else { + sqD = sqrt(D); + q1 = 0.5f * (y + sqD); + q2 = 0.5f * (y - sqD); + p1 = (a * q1 - c) / (q1 - q2); + p2 = (c - a * q2) / (q1 - q2); + } + + std::complex x1, x2, x3, x4; + D = p1 * p1 - 4.0f * q1; + if (D < 0.0f) { + x1.real(-0.5f * p1); + x1.imag(0.5f * sqrt(-D)); + x2 = std::conj(x1); + } else { + sqD = sqrt(D); + x1.real(0.5f * (-p1 + sqD)); + x2.real(0.5f * (-p1 - sqD)); + } + + D = p2 * p2 - 4.0f * q2; + if (D < 0.0f) { + x3.real(-0.5f * p2); + x3.imag(0.5f * sqrt(-D)); + x4 = std::conj(x3); + } else { + sqD = sqrt(D); + x3.real(0.5f * (-p2 + sqD)); + x4.real(0.5f * (-p2 - sqD)); + } + + bool hasRealRoot = false; + if (fabsf(x1.imag()) < EPSILON) { + roots.x = x1.real(); + hasRealRoot = true; + } + if (fabsf(x2.imag()) < EPSILON) { + roots.y = x2.real(); + hasRealRoot = true; + } + if (fabsf(x3.imag()) < EPSILON) { + roots.z = x3.real(); + hasRealRoot = true; + } + if (fabsf(x4.imag()) < EPSILON) { + roots.w = x4.real(); + hasRealRoot = true; + } + + return hasRealRoot; +} + +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); } \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 1225d89d95..a540093f40 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -88,8 +88,11 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); -bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::quat& rotation, - const glm::vec3& position, const glm::vec2& dimensions, float& parabolicDistance); +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec2& dimensions, float& parabolicDistance); + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& distance); /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose @@ -181,7 +184,11 @@ bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& pla void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); -bool computeRealQuadraticRoots(float a, float b, float c, std::pair& roots); +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots); + +unsigned int solveP3(float *x, float a, float b, float c); +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); bool isWithin(float value, float corner, float size); From ca5ce888f437310350ea093504bf75fe18674cfa Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 9 Jul 2018 18:06:33 -0700 Subject: [PATCH 06/22] parabola triangle intersection --- .../src/RenderableModelEntityItem.cpp | 12 ++ .../src/RenderableModelEntityItem.h | 4 + libraries/render-utils/src/Model.cpp | 124 +++++++++++++++-- libraries/render-utils/src/Model.h | 3 + libraries/shared/src/GeometryUtil.cpp | 57 ++++++++ libraries/shared/src/GeometryUtil.h | 12 ++ libraries/shared/src/TriangleSet.cpp | 126 +++++++++++++++++- libraries/shared/src/TriangleSet.h | 14 +- 8 files changed, 333 insertions(+), 19 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c15cf84ad0..2104c2333b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -294,6 +294,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori face, surfaceNormal, extraInfo, precisionPicking, false); } +bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, + glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + auto model = getModel(); + if (!model || !isModelLoaded()) { + return false; + } + + return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, + face, surfaceNormal, extraInfo, precisionPicking, false); +} + void RenderableModelEntityItem::getCollisionGeometryResource() { QUrl hullURL(getCompoundShapeURL()); QUrlQuery queryArgs(hullURL); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 90c5a9c250..f6505966d9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -71,6 +71,10 @@ public: OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setShapeType(ShapeType type) override; virtual void setCompoundShapeURL(const QString& url) override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index dc65863c6e..3cd4501c4b 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -353,31 +353,27 @@ void Model::initJointStates() { bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { - bool intersectedSomething = false; - // if we aren't active, we can't ray pick yet... + // if we aren't active, we can't pick yet... if (!isActive()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity - glm::vec3 position = _translation; - glm::mat4 rotation = glm::mat4_cast(_rotation); - glm::mat4 translation = glm::translate(position); - glm::mat4 modelToWorldMatrix = translation * rotation; + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; - glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the ray picking in the model frame of reference + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); - // we can use the AABox's ray intersection by mapping our origin and direction into the model frame + // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); @@ -395,7 +391,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); @@ -405,11 +401,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; for (auto &partTriangleSet : meshTriangleSets) { - float triangleSetDistance = 0.0f; + float triangleSetDistance; BoxFace triangleSetFace; Triangle triangleSetTriangle; if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); float worldDistance = glm::distance(origin, worldIntersectionPoint); @@ -457,6 +452,111 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g return intersectedSomething; } +bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + bool pickAgainstTriangles, bool allowBackface) { + bool intersectedSomething = false; + + // if we aren't active, we can't pick yet... + if (!isActive()) { + return intersectedSomething; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + + Extents modelExtents = getMeshExtents(); // NOTE: unrotated + + glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference + AABox modelFrameBox(corner, dimensions); + + glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 modelFrameVelocity = glm::vec3(worldToModelMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 modelFrameAcceleration = glm::vec3(worldToModelMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's intersection by mapping our origin and direction into the model frame + // and testing intersection there. + if (modelFrameBox.findParabolaIntersection(modelFrameOrigin, modelFrameVelocity, modelFrameAcceleration, parabolicDistance, face, surfaceNormal)) { + QMutexLocker locker(&_mutex); + + float bestDistance = FLT_MAX; + Triangle bestModelTriangle; + Triangle bestWorldTriangle; + int bestSubMeshIndex = 0; + + int subMeshIndex = 0; + const FBXGeometry& geometry = getFBXGeometry(); + + if (!_triangleSetsValid) { + calculateTriangleSets(geometry); + } + + glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; + glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); + + glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 meshFrameVelocity = glm::vec3(worldToMeshMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f)); + + int shapeID = 0; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto &partTriangleSet : meshTriangleSets) { + float triangleSetDistance; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + face = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 worldIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; + glm::vec3 meshIntersectionPoint = origin + velocity * triangleSetDistance + + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["partIndex"] = partIndex; + extraInfo["shapeID"] = shapeID; + bestSubMeshIndex = subMeshIndex; + } + } + partIndex++; + shapeID++; + } + subMeshIndex++; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; + surfaceNormal = bestWorldTriangle.getNormal(); + if (pickAgainstTriangles) { + extraInfo["subMeshIndex"] = bestSubMeshIndex; + extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshTriangleWorld"] = QVariantMap{ + { "v0", vec3toVariant(bestWorldTriangle.v0) }, + { "v1", vec3toVariant(bestWorldTriangle.v1) }, + { "v2", vec3toVariant(bestWorldTriangle.v2) }, + }; + extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); + extraInfo["subMeshTriangle"] = QVariantMap{ + { "v0", vec3toVariant(bestModelTriangle.v0) }, + { "v1", vec3toVariant(bestModelTriangle.v1) }, + { "v2", vec3toVariant(bestModelTriangle.v2) }, + }; + } + } + } + + return intersectedSomething; +} + bool Model::convexHullContains(glm::vec3 point) { // if we aren't active, we can't compute that yet... if (!isActive()) { @@ -594,7 +694,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe } scene->enqueueTransaction(transaction); } - // update triangles for ray picking + // update triangles for picking { FBXGeometry geometry; for (const auto& newMesh : meshes) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 0bddae6a38..627e5fddab 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -178,6 +178,9 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); + bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index d015ab5478..95a69756aa 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "glm/gtc/matrix_transform.hpp" #include "NumericalConstants.h" #include "GLMHelpers.h" @@ -797,6 +798,62 @@ bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& ve return false; } +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface) { + // Check if we're hitting the backface in the rotated coordinate space + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * t; + if (!allowBackface && localIntersectionVelocityZ < 0.0f) { + return; + } + + // Check that the point is within all three sides + glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t; + if (glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && + glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f && + glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + minDistance = t; + } +} + +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) { + glm::vec3 normal = glm::cross(v2 - v1, v0 - v1); + + // We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin + glm::quat inverseRot; + // Note: OpenGL view matrix is already the inverse of our camera matrix + // if the direction is nearly aligned with the Y axis, then use the X axis for 'up' + const float MAX_ABS_Y_COMPONENT = 0.9999991f; + if (fabsf(normal.y) > MAX_ABS_Y_COMPONENT) { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_X)); + } else { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_Y)); + } + + glm::vec3 localOrigin = inverseRot * (origin - v0); + glm::vec3 localVelocity = inverseRot * velocity; + 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, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index a540093f40..fb32ee2e8a 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -94,6 +94,9 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& center, float radius, float& distance); +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); + /// \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) @@ -118,6 +121,11 @@ inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3 return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); } +inline bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, const Triangle& triangle, float& parabolicDistance, bool allowBackface = false) { + return findParabolaTriangleIntersection(origin, velocity, acceleration, triangle.v0, triangle.v1, triangle.v2, parabolicDistance, allowBackface); +} + int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle* clippedTriangles, int maxClippedTriangleCount); int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount); @@ -194,5 +202,9 @@ bool isWithin(float value, float corner, float size); void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale); +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface); #endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index d7f685f8d3..86e900cf4b 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -51,6 +51,26 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& return result; } +bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + // reset our distance to be the max possible, lower level tests will store best distance here + parabolicDistance = FLT_MAX; + + if (!_isBalanced) { + balanceOctree(); + } + + int trianglesTouched = 0; + auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface); + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return result; +} + bool TriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { return false; @@ -105,7 +125,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { // if our bounding box intersects at a distance greater than the current known - // best distance, and our origin isn't inside the boounds, then we can safely + // best distance, and our origin isn't inside the boounds, then we can safely // not check any of our triangles if (boxDistance > bestDistance && !_bounds.contains(origin)) { return false; @@ -127,6 +147,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec } } else { intersectedSomething = true; + // FIXME: this needs to set triangle to something or it will carry the previous value distance = boxDistance; } } @@ -134,6 +155,48 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec return intersectedSomething; } +bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + + bool intersectedSomething = false; + float boxDistance = parabolicDistance; + float bestDistance = parabolicDistance; + glm::vec3 surfaceNormal; + + if (_bounds.findParabolaIntersection(origin, velocity, acceleration, boxDistance, face, surfaceNormal)) { + + // if our bounding box intersects at a distance greater than the current known + // best distance, and our origin isn't inside the boounds, then we can safely + // not check any of our triangles + if (boxDistance > bestDistance && !_bounds.contains(origin)) { + return false; + } + + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + triangle = thisTriangle; + parabolicDistance = bestDistance; + } + } + } + } else { + intersectedSomething = true; + // FIXME: this needs to set triangle to something or it will carry the previous value + parabolicDistance = boxDistance; + } + } + + return intersectedSomething; +} + static const int MAX_DEPTH = 4; // for now static const int MAX_CHILDREN = 8; @@ -267,3 +330,64 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } return intersects; } + +bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = parabolicDistance; + BoxFace bestLocalFace; + Triangle bestLocalTriangle; + glm::vec3 bestLocalNormal; + bool intersects = false; + + // if the ray intersects our bounding box, then continue + if (getBounds().findParabolaIntersection(origin, velocity, acceleration, bestLocalDistance, bestLocalFace, bestLocalNormal)) { + // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) + // then we know that none of our triangles can represent a better intersection and we can return + if (bestLocalDistance > parabolicDistance) { + return false; + } + + bestLocalDistance = parabolicDistance; + + float childDistance = parabolicDistance; + BoxFace childFace; + Triangle childTriangle; + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + for (auto& child : _children) { + // check each child, if there's an intersection, it will return some distance that we need + // to compare against the other results, because there might be multiple intersections and + // we will always choose the best (shortest) intersection + if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + } + } + } + } + // also check our local triangle set + if (findParabolaIntersectionInternal(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + } + } + } + if (intersects) { + parabolicDistance = bestLocalDistance; + face = bestLocalFace; + triangle = bestLocalTriangle; + } + return intersects; +} \ No newline at end of file diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 2853d0f68e..0b0d0a9ac5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -31,6 +31,9 @@ class TriangleSet { bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); const AABox& getBounds() const { return _bounds; } @@ -43,6 +46,9 @@ class TriangleSet { bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); std::vector& _allTriangles; std::map _children; @@ -65,6 +71,8 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); void balanceOctree(); @@ -72,12 +80,6 @@ public: size_t size() const { return _triangles.size(); } void clear(); - // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an - // intersection occurs, the distance and surface normal will be provided. - // note: this might side-effect internal structures - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched); - // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a // convex hull, the result of this method is meaningless and undetermined. From 845ddda6952c097932c1e687816502ea1170295f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 17 Jul 2018 12:41:03 -0700 Subject: [PATCH 07/22] 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) From 86c56195d32791243c7c2276c331acfe88230563 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 17 Jul 2018 16:15:20 -0700 Subject: [PATCH 08/22] refactoring common path pointer functionality --- interface/src/raypick/LaserPointer.cpp | 361 ++++-------------- interface/src/raypick/LaserPointer.h | 129 ++----- interface/src/raypick/PathPointer.cpp | 323 ++++++++++++++++ interface/src/raypick/PathPointer.h | 130 +++++++ .../src/raypick/PointerScriptingInterface.cpp | 173 ++++++++- .../src/raypick/PointerScriptingInterface.h | 11 +- 6 files changed, 716 insertions(+), 411 deletions(-) create mode 100644 interface/src/raypick/PathPointer.cpp create mode 100644 interface/src/raypick/PathPointer.h diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 7860d6facf..27de800091 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -20,316 +20,106 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : - Pointer(DependencyManager::get()->createRayPick(rayProps), enabled, hover), - _triggers(triggers), - _renderStates(renderStates), - _defaultRenderStates(defaultRenderStates), - _faceAvatar(faceAvatar), - _followNormal(followNormal), - _centerEndY(centerEndY), - _lockEnd(lockEnd), - _distanceScaleEnd(distanceScaleEnd), - _scaleWithAvatar(scaleWithAvatar) + PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) { - for (auto& state : _renderStates) { - if (!enabled || state.first != _currentRenderState) { - disableRenderState(state.second); - } - } - for (auto& state : _defaultRenderStates) { - if (!enabled || state.first != _currentRenderState) { - disableRenderState(state.second.second); - } - } } -LaserPointer::~LaserPointer() { - for (auto& renderState : _renderStates) { - renderState.second.deleteOverlays(); - } - for (auto& renderState : _defaultRenderStates) { - renderState.second.second.deleteOverlays(); - } -} - -void LaserPointer::setRenderState(const std::string& state) { - withWriteLock([&] { - if (!_currentRenderState.empty() && state != _currentRenderState) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - } - if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } - } - _currentRenderState = state; - }); -} - -void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { - withWriteLock([&] { - updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); - updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); - updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); - QVariant endDim = endProps.toMap()["dimensions"]; - if (endDim.isValid()) { - _renderStates[state].setEndDim(vec3FromVariant(endDim)); - } +void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + updateRenderStateOverlay(renderState->getPathID(), pathProps); QVariant lineWidth = pathProps.toMap()["lineWidth"]; if (lineWidth.isValid()) { - _renderStates[state].setLineWidth(lineWidth.toFloat()); - } - QVariant rotation = pathProps.toMap()["rotation"]; - if (rotation.isValid()) { - _renderStates[state].setEndRot(quatFromVariant(rotation)); - } - }); -} - -PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) { - PickResultPointer visualPickResult = pickResult; - auto rayPickResult = std::static_pointer_cast(visualPickResult); - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - - if (type != IntersectionType::HUD) { - glm::vec3 endVec; - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - if (!_lockEndObject.id.isNull()) { - glm::vec3 pos; - glm::quat rot; - glm::vec3 dim; - glm::vec3 registrationPoint; - if (_lockEndObject.isOverlay) { - pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); - rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); - dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); - registrationPoint = glm::vec3(0.5f); - } else { - EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); - glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); - glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; - pos = extractTranslation(finalPosAndRotMat); - rot = glmExtractRotation(finalPosAndRotMat); - dim = props.getDimensions(); - registrationPoint = props.getRegistrationPoint(); - } - const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); - endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - - rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; - rayPickResult->objectID = _lockEndObject.id; - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); - } else if (type != IntersectionType::NONE && _lockEnd) { - if (type == IntersectionType::ENTITY) { - endVec = DependencyManager::get()->getEntityTransform(rayPickResult->objectID)[3]; - } else if (type == IntersectionType::OVERLAY) { - endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value); - } else if (type == IntersectionType::AVATAR) { - endVec = DependencyManager::get()->getAvatar(rayPickResult->objectID)->getPosition(); - } - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); + renderState->setLineWidth(lineWidth.toFloat()); } } - return visualPickResult; } -void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); +glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3()); +} + +glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) { + auto rayPickResult = std::static_pointer_cast(pickResult); + if (distance > 0.0f) { + PickRay pick = PickRay(rayPickResult->pickVariant); + return pick.origin + distance * pick.direction; + } else { + return rayPickResult->intersection; } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("position", vec3toVariant(pickRay.origin)); - startProps.insert("visible", true); - startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - glm::vec3 endVec = pickRay.origin + pickRay.direction * distance; - - QVariant end = vec3toVariant(endVec); - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("start", vec3toVariant(pickRay.origin)); - pathProps.insert("end", end); - pathProps.insert("visible", true); - pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays()); - if (_scaleWithAvatar) { - pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); - } - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - glm::quat faceAvatarRotation = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))); - glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value); - if (_distanceScaleEnd) { - dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec); - endProps.insert("dimensions", vec3toVariant(dim)); - } - if (_centerEndY) { - endProps.insert("position", end); - } else { - glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; - endProps.insert("position", vec3toVariant(endVec + currentUpVector * vec3(0.0f, 0.5f * dim.y, 0.0f))); - } - if (_faceAvatar) { - glm::quat rotation = _faceAvatar ? faceAvatarRotation : renderState.getEndRot(); - endProps.insert("rotation", quatToVariant(rotation)); - } - endProps.insert("visible", true); - endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); - } +glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3()); } -void LaserPointer::disableRenderState(const RenderState& renderState) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("visible", false); - startProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("visible", false); - pathProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - endProps.insert("visible", false); - endProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); - } +IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); } -void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) { - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay(); - QUuid uid = rayPickResult->objectID; - float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; - updateRenderState(_renderStates[_currentRenderState], type, distance, rayPickResult->surfaceNormal, uid, pickRay); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, Vectors::UP, QUuid(), pickRay); - } else if (!_currentRenderState.empty()) { - disableRenderState(_renderStates[_currentRenderState]); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } +QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->objectID : QUuid()); } -Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - if (!rayPickResult) { - return PickedObject(); - } - return PickedObject(rayPickResult->objectID, rayPickResult->type); +void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto rayPickResult = std::static_pointer_cast(pickResult); + rayPickResult->type = type; + rayPickResult->objectID = id; + rayPickResult->intersection = intersection; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = surfaceNormal; + rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); } -Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) { - std::unordered_set toReturn; - auto rayPickResult = std::static_pointer_cast(pickResult); - - if (rayPickResult) { - for (const PointerTrigger& trigger : _triggers) { - std::string button = trigger.getButton(); - TriggerState& state = _states[button]; - // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { - toReturn.insert(button); - - if (_previousButtons.find(button) == _previousButtons.end()) { - // start triggering for buttons that were just pressed - state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type); - state.intersection = rayPickResult->intersection; - state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection); - state.triggerStartTime = usecTimestampNow(); - state.surfaceNormal = rayPickResult->surfaceNormal; - state.deadspotExpired = false; - state.wasTriggering = true; - state.triggering = true; - _latestState = state; - } - } else { - // stop triggering for buttons that aren't pressed - state.wasTriggering = state.triggering; - state.triggering = false; - _latestState = state; - } - } - _previousButtons = toReturn; - } - - return toReturn; -} - -void LaserPointer::setLength(float length) { - withWriteLock([&] { - _laserLength = length; - }); -} - -void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { - withWriteLock([&] { - _lockEndObject.id = objectID; - _lockEndObject.isOverlay = isOverlay; - _lockEndObject.offsetMat = offsetMat; - }); -} - -RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : - _startID(startID), _pathID(pathID), _endID(endID) +LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : + StartEndRenderState(startID, endID), _pathID(pathID) { - if (!_startID.isNull()) { - _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); - } if (!_pathID.isNull()) { _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool(); _lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat(); } - if (!_endID.isNull()) { - _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); - _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); - _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); - } } -void RenderState::deleteOverlays() { - if (!_startID.isNull()) { - qApp->getOverlays().deleteOverlay(_startID); - } +void LaserPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); if (!_pathID.isNull()) { qApp->getOverlays().deleteOverlay(_pathID); } - if (!_endID.isNull()) { - qApp->getOverlays().deleteOverlay(_endID); +} + +void LaserPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("visible", false); + pathProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getPathID(), pathProps); } } -RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { +void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) { + StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal); + QVariant endVariant = vec3toVariant(end); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("start", vec3toVariant(origin)); + pathProps.insert("end", endVariant); + pathProps.insert("visible", true); + pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays()); + if (scaleWithAvatar) { + pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); + } + qApp->getOverlays().editOverlay(getPathID(), pathProps); + } +} + +std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid startID; if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); @@ -342,7 +132,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid pathID; if (propMap["path"].isValid()) { QVariantMap pathMap = propMap["path"].toMap(); - // right now paths must be line3ds + // laser paths must be line3ds if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { pathMap.remove("visible"); pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); @@ -358,7 +148,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { } } - return RenderState(startID, pathID, endID); + return std::make_shared(startID, pathID, endID); } PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { @@ -406,16 +196,3 @@ glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const return glm::vec3(NAN); } } - -glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { - switch (pickedObject.type) { - case ENTITY: - return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); - case HUD: - return DependencyManager::get()->calculatePos2DFromHUD(origin); - default: - return glm::vec2(NAN); - } -} diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 557bc60195..3afb3d262e 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -11,122 +11,53 @@ #ifndef hifi_LaserPointer_h #define hifi_LaserPointer_h -#include -#include - -#include "ui/overlays/Overlay.h" - -#include -#include - -struct LockEndObject { - QUuid id { QUuid() }; - bool isOverlay { false }; - glm::mat4 offsetMat { glm::mat4() }; -}; - -class RenderState { +#include "PathPointer.h" +class LaserPointer : public PathPointer { + using Parent = PathPointer; public: - RenderState() {} - RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + class RenderState : public StartEndRenderState { + public: + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); - const OverlayID& getStartID() const { return _startID; } - const OverlayID& getPathID() const { return _pathID; } - const OverlayID& getEndID() const { return _endID; } - const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } - const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } - const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + const OverlayID& getPathID() const { return _pathID; } + const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } - void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } - const glm::vec3& getEndDim() const { return _endDim; } + void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } + const float& getLineWidth() const { return _lineWidth; } - void setEndRot(const glm::quat& endRot) { _endRot = endRot; } - const glm::quat& getEndRot() const { return _endRot; } + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) override; - void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } - const float& getLineWidth() const { return _lineWidth; } + private: + OverlayID _pathID; + bool _pathIgnoreRays; - void deleteOverlays(); - -private: - OverlayID _startID; - OverlayID _pathID; - OverlayID _endID; - bool _startIgnoreRays; - bool _pathIgnoreRays; - bool _endIgnoreRays; - - glm::vec3 _endDim; - glm::quat _endRot; - float _lineWidth; -}; - -class LaserPointer : public Pointer { - using Parent = Pointer; -public: - typedef std::unordered_map RenderStateMap; - typedef std::unordered_map> DefaultRenderStateMap; + float _lineWidth; + }; LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); - ~LaserPointer(); - void setRenderState(const std::string& state) override; - // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. - void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; - - void setLength(float length) override; - void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; - - void updateVisuals(const PickResultPointer& prevRayPickResult) override; - - static RenderState buildRenderState(const QVariantMap& propMap); + static std::shared_ptr buildRenderState(const QVariantMap& propMap); protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; - PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; - PickedObject getHoveredObject(const PickResultPointer& pickResult) override; - Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; - - bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - private: - PointerTriggers _triggers; - float _laserLength { 0.0f }; - std::string _currentRenderState { "" }; - RenderStateMap _renderStates; - DefaultRenderStateMap _defaultRenderStates; - bool _faceAvatar; - bool _followNormal; - bool _centerEndY; - bool _lockEnd; - bool _distanceScaleEnd; - bool _scaleWithAvatar; - LockEndObject _lockEndObject; - - void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay); - void disableRenderState(const RenderState& renderState); - - struct TriggerState { - PickedObject triggeredObject; - glm::vec3 intersection { NAN }; - glm::vec3 surfaceNormal { NAN }; - glm::vec2 triggerPos2D { NAN }; - quint64 triggerStartTime { 0 }; - bool deadspotExpired { true }; - bool triggering { false }; - bool wasTriggering { false }; - }; - - Pointer::Buttons _previousButtons; - std::unordered_map _states; - TriggerState _latestState; static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); - static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); }; diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp new file mode 100644 index 0000000000..b625c0d3da --- /dev/null +++ b/interface/src/raypick/PathPointer.cpp @@ -0,0 +1,323 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PathPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" + +#include +#include +#include "PickScriptingInterface.h" +#include "RayPick.h" + +PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + Pointer(DependencyManager::get()->createPick(type, rayProps), enabled, hover), + _renderStates(renderStates), + _defaultRenderStates(defaultRenderStates), + _triggers(triggers), + _faceAvatar(faceAvatar), + _followNormal(followNormal), + _centerEndY(centerEndY), + _lockEnd(lockEnd), + _distanceScaleEnd(distanceScaleEnd), + _scaleWithAvatar(scaleWithAvatar) +{ + for (auto& state : _renderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second->disable(); + } + } + for (auto& state : _defaultRenderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second.second->disable(); + } + } +} + +PathPointer::~PathPointer() { + for (auto& renderState : _renderStates) { + renderState.second->cleanup(); + } + for (auto& renderState : _defaultRenderStates) { + renderState.second.second->cleanup(); + } +} + +void PathPointer::setRenderState(const std::string& state) { + withWriteLock([&] { + if (!_currentRenderState.empty() && state != _currentRenderState) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } + _currentRenderState = state; + }); +} + +void PathPointer::setLength(float length) { + withWriteLock([&] { + _pathLength = length; + }); +} + +void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { + withWriteLock([&] { + _lockEndObject.id = objectID; + _lockEndObject.isOverlay = isOverlay; + _lockEndObject.offsetMat = offsetMat; + }); +} + +PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { + PickResultPointer visualPickResult = pickResult; + glm::vec3 origin = getPickOrigin(pickResult); + IntersectionType type = getPickedObjectType(pickResult); + QUuid id; + glm::vec3 intersection; + float distance; + glm::vec3 surfaceNormal; + + if (type != IntersectionType::HUD) { + glm::vec3 endVec; + if (!_lockEndObject.id.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_lockEndObject.isOverlay) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); + glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); + glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; + pos = extractTranslation(finalPosAndRotMat); + rot = glmExtractRotation(finalPosAndRotMat); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + glm::vec3 direction = endVec - origin; + float distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + + type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + id = _lockEndObject.id; + intersection = endVec; + distance = distance; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } else if (type != IntersectionType::NONE && _lockEnd) { + id = getPickedObjectID(pickResult); + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->getEntityTransform(id)[3]; + } else if (type == IntersectionType::OVERLAY) { + endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value); + } else if (type == IntersectionType::AVATAR) { + endVec = DependencyManager::get()->getAvatar(id)->getPosition(); + } + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + type = type; + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } + } + return visualPickResult; +} + +void PathPointer::updateVisuals(const PickResultPointer& pickResult) { + IntersectionType type = getPickedObjectType(pickResult); + if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && + (type != IntersectionType::NONE || _pathLength > 0.0f || !_lockEndObject.id.isNull())) { + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _pathLength); + _renderStates[_currentRenderState]->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal); + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); + _defaultRenderStates[_currentRenderState].second->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal); + } else if (!_currentRenderState.empty()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } +} + +void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + withWriteLock([&] { + updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps); + updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps); + QVariant startDim = startProps.toMap()["dimensions"]; + if (startDim.isValid()) { + _renderStates[state]->setStartDim(vec3FromVariant(startDim)); + } + QVariant endDim = endProps.toMap()["dimensions"]; + if (endDim.isValid()) { + _renderStates[state]->setEndDim(vec3FromVariant(endDim)); + } + QVariant rotation = pathProps.toMap()["rotation"]; + if (rotation.isValid()) { + _renderStates[state]->setEndRot(quatFromVariant(rotation)); + } + + editRenderStatePath(state, pathProps); + }); +} + +void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); + } +} + +Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) { + return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); +} + +Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) { + std::unordered_set toReturn; + + for (const PointerTrigger& trigger : _triggers) { + std::string button = trigger.getButton(); + TriggerState& state = _states[button]; + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(button); + + if (_previousButtons.find(button) == _previousButtons.end()) { + // start triggering for buttons that were just pressed + state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); + state.intersection = getPickEnd(pickResult); + state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection); + state.triggerStartTime = usecTimestampNow(); + state.surfaceNormal = getPickedObjectNormal(pickResult); + state.deadspotExpired = false; + state.wasTriggering = true; + state.triggering = true; + _latestState = state; + } + } else { + // stop triggering for buttons that aren't pressed + state.wasTriggering = state.triggering; + state.triggering = false; + _latestState = state; + } + } + _previousButtons = toReturn; + return toReturn; +} + +StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) : + _startID(startID), _endID(endID) { + if (!_startID.isNull()) { + _startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value); + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); + _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void StartEndRenderState::cleanup() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} + +void StartEndRenderState::disable() { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + if (!getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } +} + +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(origin)); + startProps.insert("visible", true); + if (scaleWithAvatar) { + startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale())); + } + startProps.insert("ignoreRayIntersection", doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + + if (!getEndID().isNull()) { + QVariantMap endProps; + glm::quat faceAvatarRotation = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))); + glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value); + if (distanceScaleEnd) { + dim = getEndDim() * glm::distance(origin, end); + endProps.insert("dimensions", vec3toVariant(dim)); + } else if (scaleWithAvatar) { + dim = getEndDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + endProps.insert("dimensions", vec3toVariant(dim)); + } + if (centerEndY) { + endProps.insert("position", vec3toVariant(end)); + } else { + glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; + endProps.insert("position", vec3toVariant(end + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y))); + } + if (faceAvatar) { + endProps.insert("rotation", quatToVariant(faceAvatarRotation)); + } + endProps.insert("visible", true); + endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } +} + +glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); + case OVERLAY: + return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); + case HUD: + return DependencyManager::get()->calculatePos2DFromHUD(origin); + default: + return glm::vec2(NAN); + } +} \ No newline at end of file diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h new file mode 100644 index 0000000000..ea8e2eff6e --- /dev/null +++ b/interface/src/raypick/PathPointer.h @@ -0,0 +1,130 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PathPointer_h +#define hifi_PathPointer_h + +#include +#include +#include + +#include "ui/overlays/Overlay.h" + +#include +#include + +struct LockEndObject { + QUuid id { QUuid() }; + bool isOverlay { false }; + glm::mat4 offsetMat { glm::mat4() }; +}; + +class StartEndRenderState { +public: + StartEndRenderState() {} + StartEndRenderState(const OverlayID& startID, const OverlayID& endID); + + const OverlayID& getStartID() const { return _startID; } + const OverlayID& getEndID() const { return _endID; } + const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } + const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + + void setStartDim(const glm::vec3& startDim) { _startDim = startDim; } + const glm::vec3& getStartDim() const { return _startDim; } + + void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } + const glm::vec3& getEndDim() const { return _endDim; } + + void setEndRot(const glm::quat& endRot) { _endRot = endRot; } + const glm::quat& getEndRot() const { return _endRot; } + + virtual void cleanup(); + virtual void disable(); + virtual void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal); + +protected: + OverlayID _startID; + OverlayID _endID; + bool _startIgnoreRays; + bool _endIgnoreRays; + + glm::vec3 _startDim; + glm::vec3 _endDim; + glm::quat _endRot; +}; + +typedef std::unordered_map> RenderStateMap; +typedef std::unordered_map>> DefaultRenderStateMap; + +class PathPointer : public Pointer { + using Parent = Pointer; +public: + PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + virtual ~PathPointer(); + + void setRenderState(const std::string& state) override; + // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. + void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; + + void setLength(float length) override; + void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; + + void updateVisuals(const PickResultPointer& prevRayPickResult) override; + +protected: + PointerTriggers _triggers; + float _pathLength { 0.0f }; + RenderStateMap _renderStates; + DefaultRenderStateMap _defaultRenderStates; + std::string _currentRenderState { "" }; + bool _faceAvatar; + bool _followNormal; + bool _centerEndY; + bool _lockEnd; + bool _distanceScaleEnd; + bool _scaleWithAvatar; + LockEndObject _lockEndObject; + + struct TriggerState { + PickedObject triggeredObject; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + glm::vec2 triggerPos2D { NAN }; + quint64 triggerStartTime { 0 }; + bool deadspotExpired { true }; + bool triggering { false }; + bool wasTriggering { false }; + }; + + Pointer::Buttons _previousButtons; + std::unordered_map _states; + TriggerState _latestState; + + bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + + void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0; + + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; + Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; + + PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; + virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) = 0; + virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) = 0; + virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) = 0; + virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) = 0; + virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) = 0; + virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0; + + static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); +}; + +#endif // hifi_PathPointer_h diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 8efc7f2199..fdcdee2218 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -37,6 +37,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& return createLaserPointer(properties); case PickQuery::PickType::Stylus: return createStylus(properties); + case PickQuery::PickType::Parabola: + return createParabolaPointer(properties); default: return PointerEvent::INVALID_POINTER_ID; } @@ -84,27 +86,20 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the end of the Ray Pointer, if desired. */ -/**jsdoc - * A trigger mechanism for Ray Pointers. - * - * @typedef {object} Pointers.Trigger - * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. - * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, - * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". - */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.LaserPointerProperties - * @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar. - * @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height. - * @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. - * @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance. - * @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. + * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. + * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. + * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. + * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. * @property {boolean} [enabled=false] - * @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between. - * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection. + * @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between. + * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. * @property {boolean} [hover=false] If this Pointer should generate hover events. - * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation. + * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -144,7 +139,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope enabled = propertyMap["enabled"].toBool(); } - LaserPointer::RenderStateMap renderStates; + RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -158,7 +153,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } } - LaserPointer::DefaultRenderStateMap defaultRenderStates; + DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -167,7 +162,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair(distance, LaserPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); } } } @@ -201,6 +196,146 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar, enabled)); } +/**jsdoc +* The rendering properties of the parabolic path +* +* @typedef {object} Pointers.ParabolaProperties +* @property {Color} color The color of the parabola. +* @property {number} alpha The alpha of the parabola. +* @property {number} width The width of the parabola, in meters. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, +* but with an additional distance field. +* +* @typedef {object} Pointers.DefaultParabolaPointerRenderState +* @augments Pointers.ParabolaPointerRenderState +* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. +* +* @typedef {object} Pointers.ParabolaPointerRenderState +* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} +* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* An overlay to represent the beginning of the Parabola Pointer, if desired. +* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer. +* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* An overlay to represent the end of the Parabola Pointer, if desired. +*/ +/**jsdoc +* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. +* @typedef {object} Pointers.LaserPointerProperties +* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. +* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. +* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. +* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. +* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. +* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. +* @property {boolean} [enabled=false] +* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between. +* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. +* @property {boolean} [hover=false] If this Pointer should generate hover events. +* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. +*/ +unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { + return 0; +#if 0 + QVariantMap propertyMap = properties.toMap(); + + bool faceAvatar = false; + if (propertyMap["faceAvatar"].isValid()) { + faceAvatar = propertyMap["faceAvatar"].toBool(); + } + + bool centerEndY = true; + if (propertyMap["centerEndY"].isValid()) { + centerEndY = propertyMap["centerEndY"].toBool(); + } + + bool lockEnd = false; + if (propertyMap["lockEnd"].isValid()) { + lockEnd = propertyMap["lockEnd"].toBool(); + } + + bool distanceScaleEnd = false; + if (propertyMap["distanceScaleEnd"].isValid()) { + distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); + } + + bool scaleWithAvatar = false; + if (propertyMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); + } + + bool followNormal = true; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + ParabolaPointer::RenderStateMap renderStates; + if (propertyMap["renderStates"].isValid()) { + QList renderStateVariants = propertyMap["renderStates"].toList(); + for (const QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap); + } + } + } + } + + ParabolaPointer::DefaultRenderStateMap defaultRenderStates; + if (propertyMap["defaultRenderStates"].isValid()) { + QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); + for (const QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + float distance = renderStateMap["distance"].toFloat(); + defaultRenderStates[name] = std::pair(distance, ParabolaPointer::buildRenderState(renderStateMap)); + } + } + } + } + + bool hover = false; + if (propertyMap["hover"].isValid()) { + hover = propertyMap["hover"].toBool(); + } + + PointerTriggers triggers; + auto userInputMapper = DependencyManager::get(); + if (propertyMap["triggers"].isValid()) { + QList triggerVariants = propertyMap["triggers"].toList(); + for (const QVariant& triggerVariant : triggerVariants) { + if (triggerVariant.isValid()) { + QVariantMap triggerMap = triggerVariant.toMap(); + if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { + controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); + if (endpoint) { + std::string button = triggerMap["button"].toString().toStdString(); + triggers.emplace_back(endpoint, button); + } + } + } + } + } + + return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, + faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd, + scaleWithAvatar, enabled)); +#endif +} + void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { QVariantMap propMap = properties.toMap(); diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 628af84790..9fe05182c7 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency { public: unsigned int createLaserPointer(const QVariant& properties) const; unsigned int createStylus(const QVariant& properties) const; + unsigned int createParabolaPointer(const QVariant& properties) const; + /**jsdoc + * A trigger mechanism for Ray and Parabola Pointers. + * + * @typedef {object} Pointers.Trigger + * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. + * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, + * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". + */ /**jsdoc * Adds a new Pointer * Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example, * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer. * @function Pointers.createPointer * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that + * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that * this Pointer will use to do its picking. * @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid. * From 39fa3420ec534b1b71fa6cd4d43fed8e08e64060 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 18 Jul 2018 17:01:42 -0700 Subject: [PATCH 09/22] pointer rendering, update teleport.js --- interface/src/raypick/LaserPointer.cpp | 33 +- interface/src/raypick/LaserPointer.h | 3 +- interface/src/raypick/ParabolaPointer.cpp | 390 ++++++++++++++++++ interface/src/raypick/ParabolaPointer.h | 118 ++++++ interface/src/raypick/PathPointer.cpp | 15 +- interface/src/raypick/PathPointer.h | 5 +- .../src/raypick/PointerScriptingInterface.cpp | 10 +- libraries/render-utils/src/parabola.slf | 18 + libraries/render-utils/src/parabola.slv | 53 +++ .../controllers/controllerModules/teleport.js | 131 +++--- 10 files changed, 682 insertions(+), 94 deletions(-) create mode 100644 interface/src/raypick/ParabolaPointer.cpp create mode 100644 interface/src/raypick/ParabolaPointer.h create mode 100644 libraries/render-utils/src/parabola.slf create mode 100644 libraries/render-utils/src/parabola.slv diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 27de800091..16bb4e25e1 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -14,8 +14,6 @@ #include "avatar/AvatarManager.h" #include -#include -#include "PickScriptingInterface.h" #include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, @@ -69,12 +67,14 @@ QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) { void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { auto rayPickResult = std::static_pointer_cast(pickResult); - rayPickResult->type = type; - rayPickResult->objectID = id; - rayPickResult->intersection = intersection; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = surfaceNormal; - rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); + if (rayPickResult) { + rayPickResult->type = type; + rayPickResult->objectID = id; + rayPickResult->intersection = intersection; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = surfaceNormal; + rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); + } } LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : @@ -103,8 +103,9 @@ void LaserPointer::RenderState::disable() { } } -void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) { - StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal); +void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); QVariant endVariant = vec3toVariant(end); if (!getPathID().isNull()) { QVariantMap pathProps; @@ -188,11 +189,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { - case ENTITY: - return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); - default: - return glm::vec3(NAN); + case ENTITY: + return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); + case OVERLAY: + return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); + default: + return glm::vec3(NAN); } } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 3afb3d262e..5030c90186 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -29,7 +29,8 @@ public: void cleanup() override; void disable() override; - void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) override; + void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; private: OverlayID _pathID; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp new file mode 100644 index 0000000000..aee5b8cfa1 --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -0,0 +1,390 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ParabolaPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" +#include + +#include +#include "ParabolaPick.h" + +#include "render-utils/parabola_vert.h" +#include "render-utils/parabola_frag.h" + +const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f }; +const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f }; +const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false }; + +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr }; +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr }; + +ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) +{ +} + +void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + QVariantMap pathMap = pathProps.toMap(); + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (!pathMap.isEmpty()) { + enabled = true; + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + } + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled); + } +} + +glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3()); +} + +glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (distance > 0.0f) { + PickParabola pick = PickParabola(parabolaPickResult->pickVariant); + return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance; + } else { + return parabolaPickResult->intersection; + } +} + +glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3()); +} + +IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE); +} + +QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->objectID : QUuid()); +} + +void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + parabolaPickResult->type = type; + parabolaPickResult->objectID = id; + parabolaPickResult->intersection = intersection; + parabolaPickResult->distance = distance; + parabolaPickResult->surfaceNormal = surfaceNormal; + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin - + 0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance); + } +} + +ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled) : + StartEndRenderState(startID, endID) +{ + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + _pathID = scene->allocateID(); + if (render::Item::isValidID(_pathID)) { + auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled); + // TODO: update bounds properly + renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f); + transaction.resetItem(_pathID, std::make_shared(renderItem)); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.removeItem(_pathID); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [](ParabolaRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) { + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) { + item.setColor(color); + item.setAlpha(alpha); + item.setWidth(width); + item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera); + item.setEnabled(enabled); + item.updateKey(); + item.updateUniformBuffer(); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult && render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + glm::vec3 velocity = parabola.velocity; + glm::vec3 acceleration = parabola.acceleration; + float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance; + transaction.updateItem(_pathID, [origin, velocity, acceleration, parabolicDistance](ParabolaRenderItem& item) { + item.setVisible(true); + item.setOrigin(origin); + item.setVelocity(velocity); + item.setAcceleration(acceleration); + item.setParabolicDistance(parabolicDistance); + item.updateUniformBuffer(); + }); + scene->enqueueTransaction(transaction); + } +} + +std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap) { + QUuid startID; + if (propMap["start"].isValid()) { + QVariantMap startMap = propMap["start"].toMap(); + if (startMap["type"].isValid()) { + startMap.remove("visible"); + startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + } + } + + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (propMap["path"].isValid()) { + enabled = true; + QVariantMap pathMap = propMap["path"].toMap(); + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + } + + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + + QUuid endID; + if (propMap["end"].isValid()) { + QVariantMap endMap = propMap["end"].toMap(); + if (endMap["type"].isValid()) { + endMap.remove("visible"); + endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + } + } + + return std::make_shared(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled); +} + +PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration; + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + intersection = parabolaPickResult->intersection; + surfaceNormal = parabolaPickResult->surfaceNormal; + const QVariantMap& parabola = parabolaPickResult->pickVariant; + origin = vec3FromVariant(parabola["origin"]); + velocity = vec3FromVariant(parabola["velocity"]); + acceleration = vec3FromVariant(parabola["acceleration"]); + pickedID = parabolaPickResult->objectID; + } + + if (pickedID != target.objectID) { + intersection = findIntersection(target, origin, velocity, acceleration); + } + glm::vec2 pos2D = findPos2D(target, intersection); + + // If we just started triggering and we haven't moved too much, don't update intersection and pos2D + TriggerState& state = hover ? _latestState : _states[button]; + float sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; + bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared; + if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) { + pos2D = state.triggerPos2D; + intersection = state.intersection; + surfaceNormal = state.surfaceNormal; + } + if (!withinDeadspot) { + state.deadspotExpired = true; + } + + return PointerEvent(pos2D, intersection, surfaceNormal, velocity); +} + +glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) { + // TODO: implement + switch (pickedObject.type) { + case ENTITY: + //return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration); + case OVERLAY: + //return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration); + default: + return glm::vec3(NAN); + } +} + +ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled) : + _isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled) +{ + _uniformBuffer->resize(sizeof(ParabolaData)); + setColor(color); + setAlpha(alpha); + setWidth(width); + updateKey(); + updateUniformBuffer(); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) { + if (visible && _enabled) { + _key = render::ItemKey::Builder(_key).withVisible(); + } else { + _key = render::ItemKey::Builder(_key).withInvisible(); + } +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() { + auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape(); + + if (_enabled) { + builder.withVisible(); + } else { + builder.withInvisible(); + } + + if (_isVisibleInSecondaryCamera) { + builder.withTagBits(render::hifi::TAG_ALL_VIEWS); + } else { + builder.withTagBits(render::hifi::TAG_MAIN_VIEW); + } + + _key = builder.build(); +} + +const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() { + if (!_parabolaPipeline || !_transparentParabolaPipeline) { + auto vs = parabola_vert::getShader(); + auto ps = parabola_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _parabolaPipeline = gpu::Pipeline::create(program, state); + } + + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMask(*state); + state->setCullMode(gpu::State::CULL_NONE); + _transparentParabolaPipeline = gpu::Pipeline::create(program, state); + } + } + return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + + Transform transform; + transform.setTranslation(_origin); + batch.setModelTransform(transform); + + batch.setPipeline(getParabolaPipeline()); + + batch.setUniformBuffer(0, _uniformBuffer); + + const int NUM_SECTIONS = 25; // must match value in parabola.slv + // We draw 2 * n + 2 vertices for a triangle strip + batch.draw(gpu::TRIANGLE_STRIP, 2 * NUM_SECTIONS + 2, 0); +} + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return payload->getKey(); + } + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + if (payload) { + return payload->getBound(); + } + return Item::Bound(); + } + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) { + if (payload) { + payload->render(args); + } + } + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return ShapeKey::Builder::ownPipeline(); + } +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h new file mode 100644 index 0000000000..827134eaf5 --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.h @@ -0,0 +1,118 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_ParabolaPointer_h +#define hifi_ParabolaPointer_h + +#include "PathPointer.h" + +class ParabolaPointer : public PathPointer { + using Parent = PathPointer; +public: + class RenderState : public StartEndRenderState { + public: + class ParabolaRenderItem { + public: + using Payload = render::Payload; + using Pointer = Payload::DataPointer; + + ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled); + ~ParabolaRenderItem() {} + + static gpu::PipelinePointer _parabolaPipeline; + static gpu::PipelinePointer _transparentParabolaPipeline; + const gpu::PipelinePointer getParabolaPipeline(); + + void render(RenderArgs* args); + render::Item::Bound& editBound() { return _bound; } + const render::Item::Bound& getBound() { return _bound; } + render::ItemKey getKey() const { return _key; } + + void setVisible(bool visible); + void updateKey(); + void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); } + + void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); } + void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; } + void setWidth(const float& width) { _parabolaData.width = width; } + void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; } + void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; } + void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; } + void setOrigin(const glm::vec3& origin) { _origin = origin; } + void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; } + void setEnabled(const bool& enabled) { _enabled = enabled; } + + static const glm::vec4 DEFAULT_PARABOLA_COLOR; + static const float DEFAULT_PARABOLA_WIDTH; + static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + + private: + render::Item::Bound _bound; + render::ItemKey _key; + + glm::vec3 _origin { 0.0f }; + bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; + bool _enabled { false }; + + struct ParabolaData { + glm::vec3 velocity { 0.0f }; + float parabolicDistance { 0.0f }; + vec3 acceleration { 0.0f }; + float width { DEFAULT_PARABOLA_WIDTH }; + vec4 color { vec4(DEFAULT_PARABOLA_COLOR)}; + }; + + ParabolaData _parabolaData; + gpu::BufferPointer _uniformBuffer { std::make_shared() }; + }; + + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled); + + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; + + void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); + + private: + int _pathID; + }; + + ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + + static std::shared_ptr buildRenderState(const QVariantMap& propMap); + +protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + +private: + static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration); +}; + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args); + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); +} + +#endif // hifi_ParabolaPointer_h diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index b625c0d3da..1582b62ab2 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -78,6 +78,8 @@ void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, co }); } +#include "ParabolaPick.h" + PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { PickResultPointer visualPickResult = pickResult; glm::vec3 origin = getPickOrigin(pickResult); @@ -144,10 +146,11 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick void PathPointer::updateVisuals(const PickResultPointer& pickResult) { IntersectionType type = getPickedObjectType(pickResult); if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (type != IntersectionType::NONE || _pathLength > 0.0f || !_lockEndObject.id.isNull())) { + (type != IntersectionType::NONE || _pathLength > 0.0f)) { glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _pathLength); - _renderStates[_currentRenderState]->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal); + _renderStates[_currentRenderState]->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + _followNormal, _pathLength, pickResult); if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { _defaultRenderStates[_currentRenderState].second->disable(); } @@ -157,7 +160,8 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { } glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); - _defaultRenderStates[_currentRenderState].second->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal); + _defaultRenderStates[_currentRenderState].second->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _faceAvatar, _followNormal, _defaultRenderStates[_currentRenderState].first, pickResult); } else if (!_currentRenderState.empty()) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { _renderStates[_currentRenderState]->disable(); @@ -180,7 +184,7 @@ void PathPointer::editRenderState(const std::string& state, const QVariant& star if (endDim.isValid()) { _renderStates[state]->setEndDim(vec3FromVariant(endDim)); } - QVariant rotation = pathProps.toMap()["rotation"]; + QVariant rotation = endProps.toMap()["rotation"]; if (rotation.isValid()) { _renderStates[state]->setEndRot(quatFromVariant(rotation)); } @@ -271,7 +275,8 @@ void StartEndRenderState::disable() { } } -void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) { +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { if (!getStartID().isNull()) { QVariantMap startProps; startProps.insert("position", vec3toVariant(origin)); diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index ea8e2eff6e..ff40f47646 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -44,7 +44,8 @@ public: virtual void cleanup(); virtual void disable(); - virtual void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal); + virtual void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult); protected: OverlayID _startID; @@ -69,7 +70,7 @@ public: virtual ~PathPointer(); void setRenderState(const std::string& state) override; - // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. + // You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; void setLength(float length) override; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index fdcdee2218..486e208647 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "LaserPointer.h" #include "StylusPointer.h" +#include "ParabolaPointer.h" void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -239,8 +240,6 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { - return 0; -#if 0 QVariantMap propertyMap = properties.toMap(); bool faceAvatar = false; @@ -278,7 +277,7 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr enabled = propertyMap["enabled"].toBool(); } - ParabolaPointer::RenderStateMap renderStates; + RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -292,7 +291,7 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr } } - ParabolaPointer::DefaultRenderStateMap defaultRenderStates; + DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -301,7 +300,7 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair(distance, ParabolaPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, ParabolaPointer::buildRenderState(renderStateMap)); } } } @@ -333,7 +332,6 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); -#endif } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { diff --git a/libraries/render-utils/src/parabola.slf b/libraries/render-utils/src/parabola.slf new file mode 100644 index 0000000000..ae7a44ddd1 --- /dev/null +++ b/libraries/render-utils/src/parabola.slf @@ -0,0 +1,18 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +in vec4 _color; + +out vec4 _fragColor; + +void main(void) { + _fragColor = _color; +} diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv new file mode 100644 index 0000000000..a8c892ab00 --- /dev/null +++ b/libraries/render-utils/src/parabola.slv @@ -0,0 +1,53 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +layout(std140) uniform parabolaData { + vec3 velocity; + float parabolicDistance; + vec3 acceleration; + float width; + vec4 color; +}; + +out vec4 _color; + +void main(void) { + _color = color; + + const int NUM_SECTIONS = 25; // must match value in ParabolaPointer.cpp + float t = parabolicDistance * (floor(gl_VertexID / 2) / float(NUM_SECTIONS)); + + vec4 pos = vec4(velocity * t + 0.5 * acceleration * t * t, 1); + const float EPSILON = 0.00001; + vec4 normal; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + if (dot(acceleration, acceleration) < EPSILON) { + // Handle case where acceleration == (0, 0, 0) + vec3 eyeUp = vec3(0, 1, 0); + vec3 worldUp; + <$transformEyeToWorldDir(cam, eyeUp, worldUp)$> + normal = vec4(normalize(cross(velocity, worldUp)), 0); + } else { + normal = vec4(normalize(cross(velocity, acceleration)), 0); + } + if (gl_VertexID % 2 == 0) { + pos += 0.5 * width * normal; + } else { + pos -= 0.5 * width * normal; + } + + <$transformModelToClipPos(cam, obj, pos, gl_Position)$> +} \ No newline at end of file diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 3bf99ca26a..12e3f6ab3a 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -62,49 +62,37 @@ Script.include("/~/system/libraries/controllers.js"); }; var cancelPath = { - type: "line3d", color: COLORS_TELEPORT_CANCEL, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 + width: 0.025 }; var teleportPath = { - type: "line3d", color: COLORS_TELEPORT_CAN_TELEPORT, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 + width: 0.025 }; var seatPath = { - type: "line3d", color: COLORS_TELEPORT_SEAT, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 + width: 0.025 }; var cancelEnd = { type: "model", url: TOO_CLOSE_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignoreParabolaIntersection: true }; var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignoreParabolaIntersection: true }; var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignoreParabolaIntersection: true }; @@ -112,7 +100,7 @@ Script.include("/~/system/libraries/controllers.js"); {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}]; - var DEFAULT_DISTANCE = 50; + var DEFAULT_DISTANCE = 10; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var coolInTimeout = null; @@ -134,6 +122,9 @@ Script.include("/~/system/libraries/controllers.js"); SEAT: 'seat' // The current target is a seat }; + var speed = 7.0; + var accelerationAxis = {x: 0.0, y: -5.0, z: 0.0}; + function Teleporter(hand) { var _this = this; this.hand = hand; @@ -149,46 +140,58 @@ Script.include("/~/system/libraries/controllers.js"); return otherModule; }; - this.teleportRayHandVisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, { joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + followNormal: true, + speed: speed, + accelerationAxis: accelerationAxis, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); - this.teleportRayHandInvisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + followNormal: true, + speed: speed, + accelerationAxis: accelerationAxis, renderStates: teleportRenderStates }); - this.teleportRayHeadVisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + followNormal: true, + speed: speed, + accelerationAxis: accelerationAxis, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); - this.teleportRayHeadInvisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + followNormal: true, + speed: speed, + accelerationAxis: accelerationAxis, renderStates: teleportRenderStates }); this.cleanup = function() { - Pointers.removePointer(this.teleportRayHandVisible); - Pointers.removePointer(this.teleportRayHandInvisible); - Pointers.removePointer(this.teleportRayHeadVisible); - Pointers.removePointer(this.teleportRayHeadInvisible); + Pointers.removePointer(this.teleportParabolaHandVisible); + Pointers.removePointer(this.teleportParabolaHandInvisible); + Pointers.removePointer(this.teleportParabolaHeadVisible); + Pointers.removePointer(this.teleportParabolaHeadInvisible); }; this.buttonPress = function(value) { @@ -225,20 +228,20 @@ Script.include("/~/system/libraries/controllers.js"); {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}]; - Pointers.editRenderState(this.teleportRayHandVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHandInvisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "cancel", teleportRenderStates[0]); + Pointers.editRenderState(this.teleportParabolaHandVisible, "cancel", teleportRenderStates[0]); + Pointers.editRenderState(this.teleportParabolaHandInvisible, "cancel", teleportRenderStates[0]); + Pointers.editRenderState(this.teleportParabolaHeadVisible, "cancel", teleportRenderStates[0]); + Pointers.editRenderState(this.teleportParabolaHeadInvisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHandVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHandInvisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "teleport", teleportRenderStates[1]); + Pointers.editRenderState(this.teleportParabolaHandVisible, "teleport", teleportRenderStates[1]); + Pointers.editRenderState(this.teleportParabolaHandInvisible, "teleport", teleportRenderStates[1]); + Pointers.editRenderState(this.teleportParabolaHeadVisible, "teleport", teleportRenderStates[1]); + Pointers.editRenderState(this.teleportParabolaHeadInvisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHandVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHandInvisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "seat", teleportRenderStates[2]); + Pointers.editRenderState(this.teleportParabolaHandVisible, "seat", teleportRenderStates[2]); + Pointers.editRenderState(this.teleportParabolaHandInvisible, "seat", teleportRenderStates[2]); + Pointers.editRenderState(this.teleportParabolaHeadVisible, "seat", teleportRenderStates[2]); + Pointers.editRenderState(this.teleportParabolaHeadInvisible, "seat", teleportRenderStates[2]); } }; @@ -258,18 +261,18 @@ Script.include("/~/system/libraries/controllers.js"); var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.enablePointer(_this.teleportRayHeadVisible); - Pointers.enablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.enablePointer(_this.teleportParabolaHeadVisible); + Pointers.enablePointer(_this.teleportParabolaHeadInvisible); } else { - Pointers.enablePointer(_this.teleportRayHandVisible); - Pointers.enablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.enablePointer(_this.teleportParabolaHandVisible); + Pointers.enablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); } - // We do up to 2 ray picks to find a teleport location. + // We do up to 2 picks to find a teleport location. // There are 2 types of teleport locations we are interested in: // 1. A visible floor. This can be any entity surface that points within some degree of "up" // 2. A seat. The seat can be visible or invisible. @@ -280,17 +283,17 @@ Script.include("/~/system/libraries/controllers.js"); // var result; if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); } var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); } teleportLocationType = getTeleportTargetType(result); } @@ -336,27 +339,27 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); }; this.setTeleportState = function(mode, visibleState, invisibleState) { if (mode === 'head') { - Pointers.setRenderState(_this.teleportRayHeadVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState); } else { - Pointers.setRenderState(_this.teleportRayHandVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHandInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState); } }; this.setIgnoreEntities = function(entitiesToIgnore) { - Pointers.setIgnoreItems(this.teleportRayHandVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHandInvisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore); }; } From 564578aac6d8531e92b39f9c8c7d8f773696ba16 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 19 Jul 2018 14:54:49 -0700 Subject: [PATCH 10/22] parabola aabox fixes, refactoring some intersection math --- libraries/shared/src/AABox.cpp | 241 +------------ libraries/shared/src/AACube.cpp | 242 +------------ libraries/shared/src/AACube.h | 3 - libraries/shared/src/GeometryUtil.cpp | 479 ++++++++++++++++++++++++++ libraries/shared/src/GeometryUtil.h | 10 + 5 files changed, 497 insertions(+), 478 deletions(-) diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index d8433e7fda..0d2e6295d0 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -114,9 +114,7 @@ bool AABox::contains(const Triangle& triangle) const { } bool AABox::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale.x) && - isWithin(point.y, _corner.y, _scale.y) && - isWithin(point.z, _corner.z, _scale.z); + return aaBoxContains(point, _corner, _scale); } bool AABox::contains(const AABox& otherBox) const { @@ -170,30 +168,6 @@ bool AABox::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale.z, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -220,219 +194,12 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } - - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; -} - -void AABox::checkPossibleParabolicIntersection(float t, int i, float& minDistance, - const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const { - if (t < minDistance && t > 0.0f && - isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, _corner[(i + 1) % 3], _scale[(i + 1) % 3]) && - isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, _corner[(i + 2) % 3], _scale[(i + 2) % 3])) { - minDistance = t; - hit = true; - } + return findRayAABoxIntersection(origin, direction, _corner, _scale, distance, face, surfaceNormal); } bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { - float minDistance = FLT_MAX; - BoxFace minFace; - glm::vec3 minNormal; - glm::vec2 possibleDistances; - float a, b, c; - - // 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++) { - 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]) { - { // 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 { - 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; - } - } - } - } 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; - } - } - } - } - } - } - - if (minDistance < FLT_MAX) { - parabolicDistance = minDistance; - face = minFace; - surfaceNormal = minNormal; - return true; - } - return false; + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, _scale, parabolicDistance, face, surfaceNormal); } bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 43e97f9e3e..dc1003215d 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -111,9 +111,7 @@ glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const { } bool AACube::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale) && - isWithin(point.y, _corner.y, _scale) && - isWithin(point.z, _corner.z, _scale); + return aaBoxContains(point, _corner, glm::vec3(_scale)); } bool AACube::contains(const AACube& otherCube) const { @@ -165,30 +163,6 @@ bool AACube::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -215,220 +189,12 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { - - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } - - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; -} - -void AACube::checkPossibleParabolicIntersection(float t, int i, float& minDistance, - const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const { - if (t < minDistance && t > 0.0f && - isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, _corner[(i + 1) % 3], _scale) && - isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, _corner[(i + 2) % 3], _scale)) { - minDistance = t; - hit = true; - } + return findRayAABoxIntersection(origin, direction, _corner, glm::vec3(_scale), distance, face, surfaceNormal); } bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { - float minDistance = FLT_MAX; - BoxFace minFace; - glm::vec3 minNormal; - glm::vec2 possibleDistances; - float a, b, c; - - // 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++) { - 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) { - { // 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 { - 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; - } - } - } - } 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; - } - } - } - } - } - } - - if (minDistance < FLT_MAX) { - parabolicDistance = minDistance; - face = minFace; - surfaceNormal = minNormal; - return true; - } - return false; + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, glm::vec3(_scale), parabolicDistance, face, surfaceNormal); } bool AACube::touchesSphere(const glm::vec3& center, float radius) const { diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index df00b8aefc..72aed31999 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -78,9 +78,6 @@ private: static BoxFace getOppositeFace(BoxFace face); - void checkPossibleParabolicIntersection(float t, int i, float& minDistance, - const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const; - glm::vec3 _corner; float _scale; }; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 750dd464a6..1d470c42c3 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -190,6 +190,94 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration - (currentDirection * directionalComponent); } +// finds the intersection between a ray and the facing plane on one axis +bool findIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = (corner - origin) / direction; + return true; + } else if (direction < -EPSILON) { + distance = (corner + size - origin) / direction; + return true; + } + return false; +} + +// finds the intersection between a ray and the inside facing plane on one axis +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = -1.0f * (origin - (corner + size)) / direction; + return true; + } else if (direction < -EPSILON) { + distance = -1.0f * (origin - corner) / direction; + return true; + } + return false; +} + +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, + BoxFace& face, glm::vec3& surfaceNormal) { + // handle the trivial case where the box contains the origin + if (aaBoxContains(origin, corner, scale)) { + // We still want to calculate the distance from the origin to the inside out plane + float axisDistance; + if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; + surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; + surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; + surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); + return true; + } + // This case is unexpected, but mimics the previous behavior for inside out intersections + distance = 0; + return true; + } + + // check each axis + float axisDistance; + if ((findIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); + return true; + } + if ((findIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); + return true; + } + if ((findIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); + return true; + } + return false; +} + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance) { glm::vec3 relativeOrigin = origin - center; @@ -719,6 +807,12 @@ bool isWithin(float value, float corner, float size) { return value >= corner && value <= corner + size; } +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale) { + return isWithin(point.x, corner.x, scale.x) && + isWithin(point.y, corner.y, scale.y) && + isWithin(point.z, corner.z, scale.z); +} + void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) { if (t < minDistance && t > 0.0f && @@ -979,6 +1073,391 @@ bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& v return minDistance != FLT_MAX; } +void checkPossibleParabolicIntersection(float t, int i, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, bool& hit) { + if (t < minDistance && t > 0.0f && + isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, corner[(i + 1) % 3], scale[(i + 1) % 3]) && + isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, corner[(i + 2) % 3], scale[(i + 2) % 3])) { + minDistance = t; + hit = true; + } +} + +inline float parabolaVelocityAtT(float velocity, float acceleration, float t) { + return velocity + acceleration * t; +} + +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) { + float minDistance = FLT_MAX; + BoxFace minFace; + glm::vec3 minNormal; + glm::vec2 possibleDistances; + float a, b, c; + + // 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++) { + 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, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + 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, corner, scale, 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, corner, scale, 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, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + a = 0.5f * acceleration[i]; + b = velocity[i]; + if (origin[i] < corner[i]) { + // If we're below corner, we have the following cases: + // - within bounds on other axes + // - if +velocity or +acceleration + // - can only hit MIN_FACE with -normal + // - else + // - if +acceleration + // - can only hit MIN_FACE with -normal + // - else if +velocity + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] > 0.0f || acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f) { + bool hit = false; + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + if (!hit) { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, 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 have the following cases: + // - within bounds on other axes + // - if -velocity or -acceleration + // - can only hit MAX_FACE with +normal + // - else + // - if -acceleration + // - can only hit MAX_FACE with +normal + // - else if -velocity + // - can hit MAX_FACE with +normal iff velocity at intersection is - + // - else can hit MIN_FACE with -normal iff velocity at intersection is + + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f || acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } else { + if (acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] < 0.0f) { + bool hit = false; + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + if (!hit) { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } + } else { + // If we're between corner and corner + scale, we have the following cases: + // - within bounds on other axes + // - if -velocity and -acceleration + // - can only hit MIN_FACE with +normal + // - else if +velocity and +acceleration + // - can only hit MAX_FACE with -normal + // - else + // - can hit MIN_FACE with +normal iff velocity at intersection is - + // - can hit MAX_FACE with -normal iff velocity at intersection is + + // - else + // - if -velocity and +acceleration + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else if +velocity and -acceleration + // - can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f && acceleration[i] < 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] > 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + bool hit = false; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, 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 }; + bool hit = false; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (velocity[i] < 0.0f && acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + face = minFace; + surfaceNormal = minNormal; + return true; + } + return false; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 50d8d1f801..54f9062469 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -14,6 +14,7 @@ #include #include +#include "BoxBase.h" class Plane; @@ -73,6 +74,11 @@ bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3& glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration); +bool findIntersection(float origin, float direction, float corner, float size, float& distance); +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance); +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, + BoxFace& face, glm::vec3& surfaceNormal); + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance); @@ -100,6 +106,9 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& 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); +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal); + /// \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) @@ -202,6 +211,7 @@ 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); bool isWithin(float value, float corner, float size); +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale); void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale); From 763e6465a2a1f95e3da54f8fdf22891e4b41c4c0 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 19 Jul 2018 17:59:40 -0700 Subject: [PATCH 11/22] followNormal, fixin stuff --- interface/src/avatar/OtherAvatar.cpp | 2 +- interface/src/raypick/JointParabolaPick.cpp | 4 +- interface/src/raypick/JointParabolaPick.h | 2 +- interface/src/raypick/LaserPointer.cpp | 4 +- interface/src/raypick/LaserPointer.h | 2 +- interface/src/raypick/MouseParabolaPick.cpp | 4 +- interface/src/raypick/MouseParabolaPick.h | 2 +- interface/src/raypick/ParabolaPick.cpp | 4 +- interface/src/raypick/ParabolaPick.h | 6 +-- interface/src/raypick/ParabolaPointer.cpp | 11 +++-- interface/src/raypick/ParabolaPointer.h | 6 ++- interface/src/raypick/PathPointer.cpp | 41 ++++++++++++++----- interface/src/raypick/PathPointer.h | 2 +- .../src/raypick/PickScriptingInterface.cpp | 12 +++--- interface/src/raypick/StaticParabolaPick.cpp | 4 +- interface/src/raypick/StaticParabolaPick.h | 2 +- libraries/shared/src/GeometryUtil.cpp | 20 +++++---- .../controllers/controllerModules/teleport.js | 18 ++++---- 18 files changed, 91 insertions(+), 55 deletions(-) diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 5e51658128..2061df6004 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -48,7 +48,7 @@ void OtherAvatar::createOrb() { _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); - _otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true); + _otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true); _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); // Position focus diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp index 771cbe6185..071187c082 100644 --- a/interface/src/raypick/JointParabolaPick.cpp +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -10,8 +10,8 @@ #include "avatar/AvatarManager.h" JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, - float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled), + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled), _jointName(jointName), _posOffset(posOffset), _dirOffset(dirOffset) diff --git a/interface/src/raypick/JointParabolaPick.h b/interface/src/raypick/JointParabolaPick.h index d8b4457d7f..6c03d7b8f7 100644 --- a/interface/src/raypick/JointParabolaPick.h +++ b/interface/src/raypick/JointParabolaPick.h @@ -14,7 +14,7 @@ class JointParabolaPick : public ParabolaPick { public: JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, - float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 16bb4e25e1..4c47a9f4de 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -103,9 +103,9 @@ void LaserPointer::RenderState::disable() { } } -void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, +void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { - StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); QVariant endVariant = vec3toVariant(end); if (!getPathID().isNull()) { QVariantMap pathProps; diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 5030c90186..a320e272d1 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -29,7 +29,7 @@ public: void cleanup() override; void disable() override; - void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; private: diff --git a/interface/src/raypick/MouseParabolaPick.cpp b/interface/src/raypick/MouseParabolaPick.cpp index 450c792913..769819c6b3 100644 --- a/interface/src/raypick/MouseParabolaPick.cpp +++ b/interface/src/raypick/MouseParabolaPick.cpp @@ -10,8 +10,8 @@ #include "Application.h" #include "display-plugins/CompositorHelper.h" -MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled) +MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled) { } diff --git a/interface/src/raypick/MouseParabolaPick.h b/interface/src/raypick/MouseParabolaPick.h index ad03997c09..8c02606d49 100644 --- a/interface/src/raypick/MouseParabolaPick.h +++ b/interface/src/raypick/MouseParabolaPick.h @@ -13,7 +13,7 @@ class MouseParabolaPick : public ParabolaPick { public: - MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index 8dd0ad3aed..3d1b1c410d 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -52,6 +52,8 @@ PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { } glm::vec3 ParabolaPick::getAcceleration() const { - // TODO: use rotateWithAvatar + if (_rotateAccelerationWithAvatar) { + return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis; + } return _accelerationAxis; } \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 289f6e468a..86dfc5a644 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -74,8 +74,8 @@ public: class ParabolaPick : public Pick { public: - ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : - Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateWithAvatar(rotateWithAvatar) {} + ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar) {} PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const PickParabola& pick) override; @@ -86,7 +86,7 @@ public: protected: float _speed; glm::vec3 _accelerationAxis; - bool _rotateWithAvatar; + bool _rotateAccelerationWithAvatar; glm::vec3 getAcceleration() const; }; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index aee5b8cfa1..42612ef46d 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -51,6 +51,7 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria } if (pathMap["width"].isValid()) { width = pathMap["width"].toFloat(); + renderState->setPathWidth(width); } if (pathMap["isVisibleInSecondaryCamera"].isValid()) { isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); @@ -112,6 +113,7 @@ ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const Overla render::Transaction transaction; auto scene = qApp->getMain3DScene(); _pathID = scene->allocateID(); + _pathWidth = pathWidth; if (render::Item::isValidID(_pathID)) { auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled); // TODO: update bounds properly @@ -160,9 +162,9 @@ void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float al } } -void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, +void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { - StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); auto parabolaPickResult = std::static_pointer_cast(pickResult); if (parabolaPickResult && render::Item::isValidID(_pathID)) { render::Transaction transaction; @@ -172,12 +174,14 @@ void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::ve glm::vec3 velocity = parabola.velocity; glm::vec3 acceleration = parabola.acceleration; float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance; - transaction.updateItem(_pathID, [origin, velocity, acceleration, parabolicDistance](ParabolaRenderItem& item) { + float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : getPathWidth(); + transaction.updateItem(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) { item.setVisible(true); item.setOrigin(origin); item.setVelocity(velocity); item.setAcceleration(acceleration); item.setParabolicDistance(parabolicDistance); + item.setWidth(width); item.updateUniformBuffer(); }); scene->enqueueTransaction(transaction); @@ -364,6 +368,7 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) batch.setUniformBuffer(0, _uniformBuffer); + // TODO: variable number of sections, depending on ? (acceleration?, parabolicDistance?) const int NUM_SECTIONS = 25; // must match value in parabola.slv // We draw 2 * n + 2 vertices for a triangle strip batch.draw(gpu::TRIANGLE_STRIP, 2 * NUM_SECTIONS + 2, 0); diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 827134eaf5..4a4a27e8fc 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -75,15 +75,19 @@ public: RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, bool isVisibleInSecondaryCamera, bool pathEnabled); + void setPathWidth(float width) { _pathWidth = width; } + float getPathWidth() const { return _pathWidth; } + void cleanup() override; void disable() override; - void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); private: int _pathID; + float _pathWidth; }; ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 1582b62ab2..664e124ea0 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -149,7 +149,8 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { (type != IntersectionType::NONE || _pathLength > 0.0f)) { glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _pathLength); - _renderStates[_currentRenderState]->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); + _renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal, _pathLength, pickResult); if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { _defaultRenderStates[_currentRenderState].second->disable(); @@ -160,7 +161,7 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { } glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); - _defaultRenderStates[_currentRenderState].second->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal, _defaultRenderStates[_currentRenderState].first, pickResult); } else if (!_currentRenderState.empty()) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { @@ -275,7 +276,7 @@ void StartEndRenderState::disable() { } } -void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { if (!getStartID().isNull()) { QVariantMap startProps; @@ -290,7 +291,6 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, if (!getEndID().isNull()) { QVariantMap endProps; - glm::quat faceAvatarRotation = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))); glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value); if (distanceScaleEnd) { dim = getEndDim() * glm::distance(origin, end); @@ -299,15 +299,36 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, dim = getEndDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); endProps.insert("dimensions", vec3toVariant(dim)); } - if (centerEndY) { - endProps.insert("position", vec3toVariant(end)); - } else { - glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; - endProps.insert("position", vec3toVariant(end + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y))); + + glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal); + normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0)); + glm::vec3 avatarUp = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + glm::quat rotation = glm::rotation(Vectors::UP, avatarUp); + glm::vec3 position = end; + if (!centerEndY) { + if (followNormal) { + position = end + 0.5f * dim.y * surfaceNormal; + } else { + position = end + 0.5f * dim.y * avatarUp; + } } + endProps.insert("position", vec3toVariant(position)); if (faceAvatar) { - endProps.insert("rotation", quatToVariant(faceAvatarRotation)); + if (followNormal) { + glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); + glm::quat lookAtModel = glm::inverse(normalQuat) * lookAtWorld; + glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); + glm::quat lookAtFlatWorld = normalQuat * lookAtFlatModel; + rotation = lookAtFlatWorld; + } else { + glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); + glm::quat lookAtModel = glm::inverse(DependencyManager::get()->getMyAvatar()->getWorldOrientation()) * lookAtWorld; + glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); + glm::quat lookAtFlatWorld = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * lookAtFlatModel; + rotation = lookAtFlatWorld; + } } + endProps.insert("rotation", quatToVariant(rotation)); endProps.insert("visible", true); endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); qApp->getOverlays().editOverlay(getEndID(), endProps); diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index ff40f47646..1e85ad7a92 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -44,7 +44,7 @@ public: virtual void cleanup(); virtual void disable(); - virtual void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult); protected: diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 8475126f28..ac1fa05c1e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -184,9 +184,9 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]); } - bool rotateWithAvatar = true; - if (propMap["rotateWithAvatar"].isValid()) { - rotateWithAvatar = propMap["rotateWithAvatar"].toBool(); + bool rotateAccelerationWithAvatar = true; + if (propMap["rotateAccelerationWithAvatar"].isValid()) { + rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool(); } if (propMap["joint"].isValid()) { @@ -205,10 +205,10 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti } return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(jointName, posOffset, dirOffset, - speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); } else { - return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); } } else if (propMap["position"].isValid()) { glm::vec3 position = vec3FromVariant(propMap["position"]); @@ -218,7 +218,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti direction = vec3FromVariant(propMap["direction"]); } - return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled)); + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); } return PickManager::INVALID_PICK_ID; diff --git a/interface/src/raypick/StaticParabolaPick.cpp b/interface/src/raypick/StaticParabolaPick.cpp index 57b6aeccc7..2cf8501980 100644 --- a/interface/src/raypick/StaticParabolaPick.cpp +++ b/interface/src/raypick/StaticParabolaPick.cpp @@ -7,9 +7,9 @@ // #include "StaticParabolaPick.h" -StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, +StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled), + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled), _position(position), _velocity(speed * direction) { } diff --git a/interface/src/raypick/StaticParabolaPick.h b/interface/src/raypick/StaticParabolaPick.h index 676b012b41..e0d6a2a3fa 100644 --- a/interface/src/raypick/StaticParabolaPick.h +++ b/interface/src/raypick/StaticParabolaPick.h @@ -13,7 +13,7 @@ class StaticParabolaPick : public ParabolaPick { public: - StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateWithAvatar, + StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 1d470c42c3..f34473a5d8 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -840,8 +840,9 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& 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); + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithZPlane(possibleDistances[i], minDistance, origin, velocity, acceleration, localCorner, dimensions); + } } } if (minDistance < FLT_MAX) { @@ -941,7 +942,8 @@ void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, // Check that the point is within all three sides glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t; - if (glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && + if (t < minDistance && t > 0.0f && + glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f && glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { minDistance = t; @@ -981,10 +983,10 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& 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); + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithTriangle(possibleDistances[i], minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } } } if (minDistance < FLT_MAX) { @@ -1374,8 +1376,8 @@ bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& vel { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; - bool hit = false; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); @@ -1391,8 +1393,8 @@ bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& vel { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; - bool hit = false; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 12e3f6ab3a..55162ae3e9 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -80,19 +80,19 @@ Script.include("/~/system/libraries/controllers.js"); type: "model", url: TOO_CLOSE_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreParabolaIntersection: true + ignorePickIntersection: true }; var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreParabolaIntersection: true + ignorePickIntersection: true }; var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreParabolaIntersection: true + ignorePickIntersection: true }; @@ -149,6 +149,7 @@ Script.include("/~/system/libraries/controllers.js"); followNormal: true, speed: speed, accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); @@ -161,6 +162,7 @@ Script.include("/~/system/libraries/controllers.js"); followNormal: true, speed: speed, accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates }); this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { @@ -172,6 +174,7 @@ Script.include("/~/system/libraries/controllers.js"); followNormal: true, speed: speed, accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); @@ -184,6 +187,7 @@ Script.include("/~/system/libraries/controllers.js"); followNormal: true, speed: speed, accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates }); @@ -402,7 +406,7 @@ Script.include("/~/system/libraries/controllers.js"); } // When determininig whether you can teleport to a location, the normal of the // point that is being intersected with is looked at. If this normal is more - // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; function getTeleportTargetType(result) { @@ -426,11 +430,9 @@ Script.include("/~/system/libraries/controllers.js"); } var surfaceNormal = result.surfaceNormal; - var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); - var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + var angle = Math.abs(Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation)))) * (180.0 / Math.PI); - if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || - angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || + if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT || Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE * MyAvatar.sensorToWorldScale) { return TARGET.INVALID; } else { From 318ef907b8b6af4983d69d6a3b2bdc42a99a630c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 23 Jul 2018 16:41:26 -0700 Subject: [PATCH 12/22] parabola/polyvox, fixing some bugs --- interface/src/avatar/AvatarManager.cpp | 4 + interface/src/raypick/ParabolaPick.cpp | 2 +- interface/src/raypick/ParabolaPointer.cpp | 26 ++++-- interface/src/raypick/ParabolaPointer.h | 3 + interface/src/raypick/PathPointer.cpp | 4 +- interface/src/raypick/PathPointer.h | 4 +- interface/src/raypick/RayPick.cpp | 2 +- interface/src/ui/overlays/Overlays.cpp | 78 +++++------------ interface/src/ui/overlays/Overlays.h | 6 +- libraries/avatars/src/AvatarData.cpp | 11 ++- libraries/avatars/src/AvatarData.h | 4 + .../src/RenderablePolyVoxEntityItem.cpp | 84 ++++++++++++++++++- .../src/RenderablePolyVoxEntityItem.h | 10 ++- .../entities/src/EntityScriptingInterface.cpp | 69 +-------------- .../entities/src/EntityScriptingInterface.h | 2 - libraries/entities/src/PolyLineEntityItem.h | 2 +- libraries/entities/src/PolyVoxEntityItem.h | 2 +- libraries/render-utils/src/Model.cpp | 4 +- libraries/render-utils/src/parabola.slv | 5 +- libraries/shared/src/BoxBase.cpp | 46 ++++++++++ libraries/shared/src/BoxBase.h | 22 +++++ libraries/shared/src/GeometryUtil.cpp | 28 +++---- .../controllers/controllerModules/teleport.js | 16 ++-- 23 files changed, 257 insertions(+), 177 deletions(-) create mode 100644 libraries/shared/src/BoxBase.cpp diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 40530b7701..fab512f787 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -618,6 +618,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.intersects = true; result.avatarID = avatar->getID(); result.distance = distance; + result.face = face; + result.surfaceNormal = surfaceNormal; result.extraInfo = extraInfo; } } @@ -688,6 +690,8 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector result.intersects = true; result.avatarID = avatar->getID(); result.parabolicDistance = parabolicDistance; + result.face = face; + result.surfaceNormal = surfaceNormal; result.extraInfo = extraInfo; } } diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index 3d1b1c410d..be3fa57b0c 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -39,7 +39,7 @@ PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { 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); + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 42612ef46d..6ae516bfa4 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -156,7 +156,6 @@ void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float al item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera); item.setEnabled(enabled); item.updateKey(); - item.updateUniformBuffer(); }); scene->enqueueTransaction(transaction); } @@ -182,7 +181,6 @@ void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::ve item.setAcceleration(acceleration); item.setParabolicDistance(parabolicDistance); item.setWidth(width); - item.updateUniformBuffer(); }); scene->enqueueTransaction(transaction); } @@ -293,7 +291,6 @@ ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm:: setAlpha(alpha); setWidth(width); updateKey(); - updateUniformBuffer(); } void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) { @@ -302,12 +299,16 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) } else { _key = render::ItemKey::Builder(_key).withInvisible(); } + _visible = visible; } void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() { - auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape(); + // FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain, + // it seems to block all shadows. I think this is a bug with shadows. + //auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape(); + auto builder = render::ItemKey::Builder::transparentShape(); - if (_enabled) { + if (_enabled && _visible) { builder.withVisible(); } else { builder.withInvisible(); @@ -358,6 +359,10 @@ const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::get } void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) { + if (!_visible) { + return; + } + gpu::Batch& batch = *(args->_batch); Transform transform; @@ -366,12 +371,17 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) batch.setPipeline(getParabolaPipeline()); + const int MAX_SECTIONS = 100; + if (glm::length2(_parabolaData.acceleration) < EPSILON) { + _parabolaData.numSections = 1; + } else { + _parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS); + } + updateUniformBuffer(); batch.setUniformBuffer(0, _uniformBuffer); - // TODO: variable number of sections, depending on ? (acceleration?, parabolicDistance?) - const int NUM_SECTIONS = 25; // must match value in parabola.slv // We draw 2 * n + 2 vertices for a triangle strip - batch.draw(gpu::TRIANGLE_STRIP, 2 * NUM_SECTIONS + 2, 0); + batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0); } namespace render { diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 4a4a27e8fc..28e34523c2 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -57,6 +57,7 @@ public: glm::vec3 _origin { 0.0f }; bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; + bool _visible { false }; bool _enabled { false }; struct ParabolaData { @@ -65,6 +66,8 @@ public: vec3 acceleration { 0.0f }; float width { DEFAULT_PARABOLA_WIDTH }; vec4 color { vec4(DEFAULT_PARABOLA_COLOR)}; + int numSections { 0 }; + ivec3 spare; }; ParabolaData _parabolaData; diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 664e124ea0..13ec063b80 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -113,13 +113,12 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); glm::vec3 direction = endVec - origin; - float distance = glm::distance(origin, endVec); + distance = glm::distance(origin, endVec); glm::vec3 normalizedDirection = glm::normalize(direction); type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; id = _lockEndObject.id; intersection = endVec; - distance = distance; surfaceNormal = -normalizedDirection; setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); } else if (type != IntersectionType::NONE && _lockEnd) { @@ -134,7 +133,6 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick glm::vec3 direction = endVec - origin; distance = glm::distance(origin, endVec); glm::vec3 normalizedDirection = glm::normalize(direction); - type = type; intersection = endVec; surfaceNormal = -normalizedDirection; setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index 1e85ad7a92..a8b3d1bcdb 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -79,11 +79,11 @@ public: void updateVisuals(const PickResultPointer& prevRayPickResult) override; protected: - PointerTriggers _triggers; - float _pathLength { 0.0f }; RenderStateMap _renderStates; DefaultRenderStateMap _defaultRenderStates; std::string _currentRenderState { "" }; + PointerTriggers _triggers; + float _pathLength { 0.0f }; bool _faceAvatar; bool _followNormal; bool _centerEndY; diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 75b5e77fd8..96b41dcc72 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); if (avatarRes.intersects) { - return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo); + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9a054ffaa3..de4ff94719 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -622,75 +622,37 @@ ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(con } QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { - auto obj = engine->newObject(); + QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); - obj.setProperty("overlayID", OverlayIDtoScriptValue(engine, value.overlayID)); + QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); + obj.setProperty("overlayID", overlayIDValue); obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); - QString faceName = ""; - // handle BoxFace - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - default: - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; - } - obj.setProperty("face", faceName); - auto intersection = vec3toScriptValue(engine, value.intersection); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) { - QVariantMap object = objectVar.toVariant().toMap(); - value.intersects = object["intersects"].toBool(); - value.overlayID = OverlayID(QUuid(object["overlayID"].toString())); - value.distance = object["distance"].toFloat(); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + QScriptValue overlayIDValue = object.property("overlayID"); + quuidFromScriptValue(overlayIDValue, value.overlayID); + value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object["face"].toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else if (faceName == "MAX_Z_FACE") { - value.face = MAX_Z_FACE; - } else { - value.face = UNKNOWN_FACE; - }; - auto intersection = object["intersection"]; + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { - bool valid; - auto newIntersection = vec3FromVariant(intersection, valid); - if (valid) { - value.intersection = newIntersection; - } + vec3FromScriptValue(intersection, value.intersection); } - value.extraInfo = object["extraInfo"].toMap(); + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } + value.extraInfo = object.property("extraInfo").toVariant().toMap(); } bool Overlays::isLoaded(OverlayID id) { diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 33768416fe..21b9e93648 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -59,7 +59,7 @@ class RayToOverlayIntersectionResult { public: bool intersects { false }; OverlayID overlayID { UNKNOWN_OVERLAY_ID }; - float distance { 0 }; + float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 surfaceNormal; glm::vec3 intersection; @@ -73,8 +73,8 @@ class ParabolaToOverlayIntersectionResult { public: bool intersects { false }; OverlayID overlayID { UNKNOWN_OVERLAY_ID }; - float distance { 0 }; - float parabolicDistance { 0 }; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 surfaceNormal; glm::vec3 intersection; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fc72f34304..abdac838b6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2555,15 +2555,18 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const { return _controllerRightHandMatrixCache.get(); } - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); obj.setProperty("avatarID", avatarIDValue); obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -2573,10 +2576,16 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra QScriptValue avatarIDValue = object.property("avatarID"); quuidFromScriptValue(avatarIDValue, value.avatarID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } value.extraInfo = object.property("extraInfo").toVariant().toMap(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5e799b8401..0f850aaf24 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1527,7 +1527,9 @@ public: bool intersects { false }; QUuid avatarID; float distance { 0.0f }; + BoxFace face; glm::vec3 intersection; + glm::vec3 surfaceNormal; QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) @@ -1540,7 +1542,9 @@ public: QUuid avatarID; float distance { 0.0f }; float parabolicDistance { 0.0f }; + BoxFace face; glm::vec3 intersection; + glm::vec3 surfaceNormal; QVariantMap extraInfo; }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index f9fed5ae8b..2de6316d74 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -567,8 +567,7 @@ public: bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const -{ + QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes if (!precisionPicking) { // just intersect with bounding box @@ -614,6 +613,87 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return hit; } +bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO -- correctly pick against marching-cube generated meshes + if (!precisionPicking) { + // just intersect with bounding box + return true; + } + + glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); + glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f); + glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f); + + // find the first intersection with the voxel bounding box (slightly enlarged so we can catch voxels that touch the sides) + bool success; + glm::vec3 center = getCenterPosition(success); + glm::vec3 dimensions = getScaledDimensions(); + const float FIRST_BOX_HALF_SCALE = 0.51f; + AABox voxelBox1(wtvMatrix * vec4(center - FIRST_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * FIRST_BOX_HALF_SCALE * dimensions, 0.0f)); + bool hit1; + float parabolicDistance1; + // If we're starting inside the box, our first point is originInVoxel + if (voxelBox1.contains(originInVoxel)) { + parabolicDistance1 = 0.0f; + hit1 = true; + } else { + BoxFace face1; + glm::vec3 surfaceNormal1; + hit1 = voxelBox1.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance1, face1, surfaceNormal1); + } + + if (hit1) { + // find the second intersection, which should be with the inside of the box (use a slightly large box again) + const float SECOND_BOX_HALF_SCALE = 0.52f; + AABox voxelBox2(wtvMatrix * vec4(center - SECOND_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * SECOND_BOX_HALF_SCALE * dimensions, 0.0f)); + glm::vec4 originInVoxel2 = originInVoxel + velocityInVoxel * parabolicDistance1 + 0.5f * accelerationInVoxel * parabolicDistance1 * parabolicDistance1; + glm::vec4 velocityInVoxel2 = velocityInVoxel + accelerationInVoxel * parabolicDistance1; + glm::vec4 accelerationInVoxel2 = accelerationInVoxel; + float parabolicDistance2; + BoxFace face2; + glm::vec3 surfaceNormal2; + // this should always be true + if (voxelBox2.findParabolaIntersection(glm::vec3(originInVoxel2), glm::vec3(velocityInVoxel2), glm::vec3(accelerationInVoxel2), + parabolicDistance2, face2, surfaceNormal2)) { + const int MAX_SECTIONS = 15; + PolyVox::RaycastResult raycastResult = PolyVox::RaycastResults::Completed; + glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + glm::vec4 segmentStartVoxel = originInVoxel2; + for (int i = 0; i < MAX_SECTIONS; i++) { + float t = parabolicDistance2 * ((float)(i + 1)) / ((float)MAX_SECTIONS); + glm::vec4 segmentEndVoxel = originInVoxel2 + velocityInVoxel2 * t + 0.5f * accelerationInVoxel2 * t * t; + raycastResult = doRayCast(segmentStartVoxel, segmentEndVoxel, result); + if (raycastResult != PolyVox::RaycastResults::Completed) { + // We hit something! + break; + } + segmentStartVoxel = segmentEndVoxel; + } + + if (raycastResult == PolyVox::RaycastResults::Completed) { + // the parabola completed its path -- nothing was hit. + return false; + } + + glm::vec3 result3 = glm::vec3(result); + + AABox voxelBox; + voxelBox += result3 - Vectors::HALF; + voxelBox += result3 + Vectors::HALF; + + return voxelBox.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance, face, surfaceNormal); + } + } + return false; +} PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index d7b9868fbc..7afb9b41b4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -53,9 +53,13 @@ public: virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setVoxelData(const QByteArray& voxelData) override; virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d2e40eb527..8fd87e068a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1050,65 +1050,16 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { } QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); - obj.setProperty("distance", value.distance); - - QString faceName = ""; - // handle BoxFace - /**jsdoc - *

A BoxFace specifies the face of an axis-aligned (AA) box. - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
- * @typedef {string} BoxFace - */ - // FIXME: Move enum to string function to BoxBase.cpp. - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; - } - obj.setProperty("face", faceName); + obj.setProperty("face", boxFaceToString(value.face)); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); @@ -1116,29 +1067,13 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c } void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); - // EntityItemIDfromScriptValue(entityIDValue, value.entityID); quuidFromScriptValue(entityIDValue, value.entityID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object.property("face").toVariant().toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else { - value.face = MAX_Z_FACE; - }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index d7d86fc489..a166d513d3 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -80,9 +80,7 @@ public: glm::vec3 surfaceNormal; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToEntityIntersectionResult) - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results); void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results); diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 0a48ed584e..52ec8e8c2d 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -97,7 +97,7 @@ class PolyLineEntityItem : public EntityItem { virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { return false; } + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious! diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index e3f8c48dd1..d2ca4db124 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -50,7 +50,7 @@ class PolyVoxEntityItem : public EntityItem { virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { return false; } + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual void debugDump() const override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 71312f3bb3..ba0d714f7a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -516,9 +516,9 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co face = triangleSetFace; bestModelTriangle = triangleSetTriangle; bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - glm::vec3 worldIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; - glm::vec3 meshIntersectionPoint = origin + velocity * triangleSetDistance + + glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv index a8c892ab00..c40fc89302 100644 --- a/libraries/render-utils/src/parabola.slv +++ b/libraries/render-utils/src/parabola.slv @@ -18,6 +18,8 @@ layout(std140) uniform parabolaData { vec3 acceleration; float width; vec4 color; + int numSections; + ivec3 spare; }; out vec4 _color; @@ -25,8 +27,7 @@ out vec4 _color; void main(void) { _color = color; - const int NUM_SECTIONS = 25; // must match value in ParabolaPointer.cpp - float t = parabolicDistance * (floor(gl_VertexID / 2) / float(NUM_SECTIONS)); + float t = parabolicDistance * (floor(gl_VertexID / 2) / float(numSections)); vec4 pos = vec4(velocity * t + 0.5 * acceleration * t * t, 1); const float EPSILON = 0.00001; diff --git a/libraries/shared/src/BoxBase.cpp b/libraries/shared/src/BoxBase.cpp new file mode 100644 index 0000000000..0b790dc2b0 --- /dev/null +++ b/libraries/shared/src/BoxBase.cpp @@ -0,0 +1,46 @@ +// +// Created by Sam Gondelman on 7/20/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "BoxBase.h" + +QString boxFaceToString(BoxFace face) { + switch (face) { + case MIN_X_FACE: + return "MIN_X_FACE"; + case MAX_X_FACE: + return "MAX_X_FACE"; + case MIN_Y_FACE: + return "MIN_Y_FACE"; + case MAX_Y_FACE: + return "MAX_Y_FACE"; + case MIN_Z_FACE: + return "MIN_Z_FACE"; + case MAX_Z_FACE: + return "MAX_Z_FACE"; + default: + return "UNKNOWN_FACE"; + } +} + +BoxFace boxFaceFromString(const QString& face) { + if (face == "MIN_X_FACE") { + return MIN_X_FACE; + } else if (face == "MAX_X_FACE") { + return MAX_X_FACE; + } else if (face == "MIN_Y_FACE") { + return MIN_Y_FACE; + } else if (face == "MAX_Y_FACE") { + return MAX_Y_FACE; + } else if (face == "MIN_Z_FACE") { + return MIN_Z_FACE; + } else if (face == "MAX_Z_FACE") { + return MAX_Z_FACE; + } else { + return UNKNOWN_FACE; + } +} \ No newline at end of file diff --git a/libraries/shared/src/BoxBase.h b/libraries/shared/src/BoxBase.h index 7f1dd4d34c..9bc2115d9e 100644 --- a/libraries/shared/src/BoxBase.h +++ b/libraries/shared/src/BoxBase.h @@ -16,7 +16,26 @@ #define hifi_BoxBase_h #include +#include +/**jsdoc +*

A BoxFace specifies the face of an axis-aligned (AA) box. +* +* +* +* +* +* +* +* +* +* +* +* +* +*
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
+* @typedef {string} BoxFace +*/ enum BoxFace { MIN_X_FACE, MAX_X_FACE, @@ -27,6 +46,9 @@ enum BoxFace { UNKNOWN_FACE }; +QString boxFaceToString(BoxFace face); +BoxFace boxFaceFromString(const QString& face); + enum BoxVertex { BOTTOM_LEFT_NEAR = 0, BOTTOM_RIGHT_NEAR = 1, diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index f34473a5d8..963294a360 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -952,7 +952,7 @@ void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, 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) { - glm::vec3 normal = glm::cross(v2 - v1, v0 - v1); + glm::vec3 normal = glm::normalize(glm::cross(v2 - v1, v0 - v1)); // We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin glm::quat inverseRot; @@ -1092,7 +1092,7 @@ inline float parabolaVelocityAtT(float velocity, float acceleration, float t) { bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) { float minDistance = FLT_MAX; - BoxFace minFace; + BoxFace minFace = UNKNOWN_FACE; glm::vec3 minNormal; glm::vec2 possibleDistances; float a, b, c; @@ -1720,8 +1720,8 @@ unsigned int solveP3(float* x, float a, float b, float c) { a /= 3.0f; q = -2.0f * sqrtf(q); x[0] = q * cosf(t / 3.0f) - a; - x[1] = q * cosf((t + 2.0f * M_PI) / 3.0f) - a; - x[2] = q * cosf((t - 2.0f * M_PI) / 3.0f) - a; + x[1] = q * cosf((t + 2.0f * (float)M_PI) / 3.0f) - a; + x[2] = q * cosf((t - 2.0f * (float)M_PI) / 3.0f) - a; return 3; } else { A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f); @@ -1755,27 +1755,27 @@ bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { y = px3[0]; if (iZeroes != 1) { - if (fabs(px3[1]) > fabs(y)) { + if (fabsf(px3[1]) > fabsf(y)) { y = px3[1]; } - if (fabs(px3[2]) > fabs(y)) { + if (fabsf(px3[2]) > fabsf(y)) { y = px3[2]; } } D = y * y - 4.0f * d; - if (fabs(D) < EPSILON) { + if (fabsf(D) < EPSILON) { q1 = q2 = 0.5f * y; D = a * a - 4.0f * (b - y); - if (fabs(D) < EPSILON) { + if (fabsf(D) < EPSILON) { p1 = p2 = 0.5f * a; } else { - sqD = sqrt(D); + sqD = sqrtf(D); p1 = 0.5f * (a + sqD); p2 = 0.5f * (a - sqD); } } else { - sqD = sqrt(D); + sqD = sqrtf(D); q1 = 0.5f * (y + sqD); q2 = 0.5f * (y - sqD); p1 = (a * q1 - c) / (q1 - q2); @@ -1786,10 +1786,10 @@ bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { D = p1 * p1 - 4.0f * q1; if (D < 0.0f) { x1.real(-0.5f * p1); - x1.imag(0.5f * sqrt(-D)); + x1.imag(0.5f * sqrtf(-D)); x2 = std::conj(x1); } else { - sqD = sqrt(D); + sqD = sqrtf(D); x1.real(0.5f * (-p1 + sqD)); x2.real(0.5f * (-p1 - sqD)); } @@ -1797,10 +1797,10 @@ bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { D = p2 * p2 - 4.0f * q2; if (D < 0.0f) { x3.real(-0.5f * p2); - x3.imag(0.5f * sqrt(-D)); + x3.imag(0.5f * sqrtf(-D)); x4 = std::conj(x3); } else { - sqD = sqrt(D); + sqD = sqrtf(D); x3.real(0.5f * (-p2 + sqD)); x4.real(0.5f * (-p2 - sqD)); } diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 55162ae3e9..e926a70bee 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -100,7 +100,7 @@ Script.include("/~/system/libraries/controllers.js"); {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}]; - var DEFAULT_DISTANCE = 10; + var DEFAULT_DISTANCE = 4.0; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var coolInTimeout = null; @@ -151,7 +151,8 @@ Script.include("/~/system/libraries/controllers.js"); accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 4.0 }); this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", @@ -163,7 +164,8 @@ Script.include("/~/system/libraries/controllers.js"); speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates + renderStates: teleportRenderStates, + maxDistance: 4.0 }); this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", @@ -176,7 +178,8 @@ Script.include("/~/system/libraries/controllers.js"); accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 4.0 }); this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", @@ -188,7 +191,8 @@ Script.include("/~/system/libraries/controllers.js"); speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates + renderStates: teleportRenderStates, + maxDistance: 4.0 }); this.cleanup = function() { @@ -430,7 +434,7 @@ Script.include("/~/system/libraries/controllers.js"); } var surfaceNormal = result.surfaceNormal; - var angle = Math.abs(Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation)))) * (180.0 / Math.PI); + var angle = Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation))) * (180.0 / Math.PI); if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT || Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE * MyAvatar.sensorToWorldScale) { From 645f90523982635ae08ce038cd5ea126cd0e4f53 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 24 Jul 2018 13:25:49 -0700 Subject: [PATCH 13/22] cleanup, model performance improvements --- interface/src/ui/overlays/Circle3DOverlay.cpp | 5 +- interface/src/ui/overlays/Image3DOverlay.cpp | 5 +- interface/src/ui/overlays/Planar3DOverlay.cpp | 5 +- interface/src/ui/overlays/Volume3DOverlay.cpp | 7 +- libraries/entities/src/TextEntityItem.cpp | 5 +- libraries/entities/src/WebEntityItem.cpp | 5 +- libraries/pointers/src/PickManager.h | 2 +- libraries/shared/src/TriangleSet.cpp | 154 ++++++++---------- 8 files changed, 93 insertions(+), 95 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index ed09045ac6..2e06229276 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -566,12 +566,13 @@ bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const gl if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; if (localIntersectionVelocityZ > 0.0f) { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } else { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } return true; } diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 8b79c91bc4..608e7eb72f 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -306,12 +306,13 @@ bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) { float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; if (localIntersectionVelocityZ > 0.0f) { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } else { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } return true; } diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index 77e1eceb58..cf2691bb13 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -103,12 +103,13 @@ bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const gl if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; if (localIntersectionVelocityZ > 0.0f) { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } else { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } return true; } diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index ba5dfa20c8..ba22483d6b 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -105,7 +105,12 @@ bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const gl // 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); + bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; } Transform Volume3DOverlay::evalRenderTransform() { diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 3748ba6ec3..f130995bb5 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -165,12 +165,13 @@ bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; if (localIntersectionVelocityZ > 0.0f) { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } else { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } return true; } diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index f11a3c041b..0070eb538c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -143,12 +143,13 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; if (localIntersectionVelocityZ > 0.0f) { face = MIN_Z_FACE; - surfaceNormal = rotation * Vectors::FRONT; + surfaceNormal = forward; } else { face = MAX_Z_FACE; - surfaceNormal = rotation * -Vectors::FRONT; + surfaceNormal = -forward; } return true; } else { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3bafd2186c..c726fd0668 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -67,7 +67,7 @@ protected: PickCacheOptimizer _stylusPickCacheOptimizer; PickCacheOptimizer _parabolaPickCacheOptimizer; - static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; + static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; }; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index 86e900cf4b..11e31bcccb 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -115,83 +115,65 @@ void TriangleSet::balanceOctree() { // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { - + float& distance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; - float boxDistance = distance; - float bestDistance = distance; - glm::vec3 surfaceNormal; + float bestDistance = FLT_MAX; - if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { - - // if our bounding box intersects at a distance greater than the current known - // best distance, and our origin isn't inside the boounds, then we can safely - // not check any of our triangles - if (boxDistance > bestDistance && !_bounds.contains(origin)) { - return false; - } - - if (precision) { - for (const auto& triangleIndex : _triangleIndices) { - const auto& thisTriangle = _allTriangles[triangleIndex]; - float thisTriangleDistance; - trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - triangle = thisTriangle; - distance = bestDistance; - } + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + triangle = thisTriangle; } } - } else { - intersectedSomething = true; - // FIXME: this needs to set triangle to something or it will carry the previous value - distance = boxDistance; } + } else { + intersectedSomething = true; + // FIXME: this needs to set triangle to something or it will carry the previous value + bestDistance = distance; + } + + if (intersectedSomething) { + distance = bestDistance; } return intersectedSomething; } bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, - int& trianglesTouched, bool allowBackface) { - + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; - float boxDistance = parabolicDistance; - float bestDistance = parabolicDistance; - glm::vec3 surfaceNormal; + float bestDistance = FLT_MAX; - if (_bounds.findParabolaIntersection(origin, velocity, acceleration, boxDistance, face, surfaceNormal)) { - - // if our bounding box intersects at a distance greater than the current known - // best distance, and our origin isn't inside the boounds, then we can safely - // not check any of our triangles - if (boxDistance > bestDistance && !_bounds.contains(origin)) { - return false; - } - - if (precision) { - for (const auto& triangleIndex : _triangleIndices) { - const auto& thisTriangle = _allTriangles[triangleIndex]; - float thisTriangleDistance; - trianglesTouched++; - if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - triangle = thisTriangle; - parabolicDistance = bestDistance; - } + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + triangle = thisTriangle; } } - } else { - intersectedSomething = true; - // FIXME: this needs to set triangle to something or it will carry the previous value - parabolicDistance = boxDistance; } + } else { + intersectedSomething = true; + // FIXME: this needs to set triangle to something or it will carry the previous value + bestDistance = parabolicDistance; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; } return intersectedSomething; @@ -267,26 +249,24 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { _triangleIndices.push_back(triangleIndex); } -bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, - bool allowBackface) { - +bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = distance; + float bestLocalDistance = FLT_MAX; BoxFace bestLocalFace; Triangle bestLocalTriangle; glm::vec3 bestLocalNormal; bool intersects = false; + float boxDistance = FLT_MAX; // if the ray intersects our bounding box, then continue - if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { - + if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return - if (bestLocalDistance > distance) { return false; } @@ -314,11 +294,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } } // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < childDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; intersects = true; } } @@ -332,20 +315,22 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, - int& trianglesTouched, bool allowBackface) { + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = parabolicDistance; + float bestLocalDistance = FLT_MAX; BoxFace bestLocalFace; Triangle bestLocalTriangle; glm::vec3 bestLocalNormal; bool intersects = false; + float boxDistance = FLT_MAX; // if the ray intersects our bounding box, then continue - if (getBounds().findParabolaIntersection(origin, velocity, acceleration, bestLocalDistance, bestLocalFace, bestLocalNormal)) { + if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return if (bestLocalDistance > parabolicDistance) { @@ -375,11 +360,14 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& } } // also check our local triangle set - if (findParabolaIntersectionInternal(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < childDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; intersects = true; } } From fbcddf14459ef8c8e49a0fa81fd2bfef39c5c952 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 24 Jul 2018 14:15:49 -0700 Subject: [PATCH 14/22] test gradual normal interpolation --- interface/src/raypick/LaserPointer.cpp | 5 +++-- interface/src/raypick/LaserPointer.h | 2 +- interface/src/raypick/ParabolaPointer.cpp | 5 +++-- interface/src/raypick/ParabolaPointer.h | 2 +- interface/src/raypick/PathPointer.cpp | 13 ++++++++++++- interface/src/raypick/PathPointer.h | 4 +++- .../src/raypick/PointerScriptingInterface.cpp | 18 +++++++++++++++--- .../controllers/controllerModules/teleport.js | 4 ++++ 8 files changed, 42 insertions(+), 11 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 4c47a9f4de..0d724f577b 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -17,8 +17,9 @@ #include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, - const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : - PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) { } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index a320e272d1..7ea6a593de 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -40,7 +40,7 @@ public: }; LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, - bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); static std::shared_ptr buildRenderState(const QVariantMap& propMap); diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 6ae516bfa4..14a99cdcea 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -25,8 +25,9 @@ gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabola gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr }; ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, - const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : - PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, + bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) { } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 28e34523c2..963aa8dd85 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -94,7 +94,7 @@ public: }; ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, - bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); static std::shared_ptr buildRenderState(const QVariantMap& propMap); diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 13ec063b80..91de839e96 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -16,7 +16,7 @@ #include "RayPick.h" PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, - bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : Pointer(DependencyManager::get()->createPick(type, rayProps), enabled, hover), _renderStates(renderStates), @@ -24,6 +24,7 @@ PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, con _triggers(triggers), _faceAvatar(faceAvatar), _followNormal(followNormal), + _followNormalStrength(followNormalStrength), _centerEndY(centerEndY), _lockEnd(lockEnd), _distanceScaleEnd(distanceScaleEnd), @@ -148,6 +149,16 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _pathLength); glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); + if (_followNormal && _followNormalStrength > 0.0f && _followNormalStrength < 1.0f) { + if (glm::any(glm::isnan(_avgNormal))) { + _avgNormal = surfaceNormal; + } else { + glm::quat a = Quat().lookAtSimple(glm::vec3(0.0f), _avgNormal); + glm::quat b = Quat().lookAtSimple(glm::vec3(0.0f), surfaceNormal); + surfaceNormal = glm::normalize(glm::slerp(a, b, _followNormalStrength) * Vectors::FRONT); + _avgNormal = surfaceNormal; + } + } _renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal, _pathLength, pickResult); if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index a8b3d1bcdb..7df22c80ac 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -65,7 +65,7 @@ class PathPointer : public Pointer { using Parent = Pointer; public: PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, - bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); virtual ~PathPointer(); @@ -86,6 +86,8 @@ protected: float _pathLength { 0.0f }; bool _faceAvatar; bool _followNormal; + float _followNormalStrength; + glm::vec3 _avgNormal { NAN }; bool _centerEndY; bool _lockEnd; bool _distanceScaleEnd; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 486e208647..9f728a2bd8 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -96,6 +96,8 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. + * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, + * the normal will follow exactly. * @property {boolean} [enabled=false] * @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between. * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. @@ -134,6 +136,10 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } bool enabled = false; if (propertyMap["enabled"].isValid()) { @@ -193,8 +199,8 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, - faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd, - scaleWithAvatar, enabled)); + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, + distanceScaleEnd, scaleWithAvatar, enabled)); } /**jsdoc @@ -233,6 +239,8 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. +* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, +* the normal will follow exactly. * @property {boolean} [enabled=false] * @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between. * @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. @@ -271,6 +279,10 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } bool enabled = false; if (propertyMap["enabled"].isValid()) { @@ -330,7 +342,7 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, - faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd, + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); } diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index e926a70bee..d938feb22e 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -147,6 +147,7 @@ Script.include("/~/system/libraries/controllers.js"); scaleWithAvatar: true, centerEndY: false, followNormal: true, + followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -161,6 +162,7 @@ Script.include("/~/system/libraries/controllers.js"); scaleWithAvatar: true, centerEndY: false, followNormal: true, + followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -174,6 +176,7 @@ Script.include("/~/system/libraries/controllers.js"); scaleWithAvatar: true, centerEndY: false, followNormal: true, + followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -188,6 +191,7 @@ Script.include("/~/system/libraries/controllers.js"); scaleWithAvatar: true, centerEndY: false, followNormal: true, + followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, From 9b41cb5554d93c2b25dbb2e2d416ce2ed63a66f3 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 25 Jul 2018 15:19:25 -0700 Subject: [PATCH 15/22] improve model picking performance, scale parabola v/a with avatar, improve followNormal behavior, turn off followNormal on teleport, remove 1 m no teleport spot --- interface/src/raypick/JointParabolaPick.cpp | 6 +-- interface/src/raypick/JointParabolaPick.h | 3 +- interface/src/raypick/LaserPointer.cpp | 4 +- interface/src/raypick/LaserPointer.h | 2 +- interface/src/raypick/MouseParabolaPick.cpp | 7 +-- interface/src/raypick/MouseParabolaPick.h | 3 +- interface/src/raypick/ParabolaPick.cpp | 9 +++- interface/src/raypick/ParabolaPick.h | 7 ++- interface/src/raypick/ParabolaPointer.cpp | 4 +- interface/src/raypick/ParabolaPointer.h | 2 +- interface/src/raypick/PathPointer.cpp | 47 +++++++++---------- interface/src/raypick/PathPointer.h | 6 ++- .../src/raypick/PickScriptingInterface.cpp | 18 +++++-- .../src/raypick/PointerScriptingInterface.cpp | 4 +- interface/src/raypick/StaticParabolaPick.cpp | 10 ++-- interface/src/raypick/StaticParabolaPick.h | 2 +- libraries/shared/src/TriangleSet.cpp | 36 +++++++------- .../controllers/controllerModules/teleport.js | 12 +---- 18 files changed, 94 insertions(+), 88 deletions(-) diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp index 071187c082..a0fea97995 100644 --- a/interface/src/raypick/JointParabolaPick.cpp +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -10,8 +10,8 @@ #include "avatar/AvatarManager.h" JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, - float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled), + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), _jointName(jointName), _posOffset(posOffset), _dirOffset(dirOffset) @@ -36,7 +36,7 @@ PickParabola JointParabolaPick::getMathematicalPick() const { pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); glm::vec3 dir = rot * glm::normalize(_dirOffset); - return PickParabola(pos, _speed * dir, getAcceleration()); + return PickParabola(pos, getSpeed() * dir, getAcceleration()); } return PickParabola(); diff --git a/interface/src/raypick/JointParabolaPick.h b/interface/src/raypick/JointParabolaPick.h index 6c03d7b8f7..aff6bd34d8 100644 --- a/interface/src/raypick/JointParabolaPick.h +++ b/interface/src/raypick/JointParabolaPick.h @@ -14,7 +14,8 @@ class JointParabolaPick : public ParabolaPick { public: JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, - float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 0d724f577b..f65f2e9639 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -105,8 +105,8 @@ void LaserPointer::RenderState::disable() { } void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { - StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); QVariant endVariant = vec3toVariant(end); if (!getPathID().isNull()) { QVariantMap pathProps; diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 7ea6a593de..e6d493d449 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -30,7 +30,7 @@ public: void cleanup() override; void disable() override; void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; private: OverlayID _pathID; diff --git a/interface/src/raypick/MouseParabolaPick.cpp b/interface/src/raypick/MouseParabolaPick.cpp index 769819c6b3..66351f4520 100644 --- a/interface/src/raypick/MouseParabolaPick.cpp +++ b/interface/src/raypick/MouseParabolaPick.cpp @@ -10,8 +10,9 @@ #include "Application.h" #include "display-plugins/CompositorHelper.h" -MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled) +MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, + bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled) { } @@ -20,7 +21,7 @@ PickParabola MouseParabolaPick::getMathematicalPick() const { if (position.isValid()) { QVariantMap posMap = position.toMap(); PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); - return PickParabola(pickRay.origin, _speed * pickRay.direction, getAcceleration()); + return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration()); } return PickParabola(); diff --git a/interface/src/raypick/MouseParabolaPick.h b/interface/src/raypick/MouseParabolaPick.h index 8c02606d49..cb67c3b361 100644 --- a/interface/src/raypick/MouseParabolaPick.h +++ b/interface/src/raypick/MouseParabolaPick.h @@ -13,7 +13,8 @@ class MouseParabolaPick : public ParabolaPick { public: - MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index be3fa57b0c..3507fe0407 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -51,9 +51,14 @@ PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick); } +float ParabolaPick::getSpeed() const { + return _scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed; +} + glm::vec3 ParabolaPick::getAcceleration() const { + float scale = _scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : 1.0f; if (_rotateAccelerationWithAvatar) { - return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis; + return scale * DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis; } - return _accelerationAxis; + return scale * _accelerationAxis; } \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 86dfc5a644..03ee1a8da1 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -74,8 +74,9 @@ public: class ParabolaPick : public Pick { public: - ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : - Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar) {} + ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar), + _scaleWithAvatar(scaleWithAvatar) {} PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const PickParabola& pick) override; @@ -87,7 +88,9 @@ protected: float _speed; glm::vec3 _accelerationAxis; bool _rotateAccelerationWithAvatar; + bool _scaleWithAvatar; + float getSpeed() const; glm::vec3 getAcceleration() const; }; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 14a99cdcea..24ac9d956f 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -163,8 +163,8 @@ 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, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { - StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, distance, pickResult); + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, 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/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 963aa8dd85..4cabbc5a25 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -84,7 +84,7 @@ public: void cleanup() override; void disable() override; void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) override; + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 91de839e96..475d2abbc7 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -149,18 +149,8 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _pathLength); glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); - if (_followNormal && _followNormalStrength > 0.0f && _followNormalStrength < 1.0f) { - if (glm::any(glm::isnan(_avgNormal))) { - _avgNormal = surfaceNormal; - } else { - glm::quat a = Quat().lookAtSimple(glm::vec3(0.0f), _avgNormal); - glm::quat b = Quat().lookAtSimple(glm::vec3(0.0f), surfaceNormal); - surfaceNormal = glm::normalize(glm::slerp(a, b, _followNormalStrength) * Vectors::FRONT); - _avgNormal = surfaceNormal; - } - } _renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, - _followNormal, _pathLength, pickResult); + _followNormal, _followNormalStrength, _pathLength, pickResult); if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { _defaultRenderStates[_currentRenderState].second->disable(); } @@ -171,7 +161,7 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { glm::vec3 origin = getPickOrigin(pickResult); glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); _defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, - _faceAvatar, _followNormal, _defaultRenderStates[_currentRenderState].first, pickResult); + _faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult); } else if (!_currentRenderState.empty()) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { _renderStates[_currentRenderState]->disable(); @@ -286,7 +276,7 @@ void StartEndRenderState::disable() { } void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult) { + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { if (!getStartID().isNull()) { QVariantMap startProps; startProps.insert("position", vec3toVariant(origin)); @@ -321,22 +311,29 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, position = end + 0.5f * dim.y * avatarUp; } } - endProps.insert("position", vec3toVariant(position)); if (faceAvatar) { - if (followNormal) { - glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); - glm::quat lookAtModel = glm::inverse(normalQuat) * lookAtWorld; - glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); - glm::quat lookAtFlatWorld = normalQuat * lookAtFlatModel; - rotation = lookAtFlatWorld; + glm::quat orientation = followNormal ? normalQuat : DependencyManager::get()->getMyAvatar()->getWorldOrientation(); + glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); + glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld; + glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); + glm::quat lookAtFlatWorld = orientation * lookAtFlatModel; + rotation = lookAtFlatWorld; + } else if (followNormal) { + rotation = normalQuat; + } + if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) { + if (!_avgEndRotInitialized) { + _avgEndRot = rotation; + _avgEndRotInitialized = true; } else { - glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); - glm::quat lookAtModel = glm::inverse(DependencyManager::get()->getMyAvatar()->getWorldOrientation()) * lookAtWorld; - glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); - glm::quat lookAtFlatWorld = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * lookAtFlatModel; - rotation = lookAtFlatWorld; + rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength); + if (!centerEndY) { + position = end + 0.5f * dim.y * (rotation * Vectors::UP); + } + _avgEndRot = rotation; } } + endProps.insert("position", vec3toVariant(position)); endProps.insert("rotation", quatToVariant(rotation)); endProps.insert("visible", true); endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index 7df22c80ac..b0a3444fca 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -45,7 +45,7 @@ public: virtual void cleanup(); virtual void disable(); virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, - bool faceAvatar, bool followNormal, float distance, const PickResultPointer& pickResult); + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult); protected: OverlayID _startID; @@ -56,6 +56,9 @@ protected: glm::vec3 _startDim; glm::vec3 _endDim; glm::quat _endRot; + + glm::quat _avgEndRot; + bool _avgEndRotInitialized { false }; }; typedef std::unordered_map> RenderStateMap; @@ -87,7 +90,6 @@ protected: bool _faceAvatar; bool _followNormal; float _followNormalStrength; - glm::vec3 _avgNormal { NAN }; bool _centerEndY; bool _lockEnd; bool _distanceScaleEnd; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index ac1fa05c1e..c6417d6dcf 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -154,7 +154,8 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties * @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment. * @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola. * @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction. - * @property {boolean} [rotateWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis. + * @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis. + * @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar. */ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) { QVariantMap propMap = properties.toMap(); @@ -189,6 +190,11 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool(); } + bool scaleWithAvatar = false; + if (propMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propMap["scaleWithAvatar"].toBool(); + } + if (propMap["joint"].isValid()) { std::string jointName = propMap["joint"].toString().toStdString(); @@ -205,10 +211,12 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti } return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(jointName, posOffset, dirOffset, - speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); + speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); } else { - return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); } } else if (propMap["position"].isValid()) { glm::vec3 position = vec3FromVariant(propMap["position"]); @@ -218,7 +226,9 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti direction = vec3FromVariant(propMap["direction"]); } - return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled)); + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, + rotateAccelerationWithAvatar, scaleWithAvatar, + filter, maxDistance, enabled)); } return PickManager::INVALID_PICK_ID; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 9f728a2bd8..5bb4293ef3 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -132,7 +132,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } - bool followNormal = true; + bool followNormal = false; if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } @@ -275,7 +275,7 @@ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& pr scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } - bool followNormal = true; + bool followNormal = false; if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } diff --git a/interface/src/raypick/StaticParabolaPick.cpp b/interface/src/raypick/StaticParabolaPick.cpp index 2cf8501980..a4e3ccb97f 100644 --- a/interface/src/raypick/StaticParabolaPick.cpp +++ b/interface/src/raypick/StaticParabolaPick.cpp @@ -7,13 +7,13 @@ // #include "StaticParabolaPick.h" -StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, - const PickFilter& filter, float maxDistance, bool enabled) : - ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, filter, maxDistance, enabled), - _position(position), _velocity(speed * direction) +StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, + bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), + _position(position), _velocity(direction) { } PickParabola StaticParabolaPick::getMathematicalPick() const { - return PickParabola(_position, _velocity, getAcceleration()); + return PickParabola(_position, getSpeed() * _velocity, getAcceleration()); } \ No newline at end of file diff --git a/interface/src/raypick/StaticParabolaPick.h b/interface/src/raypick/StaticParabolaPick.h index e0d6a2a3fa..df2057a6f0 100644 --- a/interface/src/raypick/StaticParabolaPick.h +++ b/interface/src/raypick/StaticParabolaPick.h @@ -14,7 +14,7 @@ class StaticParabolaPick : public ParabolaPick { public: StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, - const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); PickParabola getMathematicalPick() const override; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index 11e31bcccb..cde9c20cab 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -135,7 +135,6 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec } } else { intersectedSomething = true; - // FIXME: this needs to set triangle to something or it will carry the previous value bestDistance = distance; } @@ -168,7 +167,6 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm } } else { intersectedSomething = true; - // FIXME: this needs to set triangle to something or it will carry the previous value bestDistance = parabolicDistance; } @@ -263,29 +261,28 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi bool intersects = false; float boxDistance = FLT_MAX; - // if the ray intersects our bounding box, then continue + // if the pick intersects our bounding box, then continue if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return - if (bestLocalDistance > distance) { + if (boxDistance > distance) { return false; } - bestLocalDistance = distance; - - float childDistance = distance; - BoxFace childFace; - Triangle childTriangle; - // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { + float bestChildDistance = FLT_MAX; for (auto& child : _children) { // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; + bestChildDistance = childDistance; bestLocalFace = childFace; bestLocalTriangle = childTriangle; intersects = true; @@ -298,7 +295,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi BoxFace internalFace; Triangle internalTriangle; if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { - if (internalDistance < childDistance) { + if (internalDistance < bestLocalDistance) { bestLocalDistance = internalDistance; bestLocalFace = internalFace; bestLocalTriangle = internalTriangle; @@ -329,29 +326,28 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& bool intersects = false; float boxDistance = FLT_MAX; - // if the ray intersects our bounding box, then continue + // if the pick intersects our bounding box, then continue if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return - if (bestLocalDistance > parabolicDistance) { + if (boxDistance > parabolicDistance) { return false; } - bestLocalDistance = parabolicDistance; - - float childDistance = parabolicDistance; - BoxFace childFace; - Triangle childTriangle; - // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { + float bestChildDistance = FLT_MAX; for (auto& child : _children) { // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; + bestChildDistance = childDistance; bestLocalFace = childFace; bestLocalTriangle = childTriangle; intersects = true; @@ -364,7 +360,7 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& BoxFace internalFace; Triangle internalTriangle; if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { - if (internalDistance < childDistance) { + if (internalDistance < bestLocalDistance) { bestLocalDistance = internalDistance; bestLocalFace = internalFace; bestLocalTriangle = internalTriangle; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index d938feb22e..eea3cb0480 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -49,7 +49,6 @@ Script.include("/~/system/libraries/controllers.js"); blue: 73 }; - var TELEPORT_CANCEL_RANGE = 1; var COOL_IN_DURATION = 300; var handInfo = { @@ -146,8 +145,6 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - followNormal: true, - followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -161,8 +158,6 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - followNormal: true, - followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -175,8 +170,6 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - followNormal: true, - followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -190,8 +183,6 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - followNormal: true, - followNormalStrength: 0.1, speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, @@ -440,8 +431,7 @@ Script.include("/~/system/libraries/controllers.js"); var surfaceNormal = result.surfaceNormal; var angle = Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation))) * (180.0 / Math.PI); - if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT || - Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE * MyAvatar.sensorToWorldScale) { + if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; } else { return TARGET.SURFACE; From cb8e9f218b5021de35c25f11e3237aceef86f247 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 26 Jul 2018 11:23:28 -0700 Subject: [PATCH 16/22] reset all property fields when no selection --- scripts/system/html/js/entityProperties.js | 204 ++++++++++++++++++++- 1 file changed, 198 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a6a781b35f..8e0362c375 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -793,6 +793,7 @@ function loaded() { var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); + var elTextBackgroundColor = document.getElementById("property-text-background-color"); var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); @@ -842,7 +843,7 @@ function loaded() { var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZonehazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); + var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); @@ -906,10 +907,198 @@ function loaded() { deleteJSONMaterialEditor(); } } + elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; - elID.value = ""; elPropertiesList.className = ''; + + elID.value = ""; + elName.value = ""; + elLocked.checked = false; + elVisible.checked = false; + + elParentID.value = ""; + elParentJointIndex.value = ""; + + elColorRed.value = ""; + elColorGreen.value = ""; + elColorBlue.value = ""; + elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + + elPositionX.value = ""; + elPositionY.value = ""; + elPositionZ.value = ""; + + elDimensionsX.value = ""; + elDimensionsY.value = ""; + elDimensionsZ.value = ""; + + elRegistrationX.value = ""; + elRegistrationY.value = ""; + elRegistrationZ.value = ""; + + elRotationX.value = ""; + elRotationY.value = ""; + elRotationZ.value = ""; + + elLinearVelocityX.value = ""; + elLinearVelocityY.value = ""; + elLinearVelocityZ.value = ""; + elLinearDamping.value = ""; + + elAngularVelocityX.value = ""; + elAngularVelocityY.value = ""; + elAngularVelocityZ.value = ""; + elAngularDamping.value = ""; + + elGravityX.value = ""; + elGravityY.value = ""; + elGravityZ.value = ""; + + elAccelerationX.value = ""; + elAccelerationY.value = ""; + elAccelerationZ.value = ""; + + elRestitution.value = ""; + elFriction.value = ""; + elDensity.value = ""; + + elCollisionless.checked = false; + elDynamic.checked = false; + + elCollideStatic.checked = false; + elCollideKinematic.checked = false; + elCollideDynamic.checked = false; + elCollideMyAvatar.checked = false; + elCollideOtherAvatar.checked = false; + + elGrabbable.checked = false; + elWantsTrigger.checked = false; + elIgnoreIK.checked = false; + + elCloneable.checked = false; + elCloneableDynamic.checked = false; + elCloneableAvatarEntity.checked = false; + elCloneableGroup.style.display = "none"; + elCloneableLimit.value = ""; + elCloneableLifetime.value = ""; + + showElements(document.getElementsByClassName('can-cast-shadow-section'), true); + elCanCastShadow.checked = false; + + elCollisionSoundURL.value = ""; + elLifetime.value = ""; + elScriptURL.value = ""; + elServerScripts.value = ""; + elHyperlinkHref.value = ""; + elDescription.value = ""; + + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showSaveUserDataButton(); + showNewJSONEditorButton(); + + elShape.value = "Cube"; + setDropdownText(elShape); + + elModelURL.value = ""; + elCompoundShapeURL.value = ""; + elShapeType.value = "none"; + setDropdownText(elShapeType); + elModelAnimationURL.value = "" + elModelAnimationPlaying.checked = false; + elModelAnimationFPS.value = ""; + elModelAnimationFrame.value = ""; + elModelAnimationFirstFrame.value = ""; + elModelAnimationLastFrame.value = ""; + elModelAnimationLoop.checked = false; + elModelAnimationHold.checked = false; + elModelAnimationAllowTranslation.checked = false; + elModelTextures.value = ""; + elModelOriginalTextures.value = ""; + + elMaterialURL.value = ""; + elParentMaterialNameNumber.value = 0; + elParentMaterialNameCheckbox.checked = false; + elPriority.value = ""; + elMaterialMappingPosX.value = ""; + elMaterialMappingPosY.value = ""; + elMaterialMappingScaleX.value = ""; + elMaterialMappingScaleY.value = ""; + elMaterialMappingRot.value = ""; + + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); + + elZoneFlyingAllowed.checked = false; + elZoneGhostingAllowed.checked = false; + elZoneFilterURL.value = ""; + elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneKeyLightColorRed.value = ""; + elZoneKeyLightColorGreen.value = ""; + elZoneKeyLightColorBlue.value = ""; + elZoneKeyLightIntensity.value = ""; + elZoneKeyLightDirectionX.value = ""; + elZoneKeyLightDirectionY.value = ""; + elZoneKeyLightCastShadows.checked = false; + elZoneAmbientLightIntensity.value = ""; + elZoneAmbientLightURL.value = ""; + elZoneHazeRange.value = ""; + elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeColorRed.value = ""; + elZoneHazeColorGreen.value = ""; + elZoneHazeColorBlue.value = ""; + elZoneHazeBackgroundBlend.value = 0; + elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeGlareColorRed.value = ""; + elZoneHazeGlareColorGreen.value = ""; + elZoneHazeGlareColorBlue.value = ""; + elZoneHazeEnableGlare.checked = false; + elZoneHazeGlareAngle.value = ""; + elZoneHazeAltitudeEffect.checked = false; + elZoneHazeBaseRef.value = ""; + elZoneHazeCeiling.value = ""; + elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneSkyboxColorRed.value = ""; + elZoneSkyboxColorGreen.value = ""; + elZoneSkyboxColorBlue.value = ""; + elZoneSkyboxURL.value = ""; + showElements(document.getElementsByClassName('keylight-section'), true); + showElements(document.getElementsByClassName('skybox-section'), true); + showElements(document.getElementsByClassName('ambient-section'), true); + showElements(document.getElementsByClassName('haze-section'), true); + + elWebSourceURL.value = ""; + elWebDPI.value = ""; + + elImageURL.value = ""; + + elTextText.value = ""; + elTextLineHeight.value = ""; + elTextFaceCamera.checked = false; + elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextTextColorRed.value = ""; + elTextTextColorGreen.value = ""; + elTextTextColorBlue.value = ""; + elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextBackgroundColorRed.value = ""; + elTextBackgroundColorGreen.value = ""; + elTextBackgroundColorBlue.value = ""; + + elLightSpotLight.checked = false; + elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elLightColorRed.value = ""; + elLightColorGreen.value = ""; + elLightColorBlue.value = ""; + elLightIntensity.value = ""; + elLightFalloffRadius.value = ""; + elLightExponent.value = ""; + elLightCutoff.value = ""; + disableProperties(); } else if (data.selections.length > 1) { deleteJSONEditor(); @@ -1184,10 +1373,14 @@ function loaded() { elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + properties.textColor.blue + ")"; + properties.textColor.green + "," + + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; elTextTextColorBlue.value = properties.textColor.blue; + elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + + properties.backgroundColor.green + "," + + properties.backgroundColor.blue + ")"; elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; @@ -1260,13 +1453,12 @@ function loaded() { elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZonehazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); + elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); elShapeType.value = properties.shapeType; elCompoundShapeURL.value = properties.compoundShapeURL; @@ -1848,7 +2040,7 @@ function loaded() { elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); + elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); From 6a52b7b8f37c8f264642773e6f6dc635a7a386e6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 26 Jul 2018 12:15:12 -0700 Subject: [PATCH 17/22] CR improvements --- interface/src/raypick/ParabolaPick.cpp | 50 +++++++++++-------- interface/src/ui/overlays/Volume3DOverlay.cpp | 7 ++- libraries/entities/src/EntityTreeElement.cpp | 15 ++++-- libraries/entities/src/EntityTreeElement.h | 2 +- libraries/shared/src/AABox.cpp | 16 +----- libraries/shared/src/AABox.h | 2 +- libraries/shared/src/GeometryUtil.cpp | 18 +++---- 7 files changed, 55 insertions(+), 55 deletions(-) diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index 3507fe0407..c11d9cba95 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -15,40 +15,46 @@ #include "DependencyManager.h" PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { - ParabolaToEntityIntersectionResult entityRes = - DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), - getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); - if (entityRes.intersects) { - return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); - } else { - return std::make_shared(pick.toVariantMap()); + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToEntityIntersectionResult entityRes = + DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (entityRes.intersects) { + return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); + } } + return std::make_shared(pick.toVariantMap()); } PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { - 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 { - return std::make_shared(pick.toVariantMap()); + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + 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); + } } + return std::make_shared(pick.toVariantMap()); } PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { - 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, avatarRes.surfaceNormal, avatarRes.extraInfo); - } else { - return std::make_shared(pick.toVariantMap()); + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + 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, avatarRes.surfaceNormal, avatarRes.extraInfo); + } } + return std::make_shared(pick.toVariantMap()); } PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& 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); + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + 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); + } + return std::make_shared(pick.toVariantMap()); } float ParabolaPick::getSpeed() const { diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index ba22483d6b..c87650a77b 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -88,7 +88,12 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame // and testing intersection there. - return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; } bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 5a56f51847..5974fce6c5 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -316,7 +316,16 @@ EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin // for any details inside the cube to be closer so we don't need to consider them. QVariantMap localExtraInfo; float distanceToElementDetails = parabolicDistance; - EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, element, distanceToElementDetails, + // We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check + 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)); + EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, localExtraInfo, precisionPicking); if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { @@ -330,7 +339,7 @@ EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin } EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { @@ -350,7 +359,7 @@ EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3 // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection // below - if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration)) { + if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { return; } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 5bb8d4326e..d6f9db08d6 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -154,7 +154,7 @@ public: const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking); diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 0d2e6295d0..994df551fe 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -211,7 +211,7 @@ bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& dire || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); } -bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) const { +bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const { glm::vec3 localCenter = calcCenter() - origin; const float ONE_OVER_TWO_SQUARED = 0.25f; float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); @@ -221,24 +221,10 @@ bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const return true; } - 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) { diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index 43976b7481..fbc90cff47 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -74,7 +74,7 @@ public: bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const; - bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) const; + bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const; bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 963294a360..6fb06eb624 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -829,8 +829,8 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& float minDistance = FLT_MAX; if (fabsf(acceleration.z) < EPSILON) { - // Handle the degenerate case where we only have a line in the z-axis if (fabsf(velocity.z) > EPSILON) { + // Handle the degenerate case where we only have a line in the z-axis float possibleDistance = -origin.z / velocity.z; checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions); } @@ -853,19 +853,14 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& } bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - const glm::vec3& center, float radius, float& parabolicDistance) { + const glm::vec3& center, float radius, float& parabolicDistance) { glm::vec3 localCenter = center - origin; float radiusSquared = radius * radius; - float velocityLength2 = glm::length2(velocity); float accelerationLength = glm::length(acceleration); float minDistance = FLT_MAX; 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); @@ -971,12 +966,11 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& float minDistance = FLT_MAX; if (fabsf(localAcceleration.z) < EPSILON) { - if (fabsf(localVelocity.z) < EPSILON) { - return false; + if (fabsf(localVelocity.z) > EPSILON) { + float possibleDistance = -localOrigin.z / localVelocity.z; + checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); } - 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; From 74b3143bc3edd08df0c7bb44ccc81d9af4ae8359 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 26 Jul 2018 12:23:14 -0700 Subject: [PATCH 18/22] remove scaling from teleport script --- .../controllers/controllerModules/teleport.js | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index eea3cb0480..d46f62fbcb 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -218,34 +218,6 @@ Script.include("/~/system/libraries/controllers.js"); _this.state = TELEPORTER_STATES.TARGETTING; } }, COOL_IN_DURATION); - - // pad scale with avatar size - var AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS = Vec3.multiply(MyAvatar.sensorToWorldScale, TARGET_MODEL_DIMENSIONS); - - if (!Vec3.equal(AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS, cancelEnd.dimensions)) { - cancelEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - teleportEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - seatEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - - teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, - {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; - - Pointers.editRenderState(this.teleportParabolaHandVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportParabolaHandInvisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportParabolaHeadVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportParabolaHeadInvisible, "cancel", teleportRenderStates[0]); - - Pointers.editRenderState(this.teleportParabolaHandVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportParabolaHandInvisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportParabolaHeadVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportParabolaHeadInvisible, "teleport", teleportRenderStates[1]); - - Pointers.editRenderState(this.teleportParabolaHandVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportParabolaHandInvisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportParabolaHeadVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportParabolaHeadInvisible, "seat", teleportRenderStates[2]); - } }; this.isReady = function(controllerData, deltaTime) { From ff6ed971d862cdd3d809059b8a5e2d91f8332854 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 26 Jul 2018 15:40:27 -0700 Subject: [PATCH 19/22] comments, re-org, submesh number --- scripts/system/html/js/entityProperties.js | 80 ++++++++++++---------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8e0362c375..8b876304b7 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -929,6 +929,10 @@ function loaded() { elPositionY.value = ""; elPositionZ.value = ""; + elRotationX.value = ""; + elRotationY.value = ""; + elRotationZ.value = ""; + elDimensionsX.value = ""; elDimensionsY.value = ""; elDimensionsZ.value = ""; @@ -936,10 +940,6 @@ function loaded() { elRegistrationX.value = ""; elRegistrationY.value = ""; elRegistrationZ.value = ""; - - elRotationX.value = ""; - elRotationY.value = ""; - elRotationZ.value = ""; elLinearVelocityX.value = ""; elLinearVelocityY.value = ""; @@ -999,9 +999,22 @@ function loaded() { showSaveUserDataButton(); showNewJSONEditorButton(); + // Shape Properties elShape.value = "Cube"; setDropdownText(elShape); + // Light Properties + elLightSpotLight.checked = false; + elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elLightColorRed.value = ""; + elLightColorGreen.value = ""; + elLightColorBlue.value = ""; + elLightIntensity.value = ""; + elLightFalloffRadius.value = ""; + elLightExponent.value = ""; + elLightCutoff.value = ""; + + // Model Properties elModelURL.value = ""; elCompoundShapeURL.value = ""; elShapeType.value = "none"; @@ -1017,23 +1030,8 @@ function loaded() { elModelAnimationAllowTranslation.checked = false; elModelTextures.value = ""; elModelOriginalTextures.value = ""; - - elMaterialURL.value = ""; - elParentMaterialNameNumber.value = 0; - elParentMaterialNameCheckbox.checked = false; - elPriority.value = ""; - elMaterialMappingPosX.value = ""; - elMaterialMappingPosY.value = ""; - elMaterialMappingScaleX.value = ""; - elMaterialMappingScaleY.value = ""; - elMaterialMappingRot.value = ""; - - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showSaveMaterialDataButton(); - showNewJSONMaterialEditorButton(); - + + // Zone Properties elZoneFlyingAllowed.checked = false; elZoneGhostingAllowed.checked = false; elZoneFilterURL.value = ""; @@ -1072,11 +1070,7 @@ function loaded() { showElements(document.getElementsByClassName('ambient-section'), true); showElements(document.getElementsByClassName('haze-section'), true); - elWebSourceURL.value = ""; - elWebDPI.value = ""; - - elImageURL.value = ""; - + // Text Properties elTextText.value = ""; elTextLineHeight.value = ""; elTextFaceCamera.checked = false; @@ -1088,16 +1082,30 @@ function loaded() { elTextBackgroundColorRed.value = ""; elTextBackgroundColorGreen.value = ""; elTextBackgroundColorBlue.value = ""; - - elLightSpotLight.checked = false; - elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elLightColorRed.value = ""; - elLightColorGreen.value = ""; - elLightColorBlue.value = ""; - elLightIntensity.value = ""; - elLightFalloffRadius.value = ""; - elLightExponent.value = ""; - elLightCutoff.value = ""; + + // Image Properties + elImageURL.value = ""; + + // Web Properties + elWebSourceURL.value = ""; + elWebDPI.value = ""; + + // Material Properties + elMaterialURL.value = ""; + elParentMaterialNameNumber.value = ""; + elParentMaterialNameCheckbox.checked = false; + elPriority.value = ""; + elMaterialMappingPosX.value = ""; + elMaterialMappingPosY.value = ""; + elMaterialMappingScaleX.value = ""; + elMaterialMappingScaleY.value = ""; + elMaterialMappingRot.value = ""; + + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); disableProperties(); } else if (data.selections.length > 1) { From f5364f752fb01c761288d0517bbca411b2c6b4c1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 26 Jul 2018 18:01:24 -0700 Subject: [PATCH 20/22] fix scaling --- interface/src/raypick/ParabolaPick.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index c11d9cba95..b3e3f16345 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -58,13 +58,13 @@ PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { } float ParabolaPick::getSpeed() const { - return _scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed; + return (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed); } glm::vec3 ParabolaPick::getAcceleration() const { - float scale = _scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : 1.0f; + float scale = (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : 1.0f); if (_rotateAccelerationWithAvatar) { - return scale * DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis; + return scale * (DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis); } return scale * _accelerationAxis; } \ No newline at end of file From 1ef54b2fe662a9abb39a0040c811d3f8e8a8cef1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 27 Jul 2018 11:58:21 -0700 Subject: [PATCH 21/22] back to ray teleport --- interface/src/raypick/JointParabolaPick.cpp | 2 +- interface/src/raypick/JointRayPick.cpp | 2 +- .../controllers/controllerModules/teleport.js | 44 +++++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp index a0fea97995..11a2e90819 100644 --- a/interface/src/raypick/JointParabolaPick.cpp +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -34,7 +34,7 @@ PickParabola JointParabolaPick::getMathematicalPick() const { // Apply offset pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); - glm::vec3 dir = rot * glm::normalize(_dirOffset); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); return PickParabola(pos, getSpeed() * dir, getAcceleration()); } diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index 62912fdcd6..340014e7d2 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const { // Apply offset pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); - glm::vec3 dir = rot * glm::normalize(_dirOffset); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); return PickRay(pos, dir); } diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index d46f62fbcb..4255eeb400 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -61,19 +61,31 @@ Script.include("/~/system/libraries/controllers.js"); }; var cancelPath = { + type: "line3d", color: COLORS_TELEPORT_CANCEL, + ignorePickIntersection: true, alpha: 1, - width: 0.025 + solid: true, + drawInFront: true, + glow: 1.0 }; var teleportPath = { + type: "line3d", color: COLORS_TELEPORT_CAN_TELEPORT, + ignorePickIntersection: true, alpha: 1, - width: 0.025 + solid: true, + drawInFront: true, + glow: 1.0 }; var seatPath = { + type: "line3d", color: COLORS_TELEPORT_SEAT, + ignorePickIntersection: true, alpha: 1, - width: 0.025 + solid: true, + drawInFront: true, + glow: 1.0 }; var cancelEnd = { type: "model", @@ -99,7 +111,7 @@ Script.include("/~/system/libraries/controllers.js"); {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}]; - var DEFAULT_DISTANCE = 4.0; + var DEFAULT_DISTANCE = 50; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var coolInTimeout = null; @@ -139,8 +151,8 @@ Script.include("/~/system/libraries/controllers.js"); return otherModule; }; - this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Ray, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, @@ -149,11 +161,10 @@ Script.include("/~/system/libraries/controllers.js"); accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 4.0 + defaultRenderStates: teleportDefaultRenderStates }); - this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Ray, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, @@ -161,10 +172,9 @@ Script.include("/~/system/libraries/controllers.js"); speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 4.0 + renderStates: teleportRenderStates }); - this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Ray, { joint: "Avatar", filter: Picks.PICK_ENTITIES, faceAvatar: true, @@ -174,10 +184,9 @@ Script.include("/~/system/libraries/controllers.js"); accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 4.0 + defaultRenderStates: teleportDefaultRenderStates }); - this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { + this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Ray, { joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, @@ -186,8 +195,7 @@ Script.include("/~/system/libraries/controllers.js"); speed: speed, accelerationAxis: accelerationAxis, rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 4.0 + renderStates: teleportRenderStates }); this.cleanup = function() { From 3d4639bfdd861873eed4a946c0ad01b57c517bc5 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 27 Jul 2018 15:30:27 -0700 Subject: [PATCH 22/22] CR --- interface/src/raypick/LaserPointer.cpp | 14 +++++++------- interface/src/raypick/LaserPointer.h | 10 +++++----- interface/src/raypick/ParabolaPick.h | 12 ++++++------ interface/src/raypick/ParabolaPointer.cpp | 14 +++++++------- interface/src/raypick/ParabolaPointer.h | 10 +++++----- interface/src/raypick/PathPointer.cpp | 2 -- interface/src/raypick/PathPointer.h | 10 +++++----- interface/src/raypick/RayPick.h | 10 +++++----- 8 files changed, 40 insertions(+), 42 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index f65f2e9639..2382a95105 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -35,12 +35,12 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } } -glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) { +glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); - return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3()); + return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); } -glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) { +glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { auto rayPickResult = std::static_pointer_cast(pickResult); if (distance > 0.0f) { PickRay pick = PickRay(rayPickResult->pickVariant); @@ -50,17 +50,17 @@ glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float di } } -glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) { +glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); - return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3()); + return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f)); } -IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) { +IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); } -QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) { +QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? rayPickResult->objectID : QUuid()); } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index e6d493d449..95a5bccc6c 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -47,11 +47,11 @@ public: protected: void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; - glm::vec3 getPickOrigin(const PickResultPointer& pickResult) override; - glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) override; - glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) override; - IntersectionType getPickedObjectType(const PickResultPointer& pickResult) override; - QUuid getPickedObjectID(const PickResultPointer& pickResult) override; + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 03ee1a8da1..99a42a5380 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -20,7 +20,7 @@ public: ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : - PickResult(parabola.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), parabolicDistance(parabolicDistance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { + PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) { } ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) { @@ -34,14 +34,14 @@ public: extraInfo = parabolaPickResult.extraInfo; } - IntersectionType type { NONE }; - bool intersects { false }; + QVariantMap extraInfo; QUuid objectID; - float distance { FLT_MAX }; - float parabolicDistance { FLT_MAX }; glm::vec3 intersection { NAN }; glm::vec3 surfaceNormal { NAN }; - QVariantMap extraInfo; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + float parabolicDistance { FLT_MAX }; + bool intersects { false }; virtual QVariantMap toVariantMap() const override { QVariantMap toReturn; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 24ac9d956f..9371995a2a 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -62,12 +62,12 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria } } -glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) { +glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); - return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3()); + return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); } -glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) { +glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); if (distance > 0.0f) { PickParabola pick = PickParabola(parabolaPickResult->pickVariant); @@ -77,17 +77,17 @@ glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float } } -glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) { +glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); - return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3()); + return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f)); } -IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) { +IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE); } -QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) { +QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); return (parabolaPickResult ? parabolaPickResult->objectID : QUuid()); } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 4cabbc5a25..ee4977e1e4 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -101,11 +101,11 @@ public: protected: void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; - glm::vec3 getPickOrigin(const PickResultPointer& pickResult) override; - glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) override; - glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) override; - IntersectionType getPickedObjectType(const PickResultPointer& pickResult) override; - QUuid getPickedObjectID(const PickResultPointer& pickResult) override; + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 475d2abbc7..685611d77b 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -79,8 +79,6 @@ void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, co }); } -#include "ParabolaPick.h" - PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { PickResultPointer visualPickResult = pickResult; glm::vec3 origin = getPickOrigin(pickResult); diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index b0a3444fca..44c1b7f82b 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -121,11 +121,11 @@ protected: Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; - virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) = 0; - virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) = 0; - virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) = 0; - virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) = 0; - virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) = 0; + virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const = 0; + virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0; + virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0; + virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0; + virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 0; virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 6bdc2cb5b0..9410d39c1a 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -19,7 +19,7 @@ public: RayPickResult() {} RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : - PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { + PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) { } RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) { @@ -32,13 +32,13 @@ public: extraInfo = rayPickResult.extraInfo; } - IntersectionType type { NONE }; - bool intersects { false }; + QVariantMap extraInfo; QUuid objectID; - float distance { FLT_MAX }; glm::vec3 intersection { NAN }; glm::vec3 surfaceNormal { NAN }; - QVariantMap extraInfo; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + bool intersects { false }; virtual QVariantMap toVariantMap() const override { QVariantMap toReturn;