mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-07 04:00:37 -06:00
Merge remote-tracking branch 'origin/master' into thesis/2021/skybrowser
# Conflicts: # data/assets/util/webgui.asset
This commit is contained in:
@@ -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
60
Jenkinsfile
vendored
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
Submodule apps/OpenSpace/ext/sgct updated: 9289476ad3...3befe6c7a0
@@ -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");
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. ]],
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) multi‐spectral 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).]],
|
||||
}
|
||||
|
||||
@@ -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 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).]],
|
||||
(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()
|
||||
|
||||
@@ -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 camera’s 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,
|
||||
we’ve 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.]],
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
void enqueue(std::function<void()> f);
|
||||
void clearTasks();
|
||||
|
||||
bool hasOutstandingTasks() const;
|
||||
|
||||
private:
|
||||
friend class Worker;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
55
modules/server/include/topics/enginemodetopic.h
Normal file
55
modules/server/include/topics/enginemodetopic.h
Normal 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__
|
||||
@@ -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);
|
||||
|
||||
118
modules/server/src/topics/enginemodetopic.cpp
Normal file
118
modules/server/src/topics/enginemodetopic.cpp
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": {]])
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -99,4 +99,8 @@ void ThreadPool::clearTasks() {
|
||||
} // release lock
|
||||
}
|
||||
|
||||
bool ThreadPool::hasOutstandingTasks() const {
|
||||
return !tasks.empty();
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
Submodule support/coding/codegen updated: ad3aa1e5e1...f511a93fdd
Reference in New Issue
Block a user