From 90091d11e4c8fa8a647c569e3af85bedbfc69e7a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 3 Jul 2018 09:47:31 -0700 Subject: [PATCH] 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 {