Merge branch 'master' into thesis/2025/black-hole

This commit is contained in:
Wilhelm Björkström
2025-03-25 14:30:20 +01:00
13 changed files with 410 additions and 523 deletions

View File

@@ -62,18 +62,16 @@ public:
bool wasLaunchSelected() const;
/**
* Returns the selected profile name when launcher window closed.
* Returns the selected profile name when the launcher window closed.
*
* \return The name of selected profile (this is only the name without file extension
* and without path)
* \return The path to the selected profile
*/
std::string selectedProfile() const;
/**
* Returns the selected sgct window configuration when launcher window closed.
* Returns the selected SGCT window configuration when the launcher window closed.
*
* \return The name of selected profile (this is only the name without file extension
* and without path)
* \return The path to the selected profile
*/
std::string selectedWindowConfig() const;

View File

@@ -30,10 +30,10 @@
#include <openspace/scene/profile.h>
class QCheckBox;
class QComboBox;
class QDateTimeEdit;
class QLabel;
class QLineEdit;
class QTabWidget;
class TimeDialog final : public QDialog {
Q_OBJECT
@@ -48,18 +48,15 @@ public:
TimeDialog(QWidget* parent, std::optional<openspace::Profile::Time>* time);
private slots:
void enableAccordingToType(int);
void approved();
private:
void createWidgets();
void enableFormatForAbsolute(bool enableAbs);
std::optional<openspace::Profile::Time>* _time = nullptr;
openspace::Profile::Time _timeData;
bool _initializedAsAbsolute = true;
QComboBox* _typeCombo = nullptr;
QTabWidget* _tabWidget = nullptr;
QLabel* _absoluteLabel = nullptr;
QDateTimeEdit* _absoluteEdit = nullptr;
QLabel* _relativeLabel = nullptr;

View File

@@ -32,6 +32,7 @@
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QTabWidget>
#include <QVBoxLayout>
#include <format>
#include <algorithm>
@@ -45,8 +46,6 @@ TimeDialog::TimeDialog(QWidget* parent, std::optional<openspace::Profile::Time>*
setWindowTitle("Time");
createWidgets();
const QStringList types = { "Absolute", "Relative" };
_typeCombo->addItems(types);
if (_time->has_value()) {
_timeData = **_time;
if (_timeData.type == Profile::Time::Type::Relative) {
@@ -66,44 +65,75 @@ TimeDialog::TimeDialog(QWidget* parent, std::optional<openspace::Profile::Time>*
}
_startPaused->setChecked(_timeData.startPaused);
_initializedAsAbsolute = (_timeData.type == Profile::Time::Type::Absolute);
enableAccordingToType(static_cast<int>(_timeData.type));
if (_timeData.type == Profile::Time::Type::Relative) {
_relativeEdit->setText(QString::fromStdString(_timeData.value));
_relativeEdit->setFocus(Qt::OtherFocusReason);
}
else {
const size_t tIdx = _timeData.value.find_first_of('T', 0);
const QString importDate = QString::fromStdString(
_timeData.value.substr(0, tIdx)
);
const QString importTime = QString::fromStdString(
_timeData.value.substr(tIdx + 1)
);
_absoluteEdit->setDate(QDate::fromString(importDate, Qt::DateFormat::ISODate));
_absoluteEdit->setTime(QTime::fromString(importTime));
_relativeEdit->clear();
_absoluteEdit->setFocus(Qt::OtherFocusReason);
}
_tabWidget->setCurrentIndex(static_cast<int>(_timeData.type));
}
void TimeDialog::createWidgets() {
QBoxLayout* layout = new QVBoxLayout(this);
_tabWidget = new QTabWidget;
{
layout->addWidget(new QLabel("Time Type"));
_typeCombo = new QComboBox;
_typeCombo->setAccessibleName("Time type");
_typeCombo->setToolTip("Types: Absolute defined time or Relative to actual time");
connect(
_typeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &TimeDialog::enableAccordingToType
);
layout->addWidget(_typeCombo);
}
{
QWidget* container = new QWidget;
QBoxLayout* l = new QVBoxLayout(container);
_absoluteLabel = new QLabel("Absolute UTC:");
layout->addWidget(_absoluteLabel);
l->addWidget(_absoluteLabel);
_absoluteEdit = new QDateTimeEdit;
_absoluteEdit->setDisplayFormat("yyyy-MM-dd T hh:mm:ss");
_absoluteEdit->setDateTime(QDateTime::currentDateTime());
_absoluteEdit->setAccessibleName("Set absolute time");
layout->addWidget(_absoluteEdit);
l->addWidget(_absoluteEdit);
l->addStretch();
_tabWidget->addTab(container, "Absolute");
}
{
QWidget* container = new QWidget;
QBoxLayout* l = new QVBoxLayout(container);
_relativeLabel = new QLabel("Relative Time:");
layout->addWidget(_relativeLabel);
l->addWidget(_relativeLabel);
_relativeEdit = new QLineEdit;
_relativeEdit->setAccessibleName("Set relative time");
_relativeEdit->setToolTip(
"String for relative time to actual (e.g. \"-1d\" for back 1 day)"
);
layout->addWidget(_relativeEdit);
l->addWidget(_relativeEdit);
QLabel* desc = new QLabel(
"This field modifies the default start time. It has to be of the form "
"[-]XX(s,m,h,d,M,y). For example '-1d' will cause the profile to start at "
"yesterday's date."
);
desc->setObjectName("information");
desc->setWordWrap(true);
l->addWidget(desc);
_tabWidget->addTab(container, "Relative");
}
layout->addWidget(_tabWidget);
{
_startPaused = new QCheckBox("Start with time paused");
_startPaused->setChecked(false);
@@ -123,47 +153,9 @@ void TimeDialog::createWidgets() {
}
}
void TimeDialog::enableAccordingToType(int idx) {
const Profile::Time::Type comboIdx = static_cast<Profile::Time::Type>(idx);
const bool setFormatForAbsolute = (comboIdx == Profile::Time::Type::Absolute);
enableFormatForAbsolute(setFormatForAbsolute);
_typeCombo->setCurrentIndex(idx);
if (comboIdx == Profile::Time::Type::Relative) {
_relativeEdit->setText("<font color='black'>Relative Time:</font>");
if (_initializedAsAbsolute) {
_relativeEdit->setText("0d");
}
else {
_relativeEdit->setText(QString::fromStdString(_timeData.value));
}
_relativeEdit->setFocus(Qt::OtherFocusReason);
}
else {
_relativeEdit->setText("<font color='gray'>Relative Time:</font>");
const size_t tIdx = _timeData.value.find_first_of('T', 0);
const QString importDate = QString::fromStdString(
_timeData.value.substr(0, tIdx)
);
const QString importTime = QString::fromStdString(
_timeData.value.substr(tIdx + 1)
);
_absoluteEdit->setDate(QDate::fromString(importDate, Qt::DateFormat::ISODate));
_absoluteEdit->setTime(QTime::fromString(importTime));
_relativeEdit->clear();
_absoluteEdit->setFocus(Qt::OtherFocusReason);
}
}
void TimeDialog::enableFormatForAbsolute(bool enableAbs) {
_absoluteLabel->setEnabled(enableAbs);
_absoluteEdit->setEnabled(enableAbs);
_relativeLabel->setEnabled(!enableAbs);
_relativeEdit->setEnabled(!enableAbs);
}
void TimeDialog::approved() {
constexpr int Relative = static_cast<int>(Profile::Time::Type::Relative);
if (_typeCombo->currentIndex() == Relative) {
if (_tabWidget->currentIndex() == Relative) {
if (_relativeEdit->text().isEmpty()) {
*_time = std::nullopt;
}

View File

@@ -1415,9 +1415,39 @@ int main(int argc, char* argv[]) {
openspace::Settings settings = loadSettings();
settings.hasStartedBefore = true;
const std::filesystem::path p = global::configuration->profile;
const std::filesystem::path reducedName = p.filename().replace_extension();
settings.profile = reducedName.string();
const std::filesystem::path profile = global::configuration->profile;
const bool isDefaultProfile = ghoul::filesystem::isSubdirectory(
profile,
absPath("${PROFILES}")
);
const bool isUserProfile = ghoul::filesystem::isSubdirectory(
profile,
absPath("${USER_PROFILES}")
);
if (isDefaultProfile) {
std::filesystem::path p = std::filesystem::relative(
profile,
absPath("${PROFILES}")
);
p.replace_extension();
settings.profile = p.string();
}
else if (isUserProfile) {
std::filesystem::path p = std::filesystem::relative(
profile,
absPath("${USER_PROFILES}")
);
p.replace_extension();
settings.profile = p.string();
}
else {
LWARNING(
"Cannot save remembered profile when starting a profile that is not in "
"the data/profiles or user/data/profiles folder."
);
}
settings.configuration =
isGeneratedWindowConfig ? "" : global::configuration->windowConfiguration;

View File

@@ -77,6 +77,18 @@ public:
*/
void remove(const std::string& path);
/**
* Reloads the asset at the provided \p path as a new root asset of the AssetManager. If the
* asset at that path was not previously loaded, acts the same as the add function.
*
* \param path The path from which the Asset is loaded. This path can be either
* relative to the base directory (the path starting with . or ..), an absolute
* path (that path starting with *:/ or /) or relative to the global asset root
* (if the path starts any other way)
* \pre \p path must not be the empty string
*/
void reload(const std::string& path);
/**
* Update function that should be called at least once per frame that will load all
* queued asset loads and asset removal.
@@ -180,6 +192,9 @@ private:
std::filesystem::path generateAssetPath(const std::filesystem::path& baseDirectory,
const std::string& assetPath) const;
void runRemoveQueue();
void runAddQueue();
//
// Assets
//

View File

@@ -37,7 +37,7 @@ namespace {
namespace openspace::globebrowsing {
GeoJsonManager::GeoJsonManager()
: properties::PropertyOwner({ "GeoJson", "Geometry Overlays" })
: properties::PropertyOwner({ "GeographicOverlays", "Geographic Overlays" })
{}
void GeoJsonManager::initialize(RenderableGlobe* globe) {

View File

@@ -20,25 +20,15 @@ SGCTConfig = sgct.config.single{vsync=false}
-- Streaming OpenSpace via Spout to OBS
-- SGCTConfig = sgct.config.single{2560, 1440, shared=true, name="WV_OBS_SPOUT1"}
-- Elumenati Configs
-- SGCTConfig = "${CONFIG}/spout_output_flat.json"
-- SGCTConfig = "${CONFIG}/spout_output_cubemap.json"
-- SGCTConfig = "${CONFIG}/spout_output_equirectangular.json"
-- SGCTConfig = "${CONFIG}/spout_output_fisheye.json"
-- for more details about sgct configuration options see:
-- https://sgct.github.io/configuration-files.html
-- To use a sgct configuration file set the variable like below
-- SGCTConfig = "${CONFIG}/single_gui.xml"
-- SGCTConfig = "${CONFIG}/single_gui.json"
-- In the config/ folder we provide the some predefined files which you can modify.
-- fullscreen1080.json: fullscreen window at 1920x1080
-- gui_projector.json: one window for the gui and a second fullscreen window for rendering
-- on the second monitor
-- openvr_htcVive.json: window for VR on HTC Vive, only works if OpenSpace is compiled
-- custom with the openvr support
-- openvr_oculusRiftCv1.json: window for VR on Oculus Rift, only works if OpenSpace is
-- compiled custom with the openvr support
-- single.json: regular window
-- single_fisheye.json: regular window with fisheye rendering
-- single_fisheye_gui.json: one window for the gui, one window for fisheye rendering
@@ -50,14 +40,8 @@ SGCTConfig = sgct.config.single{vsync=false}
-- mirror rendering
-- two_nodes.json: a configuration for running two instances of openspace, used for
-- multiple projection systems
-- spout_output_flat.json: a window where the rendering is sent to spout instead of the
-- display
-- spout_output_cubemap.json: a window where the rendering is sent to spout (max of 6 spout
-- instances) instead of the display
-- spout_output_equirectangular.json: a window where the rendering is sent to spout
-- (equirectangular output) instead of the display
-- spout_output_fisheye.json: a window where the rendering is sent to spout (fisheye
-- output) instead of the display
-- Variable: Profile
-- Sets the profile that should be loaded by OpenSpace.
@@ -120,7 +104,7 @@ Paths = {
SCRIPTS = "${BASE}/scripts",
SHADERS = "${BASE}/shaders",
TEMPORARY = "${BASE}/temp",
GLOBEBROWSING = os.getenv("OPENSPACE_GLOBEBROWSING") or "${BASE}/../OpenSpaceData"
GLOBEBROWSING = os.getenv("OPENSPACE_GLOBEBROWSING") or "${USER}/globebrowsing"
}
ModuleConfigurations = {

View File

@@ -1,4 +1,8 @@
-- Helper functions that are useful to customize the openspace.cfg loading
-- Helper functions that are useful to customize the openspace.cfg loading. Only edit this
-- file if you know what you are doing. There are a few implicit variables that are passed
-- into this file and are available: `ScreenResolution` contains the size (in pixels) of
-- the main monitor. `TableToJson` is a function that receives a Lua table and returns the
-- string-representation of that table converted to JSON
-- #######################################################################################
-- ## Public functions ##
@@ -8,7 +12,7 @@
sgct = {}
sgct.config = {}
-- Creates a configuration file similar to the default 'single.xml':
-- Creates a configuration file similar to the default 'single.json':
-- The parameter is a table and can contain the follow attributes:
-- first argument: horizontal window size {default: 1280}
@@ -40,38 +44,6 @@ function sgct.config.single(arg) end
-- Creates a configuration file similar to the default 'single_fisheye.xml'
-- The parameter is a table and can contain the follow attributes:
-- first argument: horizontal window size {default: 1280}
-- second argument: vertical window size {default: 720}
-- res: A table containing the horizontal and vertical resolution [example: res={3840, 2160}]
-- pos: The position of the window on the screen [example: pos={50, 100}] {default: {50, 50}}
-- fullScreen: Whether the application should run in exclusive full screen [example: fullScreen=true] {default: false}
-- border: Whether the application should have window decorations (aka. border) [example: border=false] {default: true}
-- monitor: Determines the monitor on which the application is started [example: monitor=2] {default: 0}
-- 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: 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}
-- scene: Global settings to all scene objects (offset, orientation, scaling; each optional)
-- [example: scene = {offset = {x = 1.0, y = 1.0, z = 2.0}, orientation = { yaw = 120, pitch = 15, roll = 0.0 }, scale = 10.0}]
-- sgctDebug: Determines whether a higher debug level in SGCT is enabled [example: sgctDebug=true] {default: false}
-- fov: The field of view for the fisheye [example: fov=360] {default: 180}
-- quality: The quality setting for the cubemap textures [example: quality="4k"] {default: "1k"}
-- tilt: The forwards tilt of the fisheye, relative to the center [example: tilt=90] {default: 0.0}
-- background: The background color used outside of the fisheye rendering [example: backgruound={r=1.0, g=0.25, b=0.25, a=1.0}] {default: {r=0.1, g=0.1, b=0.1, a=1.0}}
-- Thus this function can be called the following ways:
-- sgct.config.fisheye() -> Leading to a 1280x720 resolution window
-- sgct.config.fisheye(640, 640) -> Leading to a 640x650 resolution window
-- sgct.config.fisheye(640, 360, res={3840, 3840}) -> 640x360 window with 4K rendering resolution
-- sgct.config.fisheye(msaa=1) -> 1280x720 resolution without multisampling
function sgct.config.fisheye(arg) end
-- Global variable storing the name of the config function called at initialization
sgctconfiginitializeString = ""
@@ -84,233 +56,6 @@ sgctconfiginitializeString = ""
##########################################################################################
]]--
function generateSingleViewportFOV(down, up, left, right, tracked)
local result = {}
table.insert(result, [[ {]])
if tracked ~= nil then
table.insert(result, [[ "tracked": ]] .. tostring(tracked) .. [[,]])
end
table.insert(result, [[ "projection": {]])
table.insert(result, [[ "type": "PlanarProjection",]])
table.insert(result, [[ "fov": {]])
table.insert(result, [[ "left": ]] .. left .. [[,]])
table.insert(result, [[ "right": ]] .. right .. [[,]])
table.insert(result, [[ "up": ]] .. up .. [[,]])
table.insert(result, [[ "down": ]] .. down)
table.insert(result, [[ }]])
table.insert(result, [[ }]])
table.insert(result, [[ }]])
return table.concat(result, "\n")
end
function generateFisheyeViewport(fov, quality, tilt, background, crop, offset, tracked)
local result = {}
table.insert(result, [[ {]])
if tracked ~= nil then
table.insert(result, [[ "tracked": ]] .. tostring(tracked) .. [[,]])
end
table.insert(result, [[ "projection": {]])
table.insert(result, [[ "type": "FisheyeProjection",]])
if fov then
table.insert(result, [[ "fov": ]] .. fov .. [[,]])
end
table.insert(result, [[ "quality": "]] .. quality .. [[",]])
table.insert(result, [[ "tilt": ]] .. tilt .. [[,]])
if crop then
table.insert(result, [[ "crop": {]])
table.insert(result, [[ "left": ]] .. crop["left"] .. [[,]])
table.insert(result, [[ "right": ]] .. crop["right"] .. [[,]])
table.insert(result, [[ "top": ]] .. crop["top"] .. [[,]])
table.insert(result, [[ "bottom": ]] .. crop["bottom"])
table.insert(result, [[ },]])
end
if offset then
table.insert(result, [[ "offset": {]])
table.insert(result, [[ "x": ]] .. offset["x"] .. [[,]])
table.insert(result, [[ "y": ]] .. offset["y"] .. [[,]])
table.insert(result, [[ "z": ]] .. offset["z"])
table.insert(result, [[ },]])
end
table.insert(result, [[ "background": {]])
table.insert(result, [[ "r": ]] .. background["r"] .. [[,]])
table.insert(result, [[ "g": ]] .. background["g"] .. [[,]])
table.insert(result, [[ "b": ]] .. background["b"] .. [[,]])
table.insert(result, [[ "a": ]] .. background["a"])
table.insert(result, [[ }]])
table.insert(result, [[ }]])
table.insert(result, [[ }]])
return table.concat(result, "\n")
end
function generateWindow(result, fullScreen, msaa, border, monitor, tags, stereo, pos,
size, res, viewport)
table.insert(result, [[ "name": "OpenSpace",]])
if fullScreen ~= nil then
table.insert(result, [[ "fullscreen": ]] .. tostring(fullScreen) .. [[,]])
end
if msaa then
table.insert(result, [[ "msaa": ]] .. msaa .. [[,]])
end
if border ~= nil then
table.insert(result, [[ "border": ]] .. tostring(border) .. [[,]])
end
if monitor then
table.insert(result, [[ "monitor": ]] .. monitor .. [[,]])
end
if #(tags) > 0 then
for i, v in ipairs(tags) do
tags[i] = "\"" .. v .. "\""
end
local t = table.concat(tags, [[,]])
table.insert(result, [[ "tags": [ ]] .. t .. [[ ], ]])
end
if stereo then
table.insert(result, [[ "stereo": "]] .. stereo .. [[",]])
end
local px = pos[1]
local py = pos[2]
table.insert(result, [[ "pos": { "x": ]] .. px .. [[, "y": ]] .. py .. [[ },]])
local sx = size[1]
local sy = size[2]
table.insert(result, [[ "size": { "x": ]] .. sx .. [[, "y": ]] .. sy .. [[ },]])
if res then
res[1] = res[1] or size[1]
res[2] = res[2] or size[2]
table.insert(result, [[ "res": {]])
table.insert(result, [[ "x": ]] .. res[1] .. [[,]])
table.insert(result, [[ "y": ]] .. res[2])
table.insert(result, [[ },]])
end
table.insert(result, [[ "viewports": []])
table.insert(result, viewport)
table.insert(result, [[ ] ]])
end
function generateUser(result)
table.insert(result, [[ {]])
table.insert(result, [[ "eyeseparation": 0.06,]])
table.insert(result, [[ "pos": { "x": 0.0, "y": 0.0, "z": 0.0 }]])
table.insert(result, [[ }]])
end
function generateScene(result, scene)
if scene == nil then
return nil
end
local offset = scene["offset"] or { x = 0.0, y = 0.0, z = 0.0 }
local orientation = scene["orientation"] or { yaw = 0.0, pitch = 0.0, roll = 0.0 }
local scale = scene["scale"] or 1.0
table.insert(result, [[{]])
table.insert(result, [[ "offset": {]])
table.insert(result, [[ "x": ]] .. offset["x"] .. [[,]])
table.insert(result, [[ "y": ]] .. offset["y"] .. [[,]])
table.insert(result, [[ "z": ]] .. offset["z"] .. [[,]])
table.insert(result, [[ },]])
table.insert(result, [[ "orientation": {]])
table.insert(result, [[ "yaw": ]] .. orientation["yaw"] .. [[,]])
table.insert(result, [[ "pitch": ]] .. orientation["pitch"] .. [[,]])
table.insert(result, [[ "roll": ]] .. orientation["roll"] .. [[,]])
table.insert(result, [[ },]])
table.insert(result, [[ "scale": ]] .. scale)
table.insert(result, [[}]])
end
function generateSettings(result, refreshRate, vsync)
table.insert(result, [[ "display": {]])
if refreshRate then
table.insert(result, [[ "refreshrate": ]] .. refreshRate .. [[,]])
end
if vsync then
table.insert(result, [[ "swapinterval": 1]])
else
table.insert(result, [[ "swapinterval": 0]])
end
table.insert(result, [[ }]])
end
function generateCluster(arg, viewport)
local result = {}
table.insert(result, [[{]])
table.insert(result, [[ "version": 1,]])
table.insert(result, [[ "masteraddress": "127.0.0.1",]])
if arg["sgctDebug"] ~= nil then
table.insert(result, [[ "debug": ]] .. tostring(arg["sgctdebug"]) .. [[,]])
end
table.insert(result, [[ "settings": {]])
generateSettings(result, arg["refreshRate"], arg["vsync"])
table.insert(result, [[ },]])
if arg["scene"] then
table.insert(result, [[ "scene": {]])
generateScene(result, arg["scene"])
table.insert(result, [[ },]])
end
table.insert(result, [[ "nodes": []])
table.insert(result, [[ {]])
table.insert(result, [[ "address": "127.0.0.1",]])
table.insert(result, [[ "port": 20401,]])
table.insert(result, [[ "windows": []])
table.insert(result, [[ {]])
generateWindow(result, arg["fullScreen"], arg["msaa"], arg["border"], arg["monitor"],
arg["tags"], arg["stereo"], arg["pos"], arg["size"], arg["res"], viewport)
table.insert(result, [[ }]])
table.insert(result, [[ ] ]])
table.insert(result, [[ }]])
table.insert(result, [[ ],]])
table.insert(result, [[ "users": []])
generateUser(result)
table.insert(result, [[ ] ]])
table.insert(result, [[}]])
return table.concat(result, "\n")
end
function check(type_str, arg, param, subparam_type)
local t = type(arg[param])
assert(t == type_str or t == "nil", param .. " must be a " .. type_str .. " or nil")
@@ -327,55 +72,151 @@ end
function generateSingleWindowConfig(arg, viewport)
check("table", arg, "res", "number")
check("table", arg, "tags", "string")
check("table", arg, "pos", "number")
check("boolean", arg, "shared")
check("boolean", arg, "fullScreen")
check("boolean", arg, "border")
check("boolean", arg, "vsync")
check("boolean", arg, "sgctDebug")
check("number", arg, "monitor")
check("number", arg, "msaa")
check("number", arg, "refreshRate")
check("string", arg, "stereo")
function generateSingleViewportFOV(down, up, left, right, tracked)
local result = {
projection = {
type = "PlanarProjection",
fov = {
left = left,
right = right,
up = up,
down = down
}
}
}
check("table", arg, "scene")
if arg["scene"] then
check("table", arg["scene"], "offset", "number")
check("table", arg["scene"], "orientation", "number")
check("number", arg["scene"], "scale")
if tracked ~= nil then
result["tracked"] = tracked
end
if arg["shared"] ~= nil then
local t = arg["tags"]
t[#t + 1] = "Spout"
end
return generateCluster(arg, viewport)
return result
end
function sgct.makeConfig(config)
--
-- Create a file in the operating system's `temp` folder
--
function generateWindow(fullScreen, msaa, border, monitor, tags, stereo, pos, size, res,
viewport)
local result = {
name = "OpenSpace",
pos = { x = pos[1], y = pos[2] },
size = { x = size[1], y = size[2] },
viewports = { viewport }
}
-- tmpname returns the name to a randomly chosen file within the `temp` folder
local tempFile = os.tmpname()
-- `package.config` contains the path separator used on the current platform
local directorySeparator = package.config:match("([^\n]*)\n?")
-- Remove the filename and only get the path to the temp folder
local separatorIdx = tempFile:reverse():find(directorySeparator)
local tempFolder = tempFile:sub(1, #tempFile - separatorIdx + 1)
if fullScreen ~= nil then
result["fullscreen"] = fullScreen
end
local configFile = tempFolder .. "openspace-window-configuration.json"
local file = io.open(configFile, "w+")
file:write(config)
io.close(file)
return configFile
if msaa then
result["msaa"] = msaa
end
if border ~= nil then
result["border"] = border
end
if monitor then
result["monitor"] = monitor
end
if #(tags) > 0 then
for i, v in ipairs(tags) do
tags[i] = "\"" .. v .. "\""
end
local t = table.concat(tags, [[,]])
result["tags"] = t
end
if stereo then
result["stereo"] = stereo
end
if res then
res[1] = res[1] or size[1]
res[2] = res[2] or size[2]
result["res"] = { x = res[1], y = res[2] }
end
return result
end
function generateScene(result, scene)
if scene == nil then
return nil
end
local offset = scene["offset"] or { x = 0.0, y = 0.0, z = 0.0 }
local orientation = scene["orientation"] or { yaw = 0.0, pitch = 0.0, roll = 0.0 }
local scale = scene["scale"] or 1.0
local result = {
offset = offset,
orientation = orientation,
scale = scale
}
return result
end
function generateSettings(refreshRate, vsync)
local result = {
display = {}
}
if refreshRate then
result["display"]["refreshrate"] = refreshRate
end
if vsync then
result["display"]["swapinterval"] = 1
else
result["display"]["swapinterval"] = 0
end
return result
end
function generateCluster(arg)
local viewport = generateSingleViewportFOV(arg["fov"]["down"], arg["fov"]["up"],
arg["fov"]["left"], arg["fov"]["right"], arg["tracked"])
local result = {
version = 1,
masteraddress = "127.0.0.1",
settings = generateSettings(arg["refreshRate"], arg["vsync"]),
nodes = {
{
address = "127.0.0.1",
port = 20401,
windows = {
generateWindow(arg["fullScreen"], arg["msaa"], arg["border"], arg["monitor"],
arg["tags"], arg["stereo"], arg["pos"], arg["size"], arg["res"], viewport)
}
}
},
users = {
{
eyeseparation = 0.06,
pos = { x = 0.0, y = 0.0, z = 0.0 }
}
}
}
if arg["sgctDebug"] ~= nil then
result["debug"] = arg["sgctdebug"]
end
if arg["scene"] then
result["scene"] = generateScene(arg["scene"])
end
return result
end
@@ -428,59 +269,51 @@ function sgct.config.single(arg)
end
check("boolean", arg, "tracked")
check("table", arg, "res", "number")
check("table", arg, "tags", "string")
check("table", arg, "pos", "number")
check("boolean", arg, "shared")
check("boolean", arg, "fullScreen")
check("boolean", arg, "border")
check("boolean", arg, "vsync")
check("boolean", arg, "sgctDebug")
check("number", arg, "monitor")
check("number", arg, "msaa")
check("number", arg, "refreshRate")
check("string", arg, "stereo")
check("table", arg, "scene")
if arg["scene"] then
check("table", arg["scene"], "offset", "number")
check("table", arg["scene"], "orientation", "number")
check("number", arg["scene"], "scale")
end
if arg["shared"] ~= nil then
local t = arg["tags"]
t[#t + 1] = "Spout"
end
sgctconfiginitializeString = "sgct.config.single"
arg["tracked"] = arg["tracked"] or true
local viewport = generateSingleViewportFOV(arg["fov"]["down"], arg["fov"]["up"],
arg["fov"]["left"], arg["fov"]["right"], arg["tracked"])
--
-- Create a file in the operating system's `temp` folder
--
return sgct.makeConfig(generateSingleWindowConfig(arg, viewport))
end
function sgct.config.fisheye(arg)
arg = arg or {}
check("number", arg, 1)
check("number", arg, 2)
if type(arg[1]) == "number" and type(arg[2]) == "number" then
arg["size"] = { arg[1], arg[2] }
arg[1] = nil
arg[2] = nil
else
-- No numbers specified, therefore we want to use the screen resolution of the primary
-- monitor to derive the resolution
-- ScreenResolution is a variable that got injected into the openspace.cfg by the
-- OpenSpace code prior to loading this file
local scale_factor = 2.0/3.0;
arg["size"] = { ScreenResolution.x * scale_factor, ScreenResolution.y * scale_factor }
end
check("number", arg, "fov")
check("number", arg, "tilt")
check("string", arg, "quality")
check("table", arg, "background", "number")
check("table", arg, "crop", "number")
check("table", arg, "offset", "number")
sgctconfiginitializeString = "sgct.config.fisheye"
arg["vsync"] = arg["vsync"] or false
arg["tags"] = arg["tags"] or {}
arg["pos"] = arg["pos"] or { 50, 50 }
arg["size"] = arg["size"] or { 1024, 1024 }
arg["quality"] = arg["quality"] or "1k"
arg["tilt"] = arg["tilt"] or 90.0
arg["background"] = arg["background"] or { r = 0.0, g = 0.0, b = 0.0, a = 1.0 }
arg["tracked"] = arg["tracked"] or false
local viewport = generateFisheyeViewport(arg["fov"], arg["quality"], arg["tilt"],
arg["background"], arg["crop"], arg["offset"], arg["tracked"])
return sgct.makeConfig(generateSingleWindowConfig(arg, viewport))
local config = generateCluster(arg)
-- tmpname returns the name to a randomly chosen file within the `temp` folder
local tempFile = os.tmpname()
-- `package.config` contains the path separator used on the current platform
local directorySeparator = package.config:match("([^\n]*)\n?")
-- Remove the filename and only get the path to the temp folder
local separatorIdx = tempFile:reverse():find(directorySeparator)
local tempFolder = tempFile:sub(1, #tempFile - separatorIdx + 1)
local configFile = tempFolder .. "openspace-window-configuration.json"
local file = io.open(configFile, "w+")
file:write(TableToJson(config))
io.close(file)
return configFile
end

View File

@@ -28,6 +28,7 @@
#include <openspace/engine/globals.h>
#include <openspace/engine/settings.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/util/json_helper.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
@@ -770,6 +771,19 @@ Configuration loadConfigurationFromFile(const std::filesystem::path& configurati
);
ghoul::lua::runScript(result.state, script);
// Local function to convert a dictionary to its JSON object's string representation
constexpr auto TableToJson = [](lua_State* state) {
if (!ghoul::lua::hasValue<ghoul::Dictionary>(state)) {
throw ghoul::lua::LuaError("TableToJson must receive a table object");
}
ghoul::Dictionary dict = ghoul::lua::value<ghoul::Dictionary>(state);
std::string stringRepresentation = formatJson(dict);
ghoul::lua::push(state, std::move(stringRepresentation));
return 1;
};
lua_pushcfunction(result.state, TableToJson);
lua_setglobal(result.state, "TableToJson");
// If there is an initial config helper file, load it into the state
if (std::filesystem::is_regular_file(absPath(InitialConfigHelper))) {
ghoul::lua::runScriptFile(result.state, absPath(InitialConfigHelper));

View File

@@ -144,6 +144,90 @@ void AssetManager::deinitialize() {
_toBeDeleted.clear();
}
void AssetManager::runRemoveQueue() {
ZoneScoped;
for (const std::string& asset : _assetRemoveQueue) {
std::filesystem::path path = generateAssetPath(_assetRootDirectory, asset);
const auto it = std::find_if(
_assets.cbegin(),
_assets.cend(),
[&path](const std::unique_ptr<Asset>& a) { return a->path() == path; }
);
if (it == _assets.cend()) {
LWARNING(std::format("Tried to remove unknown asset '{}'. Skipping", asset));
continue;
}
Asset* a = it->get();
auto jt = std::find(_rootAssets.cbegin(), _rootAssets.cend(), a);
if (jt == _rootAssets.cend()) {
// Trying to remove an asset from the middle of the tree might have some
// unexpected behavior since if we were to remove an asset with children, we
// would have to unload those children as well. Also, we don't know if that
// Asset's parents are actually requiring the asset we are about to remove so
// we might break things horribly for people.
// We should figure out a way to fix this without reintroducing the
// require/request confusion, but until then, we'll prohibit removing non-root
// assets
LWARNING("Tried to remove an asset that was not on the root level. Skipping");
continue;
}
_rootAssets.erase(jt);
// Even though we are removing a root asset, we might not be the only person that
// is interested in the asset, so we can only deinitialize it if we were, in fact,
// the only person, meaning that the asset never had any parents
if (!a->hasInitializedParent()) {
a->deinitialize();
}
if (!a->hasLoadedParent()) {
a->unload();
}
global::profile->removeAsset(asset);
}
_assetRemoveQueue.clear();
}
void AssetManager::runAddQueue() {
ZoneScoped;
for (const std::string& asset : _assetAddQueue) {
const std::filesystem::path path = generateAssetPath(_assetRootDirectory, asset);
Asset* a = nullptr;
try {
a = retrieveAsset(path, "");
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
continue;
}
const auto it = std::find(_rootAssets.cbegin(), _rootAssets.cend(), a);
if (it != _rootAssets.cend()) {
// Do nothing if the asset has already been requested on a root level. Even if
// another asset has already required this asset, calling `load`,
// `startSynchronization`, etc on it are still fine since all of those
// functions bail out early if they already have been called before
continue;
}
a->load(nullptr);
if (a->isFailed()) {
// The loading might fail because of any number of reasons, most likely of
// them some Lua syntax error
continue;
}
_rootAssets.push_back(a);
a->startSynchronizations();
_toBeInitialized.push_back(a);
global::profile->addAsset(asset);
}
_assetAddQueue.clear();
}
void AssetManager::update() {
ZoneScoped;
@@ -187,87 +271,10 @@ void AssetManager::update() {
break;
}
// Add all assets that have been queued for loading since the last `update` call
for (const std::string& asset : _assetAddQueue) {
ZoneScopedN("Adding queued assets");
const std::filesystem::path path = generateAssetPath(_assetRootDirectory, asset);
Asset* a = nullptr;
try {
a = retrieveAsset(path, "");
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
continue;
}
const auto it = std::find(_rootAssets.cbegin(), _rootAssets.cend(), a);
if (it != _rootAssets.cend()) {
// Do nothing if the asset has already been requested on a root level. Even if
// another asset has already required this asset, calling `load`,
// `startSynchronization`, etc on it are still fine since all of those
// functions bail out early if they already have been called before
continue;
}
a->load(nullptr);
if (a->isFailed()) {
// The loading might fail because of any number of reasons, most likely of
// them some Lua syntax error
continue;
}
_rootAssets.push_back(a);
a->startSynchronizations();
_toBeInitialized.push_back(a);
global::profile->addAsset(asset);
}
_assetAddQueue.clear();
// Remove assets
for (const std::string& asset : _assetRemoveQueue) {
ZoneScopedN("Removing queued assets");
std::filesystem::path path = generateAssetPath(_assetRootDirectory, asset);
const auto it = std::find_if(
_assets.cbegin(),
_assets.cend(),
[&path](const std::unique_ptr<Asset>& a) { return a->path() == path; }
);
if (it == _assets.cend()) {
LWARNING(std::format("Tried to remove unknown asset '{}'. Skipping", asset));
continue;
}
Asset* a = it->get();
auto jt = std::find(_rootAssets.cbegin(), _rootAssets.cend(), a);
if (jt == _rootAssets.cend()) {
// Trying to remove an asset from the middle of the tree might have some
// unexpected behavior since if we were to remove an asset with children, we
// would have to unload those children as well. Also, we don't know if that
// Asset's parents are actually requiring the asset we are about to remove so
// we might break things horribly for people.
// We should figure out a way to fix this without reintroducing the
// require/request confusion, but until then, we'll prohibit removing non-root
// assets
LWARNING("Tried to remove an asset that was not on the root level. Skipping");
continue;
}
_rootAssets.erase(jt);
// Even though we are removing a root asset, we might not be the only person that
// is interested in the asset, so we can only deinitialize it if we were, in fact,
// the only person, meaning that the asset never had any parents
if (!a->hasInitializedParent()) {
a->deinitialize();
}
if (!a->hasLoadedParent()) {
a->unload();
}
global::profile->removeAsset(asset);
}
_assetRemoveQueue.clear();
runRemoveQueue();
// Add all assets that have been queued for loading since the last `update` call
runAddQueue();
// Change state based on synchronizations. If any of the unfinished synchronizations
// has finished since the last call of this function, we should notify the assets and
@@ -317,6 +324,12 @@ void AssetManager::remove(const std::string& path) {
_assetRemoveQueue.push_back(path);
}
void AssetManager::reload(const std::string& path) {
ghoul_precondition(!path.empty(), "Path must not be empty");
_assetRemoveQueue.push_back(path);
_assetAddQueue.push_back(path);
}
std::vector<const Asset*> AssetManager::allAssets() const {
std::vector<const Asset*> res;
res.reserve(_assets.size());
@@ -1042,6 +1055,7 @@ scripting::LuaLibrary AssetManager::luaLibrary() {
{
codegen::lua::Add,
codegen::lua::Remove,
codegen::lua::Reload,
codegen::lua::RemoveAll,
codegen::lua::IsLoaded,
codegen::lua::AllAssets,

View File

@@ -41,6 +41,15 @@ namespace {
openspace::global::openSpaceEngine->assetManager().remove(assetName);
}
/**
* Reloads the asset with the specified name. If the asset was previously loaded explicity
* it will be removed and then re-added. If the asset was not previously loaded, it will
* only be loaded instead.
*/
[[codegen::luawrap]] void reload(std::string assetName) {
openspace::global::openSpaceEngine->assetManager().reload(assetName);
}
/**
* Removes all assets that are currently loaded
*/

View File

@@ -32,6 +32,7 @@
#include <openspace/scene/profile.h>
#include <openspace/scene/sceneinitializer.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/lua/lua_helper.h>
#include <stack>