diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index 99672c1d57..9293259df6 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -28,16 +28,18 @@ #include #include +#include #include #include #include #include #include #include +#include #include +#include #include #include -#include using namespace openspace; diff --git a/apps/OpenSpace/ext/launcher/src/profile/assetsdialog.cpp b/apps/OpenSpace/ext/launcher/src/profile/assetsdialog.cpp index c2573cc381..ecaba582da 100644 --- a/apps/OpenSpace/ext/launcher/src/profile/assetsdialog.cpp +++ b/apps/OpenSpace/ext/launcher/src/profile/assetsdialog.cpp @@ -26,6 +26,7 @@ #include "profile/line.h" #include +#include #include #include #include diff --git a/apps/OpenSpace/ext/launcher/src/profile/deltatimesdialog.cpp b/apps/OpenSpace/ext/launcher/src/profile/deltatimesdialog.cpp index 33db001533..4fdcbabc3d 100644 --- a/apps/OpenSpace/ext/launcher/src/profile/deltatimesdialog.cpp +++ b/apps/OpenSpace/ext/launcher/src/profile/deltatimesdialog.cpp @@ -26,6 +26,7 @@ #include "profile/line.h" #include +#include #include #include #include diff --git a/include/openspace/camera/camera.h b/include/openspace/camera/camera.h new file mode 100644 index 0000000000..7321b5b85a --- /dev/null +++ b/include/openspace/camera/camera.h @@ -0,0 +1,173 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___CAMERA___H__ +#define __OPENSPACE_CORE___CAMERA___H__ + +#include +#include +#include + +namespace openspace { + +class SceneGraphNode; + +/** + * This class still needs some more love. Suggested improvements: + * - Accessors should return constant references to double precision class members. + * - Remove the scaling variable (What is it used for?) + * - Remove the maxFov and sinMaxfov variables. Redundant since the fov is embedded + * within the perspective projection matrix. + * - Remove focusposition, part of the integration with the scale graph. The + * "focus position" should not be needed since we assume the camera is always + * positioned relative to its origin. When orbiting another object (not in origin), + * the focus position should probably be handled outside the camera class + * (interaction handler) since it does not affect the state of the camera + * (only how it interacts). + * - The class might need some more reasonable accessors depending on use cases. + * (up vector world space?) + * - Make clear which function returns a combined view matrix (things that are + * dependent on the separate sgct nodes). + */ +class Camera { +public: + /** + * Used to explicitly show which variables within the Camera class that are used + * for caching. + */ + template + struct Cached { + T datum = T(0); + bool isDirty = true; + }; + + Camera() = default; + Camera(const Camera& o); + ~Camera() = default; + + // Mutators + void setPositionVec3(glm::dvec3 pos); + void setRotation(glm::dquat rotation); + void setScaling(float scaling); + void setMaxFov(float fov); + void setParent(SceneGraphNode* parent); + + // Relative mutators + void rotate(glm::dquat rotation); + + // Accessors + // Remove Vec3 from the name when psc is gone + const glm::dvec3& positionVec3() const; + glm::dvec3 eyePositionVec3() const; + const glm::dvec3& unsynchedPositionVec3() const; + const glm::dvec3& viewDirectionWorldSpace() const; + const glm::dvec3& lookUpVectorCameraSpace() const; + const glm::dvec3& lookUpVectorWorldSpace() const; + const glm::dmat4& viewRotationMatrix() const; + const glm::dmat4& viewScaleMatrix() const; + const glm::dquat& rotationQuaternion() const; + float maxFov() const; + float sinMaxFov() const; + SceneGraphNode* parent() const; + float scaling() const; + + // @TODO this should simply be called viewMatrix! + // Or it needs to be changed so that it actually is combined. Right now it is + // only the view matrix that is the same for all SGCT cameras. + // Right now this function returns the actual combined matrix which makes some + // of the old calls to the function wrong.. + const glm::dmat4& combinedViewMatrix() const; + + void invalidateCache(); + + void serialize(std::ostream& os) const; + void deserialize(std::istream& is); + + /** + * Handles SGCT's internal matrices. Also caches a calculated viewProjection + * matrix. This is the data that is different for different cameras within SGCT. + */ + class SgctInternal { + friend class Camera; + public: + void setSceneMatrix(glm::mat4 sceneMatrix); + void setViewMatrix(glm::mat4 viewMatrix); + void setProjectionMatrix(glm::mat4 projectionMatrix); + + const glm::mat4& sceneMatrix() const; + const glm::mat4& viewMatrix() const; + const glm::mat4& projectionMatrix() const; + const glm::mat4& viewProjectionMatrix() const; + + private: + SgctInternal() = default; + SgctInternal(const SgctInternal& o); + + glm::mat4 _sceneMatrix = glm::mat4(1.f); + glm::mat4 _viewMatrix = glm::mat4(1.f); + glm::mat4 _projectionMatrix = glm::mat4(1.f); + + mutable Cached _cachedViewProjectionMatrix; + mutable std::mutex _mutex; + } sgctInternal; + + // @TODO use Camera::SgctInternal interface instead + // [[deprecated("Replaced by Camera::SgctInternal::viewMatrix()")]] + const glm::mat4& viewMatrix() const; + // [[deprecated("Replaced by Camera::SgctInternal::projectionMatrix()")]] + const glm::mat4& projectionMatrix() const; + // [[deprecated("Replaced by Camera::SgctInternal::viewProjectionMatrix()")]] + const glm::mat4& viewProjectionMatrix() const; + + std::vector getSyncables(); + + // Static constants + static const glm::dvec3 ViewDirectionCameraSpace; + static const glm::dvec3 UpDirectionCameraSpace; + +private: + + SyncData _position = glm::dvec3(1.0, 1.0, 1.0); + SyncData _rotation = glm::dquat(glm::dvec3(1.0, 1.0, 1.0)); + SyncData _scaling = 1.f; + SceneGraphNode* _parent = nullptr; + + // _focusPosition to be removed + glm::dvec3 _focusPosition = glm::dvec3(0.0); + float _maxFov = 0.f; + + // Cached data + mutable Cached _cachedViewDirection; + mutable Cached _cachedLookupVector; + mutable Cached _cachedViewRotationMatrix; + mutable Cached _cachedViewScaleMatrix; + mutable Cached _cachedCombinedViewMatrix; + mutable Cached _cachedSinMaxFov; + + mutable std::mutex _mutex; +}; + +} // namespace openspace + +#endif // __OPENSPACE_CORE___CAMERA___H__ diff --git a/include/openspace/camera/camerapose.h b/include/openspace/camera/camerapose.h new file mode 100644 index 0000000000..4b6ee21aa5 --- /dev/null +++ b/include/openspace/camera/camerapose.h @@ -0,0 +1,39 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___CAMERAPOSE___H__ +#define __OPENSPACE_CORE___CAMERAPOSE___H__ + +#include + +namespace openspace { + +struct CameraPose { + glm::dvec3 position = glm::dvec3(0.0); + glm::dquat rotation = glm::dquat(1.0, 0.0, 0.0, 0.0); +}; + +} // namespace openspace + +#endif // __OPENSPACE_CORE___CAMERAPOSE___H__ diff --git a/include/openspace/interaction/sessionrecording.h b/include/openspace/interaction/sessionrecording.h index 749f7badfe..6e776b4ce8 100644 --- a/include/openspace/interaction/sessionrecording.h +++ b/include/openspace/interaction/sessionrecording.h @@ -26,7 +26,7 @@ #define __OPENSPACE_CORE___SESSIONRECORDING___H__ #include -#include +#include #include #include #include diff --git a/include/openspace/interaction/keyframenavigator.h b/include/openspace/navigation/keyframenavigator.h similarity index 100% rename from include/openspace/interaction/keyframenavigator.h rename to include/openspace/navigation/keyframenavigator.h index 767aced843..a651dd805d 100644 --- a/include/openspace/interaction/keyframenavigator.h +++ b/include/openspace/navigation/keyframenavigator.h @@ -25,8 +25,8 @@ #ifndef __OPENSPACE_CORE___KEYFRAMENAVIGATOR___H__ #define __OPENSPACE_CORE___KEYFRAMENAVIGATOR___H__ -#include #include +#include #include #include #include diff --git a/include/openspace/interaction/navigationhandler.h b/include/openspace/navigation/navigationhandler.h similarity index 87% rename from include/openspace/interaction/navigationhandler.h rename to include/openspace/navigation/navigationhandler.h index 15d080330b..36d4c51cf4 100644 --- a/include/openspace/interaction/navigationhandler.h +++ b/include/openspace/navigation/navigationhandler.h @@ -28,10 +28,12 @@ #include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include #include #include #include @@ -48,31 +50,14 @@ namespace openspace::scripting { struct LuaLibrary; } namespace openspace::interaction { struct JoystickInputStates; +struct NavigationState; struct WebsocketInputStates; class KeyframeNavigator; class OrbitalNavigator; +class PathNavigator; class NavigationHandler : public properties::PropertyOwner { public: - struct NavigationState { - NavigationState() = default; - NavigationState(const ghoul::Dictionary& dictionary); - NavigationState(std::string anchor, std::string aim, std::string referenceFrame, - glm::dvec3 position, std::optional up = std::nullopt, - double yaw = 0.0, double pitch = 0.0); - - ghoul::Dictionary dictionary() const; - static documentation::Documentation Documentation(); - - std::string anchor; - std::string aim; - std::string referenceFrame; - glm::dvec3 position = glm::dvec3(0.0); - std::optional up; - double yaw = 0.0; - double pitch = 0.0; - }; - NavigationHandler(); ~NavigationHandler(); @@ -80,11 +65,9 @@ public: void deinitialize(); // Mutators - void setFocusNode(SceneGraphNode* node); void resetCameraDirection(); - void setNavigationStateNextFame(NavigationState state); void setCamera(Camera* camera); void setInterpolationTime(float durationInSeconds); @@ -101,6 +84,7 @@ public: const OrbitalNavigator& orbitalNavigator() const; OrbitalNavigator& orbitalNavigator(); KeyframeNavigator& keyframeNavigator(); + PathNavigator& pathNavigator(); bool isKeyFrameInteractionEnabled() const; float interpolationTime() const; @@ -154,7 +138,7 @@ public: static scripting::LuaLibrary luaLibrary(); private: - void applyNavigationState(const NavigationHandler::NavigationState& ns); + void applyNavigationState(const NavigationState& ns); bool _playbackModeEnabled = false; @@ -164,6 +148,7 @@ private: OrbitalNavigator _orbitalNavigator; KeyframeNavigator _keyframeNavigator; + PathNavigator _pathNavigator; std::optional _pendingNavigationState; diff --git a/include/openspace/navigation/navigationstate.h b/include/openspace/navigation/navigationstate.h new file mode 100644 index 0000000000..3c7cb795f0 --- /dev/null +++ b/include/openspace/navigation/navigationstate.h @@ -0,0 +1,59 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___NAVIGATIONSTATE___H__ +#define __OPENSPACE_CORE___NAVIGATIONSTATE___H__ + +#include +#include + +namespace openspace { + struct CameraPose; +} // namespace openspace + +namespace openspace::interaction { + +struct NavigationState { + NavigationState() = default; + NavigationState(const ghoul::Dictionary& dictionary); + NavigationState(std::string anchor, std::string aim, std::string referenceFrame, + glm::dvec3 position, std::optional up = std::nullopt, + double yaw = 0.0, double pitch = 0.0); + + CameraPose cameraPose() const; + ghoul::Dictionary dictionary() const; + static documentation::Documentation Documentation(); + + std::string anchor; + std::string aim; + std::string referenceFrame; + glm::dvec3 position = glm::dvec3(0.0); + std::optional up; + double yaw = 0.0; + double pitch = 0.0; +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___NAVIGATIONSTATE___H__ diff --git a/include/openspace/interaction/orbitalnavigator.h b/include/openspace/navigation/orbitalnavigator.h similarity index 99% rename from include/openspace/interaction/orbitalnavigator.h rename to include/openspace/navigation/orbitalnavigator.h index a8e2a02697..a4b4a73e35 100644 --- a/include/openspace/interaction/orbitalnavigator.h +++ b/include/openspace/navigation/orbitalnavigator.h @@ -46,6 +46,7 @@ namespace openspace { class SceneGraphNode; class Camera; + struct CameraPose; struct SurfacePositionHandle; } // namespace @@ -104,11 +105,6 @@ private: glm::dquat globalRotation = glm::dquat(1.0, 0.0, 0.0, 0.0); }; - struct CameraPose { - glm::dvec3 position = glm::dvec3(0.0); - glm::dquat rotation = glm::dquat(1.0, 0.0, 0.0, 0.0); - }; - using Displacement = std::pair; struct Friction : public properties::PropertyOwner { diff --git a/include/openspace/navigation/path.h b/include/openspace/navigation/path.h new file mode 100644 index 0000000000..3192526dac --- /dev/null +++ b/include/openspace/navigation/path.h @@ -0,0 +1,83 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___PATH___H__ +#define __OPENSPACE_CORE___PATH___H__ + +#include +#include +#include +#include + +namespace openspace { + struct CameraPose; +} // namespace openspace + +namespace openspace::interaction { + +class Path { +public: + enum CurveType { + AvoidCollision, + Linear, + ZoomOutOverview + }; + + Path(Waypoint start, Waypoint end, CurveType type, + std::optional duration = std::nullopt); + + Waypoint startPoint() const; + Waypoint endPoint() const; + double duration() const; + double pathLength() const; + + std::vector controlPoints() const; + + CameraPose traversePath(double dt); + std::string currentAnchor() const; + bool hasReachedEnd() const; + + CameraPose interpolatedPose(double distance) const; + +private: + glm::dquat interpolateRotation(double u) const; + double speedAlongPath(double traveledDistance); + + Waypoint _start; + Waypoint _end; + double _duration; + CurveType _curveType; + + std::unique_ptr _curve; + + double _speedFactorFromDuration = 1.0; + + // Playback variables + double _traveledDistance = 0.0; + double _progressedTime = 0.0; // Time since playback started +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___PATH___H__ diff --git a/include/openspace/navigation/pathcreator.h b/include/openspace/navigation/pathcreator.h new file mode 100644 index 0000000000..e93256b4da --- /dev/null +++ b/include/openspace/navigation/pathcreator.h @@ -0,0 +1,63 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___PATHCREATOR___H__ +#define __OPENSPACE_CORE___PATHCREATOR___H__ + +#include +#include +#include + +namespace openspace { class SceneGraphNode; } + +namespace openspace::interaction { + +struct Waypoint; + +class PathCreator { +public: + // Create a path from a dictionary containing the instruction + static Path createPath(const ghoul::Dictionary& dictionary, + Path::CurveType curveType); + +private: + struct NodeInfo { + std::string identifier; + std::optional position; + std::optional height; + bool useTargetUpDirection; + }; + + static Waypoint waypointFromCamera(); + static Waypoint computeDefaultWaypoint(const NodeInfo& info, + const Waypoint& startPoint); + + // Test if the node lies within a given proximity radius of any relevant node + // in the scene + static SceneGraphNode* findNodeNearTarget(const SceneGraphNode* node); +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___PATHCREATOR___H__ diff --git a/include/openspace/navigation/pathcurve.h b/include/openspace/navigation/pathcurve.h new file mode 100644 index 0000000000..bae266228f --- /dev/null +++ b/include/openspace/navigation/pathcurve.h @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___PATHCURVE___H__ +#define __OPENSPACE_CORE___PATHCURVE___H__ + +#include +#include + +namespace openspace::interaction { + +struct Waypoint; + +class PathCurve { +public: + virtual ~PathCurve() = 0; + + const double length() const; + glm::dvec3 positionAt(double relativeDistance); + + // Compute curve parameter u that matches the input arc length s + double curveParameter(double s); + + virtual glm::dvec3 interpolate(double u); + + std::vector points(); + +protected: + // Precompute information related to the pspline parameters, that are + // needed for arc length reparameterization. Must be called after + // control point creation + void initializeParameterData(); + + double approximatedDerivative(double u, double h = 0.0001); + double arcLength(double limit = 1.0); + double arcLength(double lowerLimit, double upperLimit); + + std::vector _points; + unsigned int _nSegments; + + std::vector _curveParameterSteps; // per segment + std::vector _lengthSums; // per segment + double _totalLength; + + struct ParameterPair { + double u; // curve parameter + double s; // arc length parameter + }; + + std::vector _parameterSamples; +}; + +class LinearCurve : public PathCurve { +public: + LinearCurve(const Waypoint& start, const Waypoint& end); +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___PATHCURVE___H__ diff --git a/include/openspace/navigation/pathcurves/avoidcollisioncurve.h b/include/openspace/navigation/pathcurves/avoidcollisioncurve.h new file mode 100644 index 0000000000..130d848689 --- /dev/null +++ b/include/openspace/navigation/pathcurves/avoidcollisioncurve.h @@ -0,0 +1,48 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___AVOIDCOLLISIONCURVE___H__ +#define __OPENSPACE_CORE___AVOIDCOLLISIONCURVE___H__ + +#include + +namespace openspace { class SceneGraphNode; } + +namespace openspace::interaction { + +struct WayPoint; + +class AvoidCollisionCurve : public PathCurve { +public: + AvoidCollisionCurve(const Waypoint& start, const Waypoint& end); + +private: + void removeCollisions(int step = 0); + + std::vector _relevantNodes; +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_MODULE_AUTONAVIGATION___AVOIDCOLLISIONCURVE___H__ diff --git a/include/openspace/navigation/pathcurves/zoomoutoverviewcurve.h b/include/openspace/navigation/pathcurves/zoomoutoverviewcurve.h new file mode 100644 index 0000000000..b9ca0120ee --- /dev/null +++ b/include/openspace/navigation/pathcurves/zoomoutoverviewcurve.h @@ -0,0 +1,41 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___ZOOMOUTOVERVIEWCURVE___H__ +#define __OPENSPACE_CORE___ZOOMOUTOVERVIEWCURVE___H__ + +#include + +namespace openspace::interaction { + +struct WayPoint; + +class ZoomOutOverviewCurve : public PathCurve { +public: + ZoomOutOverviewCurve(const Waypoint& start, const Waypoint& end); +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___ZOOMOUTOVERVIEWCURVE___H__ diff --git a/include/openspace/navigation/pathhelperfunctions.h b/include/openspace/navigation/pathhelperfunctions.h new file mode 100644 index 0000000000..fb30babf46 --- /dev/null +++ b/include/openspace/navigation/pathhelperfunctions.h @@ -0,0 +1,83 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___PATHHELPERFUNCTIONS___H__ +#define __OPENSPACE_CORE___PATHHELPERFUNCTIONS___H__ + +#include +#include +#include +#include +#include +#include +#include + +namespace openspace::interaction::helpers { + + // Make interpolator parameter t [0,1] progress only inside a subinterval + double shiftAndScale(double t, double newStart, double newEnd); + + glm::dquat lookAtQuaternion(glm::dvec3 eye, glm::dvec3 center, glm::dvec3 up); + + glm::dvec3 viewDirection(const glm::dquat& q); + + bool lineSphereIntersection(glm::dvec3 linePoint1, glm::dvec3 linePoint2, + glm::dvec3 sphereCenter, double spehereRadius, glm::dvec3& intersectionPoint); + + bool isPointInsideSphere(const glm::dvec3& p, const glm::dvec3& c, double r); + + double simpsonsRule(double t0, double t1, int n, std::function f); + + double fivePointGaussianQuadrature(double t0, double t1, + std::function f); +} // namespace openspace::interaction::helpers + +namespace openspace::interaction::interpolation { + + glm::dquat easedSlerp(const glm::dquat q1, const glm::dquat q2, double t); + + // TODO: make all these into template functions. + // Alternatively, add cubicBezier interpolation in ghoul and only use + // ghoul's interpolator methods + + // Centripetal version alpha = 0, uniform for alpha = 0.5 and chordal for alpha = 1 + glm::dvec3 catmullRom(double t, const glm::dvec3& p0, const glm::dvec3& p1, + const glm::dvec3& p2, const glm::dvec3& p3, double alpha = 0.5); + + glm::dvec3 cubicBezier(double t, const glm::dvec3& cp1, const glm::dvec3& cp2, + const glm::dvec3& cp3, const glm::dvec3& cp4); + + glm::dvec3 linear(double t, const glm::dvec3& cp1, const glm::dvec3& cp2); + + glm::dvec3 hermite(double t, const glm::dvec3 &cp1, const glm::dvec3 &cp2, + const glm::dvec3 &tangent1, const glm::dvec3 &tangent2); + + glm::dvec3 piecewiseCubicBezier(double t, const std::vector& points, + const std::vector& tKnots); + + glm::dvec3 piecewiseLinear(double t, const std::vector& points, + const std::vector& tKnots); + +} // namespace openspace::interaction::interpolation +#endif // __OPENSPACE_CORE___PATHHELPERFUNCTIONS___H__ diff --git a/include/openspace/navigation/pathnavigator.h b/include/openspace/navigation/pathnavigator.h new file mode 100644 index 0000000000..b0d0b5cd64 --- /dev/null +++ b/include/openspace/navigation/pathnavigator.h @@ -0,0 +1,121 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___PATHNAVIGATOR___H__ +#define __OPENSPACE_CORE___PATHNAVIGATOR___H__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openspace { + class Camera; + struct CameraPose; + class SceneGraphNode; +} // namespace openspace + +namespace openspace::scripting { struct LuaLibrary; } + +namespace openspace::interaction { + +class Path; + +class PathNavigator : public properties::PropertyOwner { +public: + enum StopBehavior { + None = 0, + Orbit + }; + + PathNavigator(); + ~PathNavigator(); + + // Accessors + Camera* camera() const; + const SceneGraphNode* anchor() const; + double speedScale() const; + + bool hasCurrentPath() const; + bool hasFinished() const; + bool isPlayingPath() const; + + void updateCamera(double deltaTime); + void createPath(const ghoul::Dictionary& dictionary); + void clearPath(); + void startPath(); + void abortPath(); + void pausePath(); + void continuePath(); + + // TODO: remove functions for debugging + std::vector curvePositions(int nSteps) const; + std::vector curveOrientations(int nSteps) const; + std::vector curveViewDirections(int nSteps) const; + std::vector controlPoints() const; + + double minValidBoundingSphere() const; + const std::vector& relevantNodes(); + + /** + * \return The Lua library that contains all Lua functions available to affect the + * path navigation + */ + static scripting::LuaLibrary luaLibrary(); + +private: + void findRelevantNodes(); + + void removeRollRotation(CameraPose& pose, double deltaTime); + void applyStopBehavior(double deltaTime); + + void orbitAnchorNode(double deltaTime); + + std::unique_ptr _currentPath = nullptr; + bool _isPlaying = false; + + properties::OptionProperty _defaultCurveOption; + properties::BoolProperty _includeRoll; + properties::FloatProperty _speedScale; + properties::FloatProperty _orbitSpeedFactor; + + properties::BoolProperty _applyStopBehaviorWhenIdle; + properties::OptionProperty _stopBehavior; + + properties::DoubleProperty _minValidBoundingSphere; + properties::StringListProperty _relevantNodeTags; + + std::vector _relevantNodes; + bool _hasInitializedRelevantNodes = false; +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___PATHNAVIGATOR___H__ diff --git a/include/openspace/navigation/waypoint.h b/include/openspace/navigation/waypoint.h new file mode 100644 index 0000000000..d26f7bc650 --- /dev/null +++ b/include/openspace/navigation/waypoint.h @@ -0,0 +1,55 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___WAYPOINT___H__ +#define __OPENSPACE_CORE___WAYPOINT___H__ + +#include +#include + +namespace openspace { class SceneGraphNode; } + +namespace openspace::interaction { + +struct NavigationState; + +struct Waypoint { + Waypoint() = default; + Waypoint(const glm::dvec3& pos, const glm::dquat& rot, const std::string& ref); + Waypoint(const NavigationState& ns); + + static double findValidBoundingSphere(const SceneGraphNode* node); + + glm::dvec3 position() const; + glm::dquat rotation() const; + SceneGraphNode* node() const; + + CameraPose pose; + std::string nodeIdentifier; + double validBoundingSphere = 0.0; // to be able to handle nodes with faulty bounding spheres +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___WAYPOINT___H__ diff --git a/include/openspace/scene/profile.h b/include/openspace/scene/profile.h index 28a3a360eb..347edd925f 100644 --- a/include/openspace/scene/profile.h +++ b/include/openspace/scene/profile.h @@ -26,9 +26,9 @@ #define __OPENSPACE_CORE___PROFILE___H__ #include -#include #include #include +#include #include #include #include @@ -37,6 +37,8 @@ namespace openspace { +namespace interaction { struct NavigationState; } + namespace scripting { struct LuaLibrary; } class Profile { @@ -127,8 +129,7 @@ public: * and all of the property & asset changes that were made since startup. */ void saveCurrentSettingsToProfile(const properties::PropertyOwner& rootOwner, - std::string currentTime, - interaction::NavigationHandler::NavigationState navState); + std::string currentTime, interaction::NavigationState navState); /// If the value passed to this function is 'true', the addAsset and removeAsset /// functions will be no-ops instead diff --git a/include/openspace/scripting/scriptscheduler.h b/include/openspace/scripting/scriptscheduler.h index a7ec3405d8..64f45a745f 100644 --- a/include/openspace/scripting/scriptscheduler.h +++ b/include/openspace/scripting/scriptscheduler.h @@ -25,8 +25,8 @@ #ifndef __OPENSPACE_CORE___SCRIPTSCHEDULER___H__ #define __OPENSPACE_CORE___SCRIPTSCHEDULER___H__ +#include #include -#include #include #include diff --git a/include/openspace/util/updatestructures.h b/include/openspace/util/updatestructures.h index 00e1f57004..c3471b6971 100644 --- a/include/openspace/util/updatestructures.h +++ b/include/openspace/util/updatestructures.h @@ -25,7 +25,7 @@ #ifndef __OPENSPACE_CORE___UPDATESTRUCTURES___H__ #define __OPENSPACE_CORE___UPDATESTRUCTURES___H__ -#include +#include #include namespace openspace { diff --git a/modules/autonavigation/autonavigationmodule_lua.inl b/modules/autonavigation/autonavigationmodule_lua.inl index 3972f9705e..e53c8efff9 100644 --- a/modules/autonavigation/autonavigationmodule_lua.inl +++ b/modules/autonavigation/autonavigationmodule_lua.inl @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/modules/autonavigation/pathcreator.cpp b/modules/autonavigation/pathcreator.cpp index c12d74491a..a364b75ff7 100644 --- a/modules/autonavigation/pathcreator.cpp +++ b/modules/autonavigation/pathcreator.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/modules/autonavigation/pathnavigationhandler.cpp b/modules/autonavigation/pathnavigationhandler.cpp index d5171f0955..cc5b740816 100644 --- a/modules/autonavigation/pathnavigationhandler.cpp +++ b/modules/autonavigation/pathnavigationhandler.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/modules/autonavigation/waypoint.h b/modules/autonavigation/waypoint.h index 8296ebdccc..e07ec45866 100644 --- a/modules/autonavigation/waypoint.h +++ b/modules/autonavigation/waypoint.h @@ -25,7 +25,7 @@ #ifndef __OPENSPACE_MODULE_AUTONAVIGATION___WAYPOINT___H__ #define __OPENSPACE_MODULE_AUTONAVIGATION___WAYPOINT___H__ -#include +#include #include namespace openspace::pathnavigation { diff --git a/modules/base/dashboard/dashboarditemangle.cpp b/modules/base/dashboard/dashboarditemangle.cpp index 1e3ed867d8..a1b1415851 100644 --- a/modules/base/dashboard/dashboarditemangle.cpp +++ b/modules/base/dashboard/dashboarditemangle.cpp @@ -24,15 +24,15 @@ #include +#include #include #include #include -#include -#include +#include +#include #include #include #include -#include #include #include #include diff --git a/modules/base/dashboard/dashboarditemdistance.cpp b/modules/base/dashboard/dashboarditemdistance.cpp index 86613fc95c..1ea1d4e326 100644 --- a/modules/base/dashboard/dashboarditemdistance.cpp +++ b/modules/base/dashboard/dashboarditemdistance.cpp @@ -24,15 +24,15 @@ #include +#include #include #include #include -#include -#include +#include +#include #include #include #include -#include #include #include #include diff --git a/modules/base/dashboard/dashboarditemvelocity.cpp b/modules/base/dashboard/dashboarditemvelocity.cpp index c13782c8aa..15e3b6bee7 100644 --- a/modules/base/dashboard/dashboarditemvelocity.cpp +++ b/modules/base/dashboard/dashboarditemvelocity.cpp @@ -24,15 +24,14 @@ #include +#include #include #include #include #include -#include #include #include #include -#include #include #include #include diff --git a/modules/base/rendering/renderablenodeline.cpp b/modules/base/rendering/renderablenodeline.cpp index acb91f056f..e7279b840a 100644 --- a/modules/base/rendering/renderablenodeline.cpp +++ b/modules/base/rendering/renderablenodeline.cpp @@ -27,8 +27,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/exoplanets/exoplanetsmodule.cpp b/modules/exoplanets/exoplanetsmodule.cpp index 4658cee9c2..f65a905e47 100644 --- a/modules/exoplanets/exoplanetsmodule.cpp +++ b/modules/exoplanets/exoplanetsmodule.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 89b7fe275b..23e557b4b4 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -37,8 +37,9 @@ #include #include #include -#include -#include +#include +#include +#include #include #include #include @@ -597,7 +598,7 @@ void GlobeBrowsingModule::goToGeodetic3(const globebrowsing::RenderableGlobe& gl Geodetic2{ geo3.geodetic2.lat + 0.001, geo3.geodetic2.lon } ); - interaction::NavigationHandler::NavigationState state; + interaction::NavigationState state; state.anchor = globe.owner()->identifier(); state.referenceFrame = globe.owner()->identifier(); state.position = positionModelSpace; diff --git a/modules/globebrowsing/globebrowsingmodule_lua.inl b/modules/globebrowsing/globebrowsingmodule_lua.inl index 4ed196188c..387ba1de47 100644 --- a/modules/globebrowsing/globebrowsingmodule_lua.inl +++ b/modules/globebrowsing/globebrowsingmodule_lua.inl @@ -27,15 +27,15 @@ #include #include #include +#include #include #include -#include +#include #include #include #include #include #include -#include #include namespace openspace::globebrowsing::luascriptfunctions { diff --git a/modules/globebrowsing/src/dashboarditemglobelocation.cpp b/modules/globebrowsing/src/dashboarditemglobelocation.cpp index 0369043f4f..1ac56208b7 100644 --- a/modules/globebrowsing/src/dashboarditemglobelocation.cpp +++ b/modules/globebrowsing/src/dashboarditemglobelocation.cpp @@ -29,8 +29,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/globebrowsing/src/shadowcomponent.cpp b/modules/globebrowsing/src/shadowcomponent.cpp index f7c63a8df0..71daef9b4a 100644 --- a/modules/globebrowsing/src/shadowcomponent.cpp +++ b/modules/globebrowsing/src/shadowcomponent.cpp @@ -26,17 +26,14 @@ #include #include +#include #include #include #include #include #include -#include -#include -#include #include #include -#include #include #include #include diff --git a/modules/globebrowsing/src/shadowcomponent.h b/modules/globebrowsing/src/shadowcomponent.h index b39c832b2a..805197fefa 100644 --- a/modules/globebrowsing/src/shadowcomponent.h +++ b/modules/globebrowsing/src/shadowcomponent.h @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -34,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/modules/imgui/imguimodule.cpp b/modules/imgui/imguimodule.cpp index cc9f132ccb..7b5e803c07 100644 --- a/modules/imgui/imguimodule.cpp +++ b/modules/imgui/imguimodule.cpp @@ -29,8 +29,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/modules/imgui/src/guiglobebrowsingcomponent.cpp b/modules/imgui/src/guiglobebrowsingcomponent.cpp index 646a0bdca1..be2df2c713 100644 --- a/modules/imgui/src/guiglobebrowsingcomponent.cpp +++ b/modules/imgui/src/guiglobebrowsingcomponent.cpp @@ -29,8 +29,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/imgui/src/guijoystickcomponent.cpp b/modules/imgui/src/guijoystickcomponent.cpp index 29b746d804..b63fdb5ac4 100644 --- a/modules/imgui/src/guijoystickcomponent.cpp +++ b/modules/imgui/src/guijoystickcomponent.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include diff --git a/modules/imgui/src/guiparallelcomponent.cpp b/modules/imgui/src/guiparallelcomponent.cpp index 68641b74c5..2a05eb1d13 100644 --- a/modules/imgui/src/guiparallelcomponent.cpp +++ b/modules/imgui/src/guiparallelcomponent.cpp @@ -26,11 +26,11 @@ #include #include -#include -#include -#include +#include +#include #include #include +#include #include diff --git a/modules/imgui/src/guispacetimecomponent.cpp b/modules/imgui/src/guispacetimecomponent.cpp index 623ecb8c5c..ca7fbb6ebd 100644 --- a/modules/imgui/src/guispacetimecomponent.cpp +++ b/modules/imgui/src/guispacetimecomponent.cpp @@ -28,8 +28,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/server/src/topics/flightcontrollertopic.cpp b/modules/server/src/topics/flightcontrollertopic.cpp index b6f3a14820..bdc139e7dc 100644 --- a/modules/server/src/topics/flightcontrollertopic.cpp +++ b/modules/server/src/topics/flightcontrollertopic.cpp @@ -30,8 +30,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/server/src/topics/getpropertytopic.cpp b/modules/server/src/topics/getpropertytopic.cpp index f7ce36f341..093b8525da 100644 --- a/modules/server/src/topics/getpropertytopic.cpp +++ b/modules/server/src/topics/getpropertytopic.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/modules/webbrowser/src/eventhandler.cpp b/modules/webbrowser/src/eventhandler.cpp index a3eb3695a8..0b562d5aff 100644 --- a/modules/webbrowser/src/eventhandler.cpp +++ b/modules/webbrowser/src/eventhandler.cpp @@ -28,9 +28,9 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 23d6ff8de2..4d5b573345 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/set_openspace_compile_settings.cmake) set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/openspace.cpp + ${OPENSPACE_BASE_DIR}/src/camera/camera.cpp ${OPENSPACE_BASE_DIR}/src/documentation/core_registration.cpp ${OPENSPACE_BASE_DIR}/src/documentation/documentation.cpp ${OPENSPACE_BASE_DIR}/src/documentation/documentationengine.cpp @@ -49,11 +50,7 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/interaction/joystickcamerastates.cpp ${OPENSPACE_BASE_DIR}/src/interaction/keybindingmanager.cpp ${OPENSPACE_BASE_DIR}/src/interaction/keybindingmanager_lua.inl - ${OPENSPACE_BASE_DIR}/src/interaction/keyframenavigator.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/navigationhandler.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/navigationhandler_lua.inl ${OPENSPACE_BASE_DIR}/src/interaction/mousecamerastates.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/orbitalnavigator.cpp ${OPENSPACE_BASE_DIR}/src/interaction/scriptcamerastates.cpp ${OPENSPACE_BASE_DIR}/src/interaction/externinteraction.cpp ${OPENSPACE_BASE_DIR}/src/interaction/sessionrecording.cpp @@ -67,6 +64,20 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/mission/mission.cpp ${OPENSPACE_BASE_DIR}/src/mission/missionmanager.cpp ${OPENSPACE_BASE_DIR}/src/mission/missionmanager_lua.inl + ${OPENSPACE_BASE_DIR}/src/navigation/pathcurves/avoidcollisioncurve.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathcurves/zoomoutoverviewcurve.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/keyframenavigator.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/navigationhandler.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/navigationhandler_lua.inl + ${OPENSPACE_BASE_DIR}/src/navigation/navigationstate.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/orbitalnavigator.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/path.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathcreator.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathcurve.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathhelperfunctions.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathnavigator.cpp + ${OPENSPACE_BASE_DIR}/src/navigation/pathnavigator_lua.inl + ${OPENSPACE_BASE_DIR}/src/navigation/waypoint.cpp ${OPENSPACE_BASE_DIR}/src/network/parallelconnection.cpp ${OPENSPACE_BASE_DIR}/src/network/parallelpeer.cpp ${OPENSPACE_BASE_DIR}/src/network/parallelpeer_lua.inl @@ -151,7 +162,6 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/scripting/systemcapabilitiesbinding.cpp ${OPENSPACE_BASE_DIR}/src/util/blockplaneintersectiongeometry.cpp ${OPENSPACE_BASE_DIR}/src/util/boxgeometry.cpp - ${OPENSPACE_BASE_DIR}/src/util/camera.cpp ${OPENSPACE_BASE_DIR}/src/util/coordinateconversion.cpp ${OPENSPACE_BASE_DIR}/src/util/distanceconversion.cpp ${OPENSPACE_BASE_DIR}/src/util/factorymanager.cpp @@ -195,6 +205,8 @@ if (APPLE) endif () set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/json.h + ${OPENSPACE_BASE_DIR}/include/openspace/camera/camera.h + ${OPENSPACE_BASE_DIR}/include/openspace/camera/camerapose.h ${OPENSPACE_BASE_DIR}/include/openspace/documentation/core_registration.h ${OPENSPACE_BASE_DIR}/include/openspace/documentation/documentation.h ${OPENSPACE_BASE_DIR}/include/openspace/documentation/documentationengine.h @@ -222,10 +234,7 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/interaction/joystickinputstate.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/joystickcamerastates.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/keybindingmanager.h - ${OPENSPACE_BASE_DIR}/include/openspace/interaction/keyframenavigator.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/mousecamerastates.h - ${OPENSPACE_BASE_DIR}/include/openspace/interaction/navigationhandler.h - ${OPENSPACE_BASE_DIR}/include/openspace/interaction/orbitalnavigator.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/externinteraction.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/scriptcamerastates.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/sessionrecording.h @@ -237,6 +246,18 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/interaction/tasks/convertrecformattask.h ${OPENSPACE_BASE_DIR}/include/openspace/mission/mission.h ${OPENSPACE_BASE_DIR}/include/openspace/mission/missionmanager.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathcurves/avoidcollisioncurve.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathcurves/zoomoutoverviewcurve.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/keyframenavigator.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/navigationhandler.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/navigationstate.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/orbitalnavigator.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/path.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathcreator.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathcurve.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathhelperfunctions.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/pathnavigator.h + ${OPENSPACE_BASE_DIR}/include/openspace/navigation/waypoint.h ${OPENSPACE_BASE_DIR}/include/openspace/network/parallelconnection.h ${OPENSPACE_BASE_DIR}/include/openspace/network/parallelpeer.h ${OPENSPACE_BASE_DIR}/include/openspace/network/parallelserver.h @@ -325,7 +346,6 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/scripting/systemcapabilitiesbinding.h ${OPENSPACE_BASE_DIR}/include/openspace/util/blockplaneintersectiongeometry.h ${OPENSPACE_BASE_DIR}/include/openspace/util/boxgeometry.h - ${OPENSPACE_BASE_DIR}/include/openspace/util/camera.h ${OPENSPACE_BASE_DIR}/include/openspace/util/concurrentjobmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/util/concurrentjobmanager.inl ${OPENSPACE_BASE_DIR}/include/openspace/util/concurrentqueue.h diff --git a/src/util/camera.cpp b/src/camera/camera.cpp similarity index 99% rename from src/util/camera.cpp rename to src/camera/camera.cpp index 0cd86da56a..2a10dbe22e 100644 --- a/src/util/camera.cpp +++ b/src/camera/camera.cpp @@ -22,7 +22,7 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include #include diff --git a/src/documentation/core_registration.cpp b/src/documentation/core_registration.cpp index 3dd2756fb2..6c6fdc6c97 100644 --- a/src/documentation/core_registration.cpp +++ b/src/documentation/core_registration.cpp @@ -28,12 +28,13 @@ #include #include #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -59,9 +60,7 @@ namespace openspace { void registerCoreClasses(documentation::DocumentationEngine& engine) { engine.addDocumentation(LogFactoryDocumentation()); engine.addDocumentation(Mission::Documentation()); - engine.addDocumentation( - interaction::NavigationHandler::NavigationState::Documentation() - ); + engine.addDocumentation(interaction::NavigationState::Documentation()); engine.addDocumentation(Renderable::Documentation()); engine.addDocumentation(Rotation::Documentation()); engine.addDocumentation(Scale::Documentation()); @@ -89,6 +88,7 @@ void registerCoreClasses(scripting::ScriptEngine& engine) { engine.addLibrary(Time::luaLibrary()); engine.addLibrary(interaction::KeybindingManager::luaLibrary()); engine.addLibrary(interaction::NavigationHandler::luaLibrary()); + engine.addLibrary(interaction::PathNavigator::luaLibrary()); engine.addLibrary(interaction::SessionRecording::luaLibrary()); engine.addLibrary(interaction::ShortcutManager::luaLibrary()); engine.addLibrary(scripting::ScriptScheduler::luaLibrary()); diff --git a/src/engine/globals.cpp b/src/engine/globals.cpp index 3552eda0e2..f11536f5a4 100644 --- a/src/engine/globals.cpp +++ b/src/engine/globals.cpp @@ -36,10 +36,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index df7bd75df3..355668d083 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -38,8 +39,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -60,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/src/interaction/externinteraction.cpp b/src/interaction/externinteraction.cpp index 3f71f3403c..9ca2c5273e 100644 --- a/src/interaction/externinteraction.cpp +++ b/src/interaction/externinteraction.cpp @@ -25,15 +25,15 @@ #include #include +#include #include #include #include -#include -#include -#include +#include +#include +#include #include #include -#include #include #include diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index b29a37bf07..0511ac5ce9 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -24,20 +24,20 @@ #include +#include #include #include -#include -#include -#include #include #include +#include +#include +#include #include #include #include #include #include #include -#include #include #include #include diff --git a/src/interaction/keyframenavigator.cpp b/src/navigation/keyframenavigator.cpp similarity index 98% rename from src/interaction/keyframenavigator.cpp rename to src/navigation/keyframenavigator.cpp index d4441b912d..a4a251bb30 100644 --- a/src/interaction/keyframenavigator.cpp +++ b/src/navigation/keyframenavigator.cpp @@ -22,13 +22,13 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include +#include #include #include #include #include -#include #include #include #include diff --git a/src/interaction/navigationhandler.cpp b/src/navigation/navigationhandler.cpp similarity index 78% rename from src/interaction/navigationhandler.cpp rename to src/navigation/navigationhandler.cpp index c28fca94c6..bd186c61e4 100644 --- a/src/interaction/navigationhandler.cpp +++ b/src/navigation/navigationhandler.cpp @@ -22,16 +22,16 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #include #include #include @@ -66,102 +66,12 @@ namespace { "If this is set to 'true' the entire interaction is based off key frames rather " "than using the mouse interaction." }; - - struct [[codegen::Dictionary(NavigationHandler)]] Parameters { - // The identifier of the anchor node - std::string anchor; - - // The identifier of the aim node, if used - std::optional aim; - - // The identifier of the scene graph node to use as reference frame. If not - // specified, this will be the same as the anchor - std::optional referenceFrame; - - // The position of the camera relative to the anchor node, expressed in meters in - // the specified reference frame - glm::dvec3 position; - - // The up vector expressed in the coordinate system of the reference frame - std::optional up; - - // The yaw angle in radians. Positive angle means yawing camera to the right - std::optional yaw; - - // The pitch angle in radians. Positive angle means pitching camera upwards - std::optional pitch; - }; -#include "navigationhandler_codegen.cpp" } // namespace #include "navigationhandler_lua.inl" namespace openspace::interaction { -ghoul::Dictionary NavigationHandler::NavigationState::dictionary() const { - constexpr const char* KeyAnchor = "Anchor"; - constexpr const char* KeyAim = "Aim"; - constexpr const char* KeyPosition = "Position"; - constexpr const char* KeyUp = "Up"; - constexpr const char* KeyYaw = "Yaw"; - constexpr const char* KeyPitch = "Pitch"; - constexpr const char* KeyReferenceFrame = "ReferenceFrame"; - - ghoul::Dictionary cameraDict; - cameraDict.setValue(KeyPosition, position); - cameraDict.setValue(KeyAnchor, anchor); - - if (anchor != referenceFrame) { - cameraDict.setValue(KeyReferenceFrame, referenceFrame); - } - if (!aim.empty()) { - cameraDict.setValue(KeyAim, aim); - } - if (up.has_value()) { - cameraDict.setValue(KeyUp, *up); - - if (std::abs(yaw) > Epsilon) { - cameraDict.setValue(KeyYaw, yaw); - } - if (std::abs(pitch) > Epsilon) { - cameraDict.setValue(KeyPitch, pitch); - } - } - - return cameraDict; -} - -NavigationHandler::NavigationState::NavigationState(const ghoul::Dictionary& dictionary) { - const Parameters p = codegen::bake(dictionary); - - anchor = p.anchor; - position = p.position; - - referenceFrame = p.referenceFrame.value_or(anchor); - aim = p.aim.value_or(aim); - - if (p.up.has_value()) { - up = *p.up; - - yaw = p.yaw.value_or(yaw); - pitch = p.pitch.value_or(pitch); - } -} - -NavigationHandler::NavigationState::NavigationState(std::string anchor_, std::string aim_, - std::string referenceFrame_, - glm::dvec3 position_, - std::optional up_, - double yaw_, double pitch_) - : anchor(std::move(anchor_)) - , aim(std::move(aim_)) - , referenceFrame(std::move(referenceFrame_)) - , position(std::move(position_)) - , up(std::move(up_)) - , yaw(yaw_) - , pitch(pitch_) -{} - NavigationHandler::NavigationHandler() : properties::PropertyOwner({ "NavigationHandler" }) , _disableMouseInputs(KeyDisableMouseInputInfo, false) @@ -169,6 +79,7 @@ NavigationHandler::NavigationHandler() , _useKeyFrameInteraction(KeyFrameInfo, false) { addPropertySubOwner(_orbitalNavigator); + addPropertySubOwner(_pathNavigator); addProperty(_disableMouseInputs); addProperty(_disableJoystickInputs); @@ -210,8 +121,7 @@ void NavigationHandler::setCamera(Camera* camera) { _orbitalNavigator.setCamera(camera); } -void NavigationHandler::setNavigationStateNextFrame( - NavigationHandler::NavigationState state) +void NavigationHandler::setNavigationStateNextFrame(NavigationState state) { _pendingNavigationState = std::move(state); } @@ -228,6 +138,10 @@ KeyframeNavigator& NavigationHandler::keyframeNavigator() { return _keyframeNavigator; } +PathNavigator& NavigationHandler::pathNavigator() { + return _pathNavigator; +} + bool NavigationHandler::isKeyFrameInteractionEnabled() const { return _useKeyFrameInteraction; } @@ -252,6 +166,9 @@ void NavigationHandler::updateCamera(double deltaTime) { if (_useKeyFrameInteraction) { _keyframeNavigator.updateCamera(*_camera, _playbackModeEnabled); } + else if (_pathNavigator.isPlayingPath()) { + _pathNavigator.updateCamera(deltaTime); + } else { if (_disableJoystickInputs) { std::fill( @@ -264,67 +181,21 @@ void NavigationHandler::updateCamera(double deltaTime) { _orbitalNavigator.updateCameraStateFromStates(deltaTime); } } + + // If session recording (playback mode) was started in the midst of a camera path, + // abort the path + if (_playbackModeEnabled && _pathNavigator.isPlayingPath()) { + _pathNavigator.abortPath(); + } } -void NavigationHandler::applyNavigationState(const NavigationHandler::NavigationState& ns) -{ - const SceneGraphNode* referenceFrame = sceneGraphNode(ns.referenceFrame); - const SceneGraphNode* anchor = sceneGraphNode(ns.anchor); - - if (!anchor) { - LERROR(fmt::format( - "Could not find scene graph node '{}' used as anchor.", ns.referenceFrame - )); - return; - } - if (!ns.aim.empty() && !sceneGraphNode(ns.aim)) { - LERROR(fmt::format( - "Could not find scene graph node '{}' used as aim.", ns.referenceFrame - )); - return; - } - if (!referenceFrame) { - LERROR(fmt::format( - "Could not find scene graph node '{}' used as reference frame.", - ns.referenceFrame) - ); - return; - } - - const glm::dvec3 anchorWorldPosition = anchor->worldPosition(); - const glm::dmat3 referenceFrameTransform = referenceFrame->worldRotationMatrix(); - +void NavigationHandler::applyNavigationState(const NavigationState& ns) { _orbitalNavigator.setAnchorNode(ns.anchor); _orbitalNavigator.setAimNode(ns.aim); - const SceneGraphNode* anchorNode = _orbitalNavigator.anchorNode(); - const SceneGraphNode* aimNode = _orbitalNavigator.aimNode(); - if (!aimNode) { - aimNode = anchorNode; - } - - const glm::dvec3 cameraPositionWorld = anchorWorldPosition + - referenceFrameTransform * glm::dvec4(ns.position, 1.0); - - glm::dvec3 up = ns.up.has_value() ? - glm::normalize(referenceFrameTransform * *ns.up) : - glm::dvec3(0.0, 1.0, 0.0); - - // Construct vectors of a "neutral" view, i.e. when the aim is centered in view. - glm::dvec3 neutralView = - glm::normalize(aimNode->worldPosition() - cameraPositionWorld); - - glm::dquat neutralCameraRotation = glm::inverse(glm::quat_cast(glm::lookAt( - glm::dvec3(0.0), - neutralView, - up - ))); - - glm::dquat pitchRotation = glm::angleAxis(ns.pitch, glm::dvec3(1.0, 0.0, 0.0)); - glm::dquat yawRotation = glm::angleAxis(ns.yaw, glm::dvec3(0.0, -1.0, 0.0)); - - _camera->setPositionVec3(cameraPositionWorld); - _camera->setRotation(neutralCameraRotation * yawRotation * pitchRotation); + CameraPose pose = ns.cameraPose(); + _camera->setPositionVec3(pose.position); + _camera->setRotation(pose.rotation); _orbitalNavigator.clearPreviousState(); } @@ -383,7 +254,7 @@ void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyActio _inputState.keyboardCallback(key, modifier, action); } -NavigationHandler::NavigationState NavigationHandler::navigationState() const { +NavigationState NavigationHandler::navigationState() const { const SceneGraphNode* referenceFrame = _orbitalNavigator.followingAnchorRotation() ? _orbitalNavigator.anchorNode() : sceneGraph()->root(); @@ -395,7 +266,7 @@ NavigationHandler::NavigationState NavigationHandler::navigationState() const { return navigationState(*referenceFrame); } -NavigationHandler::NavigationState NavigationHandler::navigationState( +NavigationState NavigationHandler::navigationState( const SceneGraphNode& referenceFrame) const { const SceneGraphNode* anchor = _orbitalNavigator.anchorNode(); @@ -441,7 +312,7 @@ NavigationHandler::NavigationState NavigationHandler::navigationState( void NavigationHandler::saveNavigationState(const std::string& filepath, const std::string& referenceFrameIdentifier) { - NavigationHandler::NavigationState state; + NavigationState state; if (!referenceFrameIdentifier.empty()) { const SceneGraphNode* referenceFrame = sceneGraphNode(referenceFrameIdentifier); if (!referenceFrame) { @@ -560,10 +431,6 @@ std::vector NavigationHandler::joystickButtonCommand(int button) co return _orbitalNavigator.joystickStates().buttonCommand(button); } -documentation::Documentation NavigationHandler::NavigationState::Documentation() { - return codegen::doc("core_navigation_state"); -} - scripting::LuaLibrary NavigationHandler::luaLibrary() { return { "navigation", diff --git a/src/interaction/navigationhandler_lua.inl b/src/navigation/navigationhandler_lua.inl similarity index 98% rename from src/interaction/navigationhandler_lua.inl rename to src/navigation/navigationhandler_lua.inl index b31d487e58..edd3204c4d 100644 --- a/src/interaction/navigationhandler_lua.inl +++ b/src/navigation/navigationhandler_lua.inl @@ -22,8 +22,9 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include #include +#include +#include namespace openspace::luascriptfunctions { @@ -53,7 +54,7 @@ int getNavigationState(lua_State* L) { "lua::getNavigationState" ); - interaction::NavigationHandler::NavigationState state; + interaction::NavigationState state; if (n == 1) { const std::string referenceFrameIdentifier = ghoul::lua::value(L, 1); const SceneGraphNode* referenceFrame = sceneGraphNode(referenceFrameIdentifier); @@ -124,7 +125,7 @@ int setNavigationState(lua_State* L) { ghoul::lua::luaDictionaryFromState(L, navigationStateDictionary); openspace::documentation::TestResult r = openspace::documentation::testSpecification( - interaction::NavigationHandler::NavigationState::Documentation(), + interaction::NavigationState::Documentation(), navigationStateDictionary ); diff --git a/src/navigation/navigationstate.cpp b/src/navigation/navigationstate.cpp new file mode 100644 index 0000000000..de2a28e4db --- /dev/null +++ b/src/navigation/navigationstate.cpp @@ -0,0 +1,186 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "NavigationState"; + + const double Epsilon = 1E-7; + + struct [[codegen::Dictionary(NavigationState)]] Parameters { + // The identifier of the anchor node + std::string anchor; + + // The identifier of the aim node, if used + std::optional aim; + + // The identifier of the scene graph node to use as reference frame. If not + // specified, this will be the same as the anchor + std::optional referenceFrame; + + // The position of the camera relative to the anchor node, expressed in meters in + // the specified reference frame + glm::dvec3 position; + + // The up vector expressed in the coordinate system of the reference frame + std::optional up; + + // The yaw angle in radians. Positive angle means yawing camera to the right + std::optional yaw; + + // The pitch angle in radians. Positive angle means pitching camera upwards + std::optional pitch; + }; +#include "navigationstate_codegen.cpp" +} // namespace + +namespace openspace::interaction { + +NavigationState::NavigationState(const ghoul::Dictionary& dictionary) { + const Parameters p = codegen::bake(dictionary); + + anchor = p.anchor; + position = p.position; + + referenceFrame = p.referenceFrame.value_or(anchor); + aim = p.aim.value_or(aim); + + if (p.up.has_value()) { + up = *p.up; + + yaw = p.yaw.value_or(yaw); + pitch = p.pitch.value_or(pitch); + } +} + +NavigationState::NavigationState(std::string anchor_, std::string aim_, + std::string referenceFrame_, glm::dvec3 position_, + std::optional up_, + double yaw_, double pitch_) + : anchor(std::move(anchor_)) + , aim(std::move(aim_)) + , referenceFrame(std::move(referenceFrame_)) + , position(std::move(position_)) + , up(std::move(up_)) + , yaw(yaw_) + , pitch(pitch_) +{} + +CameraPose NavigationState::cameraPose() const { + const SceneGraphNode* referenceFrameNode = sceneGraphNode(referenceFrame); + const SceneGraphNode* anchorNode = sceneGraphNode(anchor); + + if (!anchorNode) { + LERROR(fmt::format( + "Could not find scene graph node '{}' used as anchor.", referenceFrame + )); + return CameraPose(); + } + if (!aim.empty() && !sceneGraphNode(aim)) { + LERROR(fmt::format( + "Could not find scene graph node '{}' used as aim.", referenceFrame + )); + return CameraPose(); + } + if (!referenceFrameNode) { + LERROR(fmt::format( + "Could not find scene graph node '{}' used as reference frame.", + referenceFrame) + ); + return CameraPose(); + } + + CameraPose resultingPose; + + const glm::dvec3 anchorWorldPosition = anchorNode->worldPosition(); + const glm::dmat3 referenceFrameTransform = referenceFrameNode->worldRotationMatrix(); + + resultingPose.position = anchorWorldPosition + + glm::dvec3(referenceFrameTransform * glm::dvec4(position, 1.0)); + + glm::dvec3 upVector = up.has_value() ? + glm::normalize(referenceFrameTransform * up.value()) : + glm::dvec3(0.0, 1.0, 0.0); + + // Construct vectors of a "neutral" view, i.e. when the aim is centered in view. + glm::dvec3 neutralView = + glm::normalize(anchorWorldPosition - resultingPose.position); + + glm::dquat neutralCameraRotation = glm::inverse(glm::quat_cast(glm::lookAt( + glm::dvec3(0.0), + neutralView, + upVector + ))); + + glm::dquat pitchRotation = glm::angleAxis(pitch, glm::dvec3(1.f, 0.f, 0.f)); + glm::dquat yawRotation = glm::angleAxis(yaw, glm::dvec3(0.f, -1.f, 0.f)); + + resultingPose.rotation = neutralCameraRotation * yawRotation * pitchRotation; + + return resultingPose; +} + +ghoul::Dictionary NavigationState::dictionary() const { + constexpr const char* KeyAnchor = "Anchor"; + constexpr const char* KeyAim = "Aim"; + constexpr const char* KeyPosition = "Position"; + constexpr const char* KeyUp = "Up"; + constexpr const char* KeyYaw = "Yaw"; + constexpr const char* KeyPitch = "Pitch"; + constexpr const char* KeyReferenceFrame = "ReferenceFrame"; + + ghoul::Dictionary cameraDict; + cameraDict.setValue(KeyPosition, position); + cameraDict.setValue(KeyAnchor, anchor); + + if (anchor != referenceFrame) { + cameraDict.setValue(KeyReferenceFrame, referenceFrame); + } + if (!aim.empty()) { + cameraDict.setValue(KeyAim, aim); + } + if (up.has_value()) { + cameraDict.setValue(KeyUp, *up); + + if (std::abs(yaw) > Epsilon) { + cameraDict.setValue(KeyYaw, yaw); + } + if (std::abs(pitch) > Epsilon) { + cameraDict.setValue(KeyPitch, pitch); + } + } + + return cameraDict; +} + +documentation::Documentation NavigationState::Documentation() { + return codegen::doc("core_navigation_state"); +} + +} // namespace openspace::interaction diff --git a/src/interaction/orbitalnavigator.cpp b/src/navigation/orbitalnavigator.cpp similarity index 99% rename from src/interaction/orbitalnavigator.cpp rename to src/navigation/orbitalnavigator.cpp index b90779a4f4..e0a08c1078 100644 --- a/src/interaction/orbitalnavigator.cpp +++ b/src/navigation/orbitalnavigator.cpp @@ -22,7 +22,8 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include +#include #include #include #include @@ -643,8 +644,7 @@ glm::dquat OrbitalNavigator::composeCameraRotation( return decomposition.globalRotation * decomposition.localRotation; } -Camera* OrbitalNavigator::camera() const -{ +Camera* OrbitalNavigator::camera() const { return _camera; } @@ -909,9 +909,8 @@ OrbitalNavigator::CameraRotationDecomposition return { localCameraRotation, globalCameraRotation }; } -OrbitalNavigator::CameraPose OrbitalNavigator::followAim(CameraPose pose, - glm::dvec3 cameraToAnchor, - Displacement anchorToAim) +CameraPose OrbitalNavigator::followAim(CameraPose pose, glm::dvec3 cameraToAnchor, + Displacement anchorToAim) { CameraRotationDecomposition anchorDecomp = decomposeCameraRotation(pose, pose.position + cameraToAnchor); diff --git a/src/navigation/path.cpp b/src/navigation/path.cpp new file mode 100644 index 0000000000..6b4e055217 --- /dev/null +++ b/src/navigation/path.cpp @@ -0,0 +1,224 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "Path"; +} // namespace + +namespace openspace::interaction { + +Path::Path(Waypoint start, Waypoint end, CurveType type, + std::optional duration) + : _start(start), _end(end), _curveType(type) +{ + switch (_curveType) { + case CurveType::AvoidCollision: + _curve = std::make_unique(_start, _end); + break; + case CurveType::Linear: + _curve = std::make_unique(_start, _end); + break; + case CurveType::ZoomOutOverview: + _curve = std::make_unique(_start, _end); + break; + default: + LERROR("Could not create curve. Type does not exist!"); + throw ghoul::MissingCaseException(); + } + + const auto defaultDuration = [](double pathlength) { + const double speedScale = + global::navigationHandler->pathNavigator().speedScale(); + return std::log(pathlength) / speedScale; + }; + + _duration = duration.value_or(defaultDuration(pathLength())); + + // Compute speed factor to match the generated path length and duration, by + // traversing the path and computing how much faster/slower it should be + const int nSteps = 500; + const double dt = (_duration / nSteps) > 0.01 ? (_duration / nSteps) : 0.01; + + while (!hasReachedEnd()) { + traversePath(dt); + } + + _speedFactorFromDuration = _progressedTime / _duration; + + // Reset playback variables + _traveledDistance = 0.0; + _progressedTime = 0.0; +} + +Waypoint Path::startPoint() const { return _start; } + +Waypoint Path::endPoint() const { return _end; } + +double Path::duration() const { return _duration; } + +double Path::pathLength() const { return _curve->length(); } + +std::vector Path::controlPoints() const { + return _curve->points(); +} + +CameraPose Path::traversePath(double dt) { + const double speed = _speedFactorFromDuration * speedAlongPath(_traveledDistance); + const double displacement = dt * speed; + + _progressedTime += dt; + _traveledDistance += displacement; + + return interpolatedPose(_traveledDistance); +} + +std::string Path::currentAnchor() const { + bool pastHalfway = (_traveledDistance / pathLength()) > 0.5; + return (pastHalfway) ? _end.nodeIdentifier : _start.nodeIdentifier; +} + +bool Path::hasReachedEnd() const { + return (_traveledDistance / pathLength()) >= 1.0; +} + +CameraPose Path::interpolatedPose(double distance) const { + const double relativeDistance = distance / pathLength(); + CameraPose cs; + cs.position = _curve->positionAt(relativeDistance); + cs.rotation = interpolateRotation(relativeDistance); + return cs; +} + +glm::dquat Path::interpolateRotation(double t) const { + switch (_curveType) { + case CurveType::AvoidCollision: + case CurveType::Linear: + return interpolation::easedSlerp(_start.rotation(), _end.rotation(), t); + case CurveType::ZoomOutOverview: + { + const double t1 = 0.2; + const double t2 = 0.8; + + const glm::dvec3 startPos = _curve->positionAt(0.0); + const glm::dvec3 endPos = _curve->positionAt(1.0); + const glm::dvec3 startNodePos = _start.node()->worldPosition(); + const glm::dvec3 endNodePos = _end.node()->worldPosition(); + + glm::dvec3 lookAtPos; + if (t < t1) { + // Compute a position in front of the camera at the start orientation + const double inFrontDistance = glm::distance(startPos, startNodePos); + const glm::dvec3 viewDir = helpers::viewDirection(_start.rotation()); + const glm::dvec3 inFrontOfStart = startPos + inFrontDistance * viewDir; + + const double tScaled = ghoul::cubicEaseInOut(t / t1); + lookAtPos = + ghoul::interpolateLinear(tScaled, inFrontOfStart, startNodePos); + } + else if (t <= t2) { + const double tScaled = ghoul::cubicEaseInOut((t - t1) / (t2 - t1)); + lookAtPos = ghoul::interpolateLinear(tScaled, startNodePos, endNodePos); + } + else if (t > t2) { + // Compute a position in front of the camera at the end orientation + const double inFrontDistance = glm::distance(endPos, endNodePos); + const glm::dvec3 viewDir = helpers::viewDirection(_end.rotation()); + const glm::dvec3 inFrontOfEnd = endPos + inFrontDistance * viewDir; + + const double tScaled = ghoul::cubicEaseInOut((t - t2) / (1.0 - t2)); + lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd); + } + + // Handle up vector separately + glm::dvec3 startUp = _start.rotation() * glm::dvec3(0.0, 1.0, 0.0); + glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0); + + double tUp = helpers::shiftAndScale(t, t1, t2); + tUp = ghoul::sineEaseInOut(tUp); + glm::dvec3 up = ghoul::interpolateLinear(tUp, startUp, endUp); + + return helpers::lookAtQuaternion(_curve->positionAt(t), lookAtPos, up); + } + default: + throw ghoul::MissingCaseException(); + } +} + +double Path::speedAlongPath(double traveledDistance) { + const glm::dvec3 endNodePos = _end.node()->worldPosition(); + const glm::dvec3 startNodePos = _start.node()->worldPosition(); + + const CameraPose prevPose = interpolatedPose(traveledDistance); + const double distanceToEndNode = glm::distance(prevPose.position, endNodePos); + const double distanceToStartNode = glm::distance(prevPose.position, startNodePos); + + // Decide which is the closest node + SceneGraphNode* closestNode = _start.node(); + glm::dvec3 closestPos = startNodePos; + + if (distanceToEndNode < distanceToStartNode) { + closestPos = endNodePos; + closestNode = _end.node(); + } + + const double distanceToClosestNode = glm::distance(closestPos, prevPose.position); + double speed = distanceToClosestNode; + + // Dampen speed in beginning of path + const double startUpDistance = 2.0 * _start.node()->boundingSphere(); + if (traveledDistance < startUpDistance) { + speed *= traveledDistance / startUpDistance + 0.01; + } + + // Dampen speed in end of path + // Note: this leads to problems when the full length of the path is really big + const double closeUpDistance = 2.0 * _end.node()->boundingSphere(); + if (traveledDistance > (pathLength() - closeUpDistance)) { + const double remainingDistance = pathLength() - traveledDistance; + speed *= remainingDistance / closeUpDistance + 0.01; + } + + // TODO: also dampen speed based on curvature, or make sure the curve has a rounder shape + + // TODO: check for when path is shorter than the starUpDistance or closeUpDistance variables + + return speed; +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathcreator.cpp b/src/navigation/pathcreator.cpp new file mode 100644 index 0000000000..b8382938ed --- /dev/null +++ b/src/navigation/pathcreator.cpp @@ -0,0 +1,249 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "PathCreator"; + constexpr const float Epsilon = 1e-5f; + + // TODO: where should this documentation be? + struct [[codegen::Dictionary(PathInstruction)]] Parameters { + enum class Type { + Node, + NavigationState + }; + Type type; + + // The desired duration traversing the specified path segment should take + std::optional duration; + + // (Node): The target node of the camera path. Not optional for 'Node' instructions + std::optional target; + + // (Node): An optional position in relation to the target node, in model + // coordinates (meters) + std::optional position; + + // (Node): An optional height in relation to the target node, in meters + std::optional height; + + // (Node): If true, the up direction of the node is taken into account when + // computing the wayopoint for this instruction + std::optional useTargetUpDirection; + + // (NavigationState): A navigation state that will be the target + // of this path segment + std::optional navigationState + [[codegen::reference("core_navigation_state")]]; + + // A navigation state that determines the start state for the camera path + std::optional startState + [[codegen::reference("core_navigation_state")]]; + }; +#include "pathcreator_codegen.cpp" +} // namespace + +namespace openspace::interaction { + +Path PathCreator::createPath(const ghoul::Dictionary& dictionary, + Path::CurveType curveType) +{ + const Parameters p = codegen::bake(dictionary); + + std::optional duration = p.duration; + + bool hasStart = p.startState.has_value(); + Waypoint startPoint = hasStart ? Waypoint(p.startState.value()) : waypointFromCamera(); + + // TODO: also handle curve type here + + std::vector waypoints; + switch (p.type) { + case Parameters::Type::NavigationState: { + if (!p.navigationState.has_value()) { + throw ghoul::RuntimeError("A navigation state is required"); + } + + const NavigationState navigationState = + NavigationState(p.navigationState.value()); + + waypoints = { Waypoint(navigationState) }; + break; + } + case Parameters::Type::Node: { + if (!p.target.has_value()) { + throw ghoul::RuntimeError("A target node is required"); + } + + const std::string nodeIdentifier = p.target.value(); + const SceneGraphNode* targetNode = sceneGraphNode(nodeIdentifier); + + if (!targetNode) { + throw ghoul::RuntimeError(fmt::format( + "Could not find target node '{}'", nodeIdentifier + )); + } + + NodeInfo info { + nodeIdentifier, + p.position, + p.height, + p.useTargetUpDirection.value_or(false) + }; + + waypoints = { computeDefaultWaypoint(info, startPoint) }; + break; + } + default: { + LERROR(fmt::format("Uknown instruciton type: {}", p.type)); + throw ghoul::MissingCaseException(); + } + } + + // TODO: allow for an instruction to represent a list of waypoints + Waypoint waypointToAdd = waypoints[0]; + + return Path(startPoint, waypointToAdd, curveType, duration); +} + +Waypoint PathCreator::waypointFromCamera() { + Camera* camera = global::navigationHandler->camera(); + const glm::dvec3 pos = camera->positionVec3(); + const glm::dquat rot = camera->rotationQuaternion(); + const std::string node = global::navigationHandler->anchorNode()->identifier(); + return Waypoint{ pos, rot, node }; +} + +Waypoint PathCreator::computeDefaultWaypoint(const NodeInfo& info, + const Waypoint& startPoint) +{ + const SceneGraphNode* targetNode = sceneGraphNode(info.identifier); + if (!targetNode) { + LERROR(fmt::format("Could not find target node '{}'", info.identifier)); + return Waypoint(); + } + + glm::dvec3 targetPos; + if (info.position.has_value()) { + // Note that the anchor and reference frame is our targetnode. + // The position in instruction is given is relative coordinates + targetPos = targetNode->worldPosition() + + targetNode->worldRotationMatrix() * info.position.value(); + } + else { + const glm::dvec3 nodePos = targetNode->worldPosition(); + const glm::dvec3 sunPos = glm::dvec3(0.0, 0.0, 0.0); + const SceneGraphNode* closeNode = findNodeNearTarget(targetNode); + + glm::dvec3 stepDirection; + if (closeNode) { + // If the node is close to another node in the scene, make sure that the + // position is set to minimize risk of collision + stepDirection = glm::normalize(nodePos - closeNode->worldPosition()); + } + else if (glm::length(sunPos - nodePos) < Epsilon) { + // Special case for when the target is the Sun. Assumption: want an overview of + // the solar system, and not stay in the orbital plane + stepDirection = glm::dvec3(0.0, 0.0, 1.0); + } + else { + // Go to a point that is being lit up by the sun, slightly offsetted from sun + // direction + const glm::dvec3 prevPos = startPoint.position(); + const glm::dvec3 targetToPrev = prevPos - nodePos; + const glm::dvec3 targetToSun = sunPos - nodePos; + + constexpr const float defaultPositionOffsetAngle = -30.f; // degrees + constexpr const float angle = glm::radians(defaultPositionOffsetAngle); + const glm::dvec3 axis = glm::normalize(glm::cross(targetToPrev, targetToSun)); + const glm::dquat offsetRotation = angleAxis(static_cast(angle), axis); + + stepDirection = glm::normalize(offsetRotation * targetToSun); + } + + const double radius = Waypoint::findValidBoundingSphere(targetNode); + const double defaultHeight = 2.0 * radius; + const double height = info.height.value_or(defaultHeight); + + targetPos = nodePos + stepDirection * (radius + height); + } + + glm::dvec3 up = global::navigationHandler->camera()->lookUpVectorWorldSpace(); + if (info.useTargetUpDirection) { + // @TODO (emmbr 2020-11-17) For now, this is hardcoded to look good for Earth, + // which is where it matters the most. A better solution would be to make each + // sgn aware of its own 'up' and query + up = targetNode->worldRotationMatrix() * glm::dvec3(0.0, 0.0, 1.0); + } + + const glm::dvec3 lookAtPos = targetNode->worldPosition(); + const glm::dquat targetRot = helpers::lookAtQuaternion(targetPos, lookAtPos, up); + + return Waypoint(targetPos, targetRot, info.identifier); +} + +SceneGraphNode* PathCreator::findNodeNearTarget(const SceneGraphNode* node) { + const std::vector& relevantNodes = + global::navigationHandler->pathNavigator().relevantNodes(); + + for (SceneGraphNode* n : relevantNodes) { + if (n->identifier() == node->identifier()) { + continue; + } + + constexpr const float proximityRadiusFactor = 3.f; + + const float bs = static_cast(n->boundingSphere()); + const float proximityRadius = proximityRadiusFactor * bs; + const glm::dvec3 posInModelCoords = + glm::inverse(n->modelTransform()) * glm::dvec4(node->worldPosition(), 1.0); + + bool isClose = helpers::isPointInsideSphere( + posInModelCoords, + glm::dvec3(0.0, 0.0, 0.0), + proximityRadius + ); + + if (isClose) { + return n; + } + } + + return nullptr; +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathcurve.cpp b/src/navigation/pathcurve.cpp new file mode 100644 index 0000000000..158070114a --- /dev/null +++ b/src/navigation/pathcurve.cpp @@ -0,0 +1,236 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "PathCurve"; + constexpr const int NrSamplesPerSegment = 100; +} // namespace + +namespace openspace::interaction { + +PathCurve::~PathCurve() {} + +const double PathCurve::length() const { + return _totalLength; +} + +glm::dvec3 PathCurve::positionAt(double relativeDistance) { + const double u = curveParameter(relativeDistance * _totalLength); + return interpolate(u); +} + +// Compute the curve parameter from an arc length value, using a combination of +// Newton's method and bisection. Source: +// https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf +// Input s is a length value, in the range [0, _totalLength] +// Returns curve parameter in range [0, 1] +double PathCurve::curveParameter(double s) { + if (s <= 0.0) return 0.0; + if (s >= _totalLength) return 1.0; + + unsigned int segmentIndex = 1; + while (s > _lengthSums[segmentIndex]) { + segmentIndex++; + } + + const int startIndex = (segmentIndex - 1) * NrSamplesPerSegment; + const int endIndex = segmentIndex * NrSamplesPerSegment + 1; + + const double segmentS = s - _lengthSums[segmentIndex - 1]; + const double uMin = _curveParameterSteps[segmentIndex - 1]; + const double uMax = _curveParameterSteps[segmentIndex]; + + // Use samples to find an initial guess for Newton's method + // Find first sample with s larger than input s + auto sampleIterator = std::upper_bound( + _parameterSamples.begin() + startIndex, + _parameterSamples.begin() + endIndex, + ParameterPair{ 0.0 , s }, // 0.0 is a dummy value for u + [](ParameterPair lhs, ParameterPair rhs) { + return lhs.s < rhs.s; + } + ); + + const ParameterPair& sample = *sampleIterator; + const ParameterPair& prevSample = *(sampleIterator - 1); + const double uPrev = prevSample.u; + const double sPrev = prevSample.s; + const double slope = (sample.u - uPrev) / (sample.s - sPrev); + double u = uPrev + slope * (s - sPrev); + + constexpr const int maxIterations = 50; + + // Initialize root bounding limits for bisection + double lower = uMin; + double upper = uMax; + + for (int i = 0; i < maxIterations; ++i) { + double F = arcLength(uMin, u) - segmentS; + + // The error we tolerate, in meters. Note that distances are very large + constexpr const double tolerance = 0.005; + if (std::abs(F) <= tolerance) { + return u; + } + + // Generate a candidate for Newton's method + double dfdu = approximatedDerivative(u); // > 0 + double uCandidate = u - F / dfdu; + + // Update root-bounding interval and test candidate + if (F > 0) { // => candidate < u <= upper + upper = u; + u = (uCandidate <= lower) ? (upper + lower) / 2.0 : uCandidate; + } + else { // F < 0 => lower <= u < candidate + lower = u; + u = (uCandidate >= upper) ? (upper + lower) / 2.0 : uCandidate; + } + } + + // No root was found based on the number of iterations and tolerance. However, it is + // safe to report the last computed u value, since it is within the segment interval + return u; +} + +std::vector PathCurve::points() { + return _points; +} + +void PathCurve::initializeParameterData() { + _nSegments = static_cast(_points.size() - 3); + + ghoul_assert(_nSegments > 0, "Cannot have a curve with zero segments!"); + + _curveParameterSteps.clear(); + _lengthSums.clear(); + _parameterSamples.clear(); + + // Evenly space out parameter intervals + _curveParameterSteps.reserve(_nSegments + 1); + const double dt = 1.0 / _nSegments; + _curveParameterSteps.push_back(0.0); + for (unsigned int i = 1; i < _nSegments; i++) { + _curveParameterSteps.push_back(dt * i); + } + _curveParameterSteps.push_back(1.0); + + // Arc lengths + _lengthSums.reserve(_nSegments + 1); + _lengthSums.push_back(0.0); + for (unsigned int i = 1; i <= _nSegments; i++) { + double u = _curveParameterSteps[i]; + double uPrev = _curveParameterSteps[i - 1]; + double length = arcLength(uPrev, u); + _lengthSums.push_back(_lengthSums[i - 1] + length); + } + _totalLength = _lengthSums.back(); + + // Compute a map of arc lengths s and curve parameters u, for reparameterization + _parameterSamples.reserve(NrSamplesPerSegment * _nSegments + 1); + const double uStep = 1.0 / (_nSegments * NrSamplesPerSegment); + for (unsigned int i = 0; i < _nSegments; i++) { + double uStart = _curveParameterSteps[i]; + double sStart = _lengthSums[i]; + for (int j = 0; j < NrSamplesPerSegment; ++j) { + double u = uStart + j * uStep; + double s = sStart + arcLength(uStart, u); + _parameterSamples.push_back({ u, s }); + } + } + _parameterSamples.push_back({ 1.0, _totalLength }); +} + +double PathCurve::approximatedDerivative(double u, double h) { + if (u <= h) { + return (1.0 / h) * glm::length(interpolate(0.0 + h) - interpolate(0.0)); + } + if (u >= 1.0 - h) { + return (1.0 / h) * glm::length(interpolate(1.0) - interpolate(1.0 - h)); + } + return (0.5 / h) * glm::length(interpolate(u + h) - interpolate(u - h)); +} + +double PathCurve::arcLength(double limit) { + return arcLength(0.0, limit); +} + +double PathCurve::arcLength(double lowerLimit, double upperLimit) { + return helpers::fivePointGaussianQuadrature( + lowerLimit, + upperLimit, + [this](double u) { return approximatedDerivative(u); } + ); +} + +glm::dvec3 PathCurve::interpolate(double u) { + ghoul_assert(u >= 0 && u <= 1.0, "Interpolation variable out of range [0, 1]"); + + if (u < 0.0) { + return _points[1]; + } + if (u > 1.0) { + return *(_points.end() - 2); + } + + std::vector::iterator segmentEndIt = + std::lower_bound(_curveParameterSteps.begin(), _curveParameterSteps.end(), u); + + const int index = + static_cast((segmentEndIt - 1) - _curveParameterSteps.begin()); + + double segmentStart = _curveParameterSteps[index]; + double segmentDuration = (_curveParameterSteps[index + 1] - segmentStart); + double uSegment = (u - segmentStart) / segmentDuration; + + return interpolation::catmullRom( + uSegment, + _points[index], + _points[index + 1], + _points[index + 2], + _points[index + 3], + 1.0 // chordal version + ); +} + +LinearCurve::LinearCurve(const Waypoint& start, const Waypoint& end) { + _points.push_back(start.position()); + _points.push_back(start.position()); + _points.push_back(end.position()); + _points.push_back(end.position()); + initializeParameterData(); +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathcurves/avoidcollisioncurve.cpp b/src/navigation/pathcurves/avoidcollisioncurve.cpp new file mode 100644 index 0000000000..664c5196ea --- /dev/null +++ b/src/navigation/pathcurves/avoidcollisioncurve.cpp @@ -0,0 +1,190 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "AvoidCollisionCurve"; + + constexpr const double CloseToNodeThresholdFactor = 5.0; + constexpr const double AvoidCollisionDistanceFactor = 3.0; + constexpr const double CollisionBufferSizeFactor = 1.0; + constexpr const int MaxAvoidCollisionSteps = 10; +} // namespace + +namespace openspace::interaction { + +AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint& end) { + _relevantNodes = global::navigationHandler->pathNavigator().relevantNodes(); + + const glm::dvec3 startNodeCenter = start.node()->worldPosition(); + const glm::dvec3 endNodeCenter = end.node()->worldPosition(); + const double startNodeRadius = start.validBoundingSphere; + const double endNodeRadius = end.validBoundingSphere; + const glm::dvec3 startViewDir = start.rotation() * glm::dvec3(0.0, 0.0, -1.0); + + // Add control points for a catmull-rom spline, first and last will not be intersected + _points.push_back(start.position()); + _points.push_back(start.position()); + + // Add an extra point to first go backwards if starting close to planet + glm::dvec3 nodeToStart = start.position() - startNodeCenter; + double distanceToStartNode = glm::length(nodeToStart); + + if (distanceToStartNode < CloseToNodeThresholdFactor * startNodeRadius) { + double distance = startNodeRadius; + glm::dvec3 newPos = start.position() + distance * glm::normalize(nodeToStart); + _points.push_back(newPos); + } + + // Add point for moving out if the end state is in opposite direction + glm::dvec3 startToEnd = end.position() - start.position(); + double cosAngleToTarget = glm::dot(normalize(-startViewDir), normalize(startToEnd)); + bool targetInOppositeDirection = cosAngleToTarget > 0.7; + + if (targetInOppositeDirection) { + const glm::dquat midleRot = glm::slerp(start.rotation(), end.rotation(), 0.5); + const glm::dvec3 middleViewDir = midleRot * glm::dvec3(0.0, 0.0, -1.0); + const double stepOutDistance = 0.4 * glm::length(startToEnd); + + glm::dvec3 newPos = start.position() + 0.2 * startToEnd - + stepOutDistance * glm::normalize(middleViewDir); + + _points.push_back(newPos); + } + + // Add an extra point to approach target + const glm::dvec3 nodeToEnd = end.position() - endNodeCenter; + const double distanceToEndNode = glm::length(nodeToEnd); + + if (distanceToEndNode < CloseToNodeThresholdFactor * endNodeRadius) { + double distance = endNodeRadius; + glm::dvec3 newPos = end.position() + distance * glm::normalize(nodeToEnd); + _points.push_back(newPos); + } + + _points.push_back(end.position()); + _points.push_back(end.position()); + + // Create extra points to avoid collision + removeCollisions(); + + initializeParameterData(); +} + +// Try to reduce the risk of collision by approximating the curve with linear segments. +// If a collision happens, create a new point for the path to go through, in an attempt to +// avoid that collision +void AvoidCollisionCurve::removeCollisions(int step) { + if (step > MaxAvoidCollisionSteps) { + return; + } + + const int nSegments = static_cast( _points.size() - 3); + for (int i = 0; i < nSegments; ++i) { + const glm::dvec3 lineStart = _points[i + 1]; + const glm::dvec3 lineEnd = _points[i + 2]; + + for (SceneGraphNode* node : _relevantNodes) { + // Do collision check in relative coordinates, to avoid huge numbers + const glm::dmat4 modelTransform = node->modelTransform(); + glm::dvec3 p1 = glm::inverse(modelTransform) * glm::dvec4(lineStart, 1.0); + glm::dvec3 p2 = glm::inverse(modelTransform) * glm::dvec4(lineEnd, 1.0); + + // Sphere to check for collision + double radius = node->boundingSphere(); + glm::dvec3 center = glm::dvec3(0.0, 0.0, 0.0); + + // Add a buffer to avoid passing too close to the node. + // Dont't add it if any point is inside the buffer + double buffer = CollisionBufferSizeFactor * node->boundingSphere(); + bool p1IsInside = helpers::isPointInsideSphere(p1, center, radius + buffer); + bool p2IsInside = helpers::isPointInsideSphere(p2, center, radius + buffer); + if (!p1IsInside && !p2IsInside) { + radius += buffer; + } + + glm::dvec3 intersectionPointModelCoords; + bool collision = helpers::lineSphereIntersection( + p1, + p2, + center, + radius, + intersectionPointModelCoords + ); + + if (collision) { + glm::dvec3 collisionPoint = modelTransform * + glm::dvec4(intersectionPointModelCoords, 1.0); + + // before collision response, make sure none of the points are inside the node + bool isStartInsideNode = helpers::isPointInsideSphere(p1, center, radius); + bool isEndInsideNode = helpers::isPointInsideSphere(p2, center, radius); + if (isStartInsideNode || isEndInsideNode) { + LWARNING(fmt::format( + "Something went wrong! " + "At least one point in the path is inside node: {}", + node->identifier() + )); + break; + } + + // To avoid collision, take a step in an orhtogonal direction of the + // collision point and add a new point + const glm::dvec3 lineDirection = glm::normalize(lineEnd - lineStart); + const glm::dvec3 nodeCenter = node->worldPosition(); + const glm::dvec3 collisionPointToCenter = nodeCenter - collisionPoint; + + const glm::dvec3 parallell = glm::proj(collisionPointToCenter, lineDirection); + const glm::dvec3 orthogonal = collisionPointToCenter - parallell; + + const double avoidCollisionDistance = AvoidCollisionDistanceFactor * radius; + + glm::dvec3 extraKnot = collisionPoint - + avoidCollisionDistance * glm::normalize(orthogonal); + + _points.insert(_points.begin() + i + 2, extraKnot); + + step++; + removeCollisions(step); + break; + } + } + } +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathcurves/zoomoutoverviewcurve.cpp b/src/navigation/pathcurves/zoomoutoverviewcurve.cpp new file mode 100644 index 0000000000..1d2182fdd2 --- /dev/null +++ b/src/navigation/pathcurves/zoomoutoverviewcurve.cpp @@ -0,0 +1,103 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "ZoomOutOverviewCurve"; +} // namespace + +namespace openspace::interaction { + +// Go far out to get a view of both tagets, aimed to match lookAt orientation +ZoomOutOverviewCurve::ZoomOutOverviewCurve(const Waypoint& start, const Waypoint& end) { + const double startNodeRadius = start.validBoundingSphere; + const double endNodeRadius = end.validBoundingSphere; + + const double endTangentsLengthFactor = 2.0; + const double startTangentLength = endTangentsLengthFactor * startNodeRadius; + const double endTangentLength = endTangentsLengthFactor * endNodeRadius; + + const glm::dvec3 startNodePos = start.node()->worldPosition(); + const glm::dvec3 endNodePos = end.node()->worldPosition(); + const glm::dvec3 startTangentDir = normalize(start.position() - startNodePos); + const glm::dvec3 endTangentDir = normalize(end.position() - endNodePos); + + // Start by going outwards + _points.push_back(start.position()); + _points.push_back(start.position()); + _points.push_back(start.position() + startTangentLength * startTangentDir); + + // Zoom out + if (start.nodeIdentifier != end.nodeIdentifier) { + const glm::dvec3 n1 = startTangentDir; + const glm::dvec3 n2 = endTangentDir; + + // Decide the step direction for the "overview point" based on the directions + // at the start and end of the path, to try to get a nice curve shape + glm::dvec3 goodStepDirection; + if (glm::dot(n1, n2) < 0.0) { + // Facing in different directions => step in direction of the cross product + goodStepDirection = glm::normalize(glm::cross(-n1, n2)); + } + else { + goodStepDirection = glm::normalize(n1 + n2); + } + + // Find a direction that is orthogonal to the line between the start and end position + const glm::dvec3 startPosToEndPos = end.position() - start.position(); + const glm::dvec3 outwardStepVector = + 0.5 * glm::length(startPosToEndPos) * goodStepDirection; + + const glm::dvec3 projectedVec = glm::proj(outwardStepVector, startPosToEndPos); + const glm::dvec3 orthogonalComponent = outwardStepVector - projectedVec; + const glm::dvec3 stepDirection = glm::normalize(orthogonalComponent); + + // Step half-way along the line between the position and then orthogonally + const glm::dvec3 extraKnot = start.position() + 0.5 * startPosToEndPos + + 1.5 * glm::length(startPosToEndPos) * stepDirection; + + _points.push_back(extraKnot); + } + + // Closing in on end node + _points.push_back(end.position() + endTangentLength * endTangentDir); + _points.push_back(end.position()); + _points.push_back(end.position()); + + initializeParameterData(); +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathhelperfunctions.cpp b/src/navigation/pathhelperfunctions.cpp new file mode 100644 index 0000000000..561687c6f7 --- /dev/null +++ b/src/navigation/pathhelperfunctions.cpp @@ -0,0 +1,295 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include + +namespace { + constexpr const char* _loggerCat = "Helpers"; + const double Epsilon = 1E-7; +} // namespace + +namespace openspace::interaction::helpers { + + // Shift and scale to a subinterval [start,end] + double shiftAndScale(double t, double start, double end) { + ghoul_assert(0.0 < start && start < end&& end < 1.0, + "Values must be 0.0 < start < end < 1.0!"); + double tScaled = t / (end - start) - start; + return std::max(0.0, std::min(tScaled, 1.0)); + } + + glm::dquat lookAtQuaternion(glm::dvec3 eye, glm::dvec3 center, glm::dvec3 up) { + glm::dmat4 lookAtMat = glm::lookAt(eye, center, up); + return glm::normalize(glm::inverse(glm::quat_cast(lookAtMat))); + } + + glm::dvec3 viewDirection(const glm::dquat& q) { + return glm::normalize(q * glm::dvec3(0.0, 0.0, -1.0)); + }; + + /* + * Calculate the intersection of a line and a sphere + * The line segment is defined from p1 to p2 + * The sphere is of radius r and centered at sc + * There are potentially two points of intersection given by + * p = p1 + mu1 (p2 - p1) + * p = p1 + mu2 (p2 - p1) + * Source: http://paulbourke.net/geometry/circlesphere/raysphere.c + */ + bool lineSphereIntersection(glm::dvec3 p1, glm::dvec3 p2, glm::dvec3 sc, + double r, glm::dvec3& intersectionPoint) + { + long double a, b, c; + glm::dvec3 dp = p2 - p1; + + a = dp.x * dp.x + dp.y * dp.y + dp.z * dp.z; + b = 2 * (dp.x * (p1.x - sc.x) + dp.y * (p1.y - sc.y) + dp.z * (p1.z - sc.z)); + c = sc.x * sc.x + sc.y * sc.y + sc.z * sc.z; + c += p1.x * p1.x + p1.y * p1.y + p1.z * p1.z; + c -= 2 * (sc.x * p1.x + sc.y * p1.y + sc.z * p1.z); + c -= r * r; + + long double intersectionTest = b * b - 4.0 * a * c; + + // no intersection + if (std::abs(a) < Epsilon || intersectionTest < 0.0) { + return false; + } + else { + // only care about the first intersection point if we have two + const double t = (-b - std::sqrt(intersectionTest)) / (2.0 *a); + + if (t <= Epsilon || t >= abs(1.0 - Epsilon)) return false; + + intersectionPoint = p1 + t * dp; + return true; + } + } + + bool isPointInsideSphere(const glm::dvec3& p, const glm::dvec3& c, double r) { + const glm::dvec3 v = c - p; + const long double squaredDistance = v.x * v.x + v.y * v.y + v.z * v.z; + const long double squaredRadius = r * r; + + return (squaredDistance <= squaredRadius); + } + + double simpsonsRule(double t0, double t1, int n, std::function f) { + const double h = (t1 - t0) / static_cast(n); + const double endpoints = f(t0) + f(t1); + double times4 = 0.0; + double times2 = 0.0; + + // weight 4 + for (int i = 1; i < n; i += 2) { + double t = t0 + i * h; + times4 += f(t); + } + + // weight 2 + for (int i = 2; i < n; i += 2) { + double t = t0 + i * h; + times2 += f(t); + } + + return (h / 3) * (endpoints + 4 * times4 + 2 * times2); + } + + /* + * Approximate area under a function using 5-point Gaussian quadrature + * https://en.wikipedia.org/wiki/Gaussian_quadrature + */ + double fivePointGaussianQuadrature(double t0, double t1, + std::function f) + { + struct GaussLengendreCoefficient { + double abscissa; // xi + double weight; // wi + }; + + static constexpr GaussLengendreCoefficient coefficients[] = { + { 0.0, 0.5688889 }, + { -0.5384693, 0.47862867 }, + { 0.5384693, 0.47862867 }, + { -0.90617985, 0.23692688 }, + { 0.90617985, 0.23692688 } + }; + + const double a = t0; + const double b = t1; + double sum = 0.0; + for (auto coefficient : coefficients) { + // change of interval to [a, b] from [-1, 1] (also 0.5 * (b - a) below) + double const t = 0.5 * ((b - a) * coefficient.abscissa + (b + a)); + sum += f(t) * coefficient.weight; + } + return 0.5 * (b - a) * sum; + } + +} // helpers + +namespace openspace::interaction::interpolation { + + glm::dquat easedSlerp(const glm::dquat q1, const glm::dquat q2, double t) { + double tScaled = helpers::shiftAndScale(t, 0.1, 0.9); + tScaled = ghoul::sineEaseInOut(tScaled); + return glm::slerp(q1, q2, tScaled); + } + + // Based on implementation by Mika Rantanen + // https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html + glm::dvec3 catmullRom(double t, const glm::dvec3& p0, const glm::dvec3& p1, + const glm::dvec3& p2, const glm::dvec3& p3, double alpha) + { + glm::dvec3 m01, m02, m23, m13; + + double t01 = pow(glm::distance(p0, p1), alpha); + double t12 = pow(glm::distance(p1, p2), alpha); + double t23 = pow(glm::distance(p2, p3), alpha); + + m01 = (t01 > Epsilon) ? (p1 - p0) / t01 : glm::dvec3{}; + m23 = (t23 > Epsilon) ? (p3 - p2) / t23 : glm::dvec3{}; + m02 = (t01 + t12 > Epsilon) ? (p2 - p0) / (t01 + t12) : glm::dvec3{}; + m13 = (t12 + t23 > Epsilon) ? (p3 - p1) / (t12 + t23) : glm::dvec3{}; + + glm::dvec3 m1 = p2 - p1 + t12 * (m01 - m02); + glm::dvec3 m2 = p2 - p1 + t12 * (m23 - m13); + + glm::dvec3 a = 2.0 * (p1 - p2) + m1 + m2; + glm::dvec3 b = -3.0 * (p1 - p2) - m1 - m1 - m2; + glm::dvec3 c = m1; + glm::dvec3 d = p1; + + return a * t * t * t + + b * t * t + + c * t + + d; + } + + glm::dvec3 cubicBezier(double t, const glm::dvec3& cp1, const glm::dvec3& cp2, + const glm::dvec3& cp3, const glm::dvec3& cp4) + { + ghoul_assert(t >= 0 && t <= 1.0, "Interpolation variable out of range [0, 1]"); + + double a = 1.0 - t; + return cp1 * a * a * a + + cp2 * t * a * a * 3.0 + + cp3 * t * t * a * 3.0 + + cp4 * t * t * t; + } + + glm::dvec3 linear(double t, const glm::dvec3 &cp1, const glm::dvec3 &cp2) { + ghoul_assert(t >= 0 && t <= 1.0, "Interpolation variable out of range [0, 1]"); + return cp1 * (1.0 - t) + cp2 * t; + } + + glm::dvec3 hermite(double t, const glm::dvec3 &p1, const glm::dvec3 &p2, + const glm::dvec3 &tangent1, const glm::dvec3 &tangent2) + { + ghoul_assert(t >= 0 && t <= 1.0, "Interpolation variable out of range [0, 1]"); + + if (t <= 0.0) return p1; + if (t >= 1.0) return p2; + + const double t2 = t * t; + const double t3 = t2 * t; + + // calculate basis functions + double const a0 = (2.0 * t3) - (3.0 * t2) + 1.0; + double const a1 = (-2.0 * t3) + (3.0 * t2); + double const b0 = t3 - (2.0 * t2) + t; + double const b1 = t3 - t2; + + return (a0 * p1) + (a1 * p2) + (b0 * tangent1) + (b1 * tangent2); + } + + // uniform if tKnots are equally spaced, or else non uniform + glm::dvec3 piecewiseCubicBezier(double t, const std::vector& points, + const std::vector& tKnots) + { + ghoul_assert( + points.size() > 4, + "Minimum of four control points needed for interpolation" + ); + ghoul_assert( + (points.size() - 1) % 3 == 0, + "A vector containing 3n + 1 control points must be provided" + ); + int nrSegments = (points.size() - 1) / 3; + ghoul_assert( + rSegments == (tKnots.size() - 1), + "Number of interval times must match number of segments" + ); + + if (t <= 0.0) return points.front(); + if (t >= 1.0) return points.back(); + + // compute current segment index + std::vector::const_iterator segmentEndIt = + std::lower_bound(tKnots.begin(), tKnots.end(), t); + unsigned int segmentIdx = (segmentEndIt - 1) - tKnots.begin(); + + double segmentStart = tKnots[segmentIdx]; + double segmentDuration = (tKnots[segmentIdx + 1] - tKnots[segmentIdx]); + double tScaled = (t - segmentStart) / segmentDuration; + + unsigned int idx = segmentIdx * 3; + + // Interpolate using De Casteljau's algorithm + return interpolation::cubicBezier( + tScaled, + points[idx], + points[idx + 1], + points[idx + 2], + points[idx + 3] + ); + } + + glm::dvec3 piecewiseLinear(double t, const std::vector& points, + const std::vector& tKnots) + { + ghoul_assert(points.size() == tKnots.size(), "Must have equal number of points and times!"); + ghoul_assert(points.size() > 2, "Minimum of two control points needed for interpolation!"); + + size_t nrSegments = points.size() - 1; + + if (t <= 0.0) return points.front(); + if (t >= 1.0) return points.back(); + + // compute current segment index + std::vector::const_iterator segmentEndIt = + std::lower_bound(tKnots.begin(), tKnots.end(), t); + unsigned int idx = (segmentEndIt - 1) - tKnots.begin(); + + double segmentStart = tKnots[idx]; + double segmentDuration = (tKnots[idx + 1] - tKnots[idx]); + double tScaled = (t - segmentStart) / segmentDuration; + + return interpolation::linear(tScaled, points[idx], points[idx + 1]); + } + +} // interpolation diff --git a/src/navigation/pathnavigator.cpp b/src/navigation/pathnavigator.cpp new file mode 100644 index 0000000000..2f2cd6416c --- /dev/null +++ b/src/navigation/pathnavigator.cpp @@ -0,0 +1,597 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "PathNavigator"; + + constexpr openspace::properties::Property::PropertyInfo DefaultCurveOptionInfo = { + "DefaultCurveOption", + "Default Curve Option", + "The defualt curve type chosen when generating a path, if none is specified" + // TODO: right now there is no way to specify a type for a single path + }; + + constexpr openspace::properties::Property::PropertyInfo IncludeRollInfo = { + "IncludeRoll", + "Include Roll", + "If disabled, roll is removed from the interpolation of camera orientation" + }; + + constexpr openspace::properties::Property::PropertyInfo StopBehaviorInfo = { + "StopBehavior", + "Stop Behavior", + "A camera motion behavior that is applied when no path is being played" + }; + + constexpr openspace::properties::Property::PropertyInfo + ApplyStopBehaviorWhenIdleInfo = + { + "ApplyStopBehaviorWhenIdle", + "Apply Stop Behavior When Idle", + "If enabled, the camera is controlled using the set stop behavior when " + "no path is playing" + }; + + constexpr openspace::properties::Property::PropertyInfo SpeedScaleInfo = { + "SpeedScale", + "Speed Scale", + "Scale factor that affects the default speed for a camera path." + }; + + constexpr openspace::properties::Property::PropertyInfo OrbitSpeedFactorInfo = { + "OrbitSpeedFactor", + "Orbit Speed Factor", + "Controls the speed of the orbiting around an anchor." + }; + + constexpr const openspace::properties::Property::PropertyInfo MinBoundingSphereInfo = { + "MinimalValidBoundingSphere", + "Minimal Valid Bounding Sphere", + "The minimal allowed value for a bounding sphere, in meters. Used for " + "computation of target positions and path generation, to avoid issues when " + "there is no bounding sphere." + }; + + constexpr openspace::properties::Property::PropertyInfo RelevantNodeTagsInfo = { + "RelevantNodeTags", + "Relevant Node Tags", + "List of tags for the nodes that are relevant for path creation, for example " + "when avoiding collisions." + }; +} // namespace + +#include "pathnavigator_lua.inl" + +namespace openspace::interaction { + +PathNavigator::PathNavigator() + : properties::PropertyOwner({ "PathNavigator" }) + , _defaultCurveOption( + DefaultCurveOptionInfo, + properties::OptionProperty::DisplayType::Dropdown + ) + , _includeRoll(IncludeRollInfo, false) + , _stopBehavior( + StopBehaviorInfo, + properties::OptionProperty::DisplayType::Dropdown + ) + , _applyStopBehaviorWhenIdle(ApplyStopBehaviorWhenIdleInfo, false) + , _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f) + , _orbitSpeedFactor(OrbitSpeedFactorInfo, 0.5, 0.0, 20.0) + , _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10) + , _relevantNodeTags(RelevantNodeTagsInfo) +{ + _defaultCurveOption.addOptions({ + { Path::CurveType::AvoidCollision, "AvoidCollision" }, + { Path::CurveType::Linear, "Linear" }, + { Path::CurveType::ZoomOutOverview, "ZoomOutOverview"} + }); + addProperty(_defaultCurveOption); + + addProperty(_includeRoll); + addProperty(_speedScale); + + // OBS! Stop behavior is broken as of core merge + //addProperty(_applyStopBehaviorWhenIdle); + + //// Must be listed in the same order as in enum definition + //_stopBehavior.addOptions({ + // { StopBehavior::None, "None" }, + // { StopBehavior::Orbit, "Orbit" } + //}); + //_stopBehavior = StopBehavior::None; + //addProperty(_stopBehavior); + + //addProperty(_orbitSpeedFactor); + + addProperty(_minValidBoundingSphere); + + _relevantNodeTags = std::vector{ + "planet_solarSystem", + "moon_solarSystem" + };; + _relevantNodeTags.onChange([this]() { findRelevantNodes(); }); + addProperty(_relevantNodeTags); +} + +PathNavigator::~PathNavigator() {} // NOLINT + +Camera* PathNavigator::camera() const { + return global::navigationHandler->camera(); +} + +const SceneGraphNode* PathNavigator::anchor() const { + return global::navigationHandler->anchorNode(); +} + +double PathNavigator::speedScale() const { + return _speedScale; +} + +bool PathNavigator::hasCurrentPath() const { + return _currentPath != nullptr; +} + +bool PathNavigator::hasFinished() const { + if (!hasCurrentPath()) { + return true; + } + return _currentPath->hasReachedEnd(); +} + +bool PathNavigator::isPlayingPath() const { + return !hasFinished(); +} + +void PathNavigator::updateCamera(double deltaTime) { + ghoul_assert(camera() != nullptr, "Camera must not be nullptr"); + + if (!hasCurrentPath()) { + return; + } + + if (!_isPlaying) { + //// TODO: Determine how this should work + //// OBS! Stop behavior is broken as of core merge + //if (hasFinished() && _applyStopBehaviorWhenIdle) { + // applyStopBehavior(deltaTime); + //} + return; + } + + // If for some reason the time is no longer paused, pause it again + // TODO: Before we get here, one time tick happens. Should move this check to engine + if (!global::timeManager->isPaused()) { + global::timeManager->setPause(true); + LINFO("Cannot start simulation time during camera motion"); + } + + CameraPose newPose = _currentPath->traversePath(deltaTime); + const std::string newAnchor = _currentPath->currentAnchor(); + + // Set anchor node in orbitalNavigator, to render visible nodes and add activate + // navigation when we reach the end. + std::string currentAnchor = anchor()->identifier(); + if (currentAnchor != newAnchor) { + global::navigationHandler->orbitalNavigator().setAnchorNode(newAnchor); + } + + if (!_includeRoll) { + removeRollRotation(newPose, deltaTime); + } + + camera()->setPositionVec3(newPose.position); + camera()->setRotation(newPose.rotation); + + if (_currentPath->hasReachedEnd()) { + LINFO("Reached end of path"); + _isPlaying = false; + return; + } +} + +void PathNavigator::createPath(const ghoul::Dictionary& dictionary) { + // TODO: Improve how curve types are handled + const int curveType = _defaultCurveOption; + + // Ignore paths that are created during session recording, as the camera + // position should have been recorded + if (global::sessionRecording->isPlayingBack()) { + return; + } + + clearPath(); + try { + _currentPath = std::make_unique( + PathCreator::createPath(dictionary, Path::CurveType(curveType)) + ); + } + catch (const ghoul::RuntimeError& e) { + LERROR("Could not create path"); + return; + } + + LINFO("Successfully generated camera path"); +} + +void PathNavigator::clearPath() { + LINFO("Clearing path..."); + _currentPath = nullptr; +} + +void PathNavigator::startPath() { + if (!hasCurrentPath()) { + LERROR("There is no path to start"); + return; + } + + //OBS! Until we can handle simulation time: early out if not paused + if (!global::timeManager->isPaused()) { + LERROR("Simulation time must be paused to run a camera path"); + return; + } + + LINFO("Starting path..."); + _isPlaying = true; +} + +void PathNavigator::abortPath() { + if (!_isPlaying) { + LWARNING("No camera path is playing"); + return; + } + _isPlaying = false; + clearPath(); // TODO: instead of clearing this could be handled better + + LINFO("Aborted camera path"); +} + +void PathNavigator::pausePath() { + if (hasFinished()) { + LERROR("No path to pause (path is empty or has finished)."); + return; + } + + if (!_isPlaying) { + LERROR("Cannot pause a path that is not playing"); + return; + } + + LINFO("Path paused"); + _isPlaying = false; +} + +void PathNavigator::continuePath() { + if (hasFinished()) { + LERROR("No path to resume (path is empty or has finished)."); + return; + } + + if (_isPlaying) { + LERROR("Cannot resume a path that is already playing"); + return; + } + + LINFO("Continuing path..."); + _isPlaying = true; +} + +// Created for debugging +std::vector PathNavigator::curvePositions(int nSteps) const { + if (!hasCurrentPath()) { + LERROR("There is no current path to sample points from."); + return {}; + } + + std::vector positions; + const double du = 1.0 / nSteps; + const double length = _currentPath->pathLength(); + for (double u = 0.0; u < 1.0; u += du) { + glm::dvec3 position = _currentPath->interpolatedPose(u * length).position; + positions.push_back(position); + } + positions.push_back(_currentPath->endPoint().position()); + + return positions; +} + +// Created for debugging +std::vector PathNavigator::curveOrientations(int nSteps) const { + if (!hasCurrentPath()) { + LERROR("There is no current path to sample points from."); + return {}; + } + + std::vector orientations; + const double du = 1.0 / nSteps; + const double length = _currentPath->pathLength(); + for (double u = 0.0; u <= 1.0; u += du) { + const glm::dquat orientation = + _currentPath->interpolatedPose(u * length).rotation; + orientations.push_back(orientation); + } + orientations.push_back(_currentPath->endPoint().rotation()); + + return orientations; +} + + +// Created for debugging +std::vector PathNavigator::curveViewDirections(int nSteps) const { + if (!hasCurrentPath()) { + LERROR("There is no current path to sample points from."); + return {}; + } + + std::vector viewDirections; + const double du = 1.0 / nSteps; + for (double u = 0.0; u < 1.0; u += du) { + const glm::dquat orientation = _currentPath->interpolatedPose(u).rotation; + const glm::dvec3 direction = glm::normalize( + orientation * glm::dvec3(0.0, 0.0, -1.0) + ); + viewDirections.push_back(direction); + } + + const glm::dquat orientation = _currentPath->interpolatedPose(1.0).rotation; + const glm::dvec3 direction = glm::normalize( + orientation * glm::dvec3(0.0, 0.0, -1.0) + ); + viewDirections.push_back(direction); + + return viewDirections; +} + +// Created for debugging +std::vector PathNavigator::controlPoints() const { + if (!hasCurrentPath()) { + LERROR("There is no current path to sample points from."); + return {}; + } + + std::vector points; + const std::vector curvePoints = _currentPath->controlPoints(); + points.insert(points.end(), curvePoints.begin(), curvePoints.end()); + + return points; +} + +double PathNavigator::minValidBoundingSphere() const { + return _minValidBoundingSphere; +} + +const std::vector& PathNavigator::relevantNodes() { + if (!_hasInitializedRelevantNodes) { + findRelevantNodes(); + _hasInitializedRelevantNodes = true; + } + + return _relevantNodes; +} + +void PathNavigator::findRelevantNodes() { + const std::vector& allNodes = + global::renderEngine->scene()->allSceneGraphNodes(); + + const std::vector relevantTags = _relevantNodeTags; + + if (allNodes.empty() || relevantTags.empty()) { + _relevantNodes = std::vector(); + return; + } + + auto isRelevant = [&](const SceneGraphNode* node) { + const std::vector tags = node->tags(); + auto result = std::find_first_of( + relevantTags.begin(), + relevantTags.end(), + tags.begin(), + tags.end() + ); + + // does not match any tags => not interesting + if (result == relevantTags.end()) { + return false; + } + + return node->renderable() && (node->boundingSphere() > 0.0); + }; + + std::vector resultingNodes; + std::copy_if( + allNodes.begin(), + allNodes.end(), + std::back_inserter(resultingNodes), + isRelevant + ); + + _relevantNodes = resultingNodes; +} + +void PathNavigator::removeRollRotation(CameraPose& pose, double deltaTime) { + const glm::dvec3 anchorPos = anchor()->worldPosition(); + const glm::dvec3 cameraDir = glm::normalize( + pose.rotation * Camera::ViewDirectionCameraSpace + ); + const double anchorToPosDistance = glm::distance(anchorPos, pose.position); + const double notTooCloseDistance = deltaTime * anchorToPosDistance; + glm::dvec3 lookAtPos = pose.position + notTooCloseDistance * cameraDir; + glm::dquat rollFreeRotation = helpers::lookAtQuaternion( + pose.position, + lookAtPos, + camera()->lookUpVectorWorldSpace() + ); + pose.rotation = rollFreeRotation; +} + +void PathNavigator::applyStopBehavior(double deltaTime) { + switch (_stopBehavior) { + case StopBehavior::None: + // Do nothing + break; + case StopBehavior::Orbit: + orbitAnchorNode(deltaTime); + break; + default: + throw ghoul::MissingCaseException(); + } +} + +void PathNavigator::orbitAnchorNode(double deltaTime) { + ghoul_assert(anchor() != nullptr, "Node to orbit must be set!"); + + const glm::dvec3 prevPosition = camera()->positionVec3(); + const glm::dquat prevRotation = camera()->rotationQuaternion(); + const glm::dvec3 nodeCenter = anchor()->worldPosition(); + + const double speedFactor = 0.1 * _orbitSpeedFactor; + + // Compute orbit speed based on factor and distance to surface + const double orbitRadius = glm::distance(prevPosition, nodeCenter); + const double distanceToSurface = orbitRadius - anchor()->boundingSphere(); + const double orbitSpeed = distanceToSurface * speedFactor; + + // Compute a new position along the orbit + const glm::dvec3 up = camera()->lookUpVectorWorldSpace(); + const glm::dquat lookAtNodeRotation = helpers::lookAtQuaternion( + prevPosition, + nodeCenter, + up + ); + const glm::dvec3 targetForward = lookAtNodeRotation * glm::dvec3(0.0, 0.0, -1.0); + const glm::dvec3 rightOrbitTangent = glm::normalize(glm::cross(targetForward, up)); + + glm::dvec3 newPosition = prevPosition + orbitSpeed * deltaTime * rightOrbitTangent; + + // Adjust for numerical error - make sure we stay at the same height + const glm::dvec3 nodeToNewPos = newPosition - nodeCenter; + const double targetHeight = glm::distance(prevPosition, nodeCenter); + const double heightDiff = glm::length(nodeToNewPos) - targetHeight; + newPosition -= heightDiff * glm::normalize(nodeToNewPos); + + // Rotate along the orbit, but keep relative orientation with regards to the anchor + const glm::dquat localRotation = glm::inverse(lookAtNodeRotation) * prevRotation; + const glm::dquat newLookAtRotation = + helpers::lookAtQuaternion(newPosition, nodeCenter, up); + + const glm::dquat newRotation = newLookAtRotation * localRotation; + + camera()->setPositionVec3(newPosition); + camera()->setRotation(newRotation); +} + +scripting::LuaLibrary PathNavigator::luaLibrary() { + return { + "pathnavigation", + { + { + "isFlying", + &luascriptfunctions::isFlying, + {}, + "", + "Returns true if a camera path is currently running, and false otherwise" + }, + { + "continuePath", + &luascriptfunctions::continuePath, + {}, + "", + "Continue playing a paused camera path" + }, + { + "pausePath", + &luascriptfunctions::pausePath, + {}, + "", + "Pause a playing camera path" + }, + { + "stopPath", + &luascriptfunctions::stopPath, + {}, + "", + "Stops a path, if one is being played" + }, + { + "goTo", + &luascriptfunctions::goTo, + {}, + "string [, bool, double]", + "Move the camera to the node with the specified name. The optional double " + "specifies the duration of the motion. If the optional bool is set to true " + "the target up vector for camera is set based on the target node. Either of " + "the optional parameters can be left out." + }, + { + "goToHeight", + &luascriptfunctions::goToHeight, + {}, + "string, double [, bool, double]", + "Move the camera to the node with the specified name. The second input " + "parameter is the desired target height. The optional double " + "specifies the duration of the motion. If the optional bool is set to true " + "the target up vector for camera is set based on the target node. Either of " + "the optional parameters can be left out." + }, + //{ + // "goToGeo", + // &luascriptfunctions::goToGeo, + // {}, + // "string, double, double, double [, bool, double]", + // "Move the camera to the globe with the name given by the input string. " + // "The next three input parameters are latitude, longitude and altitude. " + // "The optional double specifies the duration of the motion. If the optional " + // "bool is set to true the target up vector for camera is set based on the " + // "target node. Either of the optional parameters can be left out." + //}, + { + "generatePath", + &luascriptfunctions::generatePath, + {}, + "table", + "Generate the path as described by the lua table input argument" + }, + } + }; +} + +} // namespace openspace::interaction diff --git a/src/navigation/pathnavigator_lua.inl b/src/navigation/pathnavigator_lua.inl new file mode 100644 index 0000000000..4373b81252 --- /dev/null +++ b/src/navigation/pathnavigator_lua.inl @@ -0,0 +1,272 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +//#include // TODO: remove dependancy +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const double Epsilon = 1e-12; +} // namespace + +namespace openspace::luascriptfunctions { + +int isFlying(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::isFlying"); + + bool hasFinished = global::navigationHandler->pathNavigator().hasFinished(); + + ghoul::lua::push(L, !hasFinished); + ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack"); + return 1; +} + +int continuePath(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::continuePath"); + + global::navigationHandler->pathNavigator().continuePath(); + + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +int pausePath(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::pausePath"); + + global::navigationHandler->pathNavigator().pausePath(); + + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +int stopPath(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::stopPath"); + + global::navigationHandler->pathNavigator().abortPath(); + + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +// All the goTo function has the same two optional input parameters at the end. The +// purpose of this function is to handle these input parameters and add the result +// to the dictionary specifying the instruction for a camera path. +int handleOptionalGoToParameters(lua_State* L, const int startLocation, + const int nArguments, + ghoul::Dictionary& resultInstruction) +{ + const bool firstIsNumber = (lua_isnumber(L, startLocation) != 0); + const bool firstIsBool = (lua_isboolean(L, startLocation) != 0); + + if (!(firstIsNumber || firstIsBool)) { + const char* msg = lua_pushfstring( + L, + "%s or %s expected, got %s", + lua_typename(L, LUA_TNUMBER), + lua_typename(L, LUA_TBOOLEAN), + luaL_typename(L, -1) + ); + return ghoul::lua::luaError( + L, fmt::format("bad argument #{} ({})", startLocation, msg) + ); + } + + int location = startLocation; + + if (firstIsBool) { + const bool useUpFromTarget = (lua_toboolean(L, location) == 1); + resultInstruction.setValue("UseTargetUpDirection", useUpFromTarget); + + if (nArguments > startLocation) { + location++; + } + } + + if (firstIsNumber || nArguments > startLocation) { + double duration = ghoul::lua::value(L, location); + if (duration <= Epsilon) { + lua_settop(L, 0); + return ghoul::lua::luaError(L, "Duration must be larger than zero."); + } + resultInstruction.setValue("Duration", duration); + } + + return 0; +} + +int goTo(lua_State* L) { + int nArguments = ghoul::lua::checkArgumentsAndThrow(L, { 1, 3 }, "lua::goTo"); + + const std::string& nodeIdentifier = ghoul::lua::value(L, 1); + + if (!sceneGraphNode(nodeIdentifier)) { + lua_settop(L, 0); + return ghoul::lua::luaError(L, "Unknown node name: " + nodeIdentifier); + } + + using namespace std::string_literals; + ghoul::Dictionary insDict; + insDict.setValue("Type", "Node"s); + insDict.setValue("Target", nodeIdentifier); + + if (nArguments > 1) { + int result = handleOptionalGoToParameters(L, 2, nArguments, insDict); + if (result != 0) { + return result; // An error occurred + } + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +int goToHeight(lua_State* L) { + int nArguments = ghoul::lua::checkArgumentsAndThrow(L, { 2, 4 }, "lua::goToHeight"); + + const std::string& nodeIdentifier = ghoul::lua::value(L, 1); + + if (!sceneGraphNode(nodeIdentifier)) { + lua_settop(L, 0); + return ghoul::lua::luaError(L, "Unknown node name: " + nodeIdentifier); + } + + double height = ghoul::lua::value(L, 2); + + using namespace std::string_literals; + ghoul::Dictionary insDict; + insDict.setValue("Type", "Node"s); + insDict.setValue("Target", nodeIdentifier); + insDict.setValue("Height", height); + + if (nArguments > 2) { + int result = handleOptionalGoToParameters(L, 3, nArguments, insDict); + if (result != 0) { + return result; // An error occurred + } + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +//// @TODO (emmbr 2020-11-06) Ideally, this module shouldn't depend on things from +//// Globebrowsing, but we want it for an istallation. Later on, move this functionality +//// somewhere else. Maybe combine with the existing "goToGeo" in globebrowsing? +//int goToGeo(lua_State* L) { +// int nArguments = ghoul::lua::checkArgumentsAndThrow(L, { 4, 6 }, "lua::goToGeo"); +// +// const std::string& nodeIdentifier = ghoul::lua::value(L, 1); +// const SceneGraphNode* n = sceneGraphNode(nodeIdentifier); +// if (!n) { +// lua_settop(L, 0); +// return ghoul::lua::luaError(L, "Unknown globe name: " + nodeIdentifier); +// } +// +// const double latitude = ghoul::lua::value(L, 2); +// const double longitude = ghoul::lua::value(L, 3); +// const double altitude = ghoul::lua::value(L, 4); +// +// using RenderableGlobe = openspace::globebrowsing::RenderableGlobe; +// const RenderableGlobe* globe = dynamic_cast(n->renderable()); +// if (!globe) { +// return ghoul::lua::luaError(L, "Identifier must be a RenderableGlobe"); +// } +// +// // Compute the relative position based on the input values +// glm::dvec3 positionModelCoords = global::moduleEngine->module() +// ->cartesianCoordinatesFromGeo( +// *globe, +// latitude, +// longitude, +// altitude +// ); +// +// using namespace std::string_literals; +// ghoul::Dictionary insDict; +// insDict.setValue("Type", "Node"s); +// insDict.setValue("Target", nodeIdentifier); +// insDict.setValue("Position", positionModelCoords); +// +// if (nArguments > 4) { +// int result = handleOptionalGoToParameters(L, 5, nArguments, insDict); +// if (result != 0) { +// return result; // An error occurred +// } +// } +// +// global::navigationHandler->pathNavigator().createPath(insDict); +// +// if (global::navigationHandler->pathNavigator().hasCurrentPath()) { +// global::navigationHandler->pathNavigator().startPath(); +// } +// +// lua_settop(L, 0); +// ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); +// return 0; +//} + +int generatePath(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::generatePath"); + + ghoul::Dictionary dictionary; + ghoul::lua::luaDictionaryFromState(L, dictionary); + + global::navigationHandler->pathNavigator().createPath(dictionary); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } + + lua_settop(L, 0); + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + +} // namespace openspace::luascriptfunctions diff --git a/src/navigation/waypoint.cpp b/src/navigation/waypoint.cpp new file mode 100644 index 0000000000..bf494a99ac --- /dev/null +++ b/src/navigation/waypoint.cpp @@ -0,0 +1,117 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "Waypoint"; +} // namespace + +namespace openspace::interaction { + +Waypoint::Waypoint(const glm::dvec3& pos, const glm::dquat& rot, const std::string& ref) + : nodeIdentifier(ref) +{ + pose = { pos, rot }; + + const SceneGraphNode* node = sceneGraphNode(nodeIdentifier); + if (!node) { + LERROR(fmt::format("Could not find node '{}'", nodeIdentifier)); + return; + } + validBoundingSphere = findValidBoundingSphere(node); +} + +Waypoint::Waypoint(const NavigationState& ns) { + const SceneGraphNode* anchorNode = sceneGraphNode(ns.anchor); + + if (!anchorNode) { + LERROR(fmt::format("Could not find node '{}' to target", ns.anchor)); + return; + } + + nodeIdentifier = ns.anchor; + validBoundingSphere = findValidBoundingSphere(anchorNode); + pose = ns.cameraPose(); +} + +glm::dvec3 Waypoint::position() const { + return pose.position; +} + +glm::dquat Waypoint::rotation() const { + return pose.rotation; +} + +SceneGraphNode* Waypoint::node() const { + return sceneGraphNode(nodeIdentifier); +} + +double Waypoint::findValidBoundingSphere(const SceneGraphNode* node) { + double bs = static_cast(node->boundingSphere()); + + const double minValidBoundingSphere = + global::navigationHandler->pathNavigator().minValidBoundingSphere(); + + if (bs < minValidBoundingSphere) { + // If the bs of the target is too small, try to find a good value in a child node. + // Only check the closest children, to avoid deep traversal in the scene graph. + // Also, the possibility to find a bounding sphere represents the visual size of + // the target well is higher for these nodes. + for (SceneGraphNode* child : node->children()) { + bs = static_cast(child->boundingSphere()); + if (bs > minValidBoundingSphere) { + LWARNING(fmt::format( + "The scene graph node '{}' has no, or a very small, bounding sphere. " + "Using bounding sphere of child node '{}' in computations.", + node->identifier(), + child->identifier() + )); + + return bs; + } + } + + LWARNING(fmt::format( + "The scene graph node '{}' has no, or a very small, bounding sphere. Using " + "minimal value. This might lead to unexpected results.", + node->identifier()) + ); + + bs = minValidBoundingSphere; + } + + return bs; +} + +} // namespace openspace::interaction diff --git a/src/network/parallelpeer.cpp b/src/network/parallelpeer.cpp index 08bee3b496..3149eaaaad 100644 --- a/src/network/parallelpeer.cpp +++ b/src/network/parallelpeer.cpp @@ -24,14 +24,14 @@ #include +#include #include #include -#include -#include -#include +#include +#include +#include #include #include -#include #include #include #include diff --git a/src/rendering/abufferrenderer.cpp b/src/rendering/abufferrenderer.cpp index 4b4f4d4654..d69509ff32 100644 --- a/src/rendering/abufferrenderer.cpp +++ b/src/rendering/abufferrenderer.cpp @@ -26,6 +26,7 @@ #include +#include #include #include #include @@ -36,7 +37,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rendering/framebufferrenderer.cpp b/src/rendering/framebufferrenderer.cpp index f25d3aa4e2..0f1da9efc6 100644 --- a/src/rendering/framebufferrenderer.cpp +++ b/src/rendering/framebufferrenderer.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -33,7 +34,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 7e1010aad5..d8e045cfe7 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -29,8 +29,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/rendering/screenspacerenderable.cpp b/src/rendering/screenspacerenderable.cpp index ef0b3b2673..ef4b358966 100644 --- a/src/rendering/screenspacerenderable.cpp +++ b/src/rendering/screenspacerenderable.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -32,7 +33,6 @@ #include #include #include -#include #include #include #include diff --git a/src/scene/profile.cpp b/src/scene/profile.cpp index 69c6061a82..9c2bcce777 100644 --- a/src/scene/profile.cpp +++ b/src/scene/profile.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -445,7 +446,7 @@ Profile::ParsingError::ParsingError(Severity severity_, std::string msg) void Profile::saveCurrentSettingsToProfile(const properties::PropertyOwner& rootOwner, std::string currentTime, - interaction::NavigationHandler::NavigationState navState) + interaction::NavigationState navState) { _version = Profile::CurrentVersion; diff --git a/src/scene/profile_lua.inl b/src/scene/profile_lua.inl index 02a5ea1bb4..9f573830b0 100644 --- a/src/scene/profile_lua.inl +++ b/src/scene/profile_lua.inl @@ -24,7 +24,8 @@ #include #include -#include +#include +#include #include #include #include @@ -88,8 +89,7 @@ int saveSettingsToProfile(lua_State* L) { const properties::PropertyOwner& root = *global::rootPropertyOwner; std::string currentTime = std::string(global::timeManager->time().ISO8601()); - interaction::NavigationHandler::NavigationState navState = - global::navigationHandler->navigationState(); + interaction::NavigationState navState = global::navigationHandler->navigationState(); global::profile->saveCurrentSettingsToProfile(root, currentTime, navState); global::configuration->profile = saveFilePath; diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index d39208d1a1..fa864535a1 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -34,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/tests/test_profile.cpp b/tests/test_profile.cpp index 813b39e9c0..15f30f6a7d 100644 --- a/tests/test_profile.cpp +++ b/tests/test_profile.cpp @@ -24,7 +24,7 @@ #include "catch2/catch.hpp" -#include +#include #include #include #include @@ -520,7 +520,7 @@ TEST_CASE("Save settings to profile", "[profile]") { p1 = 2.f; p2 = "test-string"; - interaction::NavigationHandler::NavigationState state; + interaction::NavigationState state; state.anchor = "anchor"; state.aim = "aim"; state.referenceFrame = "refFrame";