diff --git a/data/assets/apollo8.scene b/data/assets/apollo8.scene index 3301a6720a..6ec4a8b9d5 100644 --- a/data/assets/apollo8.scene +++ b/data/assets/apollo8.scene @@ -12,14 +12,19 @@ local Keybindings = { { Key = "E", Command = "openspace.time.setPause(true);" .. - "openspace.setPropertyValue('*Trail.Renderable.Enabled', false)".. - "openspace.setPropertyValue('Scene.Apollo8LaunchTrail.Renderable.Enabled', false)".. - "openspace.sessionRecording.startPlayback('apollo8')", + "openspace.time.setDeltaTime(1);" .. + "openspace.time.setTime('1968 DEC 24 16:37:31');" .. + "openspace.navigation.setNavigationState({" .. + " Anchor = 'Apollo8'," .. + " Position = { 1.494592E1, 3.236777E1, -4.171296E1 }," .. + " ReferenceFrame = 'Root'," .. + " Up = { 0.960608E0, -0.212013E0, 0.179675E0 }" .. + "});" .. + "openspace.setPropertyValue('*Trail.Renderable.Enabled', false)", Documentation = "Jump to right before the earthrise photo", Name = "Set Earthrise time", GuiPath = "/Missions/Apollo/8", Local = false - }, { Key = "U", @@ -115,13 +120,7 @@ asset.onInitialize(function () openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.MinimumAllowedDistance', 0.000000); openspace.setPropertyValueSingle('Scene.Moon.Renderable.LodScaleFactor', 24.0); - openspace.navigation.setCameraState({ - Anchor = earthAsset.Earth.Identifier, - Position = { 0, 0, 0 }, - Rotation = { 0.758797, 0.221490, -0.605693, -0.091135 }, - }) - - openspace.globebrowsing.goToGeo(20, -60, 15000000) + openspace.globebrowsing.goToGeo(earthAsset.Earth.Identifier, 20, -60, 15000000) end) asset.onDeinitialize(function () diff --git a/data/assets/apollo_sites.scene b/data/assets/apollo_sites.scene index 2c59d6f25a..05b0460970 100644 --- a/data/assets/apollo_sites.scene +++ b/data/assets/apollo_sites.scene @@ -96,12 +96,7 @@ asset.onInitialize(function () -- openspace.setPropertyValueSingle('Scene.Moon.Renderable.Layers.HeightLayers.LRO_NAC_Apollo_11.Enabled', true); -- openspace.setPropertyValueSingle('Scene.Moon.Renderable.Layers.ColorLayers.A11_M177481212_p_longlat.Enabled', true); - openspace.navigation.setCameraState({ - Anchor = moonAsset.Moon.Identifier, - Position = { 0, 0, 0 }, - Rotation = { 0, 0, 0, 0 }, - }) - openspace.globebrowsing.goToGeo(20, -60, 15000000) + openspace.globebrowsing.goToGeo(moonAsset.Moon.Identifier, 20, -60, 15000000) openspace.setPropertyValueSingle("Scene.Moon.Renderable.PerformShading", false) end) diff --git a/data/assets/dawn.scene b/data/assets/dawn.scene index b519b87da2..8af4d7bf6d 100644 --- a/data/assets/dawn.scene +++ b/data/assets/dawn.scene @@ -10,10 +10,9 @@ asset.onInitialize(function () openspace.markInterestingNodes({ "Dawn", "Ceres", "Vesta" }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = DawnAsset.Dawn.Identifier, Position = { 526781518487.171326, 257168309890.072144, -1381125204152.817383 }, - Rotation = { -0.106166, 0.981574, -0.084545, 0.134513 }, }) end) diff --git a/data/assets/default.scene b/data/assets/default.scene index 81300e342a..c6d3e2523c 100644 --- a/data/assets/default.scene +++ b/data/assets/default.scene @@ -1,20 +1,15 @@ asset.require('./base') local earthAsset = asset.require('scene/solarsystem/planets/earth/earth') + asset.onInitialize(function () local now = openspace.time.currentWallTime() - -- Jump back one day to show a complete planet + -- Jump back one day to be able to show complete weather data on Earth. openspace.time.setTime(openspace.time.advancedTime(now, "-1d")) + openspace.globebrowsing.goToGeo("Earth", 58.5877, 16.1924, 20000000) + openspace.markInterestingNodes({ "Earth", "Mars", "Moon", "Sun" }) - - openspace.navigation.setCameraState({ - Anchor = earthAsset.Earth.Identifier, - Position = { 0, 0, 0 }, - Rotation = { 0.758797, 0.221490, -0.605693, -0.091135 }, - }) - - openspace.globebrowsing.goToGeo(58.5877, 16.1924, 20000000) end) asset.onDeinitialize(function () diff --git a/data/assets/examples/basic.scene b/data/assets/examples/basic.scene index 182c4776fe..ec17991a57 100644 --- a/data/assets/examples/basic.scene +++ b/data/assets/examples/basic.scene @@ -1,8 +1,9 @@ local assetHelper = asset.require('util/asset_helper') local sceneHelper = asset.require('util/scene_helper') local propertyHelper = asset.require('util/property_helper') +local debugHelper = asset.require('util/debug_helper') --- At this point, a sceene needs basic spice data to load. +-- At this point, a scene needs basic spice data to load. asset.require('spice/base') asset.require('util/default_keybindings') @@ -12,10 +13,15 @@ asset.require('util/default_joystick') asset.require('util/webgui') local spheres = asset.require('examples/spheres') +debugHelper.registerCartesianAxes(asset, { + Parent = "Root", + Scale = 10 +}) + asset.onInitialize(function () - openspace.navigation.setCameraState({ - Anchor = spheres.ExampleSphere1.Identifier, - Position = { 20, 0, 0 }, - Rotation = { 0.758797, 0.221490, -0.605693, -0.091135 } + openspace.navigation.setNavigationState({ + Anchor = "Root", + Position = { 20, 20, 20 }, + Up = {0, 1, 0}, }) end) diff --git a/data/assets/gaia.scene b/data/assets/gaia.scene index 94d2f8ddbb..45213dfeaa 100644 --- a/data/assets/gaia.scene +++ b/data/assets/gaia.scene @@ -35,10 +35,9 @@ asset.onInitialize(function () openspace.markInterestingNodes({ "Gaia" }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = earthAsset.Earth.Identifier, Position = { 1000000000000.0, 1000000000000.0, 1000000000000.0 }, - Rotation = { 0.683224, -0.765934, -0.601234, -0.418073 }, }) end) diff --git a/data/assets/insight.scene b/data/assets/insight.scene index 3c02820e60..d185471418 100644 --- a/data/assets/insight.scene +++ b/data/assets/insight.scene @@ -60,10 +60,11 @@ asset.onInitialize(function () openspace.markInterestingNodes({ "Insight" }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = insightAsset.Insight.Identifier, - Position = { 0, 0, 0 }, - Rotation = { 0.758797, 0.221490, -0.605693, -0.091135 }, + Position = { 8.430115E0, -1.791710E1, 2.813660E0 }, + ReferenceFrame = "Root", + Up = { 0.494659E0,0.357162E0,0.792306E0 }, }) end) diff --git a/data/assets/juno.scene b/data/assets/juno.scene index 9ed6afaccd..f17e5c3f8b 100644 --- a/data/assets/juno.scene +++ b/data/assets/juno.scene @@ -18,10 +18,11 @@ asset.onInitialize(function () 28800, 57600, 115200, 230400, 460800, 921600, 1843200, 3686400, 7372800, 14745600 }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = junoAsset.Juno.Identifier, - Position = { 1837386367.601345, -389860693812.834839, 714830404470.398926 }, - Rotation = { -0.336540, 0.711402, -0.099212, 0.608937 }, + Position = { 1.243398E8, 7.176068E7, -1.519733E7 }, + ReferenceFrame = "Root", + Up = { -0.377400E0, 0.764573E0, 0.522492E0 }, }) end) diff --git a/data/assets/messenger.scene b/data/assets/messenger.scene index 850077d2b0..dc2f722880 100644 --- a/data/assets/messenger.scene +++ b/data/assets/messenger.scene @@ -33,10 +33,11 @@ asset.onInitialize(function () 28800, 57600, 115200, 230400, 460800, 921600, 1843200, 3686400, 7372800, 14745600 }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = "Mercury", - Position = { 526781518487.171326, 257168309890.072144, -1381125204152.817383 }, - Rotation = {0.180662, 0.021334, 0.979084, 0.091111}, + Position = { 2.423690E11, 1.979038E11, -2.241483E10 }, + ReferenceFrame = "Root", + Up = { -0.492046E0, 0.666088E0, 0.560551E0 } }) end) diff --git a/data/assets/newhorizons.scene b/data/assets/newhorizons.scene index 71f77d618a..9de5012f7d 100644 --- a/data/assets/newhorizons.scene +++ b/data/assets/newhorizons.scene @@ -255,10 +255,11 @@ asset.onInitialize(function () openspace.setPropertyValueSingle('Scene.Charon.Renderable.Enabled', false) openspace.setPropertyValueSingle("Scene.PlutoBarycenterTrail.Renderable.Enabled", false) - openspace.navigation.setCameraState({ - Anchor = NewHorizonsAsset.NewHorizons.Identifier, - Position = { 4662120063743.592773, 1263245003503.724854, -955413856565.788086 }, - Rotation = { 0.683224, -0.165934, 0.701234, 0.118073 }, + openspace.navigation.setNavigationState({ + Anchor = "NewHorizons", + ReferenceFrame = "Root", + Position = { -6.572656E1, -7.239404E1, -2.111890E1 }, + Up = { 0.102164, -0.362945, 0.926193 } }) end) diff --git a/data/assets/osirisrex.scene b/data/assets/osirisrex.scene index a53e817cd2..285fdb5252 100644 --- a/data/assets/osirisrex.scene +++ b/data/assets/osirisrex.scene @@ -134,10 +134,9 @@ asset.onInitialize(function () openspace.markInterestingNodes({ "OsirisRex", "BennuBarycenter", "Earth" }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = OsirisRexAsset.OsirisRex.Identifier, - Position = { 26974590199.661884, 76314608558.908020, -127086452897.101791 }, - Rotation = { 0.729548, -0.126024, 0.416827, 0.527382 }, + Position = { 26974590199.661884, 76314608558.908020, -127086452897.101791 } }) end) diff --git a/data/assets/rosetta.scene b/data/assets/rosetta.scene index 67d5dc7951..315c8804fc 100644 --- a/data/assets/rosetta.scene +++ b/data/assets/rosetta.scene @@ -134,10 +134,11 @@ asset.onInitialize(function () 28800, 57600, 115200, 230400, 460800, 921600, 1843200, 3686400, 7372800, 14745600 }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = Comet67PAsset.Comet67P.Identifier, - Position = { 526781518487.171326, 257168309890.072144, -1381125204152.817383 }, - Rotation = { -0.106166, 0.981574, -0.084545, 0.134513 }, + ReferenceFrame = "Root", + Position = { -7.294781E5 , -6.657894E5, 2.509047E6 }, + Up = { 0.146529E0, 0.944727E0, 0.293290E0 } }) openspace.setPropertyValue('Scene.67P.Renderable.PerformShading', false); diff --git a/data/assets/scene/solarsystem/missions/apollo/apollo_globebrowsing.asset b/data/assets/scene/solarsystem/missions/apollo/apollo_globebrowsing.asset index aaab113192..fb9f1e14c5 100644 --- a/data/assets/scene/solarsystem/missions/apollo/apollo_globebrowsing.asset +++ b/data/assets/scene/solarsystem/missions/apollo/apollo_globebrowsing.asset @@ -2,36 +2,36 @@ local heightmaps = asset.syncedResource({ - Name = "Apollo Globebrowsing", + Name = "Apollo Globebrowsing Heightmaps", Type = "HttpSynchronization", Identifier = "apollo_globebrowsing_heightmaps", Version = 1 }) local basemaps = asset.syncedResource({ - Name = "Apollo Globebrowsing", + Name = "Apollo Globebrowsing Basemaps", Type = "HttpSynchronization", Identifier = "apollo_globebrowsing_basemaps", Version = 1 }) local naclighting = asset.syncedResource({ - Name = "Apollo Globebrowsing", + Name = "Apollo Globebrowsing NAC Lighting", Type = "HttpSynchronization", Identifier = "apollo_globebrowsing_naclighting", Version = 1 }) local stations = asset.syncedResource({ - Name = "Apollo 17 Globebrowsing", + Name = "Apollo 17 Globebrowsing Stations", Type = "HttpSynchronization", Identifier = "apollo_17_stations", Version = 1 }) asset.onInitialize(function () - openspace.globebrowsing.addBlendingLayersFromDirectory(heightmaps,"Moon") - openspace.globebrowsing.addBlendingLayersFromDirectory(basemaps,"Moon") - openspace.globebrowsing.addBlendingLayersFromDirectory(naclighting,"Moon") - openspace.globebrowsing.addBlendingLayersFromDirectory(stations,"Moon") + openspace.globebrowsing.addBlendingLayersFromDirectory(heightmaps, "Moon") + openspace.globebrowsing.addBlendingLayersFromDirectory(basemaps, "Moon") + openspace.globebrowsing.addBlendingLayersFromDirectory(naclighting, "Moon") + openspace.globebrowsing.addBlendingLayersFromDirectory(stations, "Moon") end) diff --git a/data/assets/scene/solarsystem/planets/jupiter/minor/carpo_group.asset b/data/assets/scene/solarsystem/planets/jupiter/minor/carpo_group.asset index 157b5dbbfd..7938f89387 100644 --- a/data/assets/scene/solarsystem/planets/jupiter/minor/carpo_group.asset +++ b/data/assets/scene/solarsystem/planets/jupiter/minor/carpo_group.asset @@ -14,7 +14,7 @@ local carpoGroup = { { Identifier = "Carpo", Parent = { - Name = parentIdentifier, + Identifier = parentIdentifier, Spice = parentSpice }, Spice = "CARPO", diff --git a/data/assets/voyager.scene b/data/assets/voyager.scene index d91cc5254d..c5789bf926 100644 --- a/data/assets/voyager.scene +++ b/data/assets/voyager.scene @@ -47,10 +47,10 @@ asset.onInitialize(function () "Earth", "Voyager 1", "Voyager 2", "Jupiter", "Saturn", "Uranus", "Neptune" }) - openspace.navigation.setCameraState({ + openspace.navigation.setNavigationState({ Anchor = VoyagerAsset.Voyager_1.Identifier, - Position = { 526781518487.171326, 257168309890.072144, -1381125204152.817383 }, - Rotation = { -0.106166, 0.981574, -0.084545, 0.134513 }, + ReferenceFrame = "Root", + Position = { 526781518487.171326, 257168309890.072144, -1381125204152.817383 } }) end) diff --git a/include/openspace/interaction/interpolator.h b/include/openspace/interaction/interpolator.h index b2f79a9e55..54750bc1eb 100644 --- a/include/openspace/interaction/interpolator.h +++ b/include/openspace/interaction/interpolator.h @@ -51,7 +51,7 @@ public: private: std::function _transferFunction; - float _t = 0.f; + float _t = 1.f; float _interpolationTime = 1.f; float _scaledDeltaTime = 0.f; }; diff --git a/include/openspace/interaction/navigationhandler.h b/include/openspace/interaction/navigationhandler.h index 3ab99cd0e9..0da3db3ab8 100644 --- a/include/openspace/interaction/navigationhandler.h +++ b/include/openspace/interaction/navigationhandler.h @@ -25,13 +25,17 @@ #ifndef __OPENSPACE_CORE___NAVIGATIONHANDLER___H__ #define __OPENSPACE_CORE___NAVIGATIONHANDLER___H__ -#include - +#include +#include #include +#include +#include +#include #include #include #include #include +#include namespace openspace { class Camera; @@ -48,6 +52,25 @@ class OrbitalNavigator; 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; + std::optional up; + double yaw = 0.0; + double pitch = 0.0; + }; + NavigationHandler(); ~NavigationHandler(); @@ -55,10 +78,10 @@ public: void deinitialize(); // Mutators + void setNavigationStateNextFame(NavigationState state); void setCamera(Camera* camera); void setInterpolationTime(float durationInSeconds); - void setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict); void updateCamera(double deltaTime); void setEnableKeyFrameInteraction(); void setDisableKeyFrameInteraction(); @@ -66,12 +89,11 @@ public: void stopPlayback(); // Accessors - ghoul::Dictionary cameraStateDictionary(); Camera* camera() const; const InputState& inputState() const; const OrbitalNavigator& orbitalNavigator() const; OrbitalNavigator& orbitalNavigator(); - KeyframeNavigator& keyframeNavigator() const; + KeyframeNavigator& keyframeNavigator(); bool isKeyFrameInteractionEnabled() const; float interpolationTime() const; @@ -100,10 +122,14 @@ public: void clearJoystickButtonCommand(int button); std::vector joystickButtonCommand(int button) const; + NavigationState navigationState(const SceneGraphNode& referenceFrame) const; + void saveNavigationState(const std::string& filepath, + const std::string& referenceFrameIdentifier); - void saveCameraStateToFile(const std::string& filepath); - void restoreCameraStateFromFile(const std::string& filepath); + void loadNavigationState(const std::string& filepath); + + void setNavigationStateNextFrame(NavigationState state); /** * \return The Lua library that contains all Lua functions available to affect the @@ -112,15 +138,18 @@ public: static scripting::LuaLibrary luaLibrary(); private: - bool _cameraUpdatedFromScript = false; + void applyNavigationState(const NavigationHandler::NavigationState& ns); + bool _playbackModeEnabled = false; - std::unique_ptr _inputState; + InputState _inputState; Camera* _camera = nullptr; std::function _playbackEndCallback; - std::unique_ptr _orbitalNavigator; - std::unique_ptr _keyframeNavigator; + OrbitalNavigator _orbitalNavigator; + KeyframeNavigator _keyframeNavigator; + + std::optional _pendingNavigationState; properties::BoolProperty _useKeyFrameInteraction; }; diff --git a/include/openspace/interaction/orbitalnavigator.h b/include/openspace/interaction/orbitalnavigator.h index 3f60af50c6..0eff98c64c 100644 --- a/include/openspace/interaction/orbitalnavigator.h +++ b/include/openspace/interaction/orbitalnavigator.h @@ -38,6 +38,8 @@ #include #include +#include + namespace openspace { class SceneGraphNode; class Camera; @@ -58,6 +60,7 @@ public: Camera* camera() const; void setCamera(Camera* camera); + void clearPreviousState(); void setFocusNode(const std::string& focusNode); void setAnchorNode(const std::string& anchorNode); @@ -70,6 +73,7 @@ public: void resetNodeMovements(); JoystickCameraStates& joystickStates(); + const JoystickCameraStates& joystickStates() const; bool followingNodeRotation() const; const SceneGraphNode* anchorNode() const; @@ -146,10 +150,9 @@ private: const SceneGraphNode* _anchorNode = nullptr; const SceneGraphNode* _aimNode = nullptr; - glm::dvec3 _previousAnchorNodePosition; - glm::dquat _previousAnchorNodeRotation; - - glm::dvec3 _previousAimNodePosition; + std::optional_previousAnchorNodePosition; + std::optional _previousAnchorNodeRotation; + std::optional _previousAimNodePosition; double _currentCameraToSurfaceDistance = 0.0; bool _directlySetStereoDistance = false; diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index e17d676b3e..e5cc4fd5f4 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -338,16 +339,24 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { "goToGeo", &globebrowsing::luascriptfunctions::goToGeo, {}, - "number, number, number", - "Go to geographic coordinates latitude and longitude" + "[string], number, number, [number]", + "Go to geographic coordinates of a globe. The first (optional) argument is " + "the identifier of a scene graph node that has a RenderableGlobe attached. " + "If no globe is passed in, the current anchor will be used. " + "The second argument is latitude and the third is longitude (degrees). " + "North and East are expressed as positive angles, while South and West are " + "negative. The optional fourh argument is the altitude in meters. If no " + "altitude is provided, the altitude will be kept as the current distance to " + "the surface of the specified globe." }, { "getGeoPosition", &globebrowsing::luascriptfunctions::getGeoPosition, {}, - "name, latitude, longitude, altitude", - "Returns the specified surface position on the globe as three floating point " - "values" + "string, number, number, number", + "Returns the specified surface position on the globe identified by the first " + "argument, as three floating point values - latitude, longitude and altitude " + "(degrees and meters)." }, { "getGeoPositionForCamera", @@ -355,7 +364,7 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { {}, "void", "Get geographic coordinates of the camera poosition in latitude, " - "longitude, and altitude" + "longitude, and altitude (degrees and meters)." }, { "loadWMSCapabilities", @@ -395,29 +404,29 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { return res; } -void GlobeBrowsingModule::goToChunk(int x, int y, int level) { - Camera* cam = global::navigationHandler.camera(); - goToChunk(*cam, globebrowsing::TileIndex(x,y,level), glm::vec2(0.5f, 0.5f), true); +void GlobeBrowsingModule::goToChunk(const globebrowsing::RenderableGlobe& globe, + int x, int y, int level) +{ + goToChunk(globe, globebrowsing::TileIndex(x, y, level), glm::vec2(0.5f, 0.5f), true); } -void GlobeBrowsingModule::goToGeo(double latitude, double longitude) { +void GlobeBrowsingModule::goToGeo(const globebrowsing::RenderableGlobe& globe, + double latitude, double longitude) +{ using namespace globebrowsing; - Camera* cam = global::navigationHandler.camera(); goToGeodetic2( - *cam, + globe, Geodetic2{ glm::radians(latitude), glm::radians(longitude) }, true ); } -void GlobeBrowsingModule::goToGeo(double latitude, double longitude, - double altitude) +void GlobeBrowsingModule::goToGeo(const globebrowsing::RenderableGlobe& globe, + double latitude, double longitude, double altitude) { using namespace globebrowsing; - - Camera* cam = global::navigationHandler.camera(); goToGeodetic3( - *cam, + globe, { Geodetic2{ glm::radians(latitude), glm::radians(longitude) }, altitude @@ -437,32 +446,15 @@ glm::vec3 GlobeBrowsingModule::cartesianCoordinatesFromGeo( altitude }; - const glm::dvec3 positionModelSpace = globe.ellipsoid().cartesianPosition(pos); - //glm::dmat4 modelTransform = globe.modelTransform(); - //glm::dvec3 positionWorldSpace = glm::dvec3(modelTransform * - //glm::dvec4(positionModelSpace, 1.0)); - - return glm::vec3(positionModelSpace); + return glm::vec3(globe.ellipsoid().cartesianPosition(pos)); } -void GlobeBrowsingModule::goToChunk(Camera& camera, const globebrowsing::TileIndex& ti, +void GlobeBrowsingModule::goToChunk(const globebrowsing::RenderableGlobe& globe, + const globebrowsing::TileIndex& ti, glm::vec2 uv, bool doResetCameraDirection) { using namespace globebrowsing; - const RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); - if (!globe) { - LERROR("Focus node must have a RenderableGlobe renderable."); - return; - } - - // Camera position in model space - const glm::dvec3 camPos = camera.positionVec3(); - const glm::dmat4 inverseModelTransform = glm::inverse(globe->modelTransform()); - const glm::dvec3 cameraPositionModelSpace = glm::dvec3( - inverseModelTransform * glm::dvec4(camPos, 1) - ); - const GeodeticPatch patch(ti); const Geodetic2 corner = patch.corner(SOUTH_WEST); Geodetic2 positionOnPatch = patch.size(); @@ -473,32 +465,50 @@ void GlobeBrowsingModule::goToChunk(Camera& camera, const globebrowsing::TileInd corner.lon + positionOnPatch.lon }; - const glm::dvec3 positionOnEllipsoid = globe->ellipsoid().geodeticSurfaceProjection( + // Compute altitude + const glm::dvec3 cameraPosition = global::navigationHandler.camera()->positionVec3(); + SceneGraphNode* globeSceneGraphNode = dynamic_cast(globe.owner()); + if (!globeSceneGraphNode) { + LERROR( + "Cannot go to chunk. The renderable is not attached to a scene graph node." + ); + return; + } + const glm::dmat4 inverseModelTransform = globeSceneGraphNode->inverseModelTransform(); + const glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); + const SurfacePositionHandle posHandle = globe.calculateSurfacePositionHandle( cameraPositionModelSpace ); - const double altitude = glm::length(cameraPositionModelSpace - positionOnEllipsoid); + + const Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2( + posHandle.centerToReferenceSurface + ); - goToGeodetic3(camera, {pointPosition, altitude}, doResetCameraDirection); + const double altitude = glm::length( + cameraPositionModelSpace - posHandle.centerToReferenceSurface + ); + + goToGeodetic3(globe, { pointPosition, altitude }, doResetCameraDirection); } -void GlobeBrowsingModule::goToGeodetic2(Camera& camera, globebrowsing::Geodetic2 geo2, +void GlobeBrowsingModule::goToGeodetic2(const globebrowsing::RenderableGlobe& globe, + globebrowsing::Geodetic2 geo2, bool doResetCameraDirection) { using namespace globebrowsing; - const RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); - if (!globe) { - LERROR("Focus node must have a RenderableGlobe renderable."); - return; + const glm::dvec3 cameraPosition = global::navigationHandler.camera()->positionVec3(); + SceneGraphNode* globeSceneGraphNode = dynamic_cast(globe.owner()); + if (!globeSceneGraphNode) { + LERROR("Error when going to Geodetic2"); } - interaction::NavigationHandler& nav = global::navigationHandler; - const glm::dvec3 cameraPosition = nav.camera()->positionVec3(); - const glm::dmat4 inverseModelTransform = - nav.orbitalNavigator().anchorNode()->inverseModelTransform(); + const glm::dmat4 inverseModelTransform = globeSceneGraphNode->inverseModelTransform(); + const glm::dvec3 cameraPositionModelSpace = glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); - const SurfacePositionHandle posHandle = globe->calculateSurfacePositionHandle( + const SurfacePositionHandle posHandle = globe.calculateSurfacePositionHandle( cameraPositionModelSpace ); @@ -506,71 +516,58 @@ void GlobeBrowsingModule::goToGeodetic2(Camera& camera, globebrowsing::Geodetic2 posHandle.referenceSurfaceOutDirection * posHandle.heightToSurface; const double altitude = glm::length(cameraPositionModelSpace - centerToActualSurface); - goToGeodetic3(camera, { geo2, altitude }, doResetCameraDirection); + goToGeodetic3(globe, { geo2, altitude }, doResetCameraDirection); } -void GlobeBrowsingModule::goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3, +void GlobeBrowsingModule::goToGeodetic3(const globebrowsing::RenderableGlobe& globe, + globebrowsing::Geodetic3 geo3, bool doResetCameraDirection) { using namespace globebrowsing; + const glm::dvec3 positionModelSpace = globe.ellipsoid().cartesianPosition(geo3); - const RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); - if (!globe) { - LERROR("Focus node must have a RenderableGlobe renderable."); - return; - } - const glm::dvec3 positionModelSpace = globe->ellipsoid().cartesianPosition(geo3); - const glm::dmat4 modelTransform = globe->modelTransform(); - const glm::dvec3 positionWorldSpace = glm::dvec3(modelTransform * - glm::dvec4(positionModelSpace, 1.0)); - camera.setPositionVec3(positionWorldSpace); + const glm::dvec3 slightlyNorth = globe.ellipsoid().cartesianSurfacePosition( + Geodetic2{ geo3.geodetic2.lat + 0.001, geo3.geodetic2.lon } + ); - if (doResetCameraDirection) { - resetCameraDirection(camera, geo3.geodetic2); - } + interaction::NavigationHandler::NavigationState state; + state.anchor = globe.owner()->identifier(); + state.referenceFrame = globe.owner()->identifier(); + state.position = positionModelSpace; + state.up = slightlyNorth; + + global::navigationHandler.setNavigationStateNextFrame(state); } -void GlobeBrowsingModule::resetCameraDirection(Camera& camera, - globebrowsing::Geodetic2 geo2) +glm::dquat GlobeBrowsingModule::lookDownCameraRotation( + const globebrowsing::RenderableGlobe& globe, + glm::dvec3 cameraModelSpace, + globebrowsing::Geodetic2 geo2) { using namespace globebrowsing; - const RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); - if (!globe) { - LERROR("Focus node must have a RenderableGlobe renderable."); - return; - } - // Camera is described in world space - const glm::dmat4 modelTransform = globe->modelTransform(); + const glm::dmat4 modelTransform = globe.modelTransform(); // Lookup vector - const glm::dvec3 positionModelSpace = globe->ellipsoid().cartesianSurfacePosition( + const glm::dvec3 positionModelSpace = globe.ellipsoid().cartesianSurfacePosition( geo2 ); - const glm::dvec3 slightlyNorth = globe->ellipsoid().cartesianSurfacePosition( + const glm::dvec3 slightlyNorth = globe.ellipsoid().cartesianSurfacePosition( Geodetic2{ geo2.lat + 0.001, geo2.lon } ); const glm::dvec3 lookUpModelSpace = glm::normalize( slightlyNorth - positionModelSpace ); - const glm::dvec3 lookUpWorldSpace = glm::dmat3(modelTransform) * lookUpModelSpace; - - // Lookat vector - const glm::dvec3 lookAtWorldSpace = glm::dvec3( - modelTransform * glm::dvec4(positionModelSpace, 1.0) - ); - - // Eye position - const glm::dvec3 eye = camera.positionVec3(); // Matrix - const glm::dmat4 lookAtMatrix = glm::lookAt(eye, lookAtWorldSpace, lookUpWorldSpace); + const glm::dmat4 lookAtMatrix = + glm::lookAt(cameraModelSpace, positionModelSpace, lookUpModelSpace); // Set rotation const glm::dquat rotation = glm::quat_cast(inverse(lookAtMatrix)); - camera.setRotation(rotation); + return rotation; } const globebrowsing::RenderableGlobe* @@ -732,5 +729,4 @@ uint64_t GlobeBrowsingModule::wmsCacheSize() const { return size * 1024 * 1024; } - } // namespace openspace diff --git a/modules/globebrowsing/globebrowsingmodule.h b/modules/globebrowsing/globebrowsingmodule.h index 9db83196a9..fc96cd91c4 100644 --- a/modules/globebrowsing/globebrowsingmodule.h +++ b/modules/globebrowsing/globebrowsingmodule.h @@ -53,9 +53,12 @@ public: GlobeBrowsingModule(); - void goToChunk(int x, int y, int level); - void goToGeo(double latitude, double longitude); - void goToGeo(double latitude, double longitude, double altitude); + void goToChunk(const globebrowsing::RenderableGlobe& globe, int x, int y, int level); + void goToGeo(const globebrowsing::RenderableGlobe& globe, + double latitude, double longitude); + + void goToGeo(const globebrowsing::RenderableGlobe& globe, + double latitude, double longitude, double altitude); glm::vec3 cartesianCoordinatesFromGeo(const globebrowsing::RenderableGlobe& globe, double latitude, double longitude, double altitude); @@ -94,13 +97,17 @@ protected: void internalInitialize(const ghoul::Dictionary&) override; private: - void goToChunk(Camera& camera, const globebrowsing::TileIndex& ti, glm::vec2 uv, - bool doResetCameraDirection); - void goToGeodetic2(Camera& camera, globebrowsing::Geodetic2 geo2, - bool doResetCameraDirection); - void goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3, - bool doResetCameraDirection); - void resetCameraDirection(Camera& camera, globebrowsing::Geodetic2 geo2); + void goToChunk(const globebrowsing::RenderableGlobe& globe, + const globebrowsing::TileIndex& ti, glm::vec2 uv, bool doResetCameraDirection); + + void goToGeodetic2(const globebrowsing::RenderableGlobe& globe, + globebrowsing::Geodetic2 geo2, bool doResetCameraDirection); + + void goToGeodetic3(const globebrowsing::RenderableGlobe& globe, + globebrowsing::Geodetic3 geo3, bool doResetCameraDirection); + + glm::dquat lookDownCameraRotation(const globebrowsing::RenderableGlobe& globe, + glm::dvec3 cameraPositionModelSpace, globebrowsing::Geodetic2 geo2); /** \return a comma separated list of layer group names. diff --git a/modules/globebrowsing/globebrowsingmodule_lua.inl b/modules/globebrowsing/globebrowsingmodule_lua.inl index b9928a119f..0c6479b211 100644 --- a/modules/globebrowsing/globebrowsingmodule_lua.inl +++ b/modules/globebrowsing/globebrowsingmodule_lua.inl @@ -128,31 +128,79 @@ int deleteLayer(lua_State* L) { } int goToChunk(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 3, "lua::goToChunk"); + ghoul::lua::checkArgumentsAndThrow(L, 4, "lua::goToChunk"); - const int x = ghoul::lua::value(L, 1); - const int y = ghoul::lua::value(L, 2); - const int level = ghoul::lua::value(L, 3); - lua_pop(L, 3); + const std::string& globeIdentifier = ghoul::lua::value(L, 1); + const int x = ghoul::lua::value(L, 2); + const int y = ghoul::lua::value(L, 3); + const int level = ghoul::lua::value(L, 4); + lua_pop(L, 4); - global::moduleEngine.module()->goToChunk(x, y, level); + SceneGraphNode* n = sceneGraphNode(globeIdentifier); + if (!n) { + return ghoul::lua::luaError(L, "Unknown globe name: " + globeIdentifier); + } + + const RenderableGlobe* globe = dynamic_cast(n->renderable()); + if (!globe) { + return ghoul::lua::luaError(L, "Identifier must be a RenderableGlobe"); + } + + global::moduleEngine.module()->goToChunk(*globe, x, y, level); ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); return 0; } int goToGeo(lua_State* L) { - const int nArguments = ghoul::lua::checkArgumentsAndThrow(L, 2, 3, "lua::goToGeo"); + const int nArguments = ghoul::lua::checkArgumentsAndThrow(L, { 2, 4 }, "lua::goToGeo"); - const double latitude = ghoul::lua::value(L, 1); - const double longitude = ghoul::lua::value(L, 2); + // Check if the user provided a Scene graph node identifier as the first argument. + // lua_isstring returns true for both numbers and strings, so better use !lua_isnumber + const bool providedGlobeIdentifier = !lua_isnumber(L, 1); + const int parameterOffset = providedGlobeIdentifier ? 1 : 0; - if (nArguments == 2) { - global::moduleEngine.module()->goToGeo(latitude, longitude); + const SceneGraphNode* n; + if (providedGlobeIdentifier) { + const std::string& globeIdentifier = ghoul::lua::value(L, 1); + n = sceneGraphNode(globeIdentifier); + if (!n) { + return ghoul::lua::luaError(L, "Unknown globe name: " + globeIdentifier); + } } - else if (nArguments == 3) { - const double altitude = ghoul::lua::value(L, 3); + else { + n = global::navigationHandler.orbitalNavigator().anchorNode(); + if (!n) { + return ghoul::lua::luaError(L, "No anchor node is set."); + } + } + + const double latitude = ghoul::lua::value(L, parameterOffset + 1); + const double longitude = ghoul::lua::value(L, parameterOffset + 2); + + const RenderableGlobe* globe = dynamic_cast(n->renderable()); + if (!globe) { + if (providedGlobeIdentifier) { + return ghoul::lua::luaError(L, "Identifier must be a RenderableGlobe"); + } + else { + return ghoul::lua::luaError(L, + "Current anchor node is not a RenderableGlobe. " + "Either change the anchor to a globe, or specify a globe identifier " + "as the first argument" + ); + } + } + + if (nArguments == parameterOffset + 2) { global::moduleEngine.module()->goToGeo( + *globe, latitude, longitude + ); + } + else if (nArguments == parameterOffset + 3) { + const double altitude = ghoul::lua::value(L, parameterOffset + 3); + global::moduleEngine.module()->goToGeo( + *globe, latitude, longitude, altitude @@ -168,19 +216,19 @@ int goToGeo(lua_State* L) { int getGeoPosition(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 4, "lua::getGeoPosition"); - const std::string& name = ghoul::lua::value(L, 1); + const std::string& globeIdentifier = ghoul::lua::value(L, 1); const double latitude = ghoul::lua::value(L, 2); const double longitude = ghoul::lua::value(L, 3); const double altitude = ghoul::lua::value(L, 4); lua_pop(L, 4); - SceneGraphNode* n = sceneGraphNode(name); + SceneGraphNode* n = sceneGraphNode(globeIdentifier); if (!n) { - return ghoul::lua::luaError(L, "Unknown globe name: " + name); + return ghoul::lua::luaError(L, "Unknown globe identifier: " + globeIdentifier); } const RenderableGlobe* globe = dynamic_cast(n->renderable()); if (!globe) { - return ghoul::lua::luaError(L, "Name must be a RenderableGlobe"); + return ghoul::lua::luaError(L, "Identifier must be a RenderableGlobe"); } GlobeBrowsingModule& mod = *(global::moduleEngine.module()); diff --git a/modules/globebrowsing/src/globetranslation.cpp b/modules/globebrowsing/src/globetranslation.cpp index d1ffd5d624..cae9cc2381 100644 --- a/modules/globebrowsing/src/globetranslation.cpp +++ b/modules/globebrowsing/src/globetranslation.cpp @@ -161,6 +161,11 @@ GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary) _latitude.onChange([this]() { _positionIsDirty = true; }); _fixedAltitude.onChange([this]() { _positionIsDirty = true; }); _useFixedAltitude.onChange([this]() { _positionIsDirty = true; }); + + addProperty(_longitude); + addProperty(_latitude); + addProperty(_fixedAltitude); + addProperty(_useFixedAltitude); } void GlobeTranslation::fillAttachedNode() { diff --git a/recordings/apollo8 b/recordings/apollo8 deleted file mode 100644 index c9cafbdcaa..0000000000 Binary files a/recordings/apollo8 and /dev/null differ diff --git a/src/documentation/core_registration.cpp b/src/documentation/core_registration.cpp index 3d7e026b94..1d8de7effb 100644 --- a/src/documentation/core_registration.cpp +++ b/src/documentation/core_registration.cpp @@ -58,6 +58,9 @@ namespace openspace { void registerCoreClasses(documentation::DocumentationEngine& engine) { engine.addDocumentation(LogFactoryDocumentation()); engine.addDocumentation(Mission::Documentation()); + engine.addDocumentation( + interaction::NavigationHandler::NavigationState::Documentation() + ); engine.addDocumentation(Renderable::Documentation()); engine.addDocumentation(Rotation::Documentation()); engine.addDocumentation(Scale::Documentation()); diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 52a268ca12..b1473854a8 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -1013,8 +1013,6 @@ void OpenSpaceEngine::preSynchronization() { } global::renderEngine.updateScene(); - //_navigationHandler->updateCamera(dt); - if (_scene) { Camera* camera = _scene->camera(); diff --git a/src/interaction/navigationhandler.cpp b/src/interaction/navigationhandler.cpp index c9b765a545..f4f0c21dc9 100644 --- a/src/interaction/navigationhandler.cpp +++ b/src/interaction/navigationhandler.cpp @@ -27,14 +27,17 @@ #include #include #include -#include -#include -#include #include +#include +#include #include +#include +#include #include #include #include +#include +#include #include namespace { @@ -43,7 +46,11 @@ namespace { constexpr const char* KeyAnchor = "Anchor"; constexpr const char* KeyAim = "Aim"; constexpr const char* KeyPosition = "Position"; - constexpr const char* KeyRotation = "Rotation"; + constexpr const char* KeyUp = "Up"; + constexpr const char* KeyYaw = "Yaw"; + constexpr const char* KeyPitch = "Pitch"; + constexpr const char* KeyReferenceFrame = "ReferenceFrame"; + const double Epsilon = 1E-7; constexpr const openspace::properties::Property::PropertyInfo KeyFrameInfo = { "UseKeyFrameInteraction", @@ -51,24 +58,101 @@ namespace { "If this is set to 'true' the entire interaction is based off key frames rather " "than using the mouse interaction." }; + } // namespace #include "navigationhandler_lua.inl" namespace openspace::interaction { + +ghoul::Dictionary +openspace::interaction::NavigationHandler::NavigationState::dictionary() const +{ + 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.value()); + + if (std::abs(yaw) > Epsilon) { + cameraDict.setValue(KeyYaw, yaw); + } + if (std::abs(pitch) > Epsilon) { + cameraDict.setValue(KeyPitch, pitch); + } + } + + return cameraDict; +} + +openspace::interaction::NavigationHandler::NavigationState::NavigationState( + const ghoul::Dictionary& dictionary) +{ + const bool hasAnchor = dictionary.hasValue(KeyAnchor); + const bool hasPosition = dictionary.hasValue(KeyPosition); + if (!hasAnchor || !hasPosition) { + throw ghoul::RuntimeError( + "Position and Anchor need to be defined for navigation dictionary." + ); + } + + anchor = dictionary.value(KeyAnchor); + position = dictionary.value(KeyPosition); + + if (dictionary.hasValue(KeyReferenceFrame)) { + referenceFrame = dictionary.value(KeyReferenceFrame); + } + else { + referenceFrame = anchor; + } + if (dictionary.hasValue(KeyAim)) { + aim = dictionary.value(KeyAim); + } + + if (dictionary.hasValue(KeyUp)) { + up = dictionary.value(KeyUp); + + if (dictionary.hasValue(KeyYaw)) { + yaw = dictionary.value(KeyYaw); + } + if (dictionary.hasValue(KeyPitch)) { + pitch = dictionary.value(KeyPitch); + } + } +} + +openspace::interaction::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" }) , _useKeyFrameInteraction(KeyFrameInfo, false) { - - _inputState = std::make_unique(); - _orbitalNavigator = std::make_unique(); - _keyframeNavigator = std::make_unique(); - // Add the properties addProperty(_useKeyFrameInteraction); - addPropertySubOwner(*_orbitalNavigator); + addPropertySubOwner(_orbitalNavigator); } NavigationHandler::~NavigationHandler() {} // NOLINT @@ -90,19 +174,25 @@ void NavigationHandler::deinitialize() { void NavigationHandler::setCamera(Camera* camera) { _camera = camera; - _orbitalNavigator->setCamera(camera); + _orbitalNavigator.setCamera(camera); } -const OrbitalNavigator& NavigationHandler::orbitalNavigator() const { - return *_orbitalNavigator; +void NavigationHandler::setNavigationStateNextFrame( + NavigationHandler::NavigationState state) +{ + _pendingNavigationState = std::move(state); } OrbitalNavigator& NavigationHandler::orbitalNavigator() { - return *_orbitalNavigator; + return _orbitalNavigator; } -KeyframeNavigator& NavigationHandler::keyframeNavigator() const { - return *_keyframeNavigator; +const OrbitalNavigator& NavigationHandler::orbitalNavigator() const { + return _orbitalNavigator; +} + +KeyframeNavigator& NavigationHandler::keyframeNavigator() { + return _keyframeNavigator; } bool NavigationHandler::isKeyFrameInteractionEnabled() const { @@ -110,33 +200,93 @@ bool NavigationHandler::isKeyFrameInteractionEnabled() const { } float NavigationHandler::interpolationTime() const { - return _orbitalNavigator->retargetInterpolationTime(); + return _orbitalNavigator.retargetInterpolationTime(); } void NavigationHandler::setInterpolationTime(float durationInSeconds) { - _orbitalNavigator->setRetargetInterpolationTime(durationInSeconds); + _orbitalNavigator.setRetargetInterpolationTime(durationInSeconds); } void NavigationHandler::updateCamera(double deltaTime) { - ghoul_assert(_inputState != nullptr, "InputState must not be nullptr"); ghoul_assert(_camera != nullptr, "Camera must not be nullptr"); - if (_cameraUpdatedFromScript) { - _cameraUpdatedFromScript = false; - } - else { - if (!_playbackModeEnabled && _camera) { - if (_useKeyFrameInteraction) { - _keyframeNavigator->updateCamera(*_camera, _playbackModeEnabled); - } - else { - _orbitalNavigator->updateStatesFromInput(*_inputState, deltaTime); - _orbitalNavigator->updateCameraStateFromStates(deltaTime); - } + if (_pendingNavigationState.has_value()) { + applyNavigationState(_pendingNavigationState.value()); + _orbitalNavigator.resetVelocities(); + _pendingNavigationState.reset(); + } else if (!_playbackModeEnabled && _camera) { + if (_useKeyFrameInteraction) { + _keyframeNavigator.updateCamera(*_camera, _playbackModeEnabled); + } + else { + _orbitalNavigator.updateStatesFromInput(_inputState, deltaTime); + _orbitalNavigator.updateCameraStateFromStates(deltaTime); } } } +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(); + + _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 + + glm::dvec3(referenceFrameTransform * glm::dvec4(ns.position, 1.0)); + + glm::dvec3 up = ns.up.has_value() ? + glm::normalize(referenceFrameTransform * ns.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(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.f, 0.f, 0.f)); + glm::dquat yawRotation = glm::angleAxis(ns.yaw, glm::dvec3(0.f, -1.f, 0.f)); + + _camera->setPositionVec3(cameraPositionWorld); + _camera->setRotation(neutralCameraRotation * yawRotation * pitchRotation); + _orbitalNavigator.clearPreviousState(); +} + void NavigationHandler::setEnableKeyFrameInteraction() { _useKeyFrameInteraction = true; } @@ -150,8 +300,8 @@ void NavigationHandler::triggerPlaybackStart() { } void NavigationHandler::stopPlayback() { - _orbitalNavigator->resetVelocities(); - _orbitalNavigator->resetNodeMovements(); + _orbitalNavigator.resetVelocities(); + _orbitalNavigator.resetNodeMovements(); _playbackModeEnabled = false; } @@ -160,131 +310,120 @@ Camera* NavigationHandler::camera() const { } const InputState& NavigationHandler::inputState() const { - return *_inputState; + return _inputState; } void NavigationHandler::mouseButtonCallback(MouseButton button, MouseAction action) { - _inputState->mouseButtonCallback(button, action); + _inputState.mouseButtonCallback(button, action); } void NavigationHandler::mousePositionCallback(double x, double y) { - _inputState->mousePositionCallback(x, y); + _inputState.mousePositionCallback(x, y); } void NavigationHandler::mouseScrollWheelCallback(double pos) { - _inputState->mouseScrollWheelCallback(pos); + _inputState.mouseScrollWheelCallback(pos); } void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { - _inputState->keyboardCallback(key, modifier, action); + _inputState.keyboardCallback(key, modifier, action); } -void NavigationHandler::setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict) +NavigationHandler::NavigationState NavigationHandler::navigationState( + const SceneGraphNode& referenceFrame) const { - bool readSuccessful = true; + const SceneGraphNode* anchor = _orbitalNavigator.anchorNode(); + const SceneGraphNode* aim = _orbitalNavigator.aimNode(); - std::string anchor; - std::string aim; - glm::dvec3 cameraPosition; - glm::dvec4 cameraRotation; // Need to read the quaternion as a vector first. - - readSuccessful &= cameraDict.getValue(KeyAnchor, anchor); - readSuccessful &= cameraDict.getValue(KeyPosition, cameraPosition); - readSuccessful &= cameraDict.getValue(KeyRotation, cameraRotation); - cameraDict.getValue(KeyAim, aim); // Aim is not required - - if (!readSuccessful) { - throw ghoul::RuntimeError( - "Position, Rotation and Focus need to be defined for camera dictionary." - ); + if (!aim) { + aim = anchor; } - // Set state - _orbitalNavigator->setAnchorNode(anchor); - _orbitalNavigator->setAimNode(aim); + const glm::dquat invNeutralRotation = glm::quat_cast(glm::lookAt( + glm::dvec3(0.0, 0.0, 0.0), + aim->worldPosition() - _camera->positionVec3(), + glm::normalize(_camera->lookUpVectorWorldSpace()) + )); - _camera->setPositionVec3(cameraPosition); - _camera->setRotation(glm::dquat( - cameraRotation.x, cameraRotation.y, cameraRotation.z, cameraRotation.w)); + glm::dquat localRotation = invNeutralRotation * _camera->rotationQuaternion(); + glm::dvec3 eulerAngles = glm::eulerAngles(localRotation); + + const double pitch = eulerAngles.x; + const double yaw = -eulerAngles.y; + + // Need to compensate by redisual roll left in local rotation: + const glm::dquat unroll = glm::angleAxis(eulerAngles.z, glm::dvec3(0, 0, 1)); + const glm::dvec3 neutralUp = + glm::inverse(invNeutralRotation) * unroll * _camera->lookUpVectorCameraSpace(); + + const glm::dmat3 invReferenceFrameTransform = + glm::inverse(referenceFrame.worldRotationMatrix()); + + const glm::dvec3 position = invReferenceFrameTransform * + (glm::dvec4(_camera->positionVec3() - anchor->worldPosition(), 1.0)); + + return NavigationState( + _orbitalNavigator.anchorNode()->identifier(), + _orbitalNavigator.aimNode() ? + _orbitalNavigator.aimNode()->identifier() : "", + referenceFrame.identifier(), + position, + invReferenceFrameTransform * neutralUp, yaw, pitch + ); } -ghoul::Dictionary NavigationHandler::cameraStateDictionary() { - glm::dvec3 cameraPosition; - glm::dquat quat; - glm::dvec4 cameraRotation; +void NavigationHandler::saveNavigationState(const std::string& filepath, + const std::string& referenceFrameIdentifier) +{ + const SceneGraphNode* referenceFrame = _orbitalNavigator.followingNodeRotation() ? + _orbitalNavigator.anchorNode() : + sceneGraph()->root(); - cameraPosition = _camera->positionVec3(); - quat = _camera->rotationQuaternion(); - cameraRotation = glm::dvec4(quat.w, quat.x, quat.y, quat.z); - - ghoul::Dictionary cameraDict; - cameraDict.setValue(KeyPosition, cameraPosition); - cameraDict.setValue(KeyRotation, cameraRotation); - cameraDict.setValue(KeyAnchor, _orbitalNavigator->anchorNode()->identifier()); - if (_orbitalNavigator->aimNode()) { - cameraDict.setValue(KeyAim, _orbitalNavigator->aimNode()->identifier()); - } - - return cameraDict; -} - -void NavigationHandler::saveCameraStateToFile(const std::string& filepath) { - if (!filepath.empty()) { - std::string fullpath = absPath(filepath); - LINFO(fmt::format("Saving camera position: {}", filepath)); - - ghoul::Dictionary cameraDict = cameraStateDictionary(); - - // TODO(abock): Should get the camera state as a dictionary and save the - // dictionary to a file in form of a lua state and not use ofstreams here. - - std::ofstream ofs(fullpath.c_str()); - - glm::dvec3 p = _camera->positionVec3(); - glm::dquat q = _camera->rotationQuaternion(); - - ofs << "return {" << std::endl; - ofs << " " << KeyAnchor << " = " << "\"" << - _orbitalNavigator->anchorNode()->identifier() << "\"" - << "," << std::endl; - - if (_orbitalNavigator->aimNode()) { - ofs << " " << KeyAim << " = " << "\"" << - _orbitalNavigator->aimNode()->identifier() << "\"" - << "," << std::endl; + if (!referenceFrameIdentifier.empty()) { + referenceFrame = sceneGraphNode(referenceFrameIdentifier); + if (!referenceFrame) { + LERROR(fmt::format( + "Could not find node '{}' to use as reference frame", + referenceFrameIdentifier + )); + return; } + } - ofs << " " << KeyPosition << " = {" - << std::to_string(p.x) << ", " - << std::to_string(p.y) << ", " - << std::to_string(p.z) << "}," << std::endl; - ofs << " " << KeyRotation << " = {" - << std::to_string(q.w) << ", " - << std::to_string(q.x) << ", " - << std::to_string(q.y) << ", " - << std::to_string(q.z) << "}," << std::endl; - ofs << "}"<< std::endl; + if (!filepath.empty()) { + std::string absolutePath = absPath(filepath); + LINFO(fmt::format("Saving camera position: {}", absolutePath)); + ghoul::Dictionary cameraDict = navigationState(*referenceFrame).dictionary(); + ghoul::DictionaryLuaFormatter formatter; + + std::ofstream ofs(absolutePath.c_str()); + ofs << "return " << formatter.format(cameraDict); ofs.close(); } } -void NavigationHandler::restoreCameraStateFromFile(const std::string& filepath) { - LINFO(fmt::format("Reading camera state from file: {}", filepath)); - if (!FileSys.fileExists(filepath)) { - throw ghoul::FileNotFoundError(filepath, "CameraFilePath"); +void NavigationHandler::loadNavigationState(const std::string& filepath) { + const std::string absolutePath = absPath(filepath); + LINFO(fmt::format("Reading camera state from file: {}", absolutePath)); + + if (!FileSys.fileExists(absolutePath)) { + throw ghoul::FileNotFoundError(absolutePath, "NavigationState"); } - ghoul::Dictionary cameraDict; + ghoul::Dictionary navigationStateDictionary; try { - ghoul::lua::loadDictionaryFromFile(filepath, cameraDict); - setCameraStateFromDictionary(cameraDict); - _cameraUpdatedFromScript = true; + ghoul::lua::loadDictionaryFromFile(absolutePath, navigationStateDictionary); + openspace::documentation::testSpecificationAndThrow( + NavigationState::Documentation(), + navigationStateDictionary, + "NavigationState" + ); + setNavigationStateNextFrame(NavigationState(navigationStateDictionary)); } catch (ghoul::RuntimeError& e) { - LWARNING("Unable to set camera position"); - LWARNING(e.message); + LERROR(fmt::format("Unable to set camera position: {}", e.message)); } } @@ -293,7 +432,7 @@ void NavigationHandler::setJoystickAxisMapping(int axis, JoystickCameraStates::AxisInvert shouldInvert, JoystickCameraStates::AxisNormalize shouldNormalize) { - _orbitalNavigator->joystickStates().setAxisMapping( + _orbitalNavigator.joystickStates().setAxisMapping( axis, mapping, shouldInvert, @@ -304,15 +443,15 @@ void NavigationHandler::setJoystickAxisMapping(int axis, JoystickCameraStates::AxisInformation NavigationHandler::joystickAxisMapping(int axis) const { - return _orbitalNavigator->joystickStates().axisMapping(axis); + return _orbitalNavigator.joystickStates().axisMapping(axis); } void NavigationHandler::setJoystickAxisDeadzone(int axis, float deadzone) { - _orbitalNavigator->joystickStates().setDeadzone(axis, deadzone); + _orbitalNavigator.joystickStates().setDeadzone(axis, deadzone); } float NavigationHandler::joystickAxisDeadzone(int axis) const { - return _orbitalNavigator->joystickStates().deadzone(axis); + return _orbitalNavigator.joystickStates().deadzone(axis); } void NavigationHandler::bindJoystickButtonCommand(int button, std::string command, @@ -320,7 +459,7 @@ void NavigationHandler::bindJoystickButtonCommand(int button, std::string comman JoystickCameraStates::ButtonCommandRemote remote, std::string documentation) { - _orbitalNavigator->joystickStates().bindButtonCommand( + _orbitalNavigator.joystickStates().bindButtonCommand( button, std::move(command), action, @@ -330,11 +469,68 @@ void NavigationHandler::bindJoystickButtonCommand(int button, std::string comman } void NavigationHandler::clearJoystickButtonCommand(int button) { - _orbitalNavigator->joystickStates().clearButtonCommand(button); + _orbitalNavigator.joystickStates().clearButtonCommand(button); } std::vector NavigationHandler::joystickButtonCommand(int button) const { - return _orbitalNavigator->joystickStates().buttonCommand(button); + return _orbitalNavigator.joystickStates().buttonCommand(button); +} + +documentation::Documentation NavigationHandler::NavigationState::Documentation() { + using namespace documentation; + + return { + "Navigation State", + "core_navigation_state", + { + { + KeyAnchor, + new StringVerifier, + Optional::No, + "The identifier of the anchor node." + }, + { + KeyAim, + new StringVerifier, + Optional::Yes, + "The identifier of the aim node, if used." + }, + { + KeyReferenceFrame, + new StringVerifier, + Optional::Yes, + "The identifier of the scene graph node to use as reference frame. " + "If not specified, this will be the same as the anchor." + }, + { + KeyPosition, + new DoubleVector3Verifier, + Optional::No, + "The position of the camera relative to the anchor node, " + "expressed in meters in the specified reference frame." + }, + { + KeyUp, + new DoubleVector3Verifier, + Optional::Yes, + "The up vector expressed in the coordinate system of the reference frame." + }, + { + KeyYaw, + new DoubleVerifier, + Optional::Yes, + "The yaw angle in radians. " + "Positive angle means yawing camera to the right." + }, + { + KeyPitch, + new DoubleVerifier, + Optional::Yes, + "The pitch angle in radians. " + "Positive angle means pitching camera upwards." + }, + } + }; } scripting::LuaLibrary NavigationHandler::luaLibrary() { @@ -342,25 +538,33 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { "navigation", { { - "setCameraState", - &luascriptfunctions::setCameraState, + "setNavigationState", + &luascriptfunctions::setNavigationState, {}, - "object", - "Set the camera state" + "table", + "Set the navigation state. " + "The argument must be a valid Navigation State." }, { - "saveCameraStateToFile", - &luascriptfunctions::saveCameraStateToFile, + "saveNavigationState", + &luascriptfunctions::saveNavigationState, {}, - "string", - "Save the current camera state to file" + "string, [string]", + "Save the current navigation state to a file with the path given by the " + "first argument. The optoinal second argument is the scene graph node to " + "use as reference frame. By default, the reference frame will picked " + "based on whether the orbital navigator is currently following the " + "anchor node rotation. If it is, the anchor will be chosen as reference " + "frame. If not, the reference frame will be set to the scene graph root." }, { - "restoreCameraStateFromFile", - &luascriptfunctions::restoreCameraStateFromFile, + "loadNavigationState", + &luascriptfunctions::loadNavigationState, {}, "string", - "Restore the camera state from file" + "Load a navigation state from file. The file should be a lua file " + "returning the navigation state as a table formatted as a " + "Navigation State, such as the output files of saveNavigationState." }, { "retargetAnchor", diff --git a/src/interaction/navigationhandler_lua.inl b/src/interaction/navigationhandler_lua.inl index f2affeeea7..899859c36e 100644 --- a/src/interaction/navigationhandler_lua.inl +++ b/src/interaction/navigationhandler_lua.inl @@ -26,8 +26,8 @@ namespace openspace::luascriptfunctions { -int restoreCameraStateFromFile(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::restoreCameraStateFromFile"); +int loadNavigationState(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::loadNavigationState"); const std::string& cameraStateFilePath = ghoul::lua::value( L, @@ -39,27 +39,35 @@ int restoreCameraStateFromFile(lua_State* L) { return ghoul::lua::luaError(L, "filepath string is empty"); } - global::navigationHandler.restoreCameraStateFromFile(cameraStateFilePath); + global::navigationHandler.loadNavigationState(cameraStateFilePath); ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); return 0; } -int setCameraState(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setCameraState"); +int setNavigationState(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setNavigationState"); - try { - ghoul::Dictionary dictionary; - ghoul::lua::luaDictionaryFromState(L, dictionary); - global::navigationHandler.setCameraStateFromDictionary(dictionary); - } catch (const ghoul::RuntimeError& e) { + ghoul::Dictionary navigationStateDictionary; + ghoul::lua::luaDictionaryFromState(L, navigationStateDictionary); + + using namespace openspace::documentation; + + TestResult r = testSpecification( + interaction::NavigationHandler::NavigationState::Documentation(), + navigationStateDictionary + ); + + if (!r.success) { lua_settop(L, 0); return ghoul::lua::luaError( L, - fmt::format("Could not set camera state: {}", e.what()) + fmt::format("Could not set camera state: {}", ghoul::to_string(r)) ); } + global::navigationHandler.setNavigationStateNextFrame(navigationStateDictionary); + // @CLEANUP: When luaDictionaryFromState doesn't leak space anymore, remove the next // line ---abock(2018-02-15) lua_settop(L, 0); @@ -67,21 +75,27 @@ int setCameraState(lua_State* L) { return 0; } -int saveCameraStateToFile(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::saveCameraStateToFile"); - - const std::string& cameraStateFilePath = ghoul::lua::value( +int saveNavigationState(lua_State* L) { + const int n = ghoul::lua::checkArgumentsAndThrow( L, - 1, - ghoul::lua::PopValue::Yes + { 1, 2 }, + "lua::saveNavigationState" ); + const std::string& cameraStateFilePath = ghoul::lua::value(L, 1); + + std::string referenceFrame = ""; + if (n > 1) { + referenceFrame = ghoul::lua::value(L, 2); + } + if (cameraStateFilePath.empty()) { return ghoul::lua::luaError(L, "filepath string is empty"); } - global::navigationHandler.saveCameraStateToFile(cameraStateFilePath); + global::navigationHandler.saveNavigationState(cameraStateFilePath, referenceFrame); + lua_settop(L, 0); ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); return 0; } diff --git a/src/interaction/orbitalnavigator.cpp b/src/interaction/orbitalnavigator.cpp index a1ae0da15b..2ec2474bf9 100644 --- a/src/interaction/orbitalnavigator.cpp +++ b/src/interaction/orbitalnavigator.cpp @@ -365,20 +365,26 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) { const glm::dvec3 anchorPos = _anchorNode->worldPosition(); const glm::dvec3 prevCameraPosition = _camera->positionVec3(); - const glm::dvec3 anchorDisplacement = anchorPos - _previousAnchorNodePosition; + const glm::dvec3 anchorDisplacement = _previousAnchorNodePosition.has_value() ? + (anchorPos - _previousAnchorNodePosition.value()) : + glm::dvec3(0.0); CameraPose pose = { _camera->positionVec3() + anchorDisplacement, _camera->rotationQuaternion() }; - if (_aimNode && _aimNode != _anchorNode) { + const bool hasPreviousPositions = + _previousAnchorNodePosition.has_value() && + _previousAimNodePosition.has_value(); + + if (_aimNode && _aimNode != _anchorNode && hasPreviousPositions) { const glm::dvec3 aimPos = _aimNode->worldPosition(); const glm::dvec3 cameraToAnchor = - _previousAnchorNodePosition - prevCameraPosition; + _previousAnchorNodePosition.value() - prevCameraPosition; Displacement anchorToAim = { - _previousAimNodePosition - _previousAnchorNodePosition, + _previousAimNodePosition.value() - _previousAnchorNodePosition.value(), aimPos - anchorPos }; @@ -413,8 +419,9 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) { glm::dquat anchorRotation = glm::quat_cast(_anchorNode->worldRotationMatrix()); - glm::dquat anchorNodeRotationDiff = - _previousAnchorNodeRotation * glm::inverse(anchorRotation); + glm::dquat anchorNodeRotationDiff = _previousAnchorNodeRotation.has_value() ? + _previousAnchorNodeRotation.value() * glm::inverse(anchorRotation) : + glm::dquat(); _previousAnchorNodeRotation = anchorRotation; @@ -563,11 +570,17 @@ void OrbitalNavigator::setAnchorNode(const SceneGraphNode* anchorNode) { _anchorNode = anchorNode; if (_anchorNode) { - _previousAnchorNodePosition = _anchorNode->worldPosition(); - _previousAnchorNodeRotation = glm::quat_cast(_anchorNode->worldRotationMatrix()); + _previousAnchorNodePosition.reset(); + _previousAnchorNodeRotation.reset(); } } +void OrbitalNavigator::clearPreviousState() { + _previousAnchorNodePosition.reset(); + _previousAnchorNodeRotation.reset(); + _previousAimNodePosition.reset(); +} + void OrbitalNavigator::setAimNode(const SceneGraphNode* aimNode) { _retargetAimInterpolator.end(); _aimNode = aimNode; @@ -652,6 +665,9 @@ void OrbitalNavigator::setRetargetInterpolationTime(float durationInSeconds) { } bool OrbitalNavigator::followingNodeRotation() const { + if (_aimNode != nullptr && _aimNode != _anchorNode) { + return false; + } return _followRotationInterpolator.value() >= 1.0; } @@ -1219,4 +1235,8 @@ JoystickCameraStates& OrbitalNavigator::joystickStates() { return _joystickStates; } +const JoystickCameraStates& OrbitalNavigator::joystickStates() const { + return _joystickStates; +} + } // namespace openspace::interaction diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index 8fce29ad62..914242cf9a 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -374,14 +374,14 @@ void SceneGraphNode::update(const UpdateData& data) { } UpdateData newUpdateData = data; - _worldRotationCached = calculateWorldRotation(); - _worldScaleCached = calculateWorldScale(); // Assumes _worldRotationCached and _worldScaleCached have been calculated for parent _worldPositionCached = calculateWorldPosition(); + _worldRotationCached = calculateWorldRotation(); + _worldScaleCached = calculateWorldScale(); - newUpdateData.modelTransform.translation = worldPosition(); - newUpdateData.modelTransform.rotation = worldRotationMatrix(); - newUpdateData.modelTransform.scale = worldScale(); + newUpdateData.modelTransform.translation = _worldPositionCached; + newUpdateData.modelTransform.rotation = _worldRotationCached; + newUpdateData.modelTransform.scale = _worldScaleCached; glm::dmat4 translation = glm::translate( glm::dmat4(1.0), @@ -655,7 +655,7 @@ bool SceneGraphNode::hasGuiHintHidden() const { glm::dvec3 SceneGraphNode::calculateWorldPosition() const { // recursive up the hierarchy if there are parents available if (_parent) { - const glm::dvec3 wp = _parent->calculateWorldPosition(); + const glm::dvec3 wp = _parent->worldPosition(); const glm::dmat3 wrot = _parent->worldRotationMatrix(); const double ws = _parent->worldScale(); const glm::dvec3 p = position(); @@ -684,7 +684,7 @@ bool SceneGraphNode::isTimeFrameActive(const Time& time) const { glm::dmat3 SceneGraphNode::calculateWorldRotation() const { // recursive up the hierarchy if there are parents available if (_parent) { - return _parent->calculateWorldRotation() * rotationMatrix(); + return _parent->worldRotationMatrix() * rotationMatrix(); } else { return rotationMatrix(); @@ -694,7 +694,7 @@ glm::dmat3 SceneGraphNode::calculateWorldRotation() const { double SceneGraphNode::calculateWorldScale() const { // recursive up the hierarchy if there are parents available if (_parent) { - return _parent->calculateWorldScale() * scale(); + return _parent->worldScale() * scale(); } else { return scale();