diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index 6d1e775849..200affb7d7 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -220,8 +221,17 @@ QWidget* LauncherWindow::createCentralWidget() { connect( startButton, &QPushButton::released, [this]() { - _shouldLaunch = true; - close(); + if (_profileBox->currentText().isEmpty()) { + QMessageBox::critical( + this, + "Empty Profile", + "Cannot launch with an empty profile" + ); + } + else { + _shouldLaunch = true; + close(); + } } ); startButton->setObjectName("large"); @@ -301,6 +311,13 @@ void LauncherWindow::populateProfilesList(std::string preset) { _profileBox->clear(); + if (!std::filesystem::exists(_profilePath)) { + LINFOC( + "LauncherWindow", + fmt::format("Could not find profile folder '{}'", _profilePath) + ); + return; + } // Add all the files with the .profile extension to the dropdown for (const fs::directory_entry& p : fs::directory_iterator(_profilePath)) { if (p.path().extension() != ".profile") { @@ -321,12 +338,20 @@ void LauncherWindow::populateWindowConfigsList(std::string preset) { namespace fs = std::filesystem; _windowConfigBox->clear(); - // Add all the files with the .xml extension to the dropdown - for (const fs::directory_entry& p : fs::directory_iterator(_configPath)) { - if (p.path().extension() != ".xml") { - continue; + if (std::filesystem::exists(_configPath)) { + // Add all the files with the .xml extension to the dropdown + for (const fs::directory_entry& p : fs::directory_iterator(_configPath)) { + if (p.path().extension() != ".xml") { + continue; + } + _windowConfigBox->addItem(QString::fromStdString(p.path().stem().string())); } - _windowConfigBox->addItem(QString::fromStdString(p.path().stem().string())); + } + else { + LINFOC( + "LauncherWindow", + fmt::format("Could not find config folder '{}'", _configPath) + ); } // Try to find the requested configuration file and set it as the current one. As we diff --git a/apps/OpenSpace/ext/sgct b/apps/OpenSpace/ext/sgct index cef10f55bd..6c351ebf2e 160000 --- a/apps/OpenSpace/ext/sgct +++ b/apps/OpenSpace/ext/sgct @@ -1 +1 @@ -Subproject commit cef10f55bda2c683a936159e97c43a9104c03fb1 +Subproject commit 6c351ebf2e7a1106982502f4050363f31ac3d0a3 diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index c41a94245f..ec1d62ec3f 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -925,6 +925,9 @@ void setSgctDelegateFunctions() { return currentWindow->swapGroupFrameNumber(); }; + sgctDelegate.setScreenshotFolder = [](std::string path) { + Settings::instance().setCapturePath(std::move(path)); + }; } void checkCommandLineForSettings(int& argc, char** argv, bool& hasSGCT, bool& hasProfile, @@ -1189,6 +1192,11 @@ int main(int argc, char** argv) { xmlExt ); } + if (global::configuration->profile.empty()) { + LFATAL("Cannot launch with an empty profile"); + exit(EXIT_FAILURE); + } + // Prepend the outgoing sgctArguments with the program name // as well as the configuration file that sgct is supposed to use diff --git a/data/assets/scene/digitaluniverse/stars.asset b/data/assets/scene/digitaluniverse/stars.asset index 7328d0fae4..e269202667 100644 --- a/data/assets/scene/digitaluniverse/stars.asset +++ b/data/assets/scene/digitaluniverse/stars.asset @@ -66,7 +66,8 @@ local sunstar = { MagnitudeExponent = 6.2, SizeComposition = "Distance Modulus", RenderMethod = "Texture Based", -- or PSF - FadeInDistances = { 0.0001, 0.1 } + FadeInDistances = { 0.0001, 0.1 }, + RenderableType = "PostDeferredTransparent" }, GUI = { Name = "Sun", diff --git a/data/profiles/asteroids.profile b/data/profiles/asteroids.profile index 70bbb0af63..8e6cb40535 100644 --- a/data/profiles/asteroids.profile +++ b/data/profiles/asteroids.profile @@ -1,6 +1,8 @@ { "assets": [ "base", + "scene/solarsystem/interstellar/c-2019_q4_borisov", + "scene/solarsystem/interstellar/oumuamua", "scene/solarsystem/sssb/amor_asteroid", "scene/solarsystem/sssb/apollo_asteroid", "scene/solarsystem/sssb/aten_asteroid", diff --git a/ext/ghoul b/ext/ghoul index 7814e7d3be..68af9b6971 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit 7814e7d3be623a229ce3fb95754f0e8d2923c958 +Subproject commit 68af9b6971e1e6447fa63afe3ea9918bb0e5c6d3 diff --git a/include/openspace/engine/windowdelegate.h b/include/openspace/engine/windowdelegate.h index 0fd27ba589..b5b978c8dc 100644 --- a/include/openspace/engine/windowdelegate.h +++ b/include/openspace/engine/windowdelegate.h @@ -100,6 +100,8 @@ struct WindowDelegate { Frustum (*frustumMode)() = []() { return Frustum::Mono; }; uint64_t (*swapGroupFrameNumber)() = []() { return uint64_t(0); }; + + void (*setScreenshotFolder)(std::string) = [](std::string) {}; }; } // namespace openspace diff --git a/include/openspace/interaction/navigationhandler.h b/include/openspace/interaction/navigationhandler.h index 24d3ef495b..600456a50d 100644 --- a/include/openspace/interaction/navigationhandler.h +++ b/include/openspace/interaction/navigationhandler.h @@ -166,7 +166,8 @@ private: std::optional _pendingNavigationState; - properties::BoolProperty _disableInputs; + properties::BoolProperty _disableMouseInputs; + properties::BoolProperty _disableJoystickInputs; properties::BoolProperty _useKeyFrameInteraction; }; diff --git a/include/openspace/interaction/orbitalnavigator.h b/include/openspace/interaction/orbitalnavigator.h index e541ab23fd..eec6daf2fb 100644 --- a/include/openspace/interaction/orbitalnavigator.h +++ b/include/openspace/interaction/orbitalnavigator.h @@ -268,7 +268,7 @@ private: */ glm::dvec3 moveCameraAlongVector(const glm::dvec3& camPos, double distFromCameraToFocus, const glm::dvec3& camPosToCenterPosDiff, - double destination) const; + double destination, double deltaTime) const; /* * Adds rotation to the camera position so that it follows the rotation of the anchor diff --git a/include/openspace/rendering/renderengine.h b/include/openspace/rendering/renderengine.h index 08209e3ac8..59b03d02b0 100644 --- a/include/openspace/rendering/renderengine.h +++ b/include/openspace/rendering/renderengine.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace ghoul { namespace fontrendering { class Font; } @@ -229,6 +230,8 @@ private: properties::FloatProperty _saturation; properties::FloatProperty _value; + properties::IntProperty _framerateLimit; + std::chrono::high_resolution_clock::time_point _lastFrameTime; properties::FloatProperty _horizFieldOfView; properties::Vec3Property _globalRotation; diff --git a/modules/kameleon/ext/kameleon b/modules/kameleon/ext/kameleon index 1b4549edc7..338d482c86 160000 --- a/modules/kameleon/ext/kameleon +++ b/modules/kameleon/ext/kameleon @@ -1 +1 @@ -Subproject commit 1b4549edc74ef371730ef9d39c1ffa0efe90a985 +Subproject commit 338d482c8617bfacda0a5af8f3f6bb23163d436f diff --git a/modules/space/rendering/renderablesmallbody.cpp b/modules/space/rendering/renderablesmallbody.cpp index 1da9da54a7..24666a385f 100644 --- a/modules/space/rendering/renderablesmallbody.cpp +++ b/modules/space/rendering/renderablesmallbody.cpp @@ -399,6 +399,10 @@ void RenderableSmallBody::readOrbitalParamsFromThisLine(bool firstDataLine, } static double importAngleValue(const std::string& angle) { + if (angle.empty()) { + return 0.0; + } + double output = std::stod(angle); output = std::fmod(output, 360.0); if (output < 0.0) { diff --git a/modules/space/shaders/star_fs.glsl b/modules/space/shaders/star_fs.glsl index 46c6ebb4a0..6b98775e7a 100644 --- a/modules/space/shaders/star_fs.glsl +++ b/modules/space/shaders/star_fs.glsl @@ -101,7 +101,7 @@ Fragment getFragment() { vec4 fullColor = vec4(color.rgb, textureColor.a); fullColor.a *= alphaValue; - if (fullColor.a == 0) { + if (fullColor.a < 0.001) { discard; } diff --git a/openspace.cfg b/openspace.cfg index 4410c6c261..534ae46f9f 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -95,7 +95,11 @@ Paths = { PROFILES = "${DATA}/profiles", FONTS = "${DATA}/fonts", TASKS = "${DATA}/tasks", - SYNC = "${BASE}/sync", + -- If the the 'OPENSPACE_SYNC' environment variable is defined on the system, use that + -- value. Otherwise, fall back to the ${BASE}/sync folder instead. This allows a + -- reuse of the sync folder between multiple OpenSpace installations by simply setting + -- that environment variable + SYNC = os.getenv("OPENSPACE_SYNC") or "${BASE}/sync", SCREENSHOTS = "${BASE}/screenshots", WEB = "${DATA}/web", RECORDINGS = "${BASE}/recordings", diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 61442d56c8..edb6de88f3 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -1522,6 +1522,13 @@ scripting::LuaLibrary OpenSpaceEngine::luaLibrary() { "", "Remove all registered virtual properties" }, + { + "setScreenshotFolder", + &luascriptfunctions::setScreenshotFolder, + {}, + "string", + "Sets the folder used for storing screenshots or session recording frames" + }, { "addTag", &luascriptfunctions::addTag, diff --git a/src/engine/openspaceengine_lua.inl b/src/engine/openspaceengine_lua.inl index f92dad961b..e9e647296c 100644 --- a/src/engine/openspaceengine_lua.inl +++ b/src/engine/openspaceengine_lua.inl @@ -27,6 +27,7 @@ #include #include #include +#include namespace openspace::luascriptfunctions { @@ -182,6 +183,27 @@ int removeAllVirtualProperties(lua_State* L) { return 0; } +int setScreenshotFolder(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setScreenshotFolder"); + + std::string arg = ghoul::lua::value(L); + lua_pop(L, 0); + + std::string folder = FileSys.absolutePath(arg); + if (!std::filesystem::exists(folder)) { + std::filesystem::create_directory(folder); + } + + FileSys.registerPathToken( + "${SCREENSHOTS}", + folder, + ghoul::filesystem::FileSystem::Override::Yes + ); + + global::windowDelegate->setScreenshotFolder(folder); + return 0; +} + /** * \ingroup LuaScripts * addTag() diff --git a/src/interaction/navigationhandler.cpp b/src/interaction/navigationhandler.cpp index 548a0b9c3b..700dc8d06d 100644 --- a/src/interaction/navigationhandler.cpp +++ b/src/interaction/navigationhandler.cpp @@ -53,13 +53,20 @@ namespace { constexpr const char* KeyReferenceFrame = "ReferenceFrame"; const double Epsilon = 1E-7; - constexpr const openspace::properties::Property::PropertyInfo KeyDisableInputInfo = { - "DisableInputs", + using namespace openspace; + constexpr const properties::Property::PropertyInfo KeyDisableMouseInputInfo = { + "DisableMouseInputs", "Disable all mouse inputs", "Disables all mouse inputs and prevents them from affecting the camera" }; - constexpr const openspace::properties::Property::PropertyInfo KeyFrameInfo = { + constexpr const properties::Property::PropertyInfo KeyDisableJoystickInputInfo = { + "DisableJoystickInputs", + "Disable all joystick inputs", + "Disables all joystick inputs and prevents them from affecting the camera" + }; + + constexpr const properties::Property::PropertyInfo KeyFrameInfo = { "UseKeyFrameInteraction", "Use keyframe interaction", "If this is set to 'true' the entire interaction is based off key frames rather " @@ -148,12 +155,14 @@ NavigationHandler::NavigationState::NavigationState(std::string anchor, std::str NavigationHandler::NavigationHandler() : properties::PropertyOwner({ "NavigationHandler" }) - , _disableInputs(KeyDisableInputInfo, false) + , _disableMouseInputs(KeyDisableMouseInputInfo, false) + , _disableJoystickInputs(KeyDisableJoystickInputInfo, true) , _useKeyFrameInteraction(KeyFrameInfo, false) { addPropertySubOwner(_orbitalNavigator); - addProperty(_disableInputs); + addProperty(_disableMouseInputs); + addProperty(_disableJoystickInputs); addProperty(_useKeyFrameInteraction); } @@ -235,6 +244,13 @@ void NavigationHandler::updateCamera(double deltaTime) { _keyframeNavigator.updateCamera(*_camera, _playbackModeEnabled); } else { + if (_disableJoystickInputs) { + std::fill( + global::joystickInputStates->begin(), + global::joystickInputStates->end(), + JoystickInputState() + ); + } _orbitalNavigator.updateStatesFromInput(_inputState, deltaTime); _orbitalNavigator.updateCameraStateFromStates(deltaTime); } @@ -334,28 +350,28 @@ const InputState& NavigationHandler::inputState() const { } void NavigationHandler::mouseButtonCallback(MouseButton button, MouseAction action) { - if (!_disableInputs) { + if (!_disableMouseInputs) { _inputState.mouseButtonCallback(button, action); } } void NavigationHandler::mousePositionCallback(double x, double y) { - if (!_disableInputs) { + if (!_disableMouseInputs) { _inputState.mousePositionCallback(x, y); } } void NavigationHandler::mouseScrollWheelCallback(double pos) { - if (!_disableInputs) { + if (!_disableMouseInputs) { _inputState.mouseScrollWheelCallback(pos); } } void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { - if (!_disableInputs) { - _inputState.keyboardCallback(key, modifier, action); - } + // There is no need to disable the keyboard callback based on a property as the vast + // majority of input is coming through Lua scripts anyway which are not blocked here + _inputState.keyboardCallback(key, modifier, action); } NavigationHandler::NavigationState NavigationHandler::navigationState() const { diff --git a/src/interaction/orbitalnavigator.cpp b/src/interaction/orbitalnavigator.cpp index 3f923ac7e6..47ab0f642f 100644 --- a/src/interaction/orbitalnavigator.cpp +++ b/src/interaction/orbitalnavigator.cpp @@ -103,6 +103,13 @@ namespace { "the sensitivity is the less impact a mouse motion will have." }; + constexpr openspace::properties::Property::PropertyInfo JoystickEnabledInfo = { + "JoystickEnabled", + "Joystick Control Enabled", + "If this is selected, a connected joystick will be used as an optional input " + "device. If this is deselected, any joystick input will be ignored" + }; + constexpr openspace::properties::Property::PropertyInfo JoystickSensitivityInfo = { "JoystickSensitivity", "Joystick Sensitivity", @@ -249,7 +256,7 @@ OrbitalNavigator::OrbitalNavigator() , _flightDestinationDistance(FlightDestinationDistInfo, 2e8f, 0.0f, 1e10f) , _flightDestinationFactor(FlightDestinationFactorInfo, 1E-4, 1E-6, 0.5) , _applyLinearFlight(ApplyLinearFlightInfo, false) - , _velocitySensitivity(VelocityZoomControlInfo, 0.02f, 0.01f, 0.15f) + , _velocitySensitivity(VelocityZoomControlInfo, 3.5f, 0.001f, 20.f) , _mouseSensitivity(MouseSensitivityInfo, 15.f, 1.f, 50.f) , _joystickSensitivity(JoystickSensitivityInfo, 10.f, 1.0f, 50.f) , _websocketSensitivity(WebsocketSensitivityInfo, 5.f, 1.0f, 50.f) @@ -474,7 +481,8 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) { pose.position, distFromCameraToFocus, camPosToAnchorPosDiff, - _flightDestinationDistance + _flightDestinationDistance, + deltaTime ); } else { @@ -1261,7 +1269,8 @@ glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime, glm::dvec3 OrbitalNavigator::moveCameraAlongVector(const glm::dvec3& camPos, double distFromCameraToFocus, const glm::dvec3& camPosToAnchorPosDiff, - double destination) const + double destination, + double deltaTime) const { // This factor adapts the velocity so it slows down when getting closer // to our final destination @@ -1278,7 +1287,7 @@ glm::dvec3 OrbitalNavigator::moveCameraAlongVector(const glm::dvec3& camPos, velocity = distFromCameraToFocus / destination - 1.0; } } - velocity *= _velocitySensitivity; + velocity *= _velocitySensitivity * deltaTime; // Return the updated camera position return camPos - velocity * camPosToAnchorPosDiff; diff --git a/src/rendering/framebufferrenderer.cpp b/src/rendering/framebufferrenderer.cpp index aae263a0a5..028fd63fc7 100644 --- a/src/rendering/framebufferrenderer.cpp +++ b/src/rendering/framebufferrenderer.cpp @@ -73,6 +73,7 @@ namespace { } }; + constexpr const glm::vec4 PosBufferClearVal = { 1e32, 1e32, 1e32, 1.f }; constexpr const std::array HDRUniformNames = { "hdrFeedingTexture", "blackoutFactor", "hdrExposure", "gamma", @@ -1170,6 +1171,7 @@ void FramebufferRenderer::render(Scene* scene, Camera* camera, float blackoutFac glBindFramebuffer(GL_FRAMEBUFFER, _gBuffers.framebuffer); glDrawBuffers(3, ColorAttachmentArray); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClearBufferfv(GL_COLOR, 1, glm::value_ptr(PosBufferClearVal)); } Time time = global::timeManager->time(); diff --git a/src/rendering/luaconsole.cpp b/src/rendering/luaconsole.cpp index 104f637218..441abc583b 100644 --- a/src/rendering/luaconsole.cpp +++ b/src/rendering/luaconsole.cpp @@ -600,7 +600,9 @@ void LuaConsole::update() { // what the bounding box for that text would be. using namespace ghoul::fontrendering; - const float height = _historyFont->height() * (_commandsHistory.size() + 1); + const float height = + _historyFont->height() * + (std::min(static_cast(_commandsHistory.size()), _historyLength.value()) + 1); // Update the full height and the target height. // Add the height of the entry line and space for a separator. diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index b1ca205edc..c784d1fda3 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -230,6 +230,13 @@ namespace { "Value" }; + constexpr openspace::properties::Property::PropertyInfo FramerateLimitInfo = { + "FramerateLimit", + "Framerate Limit", + "If set to a value bigger than 0, the framerate will be limited to that many " + "frames per second without using V-Sync" + }; + constexpr openspace::properties::Property::PropertyInfo HorizFieldOfViewInfo = { "HorizFieldOfView", "Horizontal Field of View", @@ -276,6 +283,7 @@ RenderEngine::RenderEngine() , _hue(HueInfo, 0.f, 0.f, 360.f) , _saturation(SaturationInfo, 1.f, 0.0f, 2.f) , _value(ValueInfo, 1.f, 0.f, 2.f) + , _framerateLimit(FramerateLimitInfo, 0.f, 0.f, 500.f) , _horizFieldOfView(HorizFieldOfViewInfo, 80.f, 1.f, 179.f) , _globalRotation( GlobalRotationInfo, @@ -381,6 +389,7 @@ RenderEngine::RenderEngine() addProperty(_saveFrameInformation); #endif // OPENSPACE_WITH_INSTRUMENTATION + addProperty(_framerateLimit); addProperty(_globalRotation); addProperty(_screenSpaceRotation); addProperty(_masterRotation); @@ -676,6 +685,16 @@ void RenderEngine::render(const glm::mat4& sceneMatrix, const glm::mat4& viewMat _camera->invalidateCache(); } + const int fpsLimit = _framerateLimit; + if (fpsLimit > 0) { + // Using a sleep here is not optimal, but we are not looking for FPS-perfect + // limiting + std::this_thread::sleep_until(_lastFrameTime); + const double delta = (1.0 / fpsLimit) * 1000.0 * 1000.0; + auto now = std::chrono::high_resolution_clock::now(); + _lastFrameTime = now + std::chrono::microseconds(static_cast(delta)); + } + const bool masterEnabled = delegate.isMaster() ? !_disableMasterRendering : true; if (masterEnabled && !delegate.isGuiWindow() && _globalBlackOutFactor > 0.f) { _renderer->render(