Merge remote-tracking branch 'origin/master' into thesis/2021/skybrowser

# Conflicts:
#	data/assets/util/webgui.asset
This commit is contained in:
sylvass
2022-03-02 16:41:11 -05:00
63 changed files with 1005 additions and 623 deletions

View File

@@ -27,9 +27,9 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(OpenSpace)
set(OPENSPACE_VERSION_MAJOR 0)
set(OPENSPACE_VERSION_MINOR 17)
set(OPENSPACE_VERSION_PATCH 1)
set(OPENSPACE_VERSION_STRING "Beta-10")
set(OPENSPACE_VERSION_MINOR 18)
set(OPENSPACE_VERSION_PATCH -1)
set(OPENSPACE_VERSION_STRING "Beta-11")
set(OPENSPACE_BASE_DIR "${PROJECT_SOURCE_DIR}")
set(OPENSPACE_CMAKE_EXT_DIR "${OPENSPACE_BASE_DIR}/support/cmake")

60
Jenkinsfile vendored
View File

@@ -29,42 +29,6 @@ def moduleCMakeFlags() {
modules = bat(returnStdout: true, script: '@dir modules /b /ad /on').trim().split('\r\n');
}
// def dirs = readDir();
// def currentDir = new File('.')
// def dirs = []
// currentDir.eachFile FileType.DIRECTORIES, {
// dirs << it.name
// }
// def moduleFlags = [
// 'atmosphere',
// 'base',
// // 'cefwebgui',
// 'debugging',
// 'digitaluniverse',
// 'fieldlines',
// 'fieldlinessequence',
// 'fitsfilereader',
// 'gaia',
// 'galaxy',
// 'globebrowsing',
// 'imgui',
// 'iswa',
// 'kameleon',
// 'kameleonvolume',
// 'multiresvolume',
// 'server',
// 'space',
// 'spacecraftinstruments',
// 'space',
// 'spout',
// 'sync',
// 'touch',
// 'toyvolume',
// 'volume',
// // 'webbrowser',
// // 'webgui'
// ];
def flags = '';
for (module in modules) {
flags += "-DOPENSPACE_MODULE_${module.toUpperCase()}=ON "
@@ -72,8 +36,6 @@ def moduleCMakeFlags() {
return flags;
}
// echo flags
//
// Pipeline start
//
@@ -114,8 +76,10 @@ linux_gcc_make: {
compileHelper.recordCompileIssues(compileHelper.Gcc());
}
stage('linux-gcc-make/test') {
testHelper.runUnitTests('bin/codegentest');
testHelper.runUnitTests('bin/SGCTTest');
testHelper.runUnitTests('bin/GhoulTest');
testHelper.runUnitTests('bin/OpenSpaceTest');
testHelper.runUnitTests('bin/codegentest')
}
cleanWs()
} // node('linux')
@@ -135,8 +99,10 @@ linux_gcc_ninja: {
compileHelper.build(compileHelper.Ninja(), compileHelper.Gcc(), cmakeCompileOptions, '', 'build-ninja');
}
stage('linux-gcc-ninja/test') {
testHelper.runUnitTests('bin/codegentest');
testHelper.runUnitTests('bin/SGCTTest');
testHelper.runUnitTests('bin/GhoulTest');
testHelper.runUnitTests('bin/OpenSpaceTest');
testHelper.runUnitTests('bin/codegentest')
}
cleanWs()
} // node('linux')
@@ -157,8 +123,10 @@ linux_clang_make: {
compileHelper.recordCompileIssues(compileHelper.Clang());
}
stage('linux-clang-make/test') {
testHelper.runUnitTests('bin/codegentest');
testHelper.runUnitTests('bin/SGCTTest');
testHelper.runUnitTests('bin/GhoulTest');
testHelper.runUnitTests('bin/OpenSpaceTest');
testHelper.runUnitTests('bin/codegentest')
}
cleanWs()
} // node('linux')
@@ -178,8 +146,10 @@ linux_clang_ninja: {
compileHelper.build(compileHelper.Ninja(), compileHelper.Clang(), cmakeCompileOptions, '', 'build-ninja');
}
stage('linux-clang-ninja/test') {
testHelper.runUnitTests('bin/codegentest');
testHelper.runUnitTests('bin/SGCTTest');
testHelper.runUnitTests('bin/GhoulTest');
testHelper.runUnitTests('bin/OpenSpaceTest');
testHelper.runUnitTests('bin/codegentest')
}
cleanWs()
} // node('linux')
@@ -197,8 +167,10 @@ windows_msvc: {
compileHelper.recordCompileIssues(compileHelper.VisualStudio());
}
stage('windows-msvc/test') {
testHelper.runUnitTests('bin\\Debug\\OpenSpaceTest')
testHelper.runUnitTests('bin\\Debug\\codegentest')
testHelper.runUnitTests('bin\\Debug\\codegentest');
testHelper.runUnitTests('bin\\Debug\\SGCTTest');
testHelper.runUnitTests('bin\\Debug\\GhoulTest');
testHelper.runUnitTests('bin\\Debug\\OpenSpaceTest');
}
cleanWs()
} // node('windows')

View File

@@ -123,6 +123,7 @@ set_folder_location(quat "External")
set_folder_location(tinyxml2static "External")
set_folder_location(vrpn "External")
set_folder_location(zlibstatic "External")
set_folder_location(SGCTTest "Unit Tests")
if (UNIX AND (NOT APPLE))
target_link_libraries(OpenSpace PRIVATE Xcursor Xinerama X11)

View File

@@ -1138,15 +1138,21 @@ int main(int argc, char* argv[]) {
std::filesystem::path base = configurationFilePath.parent_path();
FileSys.registerPathToken("${BASE}", base);
// For now, we just initialize glfw since we can't execute anything meaningfully
// after SGCT initializes glfw. There is a bit of startup delay because of this,
// but it shouldn't be too bad
glfwInit();
GLFWmonitor* m = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(m);
glm::ivec2 size = glm::ivec2(mode->width, mode->height);
glfwTerminate();
// The previous incarnation of this was initializing GLFW to get the primary
// monitor's resolution, but that had some massive performance implications as
// there was some issue with the swap buffer handling inside of GLFW. My
// assumption is that GLFW doesn't like being initialized, destroyed, and then
// initialized again. Therefore we are using the platform specific functions now
glm::ivec2 size = glm::ivec2(1920, 1080);
#ifdef WIN32
DEVMODEW dm = { 0 };
dm.dmSize = sizeof(DEVMODEW);
BOOL success = EnumDisplaySettingsW(nullptr, ENUM_CURRENT_SETTINGS, &dm);
if (success) {
size.x = dm.dmPelsWidth;
size.y = dm.dmPelsHeight;
}
#endif // WIN32
// Loading configuration from disk
LDEBUG("Loading configuration from disk");

View File

@@ -1,4 +1,5 @@
-- Color layers
asset.require("./layers/colorlayers/blue_marble")
local colorLayer = asset.require("./layers/colorlayers/esri_viirs_combo")
asset.require("./layers/colorlayers/esri_world_imagery")
asset.require("./layers/colorlayers/esri_imagery_world_2D")
@@ -13,9 +14,11 @@ asset.require("./layers/colorlayers/ghrsst_l4_g1sst_sea_surface_temperature_temp
asset.require("./layers/colorlayers/ghrsst_l4_mur_sea_surface_temperature_temporal")
-- Height layers
asset.require("./layers/heightlayers/blue_marble_height")
local heightLayer = asset.require("./layers/heightlayers/terrain_tileset")
-- Night layers
asset.require("./layers/nightlayers/earth_night_texture")
local nightLayer = asset.require("./layers/nightlayers/earth_at_night_2012")
asset.require("./layers/nightlayers/earth_at_night_temporal")

View File

@@ -1,4 +1,4 @@
local globeIdentifier = asset.require("../../../earth").Earth.Identifier
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local texturesPath = asset.syncedResource({
Name = "Earth Textures",
@@ -14,6 +14,15 @@ local layer = {
Description = [[ Earth image from Blue Marble Next Generation ]],
}
asset.onInitialize(function()
openspace.globebrowsing.addLayer(globeIdentifier, "ColorLayers", layer)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteLayer(globeIdentifier, "ColorLayers", layer)
end)
asset.export("layer", layer)

View File

@@ -1,7 +1,5 @@
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local fallBackLayer = asset.require("./fallbacks/blue_marble").layer
local layer = {
Identifier = "ESRI_VIIRS_Combo",
Name = "ESRI VIIRS Combo",
@@ -41,7 +39,6 @@ local layer = {
},
},
PadTiles = false,
Fallback = fallBackLayer,
Description = [[Level based layer combining "VIIRS SNPP (Temporal)" and ESRI World
Imagery. "VIIRS SNPP (Temporal)" is faded out at tile level 4]]
}

View File

@@ -1,3 +1,5 @@
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local texturesPath = asset.syncedResource({
Name = "Earth Textures",
Type = "HttpSynchronization",
@@ -12,6 +14,14 @@ local layer = {
Description = [[ Topographic layer from Blue Marble Next Generation]]
}
asset.onInitialize(function()
openspace.globebrowsing.addLayer(globeIdentifier, "HeightLayers", layer)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteLayer(globeIdentifier, "HeightLayers", layer)
end)
asset.export("layer", layer)

View File

@@ -1,12 +1,10 @@
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local fallBackLayer = asset.require("./fallbacks/blue_marble_height").layer
local layer = {
Identifier = "Terrain_tileset",
Name = "Terrain tileset",
FilePath = asset.localResource("terrain_tileset.wms"),
TilePixelSize = 64,
Fallback = fallBackLayer,
Description = [[This globe layer presents elevation data at approximately 90m or 1km
per pixel resolution for the world. The elevation data includes 90m Shuttle Radar
Topography Mission (SRTM) elevation data from NASA and National

View File

@@ -1,12 +1,9 @@
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local fallBackLayer = asset.require("./fallbacks/earth_night_texture").layer
local layer = {
Identifier = "Earth_at_Night_2012",
Name = "Earth at Night 2012",
FilePath = asset.localResource("earth_at_night_2012.wms"),
Fallback = fallBackLayer,
Description = [[ The lights of cities and villages trace the outlines of civilization
in this global view. ]],
}

View File

@@ -1,3 +1,5 @@
local globeIdentifier = asset.require("../../earth").Earth.Identifier
local texturesPath = asset.syncedResource({
Name = "Earth Textures",
Type = "HttpSynchronization",
@@ -12,6 +14,14 @@ local layer = {
Description = [[ Earth's city lights are clearly visible from space ]]
}
asset.onInitialize(function()
openspace.globebrowsing.addLayer(globeIdentifier, "NightLayers", layer)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteLayer(globeIdentifier, "NightLayers", layer)
end)
asset.export("layer", layer)

View File

@@ -23,6 +23,7 @@ local initializeAndAddNodes = function()
local iss = {
Identifier = "ISS",
Parent = transforms.EarthInertial.Identifier,
BoundingSphere = 54.5, -- half the width
InteractionSphere = 30,
Transform = {
Translation = {

View File

@@ -1,6 +1,7 @@
-- Color layer
local colorLayer = asset.require("./layers/colorlayers/moc_wa_color_utah") -- TODO: fallback
asset.require("./layers/colorlayers/moc_wa_color_sweden") -- TODO: fallback
asset.require("./layers/colorlayers/mars_texture")
local colorLayer = asset.require("./layers/colorlayers/moc_wa_color_utah")
asset.require("./layers/colorlayers/moc_wa_color_sweden")
asset.require("./layers/colorlayers/viking_mdim_utah")
asset.require("./layers/colorlayers/viking_mdim_sweden")
asset.require("./layers/colorlayers/mola_pseudo_color_utah")

View File

@@ -1,24 +0,0 @@
local texturesPath = asset.syncedResource({
Name = "Mars Textures",
Type = "HttpSynchronization",
Identifier = "mars_textures",
Version = 1
})
local layer = {
Identifier = "Mars_Texture",
Name = "Mars Texture",
FilePath = texturesPath .. "mars.jpg",
}
asset.export("layer", layer)
asset.meta = {
Name = "Mars Texutre",
Version = "1.0",
Description = [[ Default jpg texture for Mars]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -1,7 +1,5 @@
local globeIdentifier = asset.require("../../mars").Mars.Identifier
local fallbackLayer = asset.require("./fallbacks/mars_texture").layer
local layer = {
Identifier = "MOC_WA_Color_LiU",
Name = "MOC WA Color [Sweden]",
@@ -10,7 +8,6 @@ local layer = {
Gamma = 1.6,
Multiplier = 1.07
},
Fallback = fallbackLayer,
Description = [[This map is an AMNH version of the global mossaic produced by the
Mars Global Surveyor Wide Angle Camera. This version has color added and the
shadows subdued based on the MOLA DTM. Data Reference:

View File

@@ -1,7 +1,5 @@
local globeIdentifier = asset.require("../../mars").Mars.Identifier
local fallbackLayer = asset.require("./fallbacks/mars_texture").layer
local layer = {
Identifier = "MOC_WA_Color_Utah",
Name = "MOC WA Color [Utah]",
@@ -10,7 +8,6 @@ local layer = {
Gamma = 1.6,
Multiplier = 1.07
},
Fallback = fallbackLayer,
Description = [[This map is an AMNH version of the global mossaic produced by the
Mars Global Surveyor Wide Angle Camera. This version has color added and the
shadows subdued based on the MOLA DTM. Data Reference:

View File

@@ -13,7 +13,7 @@ local layer = {
latitude. <br><br> Reference: * Edwards, C.S., K. J. Nowicki, P. R. Christensen,
J. Hill, N. Gorelick, and K. Murray (2011), Mosaicking of global planetary image
datasets: 1. Techniques and data processing for Thermal Emission Imaging System
(THEMIS) multispectral data, J. Geophys. Res., 116, E10008,
(THEMIS) multi-spectral data, J. Geophys. Res., 116, E10008,
doi:10.1029/2010JE003755. http://dx.doi.org/10.1029/2010JE003755 (Description from
URL).]],
}

View File

@@ -5,13 +5,12 @@ local layer = {
Name = "HRSC MOLA Blended DEM Global 200m v2",
FilePath = asset.localResource("mdem200m.wms"),
Description = [[ Blend of data derived from the Mars Orbiter Laser Altimeter
(MOLA, an instrument aboard NASAs
Mars Global Surveyor spacecraft), and data derived from the High-Resolution
Stereo Camera (HRSC, an instrument aboard the European Space Agencys Mars
Express spacecraft). The average accuracy is ~100 meters in horizontal
position and the elevation uncertainty is at least ±3 m. This
tiled elevation layer, hosted by Esri, is made available using
lossless LERC compression. (Description from URL).]],
(MOLA, an instrument aboard NASA's Mars Global Surveyor spacecraft), and data derived
from the High-Resolution Stereo Camera (HRSC, an instrument aboard the European Space
Agency's Mars Express spacecraft). The average accuracy is ~100 meters in horizontal
position and the elevation uncertainty is at least ±3 m. This tiled elevation layer,
hosted by ESRI, is made available using lossless LERC compression.
(Description from URL).]],
}
asset.onInitialize(function()

View File

@@ -7,11 +7,11 @@ local layer = {
Description = [[ HiRISE (High Resolution Imaging Science Experiment) is the most
powerful camera ever sent to another planet, one of six instruments onboard the
Mars Reconnaissance Orbiter. We launched in 2005, arrived at Mars in 2006 and have
been imaging ever since. Our cameras high resolution capability (imaging up to 30
been imaging ever since. Our camera's high resolution capability (imaging up to 30
centimeters per pixel) remains unprecedented for any existing orbiter in the study
of the Red Planet, as well as being an indispensable instrument for helping to
select landing sites for robotic and future human exploration. In the past decade,
weve also imaged avalanches in progress, and discovered dark flows that may or
we've also imaged avalanches in progress, and discovered dark flows that may or
may not be briny seeps. Hundreds of science papers have been published with our
data. (Description from URL). This map contains a subet set of the HiRISE
DEM where available as of 2018.]],

View File

@@ -107,6 +107,12 @@ public:
bool setMode(Mode newMode);
void resetMode();
using CallbackHandle = int;
using ModeChangeCallback = std::function<void()>;
CallbackHandle addModeChangeCallback(ModeChangeCallback cb);
void removeModeChangeCallback(CallbackHandle handle);
// Guaranteed to return a valid pointer
AssetManager& assetManager();
LoadingScreen* loadingScreen();
@@ -151,6 +157,10 @@ private:
bool _isRenderingFirstFrame = true;
Mode _currentMode = Mode::UserControl;
Mode _modeLastFrame = Mode::UserControl;
int _nextCallbackHandle = 0;
std::vector<std::pair<CallbackHandle, ModeChangeCallback>> _modeChangeCallbacks;
};
/**

View File

@@ -671,7 +671,6 @@ protected:
bool isPropertyAllowedForBaseline(const std::string& propString);
unsigned int findIndexOfLastCameraKeyframeInTimeline();
bool doesTimelineEntryContainCamera(unsigned int index) const;
std::vector<std::pair<CallbackHandle, StateChangeCallback>> _stateChangeCallbacks;
bool doesStartWithSubstring(const std::string& s, const std::string& matchSubstr);
void trimCommandsFromScriptIfFound(std::string& script);
void replaceCommandsFromScriptIfFound(std::string& script);
@@ -800,6 +799,7 @@ protected:
double _cameraFirstInTimeline_timestamp = 0;
int _nextCallbackHandle = 0;
std::vector<std::pair<CallbackHandle, StateChangeCallback>> _stateChangeCallbacks;
DataMode _conversionDataMode = DataMode::Binary;
int _conversionLineNum = 1;

View File

@@ -155,16 +155,6 @@ private:
properties::FloatProperty _followAnchorNodeRotationDistance;
properties::FloatProperty _minimumAllowedDistance;
struct LinearFlight : public properties::PropertyOwner {
LinearFlight();
properties::BoolProperty apply;
properties::FloatProperty destinationDistance;
properties::DoubleProperty destinationFactor;
properties::FloatProperty velocitySensitivity;
};
LinearFlight _linearFlight;
properties::FloatProperty _mouseSensitivity;
properties::FloatProperty _joystickSensitivity;
properties::FloatProperty _websocketSensitivity;
@@ -291,20 +281,6 @@ private:
const glm::dvec3& objectPosition, const glm::dquat& globalCameraRotation,
const SurfacePositionHandle& positionHandle) const;
/**
* Moves the camera along a vector, camPosToCenterPosDiff, until it reaches the
* focusLimit. The velocity of the zooming depend on distFromCameraToFocus and the
* final frame where the camera stops moving depends on the distance set in the
* variable focusLimit. The bool determines whether to move/fly towards the focus node
* or away from it.
*
* \return a new position of the camera, closer to the focusLimit than the previous
* position
*/
glm::dvec3 moveCameraAlongVector(const glm::dvec3& camPos,
double distFromCameraToFocus, const glm::dvec3& camPosToCenterPosDiff,
double destination, double deltaTime) const;
/*
* Adds rotation to the camera position so that it follows the rotation of the anchor
* node defined by the differential anchorNodeRotationDiff.

View File

@@ -92,24 +92,50 @@ public:
*/
CameraPose interpolatedPose(double distance) const;
/**
* Reset variables used to play back path
*/
void resetPlaybackVariables();
private:
/**
* Interpolate between the paths start and end rotation using the approach that
* corresponds to the path's curve type. The interpolation parameter \p t is the
* same as for the position interpolation, i.e. the relative traveled in distance
* same as for the position interpolation, i.e. the relative traveled distance
* along the path, in [0, 1]
*
* \param t The interpolation parameter, given as the relative traveled distance
along the path, in [0, 1]
*/
glm::dquat interpolateRotation(double t) const;
/**
* Compute the interpolated rotation quaternion using an eased SLERP approach
*
* \param t The interpolation variable for the rotatation interpolation.
* Should be the relative traveled distance, in [0, 1]
*/
glm::dquat easedSlerpRotation(double t) const;
/**
* Compute the interpolated rotation quaternion using a method that is customized
* for linear paths. The camera will first interpoalte to look at the targetted
* node, and keep doing so for most of the path. At the end, when within a certain
* distance from the target, the rotation is interpolated so that the camera ends up
* in the target pose at the end of the path.
*
* \param t The interpolation variable for the rotatation interpolation.
* Should be the relative traveled distance, in [0, 1]
*/
glm::dquat linearPathRotation(double t) const;
/**
* Compute the interpolated rotation quaternion using an approach that first
* interpolates to look at the start node, and then the end node, before
* interpolating to the end rotation
*
* \param t The interpolation variable for the rotatation interpolation.
* Should be the relative traveled distance, in [0, 1]
*/
glm::dquat lookAtTargetsRotation(double t) const;
@@ -117,6 +143,8 @@ private:
* Evaluate the current traversal speed along the path, based on the currently
* traveled distance. The final speed will be scaled to match the desired duration
* for the path (which might have been specified by the user)
*
* \param traveledDistance The current distance traveled along the path, in meters
*/
double speedAlongPath(double traveledDistance) const;
@@ -129,15 +157,23 @@ private:
double _speedFactorFromDuration = 1.0;
// Playback variables
double _traveledDistance = 0.0;
double _progressedTime = 0.0; // Time since playback started
double _traveledDistance = 0.0; // Meters
double _progressedTime = 0.0; // Time since playback started (seconds)
bool _shouldQuit = false;
CameraPose _prevPose;
};
// Create a path of the given type based on an instruction given as a dictionary.
// See top of cpp file for documentation on keys and values for the dictionary.
// Returns the created path.
Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type type);
/**
* Create a path based on an instruction given as a dictionary. (See top of cpp file
* for documentation on keys and values for the dictionary.)
* If /p forceType is specified, that type will primarily be used as the type for the
* created path. Secondly, the type will be read from the dictionary, and lastly it will
* use the default from PathNavigator.
*
* \return the created path
*/
Path createPathFromDictionary(const ghoul::Dictionary& dictionary,
std::optional<Path::Type> forceType = std::nullopt);
} // namespace openspace::interaction

View File

@@ -26,6 +26,7 @@
#define __OPENSPACE_CORE___PATHCURVE___H__
#include <ghoul/glm.h>
#include <ghoul/misc/exception.h>
#include <vector>
namespace openspace::interaction {
@@ -34,21 +35,38 @@ class Waypoint;
class PathCurve {
public:
struct InsufficientPrecisionError : public ghoul::RuntimeError {
explicit InsufficientPrecisionError(std::string msg);
};
struct TooShortPathError : public ghoul::RuntimeError {
explicit TooShortPathError(std::string msg);
};
virtual ~PathCurve() = 0;
/**
* Return the length of the curve, in meters
*/
double length() const;
/**
* Compute and return the position along the path at the specified relative
* distance. The input parameter should be in range [0, 1], where 1 correspond to
* the full length of the path
* the full length of the path.
*
* Can be overridden by subclasses that want more control over the position
* interpolation
*/
glm::dvec3 positionAt(double relativeDistance) const;
virtual glm::dvec3 positionAt(double relativeDistance) const;
/**
* Get the intorlatied position along the spline, based on the given curve parameter
* u in range [0, 1]. A curve parameter of 0 returns the start position and 1 the end
* position. Note that u does not correspond to the relatively traveled distance.
*
* Can be overridden by subclasses that want more control over the position
* interpolation
*/
virtual glm::dvec3 interpolate(double u) const;
@@ -59,16 +77,16 @@ public:
protected:
/**
* Precompute information related to the spline parameters, that are
* Precompute information related to the spline parameters that are
* needed for arc length reparameterization. Must be called after
* control point creation
* control point creation.
*/
void initializeParameterData();
/**
* Compute curve parameter u that matches the input arc length s.
* Input s is a length value, in the range [0, _totalLength]. The returned curve
* parameter u is in range [0, 1]
* Input s is a length value in meters, in the range [0, _totalLength].
* The returned curve parameter u is in range [0, 1].
*/
double curveParameter(double s) const;
@@ -81,7 +99,7 @@ protected:
std::vector<double> _curveParameterSteps; // per segment
std::vector<double> _lengthSums; // per segment
double _totalLength = 0.0;
double _totalLength = 0.0; // meters
struct ParameterPair {
double u; // curve parameter
@@ -94,6 +112,9 @@ protected:
class LinearCurve : public PathCurve {
public:
LinearCurve(const Waypoint& start, const Waypoint& end);
glm::dvec3 positionAt(double relativeDistance) const override;
glm::dvec3 interpolate(double u) const override;
};
} // namespace openspace::interaction

View File

@@ -58,6 +58,7 @@ public:
const SceneGraphNode* anchor() const;
const Path* currentPath() const;
double speedScale() const;
double arrivalDistanceFactor() const;
bool hasCurrentPath() const;
bool hasFinished() const;
@@ -71,7 +72,10 @@ public:
void pausePath();
void continuePath();
Path::Type defaultPathType() const;
double minValidBoundingSphere() const;
double findValidBoundingSphere(const SceneGraphNode* node) const;
const std::vector<SceneGraphNode*>& relevantNodes();
/**
@@ -98,6 +102,7 @@ private:
properties::BoolProperty _includeRoll;
properties::FloatProperty _speedScale;
properties::BoolProperty _applyIdleBehaviorOnFinish;
properties::DoubleProperty _arrivalDistanceFactor;
properties::DoubleProperty _minValidBoundingSphere;
properties::StringListProperty _relevantNodeTags;

View File

@@ -41,8 +41,6 @@ public:
Waypoint(const glm::dvec3& pos, const glm::dquat& rot, const std::string& ref);
explicit Waypoint(const NavigationState& ns);
static double findValidBoundingSphere(const SceneGraphNode* node);
CameraPose pose() const;
glm::dvec3 position() const;
glm::dquat rotation() const;

View File

@@ -56,6 +56,8 @@ public:
void enqueue(std::function<void()> f);
void clearTasks();
bool hasOutstandingTasks() const;
private:
friend class Worker;

View File

@@ -147,20 +147,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemDistance::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_distance");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_distance",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary)

View File

@@ -157,20 +157,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemFramerate::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_framerate");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_framerate",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemFramerate::DashboardItemFramerate(const ghoul::Dictionary& dictionary)

View File

@@ -62,20 +62,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemPropertyValue::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_propertyvalue");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_propertyvalue",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemPropertyValue::DashboardItemPropertyValue(

View File

@@ -101,20 +101,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemSimulationIncrement::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_simulationincrement");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_simulationincrement",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemSimulationIncrement::DashboardItemSimulationIncrement(

View File

@@ -50,20 +50,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemText::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_text");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_text",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemText::DashboardItemText(const ghoul::Dictionary& dictionary)

View File

@@ -82,20 +82,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemVelocity::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_velocity");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"base_dashboarditem_velocity",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemVelocity::DashboardItemVelocity(const ghoul::Dictionary& dictionary)

View File

@@ -60,20 +60,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderablePlaneImageLocal::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"base_renderable_plane_image_local"
return codegen::doc<Parameters>(
"base_renderable_plane_image_local",
RenderablePlane::Documentation()
);
// @TODO cleanup
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderablePlane::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderablePlaneImageLocal::RenderablePlaneImageLocal(const ghoul::Dictionary& dictionary)

View File

@@ -52,20 +52,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderablePlaneImageOnline::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"base_renderable_plane_image_online"
return codegen::doc<Parameters>(
"base_renderable_plane_image_online",
RenderablePlane::Documentation()
);
// @TODO cleanup
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderablePlane::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderablePlaneImageOnline::RenderablePlaneImageOnline(

View File

@@ -76,20 +76,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderablePlaneTimeVaryingImage::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"base_renderable_plane_time_varying_image"
return codegen::doc<Parameters>(
"base_renderable_plane_time_varying_image",
RenderablePlane::Documentation()
);
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderablePlane::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderablePlaneTimeVaryingImage::RenderablePlaneTimeVaryingImage(

View File

@@ -136,20 +136,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderableTrailOrbit::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"base_renderable_renderabletrailorbit"
return codegen::doc<Parameters>(
"base_renderable_renderabletrailorbit",
RenderableTrail::Documentation()
);
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderableTrail::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderableTrailOrbit::RenderableTrailOrbit(const ghoul::Dictionary& dictionary)

View File

@@ -105,21 +105,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderableTrailTrajectory::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"base_renderable_renderabletrailtrajectory"
return codegen::doc<Parameters>(
"base_renderable_renderabletrailtrajectory",
RenderableTrail::Documentation()
);
// @TODO cleanup
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderableTrail::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderableTrailTrajectory::RenderableTrailTrajectory(const ghoul::Dictionary& dictionary)

View File

@@ -77,7 +77,7 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo ColorMapInfo = {
"ColorMap",
"Color Map File",
"The path to the color map file of the astronomical onject."
"The path to the color map file of the astronomical object."
};
struct [[codegen::Dictionary(RenderablePoints)]] Parameters {

View File

@@ -494,6 +494,11 @@ void RenderableGalaxy::deinitializeGL() {
_raycaster = nullptr;
}
global::renderEngine->removeRenderProgram(_pointsProgram.get());
_pointsProgram = nullptr;
global::renderEngine->removeRenderProgram(_billboardsProgram.get());
_billboardsProgram = nullptr;
glDeleteVertexArrays(1, &_pointsVao);
glDeleteBuffers(1, &_positionVbo);
glDeleteBuffers(1, &_colorVbo);

View File

@@ -322,10 +322,21 @@ int flyToGeo(lua_State* L) {
altitude
);
const glm::dvec3 currentPosW = global::navigationHandler->camera()->positionVec3();
const glm::dvec3 currentPosModelCoords =
glm::inverse(globe->modelTransform()) * glm::dvec4(currentPosW, 1.0);
constexpr const double LengthEpsilon = 10.0; // meters
if (glm::distance(currentPosModelCoords, positionModelCoords) < LengthEpsilon) {
LINFOC("GlobeBrowsing", "flyToGeo: Already at the requested position");
return 0;
}
ghoul::Dictionary instruction;
instruction.setValue("TargetType", std::string("Node"));
instruction.setValue("Target", n->identifier());
instruction.setValue("Position", positionModelCoords);
instruction.setValue("PathType", std::string("ZoomOutOverview"));
// Handle the two optional arguments: duration and use target's up direction argument
// The user can either provide both, or one of them

View File

@@ -34,7 +34,6 @@
namespace {
constexpr const char* _loggerCat = "LayerGroup";
constexpr const char* KeyFallback = "Fallback";
constexpr openspace::properties::Property::PropertyInfo BlendTileInfo = {
"BlendTileLevels",
@@ -68,18 +67,6 @@ void LayerGroup::setLayersFromDict(const ghoul::Dictionary& dict) {
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
if (layerDict.hasValue<ghoul::Dictionary>(KeyFallback)) {
LWARNING("Unable to create layer. Initializing fallback layer.");
ghoul::Dictionary fallbackLayerDict =
layerDict.value<ghoul::Dictionary>(KeyFallback);
try {
addLayer(fallbackLayerDict);
}
catch (const ghoul::RuntimeError& except) {
LERRORC(except.component, except.message);
}
}
}
}
}

View File

@@ -34,6 +34,7 @@ set(HEADER_FILES
include/topics/authorizationtopic.h
include/topics/bouncetopic.h
include/topics/documentationtopic.h
include/topics/enginemodetopic.h
include/topics/flightcontrollertopic.h
include/topics/getpropertytopic.h
include/topics/luascripttopic.h
@@ -57,6 +58,7 @@ set(SOURCE_FILES
src/topics/authorizationtopic.cpp
src/topics/bouncetopic.cpp
src/topics/documentationtopic.cpp
src/topics/enginemodetopic.cpp
src/topics/flightcontrollertopic.cpp
src/topics/getpropertytopic.cpp
src/topics/luascripttopic.cpp

View File

@@ -0,0 +1,55 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_SERVER___ENGINE_MODE_TOPIC___H__
#define __OPENSPACE_MODULE_SERVER___ENGINE_MODE_TOPIC___H__
#include <modules/server/include/topics/topic.h>
#include <openspace/engine/openspaceengine.h>
namespace openspace {
class EngineModeTopic : public Topic {
public:
EngineModeTopic();
virtual ~EngineModeTopic();
void handleJson(const nlohmann::json& json) override;
bool isDone() const override;
private:
static const int UnsetOnChangeHandle = -1;
// Provides the mode int value in json message
void sendJsonData();
OpenSpaceEngine::Mode _lastMode;
int _modeCallbackHandle = UnsetOnChangeHandle;
bool _isDone = false;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_SERVER___ENGINE_MODE_TOPIC___H__

View File

@@ -27,6 +27,7 @@
#include <modules/server/include/topics/authorizationtopic.h>
#include <modules/server/include/topics/bouncetopic.h>
#include <modules/server/include/topics/documentationtopic.h>
#include <modules/server/include/topics/enginemodetopic.h>
#include <modules/server/include/topics/flightcontrollertopic.h>
#include <modules/server/include/topics/getpropertytopic.h>
#include <modules/server/include/topics/luascripttopic.h>
@@ -59,6 +60,7 @@ namespace {
constexpr const char* DocumentationTopicKey = "documentation";
constexpr const char* GetPropertyTopicKey = "get";
constexpr const char* LuaScriptTopicKey = "luascript";
constexpr const char* EngineModeTopicKey = "engineMode";
constexpr const char* SessionRecordingTopicKey = "sessionRecording";
constexpr const char* SetPropertyTopicKey = "set";
constexpr const char* ShortcutTopicKey = "shortcuts";
@@ -97,6 +99,7 @@ Connection::Connection(std::unique_ptr<ghoul::io::Socket> s,
_topicFactory.registerClass<DocumentationTopic>(DocumentationTopicKey);
_topicFactory.registerClass<GetPropertyTopic>(GetPropertyTopicKey);
_topicFactory.registerClass<LuaScriptTopic>(LuaScriptTopicKey);
_topicFactory.registerClass<EngineModeTopic>(EngineModeTopicKey);
_topicFactory.registerClass<SessionRecordingTopic>(SessionRecordingTopicKey);
_topicFactory.registerClass<SetPropertyTopic>(SetPropertyTopicKey);
_topicFactory.registerClass<ShortcutTopic>(ShortcutTopicKey);

View File

@@ -0,0 +1,118 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include "modules/server/include/topics/enginemodetopic.h"
#include <modules/server/include/connection.h>
#include <openspace/engine/globals.h>
#include <openspace/query/query.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr const char _loggerCat[] = "EngineModeTopic";
constexpr const char EventKey[] = "event";
constexpr const char SubscribeEvent[] = "start_subscription";
constexpr const char UnsubscribeEvent[] = "stop_subscription";
constexpr const char RefreshEvent[] = "refresh";
constexpr const char ModeKey[] = "mode";
} // namespace
using nlohmann::json;
namespace openspace {
EngineModeTopic::EngineModeTopic() {
LDEBUG("Starting new EngineMode subscription");
}
EngineModeTopic::~EngineModeTopic() {
if (_modeCallbackHandle != UnsetOnChangeHandle) {
global::openSpaceEngine->removeModeChangeCallback(_modeCallbackHandle);
}
}
bool EngineModeTopic::isDone() const {
return _isDone;
}
void EngineModeTopic::handleJson(const nlohmann::json& json) {
const std::string event = json.at(EventKey).get<std::string>();
if (event != SubscribeEvent && event != UnsubscribeEvent &&
event != RefreshEvent)
{
LERROR("Unsupported event");
_isDone = true;
return;
}
if (event == UnsubscribeEvent) {
_isDone = true;
return;
}
sendJsonData();
if (event == SubscribeEvent) {
_modeCallbackHandle = global::openSpaceEngine->addModeChangeCallback(
[this]() {
OpenSpaceEngine::Mode currentMode =
global::openSpaceEngine->currentMode();
if (currentMode != _lastMode) {
sendJsonData();
_lastMode = currentMode;
}
}
);
}
}
void EngineModeTopic::sendJsonData() {
json stateJson;
OpenSpaceEngine::Mode mode = global::openSpaceEngine->currentMode();
std::string modeString;
switch (mode) {
case OpenSpaceEngine::Mode::UserControl:
modeString = "user_control";
break;
case OpenSpaceEngine::Mode::SessionRecordingPlayback:
modeString = "session_recording_playback";
break;
case OpenSpaceEngine::Mode::CameraPath:
modeString = "camera_path";
break;
default:
throw ghoul::MissingCaseException();
}
stateJson[ModeKey] = modeString;
if (!stateJson.empty()) {
_connection->sendJson(wrappedPayload(stateJson));
}
}
} // namespace openspace

View File

@@ -94,21 +94,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderableHabitableZone::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"space_renderablehabitablezone"
return codegen::doc<Parameters>(
"space_renderablehabitablezone",
RenderableDisc::Documentation()
);
// @TODO cleanup
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderableDisc::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderableHabitableZone::RenderableHabitableZone(const ghoul::Dictionary& dictionary)

View File

@@ -64,20 +64,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderableSatellites::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"space_renderablesatellites"
return codegen::doc<Parameters>(
"space_renderablesatellites",
RenderableOrbitalKepler::Documentation()
);
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderableOrbitalKepler::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderableSatellites::RenderableSatellites(const ghoul::Dictionary& dictionary)

View File

@@ -98,19 +98,10 @@ namespace {
namespace openspace {
documentation::Documentation RenderableSmallBody::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>(
"space_renderablesmallbody"
return codegen::doc<Parameters>(
"space_renderablesmallbody",
RenderableOrbitalKepler::Documentation()
);
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = RenderableOrbitalKepler::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
);
return doc;
}
RenderableSmallBody::RenderableSmallBody(const ghoul::Dictionary& dictionary)

View File

@@ -85,20 +85,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemInstruments::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("spacecraftinstruments_dashboarditem_instuments");
// @TODO cleanup
// Insert the parent's documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::Documentation parentDoc = DashboardTextItem::Documentation();
doc.entries.insert(
doc.entries.end(),
parentDoc.entries.begin(),
parentDoc.entries.end()
return codegen::doc<Parameters>(
"spacecraftinstruments_dashboarditem_instuments",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemInstruments::DashboardItemInstruments(const ghoul::Dictionary& dictionary)

View File

@@ -53,7 +53,7 @@ function sgct.config.single(arg) end
-- shared: Determines whether the contents of the window should be shared using the SPOUT library [example: shared=true] {default: false}
-- Expert settings:
-- vsync: Whether the rendering speed is locked to the refreshrate [example: vsync=true] {default: false}
-- vsync: Whether the rendering speed is locked to the refreshrate [example: vsync=true] {default: true}
-- refreshRate: If vsync is enabled, this is the target framerate [example: refreshRate=30] {default: infinity}
-- stereo: Select the stereo rendering mode as supported by SGCT [example: stereo='anaglyph_red_cyan'] {default: 'none'}
-- msaa: The multisampling anti-aliasing factor [example: msaa=8] {default: 4}
@@ -279,11 +279,9 @@ function generateCluster(arg, viewport)
table.insert(result, [[ "debug": ]] .. tostring(arg["sgctdebug"]) .. [[,]])
end
if (arg["settings"]) then
table.insert(result, [[ "settings": {]])
generateSettings(result, arg["refreshRate"], arg["vsync"])
table.insert(result, [[ },]])
end
table.insert(result, [[ "settings": {]])
generateSettings(result, arg["refreshRate"], arg["vsync"])
table.insert(result, [[ },]])
if arg["scene"] then
table.insert(result, [[ "scene": {]])

View File

@@ -1180,6 +1180,17 @@ void OpenSpaceEngine::preSynchronization() {
setCameraFromProfile(*global::profile);
setAdditionalScriptsFromProfile(*global::profile);
}
// Handle callback(s) for change in engine mode
if (_modeLastFrame != _currentMode) {
using K = CallbackHandle;
using V = ModeChangeCallback;
for (const std::pair<K, V>& it : _modeChangeCallbacks) {
it.second();
}
}
_modeLastFrame = _currentMode;
LTRACE("OpenSpaceEngine::preSynchronization(end)");
}
@@ -1617,6 +1628,31 @@ void OpenSpaceEngine::resetMode() {
LDEBUG(fmt::format("Reset engine mode to {}", stringify(_currentMode)));
}
OpenSpaceEngine::CallbackHandle OpenSpaceEngine::addModeChangeCallback(
ModeChangeCallback cb)
{
CallbackHandle handle = _nextCallbackHandle++;
_modeChangeCallbacks.emplace_back(handle, std::move(cb));
return handle;
}
void OpenSpaceEngine::removeModeChangeCallback(CallbackHandle handle) {
const auto it = std::find_if(
_modeChangeCallbacks.begin(),
_modeChangeCallbacks.end(),
[handle](const std::pair<CallbackHandle, ModeChangeCallback>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _modeChangeCallbacks.end(),
"handle must be a valid callback handle"
);
_modeChangeCallbacks.erase(it);
}
void setCameraFromProfile(const Profile& p) {
if (!p.camera.has_value()) {
throw ghoul::RuntimeError("No 'camera' entry exists in the startup profile");

View File

@@ -158,34 +158,6 @@ namespace {
"meters above the surface."
};
constexpr openspace::properties::Property::PropertyInfo VelocityZoomControlInfo = {
"VelocityZoomControl",
"Velocity Zoom Control",
"Controls the velocity of the camera motion when zooming in to the focus node "
"on a linear flight. The higher the value the faster the camera will move "
"towards the focus."
};
constexpr openspace::properties::Property::PropertyInfo ApplyLinearFlightInfo = {
"ApplyLinearFlight",
"Apply Linear Flight",
"This property makes the camera move to the specified distance "
"'DestinationDistance' while facing the anchor"
};
constexpr openspace::properties::Property::PropertyInfo FlightDestinationDistInfo = {
"FlightDestinationDistance",
"Flight Destination Distance",
"The final distance we want to fly to, with regards to the anchor node."
};
constexpr openspace::properties::Property::PropertyInfo FlightDestinationFactorInfo =
{
"FlightDestinationFactor",
"Flight Destination Factor",
"The minimal distance factor that we need to reach to end linear flight."
};
constexpr openspace::properties::Property::PropertyInfo
StereoInterpolationTimeInfo = {
"StereoInterpolationTime",
@@ -299,20 +271,6 @@ OrbitalNavigator::Friction::Friction()
addProperty(friction);
}
OrbitalNavigator::LinearFlight::LinearFlight()
: properties::PropertyOwner({ "LinearFlight" })
, apply(ApplyLinearFlightInfo, false)
, destinationDistance(FlightDestinationDistInfo, 2e8f, 10.f, 1e10f)
, destinationFactor(FlightDestinationFactorInfo, 1E-4, 1E-6, 0.5, 1E-3)
, velocitySensitivity(VelocityZoomControlInfo, 3.5f, 0.001f, 20.f)
{
addProperty(apply);
addProperty(velocitySensitivity);
destinationDistance.setExponent(5.f);
addProperty(destinationDistance);
addProperty(destinationFactor);
}
OrbitalNavigator::IdleBehavior::IdleBehavior()
: properties::PropertyOwner({ "IdleBehavior" })
, apply(ApplyIdleBehaviorInfo, false)
@@ -472,7 +430,6 @@ OrbitalNavigator::OrbitalNavigator()
});
addPropertySubOwner(_friction);
addPropertySubOwner(_linearFlight);
addPropertySubOwner(_idleBehavior);
_idleBehaviorDampenInterpolator.setTransferFunction(
@@ -593,37 +550,6 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) {
_camera->rotationQuaternion()
};
if (_linearFlight.apply) {
// Calculate a position handle based on the camera position in world space
glm::dvec3 camPosToAnchorPosDiff = prevCameraPosition - anchorPos;
// Use the interaction sphere to get an approximate distance to the node surface
double nodeRadius = _anchorNode->interactionSphere();
double distFromCameraToFocus =
glm::distance(prevCameraPosition, anchorPos) - nodeRadius;
// Make the approximation delta size depending on the flight distance
double arrivalThreshold =
_linearFlight.destinationDistance * _linearFlight.destinationFactor;
const double distToDestination =
std::fabs(distFromCameraToFocus - _linearFlight.destinationDistance);
// Fly towards the flight destination distance. When getting closer than
// arrivalThreshold terminate the flight
if (distToDestination > arrivalThreshold) {
pose.position = moveCameraAlongVector(
pose.position,
distFromCameraToFocus,
camPosToAnchorPosDiff,
_linearFlight.destinationDistance,
deltaTime
);
}
else {
_linearFlight.apply = false;
}
}
const bool hasPreviousPositions =
_previousAnchorNodePosition.has_value() &&
_previousAimNodePosition.has_value();
@@ -1416,28 +1342,6 @@ glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime,
return cameraPosition + rotationDiffVec3;
}
glm::dvec3 OrbitalNavigator::moveCameraAlongVector(const glm::dvec3& camPos,
double distFromCameraToFocus,
const glm::dvec3& camPosToAnchorPosDiff,
double destination,
double deltaTime) const
{
// This factor adapts the velocity so it slows down when getting closer
// to our final destination
double velocity = 0.0;
if (destination < distFromCameraToFocus) { // When flying towards anchor
velocity = 1.0 - destination / distFromCameraToFocus;
}
else { // When flying away from anchor
velocity = distFromCameraToFocus / destination - 1.0;
}
velocity *= _linearFlight.velocitySensitivity * deltaTime;
// Return the updated camera position
return camPos - velocity * camPosToAnchorPosDiff;
}
glm::dvec3 OrbitalNavigator::followAnchorNodeRotation(const glm::dvec3& cameraPosition,
const glm::dvec3& objectPosition,
const glm::dquat& focusNodeRotationDiff) const

View File

@@ -42,7 +42,7 @@
namespace {
constexpr const char _loggerCat[] = "Path";
constexpr const float Epsilon = 1e-5f;
constexpr const float LengthEpsilon = 1e-5f;
constexpr const char SunIdentifier[] = "Sun";
@@ -82,6 +82,15 @@ namespace {
// A navigation state that determines the start state for the camera path
std::optional<ghoul::Dictionary> startState
[[codegen::reference("core_navigation_state")]];
enum class [[codegen::map(openspace::interaction::Path::Type)]] PathType {
AvoidCollision,
ZoomOutOverview,
Linear,
AvoidCollisionWithLookAt
};
// The type of the created path. Affects the shape of the resulting path
std::optional<PathType> pathType;
};
#include "path_codegen.cpp"
} // namespace
@@ -108,6 +117,8 @@ Path::Path(Waypoint start, Waypoint end, Type type,
throw ghoul::MissingCaseException();
}
_prevPose = _start.pose();
// Compute speed factor to match any given duration, by traversing the path and
// computing how much faster/slower it should be
_speedFactorFromDuration = 1.0;
@@ -119,11 +130,9 @@ Path::Path(Waypoint start, Waypoint end, Type type,
// We now know how long it took to traverse the path. Use that
_speedFactorFromDuration = _progressedTime / *duration;
}
// Reset playback variables
_traveledDistance = 0.0;
_progressedTime = 0.0;
resetPlaybackVariables();
}
}
Waypoint Path::startPoint() const { return _start; }
@@ -139,12 +148,49 @@ std::vector<glm::dvec3> Path::controlPoints() const {
CameraPose Path::traversePath(double dt, float speedScale) {
double speed = speedAlongPath(_traveledDistance);
speed *= static_cast<double>(speedScale);
const double displacement = dt * speed;
double displacement = dt * speed;
const double prevDistance = _traveledDistance;
_progressedTime += dt;
_traveledDistance += displacement;
return interpolatedPose(_traveledDistance);
CameraPose newPose;
if (_type == Type::Linear) {
// Special handling of linear paths, so that it can be used when we are
// traversing very large distances without introducing precision problems
const glm::dvec3 prevPosToEnd = _prevPose.position - _end.position();
const double remainingDistance = glm::length(prevPosToEnd);
// Actual displacement may not be bigger than remaining distance
if (displacement > remainingDistance) {
displacement = remainingDistance;
_traveledDistance = pathLength();
_shouldQuit = true;
return _end.pose();
}
// Just move along the line from the current position to the target
newPose.position = _prevPose.position -
displacement * glm::normalize(prevPosToEnd);
const double relativeDistance = _traveledDistance / pathLength();
newPose.rotation = interpolateRotation(relativeDistance);
}
else {
if (std::abs(prevDistance - _traveledDistance) < LengthEpsilon) {
// The distaces are too large, so we are not making progress because of
// insufficient precision
_shouldQuit = true;
LWARNING("Quit camera path prematurely due to insufficient precision");
}
newPose = interpolatedPose(_traveledDistance);
}
_prevPose = newPose;
return newPose;
}
std::string Path::currentAnchor() const {
@@ -153,9 +199,20 @@ std::string Path::currentAnchor() const {
}
bool Path::hasReachedEnd() const {
if (_shouldQuit) {
return true;
}
return (_traveledDistance / pathLength()) >= 1.0;
}
void Path::resetPlaybackVariables() {
_prevPose = _start.pose();
_traveledDistance = 0.0;
_progressedTime = 0.0;
_shouldQuit = false;
}
CameraPose Path::interpolatedPose(double distance) const {
const double relativeDistance = distance / pathLength();
CameraPose cs;
@@ -167,8 +224,9 @@ CameraPose Path::interpolatedPose(double distance) const {
glm::dquat Path::interpolateRotation(double t) const {
switch (_type) {
case Type::AvoidCollision:
case Type::Linear:
return easedSlerpRotation(t);
case Type::Linear:
return linearPathRotation(t);
case Type::ZoomOutOverview:
case Type::AvoidCollisionWithLookAt:
return lookAtTargetsRotation(t);
@@ -183,6 +241,48 @@ glm::dquat Path::easedSlerpRotation(double t) const {
return glm::slerp(_start.rotation(), _end.rotation(), tScaled);
}
glm::dquat Path::linearPathRotation(double t) const {
const double tHalf = 0.5;
const glm::dvec3 endNodePos = _end.node()->worldPosition();
const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0);
if (t < tHalf) {
// Interpolate to look at target
const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf);
const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp);
const double tScaled = ghoul::sineEaseInOut(t / tHalf);
return glm::slerp(_start.rotation(), q, tScaled);
}
// This distance is guaranteed to be strictly decreasing for linear paths
const double distanceToEnd = glm::distance(_prevPose.position, _end.position());
// Determine the distance at which to start interpolating to the target rotation.
// The magic numbers here are just randomly picked constants, set to make the
// resulting rotation look ok-ish
double closingUpDistance = 10.0 * _end.validBoundingSphere();
if (pathLength() < 2.0 * closingUpDistance) {
closingUpDistance = 0.2 * pathLength();
}
if (distanceToEnd < closingUpDistance) {
// Interpolate to target rotation
const double tScaled = ghoul::sineEaseInOut(1.0 - distanceToEnd / closingUpDistance);
// Compute a position in front of the camera at the end orientation
const double inFrontDistance = glm::distance(_end.position(), endNodePos);
const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation());
const glm::dvec3 inFrontOfEnd = _end.position() + inFrontDistance * viewDir;
const glm::dvec3 lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd);
return ghoul::lookAtQuaternion(_prevPose.position, lookAtPos, endUp);
}
// Keep looking at the end node
return ghoul::lookAtQuaternion(_prevPose.position, endNodePos, endUp);
}
glm::dquat Path::lookAtTargetsRotation(double t) const {
const double t1 = 0.2;
const double t2 = 0.8;
@@ -234,51 +334,55 @@ double Path::speedAlongPath(double traveledDistance) const {
const glm::dvec3 endNodePos = _end.node()->worldPosition();
const glm::dvec3 startNodePos = _start.node()->worldPosition();
const CameraPose prevPose = interpolatedPose(traveledDistance);
const double distanceToEndNode = glm::distance(prevPose.position, endNodePos);
const double distanceToStartNode = glm::distance(prevPose.position, startNodePos);
// Set speed based on distance to closest node
const double distanceToEndNode = glm::distance(_prevPose.position, endNodePos);
const double distanceToStartNode = glm::distance(_prevPose.position, startNodePos);
bool isCloserToEnd = distanceToEndNode < distanceToStartNode;
// Decide which is the closest node
SceneGraphNode* closestNode = _start.node();
glm::dvec3 closestPos = startNodePos;
const glm::dvec3 closestPos = isCloserToEnd ? endNodePos : startNodePos;
const double distanceToClosestNode = glm::distance(closestPos, _prevPose.position);
if (distanceToEndNode < distanceToStartNode) {
closestPos = endNodePos;
closestNode = _end.node();
}
const double distanceToClosestNode = glm::distance(closestPos, prevPose.position);
double speed = distanceToClosestNode;
// Dampen speed in beginning of path
double startUpDistance = 2.0 * _start.node()->boundingSphere();
if (startUpDistance < Epsilon) { // zero bounding sphere
startUpDistance = glm::distance(_start.position(), startNodePos);
const double speed = distanceToClosestNode;
// Dampen at the start and end
constexpr const double DampenDistanceFactor = 3.0;
double startUpDistance = DampenDistanceFactor * _start.validBoundingSphere();
double closeUpDistance = DampenDistanceFactor * _end.validBoundingSphere();
if (pathLength() < startUpDistance + closeUpDistance) {
startUpDistance = 0.49 * pathLength(); // a little less than half
closeUpDistance = startUpDistance;
}
double dampeningFactor = 1.0;
if (traveledDistance < startUpDistance) {
speed *= traveledDistance / startUpDistance + 0.01;
dampeningFactor = traveledDistance / startUpDistance;
}
// Dampen speed in end of path
// Note: this leads to problems when the full length of the path is really big
double closeUpDistance = 2.0 * _end.node()->boundingSphere();
if (closeUpDistance < Epsilon) { // zero bounding sphere
closeUpDistance = glm::distance(_end.position(), endNodePos);
else if (_type == Type::Linear) {
// Dampen at end of linear path is handled separately, as we can use the
// current position to scompute the remaining distance rather than the
// path length minus travels distance. This is more suitable for long paths
const double remainingDistance = glm::distance(
_prevPose.position,
_end.position()
);
if (remainingDistance < closeUpDistance) {
dampeningFactor = remainingDistance / closeUpDistance;
}
}
if (traveledDistance > (pathLength() - closeUpDistance)) {
else if (traveledDistance > (pathLength() - closeUpDistance)) {
const double remainingDistance = pathLength() - traveledDistance;
speed *= remainingDistance / closeUpDistance + 0.01;
dampeningFactor = remainingDistance / closeUpDistance;
}
dampeningFactor = ghoul::sineEaseOut(dampeningFactor);
// Prevent multiplying with 0 (and hence a speed of 0.0 => no movement)
dampeningFactor += 0.01;
// TODO: also dampen speed based on curvature, or make sure the curve has a rounder
// shape
// TODO: check for when path is shorter than the starUpDistance or closeUpDistance
// variables
return _speedFactorFromDuration * speed;
return _speedFactorFromDuration * speed * dampeningFactor;
}
Waypoint waypointFromCamera() {
@@ -297,7 +401,7 @@ SceneGraphNode* findNodeNearTarget(const SceneGraphNode* node) {
bool isSame = (n->identifier() == node->identifier());
// If the nodes are in the very same position, they are probably representing
// the same object
isSame |= glm::distance(n->worldPosition(), node->worldPosition()) < Epsilon;
isSame |= glm::distance(n->worldPosition(), node->worldPosition()) < LengthEpsilon;
if (isSame) {
continue;
@@ -376,7 +480,9 @@ struct NodeInfo {
bool useTargetUpDirection;
};
Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& startPoint) {
Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& startPoint,
Path::Type type)
{
const SceneGraphNode* targetNode = sceneGraphNode(info.identifier);
if (!targetNode) {
LERROR(fmt::format("Could not find target node '{}'", info.identifier));
@@ -392,12 +498,23 @@ Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& start
);
}
else {
const double radius = Waypoint::findValidBoundingSphere(targetNode);
const double defaultHeight = 2.0 * radius;
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
const double radius = navigator.findValidBoundingSphere(targetNode);
const double defaultHeight = radius * navigator.arrivalDistanceFactor();
const double height = info.height.value_or(defaultHeight);
const double distanceFromNodeCenter = radius + height;
const glm::dvec3 stepDir = computeGoodStepDirection(targetNode, startPoint);
glm::dvec3 stepDir = glm::dvec3(0.0);
if (type == Path::Type::Linear) {
// If linear path, compute position along line form start to end point
glm::dvec3 endNodePos = targetNode->worldPosition();
stepDir = glm::normalize(startPoint.position() - endNodePos);
}
else {
stepDir = computeGoodStepDirection(targetNode, startPoint);
}
targetPos = targetNode->worldPosition() + stepDir * distanceFromNodeCenter;
}
@@ -416,18 +533,29 @@ Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& start
return Waypoint(targetPos, targetRot, info.identifier);
}
Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type type) {
Path createPathFromDictionary(const ghoul::Dictionary& dictionary,
std::optional<Path::Type> forceType)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
const std::optional<float> duration = p.duration;
Path::Type type;
if (forceType.has_value()) {
type = *forceType;
}
else if (p.pathType.has_value()) {
type = codegen::map<Path::Type>(*p.pathType);
}
else {
type = global::navigationHandler->pathNavigator().defaultPathType();
}
bool hasStart = p.startState.has_value();
const Waypoint startPoint = hasStart ?
Waypoint(NavigationState(p.startState.value())) :
waypointFromCamera();
// TODO: also handle curve type here
std::vector<Waypoint> waypoints;
switch (p.targetType) {
case Parameters::TargetType::NavigationState: {
@@ -462,7 +590,7 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type ty
p.useTargetUpDirection.value_or(false)
};
waypoints = { computeWaypointFromNodeInfo(info, startPoint) };
waypoints = { computeWaypointFromNodeInfo(info, startPoint, type) };
break;
}
default: {
@@ -473,7 +601,36 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type ty
// @TODO (emmbr) Allow for an instruction to represent a list of multiple waypoints
const Waypoint waypointToAdd = waypoints[0];
return Path(startPoint, waypointToAdd, type, duration);
if (glm::distance(startPoint.position(), waypointToAdd.position()) < LengthEpsilon) {
LINFO("Already at the requested target");
throw PathCurve::TooShortPathError("Path too short!");
}
try {
return Path(startPoint, waypointToAdd, type, duration);
}
catch (const PathCurve::TooShortPathError& e) {
LINFO("Already at the requested target");
// Rethrow e, so the pathnavigator can handle it as well
throw e;
}
catch (const PathCurve::InsufficientPrecisionError&) {
// There wasn't enough precision to represent the full curve in world
// coordinates. For now, use a linear path instead. It uses another
// method of interpolation that isn't as sensitive to these kinds of problems
LINFO(
"Switching to a linear path, to avoid problems with precision due to "
"immense path length."
);
return createPathFromDictionary(dictionary, Path::Type::Linear);
// @TODO (emmbr26, 2022-02-15): later on we want to improve this case, so that
// interpolation is not a problem. One suggestion to solve this would be using
// two identical paths and switch the direction of interpolation halfway through.
// That should give us sufficient precision at both ends of the path
}
}
} // namespace openspace::interaction

View File

@@ -35,11 +35,23 @@
#include <vector>
namespace {
constexpr const int NrSamplesPerSegment = 100;
constexpr const char* _loggerCat = "PathCurve";
constexpr const double LengthEpsilon =
100.0 * std::numeric_limits<double>::epsilon();
} // namespace
namespace openspace::interaction {
PathCurve::InsufficientPrecisionError::InsufficientPrecisionError(std::string error)
: ghoul::RuntimeError(std::move(error), "PathCurve")
{}
PathCurve::TooShortPathError::TooShortPathError(std::string error)
: ghoul::RuntimeError(std::move(error), "PathCurve")
{}
PathCurve::~PathCurve() {}
double PathCurve::length() const {
@@ -57,21 +69,19 @@ std::vector<glm::dvec3> PathCurve::points() const {
void PathCurve::initializeParameterData() {
_nSegments = static_cast<int>(_points.size() - 3);
ghoul_assert(_nSegments > 0, "Cannot have a curve with zero segments!");
_curveParameterSteps.clear();
_lengthSums.clear();
_parameterSamples.clear();
const double max = static_cast<double>(_nSegments);
// Evenly space out parameter intervals
_curveParameterSteps.reserve(_nSegments + 1);
const double dt = 1.0 / _nSegments;
_curveParameterSteps.push_back(0.0);
for (unsigned int i = 1; i < _nSegments; i++) {
_curveParameterSteps.push_back(dt * i);
for (int i = 0; i <= _nSegments; i++) {
_curveParameterSteps.push_back(static_cast<double>(i));
}
_curveParameterSteps.push_back(1.0);
// Arc lengths
_lengthSums.reserve(_nSegments + 1);
@@ -84,49 +94,82 @@ void PathCurve::initializeParameterData() {
}
_totalLength = _lengthSums.back();
if (_totalLength < LengthEpsilon) {
throw TooShortPathError("Path too short!");
}
// Compute a map of arc lengths s and curve parameters u, for reparameterization
_parameterSamples.reserve(NrSamplesPerSegment * _nSegments + 1);
const double uStep = 1.0 / (_nSegments * NrSamplesPerSegment);
constexpr const int Steps = 100;
const double uStep = 1.0 / static_cast<double>(Steps);
_parameterSamples.reserve(Steps * _nSegments + 1);
for (unsigned int i = 0; i < _nSegments; i++) {
double uStart = _curveParameterSteps[i];
double sStart = _lengthSums[i];
for (int j = 0; j < NrSamplesPerSegment; ++j) {
_parameterSamples.push_back({ uStart, sStart });
// Intermediate sampels
for (int j = 1; j < Steps; ++j) {
double u = uStart + j * uStep;
double s = sStart + arcLength(uStart, u);
// Identify samples that are indistinguishable due to precision limitations
if (std::abs(s - _parameterSamples.back().s) < LengthEpsilon) {
throw InsufficientPrecisionError("Insufficient precision due to path length");
}
_parameterSamples.push_back({ u, s });
}
}
_parameterSamples.push_back({ 1.0, _totalLength });
// Remove the very last sample if indistinguishable from the final one
const double diff = std::abs(_totalLength - _parameterSamples.back().s);
if (diff < LengthEpsilon) {
_parameterSamples.pop_back();
}
_parameterSamples.push_back({ max, _totalLength });
_parameterSamples.shrink_to_fit();
}
// Compute the curve parameter from an arc length value, using a combination of
// Newton's method and bisection. Source:
// https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
// Input s is a length value, in the range [0, _totalLength]
// Returns curve parameter in range [0, 1]
// Returns curve parameter in range [0, _nSegments]
double PathCurve::curveParameter(double s) const {
if (s <= 0.0) return 0.0;
if (s >= _totalLength) return 1.0;
if (s >= (_totalLength - LengthEpsilon)) return _curveParameterSteps.back();
unsigned int segmentIndex = 1;
while (s > _lengthSums[segmentIndex]) {
segmentIndex++;
}
const int startIndex = (segmentIndex - 1) * NrSamplesPerSegment;
const int endIndex = segmentIndex * NrSamplesPerSegment + 1;
const double segmentS = s - _lengthSums[segmentIndex - 1];
const double uMin = _curveParameterSteps[segmentIndex - 1];
const double uMax = _curveParameterSteps[segmentIndex];
// Find sample indices corresponding to the selected sample
auto findIndexOfFirstEqualOrLarger_u = [&samples = _parameterSamples](double value) {
auto it = std::lower_bound(
samples.begin(),
samples.end(),
ParameterPair{ value, 0.0 }, // 0.0 is a dummy value for s
[](const ParameterPair& lhs, const ParameterPair& rhs) {
return lhs.u < rhs.u;
}
);
return std::distance(samples.begin(), it);
};
size_t startIndex = findIndexOfFirstEqualOrLarger_u(uMin);
size_t endIndex = findIndexOfFirstEqualOrLarger_u(uMax);
// Use samples to find an initial guess for Newton's method
// Find first sample with s larger than input s
auto sampleIterator = std::upper_bound(
_parameterSamples.begin() + startIndex,
_parameterSamples.begin() + endIndex,
ParameterPair{ 0.0 , s }, // 0.0 is a dummy value for u
[](const ParameterPair& lhs,const ParameterPair& rhs) {
[](const ParameterPair& lhs, const ParameterPair& rhs) {
return lhs.s < rhs.s;
}
);
@@ -148,7 +191,7 @@ double PathCurve::curveParameter(double s) const {
double F = arcLength(uMin, u) - segmentS;
// The error we tolerate, in meters. Note that distances are very large
constexpr const double tolerance = 0.005;
constexpr const double tolerance = 0.5;
if (std::abs(F) <= tolerance) {
return u;
}
@@ -174,11 +217,12 @@ double PathCurve::curveParameter(double s) const {
}
double PathCurve::approximatedDerivative(double u, double h) const {
const double max = _curveParameterSteps.back();
if (u <= h) {
return (1.0 / h) * glm::length(interpolate(0.0 + h) - interpolate(0.0));
}
if (u >= 1.0 - h) {
return (1.0 / h) * glm::length(interpolate(1.0) - interpolate(1.0 - h));
if (u >= max - h) {
return (1.0 / h) * glm::length(interpolate(max) - interpolate(max - h));
}
return (0.5 / h) * glm::length(interpolate(u + h) - interpolate(u - h));
}
@@ -196,12 +240,13 @@ double PathCurve::arcLength(double lowerLimit, double upperLimit) const {
}
glm::dvec3 PathCurve::interpolate(double u) const {
ghoul_assert(u >= 0 && u <= 1.0, "Interpolation variable must be in range [0,1]");
const double max = _curveParameterSteps.back();
ghoul_assert(u >= 0 && u <= max, "Interpolation variable must be in range [0,_nSegments]");
if (u < 0.0) {
if (u <= 0.0) {
return _points[1];
}
if (u > 1.0) {
if (u >= max) {
return *(_points.end() - 2);
}
@@ -226,11 +271,26 @@ glm::dvec3 PathCurve::interpolate(double u) const {
}
LinearCurve::LinearCurve(const Waypoint& start, const Waypoint& end) {
_points.push_back(start.position());
_points.push_back(start.position());
_points.push_back(end.position());
_points.push_back(end.position());
initializeParameterData();
_totalLength = glm::distance(start.position(), end.position());
_nSegments = 1;
}
glm::dvec3 LinearCurve::positionAt(double relativeDistance) const {
return interpolate(relativeDistance);
}
glm::dvec3 LinearCurve::interpolate(double u) const {
if (u <= 0.0) {
return _points.front();
}
if (u >= 1.0) {
return _points.back();
}
return ghoul::interpolateLinear(u, _points.front(), _points.back());
}
} // namespace openspace::interaction

View File

@@ -70,10 +70,18 @@ AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint&
// Add an extra point to first go backwards if starting close to planet
glm::dvec3 nodeToStart = start.position() - startNodeCenter;
double distanceToStartNode = glm::length(nodeToStart);
const double distanceToStartNode = glm::length(nodeToStart);
if (distanceToStartNode < CloseToNodeThresholdRadiusMultiplier * startNodeRadius) {
double distance = startNodeRadius;
// Note that the factor 2.0 is arbitrarily chosen to look ok.
// @TODO: (2022-02-27, emmbr) Should be unified to a "getting close to object sphere"
// that can be used in multiple cases when creating paths more cleverly later on
const double closeToNodeThresholdFactor = glm::max(
CloseToNodeThresholdRadiusMultiplier,
2.0 * global::navigationHandler->pathNavigator().arrivalDistanceFactor()
);
if (distanceToStartNode < closeToNodeThresholdFactor * startNodeRadius) {
const double distance = startNodeRadius;
glm::dvec3 newPos = start.position() + distance * glm::normalize(nodeToStart);
_points.push_back(newPos);
}
@@ -104,8 +112,8 @@ AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint&
const glm::dvec3 nodeToEnd = end.position() - endNodeCenter;
const double distanceToEndNode = glm::length(nodeToEnd);
if (distanceToEndNode < CloseToNodeThresholdRadiusMultiplier * endNodeRadius) {
double distance = endNodeRadius;
if (distanceToEndNode < closeToNodeThresholdFactor * endNodeRadius) {
const double distance = endNodeRadius;
glm::dvec3 newPos = end.position() + distance * glm::normalize(nodeToEnd);
_points.push_back(newPos);
}

View File

@@ -45,10 +45,9 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo DefaultCurveOptionInfo = {
"DefaultPathType",
"Default Path Type",
"The defualt path type chosen when generating a path. See wiki for alternatives."
" The shape of the generated path will be different depending on the path type."
// TODO (2021-08-15, emmbr) right now there is no way to specify a type for a
// single path instance, only for any created paths
"The default path type chosen when generating a path or flying to a target. "
"See wiki for alternatives. The shape of the generated path will be different "
"depending on the path type."
};
constexpr openspace::properties::Property::PropertyInfo IncludeRollInfo = {
@@ -72,6 +71,15 @@ namespace {
"triggered once the path has reached its target."
};
constexpr openspace::properties::Property::PropertyInfo ArrivalDistanceFactorInfo = {
"ArrivalDistanceFactor",
"Arrival Distance Factor",
"A factor used to compute the default distance from a target scene graph node "
"when creating a camera path. The factor will be multipled with the node's "
"bounding sphere to compute the target height from the bounding sphere of the "
"object."
};
constexpr const openspace::properties::Property::PropertyInfo MinBoundingSphereInfo =
{
"MinimalValidBoundingSphere",
@@ -102,13 +110,14 @@ PathNavigator::PathNavigator()
, _includeRoll(IncludeRollInfo, false)
, _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f)
, _applyIdleBehaviorOnFinish(IdleBehaviorOnFinishInfo, false)
, _arrivalDistanceFactor(ArrivalDistanceFactorInfo, 2.0, 0.1, 20.0)
, _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10)
, _relevantNodeTags(RelevantNodeTagsInfo)
{
_defaultPathType.addOptions({
{ Path::Type::AvoidCollision, "AvoidCollision" },
{ Path::Type::Linear, "Linear" },
{ Path::Type::ZoomOutOverview, "ZoomOutOverview"},
{ Path::Type::Linear, "Linear" },
{ Path::Type::AvoidCollisionWithLookAt, "AvoidCollisionWithLookAt"}
});
addProperty(_defaultPathType);
@@ -116,6 +125,7 @@ PathNavigator::PathNavigator()
addProperty(_includeRoll);
addProperty(_speedScale);
addProperty(_applyIdleBehaviorOnFinish);
addProperty(_arrivalDistanceFactor);
addProperty(_minValidBoundingSphere);
_relevantNodeTags = std::vector<std::string>{
@@ -144,6 +154,10 @@ double PathNavigator::speedScale() const {
return _speedScale;
}
double PathNavigator::arrivalDistanceFactor() const {
return _arrivalDistanceFactor;
}
bool PathNavigator::hasCurrentPath() const {
return _currentPath != nullptr;
}
@@ -191,7 +205,7 @@ void PathNavigator::updateCamera(double deltaTime) {
camera()->setPose(newPose);
if (_currentPath->hasReachedEnd()) {
LINFO("Reached end of path");
LINFO("Reached target");
handlePathEnd();
if (_applyIdleBehaviorOnFinish) {
@@ -211,10 +225,6 @@ void PathNavigator::updateCamera(double deltaTime) {
}
void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
// @TODO (2021-08.16, emmbr): Improve how curve types are handled.
// We want the user to be able to choose easily
const int pathType = _defaultPathType;
OpenSpaceEngine::Mode m = global::openSpaceEngine->currentMode();
if (m == OpenSpaceEngine::Mode::SessionRecordingPlayback) {
// Silently ignore any paths that are being created during a session recording
@@ -225,9 +235,7 @@ void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
clearPath();
try {
_currentPath = std::make_unique<Path>(
createPathFromDictionary(dictionary, Path::Type(pathType))
);
_currentPath = std::make_unique<Path>(createPathFromDictionary(dictionary));
}
catch (const documentation::SpecificationError& e) {
LERROR("Could not create camera path");
@@ -238,16 +246,21 @@ void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
LWARNINGC(w.offender, ghoul::to_string(w.reason));
}
}
catch (const PathCurve::TooShortPathError&) {
// Do nothing
}
catch (const ghoul::RuntimeError& e) {
LERROR(fmt::format("Could not create path. Reason: ", e.message));
return;
}
LINFO("Successfully generated camera path");
LDEBUG("Successfully generated camera path");
}
void PathNavigator::clearPath() {
LDEBUG("Clearing path");
if (_currentPath) {
LDEBUG("Clearing path");
}
_currentPath = nullptr;
}
@@ -272,16 +285,14 @@ void PathNavigator::startPath() {
);
_startSimulationTimeOnFinish = true;
LINFO(
"Pausing time simulation during path traversal. "
"Will unpause once the camera path is finished"
);
LINFO("Pausing time simulation during path traversal");
}
LINFO("Starting path");
_isPlaying = true;
global::navigationHandler->orbitalNavigator().updateOnCameraInteraction();
global::navigationHandler->orbitalNavigator().resetVelocities();
}
void PathNavigator::abortPath() {
@@ -323,10 +334,58 @@ void PathNavigator::continuePath() {
_isPlaying = true;
}
Path::Type PathNavigator::defaultPathType() const {
const int pathType = _defaultPathType;
return Path::Type(pathType);
}
double PathNavigator::minValidBoundingSphere() const {
return _minValidBoundingSphere;
}
double PathNavigator::findValidBoundingSphere(const SceneGraphNode* node) const {
ghoul_assert(node != nullptr, "Node must not be nulltpr");
auto sphere = [](const SceneGraphNode* n) {
// Use the biggest of the bounding sphere and interaction sphere,
// so we don't accidentally choose a bounding sphere that is much smaller
// than the interaction sphere of the node
double bs = n->boundingSphere();
double is = n->interactionSphere();
return is > bs ? is : bs;
};
double result = sphere(node);
if (result < _minValidBoundingSphere) {
// If the bs of the target is too small, try to find a good value in a child node.
// Only check the closest children, to avoid deep traversal in the scene graph.
// Alsp. the chance to find a bounding sphere that represents the visual size of
// the target well is higher for these nodes
for (SceneGraphNode* child : node->children()) {
result = sphere(child);
if (result > _minValidBoundingSphere) {
LDEBUG(fmt::format(
"The scene graph node '{}' has no, or a very small, bounding sphere. "
"Using bounding sphere of child node '{}' in computations.",
node->identifier(),
child->identifier()
));
return result;
}
}
LDEBUG(fmt::format(
"The scene graph node '{}' has no, or a very small, bounding sphere. Using "
"minimal value. This might lead to unexpected results.",
node->identifier())
);
result = _minValidBoundingSphere;
}
return result;
}
const std::vector<SceneGraphNode*>& PathNavigator::relevantNodes() {
if (!_hasInitializedRelevantNodes) {
findRelevantNodes();
@@ -346,7 +405,6 @@ void PathNavigator::handlePathEnd() {
);
}
_startSimulationTimeOnFinish = false;
clearPath();
global::openSpaceEngine->resetMode();
}
@@ -438,9 +496,9 @@ scripting::LuaLibrary PathNavigator::luaLibrary() {
&luascriptfunctions::flyTo,
"string [, bool, double]",
"Move the camera to the node with the specified identifier. The optional "
"double specifies the duration of the motion. If the optional bool is "
"set to true the target up vector for camera is set based on the target "
"node. Either of the optional parameters can be left out."
"double specifies the duration of the motion, in seconds. If the optional "
"bool is set to true the target up vector for camera is set based on the "
"target node. Either of the optional parameters can be left out."
},
{
"flyToHeight",
@@ -449,18 +507,48 @@ scripting::LuaLibrary PathNavigator::luaLibrary() {
"Move the camera to the node with the specified identifier. The second "
"argument is the desired target height above the target node's bounding "
"sphere, in meters. The optional double specifies the duration of the "
"motion. If the optional bool is set to true, the target up vector for "
"camera is set based on the target node. Either of the optional "
"parameters can be left out."
"motion, in seconds. If the optional bool is set to true, the target "
"up vector for camera is set based on the target node. Either of the "
"optional parameters can be left out."
},
{
{
"flyToNavigationState",
&luascriptfunctions::flyToNavigationState,
"table, [double]",
"Create a path to the navigation state described by the input table. "
"The optional double specifies the target duration of the motion. Note "
"that roll must be included for the target up direction to be taken "
"into account."
"The optional double specifies the target duration of the motion, "
"in seconds. Note that roll must be included for the target up "
"direction to be taken into account."
},
{
"zoomToFocus",
&luascriptfunctions::zoomToFocus,
"[duration]",
"Zoom linearly to the current focus node, using the default distance."
"The optional input parameter specifies the duration for the motion, "
"in seconds."
},
{
"zoomToDistance",
&luascriptfunctions::zoomToDistance,
"distance, [duration]",
"Fly linearly to a specific distance in relation to the focus node. "
"The distance is given in meters above the bounding sphere of the "
"current focus node."
"The optional input parameter specifies the duration for the motion, "
"in seconds."
},
{
"zoomToDistanceRelative",
&luascriptfunctions::zoomToDistanceRelative,
"distance, [duration]",
"Fly linearly to a specific distance in relation to the focus node. "
"The distance is given as a multiple of the bounding sphere of the "
"current focus node. That is, a value of 1 will result in a position "
"at a distance of one times the size of the bounding sphere away from "
"the object."
"The optional input parameter specifies the duration for the motion, "
"in seconds."
},
{
"createPath",

View File

@@ -37,10 +37,6 @@
#include <ghoul/logging/logmanager.h>
#include <glm/gtx/vector_angle.hpp>
namespace {
constexpr const double Epsilon = 1e-5;
} // namespace
namespace openspace::luascriptfunctions {
int isFlying(lua_State* L) {
@@ -81,7 +77,6 @@ int flyTo(lua_State* L) {
return ghoul::lua::luaError(L, "Duration cannot be specified twice");
}
if (!sceneGraphNode(nodeIdentifier)) {
return ghoul::lua::luaError(L, "Unknown node name: " + nodeIdentifier);
}
@@ -98,7 +93,7 @@ int flyTo(lua_State* L) {
}
else {
double d = std::get<double>(*useUpFromTargetOrDuration);
if (d <= Epsilon) {
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
@@ -106,7 +101,7 @@ int flyTo(lua_State* L) {
}
if (duration.has_value()) {
double d = *duration;
if (d <= Epsilon) {
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
@@ -146,7 +141,7 @@ int flyToHeight(lua_State* L) {
}
else {
double d = std::get<double>(*useUpFromTargetOrDuration);
if (d <= Epsilon) {
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
@@ -154,7 +149,7 @@ int flyToHeight(lua_State* L) {
}
if (duration.has_value()) {
double d = *duration;
if (d <= Epsilon) {
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
@@ -194,7 +189,7 @@ int flyToNavigationState(lua_State* L) {
if (duration.has_value()) {
double d = *duration;
if (d <= Epsilon) {
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
instruction.setValue("Duration", d);
@@ -208,6 +203,113 @@ int flyToNavigationState(lua_State* L) {
return 0;
}
int zoomToFocus(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, { 0, 1 }, "lua::zoomToFocus");
std::optional<double> duration = ghoul::lua::value<std::optional<double>>(L);
const SceneGraphNode* node = global::navigationHandler->anchorNode();
if (!node) {
return ghoul::lua::luaError(L, "Could not determine current focus node");
}
ghoul::Dictionary insDict;
insDict.setValue("TargetType", std::string("Node"));
insDict.setValue("Target", node->identifier());
insDict.setValue("PathType", std::string("Linear"));
if (duration.has_value()) {
double d = *duration;
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
}
global::navigationHandler->pathNavigator().createPath(insDict);
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
global::navigationHandler->pathNavigator().startPath();
}
return 0;
}
int zoomToDistance(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, { 1, 2 }, "lua::zoomToDistance");
auto [distance, duration] =
ghoul::lua::values<double, std::optional<double>>(L);
if (distance <= 0.0) {
return ghoul::lua::luaError(L, "The distance must be larger than zero");
}
const SceneGraphNode* node = global::navigationHandler->anchorNode();
if (!node) {
return ghoul::lua::luaError(L, "Could not determine current focus node");
}
ghoul::Dictionary insDict;
insDict.setValue("TargetType", std::string("Node"));
insDict.setValue("Target", node->identifier());
insDict.setValue("Height", distance);
insDict.setValue("PathType", std::string("Linear"));
if (duration.has_value()) {
double d = *duration;
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
}
global::navigationHandler->pathNavigator().createPath(insDict);
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
global::navigationHandler->pathNavigator().startPath();
}
return 0;
}
int zoomToDistanceRelative(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, { 1, 2 }, "lua::zoomToDistanceRelative");
auto [distance, duration] =
ghoul::lua::values<double, std::optional<double>>(L);
if (distance <= 0.0) {
return ghoul::lua::luaError(L, "The distance must be larger than zero");
}
const SceneGraphNode* node = global::navigationHandler->anchorNode();
if (!node) {
return ghoul::lua::luaError(L, "Could not determine current focus node");
}
distance *= node->boundingSphere();
ghoul::Dictionary insDict;
insDict.setValue("TargetType", std::string("Node"));
insDict.setValue("Target", node->identifier());
insDict.setValue("Height", distance);
insDict.setValue("PathType", std::string("Linear"));
if (duration.has_value()) {
double d = *duration;
if (d <= 0.0) {
return ghoul::lua::luaError(L, "Duration must be larger than zero");
}
insDict.setValue("Duration", d);
}
global::navigationHandler->pathNavigator().createPath(insDict);
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
global::navigationHandler->pathNavigator().startPath();
}
return 0;
}
int createPath(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::createPath");
ghoul::Dictionary dictionary = ghoul::lua::value<ghoul::Dictionary>(L);

View File

@@ -49,7 +49,9 @@ Waypoint::Waypoint(const glm::dvec3& pos, const glm::dquat& rot, const std::stri
LERROR(fmt::format("Could not find node '{}'", _nodeIdentifier));
return;
}
_validBoundingSphere = findValidBoundingSphere(node);
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
_validBoundingSphere = navigator.findValidBoundingSphere(node);
}
Waypoint::Waypoint(const NavigationState& ns) {
@@ -61,7 +63,8 @@ Waypoint::Waypoint(const NavigationState& ns) {
}
_nodeIdentifier = ns.anchor;
_validBoundingSphere = findValidBoundingSphere(anchorNode);
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
_validBoundingSphere = navigator.findValidBoundingSphere(anchorNode);
_pose = ns.cameraPose();
}
@@ -89,41 +92,4 @@ double Waypoint::validBoundingSphere() const {
return _validBoundingSphere;
}
double Waypoint::findValidBoundingSphere(const SceneGraphNode* node) {
double bs = node->boundingSphere();
const double minValidBoundingSphere =
global::navigationHandler->pathNavigator().minValidBoundingSphere();
if (bs < minValidBoundingSphere) {
// If the bs of the target is too small, try to find a good value in a child node.
// Only check the closest children, to avoid deep traversal in the scene graph.
// Alsp. the chance to find a bounding sphere that represents the visual size of
// the target well is higher for these nodes
for (SceneGraphNode* child : node->children()) {
bs = child->boundingSphere();
if (bs > minValidBoundingSphere) {
LWARNING(fmt::format(
"The scene graph node '{}' has no, or a very small, bounding sphere. "
"Using bounding sphere of child node '{}' in computations.",
node->identifier(),
child->identifier()
));
return bs;
}
}
LWARNING(fmt::format(
"The scene graph node '{}' has no, or a very small, bounding sphere. Using "
"minimal value. This might lead to unexpected results.",
node->identifier())
);
bs = minValidBoundingSphere;
}
return bs;
}
} // namespace openspace::interaction

View File

@@ -100,6 +100,13 @@ void MultiThreadedSceneInitializer::initializeNode(SceneGraphNode* node) {
}
std::vector<SceneGraphNode*> MultiThreadedSceneInitializer::takeInitializedNodes() {
// Some of the scene graph nodes might still be in the initialization queue and we
// should wait for those to finish or we end up in some half-initialized state since
// other parts of the application already know about their existence
while (_threadPool.hasOutstandingTasks()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::lock_guard g(_mutex);
std::vector<SceneGraphNode*> nodes = std::move(_initializedNodes);
return nodes;

View File

@@ -99,4 +99,8 @@ void ThreadPool::clearTasks() {
} // release lock
}
bool ThreadPool::hasOutstandingTasks() const {
return !tasks.empty();
}
} // namespace openspace