Feature/navigation state (#930)

* Replace setCameraState with setNavigationState + equivalents
* Add documentation and verification of NavigationState-related interfaces
* Documentation and verification fixes
* Change reference frame behavior
* Scene fixes
* Replace earthrise recording with navigation state and time
This commit is contained in:
Emil Axelsson
2019-07-16 13:13:33 +02:00
committed by Alexander Bock
parent b25b205e99
commit f43bcadee3
29 changed files with 698 additions and 374 deletions

View File

@@ -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 ()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 ()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);

View File

@@ -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)

View File

@@ -14,7 +14,7 @@ local carpoGroup = {
{
Identifier = "Carpo",
Parent = {
Name = parentIdentifier,
Identifier = parentIdentifier,
Spice = parentSpice
},
Spice = "CARPO",

View File

@@ -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)

View File

@@ -51,7 +51,7 @@ public:
private:
std::function<T(float)> _transferFunction;
float _t = 0.f;
float _t = 1.f;
float _interpolationTime = 1.f;
float _scaledDeltaTime = 0.f;
};

View File

@@ -25,13 +25,17 @@
#ifndef __OPENSPACE_CORE___NAVIGATIONHANDLER___H__
#define __OPENSPACE_CORE___NAVIGATIONHANDLER___H__
#include <openspace/properties/propertyowner.h>
#include <openspace/documentation/documentation.h>
#include <openspace/interaction/inputstate.h>
#include <openspace/interaction/joystickcamerastates.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/interaction/keyframenavigator.h>
#include <openspace/properties/propertyowner.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/util/mouse.h>
#include <openspace/util/keys.h>
#include <optional>
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<glm::dvec3> 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<glm::dvec3> 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<std::string> 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 _inputState;
Camera* _camera = nullptr;
std::function<void()> _playbackEndCallback;
std::unique_ptr<OrbitalNavigator> _orbitalNavigator;
std::unique_ptr<KeyframeNavigator> _keyframeNavigator;
OrbitalNavigator _orbitalNavigator;
KeyframeNavigator _keyframeNavigator;
std::optional<NavigationState> _pendingNavigationState;
properties::BoolProperty _useKeyFrameInteraction;
};

View File

@@ -38,6 +38,8 @@
#include <ghoul/glm.h>
#include <glm/gtx/quaternion.hpp>
#include <optional>
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<glm::dvec3>_previousAnchorNodePosition;
std::optional<glm::dquat> _previousAnchorNodeRotation;
std::optional<glm::dvec3> _previousAimNodePosition;
double _currentCameraToSurfaceDistance = 0.0;
bool _directlySetStereoDistance = false;

View File

@@ -38,6 +38,7 @@
#include <openspace/util/factorymanager.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/fmt.h>
#include <ghoul/misc/templatefactory.h>
#include <ghoul/misc/assert.h>
#include <ghoul/systemcapabilities/generalcapabilitiescomponent.h>
@@ -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<SceneGraphNode*>(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<SceneGraphNode*>(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

View File

@@ -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.

View File

@@ -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<int>(L, 1);
const int y = ghoul::lua::value<int>(L, 2);
const int level = ghoul::lua::value<int>(L, 3);
lua_pop(L, 3);
const std::string& globeIdentifier = ghoul::lua::value<std::string>(L, 1);
const int x = ghoul::lua::value<int>(L, 2);
const int y = ghoul::lua::value<int>(L, 3);
const int level = ghoul::lua::value<int>(L, 4);
lua_pop(L, 4);
global::moduleEngine.module<GlobeBrowsingModule>()->goToChunk(x, y, level);
SceneGraphNode* n = sceneGraphNode(globeIdentifier);
if (!n) {
return ghoul::lua::luaError(L, "Unknown globe name: " + globeIdentifier);
}
const RenderableGlobe* globe = dynamic_cast<const RenderableGlobe*>(n->renderable());
if (!globe) {
return ghoul::lua::luaError(L, "Identifier must be a RenderableGlobe");
}
global::moduleEngine.module<GlobeBrowsingModule>()->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<double>(L, 1);
const double longitude = ghoul::lua::value<double>(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<GlobeBrowsingModule>()->goToGeo(latitude, longitude);
const SceneGraphNode* n;
if (providedGlobeIdentifier) {
const std::string& globeIdentifier = ghoul::lua::value<std::string>(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<double>(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<double>(L, parameterOffset + 1);
const double longitude = ghoul::lua::value<double>(L, parameterOffset + 2);
const RenderableGlobe* globe = dynamic_cast<const RenderableGlobe*>(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<GlobeBrowsingModule>()->goToGeo(
*globe, latitude, longitude
);
}
else if (nArguments == parameterOffset + 3) {
const double altitude = ghoul::lua::value<double>(L, parameterOffset + 3);
global::moduleEngine.module<GlobeBrowsingModule>()->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<std::string>(L, 1);
const std::string& globeIdentifier = ghoul::lua::value<std::string>(L, 1);
const double latitude = ghoul::lua::value<double>(L, 2);
const double longitude = ghoul::lua::value<double>(L, 3);
const double altitude = ghoul::lua::value<double>(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<const RenderableGlobe*>(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<GlobeBrowsingModule>());

View File

@@ -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() {

Binary file not shown.

View File

@@ -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());

View File

@@ -1013,8 +1013,6 @@ void OpenSpaceEngine::preSynchronization() {
}
global::renderEngine.updateScene();
//_navigationHandler->updateCamera(dt);
if (_scene) {
Camera* camera = _scene->camera();

View File

@@ -27,14 +27,17 @@
#include <openspace/engine/globals.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/interaction/keyframenavigator.h>
#include <openspace/interaction/inputstate.h>
#include <openspace/network/parallelpeer.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scene/scene.h>
#include <openspace/util/camera.h>
#include <openspace/documentation/verifier.h>
#include <openspace/query/query.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/dictionaryluaformatter.h>
#include <glm/gtx/vector_angle.hpp>
#include <fstream>
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<std::string>(KeyAnchor);
const bool hasPosition = dictionary.hasValue<glm::dvec3>(KeyPosition);
if (!hasAnchor || !hasPosition) {
throw ghoul::RuntimeError(
"Position and Anchor need to be defined for navigation dictionary."
);
}
anchor = dictionary.value<std::string>(KeyAnchor);
position = dictionary.value<glm::dvec3>(KeyPosition);
if (dictionary.hasValue<std::string>(KeyReferenceFrame)) {
referenceFrame = dictionary.value<std::string>(KeyReferenceFrame);
}
else {
referenceFrame = anchor;
}
if (dictionary.hasValue<std::string>(KeyAim)) {
aim = dictionary.value<std::string>(KeyAim);
}
if (dictionary.hasValue<glm::dvec3>(KeyUp)) {
up = dictionary.value<glm::dvec3>(KeyUp);
if (dictionary.hasValue<double>(KeyYaw)) {
yaw = dictionary.value<double>(KeyYaw);
}
if (dictionary.hasValue<double>(KeyPitch)) {
pitch = dictionary.value<double>(KeyPitch);
}
}
}
openspace::interaction::NavigationHandler::NavigationState::NavigationState(
std::string anchor,
std::string aim,
std::string referenceFrame,
glm::dvec3 position,
std::optional<glm::dvec3> 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<InputState>();
_orbitalNavigator = std::make_unique<OrbitalNavigator>();
_keyframeNavigator = std::make_unique<KeyframeNavigator>();
// 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<std::string> 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",

View File

@@ -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<std::string>(
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<std::string>(
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<std::string>(L, 1);
std::string referenceFrame = "";
if (n > 1) {
referenceFrame = ghoul::lua::value<std::string>(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;
}

View File

@@ -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

View File

@@ -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();