Merge branch 'master' into merge/2022softwareintegration

This commit is contained in:
Micah Acinapura
2025-05-14 10:09:29 -04:00
7161 changed files with 207674 additions and 207229 deletions

View File

@@ -2,7 +2,7 @@
# #
# OpenSpace #
# #
# Copyright (c) 2014-2022 #
# Copyright (c) 2014-2025 #
# #
# 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 #
@@ -22,8 +22,8 @@
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${OPENSPACE_CMAKE_EXT_DIR}/global_variables.cmake)
include(${GHOUL_BASE_DIR}/support/cmake/message_macros.cmake)
include(${PROJECT_SOURCE_DIR}/support/cmake/global_variables.cmake)
include(${PROJECT_SOURCE_DIR}/ext/ghoul/support/cmake/message_macros.cmake)
# This function takes a list of module paths and returns the list of include paths that
@@ -91,7 +91,7 @@ function (get_module_attribute_supported path result)
endfunction()
# Returns the path for the 'module_name'. If the module has not been seen before by
# Returns the path for the 'module_name'. If the module has not been seen before by
# get_individual_modules, an empty string is returned
function (find_path_for_module module_name module_names module_paths result)
list(FIND module_names ${module_name} i)
@@ -121,7 +121,7 @@ function (get_recursive_dependencies module_name module_path module_names module
"${module_names}" "${module_paths}"
res
)
# 1. We add "base" to the list of dependencies as we always want it
# 1. We add "base" to the list of dependencies as we always want it
# 2. We add dependencies in this order such that when we later traverse
# this list, we automatically get them in the correct order (meaning
# that we include a dependency first)
@@ -162,9 +162,8 @@ endfunction ()
set(OPENSPACE_EXTERNAL_MODULES_PATHS "" CACHE STRING "List of external modules")
set(internal_module_path "${OPENSPACE_BASE_DIR}/modules")
option(OPENSPACE_ENABLE_ALL_MODULES "If this is ON, all modules will be enabled by default")
set(internal_module_path "${PROJECT_SOURCE_DIR}/modules")
set(all_enabled_modules "")
@@ -173,21 +172,6 @@ set(all_enabled_modules "")
#
# First get all the internal module
get_individual_modules(${internal_module_path} all_module_names all_module_paths)
# Then get all external modules
foreach (path ${OPENSPACE_EXTERNAL_MODULES_PATHS})
get_individual_modules(${path} names paths)
foreach (n ${names})
if (${n} IN_LIST all_module_names)
message(FATAL_ERROR "Module name ${n} is not unique among the external directories")
endif ()
endforeach ()
set(all_module_names ${all_module_names} ${names})
set(all_module_paths ${all_module_paths} ${paths})
endforeach ()
list(LENGTH all_module_names all_module_names_count)
math(EXPR all_module_names_count "${all_module_names_count} - 1")
@@ -209,7 +193,11 @@ foreach(val RANGE ${all_module_names_count})
get_module_attribute_default(${path} is_default_module)
create_option_name(${name} optionName)
option(${optionName} "Build ${path} Module" ${is_default_module})
if (${OPENSPACE_ENABLE_ALL_MODULES})
option(${optionName} "Build ${path} Module" ON)
else ()
option(${optionName} "Build ${path} Module" ${is_default_module})
endif ()
if (${optionName})
list(APPEND enabled_module_names ${name})
@@ -241,7 +229,7 @@ foreach (val RANGE ${enabled_module_count})
set(dependencies ${dependencies} ${deps})
endforeach()
# We can remove the duplicates here. We constructed the list such that nested
# We can remove the duplicates here. We constructed the list such that nested
# dependencies are order left to right. REMOVE_DUPLICATES will keep the left most
# value in the case of duplicates, so that will still work
list(REMOVE_DUPLICATES dependencies)
@@ -332,7 +320,7 @@ list(REMOVE_DUPLICATES topologically_sorted)
set(MODULE_HEADERS "")
set(MODULE_CLASSES "")
set(MODULE_PATHS "")
set(MODULE_DOCUMENTATION "")
foreach (key RANGE ${enabled_module_count})
list(GET topologically_sorted ${key} name)
@@ -348,6 +336,7 @@ foreach (key RANGE ${enabled_module_count})
list(APPEND MODULE_HEADERS "#include <${header_filepath}>\n")
list(APPEND MODULE_CLASSES " new ${class_name},\n")
list(APPEND MODULE_DOCUMENTATION " ${class_name}::Documentation(),\n")
endforeach ()
get_unique_include_paths(
@@ -361,7 +350,7 @@ foreach (path ${module_paths})
# The module path should include the 'modules' directory, which is removed for the
# include path to make all of the includes look the same
list(APPEND MODULE_PATHS " \"${path}/modules\",\n")
target_include_directories(openspace-module-collection PUBLIC ${path})
target_include_directories(openspace-module-collection INTERFACE ${path})
endforeach ()
if (NOT "${MODULE_HEADERS}" STREQUAL "")
@@ -372,20 +361,14 @@ if (NOT "${MODULE_CLASSES}" STREQUAL "")
string(REPLACE ";" "" MODULE_CLASSES ${MODULE_CLASSES})
endif ()
if (NOT "${MODULE_PATHS}" STREQUAL "")
string(REPLACE ";" "" MODULE_PATHS ${MODULE_PATHS})
string(REPLACE "\\" "/" MODULE_PATHS ${MODULE_PATHS})
if (NOT "${MODULE_DOCUMENTATION}" STREQUAL "")
string(REPLACE ";" "" MODULE_DOCUMENTATION ${MODULE_DOCUMENTATION})
endif ()
configure_file(
${OPENSPACE_CMAKE_EXT_DIR}/module_registration.template
${PROJECT_SOURCE_DIR}/support/cmake/module_registration.template
${CMAKE_BINARY_DIR}/_generated/include/openspace/moduleregistration.h
)
configure_file(
${OPENSPACE_CMAKE_EXT_DIR}/module_path.template
${CMAKE_BINARY_DIR}/_generated/include/openspace/modulepath.h
)
# Return the list of enabled modules to the caller
set(all_enabled_modules ${all_enabled_modules} PARENT_SCOPE)

View File

@@ -2,7 +2,7 @@
# #
# OpenSpace #
# #
# Copyright (c) 2014-2022 #
# Copyright (c) 2014-2025 #
# #
# 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 #
@@ -22,7 +22,7 @@
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
include(${PROJECT_SOURCE_DIR}/support/cmake/module_definition.cmake)
set(HEADER_FILES
rendering/atmospheredeferredcaster.h
@@ -38,7 +38,7 @@ source_group("Source Files" FILES ${SOURCE_FILES})
set(SHADER_FILES
shaders/atmosphere_common.glsl
shaders/atmosphere_deferred_vs.glsl
shaders/atmosphere_deferred_vs.glsl
shaders/atmosphere_deferred_fs.glsl
shaders/calculation_gs.glsl
shaders/calculation_vs.glsl

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -70,16 +70,6 @@
namespace {
constexpr std::string_view _loggerCat = "AtmosphereDeferredcaster";
constexpr std::array<const char*, 27> UniformNames = {
"cullAtmosphere", "Rg", "Rt", "groundRadianceEmission", "HR", "betaRayleigh",
"HM", "betaMieExtinction", "mieG", "sunRadiance", "ozoneLayerEnabled", "HO",
"betaOzoneExtinction", "SAMPLES_R", "SAMPLES_MU", "SAMPLES_MU_S", "SAMPLES_NU",
"inverseModelTransformMatrix", "modelTransformMatrix",
"projectionToModelTransformMatrix", "viewToWorldMatrix", "camPosObj",
"sunDirectionObj", "hardShadows", "transmittanceTexture", "irradianceTexture",
"inscatterTexture"
};
constexpr float ATM_EPS = 2000.f;
constexpr float KM_TO_M = 1000.f;
@@ -112,14 +102,14 @@ namespace {
}
}
bool isAtmosphereInFrustum(const glm::dmat4& MVMatrix, const glm::dvec3& position,
bool isAtmosphereInFrustum(const glm::dmat4& mv, const glm::dvec3& position,
double radius)
{
// Frustum Planes
glm::dvec3 col1 = glm::dvec3(MVMatrix[0][0], MVMatrix[1][0], MVMatrix[2][0]);
glm::dvec3 col2 = glm::dvec3(MVMatrix[0][1], MVMatrix[1][1], MVMatrix[2][1]);
glm::dvec3 col3 = glm::dvec3(MVMatrix[0][2], MVMatrix[1][2], MVMatrix[2][2]);
glm::dvec3 col4 = glm::dvec3(MVMatrix[0][3], MVMatrix[1][3], MVMatrix[2][3]);
const glm::dvec3 col1 = glm::dvec3(mv[0][0], mv[1][0], mv[2][0]);
const glm::dvec3 col2 = glm::dvec3(mv[0][1], mv[1][1], mv[2][1]);
const glm::dvec3 col3 = glm::dvec3(mv[0][2], mv[1][2], mv[2][2]);
const glm::dvec3 col4 = glm::dvec3(mv[0][3], mv[1][3], mv[2][3]);
glm::dvec3 leftNormal = col4 + col1;
glm::dvec3 rightNormal = col4 - col1;
@@ -129,11 +119,11 @@ namespace {
glm::dvec3 farNormal = col4 - col3;
// Plane Distances
double leftDistance = MVMatrix[3][3] + MVMatrix[3][0];
double rightDistance = MVMatrix[3][3] - MVMatrix[3][0];
double bottomDistance = MVMatrix[3][3] + MVMatrix[3][1];
double topDistance = MVMatrix[3][3] - MVMatrix[3][1];
double nearDistance = MVMatrix[3][3] + MVMatrix[3][2];
double leftDistance = mv[3][3] + mv[3][0];
double rightDistance = mv[3][3] - mv[3][0];
double bottomDistance = mv[3][3] + mv[3][1];
double topDistance = mv[3][3] - mv[3][1];
double nearDistance = mv[3][3] + mv[3][2];
// Normalize Planes
const double invLeftMag = 1.0 / glm::length(leftNormal);
@@ -159,20 +149,18 @@ namespace {
const double invFarMag = 1.0 / glm::length(farNormal);
farNormal *= invFarMag;
if (((glm::dot(leftNormal, position) + leftDistance) < -radius) ||
const bool outsideFrustum =
(((glm::dot(leftNormal, position) + leftDistance) < -radius) ||
((glm::dot(rightNormal, position) + rightDistance) < -radius) ||
((glm::dot(bottomNormal, position) + bottomDistance) < -radius) ||
((glm::dot(topNormal, position) + topDistance) < -radius) ||
((glm::dot(nearNormal, position) + nearDistance) < -radius))
// The far plane testing is disabled because the atm has no depth.
{
return false;
}
return true;
((glm::dot(nearNormal, position) + nearDistance) < -radius));
return !outsideFrustum;
}
GLuint createTexture(const glm::ivec2& size, std::string_view name) {
GLuint t;
GLuint t = 0;
glGenTextures(1, &t);
glBindTexture(GL_TEXTURE_2D, t);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -201,7 +189,7 @@ namespace {
GLuint createTexture(const glm::ivec3& size, std::string_view name, int components) {
ghoul_assert(components == 3 || components == 4, "Only 3-4 components supported");
GLuint t;
GLuint t = 0;
glGenTextures(1, &t);
glBindTexture(GL_TEXTURE_3D, t);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -252,7 +240,7 @@ AtmosphereDeferredcaster::AtmosphereDeferredcaster(float textureScale,
}
void AtmosphereDeferredcaster::initialize() {
ZoneScoped
ZoneScoped;
_transmittanceTableTexture = createTexture(_transmittanceTableSize, "Transmittance");
_irradianceTableTexture = createTexture(_irradianceTableSize, "Irradiance");
@@ -261,7 +249,7 @@ void AtmosphereDeferredcaster::initialize() {
}
void AtmosphereDeferredcaster::deinitialize() {
ZoneScoped
ZoneScoped;
glDeleteTextures(1, &_transmittanceTableTexture);
glDeleteTextures(1, &_irradianceTableTexture);
@@ -270,13 +258,60 @@ void AtmosphereDeferredcaster::deinitialize() {
void AtmosphereDeferredcaster::update(const UpdateData&) {}
float AtmosphereDeferredcaster::eclipseShadow(const glm::dvec3& position) {
// This code is copied from the atmosphere deferred fragment shader
// It is used to calculate the eclipse shadow
if (_shadowDataArrayCache.empty() || !_shadowDataArrayCache.front().isShadowing) {
return 1.f;
}
const ShadowRenderingStruct& shadow = _shadowDataArrayCache.front();
const glm::dvec3 positionToCaster = shadow.casterPositionVec - position;
const glm::dvec3 sourceToCaster = shadow.sourceCasterVec; // Normalized
const glm::dvec3 casterShadow =
dot(positionToCaster, sourceToCaster) * sourceToCaster;
const glm::dvec3 positionToShadow = positionToCaster - casterShadow;
const float distanceToShadow = static_cast<float>(length(positionToShadow));
const double shadowLength = length(casterShadow);
const float radiusPenumbra = static_cast<float>(
shadow.radiusCaster * (shadowLength + shadow.penumbra) / shadow.penumbra
);
const float radiusUmbra = static_cast<float>(
shadow.radiusCaster * (shadow.umbra - shadowLength) / shadow.umbra
);
// Is the position in the umbra - the fully shaded part
if (distanceToShadow < radiusUmbra) {
if (_hardShadowsEnabled) {
return 0.5f;
}
else {
// Smooth the shadow with the butterworth function
const float s = radiusUmbra / (radiusUmbra + std::pow(distanceToShadow, 4.f));
return std::sqrt(s);
}
}
else if (distanceToShadow < radiusPenumbra) { // In penumbra - partially shaded part
return _hardShadowsEnabled ? 0.5f : distanceToShadow / radiusPenumbra;
}
else {
return 1.f;
}
}
void AtmosphereDeferredcaster::preRaycast(const RenderData& data, const DeferredcastData&,
ghoul::opengl::ProgramObject& prg)
ghoul::opengl::ProgramObject& program)
{
ZoneScoped
ZoneScoped;
TracyGpuZone("Atmosphere preRaycast");
// Atmosphere Frustum Culling
glm::dvec3 tPlanetPos = glm::dvec3(_modelTransform * glm::dvec4(0.0, 0.0, 0.0, 1.0));
const glm::dvec3 tPlanetPos = glm::dvec3(
_modelTransform * glm::dvec4(0.0, 0.0, 0.0, 1.0)
);
const double distance = glm::distance(tPlanetPos, data.camera.eyePositionVec3());
// Radius is in KM
@@ -285,75 +320,83 @@ void AtmosphereDeferredcaster::preRaycast(const RenderData& data, const Deferred
);
// Number of planet radii to use as distance threshold for culling
prg.setUniform(_uniformCache.cullAtmosphere, 1);
program.setUniform(_uniformCache.cullAtmosphere, 1);
constexpr double DistanceCullingRadii = 5000;
glm::dmat4 MV = glm::dmat4(data.camera.sgctInternal.projectionMatrix()) *
const glm::dmat4 MV = glm::dmat4(data.camera.sgctInternal.projectionMatrix()) *
data.camera.combinedViewMatrix();
if (distance <= scaledRadius * DistanceCullingRadii &&
isAtmosphereInFrustum(MV, tPlanetPos, scaledRadius + ATM_EPS))
{
prg.setUniform(_uniformCache.cullAtmosphere, 0);
prg.setUniform(_uniformCache.Rg, _atmospherePlanetRadius);
prg.setUniform(_uniformCache.Rt, _atmosphereRadius);
prg.setUniform(_uniformCache.groundRadianceEmission, _groundRadianceEmission);
prg.setUniform(_uniformCache.HR, _rayleighHeightScale);
prg.setUniform(_uniformCache.betaRayleigh, _rayleighScatteringCoeff);
prg.setUniform(_uniformCache.HM, _mieHeightScale);
prg.setUniform(_uniformCache.betaMieExtinction, _mieExtinctionCoeff);
prg.setUniform(_uniformCache.mieG, _miePhaseConstant);
prg.setUniform(_uniformCache.sunRadiance, _sunRadianceIntensity);
prg.setUniform(_uniformCache.ozoneLayerEnabled, _ozoneEnabled);
prg.setUniform(_uniformCache.HO, _ozoneHeightScale);
prg.setUniform(_uniformCache.betaOzoneExtinction, _ozoneExtinctionCoeff);
prg.setUniform(_uniformCache.SAMPLES_R, _rSamples);
prg.setUniform(_uniformCache.SAMPLES_MU, _muSamples);
prg.setUniform(_uniformCache.SAMPLES_MU_S, _muSSamples);
prg.setUniform(_uniformCache.SAMPLES_NU, _nuSamples);
program.setUniform(_uniformCache.cullAtmosphere, 0);
program.setUniform(_uniformCache.opacity, _opacity);
program.setUniform(_uniformCache.Rg, _atmospherePlanetRadius);
program.setUniform(_uniformCache.Rt, _atmosphereRadius);
program.setUniform(_uniformCache.groundRadianceEmission, _groundRadianceEmission);
program.setUniform(_uniformCache.HR, _rayleighHeightScale);
program.setUniform(_uniformCache.betaRayleigh, _rayleighScatteringCoeff);
program.setUniform(_uniformCache.HM, _mieHeightScale);
program.setUniform(_uniformCache.betaMieExtinction, _mieExtinctionCoeff);
program.setUniform(_uniformCache.mieG, _miePhaseConstant);
program.setUniform(_uniformCache.sunRadiance, _sunRadianceIntensity);
program.setUniform(_uniformCache.ozoneLayerEnabled, _ozoneEnabled);
program.setUniform(_uniformCache.HO, _ozoneHeightScale);
program.setUniform(_uniformCache.betaOzoneExtinction, _ozoneExtinctionCoeff);
program.setUniform(_uniformCache.SAMPLES_R, _rSamples);
program.setUniform(_uniformCache.SAMPLES_MU, _muSamples);
program.setUniform(_uniformCache.SAMPLES_MU_S, _muSSamples);
program.setUniform(_uniformCache.SAMPLES_NU, _nuSamples);
// We expose the value as degrees, but the shader wants radians
program.setUniform(_uniformCache.sunAngularSize, glm::radians(_sunAngularSize));
// Object Space
glm::dmat4 invModelMatrix = glm::inverse(_modelTransform);
prg.setUniform(_uniformCache.inverseModelTransformMatrix, invModelMatrix);
prg.setUniform(_uniformCache.modelTransformMatrix, _modelTransform);
const glm::dmat4 invModelMatrix = glm::inverse(_modelTransform);
program.setUniform(_uniformCache.inverseModelTransformMatrix, invModelMatrix);
program.setUniform(_uniformCache.modelTransformMatrix, _modelTransform);
glm::dmat4 viewToWorldMatrix = glm::inverse(data.camera.combinedViewMatrix());
const glm::dmat4 viewToWorld = glm::inverse(data.camera.combinedViewMatrix());
// Eye Space to World Space
prg.setUniform(_uniformCache.viewToWorldMatrix, viewToWorldMatrix);
program.setUniform(_uniformCache.viewToWorldMatrix, viewToWorld);
// Projection to Eye Space
glm::dmat4 dInvProj = glm::inverse(glm::dmat4(data.camera.projectionMatrix()));
const glm::dmat4 dInvProj = glm::inverse(
glm::dmat4(data.camera.projectionMatrix())
);
glm::dmat4 invWholePipeline = invModelMatrix * viewToWorldMatrix * dInvProj;
const glm::dmat4 invWholePipeline = invModelMatrix * viewToWorld * dInvProj;
prg.setUniform(_uniformCache.projectionToModelTransform, invWholePipeline);
program.setUniform(
_uniformCache.projectionToModelTransformMatrix,
invWholePipeline
);
glm::dvec4 camPosObjCoords =
const glm::dvec4 camPosObjCoords =
invModelMatrix * glm::dvec4(data.camera.eyePositionVec3(), 1.0);
prg.setUniform(_uniformCache.camPosObj, glm::dvec3(camPosObjCoords));
program.setUniform(_uniformCache.camPosObj, glm::dvec3(camPosObjCoords));
SceneGraphNode* node = sceneGraph()->sceneGraphNode("Sun");
glm::dvec3 sunPosWorld = node ? node->worldPosition() : glm::dvec3(0.0);
// For the lighting we use the provided node, or the Sun
SceneGraphNode* node =
_lightSourceNode ? _lightSourceNode : sceneGraph()->sceneGraphNode("Sun");
const glm::dvec3 sunPosWorld = node ? node->worldPosition() : glm::dvec3(0.0);
glm::dvec3 sunPosObj;
// Sun following camera position
if (_sunFollowingCameraEnabled) {
sunPosObj = invModelMatrix * glm::dvec4(data.camera.eyePositionVec3(), 1.0);
sunPosObj = camPosObjCoords;
}
else {
sunPosObj = invModelMatrix *
glm::dvec4((sunPosWorld - data.modelTransform.translation) * 1000.0, 1.0);
sunPosObj = invModelMatrix * glm::dvec4(sunPosWorld, 1.0);
}
// Sun Position in Object Space
prg.setUniform(_uniformCache.sunDirectionObj, glm::normalize(sunPosObj));
program.setUniform(_uniformCache.sunDirectionObj, glm::normalize(sunPosObj));
// Shadow calculations..
_shadowDataArrayCache.clear();
for (ShadowConfiguration& shadowConf : _shadowConfArray) {
// TO REMEMBER: all distances and lengths in world coordinates are in
// meters!!! We need to move this to view space...
double lt;
double lt = 0.0;
glm::dvec3 sourcePos = SpiceManager::ref().targetPosition(
shadowConf.source.first,
"SSB",
@@ -392,25 +435,25 @@ void AtmosphereDeferredcaster::preRaycast(const RenderData& data, const Deferred
const double sourceScale = std::max(glm::compMax(sourceNode->scale()), 1.0);
const double casterScale = std::max(glm::compMax(casterNode->scale()), 1.0);
const double actualSourceRadius = shadowConf.source.second * sourceScale;
const double actualCasterRadius = shadowConf.caster.second * casterScale;
// First we determine if the caster is shadowing the current planet
// (all calculations in World Coordinates):
glm::dvec3 planetCasterVec = casterPos - data.modelTransform.translation;
glm::dvec3 sourceCasterVec = casterPos - sourcePos;
double scLength = glm::length(sourceCasterVec);
glm::dvec3 planetCasterProj =
const glm::dvec3 planetCasterVec =
casterPos - data.modelTransform.translation;
const glm::dvec3 sourceCasterVec = casterPos - sourcePos;
const double scLength = glm::length(sourceCasterVec);
const glm::dvec3 planetCasterProj =
(glm::dot(planetCasterVec, sourceCasterVec) / (scLength * scLength)) *
sourceCasterVec;
double dTest = glm::length(planetCasterVec - planetCasterProj);
double xpTest = shadowConf.caster.second * casterScale *
scLength /
(shadowConf.source.second * sourceScale +
shadowConf.caster.second * casterScale);
double rpTest = shadowConf.caster.second * casterScale *
const double dTest = glm::length(planetCasterVec - planetCasterProj);
const double xpTest = actualCasterRadius * scLength /
(actualSourceRadius + actualCasterRadius);
const double rpTest = actualCasterRadius *
(glm::length(planetCasterProj) + xpTest) / xpTest;
double casterDistSun = glm::length(casterPos - sunPosWorld);
double planetDistSun = glm::length(
const double casterDistSun = glm::length(casterPos - sunPosWorld);
const double planetDistSun = glm::length(
data.modelTransform.translation - sunPosWorld
);
@@ -422,11 +465,13 @@ void AtmosphereDeferredcaster::preRaycast(const RenderData& data, const Deferred
{
// The current caster is shadowing the current planet
shadow.isShadowing = true;
shadow.rs = shadowConf.source.second * sourceScale;
shadow.rc = shadowConf.caster.second * casterScale;
shadow.radiusSource = actualSourceRadius;
shadow.radiusCaster = actualCasterRadius;
shadow.sourceCasterVec = glm::normalize(sourceCasterVec);
shadow.xp = xpTest;
shadow.xu = shadow.rc * scLength / (shadow.rs - shadow.rc);
shadow.penumbra = xpTest;
shadow.umbra =
shadow.radiusCaster * scLength /
(shadow.radiusSource - shadow.radiusCaster);
shadow.casterPositionVec = casterPos;
}
_shadowDataArrayCache.push_back(shadow);
@@ -436,44 +481,49 @@ void AtmosphereDeferredcaster::preRaycast(const RenderData& data, const Deferred
unsigned int counter = 0;
for (const ShadowRenderingStruct& sd : _shadowDataArrayCache) {
// Add the counter
char* bf = fmt::format_to(_uniformNameBuffer + 16, "{}", counter);
char* bf = std::format_to(_uniformNameBuffer + 16, "{}", counter);
std::strcpy(bf, "].isShadowing\0");
prg.setUniform(_uniformNameBuffer, sd.isShadowing);
program.setUniform(_uniformNameBuffer, sd.isShadowing);
if (sd.isShadowing) {
std::strcpy(bf, "].xp\0");
prg.setUniform(_uniformNameBuffer, sd.xp);
program.setUniform(_uniformNameBuffer, sd.penumbra);
std::strcpy(bf, "].xu\0");
prg.setUniform(_uniformNameBuffer, sd.xu);
program.setUniform(_uniformNameBuffer, sd.umbra);
std::strcpy(bf, "].rc\0");
prg.setUniform(_uniformNameBuffer, sd.rc);
program.setUniform(_uniformNameBuffer, sd.radiusCaster);
std::strcpy(bf, "].sourceCasterVec\0");
prg.setUniform(_uniformNameBuffer, sd.sourceCasterVec);
program.setUniform(_uniformNameBuffer, sd.sourceCasterVec);
std::strcpy(bf, "].casterPositionVec\0");
prg.setUniform(_uniformNameBuffer, sd.casterPositionVec);
program.setUniform(_uniformNameBuffer, sd.casterPositionVec);
}
counter++;
}
prg.setUniform(_uniformCache.hardShadows, _hardShadowsEnabled);
program.setUniform(_uniformCache.hardShadows, _hardShadowsEnabled);
}
_transmittanceTableTextureUnit.activate();
glBindTexture(GL_TEXTURE_2D, _transmittanceTableTexture);
prg.setUniform(_uniformCache.transmittanceTexture, _transmittanceTableTextureUnit);
program.setUniform(
_uniformCache.transmittanceTexture,
_transmittanceTableTextureUnit
);
_irradianceTableTextureUnit.activate();
glBindTexture(GL_TEXTURE_2D, _irradianceTableTexture);
prg.setUniform(_uniformCache.irradianceTexture, _irradianceTableTextureUnit);
program.setUniform(_uniformCache.irradianceTexture, _irradianceTableTextureUnit);
_inScatteringTableTextureUnit.activate();
glBindTexture(GL_TEXTURE_3D, _inScatteringTableTexture);
prg.setUniform(_uniformCache.inscatterTexture, _inScatteringTableTextureUnit);
program.setUniform(_uniformCache.inscatterTexture, _inScatteringTableTextureUnit);
}
void AtmosphereDeferredcaster::postRaycast(const RenderData&, const DeferredcastData&,
ghoul::opengl::ProgramObject&)
{
ZoneScoped
ZoneScoped;
TracyGpuZone("Atmosphere postRaycast");
// Deactivate the texture units
_transmittanceTableTextureUnit.deactivate();
@@ -496,13 +546,17 @@ std::filesystem::path AtmosphereDeferredcaster::helperPath() const {
void AtmosphereDeferredcaster::initializeCachedVariables(
ghoul::opengl::ProgramObject& program)
{
ghoul::opengl::updateUniformLocations(program, _uniformCache, UniformNames);
ghoul::opengl::updateUniformLocations(program, _uniformCache);
}
void AtmosphereDeferredcaster::setModelTransform(glm::dmat4 transform) {
_modelTransform = std::move(transform);
}
void AtmosphereDeferredcaster::setOpacity(float opacity) {
_opacity = opacity;
}
void AtmosphereDeferredcaster::setParameters(float atmosphereRadius, float planetRadius,
float averageGroundReflectance,
float groundRadianceEmission,
@@ -513,7 +567,8 @@ void AtmosphereDeferredcaster::setParameters(float atmosphereRadius, float plane
glm::vec3 ozoneExtinctionCoefficients,
glm::vec3 mieScatteringCoefficients,
glm::vec3 mieExtinctionCoefficients,
bool sunFollowing)
bool sunFollowing, float sunAngularSize,
SceneGraphNode* lightSourceNode)
{
_atmosphereRadius = atmosphereRadius;
_atmospherePlanetRadius = planetRadius;
@@ -530,6 +585,9 @@ void AtmosphereDeferredcaster::setParameters(float atmosphereRadius, float plane
_mieScatteringCoeff = std::move(mieScatteringCoefficients);
_mieExtinctionCoeff = std::move(mieExtinctionCoefficients);
_sunFollowingCameraEnabled = sunFollowing;
_sunAngularSize = sunAngularSize;
// The light source may be nullptr which we interpret to mean a position of (0,0,0)
_lightSourceNode = lightSourceNode;
}
void AtmosphereDeferredcaster::setHardShadows(bool enabled) {
@@ -537,7 +595,7 @@ void AtmosphereDeferredcaster::setHardShadows(bool enabled) {
}
void AtmosphereDeferredcaster::calculateTransmittance() {
ZoneScoped
ZoneScoped;
glFramebufferTexture(
GL_FRAMEBUFFER,
@@ -564,8 +622,8 @@ void AtmosphereDeferredcaster::calculateTransmittance() {
program->setUniform("HO", _ozoneHeightScale);
program->setUniform("betaOzoneExtinction", _ozoneExtinctionCoeff);
constexpr float Black[] = { 0.f, 0.f, 0.f, 0.f };
glClearBufferfv(GL_COLOR, 0, Black);
constexpr glm::vec4 Black = glm::vec4(0.f, 0.f, 0.f, 0.f);
glClearBufferfv(GL_COLOR, 0, glm::value_ptr(Black));
glDrawArrays(GL_TRIANGLES, 0, 6);
if (_saveCalculationTextures) {
saveTextureFile("transmittance_texture.ppm", _transmittanceTableSize);
@@ -574,9 +632,9 @@ void AtmosphereDeferredcaster::calculateTransmittance() {
}
GLuint AtmosphereDeferredcaster::calculateDeltaE() {
ZoneScoped
ZoneScoped;
GLuint deltaE = createTexture(_deltaETableSize, "DeltaE");
const GLuint deltaE = createTexture(_deltaETableSize, "DeltaE");
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, deltaE, 0);
glViewport(0, 0, _deltaETableSize.x, _deltaETableSize.y);
using ProgramObject = ghoul::opengl::ProgramObject;
@@ -603,14 +661,14 @@ GLuint AtmosphereDeferredcaster::calculateDeltaE() {
}
std::pair<GLuint, GLuint> AtmosphereDeferredcaster::calculateDeltaS() {
ZoneScoped
ZoneScoped;
GLuint deltaSRayleigh = createTexture(_textureSize, "DeltaS Rayleigh", 3);
const GLuint deltaSRayleigh = createTexture(_textureSize, "DeltaS Rayleigh", 3);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, deltaSRayleigh, 0);
GLuint deltaSMie = createTexture(_textureSize, "DeltaS Mie", 3);
const GLuint deltaSMie = createTexture(_textureSize, "DeltaS Mie", 3);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, deltaSMie, 0);
GLenum colorBuffers[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, colorBuffers);
std::array<GLenum, 2> colorBuffers = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, colorBuffers.data());
glViewport(0, 0, _textureSize.x, _textureSize.y);
using ProgramObject = ghoul::opengl::ProgramObject;
std::unique_ptr<ProgramObject> program = ProgramObject::Build(
@@ -649,15 +707,15 @@ std::pair<GLuint, GLuint> AtmosphereDeferredcaster::calculateDeltaS() {
);
}
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, drawBuffers);
const std::array<GLenum, 1> drawBuffers = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, drawBuffers.data());
program->deactivate();
return { deltaSRayleigh, deltaSMie };
}
void AtmosphereDeferredcaster::calculateIrradiance() {
ZoneScoped
ZoneScoped;
glFramebufferTexture(
GL_FRAMEBUFFER,
@@ -686,7 +744,7 @@ void AtmosphereDeferredcaster::calculateIrradiance() {
void AtmosphereDeferredcaster::calculateInscattering(GLuint deltaSRayleigh,
GLuint deltaSMie)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(
GL_FRAMEBUFFER,
@@ -734,7 +792,7 @@ void AtmosphereDeferredcaster::calculateDeltaJ(int scatteringOrder,
GLuint deltaJ, GLuint deltaE,
GLuint deltaSRayleigh, GLuint deltaSMie)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, deltaJ, 0);
glViewport(0, 0, _textureSize.x, _textureSize.y);
@@ -780,7 +838,7 @@ void AtmosphereDeferredcaster::calculateDeltaJ(int scatteringOrder,
}
if (_saveCalculationTextures) {
saveTextureFile(
fmt::format("deltaJ_texture-scattering_order-{}.ppm", scatteringOrder),
std::format("deltaJ_texture-scattering_order-{}.ppm", scatteringOrder),
glm::ivec2(_textureSize)
);
}
@@ -792,7 +850,7 @@ void AtmosphereDeferredcaster::calculateDeltaE(int scatteringOrder,
GLuint deltaE, GLuint deltaSRayleigh,
GLuint deltaSMie)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, deltaE, 0);
glViewport(0, 0, _deltaETableSize.x, _deltaETableSize.y);
@@ -820,7 +878,7 @@ void AtmosphereDeferredcaster::calculateDeltaE(int scatteringOrder,
glDrawArrays(GL_TRIANGLES, 0, 6);
if (_saveCalculationTextures) {
saveTextureFile(
fmt::format("deltaE_texture-scattering_order-{}.ppm", scatteringOrder),
std::format("deltaE_texture-scattering_order-{}.ppm", scatteringOrder),
_deltaETableSize
);
}
@@ -831,7 +889,7 @@ void AtmosphereDeferredcaster::calculateDeltaS(int scatteringOrder,
ghoul::opengl::ProgramObject& program,
GLuint deltaSRayleigh, GLuint deltaJ)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, deltaSRayleigh, 0);
glViewport(0, 0, _textureSize.x, _textureSize.y);
@@ -860,7 +918,7 @@ void AtmosphereDeferredcaster::calculateDeltaS(int scatteringOrder,
}
if (_saveCalculationTextures) {
saveTextureFile(
fmt::format("deltaS_texture-scattering_order-{}.ppm", scatteringOrder),
std::format("deltaS_texture-scattering_order-{}.ppm", scatteringOrder),
glm::ivec2(_textureSize)
);
}
@@ -871,7 +929,7 @@ void AtmosphereDeferredcaster::calculateIrradiance(int scatteringOrder,
ghoul::opengl::ProgramObject& program,
GLuint deltaE)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(
GL_FRAMEBUFFER,
@@ -891,7 +949,7 @@ void AtmosphereDeferredcaster::calculateIrradiance(int scatteringOrder,
glDrawArrays(GL_TRIANGLES, 0, 6);
if (_saveCalculationTextures) {
saveTextureFile(
fmt::format("irradianceTable_order-{}.ppm", scatteringOrder),
std::format("irradianceTable_order-{}.ppm", scatteringOrder),
_deltaETableSize
);
}
@@ -899,11 +957,11 @@ void AtmosphereDeferredcaster::calculateIrradiance(int scatteringOrder,
}
void AtmosphereDeferredcaster::calculateInscattering(int scatteringOrder,
ghoul::opengl::ProgramObject& prg,
GLuint deltaSRayleigh)
ghoul::opengl::ProgramObject& program,
GLuint deltaSRayleigh)
{
ZoneScoped
ZoneScoped;
glFramebufferTexture(
GL_FRAMEBUFFER,
@@ -912,31 +970,31 @@ void AtmosphereDeferredcaster::calculateInscattering(int scatteringOrder,
0
);
glViewport(0, 0, _textureSize.x, _textureSize.y);
prg.activate();
program.activate();
ghoul::opengl::TextureUnit unit;
unit.activate();
glBindTexture(GL_TEXTURE_3D, deltaSRayleigh);
prg.setUniform("deltaSTexture", unit);
prg.setUniform("SAMPLES_MU_S", _muSSamples);
prg.setUniform("SAMPLES_NU", _nuSamples);
prg.setUniform("SAMPLES_MU", _muSamples);
prg.setUniform("SAMPLES_R", _rSamples);
program.setUniform("deltaSTexture", unit);
program.setUniform("SAMPLES_MU_S", _muSSamples);
program.setUniform("SAMPLES_NU", _nuSamples);
program.setUniform("SAMPLES_MU", _muSamples);
program.setUniform("SAMPLES_R", _rSamples);
for (int layer = 0; layer < _rSamples; ++layer) {
prg.setUniform("layer", layer);
program.setUniform("layer", layer);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
if (_saveCalculationTextures) {
saveTextureFile(
fmt::format("inscatteringTable_order-{}.ppm", scatteringOrder),
std::format("inscatteringTable_order-{}.ppm", scatteringOrder),
glm::ivec2(_textureSize)
);
}
prg.deactivate();
program.deactivate();
}
void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
ZoneScoped
ZoneScoped;
using ProgramObject = ghoul::opengl::ProgramObject;
std::unique_ptr<ProgramObject> deltaJProgram = ProgramObject::Build(
@@ -970,38 +1028,38 @@ void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
// Saves current FBO first
GLint defaultFBO;
GLint defaultFBO = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
GLint viewport[4];
global::renderEngine->openglStateCache().viewport(viewport);
std::array<GLint, 4> viewport;
global::renderEngine->openglStateCache().viewport(viewport.data());
// Creates the FBO for the calculations
GLuint calcFBO;
GLuint calcFBO = 0;
glGenFramebuffers(1, &calcFBO);
glBindFramebuffer(GL_FRAMEBUFFER, calcFBO);
GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, drawBuffers);
std::array<GLenum, 1> drawBuffers = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, drawBuffers.data());
// Prepare for rendering/calculations
GLuint quadVao;
GLuint quadVao = 0;
glGenVertexArrays(1, &quadVao);
glBindVertexArray(quadVao);
GLuint quadVbo;
GLuint quadVbo = 0;
glGenBuffers(1, &quadVbo);
glBindBuffer(GL_ARRAY_BUFFER, quadVbo);
const GLfloat VertexData[] = {
// x y z
constexpr std::array<GLfloat, 12> VertexData = {
// x y
-1.f, -1.f,
1.f, 1.f,
1.f, 1.f,
-1.f, 1.f,
-1.f, -1.f,
1.f, -1.f,
1.f, 1.f,
1.f, -1.f,
1.f, 1.f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData), VertexData, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData), VertexData.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
@@ -1013,7 +1071,7 @@ void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
calculateTransmittance();
// line 2 in algorithm 4.1
GLuint deltaETable = calculateDeltaE();
const GLuint deltaETable = calculateDeltaE();
// line 3 in algorithm 4.1
auto [deltaSRayleighTable, deltaSMieTable] = calculateDeltaS();
@@ -1024,7 +1082,7 @@ void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
// line 5 in algorithm 4.1
calculateInscattering(deltaSRayleighTable, deltaSMieTable);
GLuint deltaJTable = createTexture(_textureSize, "DeltaJ", 3);
const GLuint deltaJTable = createTexture(_textureSize, "DeltaJ", 3);
// loop in line 6 in algorithm 4.1
for (int scatteringOrder = 2; scatteringOrder <= 4; ++scatteringOrder) {
@@ -1086,7 +1144,7 @@ void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
// Restores system state
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
global::renderEngine->openglStateCache().setViewportState(viewport);
global::renderEngine->openglStateCache().setViewportState(viewport.data());
glDeleteBuffers(1, &quadVbo);
glDeleteVertexArrays(1, &quadVao);
glDeleteFramebuffers(1, &calcFBO);
@@ -1095,7 +1153,8 @@ void AtmosphereDeferredcaster::calculateAtmosphereParameters() {
LDEBUG("Ended precalculations for Atmosphere effects");
}
void AtmosphereDeferredcaster::step3DTexture(ghoul::opengl::ProgramObject& prg, int layer)
void AtmosphereDeferredcaster::step3DTexture(ghoul::opengl::ProgramObject& prg,
int layer) const
{
// See OpenGL redbook 8th Edition page 556 for Layered Rendering
const float planet2 = _atmospherePlanetRadius * _atmospherePlanetRadius;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -46,10 +46,10 @@ struct DeferredcastData;
struct ShadowConfiguration;
struct ShadowRenderingStruct {
double xu = 0.0;
double xp = 0.0;
double rs = 0.0;
double rc = 0.0;
double umbra = 0.0;
double penumbra = 0.0;
double radiusSource = 0.0;
double radiusCaster = 0.0;
glm::dvec3 sourceCasterVec = glm::dvec3(0.0);
glm::dvec3 casterPositionVec = glm::dvec3(0.0);
bool isShadowing = false;
@@ -75,10 +75,12 @@ public:
void initializeCachedVariables(ghoul::opengl::ProgramObject& program) override;
void update(const UpdateData&) override;
float eclipseShadow(const glm::dvec3& position);
void calculateAtmosphereParameters();
void setModelTransform(glm::dmat4 transform);
void setOpacity(float opacity);
void setParameters(float atmosphereRadius, float planetRadius,
float averageGroundReflectance, float groundRadianceEmission,
@@ -86,12 +88,12 @@ public:
float mieHeightScale, float miePhaseConstant, float sunRadiance,
glm::vec3 rayScatteringCoefficients, glm::vec3 ozoneExtinctionCoefficients,
glm::vec3 mieScatteringCoefficients, glm::vec3 mieExtinctionCoefficients,
bool sunFollowing);
bool sunFollowing, float sunAngularSize, SceneGraphNode* lightSourceNode);
void setHardShadows(bool enabled);
private:
void step3DTexture(ghoul::opengl::ProgramObject& prg, int layer);
void step3DTexture(ghoul::opengl::ProgramObject& prg, int layer) const;
void calculateTransmittance();
GLuint calculateDeltaE();
@@ -112,12 +114,13 @@ private:
ghoul::opengl::ProgramObject& program, GLuint deltaSRayleigh);
UniformCache(cullAtmosphere, Rg, Rt, groundRadianceEmission, HR, betaRayleigh, HM,
betaMieExtinction, mieG, sunRadiance, ozoneLayerEnabled, HO, betaOzoneExtinction,
SAMPLES_R, SAMPLES_MU, SAMPLES_MU_S, SAMPLES_NU, inverseModelTransformMatrix,
modelTransformMatrix, projectionToModelTransform, viewToWorldMatrix,
camPosObj, sunDirectionObj, hardShadows, transmittanceTexture, irradianceTexture,
inscatterTexture) _uniformCache;
UniformCache(cullAtmosphere, opacity, Rg, Rt, groundRadianceEmission, HR,
betaRayleigh, HM, betaMieExtinction, mieG, sunRadiance, ozoneLayerEnabled, HO,
betaOzoneExtinction, SAMPLES_R, SAMPLES_MU, SAMPLES_MU_S, SAMPLES_NU,
inverseModelTransformMatrix, modelTransformMatrix,
projectionToModelTransformMatrix, viewToWorldMatrix, camPosObj, sunDirectionObj,
hardShadows, transmittanceTexture, irradianceTexture, inscatterTexture,
sunAngularSize) _uniformCache;
ghoul::opengl::TextureUnit _transmittanceTableTextureUnit;
ghoul::opengl::TextureUnit _irradianceTableTextureUnit;
@@ -139,6 +142,8 @@ private:
float _mieHeightScale = 0.f;
float _miePhaseConstant = 0.f;
float _sunRadianceIntensity = 5.f;
float _sunAngularSize = 0.3f;
SceneGraphNode* _lightSourceNode = nullptr;
glm::vec3 _rayleighScatteringCoeff = glm::vec3(0.f);
glm::vec3 _ozoneExtinctionCoeff = glm::vec3(0.f);
@@ -156,6 +161,7 @@ private:
const glm::ivec3 _textureSize;
glm::dmat4 _modelTransform;
float _opacity = 1.f;
// Eclipse Shadows
std::vector<ShadowConfiguration> _shadowConfArray;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -30,10 +30,12 @@
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/query/query.h>
#include <ghoul/misc/profiling.h>
#include <openspace/properties/property.h>
#include <openspace/rendering/deferredcastermanager.h>
#include <math.h>
#include <algorithm>
#include <cmath>
namespace {
constexpr float KM_TO_M = 1000.f;
@@ -41,7 +43,8 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo AtmosphereHeightInfo = {
"AtmosphereHeight",
"Atmosphere Height (KM)",
"The thickness of the atmosphere in km"
"The thickness of the atmosphere in kilometers.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo AverageGroundReflectanceInfo =
@@ -49,59 +52,68 @@ namespace {
"AverageGroundReflectance",
"Average Ground Reflectance (%)",
"Average percentage of light reflected by the ground during the pre-calculation "
"phase"
"phase.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo GroundRadianceEmissionInfo = {
"GroundRadianceEmission",
"Percentage of initial radiance emitted from ground",
"Multiplier of the ground radiance color during the rendering phase"
"Multiplier of the ground radiance color during the rendering phase.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RayleighHeightScaleInfo = {
"RayleighHeightScale",
"Rayleigh Scale Height (KM)",
"It is the vertical distance over which the density and pressure fall by a "
"constant factor"
"The vertical distance over which the density and pressure falls by a constant "
"factor.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RayleighScatteringCoeffInfo =
{
"RayleighScatteringCoeff",
"Rayleigh Scattering Coeff",
"Rayleigh sea-level scattering coefficients in meters"
"Rayleigh sea-level scattering coefficients in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OzoneLayerInfo = {
"Ozone",
"Ozone Layer Enabled",
"Enables/Disable Ozone Layer during pre-calculation phase"
"Enables/Disable Ozone Layer during pre-calculation phase.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OzoneHeightScaleInfo = {
"OzoneLayerHeightScale",
"Ozone Scale Height (KM)",
"It is the vertical distance over which the density and pressure fall by a "
"constant factor"
"Ozone Scale Height (km)",
"The vertical distance over which the density and pressure fall by a constant "
"factor, given in kilometers.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OzoneLayerCoeffInfo = {
"OzoneLayerCoeff",
"Ozone Layer Extinction Coeff",
"Ozone scattering coefficients in meters"
"Ozone Layer Extinction Coefficient",
"Ozone scattering coefficients in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo MieHeightScaleInfo = {
"MieHeightScale",
"Mie Scale Height (KM)",
"It is the vertical distance over which the density and pressure fall by a "
"constant factor"
"Mie Scale Height (km)",
"The vertical distance over which the density and pressure fall by a constant "
"factor, given in kilometers.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo MieScatteringCoeffInfo = {
"MieScatteringCoeff",
"Mie Scattering Coeff",
"Mie sea-level scattering coefficients in meters"
"Mie Scattering Coefficient",
"Mie sea-level scattering coefficients in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo
@@ -109,19 +121,22 @@ namespace {
{
"MieScatteringExtinctionPropCoefficient",
"Mie Scattering/Extinction Proportion Coefficient (%)",
"Mie Scattering/Extinction Proportion Coefficient (%)"
"Mie Scattering/Extinction Proportion Coefficient.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo MieAsymmetricFactorGInfo = {
"MieAsymmetricFactorG",
"Mie Asymmetric Factor G",
"Averaging of the scattering angle over a high number of scattering events"
"Averaging of the scattering angle over a high number of scattering events.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo SunIntensityInfo = {
"SunIntensity",
"Sun Intensity",
"Unitless for now"
"A unitless value that controls the intensity/brightness of the Sun.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo
@@ -129,59 +144,78 @@ namespace {
{
"SunFollowingCamera",
"Enable Sun On Camera Position",
"When selected the Sun is artificially positioned behind the observer all times"
"When selected the Sun is artificially positioned behind the observer all times.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EclipseHardShadowsInfo = {
"EclipseHardShadowsInfo",
"EclipseHardShadows",
"Enable Hard Shadows for Eclipses",
"Enable/Disables hard shadows through the atmosphere"
"Enables/Disables hard shadows through the atmosphere.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AtmosphereDimmingHeightInfo ={
"AtmosphereDimmingHeight",
"Atmosphere Dimming Height",
"Percentage of the atmosphere where other objects, such as the stars, are faded"
"Percentage of the atmosphere where other objects, such as the stars, are faded.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo SunsetAngleInfo = {
"AtmosphereDimmingSunsetAngle",
"Atmosphere Dimming Sunset Angle",
"The angle (degrees) between the Camera and the Sun where the sunset starts, and "
"the atmosphere starts to fade in objects such as the stars"
"the atmosphere starts to fade in objects such as the stars.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo SunAngularSize = {
"SunAngularSize",
"Angular Size of the Sun",
"The angular size of the Sun in degrees.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo LightSourceNodeInfo = {
"LightSourceNode",
"Light Source",
"The name of a scene graph node to be used as the source of illumination "
"for the atmosphere. If not specified, the solar system's Sun is used.",
openspace::properties::Property::Visibility::AdvancedUser
};
struct [[codegen::Dictionary(RenderableAtmosphere)]] Parameters {
struct ShadowGroup {
// Individual light sources
// Individual light sources.
struct SourceElement {
// The scene graph node name of the source
// The scene graph node name of the source.
std::string name;
// The radius of the object in meters
// The radius of the object in meters.
double radius;
};
// A list of light sources
// A list of light sources.
std::vector<SourceElement> sources;
// Individual shadow casters
// Individual shadow casters.
struct CasterElement {
// The scene graph node name of the source
// The scene graph node name of the source.
std::string name;
// The radius of the object in meters
// The radius of the object in meters.
double radius;
};
// A list of objects that cast light on this atmosphere
// A list of objects that cast light on this atmosphere.
std::vector<CasterElement> casters;
};
// Declares shadow groups, meaning which nodes are considered in shadow
// calculations
// calculations.
std::optional<ShadowGroup> shadowGroup;
// [[codegen::verbatim(AtmosphereHeightInfo.description)]]
float atmosphereHeight;
// The radius of the planet in meters
// The radius of the planet in meters.
float planetRadius;
float planetAverageGroundReflectance;
@@ -236,6 +270,12 @@ namespace {
// [[codegen::verbatim(SunsetAngleInfo.description)]]
std::optional<glm::vec2> sunsetAngle;
// [[codegen::verbatim(SunAngularSize.description)]]
std::optional<float> sunAngularSize [[codegen::inrange(0.0, 180.0)]];
// [[codegen::verbatim(LightSourceNodeInfo.description)]]
std::optional<std::string> lightSourceNode;
};
#include "renderableatmosphere_codegen.cpp"
@@ -249,7 +289,7 @@ documentation::Documentation RenderableAtmosphere::Documentation() {
RenderableAtmosphere::RenderableAtmosphere(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _atmosphereHeight(AtmosphereHeightInfo, 60.f, 0.1f, 99.0f)
, _atmosphereHeight(AtmosphereHeightInfo, 60.f, 0.1f, 99.f)
, _groundAverageReflectance(AverageGroundReflectanceInfo, 0.f, 0.f, 1.f)
, _groundRadianceEmission(GroundRadianceEmissionInfo, 0.f, 0.f, 1.f)
, _rayleighHeightScale(RayleighHeightScaleInfo, 0.f, 0.1f, 50.f)
@@ -276,8 +316,11 @@ RenderableAtmosphere::RenderableAtmosphere(const ghoul::Dictionary& dictionary)
, _sunIntensity(SunIntensityInfo, 5.f, 0.1f, 1000.f)
, _sunFollowingCameraEnabled(EnableSunOnCameraPositionInfo, false)
, _hardShadowsEnabled(EclipseHardShadowsInfo, false)
, _sunAngularSize(SunAngularSize, 0.3f, 0.f, 180.f)
, _lightSourceNodeName(LightSourceNodeInfo)
, _atmosphereDimmingHeight(AtmosphereDimmingHeightInfo, 0.7f, 0.f, 1.f)
, _atmosphereDimmingSunsetAngle(SunsetAngleInfo,
, _atmosphereDimmingSunsetAngle(
SunsetAngleInfo,
glm::vec2(95.f, 100.f), glm::vec2(0.f), glm::vec2(180.f)
)
{
@@ -384,7 +427,8 @@ RenderableAtmosphere::RenderableAtmosphere(const ghoul::Dictionary& dictionary)
setBoundingSphere(_planetRadius * 1000.0);
_atmosphereDimmingHeight = p.atmosphereDimmingHeight.value_or(_atmosphereDimmingHeight);
_atmosphereDimmingHeight =
p.atmosphereDimmingHeight.value_or(_atmosphereDimmingHeight);
addProperty(_atmosphereDimmingHeight);
_atmosphereDimmingSunsetAngle = p.sunsetAngle.value_or(
@@ -394,6 +438,34 @@ RenderableAtmosphere::RenderableAtmosphere(const ghoul::Dictionary& dictionary)
properties::Property::ViewOptions::MinMaxRange
);
addProperty(_atmosphereDimmingSunsetAngle);
_sunAngularSize = p.sunAngularSize.value_or(_sunAngularSize);
_sunAngularSize.onChange(updateWithoutCalculation);
addProperty(_sunAngularSize);
_lightSourceNodeName.onChange([this]() {
if (_lightSourceNodeName.value().empty()) {
_lightSourceNode = nullptr;
return;
}
SceneGraphNode* n = sceneGraphNode(_lightSourceNodeName);
if (!n) {
LERRORC(
"RenderabeAtmosphere",
std::format(
"Could not find node '{}' as illumination for '{}'",
_lightSourceNodeName.value(), identifier()
)
);
}
else {
_lightSourceNode = n;
_deferredCasterNeedsUpdate = true;
}
});
_lightSourceNodeName = p.lightSourceNode.value_or("");
addProperty(_lightSourceNodeName);
}
void RenderableAtmosphere::deinitializeGL() {
@@ -425,11 +497,11 @@ glm::dmat4 RenderableAtmosphere::computeModelTransformMatrix(const TransformData
glm::scale(glm::dmat4(1.0), glm::dvec3(data.scale));
}
void RenderableAtmosphere::render(const RenderData& data, RendererTasks& renderTask) {
ZoneScoped
void RenderableAtmosphere::render(const RenderData& data, RendererTasks& rendererTask) {
ZoneScoped;
DeferredcasterTask task{ _deferredcaster.get(), data };
renderTask.deferredcasterTasks.push_back(task);
DeferredcasterTask task = { _deferredcaster.get(), data };
rendererTask.deferredcasterTasks.push_back(std::move(task));
}
void RenderableAtmosphere::update(const UpdateData& data) {
@@ -443,57 +515,10 @@ void RenderableAtmosphere::update(const UpdateData& data) {
}
glm::dmat4 modelTransform = computeModelTransformMatrix(data.modelTransform);
_deferredcaster->setModelTransform(modelTransform);
_deferredcaster->setModelTransform(std::move(modelTransform));
_deferredcaster->setOpacity(opacity());
_deferredcaster->update(data);
// Calculate atmosphere dimming coefficient
// Calculate if the camera is in the atmosphere and if it is in the fading region
float atmosphereDimming = 1.f;
glm::dvec3 cameraPos = global::navigationHandler->camera()->positionVec3();
glm::dvec3 planetPos = glm::dvec3(modelTransform * glm::dvec4(0.0, 0.0, 0.0, 1.0));
float cameraDistance = static_cast<float>(glm::distance(planetPos, cameraPos));
// Atmosphere height is in KM
float atmosphereEdge = KM_TO_M * (_planetRadius + _atmosphereHeight);
// Height of the atmosphere where the objects will be faded
float atmosphereFadingHeight = KM_TO_M * _atmosphereDimmingHeight * _atmosphereHeight;
float atmosphereInnerEdge = atmosphereEdge - atmosphereFadingHeight;
bool cameraIsInAtmosphere = cameraDistance < atmosphereEdge;
bool cameraIsInFadingRegion = cameraDistance > atmosphereInnerEdge;
// Check if camera is in sunset
glm::dvec3 normalUnderCamera = glm::normalize(cameraPos - planetPos);
glm::dvec3 vecToSun = glm::normalize(-planetPos);
float cameraSunAngle = glm::degrees(static_cast<float>(
glm::acos(glm::dot(vecToSun, normalUnderCamera))
));
float sunsetStart = _atmosphereDimmingSunsetAngle.value().x;
float sunsetEnd = _atmosphereDimmingSunsetAngle.value().y;
// If cameraSunAngle is more than 90 degrees, we are in shaded part of globe
bool cameraIsInSun = cameraSunAngle <= sunsetEnd;
bool cameraIsInSunset = cameraSunAngle > sunsetStart && cameraIsInSun;
// Fade if camera is inside the atmosphere
if (cameraIsInAtmosphere && cameraIsInSun) {
// If camera is in fading part of the atmosphere
// Fade with regards to altitude
if (cameraIsInFadingRegion) {
// Fading - linear interpolation
atmosphereDimming = (cameraDistance - atmosphereInnerEdge) /
atmosphereFadingHeight;
}
else {
// Camera is below fading region - atmosphere dims objects completely
atmosphereDimming = 0.0;
}
if (cameraIsInSunset) {
// Fading - linear interpolation
atmosphereDimming = (cameraSunAngle - sunsetStart) /
(sunsetEnd - sunsetStart);
}
global::navigationHandler->camera()->setAtmosphereDimmingFactor(
atmosphereDimming
);
}
setDimmingCoefficient(computeModelTransformMatrix(data.modelTransform));
}
void RenderableAtmosphere::updateAtmosphereParameters() {
@@ -515,9 +540,87 @@ void RenderableAtmosphere::updateAtmosphereParameters() {
_ozoneCoeff,
_mieScatteringCoeff,
_mieExtinctionCoeff,
_sunFollowingCameraEnabled
_sunFollowingCameraEnabled,
_sunAngularSize,
_lightSourceNode
);
_deferredcaster->setHardShadows(_hardShadowsEnabled);
}
// Calculate atmosphere dimming coefficient
void RenderableAtmosphere::setDimmingCoefficient(const glm::dmat4& modelTransform) {
// Calculate if the camera is in the atmosphere and if it is in the sunny region
const glm::dvec3 cameraPos = global::navigationHandler->camera()->positionVec3();
// TODO: change the assumption that the Sun is placed in the origin
const glm::dvec3 planetPos =
glm::dvec3(modelTransform * glm::dvec4(0.0, 0.0, 0.0, 1.0));
const glm::dvec3 normalUnderCamera = glm::normalize(cameraPos - planetPos);
const glm::dvec3 vecToSun = glm::normalize(-planetPos);
const float cameraDistance = static_cast<float>(glm::distance(planetPos, cameraPos));
const float cameraSunAngle = static_cast<float>(
glm::degrees(glm::acos(glm::dot(vecToSun, normalUnderCamera))
));
const float sunsetEnd = _atmosphereDimmingSunsetAngle.value().y;
// If cameraSunAngle is more than 90 degrees, we are in shaded part of globe
const bool cameraIsInSun = cameraSunAngle <= sunsetEnd;
// Atmosphere height is in KM
const float atmosphereEdge = KM_TO_M * (_planetRadius + _atmosphereHeight);
const bool cameraIsInAtmosphere = cameraDistance < atmosphereEdge;
// Don't fade if camera is not in the sunny part of an atmosphere
if (!cameraIsInAtmosphere || !cameraIsInSun) {
return;
}
// Else we need to fade the objects
// Height of the atmosphere where the objects will be faded
const float atmosphereFadingHeight =
KM_TO_M * _atmosphereDimmingHeight * _atmosphereHeight;
const float atmosphereInnerEdge = atmosphereEdge - atmosphereFadingHeight;
const bool cameraIsInFadingRegion = cameraDistance > atmosphereInnerEdge;
// Check if camera is in sunset
const float sunsetStart = _atmosphereDimmingSunsetAngle.value().x;
const bool cameraIsInSunset = cameraSunAngle > sunsetStart && cameraIsInSun;
// See if we are inside of an eclipse shadow
float eclipseShadow = _deferredcaster->eclipseShadow(cameraPos);
const bool cameraIsInEclipse = std::abs(eclipseShadow - 1.f) > glm::epsilon<float>();
// Invert shadow and multiply with itself to make it more narrow
eclipseShadow = std::pow(1.f - eclipseShadow, 2.f);
float atmosphereDimming = 0.f;
if (cameraIsInSunset) {
// Fading - linear interpolation
atmosphereDimming = (cameraSunAngle - sunsetStart) /
(sunsetEnd - sunsetStart);
}
else if (cameraIsInFadingRegion && cameraIsInEclipse) {
// Fade with regards to altitude & eclipse shadow
// Fading - linear interpolation
const float fading =
(cameraDistance - atmosphereInnerEdge) / atmosphereFadingHeight;
atmosphereDimming = std::clamp(eclipseShadow + fading, 0.f, 1.f);
}
else if (cameraIsInFadingRegion) {
// Fade with regards to altitude
// Fading - linear interpolation
atmosphereDimming = (cameraDistance - atmosphereInnerEdge) /
atmosphereFadingHeight;
}
else if (cameraIsInEclipse) {
atmosphereDimming = eclipseShadow;
}
else {
// Camera is below fading region - atmosphere dims objects completely
atmosphereDimming = 0.f;
}
// Calculate dimming coefficient for stars, labels etc that are dimmed in the
// atmosphere
global::navigationHandler->camera()->setAtmosphereDimmingFactor(
atmosphereDimming
);
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,7 @@
#include <openspace/rendering/renderable.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
@@ -65,7 +65,7 @@ namespace planetgeometry { class PlanetGeometry; }
class RenderableAtmosphere : public Renderable {
public:
RenderableAtmosphere(const ghoul::Dictionary& dictionary);
explicit RenderableAtmosphere(const ghoul::Dictionary& dictionary);
void initializeGL() override;
void deinitializeGL() override;
@@ -79,6 +79,7 @@ public:
private:
glm::dmat4 computeModelTransformMatrix(const openspace::TransformData& data);
void updateAtmosphereParameters();
void setDimmingCoefficient(const glm::dmat4& modelTransform);
properties::FloatProperty _atmosphereHeight;
properties::FloatProperty _groundAverageReflectance;
@@ -95,6 +96,9 @@ private:
properties::FloatProperty _sunIntensity;
properties::BoolProperty _sunFollowingCameraEnabled;
properties::BoolProperty _hardShadowsEnabled;
properties::FloatProperty _sunAngularSize;
SceneGraphNode* _lightSourceNode = nullptr;
properties::StringProperty _lightSourceNodeName;
// Atmosphere dimming
properties::FloatProperty _atmosphereDimmingHeight;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -24,7 +24,7 @@
/*****************************************************************************************
* Modified parts of the code (4D texture mechanism, analytical transmittance etc) *
* from Eric Bruneton is used in the following code. *
* from Eric Bruneton is used in the following code. *
****************************************************************************************/
/**
@@ -67,17 +67,17 @@ const float ATM_EPSILON = 1.0;
// Calculate the distance of the ray starting at x (height r) until the planet's ground
// or top of atmosphere
// r := || vec(x) || e [0, Rt]
// mu := cosine of the zeith angle of vec(v). Or mu = (vec(x) * vec(v))/r
// mu := cosine of the zeith angle of vec(v). Or mu = (vec(x) * vec(v))/r
float rayDistance(float r, float mu, float Rt, float Rg) {
// The light ray starting at the observer in/on the atmosphere can have to possible end
// points: the top of the atmosphere or the planet ground. So the shortest path is the
// one we are looking for, otherwise we may be passing through the ground
// cosine law
float atmRadiusEps2 = (Rt + ATM_EPSILON) * (Rt + ATM_EPSILON);
float mu2 = mu * mu;
float r2 = r * r;
float rayDistanceAtmosphere = -r * mu + sqrt(r2 * (mu2 - 1.0) + atmRadiusEps2);
float rayDistanceAtmosphere = -r * mu + sqrt(r2 * (mu2 - 1.0) + atmRadiusEps2);
float delta = r2 * (mu2 - 1.0) + Rg*Rg;
// Ray may be hitting ground
@@ -108,7 +108,7 @@ void unmappingMuMuSunNu(float r, vec4 dhdH, int SAMPLES_MU, float Rg, float Rt,
// Pre-calculations
float r2 = r * r;
float Rg2 = Rg * Rg;
float halfSAMPLE_MU = float(SAMPLES_MU) / 2.0;
// If the (vec(x) dot vec(v))/r is negative, i.e., the light ray has great probability
// to touch the ground, we obtain mu considering the geometry of the ground
@@ -130,7 +130,7 @@ void unmappingMuMuSunNu(float r, vec4 dhdH, int SAMPLES_MU, float Rg, float Rt,
// cosine law: Rt^2 = r^2 + d^2 - 2rdcos(pi-theta) where cosine(theta) = mu
mu = (Rt*Rt - r2 - d * d) / (2.0 * r * d);
}
float modValueMuSun = mod(fragment.x, float(SAMPLES_MU_S)) / (float(SAMPLES_MU_S) - 1.0);
// The following mapping is different from the paper. See Collienne for an details.
muSun = tan((2.0 * modValueMuSun - 1.0 + 0.26) * 1.1) / tan(1.26 * 1.1);
@@ -148,7 +148,7 @@ vec3 transmittance(sampler2D tex, float r, float mu, float Rg, float Rt) {
float u_r = sqrt((r - Rg) / (Rt - Rg));
// See Collienne to understand the mapping
float u_mu = atan((mu + 0.15) / 1.15 * tan(1.5)) / 1.5;
return texture(tex, vec2(u_mu, u_r)).rgb;
}
@@ -161,7 +161,7 @@ vec3 transmittance(sampler2D tex, float r, float mu, float d, float Rg, float Rt
// Here we use the transmittance property: T(x,v) = T(x,d)*T(d,v) to, given a distance
// d, calculates that transmittance along that distance starting in x (height r):
// T(x,d) = T(x,v)/T(d,v).
//
//
// From cosine law: c^2 = a^2 + b^2 - 2*a*b*cos(ab)
float ri = sqrt(d * d + r * r + 2.0 * r * d * mu);
// mu_i = (vec(d) dot vec(v)) / r_i

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -24,7 +24,7 @@
/*****************************************************************************************
* Modified parts of the code (4D texture mechanism) from Eric Bruneton is used in the *
* following code. *
* following code. *
****************************************************************************************/
/**
@@ -64,6 +64,7 @@ in vec2 texCoord;
out vec4 renderTarget;
uniform int cullAtmosphere;
uniform float opacity;
uniform float Rg;
uniform float Rt;
uniform float groundRadianceEmission;
@@ -76,6 +77,7 @@ uniform vec3 betaMieExtinction;
uniform float mieG;
uniform float sunRadiance;
uniform bool ozoneLayerEnabled;
uniform float sunAngularSize;
uniform int SAMPLES_R;
uniform int SAMPLES_MU;
uniform int SAMPLES_MU_S;
@@ -86,7 +88,7 @@ uniform sampler3D inscatterTexture;
uniform sampler2D mainPositionTexture;
uniform sampler2D mainNormalTexture;
uniform sampler2D mainColorTexture;
uniform dmat4 inverseModelTransformMatrix;
uniform dmat4 inverseModelTransformMatrix;
uniform dmat4 modelTransformMatrix;
uniform dmat4 viewToWorldMatrix;
uniform dmat4 projectionToModelTransformMatrix;
@@ -118,40 +120,42 @@ uniform ShadowRenderingStruct shadowDataArray[numberOfShadows];
uniform int shadows;
uniform bool hardShadows;
float calcShadow(ShadowRenderingStruct shadowInfoArray[numberOfShadows], dvec3 position,
// Returns whether there is an eclipse in the x component and the strength of the
// shadowing in the y component
vec2 calcShadow(ShadowRenderingStruct shadowInfoArray[numberOfShadows], dvec3 position,
bool ground)
{
if (!shadowInfoArray[0].isShadowing) {
return 1.0;
return vec2(0.0, 1.0);
}
dvec3 pc = shadowInfoArray[0].casterPositionVec - position;
dvec3 scNorm = shadowInfoArray[0].sourceCasterVec;
dvec3 pcProj = dot(pc, scNorm) * scNorm;
dvec3 d = pc - pcProj;
float length_d = float(length(d));
double lengthPcProj = length(pcProj);
float r_p_pi = float(shadowInfoArray[0].rc * (lengthPcProj + shadowInfoArray[0].xp) / shadowInfoArray[0].xp);
float r_u_pi = float(shadowInfoArray[0].rc * (shadowInfoArray[0].xu - lengthPcProj) / shadowInfoArray[0].xu);
if (length_d < r_u_pi) {
// umbra
if (hardShadows) {
return ground ? 0.2 : 0.5;
return ground ? vec2(1.0, 0.2) : vec2(1.0, 0.5);
}
else {
// butterworth function
return sqrt(r_u_pi / (r_u_pi + pow(length_d, 4.0)));
return vec2(1.0, sqrt(r_u_pi / (r_u_pi + pow(length_d, 4.0))));
}
}
else if (length_d < r_p_pi) {
// penumbra
return hardShadows ? 0.5 : length_d / r_p_pi;
return hardShadows ? vec2(1.0, 0.5) : vec2(1.0, length_d / r_p_pi);
}
else {
return 1.0;
return vec2(1.0, 1.0);
}
}
@@ -240,7 +244,7 @@ bool atmosphereIntersection(Ray ray, double atmRadius, out double offset,
offset = 0.0;
maxLength = s + q;
}
return true;
}
@@ -257,7 +261,7 @@ Ray calculateRayRenderableGlobe(vec2 st) {
// Clip to Object Coords
dvec4 objectCoords = projectionToModelTransformMatrix * clipCoords;
objectCoords.xyz /= objectCoords.w;
// Building Ray
// Ray in object space (in KM)
Ray ray;
@@ -266,7 +270,7 @@ Ray calculateRayRenderableGlobe(vec2 st) {
return ray;
}
/*
/*
* Calculates the light scattering in the view direction comming from other light rays
* scattered in the atmosphere.
* Following the paper: S[L]|x - T(x,xs) * S[L]|xs
@@ -274,7 +278,7 @@ Ray calculateRayRenderableGlobe(vec2 st) {
* position and zenith cosine angle as in the paper.
* Arguments:
* x := camera position
* t := ray displacement variable after calculating the intersection with the
* t := ray displacement variable after calculating the intersection with the
* atmosphere. It is the distance from the camera to the last intersection with the
* atmosphere. If the ray hits the ground, t is updated to the correct value
* v := view direction (ray's direction) (normalized)
@@ -292,7 +296,7 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
const float INTERPOLATION_EPS = 0.004; // precision const from Brunetton
vec3 radiance;
mu = dot(x, v) / r;
float r2 = r * r;
@@ -300,7 +304,7 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
float muSun = dot(x, s) / r;
float rayleighPhase = rayleighPhaseFunction(nu);
float miePhase = miePhaseFunction(nu, mieG);
// S[L](x,s,v)
// I.e. the next line has the scattering light for the "infinite" ray passing through
// the atmosphere. If this ray hits something inside the atmosphere, we will subtract
@@ -310,7 +314,7 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
SAMPLES_MU_S, SAMPLES_NU),
0.0
);
// After removing the initial path from camera pos to top of atmosphere (for an
// observer in the space) we test if the light ray is hitting the atmosphere
float r0 = length(fragPosObj);
@@ -319,18 +323,18 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
float mu0 = dot(fragPosObj, v) * invr0;
if ((pixelDepth > INTERPOLATION_EPS) && (pixelDepth < maxLength)) {
t = float(pixelDepth);
t = float(pixelDepth);
groundHit = true;
// Transmittance from point r, direction mu, distance t
// By Analytical calculation
// attenuation = analyticTransmittance(r, mu, t);
// JCC: change from analytical to LUT transmittance to avoid
// acme on planet surface when looking from far away. (11/02/2017)
attenuation = transmittance(transmittanceTexture, r, mu, t, Rg, Rt);
attenuation = transmittance(transmittanceTexture, r, mu, t, Rg, Rt);
// Here we use the idea of S[L](a->b) = S[L](b->a), and get the S[L](x0, v, s)
// Then we calculate S[L] = S[L]|x - T(x, x0)*S[L]|x0
// Then we calculate S[L] = S[L]|x - T(x, x0)*S[L]|x0
// The "infinite" ray hist something inside the atmosphere, so we need to remove
// the unsused contribution to the final radiance.
vec4 inscatterFromSurface = texture4D(inscatterTexture, r0, mu0, muSun0, nu, Rg,
@@ -367,13 +371,13 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
float halfCosineLaw1 = r2 + (t * t);
float halfCosineLaw2 = 2.0 * r * t;
r0 = sqrt(halfCosineLaw1 + halfCosineLaw2 * mu);
// From the dot product: cos(theta0) = (x0 dot v)/(||ro||*||v||)
// mu0 = ((x + t) dot v) / r0
// mu0 = (x dot v + t dot v) / r0
// mu0 = (r*mu + t) / r0
mu0 = (r * mu + t) * (1.0 / r0);
vec4 inScatterAboveX = texture4D(inscatterTexture, r, mu, muSun, nu, Rg,
SAMPLES_MU, Rt, SAMPLES_R, SAMPLES_MU_S, SAMPLES_NU);
vec4 inScatterAboveXs = texture4D(inscatterTexture, r0, mu0, muSun0, nu, Rg,
@@ -385,9 +389,9 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
mu = muHorizon + INTERPOLATION_EPS;
//r0 = sqrt(r2 + t2 + 2.0 * r * t * mu);
r0 = sqrt(halfCosineLaw1 + halfCosineLaw2 * mu);
mu0 = (r * mu + t) * (1.0 / r0);
vec4 inScatterBelowX = texture4D(inscatterTexture, r, mu, muSun, nu, Rg,
SAMPLES_MU, Rt, SAMPLES_R, SAMPLES_MU_S, SAMPLES_NU);
vec4 inScatterBelowXs = texture4D(inscatterTexture, r0, mu0, muSun0, nu, Rg,
@@ -397,12 +401,12 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
// Interpolate between above and below inScattering radiance
inscatterRadiance = mix(inScatterAbove, inScatterBelow, interpolationValue);
}
}
// The w component of inscatterRadiance has stored the Cm,r value (Cm = Sm[L0])
// So, we must reintroduce the Mie inscatter by the proximity rule as described in the
// paper by Bruneton and Neyret in "Angular precision" paragraph:
// Hermite interpolation between two values
// This step is done because imprecision problems happen when the Sun is slightly
// below the horizon. When this happens, we avoid the Mie scattering contribution
@@ -410,19 +414,19 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
vec3 inscatterMie =
inscatterRadiance.rgb * inscatterRadiance.a / max(inscatterRadiance.r, 1e-4) *
(betaRayleigh.r / betaRayleigh);
radiance = max(inscatterRadiance.rgb * rayleighPhase + inscatterMie * miePhase, 0.0);
radiance = max(inscatterRadiance.rgb * rayleighPhase + inscatterMie * miePhase, 0.0);
// Finally we add the Lsun (all calculations are done with no Lsun so we can change it
// on the fly with no precomputations)
vec3 finalScatteringRadiance = radiance * sunIntensity;
return groundHit ? finalScatteringRadiance : spaceColor + finalScatteringRadiance;
}
/*
/*
* Calculates the light reflected in the view direction comming from other light rays
* integrated over the hemispehre plus the direct light (L0) from Sun.
* Following the paper: R[L]= R[L0]+R[L*]
* Following the paper: R[L]= R[L0]+R[L*]
* The ray is x + tv, v the view direction, s is the sun direction, r and mu the position
* and zenith cosine angle as in the paper.
* As for all calculations in the atmosphere, the center of the coordinate system is the
@@ -430,7 +434,7 @@ vec3 inscatterRadiance(vec3 x, inout float t, inout float irradianceFactor, vec3
* Arguments:
* x := camera position
* t := ray displacement variable. Here, differently from the inScatter light calculation,
* the position of the camera is already offset (on top of atmosphere) or inside
* the position of the camera is already offset (on top of atmosphere) or inside
* the atmosphere
* v := view direction (ray's direction) (normalized)
* s := Sun direction (normalized)
@@ -465,7 +469,7 @@ vec3 groundColor(vec3 x, float t, vec3 v, vec3 s, vec3 attenuationXtoX0, vec3 gr
groundReflectance * mix(30.0, 1.0, smoothstep(-1.0, 0.05, dotNS)) * RLStar :
groundReflectance * RLStar;
// Specular reflection from sun on oceans and rivers
// Specular reflection from sun on oceans and rivers
if ((waterReflectance > 0.1) && (muSun > 0.0)) {
vec3 h = normalize(s - v);
// Fresnell Schlick's approximation
@@ -478,11 +482,11 @@ vec3 groundColor(vec3 x, float t, vec3 v, vec3 s, vec3 attenuationXtoX0, vec3 gr
}
// Finally, we attenuate the surface Radiance from the point x0 to the camera location
vec3 reflectedRadiance = attenuationXtoX0 * groundRadiance;
return reflectedRadiance;
vec3 reflectedRadiance = attenuationXtoX0 * groundRadiance;
return reflectedRadiance;
}
/*
/*
* Calculates the Sun color. The ray is x + tv, v the view direction, s is the sun
* direction, r and mu the position and zenith cosine angle as in the paper. As for all
* calculations in the atmosphere, the center of the coordinate system is the planet's
@@ -502,11 +506,11 @@ vec3 sunColor(vec3 v, vec3 s, float r, float mu, float irradianceFactor) {
// @TODO (abock, 2021-07-01) This value is hard-coded to our sun+earth right now
// Convert 0.3 degrees -> radians
const float SunAngularSize = (0.3 * M_PI / 180.0);
// const float SunAngularSize = (0.3 * M_PI / 180.0);
const float FuzzyFactor = 0.5; // How fuzzy should the edges be
const float p1 = cos(SunAngularSize);
const float p2 = cos(SunAngularSize * FuzzyFactor);
float p1 = cos(sunAngularSize);
float p2 = cos(sunAngularSize * FuzzyFactor);
float t = (angle - p1) / (p2 - p1);
float scale = clamp(t, 0.0, 1.0);
@@ -527,20 +531,20 @@ void main() {
st.y = st.y / (resolution.y / viewport[3]) + (viewport[1] / resolution.y);
// Color from G-Buffer
vec3 color = texture(mainColorTexture, st).rgb;
vec4 color = texture(mainColorTexture, st);
if (cullAtmosphere == 1) {
renderTarget.rgb = color;
renderTarget = color;
return;
}
// Get the ray from camera to atm in object space
Ray ray = calculateRayRenderableGlobe(texCoord);
double offset = 0.0; // in KM
double maxLength = 0.0; // in KM
bool intersect = atmosphereIntersection(ray, Rt - (ATM_EPSILON * 0.001), offset, maxLength);
if (!intersect) {
renderTarget.rgb = color;
renderTarget = color;
return;
}
@@ -561,7 +565,7 @@ void main() {
// Data in the mainPositionTexture are written in view space (view plus camera rig)
vec4 position = texture(mainPositionTexture, st);
// OS Eye to World coords
// OS Eye to World coords
dvec4 positionWorldCoords = viewToWorldMatrix * position;
// World to Object (Normal and Position in meters)
@@ -571,7 +575,7 @@ void main() {
// JCC (12/12/2017): AMD distance function is buggy.
//double pixelDepth = distance(cameraPositionInObject.xyz, positionObjectsCoords.xyz);
double pixelDepth = length(camPosObj - positionObjectsCoords);
// JCC (12/13/2017): Trick to remove floating error in texture.
// We see a squared noise on planet's surface when seeing the planet from far away
// @TODO (abock, 2021-07-01) I don't think this does anything. Remove?
@@ -581,12 +585,12 @@ void main() {
pixelDepth += 1000.0;
const float alpha = 1000.0;
const float beta = 1000000.0;
const float x2 = 1e9;
const float x2 = 1e9;
const float diffGreek = beta - alpha;
const float diffDist = x2 - x1;
const float varA = diffGreek / diffDist;
const float varB = (alpha - varA * x1);
pixelDepth += double(varA * dC + varB);
pixelDepth += double(varA * dC + varB);
}
// All calculations are done in KM:
@@ -595,12 +599,12 @@ void main() {
if (pixelDepth < offset) {
// ATM Occluded - Something in front of ATM
renderTarget.rgb = color;
renderTarget = color;
return;
}
// Following paper nomenclature
double t = offset;
// Following paper nomenclature
double t = offset;
// Moving observer from camera location to top atmosphere. If the observer is already
// inside the atm, offset = 0.0 and no changes at all
@@ -615,32 +619,34 @@ void main() {
// adjust the pixelDepth for tdCalculateRayRenderableGlobe' offset so the next
// comparison with the planet's ground make sense:
pixelDepth -= offset;
dvec3 onATMPos = (modelTransformMatrix * dvec4(x * 1000.0, 1.0)).xyz;
float eclipseShadowATM = calcShadow(shadowDataArray, onATMPos, false);
float sunIntensityInscatter = sunRadiance * eclipseShadowATM;
vec2 eclipseShadowATM = calcShadow(shadowDataArray, onATMPos, false);
float sunIntensityInscatter = sunRadiance * eclipseShadowATM.y;
float irradianceFactor = 0.0;
bool groundHit = false;
vec3 attenuation;
vec3 attenuation;
vec3 inscatterColor = inscatterRadiance(x, tF, irradianceFactor, v, s, r,
vec3(positionObjectsCoords), maxLength, pixelDepth, color, sunIntensityInscatter, mu,
vec3(positionObjectsCoords), maxLength, pixelDepth, color.rgb, sunIntensityInscatter, mu,
attenuation, groundHit);
vec3 atmColor = vec3(0.0);
if (groundHit) {
float eclipseShadowPlanet = calcShadow(shadowDataArray, positionWorldCoords.xyz, true);
float sunIntensityGround = sunRadiance * eclipseShadowPlanet;
atmColor = groundColor(x, tF, v, s, attenuation, color, normal.xyz, irradianceFactor,
vec2 eclipseShadowPlanet = calcShadow(shadowDataArray, positionWorldCoords.xyz, true);
float sunIntensityGround = sunRadiance * eclipseShadowPlanet.y;
atmColor = groundColor(x, tF, v, s, attenuation, color.rgb, normal.xyz, irradianceFactor,
normal.w, sunIntensityGround);
}
else {
// In order to get better performance, we are not tracing multiple rays per pixel
// when the ray doesn't intersect the ground
atmColor = sunColor(v, s, r, mu, irradianceFactor);
}
// Final Color of ATM plus terrain:
renderTarget = vec4(inscatterColor + atmColor, 1.0);;
atmColor = sunColor(v, s, r, mu, irradianceFactor) * (1.0 - eclipseShadowATM.x);
}
// Final Color of ATM plus terrain. We want to support opacity so we blend between the
// planet color and the full atmosphere color using the opacity value
vec3 c = mix(color.rgb, inscatterColor + atmColor, opacity);
renderTarget = vec4(c, 1.0);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -21,7 +21,7 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#version __CONTEXT__
layout (triangles) in;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,6 +27,6 @@
out vec4 renderTableColor;
void main() {
void main() {
renderTableColor = vec4(0.0, 0.0, 0.0, 1.0);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -21,7 +21,7 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#version __CONTEXT__
#include "atmosphere_common.glsl"
@@ -100,7 +100,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
// Now we get vec(v) and vec(s) from mu, muSun and nu:
// Assuming:
// z |theta
// z |theta
// |\ vec(v) ||vec(v)|| = 1
// | \
// |__\_____x
@@ -121,7 +121,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
// 1 = sqrt(s.x*s.x + s.y*s.y + s.z*s.z)
// s.y = sqrt(1 - s.x*s.x - s.z*s.z) = sqrt(1 - s.x*s.x - muSun*muSun)
vec3 s = vec3(sx, sqrt(max(0.0, 1.0 - sx * sx - muSun2)), muSun);
// In order to integrate over 4PI, we scan the sphere using the spherical coordinates
// previously defined
vec3 radianceJAcc = vec3(0.0);
@@ -132,7 +132,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
float distanceToGround = 0.0;
float groundReflectance = 0.0;
vec3 groundTransmittance = vec3(0.0);
// If the ray w can see the ground we must compute the transmittance
// effect from the starting point x to the ground point in direction -vec(v):
if (cosineTheta < cosHorizon) { // ray hits ground
@@ -146,7 +146,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
// |\ distGround
// r | \ alpha
// | \/
// | /
// | /
// | / Rg
// |/
// So cos(alpha) = ((vec(x)+vec(dg)) dot -vec(distG))/(||(vec(x)+vec(distG))|| * ||vec(distG)||)
@@ -178,7 +178,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
float nuWV = dot(v, w);
float phaseRayleighWV = rayleighPhaseFunction(nuWV);
float phaseMieWV = miePhaseFunction(nuWV, mieG);
vec3 groundNormal = (vec3(0.0, 0.0, r) + distanceToGround * w) / Rg;
vec3 groundIrradiance = irradianceLUT(deltaETexture, dot(groundNormal, s), Rg);
@@ -194,7 +194,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
// light. We stored these values in the deltaS textures (Ray and Mie), and in order
// to avoid problems with the high angle dependency in the phase functions, we don't
// include the phase functions on those tables (that's why we calculate them now).
if (firstIteration == 1) {
if (firstIteration == 1) {
float phaseRaySW = rayleighPhaseFunction(nuSW);
float phaseMieSW = miePhaseFunction(nuSW, mieG);
// We can now access the values for the single InScattering in the textures deltaS textures.
@@ -204,7 +204,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
Rt, SAMPLES_R, SAMPLES_MU_S, SAMPLES_NU).rgb;
// Initial InScattering including the phase functions
radianceJ1 += singleRay * phaseRaySW + singleMie * phaseMieSW;
radianceJ1 += singleRay * phaseRaySW + singleMie * phaseMieSW;
}
else {
// On line 9 of the algorithm, the texture table deltaSR is updated, so when we
@@ -219,7 +219,7 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
// Finally, we add the atmospheric scale height (See: Radiation Transfer on the
// Atmosphere and Ocean from Thomas and Stamnes, pg 9-10.
radianceJAcc += radianceJ1 * (betaRayleigh * exp(-(r - Rg) / HR) * phaseRayleighWV +
betaMieScattering * exp(-(r - Rg) / HM) * phaseMieWV) * dw;
betaMieScattering * exp(-(r - Rg) / HM) * phaseMieWV) * dw;
}
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -41,11 +41,11 @@ void main() {
// First we convert the window's fragment coordinate to texel coordinates
vec3 rst = vec3(gl_FragCoord.xy, float(layer) + 0.5) /
vec3(ivec3(SAMPLES_MU_S * SAMPLES_NU, SAMPLES_MU, SAMPLES_R));
vec3 rayleighInscattering = texture(deltaSRTexture, rst).rgb;
float mieInscattering = texture(deltaSMTexture, rst).r;
// We are using only the red component of the Mie scattering. See the Precomputed
// Atmosphere Scattering paper for details about the angular precision
renderTarget = vec4(rayleighInscattering, mieInscattering);
renderTarget = vec4(rayleighInscattering, mieInscattering);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -21,7 +21,7 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#version __CONTEXT__
#include "atmosphere_common.glsl"

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -45,7 +45,7 @@ uniform float r;
uniform vec4 dhdH;
void integrand(float r, float mu, float muSun, float nu, float y, out vec3 S_R,
void integrand(float r, float mu, float muSun, float nu, float y, out vec3 S_R,
out vec3 S_M)
{
// The integral's integrand is the single inscattering radiance:
@@ -59,10 +59,10 @@ void integrand(float r, float mu, float muSun, float nu, float y, out vec3 S_R,
// angular precision
S_R = vec3(0.0);
S_M = vec3(0.0);
// cosine law
float ri = max(sqrt(r * r + y * y + 2.0 * r * mu * y), Rg);
// Considering the Sun as a parallel light source, thew vector s_i = s.
// So muSun_i = (vec(y_i) dot vec(s))/r_i = ((vec(x) + vec(yi-x)) dot vec(s))/r_i
// muSun_i = (vec(x) dot vec(s) + vec(yi-x) dot vec(s))/r_i = (r*muSun + yi*nu)/r_i
@@ -103,7 +103,7 @@ void inscatter(float r, float mu, float muSun, float nu, out vec3 S_R, out vec3
vec3 S_Ri;
vec3 S_Mi;
integrand(r, mu, muSun, nu, 0.0, S_Ri, S_Mi);
for (int i = 1; i <= INSCATTER_INTEGRAL_SAMPLES; ++i) {
for (int i = 1; i <= INSCATTER_INTEGRAL_SAMPLES; i++) {
float yj = float(i) * dy;
vec3 S_Rj;
vec3 S_Mj;
@@ -123,7 +123,7 @@ void main() {
// parameters (uv), we unmapping mu, muSun and nu.
float mu, muSun, nu;
unmappingMuMuSunNu(r, dhdH, SAMPLES_MU, Rg, Rt, SAMPLES_MU_S, SAMPLES_NU, mu, muSun, nu);
// Here we calculate the single inScattered light. Because this is a single
// inscattering, the light that arrives at a point y in the path from the eye to the
// infinity (top of atmosphere or planet's ground), comes only from the light source,
@@ -135,7 +135,7 @@ void main() {
// S[L0] = P_R*S_R[L0] + P_M*S_M[L0]
// In order to save memory, we just store the red component of S_M[L0], and later we use
// the proportionality rule to calcule the other components.
vec3 S_R; // First Order Rayleigh InScattering
vec3 S_R; // First Order Rayleigh InScattering
vec3 S_M; // First Order Mie InScattering
inscatter(r, mu, muSun, nu, S_R, S_M);
renderTarget1 = vec4(S_R, 1.0);

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -64,11 +64,11 @@ vec3 inscatter(float r, float mu, float muSun, float nu) {
vec3 inScatteringRadiance = vec3(0.0);
float dy = rayDistance(r, mu, Rt, Rg) / float(INSCATTER_INTEGRAL_SAMPLES);
vec3 inScatteringRadiance_i = integrand(r, mu, muSun, nu, 0.0);
// In order to solve the integral from equation (11) we use the trapezoidal rule:
// Integral(f(y)dy)(from a to b) = ((b-a)/2n_steps)*(Sum(f(y_i+1)+f(y_i)))
// where y_i+1 = y_j
for (int i = 1; i <= INSCATTER_INTEGRAL_SAMPLES; ++i) {
for (int i = 1; i <= INSCATTER_INTEGRAL_SAMPLES; i++) {
float y_j = float(i) * dy;
vec3 inScatteringRadiance_j = integrand(r, mu, muSun, nu, y_j);
inScatteringRadiance += (inScatteringRadiance_i + inScatteringRadiance_j) / 2.0 * dy;
@@ -84,7 +84,7 @@ void main() {
float nu = 0.0;
// Unmapping the variables from texture texels coordinates to mapped coordinates
unmappingMuMuSunNu(r, dhdH, SAMPLES_MU, Rg, Rt, SAMPLES_MU_S, SAMPLES_NU, mu, muSun, nu);
// Write to texture deltaSR
// Write to texture deltaSR
renderTarget = vec4(inscatter(r, mu, muSun, nu), 1.0);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -30,9 +30,9 @@ uniform ivec2 OTHER_TEXTURES;
uniform sampler2D deltaETexture;
void main() {
void main() {
vec2 uv = gl_FragCoord.xy / vec2(OTHER_TEXTURES);
// Update texture E with E plus deltaE textures.
renderTableColor = texture(deltaETexture, uv);
renderTableColor = texture(deltaETexture, uv);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -21,7 +21,7 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#version __CONTEXT__
#include "atmosphere_common.glsl"
@@ -60,9 +60,9 @@ void main() {
// In order to solve the integral from equation (15) we use the trapezoidal rule:
// Integral(f(y)dy)(from a to b) = ((b-a)/2n_steps)*(Sum(f(y_i+1)+f(y_i)))
vec3 irradianceE = vec3(0.0);
for (int iphi = 0; iphi < IRRADIANCE_INTEGRAL_SAMPLES; ++iphi) {
for (int iphi = 0; iphi < IRRADIANCE_INTEGRAL_SAMPLES; iphi++) {
float phi = (float(iphi) + 0.5) * stepPhi;
for (int itheta = 0; itheta < IRRADIANCE_INTEGRAL_SAMPLES; ++itheta) {
for (int itheta = 0; itheta < IRRADIANCE_INTEGRAL_SAMPLES; itheta++) {
float theta = (float(itheta) + 0.5) * stepTheta;
// spherical coordinates: dw = dtheta*dphi*sin(theta)*rho^2
// rho = 1, we are integrating over a unit sphere
@@ -98,5 +98,5 @@ void main() {
}
// Write the higher order irradiance to texture deltaE
renderTableColor = vec4(irradianceE, 0.0);
renderTableColor = vec4(irradianceE, 0.0);
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -47,14 +47,14 @@ const int TRANSMITTANCE_STEPS = 500;
// r := height of starting point vect(x)
// mu := cosine of the zeith angle of vec(v). Or mu = (vec(x) * vec(v))/r
// H := Thickness of atmosphere if its density were uniform (used for Rayleigh and Mie)
float opticalDepth(float r, float mu, float H) {
float opticalDepth(float r, float mu, float H) {
float r2 = r * r;
// Is ray below horizon? The transmittance table will have only the values for
// transmittance starting at r (x) until the light ray touches the atmosphere or the
// ground and only for view angles v between 0 and pi/2 + eps. That's because we can
// calculate the transmittance for angles bigger than pi/2 just inverting the ray
// direction and starting and ending points.
// cosine law for triangles: y_i^2 = a^2 + b^2 - 2abcos(alpha)
float cosZenithHorizon = -sqrt(1.0 - ((Rg * Rg) / r2));
if (mu < cosZenithHorizon) {
@@ -67,9 +67,9 @@ float opticalDepth(float r, float mu, float H) {
float deltaStep = b_a / float(TRANSMITTANCE_STEPS);
// cosine law
float y_i = exp(-(r - Rg) / H);
float accumulation = 0.0;
for (int i = 1; i <= TRANSMITTANCE_STEPS; ++i) {
for (int i = 1; i <= TRANSMITTANCE_STEPS; i++) {
float x_i = float(i) * deltaStep;
// cosine law for triangles: y_i^2 = a^2 + b^2 - 2abcos(alpha)
// In this case, a = r, b = x_i and cos(alpha) = cos(PI-zenithView) = mu
@@ -84,11 +84,11 @@ float opticalDepth(float r, float mu, float H) {
void main() {
float u_mu = gl_FragCoord.x / float(TRANSMITTANCE.x);
float u_r = gl_FragCoord.y / float(TRANSMITTANCE.y);
// In the paper u_r^2 = (r^2-Rg^2)/(Rt^2-Rg^2)
// So, extracting r from u_r in the above equation:
float r = Rg + (u_r * u_r) * (Rt - Rg);
// In the paper the Bruneton suggest mu = dot(v,x)/||x|| with ||v|| = 1.0
// Later he proposes u_mu = (1-exp(-3mu-0.6))/(1-exp(-3.6))
// But the below one is better. See Collienne.
@@ -99,9 +99,9 @@ void main() {
if (ozoneLayerEnabled) {
ozoneContribution = betaOzoneExtinction * 0.0000006 * opticalDepth(r, muSun, HO);
}
vec3 opDepth = ozoneContribution +
vec3 opDepth = ozoneContribution +
betaMieExtinction * opticalDepth(r, muSun, HM) +
betaRayleigh * opticalDepth(r, muSun, HR);
renderTableColor = vec4(exp(-opDepth), 0.0);
}

View File

@@ -2,7 +2,7 @@
# #
# OpenSpace #
# #
# Copyright (c) 2014-2022 #
# Copyright (c) 2014-2025 #
# #
# 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 #
@@ -22,16 +22,39 @@
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
include(${PROJECT_SOURCE_DIR}/support/cmake/module_definition.cmake)
set(HEADER_FILES
rendering/renderabledistancelabel.h
audiomodule.h
)
source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
rendering/renderabledistancelabel.cpp
audiomodule.cpp
audiomodule_lua.inl
)
source_group("Source Files" FILES ${SOURCE_FILES})
create_new_module("VisLab" vislab_module ${HEADER_FILES} ${SOURCE_FILES})
create_new_module(
"Audio"
audio_module
STATIC
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
)
begin_dependency("SoLoud")
set(SOLOUD_BACKEND_SDL2 OFF CACHE BOOL "")
if (WIN32)
set(SOLOUD_BACKEND_WINMM ON CACHE BOOL "")
elseif (UNIX AND NOT APPLE)
set(SOLOUD_BACKEND_ALSA ON CACHE BOOL "")
elseif (APPLE)
set(SOLOUD_BACKEND_COREAUDIO ON CACHE BOOL "")
endif ()
add_subdirectory(ext/soloud/contrib SYSTEM)
# Unfortunately, the soloud cmake tarket doesn't set the include directories correctly
target_include_directories(openspace-module-audio SYSTEM PRIVATE ext/soloud/include)
target_link_libraries(openspace-module-audio PRIVATE soloud)
set_property(TARGET soloud PROPERTY FOLDER "External")
end_dependency("SoLoud")

View File

@@ -0,0 +1,418 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/audio/audiomodule.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <ghoul/logging/logmanager.h>
#include <soloud.h>
#include <soloud_wav.h>
#include "audiomodule_lua.inl"
namespace {
constexpr std::string_view _loggerCat = "AudioModule";
struct [[codegen::Dictionary(AudioModule)]] Parameters {
// Sets the maximum number of simultaneous channels that can be played back by the
// audio subsystem. If this value is not specified, it defaults to 128.
std::optional<int> maxNumberOfChannels [[codegen::greater(0)]];
};
#include "audiomodule_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation AudioModule::Documentation() {
return codegen::doc<Parameters>("module_audio");
}
AudioModule::AudioModule()
: OpenSpaceModule(Name)
, _engine(std::make_unique<SoLoud::Soloud>())
{}
AudioModule::~AudioModule() {}
void AudioModule::internalInitialize(const ghoul::Dictionary& dictionary) {
const Parameters p = codegen::bake<Parameters>(dictionary);
LDEBUG(std::format("Initializing SoLoud version: {}", SOLOUD_VERSION));
_engine->init();
_engine->setGlobalVolume(0.5f);
const int nChannels = p.maxNumberOfChannels.value_or(16);
_engine->setMaxActiveVoiceCount(static_cast<unsigned int>(nChannels));
global::callback::postDraw->emplace_back([this]() {
if (!_sounds.empty()) {
_engine->update3dAudio();
}
});
const char* backend = _engine->getBackendString();
if (backend) {
LDEBUG(std::format("Audio backend: {}", backend));
LDEBUG(std::format("Number of channels: {}", _engine->getBackendChannels()));
}
else {
LINFO(std::format("Audio subsystem disabled as no backend was detected"));
}
}
void AudioModule::internalDeinitializeGL() {
ghoul_assert(_engine, "No audio engine loaded");
_sounds.clear();
_engine->deinit();
_engine = nullptr;
}
std::unique_ptr<SoLoud::Wav> AudioModule::loadSound(const std::filesystem::path& path) {
ghoul_assert(_engine, "No audio engine loaded");
std::unique_ptr<SoLoud::Wav> sound = std::make_unique<SoLoud::Wav>();
const std::string p = path.string();
SoLoud::result res = sound->load(p.c_str());
if (res != 0) {
throw ghoul::RuntimeError(std::format(
"Error loading sound from {}. {}: {}",
path, static_cast<int>(res), _engine->getErrorString(res)
));
}
// While we are loading a sound, we also want to do a little garbage collection on our
// internal data structure to remove the songs that someone has loaded at some point
// and that have since organically stopped. In general, this should only happen if the
// song was started without looping and has ended
for (auto it = _sounds.begin(); it != _sounds.end();) {
if (!isPlaying(it->first)) {
// We have found one of the candidates
LDEBUG(std::format("Removing song {} as it has ended", it->first));
_sounds.erase(it);
// It is easier to just reset the iterator to the beginning than deal with the
// off-by-one error when deleting the last element in the list and the
// subsequent crash
it = _sounds.begin();
}
else {
it++;
}
}
return sound;
}
void AudioModule::playAudio(const std::filesystem::path& path, std::string identifier,
ShouldLoop loop)
{
ghoul_assert(_engine, "No audio engine loaded");
if (_sounds.find(identifier) != _sounds.end()) {
LERROR(std::format("Sound with name '{}' already played", identifier));
return;
}
std::unique_ptr<SoLoud::Wav> sound = loadSound(path);
sound->setLooping(loop);
SoLoud::handle handle = _engine->playBackground(*sound);
ghoul_assert(_sounds.find(identifier) == _sounds.end(), "Handle already used");
_sounds[identifier] = {
.sound = std::move(sound),
.handle = handle
};
}
void AudioModule::playAudio3d(const std::filesystem::path& path, std::string identifier,
const glm::vec3& position, ShouldLoop loop)
{
ghoul_assert(_engine, "No audio engine loaded");
if (_sounds.find(identifier) != _sounds.end()) {
LERROR(std::format("Sound with name '{}' already played", identifier));
return;
}
std::unique_ptr<SoLoud::Wav> sound = loadSound(path);
sound->setLooping(loop);
SoLoud::handle handle = _engine->play3d(*sound, position.x, position.y, position.z);
_sounds[identifier] = {
.sound = std::move(sound),
.handle = handle
};
}
void AudioModule::stopAudio(const std::string& identifier) {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
_engine->stop(it->second.handle);
_sounds.erase(it);
}
}
void AudioModule::stopAll() {
ghoul_assert(_engine, "No audio engine loaded");
_engine->stopAll();
_sounds.clear();
}
void AudioModule::pauseAll() const {
for (const std::pair<const std::string, Info>& sound : _sounds) {
pauseAudio(sound.first);
}
}
void AudioModule::resumeAll() const {
for (const std::pair<const std::string, Info>& sound : _sounds) {
resumeAudio(sound.first);
}
}
void AudioModule::playAllFromStart() const {
for (const std::pair<const std::string, Info>& sound : _sounds) {
_engine->seek(sound.second.handle, 0.f);
}
resumeAll();
}
bool AudioModule::isPlaying(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
return _engine->isValidVoiceHandle(it->second.handle);
}
else {
return false;
}
}
void AudioModule::pauseAudio(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
_engine->setPause(it->second.handle, true);
}
}
void AudioModule::resumeAudio(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
_engine->setPause(it->second.handle, false);
}
}
bool AudioModule::isPaused(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
const bool isPaused = _engine->getPause(it->second.handle);
return isPaused;
}
else {
return true;
}
}
void AudioModule::setLooping(const std::string& identifier, ShouldLoop loop) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
_engine->setLooping(it->second.handle, loop);
}
}
AudioModule::ShouldLoop AudioModule::isLooping(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
return _engine->getLooping(it->second.handle) ? ShouldLoop::Yes : ShouldLoop::No;
}
else {
return ShouldLoop::No;
}
}
void AudioModule::setVolume(const std::string& identifier, float volume, float fade) const
{
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it == _sounds.end()) {
return;
}
// We clamp the volume level between [0, 1] to not accidentally blow any speakers
volume = glm::clamp(volume, 0.f, 1.f);
if (fade == 0.f) {
_engine->setVolume(it->second.handle, volume);
}
else {
_engine->fadeVolume(it->second.handle, volume, fade);
}
}
float AudioModule::volume(const std::string& identifier) const {
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
const float volume = _engine->getVolume(it->second.handle);
return volume;
}
else {
return 0.f;
}
}
void AudioModule::set3dSourcePosition(const std::string& identifier,
const glm::vec3& position) const
{
ghoul_assert(_engine, "No audio engine loaded");
auto it = _sounds.find(identifier);
if (it != _sounds.end()) {
_engine->set3dSourcePosition(
it->second.handle,
position.x, position.y, position.z
);
}
}
std::vector<std::string> AudioModule::currentlyPlaying() const {
// This function is *technically* not the ones that are playing, but that ones that we
// are keeping track of. So we still have songs in our internal data structure that
// were started as not-looping and that have ended playing. We need to filter them out
// here.
// The alternative would be to have a periodic garbage collection running, but that
// feels worse. We are doing the garbage collection in the two playAudio functions
// instead, since we have to do some work their either way
std::vector<std::string> res;
res.reserve(_sounds.size());
for (const std::pair<const std::string, Info>& sound : _sounds) {
if (isPlaying(sound.first)) {
res.push_back(sound.first);
}
}
return res;
}
void AudioModule::setGlobalVolume(float volume, float fade) const {
ghoul_assert(_engine, "No audio engine loaded");
// We clamp the volume level between [0, 1] to not accidentally blow any speakers
volume = glm::clamp(volume, 0.f, 1.f);
if (fade == 0.f) {
_engine->setGlobalVolume(volume);
}
else {
_engine->fadeGlobalVolume(volume, fade);
}
}
float AudioModule::globalVolume() const {
ghoul_assert(_engine, "No audio engine loaded");
return _engine->getGlobalVolume();
}
void AudioModule::set3dListenerParameters(const std::optional<glm::vec3>& position,
const std::optional<glm::vec3>& lookAt,
const std::optional<glm::vec3>& up) const
{
ghoul_assert(_engine, "No audio engine loaded");
if (position.has_value()) {
_engine->set3dListenerPosition(position->x, position->y, position->z);
}
if (lookAt.has_value()) {
_engine->set3dListenerAt(lookAt->x, lookAt->y, lookAt->z);
}
if (up.has_value()) {
_engine->set3dListenerUp(up->x, up->y, up->z);
}
}
void AudioModule::setSpeakerPosition(int channel, const glm::vec3& position) const {
ghoul_assert(_engine, "No audio engine loaded");
_engine->setSpeakerPosition(channel, position.x, position.y, position.z);
}
glm::vec3 AudioModule::speakerPosition(int channel) const {
ghoul_assert(_engine, "No audio engine loaded");
float x = 0.f;
float y = 0.f;
float z = 0.f;
_engine->getSpeakerPosition(channel, x, y, z);
return glm::vec3(x, y, z);
}
std::vector<documentation::Documentation> AudioModule::documentations() const {
return {
};
}
scripting::LuaLibrary AudioModule::luaLibrary() const {
return {
"audio",
{
codegen::lua::PlayAudio,
codegen::lua::PlayAudio3d,
codegen::lua::StopAudio,
codegen::lua::StopAll,
codegen::lua::PauseAll,
codegen::lua::ResumeAll,
codegen::lua::PlayAllFromStart,
codegen::lua::IsPlaying,
codegen::lua::PauseAudio,
codegen::lua::ResumeAudio,
codegen::lua::IsPaused,
codegen::lua::SetLooping,
codegen::lua::IsLooping,
codegen::lua::SetVolume,
codegen::lua::Volume,
codegen::lua::Set3dSourcePosition,
codegen::lua::CurrentlyPlaying,
codegen::lua::SetGlobalVolume,
codegen::lua::GlobalVolume,
codegen::lua::Set3dListenerPosition,
codegen::lua::SetSpeakerPosition,
codegen::lua::SpeakerPosition
}
};
}
} // namespace openspace

318
modules/audio/audiomodule.h Normal file
View File

@@ -0,0 +1,318 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_AUDIO___AUDIOMODULE___H__
#define __OPENSPACE_MODULE_AUDIO___AUDIOMODULE___H__
#include <openspace/util/openspacemodule.h>
#include <openspace/documentation/documentation.h>
#include <openspace/scripting/lualibrary.h>
#include <ghoul/misc/boolean.h>
namespace SoLoud {
class Soloud;
class Wav;
} // namespace SoLoud
namespace openspace {
class AudioModule : public OpenSpaceModule {
public:
constexpr static const char* Name = "Audio";
AudioModule();
~AudioModule() override;
std::vector<documentation::Documentation> documentations() const override;
scripting::LuaLibrary luaLibrary() const override;
BooleanType(ShouldLoop);
/**
* Starts playing the audio file located and the provided \p path. The \p loop
* parameter determines whether the file is only played once, or on a loop. The sound
* is later referred to by the \p identifier name. The audio file will be played in
* "background" mode, which means that each channel will be played at full volume. To
* play a video using spatial audio, use the #playAudio3d function instead.
*
* \param path The audio file that should be played
* \param identifier The name for the sound that is used to refer to the sound
* \param loop If `Yes` then the song will be played in a loop until the program is
* closed or the playing is stopped through the #stopAudio function
*/
void playAudio(const std::filesystem::path& path, std::string identifier,
ShouldLoop loop);
/**
* Starts playing the audio file located and the provided \p path. The \p loop
* parameter determines whether the file is only played once, or on a loop. The sound
* is later referred to by the \p identifier name. The \p position parameter
* determines the spatial location of the sound in a meter-based coordinate system.
* The position of the listener is (0,0,0) with the forward direction along the +y
* axis. This means that the "left" channel in a stereo setting is towards -x and the
* "right" channel towards x. This default value can be customized through the
* #set3dListenerParameters function. If you want to play a video without spatial
* audio, use the #playAudio function instead.
*
* \param path The audio file that should be played
* \param identifier The name for the sound that is used to refer to the sound
* \param position The position of the audio file in the 3D environment
* \param loop If `Yes` then the song will be played in a loop until the program is
* closed or the playing is stopped through the #stopAudio function
*/
void playAudio3d(const std::filesystem::path& path, std::string identifier,
const glm::vec3& position, ShouldLoop loop);
/**
* Stops the audio referenced by the \p identifier. The \p identifier must be a name
* for a sound that was started through the #playAudio or #playAudio3d functions.
* After this function, the \p identifier can not be used for any other function
* anymore except for #playAudio or #playAudio3d to start a new sound.
*
* \param identifier The identifier to the track that should be stopped
*/
void stopAudio(const std::string& identifier);
/**
* Stops all currently playing tracks. After this function, none of the identifiers
* used to previously play a sound a valid any longer, but can still be used by the
* #playAudio or #playAudio3d functions to start a new sound.
* This function behaves the same way as if manually calling #stopAudio on all of the
* sounds that have been started.
*/
void stopAll();
/**
* Pauses the playback for all sounds, while keeping them valid. This function behaves
* the same as if calling #pauseAudio on all of the sounds that are currently playing.
*/
void pauseAll() const;
/**
* Resumes the playback for all sounds that have been paused. Please note that this
* will also resume the playback for the sounds that have been manually paused, not
* just those that were paused through the #pauseAll function.
*/
void resumeAll() const;
/**
* Takes all of the sounds that are currently registers, unpauses them and plays them
* from their starting points
*/
void playAllFromStart() const;
/**
* Returns whether the track referred to by the \p identifier is currently playing. A
* volume of 0 is still considered to be playing. The \p identifier must be a name for
* a sound that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `true` if the track is currently playing, `false` otherwise
*/
bool isPlaying(const std::string& identifier) const;
/**
* Pauses the playback of the track referred to by the \p identifier. The playback can
* later be resumed through the #resumeAudio function. Trying to pause an already
* paused track will not do anything, but is valid. The \p identifier must be a name
* for a sound that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
*/
void pauseAudio(const std::string& identifier) const;
/**
* Resumes the playback of a track that was previously paused through the #pauseAudio
* function. Trying to resume an already playing track will not do anything, but is
* valid. The \p identifier must be a name for a sound that was started through the
* #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
*/
void resumeAudio(const std::string& identifier) const;
/**
* Returns whether the track refered to by the \p identifier is currently playing or
* paused. If it was be paused through a previous call to #pauseAudio, this function
* will return `true`. If it has just been created or resumed through a call to
* #resumeAudio, it will return `false`. The \p identifier must be a name for a sound
* that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `true` if the track is currently paused, `false` if it is playing
*/
bool isPaused(const std::string& identifier) const;
/**
* Controls whether the track referred to by the \p identifier should be looping or
* just be played once. If a track is converted to not looping, it will finish playing
* until the end of the file. The \p identifier must be a name for a sound that was
* started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \param loop If `Yes` then the song will be played in a loop until the program is
* closed or the playing is stopped through the #stopAudio function
*/
void setLooping(const std::string& identifier, ShouldLoop loop) const;
/**
* Returns whether the track referred to by the \p identifier is set to be looping or
* whether it should played only once. The \p identifier must be a name for a sound
* that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `Yes` if the track is looping, `No` otherwise
*/
ShouldLoop isLooping(const std::string& identifier) const;
/**
* Sets the volume of the track referred to by \p identifier to the new \p volume. The
* volume should be a number bigger than 0, where 1 is the maximum volume level.
* The \p fade controls whether the volume change should be immediately (if
* it is 0) or over how many seconds it should change. The default is for it to change
* over 500 ms. The \p identifier must be a name for a sound that was started through
* the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \param volume The new volume level. Must be greater or equal to 0
* \param fade How much time the fade from the current volume to the new volume should
* take
*/
void setVolume(const std::string& identifier, float volume, float fade = 0.5f) const;
/**
* Returns the volume for the track referred to by the \p handle. The number returned
* will be greater or equal to 0. The \p identifier must be a name for a sound that
* was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return The volume for the track referred to by the \p handle, which will be
* greater or equal to 0
*/
float volume(const std::string& identifier) const;
/**
* Updates the 3D position of a track started through the #playAudio3d function. See
* that function and the #set3dListenerParameters function for a complete description.
* The \p identifier must be a name for a sound that was started through the
* #playAudio3d function.
*
* \param identifier The identifier of a track started through the #playAudio3d
* function
* \param position The new position from which the track originates
*/
void set3dSourcePosition(const std::string& identifier,
const glm::vec3& position) const;
/**
* Returns the list of all tracks that are currently playing.
*
* \return The list of all tracks that are currently playing
*/
std::vector<std::string> currentlyPlaying() const;
/**
* Sets the global volume for all track referred to the new \p volume. The total
* for each track is the global volume set by this function multiplied with the volume
* for the specific track set through the #setVolume function. The default value for
* the global volume is 0.5. The volume should be a number bigger than 0, where 1 is
* the maximum volume level. The \p fade controls whether the volume change should be
* immediately (if it is 0) or over how many seconds it should change. The default is
* for it to change over 500 ms.
*
* \param volume The new volume level. Must be greater or equal to 0
* \param fade How much time the fade from the current volume to the new volume should
* take
*/
void setGlobalVolume(float volume, float fade = 0.5f) const;
/**
* Returns the global volume for all track. The number returned will be greater or
* equal to 0.
*
* \return The global volume
*/
float globalVolume() const;
/**
* Sets the position and orientation of the listener. This new position is
* automatically used to adjust the relative position of all 3D tracks. Each parameter
* to this function call is optional and if a value is omitted, the currently set
* value continues to be used instead. The coordinate system for the tracks and the
* listener is a meter-based coordinate system.
*
* \param position The position of the listener.
* \param lookAt The direction vector of the forward direction
* \param up The up-vector of the coordinate system
*/
void set3dListenerParameters(const std::optional<glm::vec3>& position,
const std::optional<glm::vec3>& lookAt = std::nullopt,
const std::optional<glm::vec3>& up = std::nullopt) const;
/**
* Sets the position of the speaker for the provided \p channel to the provided
* \p position. In general, this is considered an advanced feature to accommodate
* non-standard audio environments.
*
* \param channel The channel whose speaker's position should be changed
* \param position The new position for the speaker
*/
void setSpeakerPosition(int channel, const glm::vec3& position) const;
/**
* Returns the position for the speaker of the provided \p channel.
* \return The position for the speaker of the provided \p channel
*/
glm::vec3 speakerPosition(int channel) const;
static documentation::Documentation Documentation();
private:
struct Info {
std::unique_ptr<SoLoud::Wav> sound;
unsigned int handle = 0;
};
void internalInitialize(const ghoul::Dictionary&) override;
void internalDeinitializeGL() override;
/**
* Loads the sound at the provided \p path as an audio source and returns the pointer
* to it. The sound has only been loaded and no other attributes have changed.
*
* \param path The path to the audio file on disk that should be loaded
* \return The SoLoud::Wav object of the loaded file
* \throw ghoul::RuntimeError If the \p path is not a loadable audio file
*/
std::unique_ptr<SoLoud::Wav> loadSound(const std::filesystem::path& path);
std::unique_ptr<SoLoud::Soloud> _engine;
std::map<std::string, Info> _sounds;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_AUDIO___AUDIOMODULE___H__

View File

@@ -0,0 +1,355 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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 <openspace/engine/moduleengine.h>
#include <ghoul/lua/lua_helper.h>
namespace {
/**
* Starts playing the audio file located and the provided \p path. The \p loop parameter
* determines whether the file is only played once, or on a loop. The sound is later
* referred to by the \p identifier name. The audio file will be played in "background"
* mode, which means that each channel will be played at full volume. To play a video
* using spatial audio, use the #playAudio3d function instead.
*
* \param path The audio file that should be played
* \param identifier The name for the sound that is used to refer to the sound
* \param loop If `Yes` then the song will be played in a loop until the program is closed
* or the playing is stopped through the #stopAudio function
*/
[[codegen::luawrap]] void playAudio(std::filesystem::path path, std::string identifier,
bool shouldLoop = true)
{
using namespace openspace;
global::moduleEngine->module<AudioModule>()->playAudio(
path,
std::move(identifier),
AudioModule::ShouldLoop(shouldLoop)
);
}
/**
* Starts playing the audio file located and the provided \p path. The \p loop parameter
* determines whether the file is only played once, or on a loop. The sound is later
* referred to by the \p identifier name. The \p position parameter determines the spatial
* location of the sound in a meter-based coordinate system. The position of the listener
* is (0,0,0) with the forward direction along the +y axis. This means that the "left"
* channel in a stereo setting is towards -x and the "right" channel towards x. This
* default value can be customized through the #set3dListenerParameters function. If you
* want to play a video without spatial audio, use the #playAudio function instead.
*
* \param path The audio file that should be played
* \param identifier The name for the sound that is used to refer to the sound
* \param position The position of the audio file in the 3D environment
* \param loop If `Yes` then the song will be played in a loop until the program is closed
* or the playing is stopped through the #stopAudio function
*/
[[codegen::luawrap]] void playAudio3d(std::filesystem::path path, std::string identifier,
glm::vec3 position, bool shouldLoop = true)
{
using namespace openspace;
global::moduleEngine->module<AudioModule>()->playAudio3d(
path,
std::move(identifier),
position,
AudioModule::ShouldLoop(shouldLoop)
);
}
/**
* Stops the audio referenced by the \p identifier. The \p identifier must be a name for a
* sound that was started through the #playAudio or #playAudio3d functions. After this
* function, the \p identifier can not be used for any other function anymore except for
* #playAudio or #playAudio3d to start a new sound.
*
* \param identifier The identifier to the track that should be stopped
*/
[[codegen::luawrap]] void stopAudio(std::string identifier) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->stopAudio(identifier);
}
/**
* Stops all currently playing tracks. After this function, none of the identifiers used
* to previously play a sound a valid any longer, but can still be used by the #playAudio
* or #playAudio3d functions to start a new sound. This function behaves the same way as
* if manually calling #stopAudio on all of the sounds that have been started.
*/
[[codegen::luawrap]] void stopAll() {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->stopAll();
}
/**
* Pauses the playback for all sounds, while keeping them valid. This function behaves the
* same as if calling #pauseAudio on all of the sounds that are currently playing.
*/
[[codegen::luawrap]] void pauseAll() {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->pauseAll();
}
/**
* Resumes the playback for all sounds that have been paused. Please note that this will
* also resume the playback for the sounds that have been manually paused, not just those
* that were paused through the #pauseAll function.
*/
[[codegen::luawrap]] void resumeAll() {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->resumeAll();
}
/**
* Takes all of the sounds that are currently registers, unpauses them and plays them
* from their starting points
*/
[[codegen::luawrap]] void playAllFromStart() {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->playAllFromStart();
}
/**
* Returns whether the track referred to by the \p identifier is currently playing. A
* volume of 0 is still considered to be playing. The \p identifier must be a name for a
* sound that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `true` if the track is currently playing, `false` otherwise
*/
[[codegen::luawrap]] bool isPlaying(std::string identifier) {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->isPlaying(identifier);
}
/**
* Pauses the playback of the track referred to by the \p identifier. The playback can
* later be resumed through the #resumeAudio function. Trying to pause an already paused
* track will not do anything, but is valid. The \p identifier must be a name for a sound
* that was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
*/
[[codegen::luawrap]] void pauseAudio(std::string identifier) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->pauseAudio(identifier);
}
/**
* Returns whether the track refered to by the \p identifier is currently playing or
* paused. If it was be paused through a previous call to #pauseAudio, this function will
* return `true`. If it has just been created or resumed through a call to #resumeAudio,
* it will return `false`. The \p identifier must be a name for a sound that was started
* through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `true` if the track is currently paused, `false` if it is playing
*/
[[codegen::luawrap]] bool isPaused(std::string identifier) {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->isPaused(identifier);
}
/**
* Resumes the playback of a track that was previously paused through the #pauseAudio
* function. Trying to resume an already playing track will not do anything, but is valid.
* The \p identifier must be a name for a sound that was started through the #playAudio or
* #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
*/
[[codegen::luawrap]] void resumeAudio(std::string identifier) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->resumeAudio(identifier);
}
/**
* Controls whether the track referred to by the \p identifier should be looping or just
* be played once. If a track is converted to not looping, it will finish playing until
* the end of the file. The \p identifier must be a name for a sound that was started
* through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \param loop If `Yes` then the song will be played in a loop until the program is closed
* or the playing is stopped through the #stopAudio function
*/
[[codegen::luawrap]] void setLooping(std::string identifier, bool shouldLoop) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->setLooping(
identifier,
AudioModule::ShouldLoop(shouldLoop)
);
}
/**
* Returns whether the track referred to by the \p identifier is set to be looping or
* whether it should played only once. The \p identifier must be a name for a sound that
* was started through the #playAudio or #playAudio3d functions.
*
* \param identifier The identifier to the track that should be stopped
* \return `Yes` if the track is looping, `No` otherwise
*/
[[codegen::luawrap]] bool isLooping(std::string identifier) {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->isLooping(identifier);
}
/**
* Sets the volume of the track referred to by \p handle to the new \p volume. The volume
* should be a number bigger than 0, where 1 is the maximum volume level. The \p fade
* controls whether the volume change should be immediately (if it is 0) or over how many
* seconds it should change. The default is for it to change over 500 ms.
*
* \param handle The handle to the track whose volume should be changed
* \param volume The new volume level. Must be greater or equal to 0
* \param fade How much time the fade from the current volume to the new volume should
* take
*/
[[codegen::luawrap]] void setVolume(std::string identifier, float volume,
float fade = 0.5f)
{
using namespace openspace;
global::moduleEngine->module<AudioModule>()->setVolume(identifier, volume, fade);
}
/**
* Returns the volume for the track referred to by the \p handle. The number returned will
* be greater or equal to 0.
*
* \return The volume for the track referred to by the \p handle, which will be
* greater or equal to 0
*/
[[codegen::luawrap]] float volume(std::string identifier) {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->volume(identifier);
}
/**
* Updates the 3D position of a track started through the #playAudio3d function. See that
* function and the #set3dListenerParameters function for a complete description. The
* \p identifier must be a name for a sound that was started through the #playAudio3d
* function.
*
* \param handle A valid handle for a track started through the #playAudio3d function
* \param position The new position from which the track originates
*/
[[codegen::luawrap]] void set3dSourcePosition(std::string identifier,
glm::vec3 position)
{
using namespace openspace;
global::moduleEngine->module<AudioModule>()->set3dSourcePosition(
identifier,
position
);
}
/**
* Returns the list of all tracks that are currently playing.
*
* \return The list of all tracks that are currently playing
*/
[[codegen::luawrap]] std::vector<std::string> currentlyPlaying() {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->currentlyPlaying();
}
/**
* Sets the global volume for all track referred to the new \p volume. The total for each
* track is the global volume set by this function multiplied with the volume for the
* specific track set through the #setVolume function. The default value for the global
* volume is 0.5. The volume should be a number bigger than 0, where 1 is the maximum
* volume level. The \p fade controls whether the volume change should be immediately (if
* it is 0) or over how many seconds it should change. The default is for it to change
* over 500 ms.
*
* \param volume The new volume level. Must be greater or equal to 0
* \param fade How much time the fade from the current volume to the new volume should
* take
*/
[[codegen::luawrap]] void setGlobalVolume(float volume, float fade = 0.5f) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->setGlobalVolume(volume, fade);
}
/**
* Returns the global volume for all track. The number returned will be greater or equal
* to 0.
*
* \return The global volume
*/
[[codegen::luawrap]] float globalVolume() {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->globalVolume();
}
/**
* Sets the position and orientation of the listener. This new position is automatically
* used to adjust the relative position of all 3D tracks. Each parameter to this function
* call is optional and if a value is omitted, the currently set value continues to be
* used instead. The coordinate system for the tracks and the listener is a meter-based
* coordinate system.
*
* \param position The position of the listener.
* \param lookAt The direction vector of the forward direction
* \param up The up-vector of the coordinate system
*/
[[codegen::luawrap]] void set3dListenerPosition(glm::vec3 position,
std::optional<glm::vec3> lookAt,
std::optional<glm::vec3> up)
{
using namespace openspace;
global::moduleEngine->module<AudioModule>()->set3dListenerParameters(
position,
lookAt,
up
);
}
/**
* Sets the position of the speaker for the provided \p channel to the provided
* \p position. In general, this is considered an advanced feature to accommodate
* non-standard audio environments.
*
* \param channel The channel whose speaker's position should be changed
* \param position The new position for the speaker
*/
[[codegen::luawrap]] void setSpeakerPosition(int handle, glm::vec3 position) {
using namespace openspace;
global::moduleEngine->module<AudioModule>()->setSpeakerPosition(handle, position);
}
/**
* Returns the position for the speaker of the provided \p channel.
* \return The position for the speaker of the provided \p channel
*/
[[codegen::luawrap]] glm::vec3 speakerPosition(int handle) {
using namespace openspace;
return global::moduleEngine->module<AudioModule>()->speakerPosition(handle);
}
#include "audiomodule_lua_codegen.cpp"
} // namespace

View File

@@ -2,7 +2,7 @@
# #
# OpenSpace #
# #
# Copyright (c) 2014-2022 #
# Copyright (c) 2014-2025 #
# #
# 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 #
@@ -22,20 +22,23 @@
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
include(${PROJECT_SOURCE_DIR}/support/cmake/module_definition.cmake)
set(HEADER_FILES
dashboard/dashboarditemangle.h
dashboard/dashboarditemcameraorientation.h
dashboard/dashboarditemdate.h
dashboard/dashboarditemdistance.h
dashboard/dashboarditemelapsedtime.h
dashboard/dashboarditemframerate.h
dashboard/dashboarditeminputstate.h
dashboard/dashboarditemmission.h
dashboard/dashboarditemparallelconnection.h
dashboard/dashboarditempropertyvalue.h
dashboard/dashboarditemsimulationincrement.h
dashboard/dashboarditemspacing.h
dashboard/dashboarditemtext.h
dashboard/dashboarditemtimevaryingtext.h
dashboard/dashboarditemvelocity.h
lightsource/cameralightsource.h
lightsource/scenegraphlightsource.h
@@ -43,10 +46,16 @@ set(HEADER_FILES
rendering/grids/renderablegrid.h
rendering/grids/renderableradialgrid.h
rendering/grids/renderablesphericalgrid.h
rendering/pointcloud/renderableinterpolatedpoints.h
rendering/pointcloud/renderablepointcloud.h
rendering/pointcloud/renderablepolygoncloud.h
rendering/pointcloud/sizemappingcomponent.h
rendering/renderablecartesianaxes.h
rendering/renderabledisc.h
rendering/renderabledistancelabel.h
rendering/renderablelabel.h
rendering/renderablemodel.h
rendering/renderablenodearrow.h
rendering/renderablenodeline.h
rendering/renderableplane.h
rendering/renderableplaneimagelocal.h
@@ -54,6 +63,9 @@ set(HEADER_FILES
rendering/renderableplanetimevaryingimage.h
rendering/renderableprism.h
rendering/renderablesphere.h
rendering/renderablesphereimagelocal.h
rendering/renderablesphereimageonline.h
rendering/renderableswitch.h
rendering/renderabletimevaryingsphere.h
rendering/renderabletrail.h
rendering/renderabletrailorbit.h
@@ -62,18 +74,27 @@ set(HEADER_FILES
rendering/screenspaceframebuffer.h
rendering/screenspaceimagelocal.h
rendering/screenspaceimageonline.h
rendering/screenspacerenderablerenderable.h
rendering/screenspacetimevaryingimageonline.h
rotation/timelinerotation.h
rotation/constantrotation.h
rotation/fixedrotation.h
rotation/globerotation.h
rotation/luarotation.h
rotation/multirotation.h
rotation/staticrotation.h
scale/luascale.h
scale/multiscale.h
scale/nonuniformstaticscale.h
scale/staticscale.h
scale/timedependentscale.h
scale/timelinescale.h
task/convertmodeltask.h
timeframe/timeframeinterval.h
timeframe/timeframeunion.h
translation/globetranslation.h
translation/luatranslation.h
translation/multitranslation.h
translation/statictranslation.h
translation/timelinetranslation.h
)
@@ -81,16 +102,19 @@ source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
dashboard/dashboarditemangle.cpp
dashboard/dashboarditemcameraorientation.cpp
dashboard/dashboarditemdate.cpp
dashboard/dashboarditemdistance.cpp
dashboard/dashboarditemelapsedtime.cpp
dashboard/dashboarditemframerate.cpp
dashboard/dashboarditeminputstate.cpp
dashboard/dashboarditemmission.cpp
dashboard/dashboarditemparallelconnection.cpp
dashboard/dashboarditempropertyvalue.cpp
dashboard/dashboarditemsimulationincrement.cpp
dashboard/dashboarditemspacing.cpp
dashboard/dashboarditemtext.cpp
dashboard/dashboarditemtimevaryingtext.cpp
dashboard/dashboarditemvelocity.cpp
lightsource/cameralightsource.cpp
lightsource/scenegraphlightsource.cpp
@@ -98,10 +122,16 @@ set(SOURCE_FILES
rendering/grids/renderablegrid.cpp
rendering/grids/renderableradialgrid.cpp
rendering/grids/renderablesphericalgrid.cpp
rendering/pointcloud/renderableinterpolatedpoints.cpp
rendering/pointcloud/renderablepointcloud.cpp
rendering/pointcloud/renderablepolygoncloud.cpp
rendering/pointcloud/sizemappingcomponent.cpp
rendering/renderablecartesianaxes.cpp
rendering/renderabledisc.cpp
rendering/renderabledistancelabel.cpp
rendering/renderablelabel.cpp
rendering/renderablemodel.cpp
rendering/renderablenodearrow.cpp
rendering/renderablenodeline.cpp
rendering/renderableplane.cpp
rendering/renderableplaneimagelocal.cpp
@@ -109,6 +139,9 @@ set(SOURCE_FILES
rendering/renderableplanetimevaryingimage.cpp
rendering/renderableprism.cpp
rendering/renderablesphere.cpp
rendering/renderablesphereimagelocal.cpp
rendering/renderablesphereimageonline.cpp
rendering/renderableswitch.cpp
rendering/renderabletimevaryingsphere.cpp
rendering/renderabletrail.cpp
rendering/renderabletrailorbit.cpp
@@ -118,24 +151,35 @@ set(SOURCE_FILES
rendering/screenspaceframebuffer.cpp
rendering/screenspaceimagelocal.cpp
rendering/screenspaceimageonline.cpp
rendering/screenspacerenderablerenderable.cpp
rendering/screenspacetimevaryingimageonline.cpp
rotation/timelinerotation.cpp
rotation/constantrotation.cpp
rotation/fixedrotation.cpp
rotation/globerotation.cpp
rotation/luarotation.cpp
rotation/multirotation.cpp
rotation/staticrotation.cpp
scale/luascale.cpp
scale/multiscale.cpp
scale/nonuniformstaticscale.cpp
scale/staticscale.cpp
scale/timedependentscale.cpp
scale/timelinescale.cpp
task/convertmodeltask.cpp
timeframe/timeframeinterval.cpp
timeframe/timeframeunion.cpp
translation/globetranslation.cpp
translation/luatranslation.cpp
translation/multitranslation.cpp
translation/statictranslation.cpp
translation/timelinetranslation.cpp
)
source_group("Source Files" FILES ${SOURCE_FILES})
set(SHADER_FILES
shaders/arrow_fs.glsl
shaders/arrow_vs.glsl
shaders/axes_fs.glsl
shaders/axes_vs.glsl
shaders/disc_fs.glsl
@@ -150,6 +194,13 @@ set(SHADER_FILES
shaders/model_vs.glsl
shaders/plane_fs.glsl
shaders/plane_vs.glsl
shaders/pointcloud/pointcloud_fs.glsl
shaders/pointcloud/pointcloud_gs.glsl
shaders/pointcloud/pointcloud_vs.glsl
shaders/pointcloud/pointcloud_interpolated_vs.glsl
shaders/polygon_fs.glsl
shaders/polygon_gs.glsl
shaders/polygon_vs.glsl
shaders/prism_fs.glsl
shaders/prism_vs.glsl
shaders/renderabletrail_fs.glsl

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -25,16 +25,19 @@
#include <modules/base/basemodule.h>
#include <modules/base/dashboard/dashboarditemangle.h>
#include <modules/base/dashboard/dashboarditemcameraorientation.h>
#include <modules/base/dashboard/dashboarditemdate.h>
#include <modules/base/dashboard/dashboarditemdistance.h>
#include <modules/base/dashboard/dashboarditemelapsedtime.h>
#include <modules/base/dashboard/dashboarditemframerate.h>
#include <modules/base/dashboard/dashboarditeminputstate.h>
#include <modules/base/dashboard/dashboarditemmission.h>
#include <modules/base/dashboard/dashboarditemparallelconnection.h>
#include <modules/base/dashboard/dashboarditempropertyvalue.h>
#include <modules/base/dashboard/dashboarditemsimulationincrement.h>
#include <modules/base/dashboard/dashboarditemspacing.h>
#include <modules/base/dashboard/dashboarditemtext.h>
#include <modules/base/dashboard/dashboarditemtimevaryingtext.h>
#include <modules/base/dashboard/dashboarditemvelocity.h>
#include <modules/base/lightsource/cameralightsource.h>
#include <modules/base/lightsource/scenegraphlightsource.h>
@@ -42,12 +45,20 @@
#include <modules/base/rendering/grids/renderablegrid.h>
#include <modules/base/rendering/grids/renderableradialgrid.h>
#include <modules/base/rendering/grids/renderablesphericalgrid.h>
#include <modules/base/rendering/pointcloud/renderableinterpolatedpoints.h>
#include <modules/base/rendering/pointcloud/renderablepointcloud.h>
#include <modules/base/rendering/pointcloud/renderablepolygoncloud.h>
#include <modules/base/rendering/pointcloud/sizemappingcomponent.h>
#include <modules/base/rendering/renderablecartesianaxes.h>
#include <modules/base/rendering/renderabledisc.h>
#include <modules/base/rendering/renderabledistancelabel.h>
#include <modules/base/rendering/renderablelabel.h>
#include <modules/base/rendering/renderablemodel.h>
#include <modules/base/rendering/renderablenodearrow.h>
#include <modules/base/rendering/renderablenodeline.h>
#include <modules/base/rendering/renderablesphere.h>
#include <modules/base/rendering/renderablesphereimagelocal.h>
#include <modules/base/rendering/renderablesphereimageonline.h>
#include <modules/base/rendering/renderableswitch.h>
#include <modules/base/rendering/renderabletrailorbit.h>
#include <modules/base/rendering/renderabletrailtrajectory.h>
#include <modules/base/rendering/renderableplaneimagelocal.h>
@@ -59,17 +70,26 @@
#include <modules/base/rendering/screenspaceimagelocal.h>
#include <modules/base/rendering/screenspaceimageonline.h>
#include <modules/base/rendering/screenspaceframebuffer.h>
#include <modules/base/rendering/screenspacerenderablerenderable.h>
#include <modules/base/rendering/screenspacetimevaryingimageonline.h>
#include <modules/base/rotation/constantrotation.h>
#include <modules/base/rotation/fixedrotation.h>
#include <modules/base/rotation/globerotation.h>
#include <modules/base/rotation/luarotation.h>
#include <modules/base/rotation/multirotation.h>
#include <modules/base/rotation/staticrotation.h>
#include <modules/base/rotation/timelinerotation.h>
#include <modules/base/scale/luascale.h>
#include <modules/base/scale/multiscale.h>
#include <modules/base/scale/nonuniformstaticscale.h>
#include <modules/base/scale/staticscale.h>
#include <modules/base/scale/timedependentscale.h>
#include <modules/base/scale/timelinescale.h>
#include <modules/base/task/convertmodeltask.h>
#include <modules/base/translation/timelinetranslation.h>
#include <modules/base/translation/globetranslation.h>
#include <modules/base/translation/luatranslation.h>
#include <modules/base/translation/multitranslation.h>
#include <modules/base/translation/statictranslation.h>
#include <modules/base/timeframe/timeframeinterval.h>
#include <modules/base/timeframe/timeframeunion.h>
@@ -89,26 +109,33 @@ ghoul::opengl::TextureManager BaseModule::TextureManager;
BaseModule::BaseModule() : OpenSpaceModule(BaseModule::Name) {}
void BaseModule::internalInitialize(const ghoul::Dictionary&) {
FactoryManager::ref().addFactory<ScreenSpaceRenderable>("ScreenSpaceRenderable");
ghoul::TemplateFactory<ScreenSpaceRenderable>* fSsRenderable =
FactoryManager::ref().factory<ScreenSpaceRenderable>();
ghoul_assert(fSsRenderable, "ScreenSpaceRenderable factory was not created");
fSsRenderable->registerClass<ScreenSpaceDashboard>("ScreenSpaceDashboard");
fSsRenderable->registerClass<ScreenSpaceFramebuffer>("ScreenSpaceFramebuffer");
fSsRenderable->registerClass<ScreenSpaceImageLocal>("ScreenSpaceImageLocal");
fSsRenderable->registerClass<ScreenSpaceImageOnline>("ScreenSpaceImageOnline");
fSsRenderable->registerClass<ScreenSpaceFramebuffer>("ScreenSpaceFramebuffer");
fSsRenderable->registerClass<ScreenSpaceRenderableRenderable>(
"ScreenSpaceRenderableRenderable"
);
fSsRenderable->registerClass<ScreenSpaceTimeVaryingImageOnline>("ScreenSpaceTimeVaryingImageOnline");
ghoul::TemplateFactory<DashboardItem>* fDashboard =
FactoryManager::ref().factory<DashboardItem>();
ghoul_assert(fDashboard, "Dashboard factory was not created");
fDashboard->registerClass<DashboardItemAngle>("DashboardItemAngle");
fDashboard->registerClass<DashboardItemCameraOrientation>(
"DashboardItemCameraOrientation"
);
fDashboard->registerClass<DashboardItemDate>("DashboardItemDate");
fDashboard->registerClass<DashboardItemDistance>("DashboardItemDistance");
fDashboard->registerClass<DashboardItemElapsedTime>("DashboardItemElapsedTime");
fDashboard->registerClass<DashboardItemFramerate>("DashboardItemFramerate");
fDashboard->registerClass<DashboardItemInputState>("DashboardItemInputState");
fDashboard->registerClass<DashboardItemMission>("DashboardItemMission");
fDashboard->registerClass<DashboardItemParallelConnection>(
"DashboardItemParallelConnection"
@@ -121,8 +148,20 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
);
fDashboard->registerClass<DashboardItemSpacing>("DashboardItemSpacing");
fDashboard->registerClass<DashboardItemText>("DashboardItemText");
fDashboard->registerClass<DashboardItemTimeVaryingText>(
"DashboardItemTimeVaryingText"
);
fDashboard->registerClass<DashboardItemVelocity>("DashboardItemVelocity");
ghoul::TemplateFactory<LightSource>* fLightSource =
FactoryManager::ref().factory<LightSource>();
ghoul_assert(fLightSource, "Light Source factory was not created");
fLightSource->registerClass<CameraLightSource>("CameraLightSource");
fLightSource->registerClass<SceneGraphLightSource>("SceneGraphLightSource");
ghoul::TemplateFactory<Renderable>* fRenderable =
FactoryManager::ref().factory<Renderable>();
ghoul_assert(fRenderable, "Renderable factory was not created");
@@ -130,32 +169,36 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fRenderable->registerClass<RenderableBoxGrid>("RenderableBoxGrid");
fRenderable->registerClass<RenderableCartesianAxes>("RenderableCartesianAxes");
fRenderable->registerClass<RenderableDisc>("RenderableDisc");
fRenderable->registerClass<RenderableDistanceLabel>("RenderableDistanceLabel");
fRenderable->registerClass<RenderableGrid>("RenderableGrid");
fRenderable->registerClass<RenderableLabel>("RenderableLabel");
fRenderable->registerClass<RenderableModel>("RenderableModel");
fRenderable->registerClass<RenderableNodeArrow>("RenderableNodeArrow");
fRenderable->registerClass<RenderableNodeLine>("RenderableNodeLine");
fRenderable->registerClass<RenderablePlaneImageLocal>("RenderablePlaneImageLocal");
fRenderable->registerClass<RenderablePlaneImageOnline>("RenderablePlaneImageOnline");
fRenderable->registerClass<RenderablePlaneTimeVaryingImage>(
"RenderablePlaneTimeVaryingImage"
);
fRenderable->registerClass<RenderableInterpolatedPoints>(
"RenderableInterpolatedPoints"
);
fRenderable->registerClass<RenderablePointCloud>("RenderablePointCloud");
fRenderable->registerClass<RenderablePolygonCloud>("RenderablePolygonCloud");
fRenderable->registerClass<RenderablePrism>("RenderablePrism");
fRenderable->registerClass<RenderableTimeVaryingSphere>(
"RenderableTimeVaryingSphere"
);
fRenderable->registerClass<RenderableRadialGrid>("RenderableRadialGrid");
fRenderable->registerClass<RenderableSphere>("RenderableSphere");
fRenderable->registerClass<RenderableSphereImageLocal>("RenderableSphereImageLocal");
fRenderable->registerClass<RenderableSphereImageOnline>(
"RenderableSphereImageOnline"
);
fRenderable->registerClass<RenderableSwitch>("RenderableSwitch");
fRenderable->registerClass<RenderableSphericalGrid>("RenderableSphericalGrid");
fRenderable->registerClass<RenderableTrailOrbit>("RenderableTrailOrbit");
fRenderable->registerClass<RenderableTrailTrajectory>("RenderableTrailTrajectory");
ghoul::TemplateFactory<Translation>* fTranslation =
FactoryManager::ref().factory<Translation>();
ghoul_assert(fTranslation, "Ephemeris factory was not created");
fTranslation->registerClass<TimelineTranslation>("TimelineTranslation");
fTranslation->registerClass<LuaTranslation>("LuaTranslation");
fTranslation->registerClass<StaticTranslation>("StaticTranslation");
ghoul::TemplateFactory<Rotation>* fRotation =
FactoryManager::ref().factory<Rotation>();
@@ -163,7 +206,9 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fRotation->registerClass<ConstantRotation>("ConstantRotation");
fRotation->registerClass<FixedRotation>("FixedRotation");
fRotation->registerClass<GlobeRotation>("GlobeRotation");
fRotation->registerClass<LuaRotation>("LuaRotation");
fRotation->registerClass<MultiRotation>("MultiRotation");
fRotation->registerClass<StaticRotation>("StaticRotation");
fRotation->registerClass<TimelineRotation>("TimelineRotation");
@@ -172,9 +217,12 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
ghoul_assert(fScale, "Scale factory was not created");
fScale->registerClass<LuaScale>("LuaScale");
fScale->registerClass<MultiScale>("MultiScale");
fScale->registerClass<NonUniformStaticScale>("NonUniformStaticScale");
fScale->registerClass<StaticScale>("StaticScale");
fScale->registerClass<TimeDependentScale>("TimeDependentScale");
fScale->registerClass<TimelineScale>("TimelineScale");
ghoul::TemplateFactory<TimeFrame>* fTimeFrame =
FactoryManager::ref().factory<TimeFrame>();
@@ -183,12 +231,23 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fTimeFrame->registerClass<TimeFrameInterval>("TimeFrameInterval");
fTimeFrame->registerClass<TimeFrameUnion>("TimeFrameUnion");
ghoul::TemplateFactory<LightSource>* fLightSource =
FactoryManager::ref().factory<LightSource>();
ghoul_assert(fLightSource, "Light Source factory was not created");
fLightSource->registerClass<CameraLightSource>("CameraLightSource");
fLightSource->registerClass<SceneGraphLightSource>("SceneGraphLightSource");
ghoul::TemplateFactory<Translation>* fTranslation =
FactoryManager::ref().factory<Translation>();
ghoul_assert(fTranslation, "Translation factory was not created");
fTranslation->registerClass<GlobeTranslation>("GlobeTranslation");
fTranslation->registerClass<LuaTranslation>("LuaTranslation");
fTranslation->registerClass<MultiTranslation>("MultiTranslation");
fTranslation->registerClass<StaticTranslation>("StaticTranslation");
fTranslation->registerClass<TimelineTranslation>("TimelineTranslation");
ghoul::TemplateFactory<Task>* fTask =
FactoryManager::ref().factory<Task>();
ghoul_assert(fTask, "Task factory was not created");
fTask->registerClass<ConvertModelTask>("ConvertModelTask");
}
void BaseModule::internalDeinitializeGL() {
@@ -199,61 +258,83 @@ void BaseModule::internalDeinitializeGL() {
std::vector<documentation::Documentation> BaseModule::documentations() const {
return {
DashboardItemAngle::Documentation(),
DashboardItemCameraOrientation::Documentation(),
DashboardItemDate::Documentation(),
DashboardItemDistance::Documentation(),
DashboardItemElapsedTime::Documentation(),
DashboardItemFramerate::Documentation(),
DashboardItemInputState::Documentation(),
DashboardItemMission::Documentation(),
DashboardItemParallelConnection::Documentation(),
DashboardItemPropertyValue::Documentation(),
DashboardItemSimulationIncrement::Documentation(),
DashboardItemSpacing::Documentation(),
DashboardItemText::Documentation(),
DashboardItemTimeVaryingText::Documentation(),
DashboardItemVelocity::Documentation(),
CameraLightSource::Documentation(),
SceneGraphLightSource::Documentation(),
RenderableBoxGrid::Documentation(),
RenderableCartesianAxes::Documentation(),
RenderableDisc::Documentation(),
RenderableDistanceLabel::Documentation(),
RenderableGrid::Documentation(),
RenderableInterpolatedPoints::Documentation(),
RenderableLabel::Documentation(),
RenderableModel::Documentation(),
RenderableNodeArrow::Documentation(),
RenderableNodeLine::Documentation(),
RenderablePlane::Documentation(),
RenderablePlaneImageLocal::Documentation(),
RenderablePlaneImageOnline::Documentation(),
RenderablePlaneTimeVaryingImage::Documentation(),
RenderablePointCloud::Documentation(),
RenderablePolygonCloud::Documentation(),
RenderablePrism::Documentation(),
RenderableRadialGrid::Documentation(),
RenderableSphere::Documentation(),
RenderableSphereImageLocal::Documentation(),
RenderableSphereImageOnline::Documentation(),
RenderableSphericalGrid::Documentation(),
RenderableSwitch::Documentation(),
RenderableTimeVaryingSphere::Documentation(),
RenderableTrailOrbit::Documentation(),
RenderableTrailTrajectory::Documentation(),
SizeMappingComponent::Documentation(),
ScreenSpaceDashboard::Documentation(),
ScreenSpaceFramebuffer::Documentation(),
ScreenSpaceImageLocal::Documentation(),
ScreenSpaceImageOnline::Documentation(),
ScreenSpaceRenderableRenderable::Documentation(),
ScreenSpaceTimeVaryingImageOnline::Documentation(),
ConstantRotation::Documentation(),
FixedRotation::Documentation(),
GlobeRotation::Documentation(),
LuaRotation::Documentation(),
MultiRotation::Documentation(),
StaticRotation::Documentation(),
TimelineRotation::Documentation(),
LuaScale::Documentation(),
MultiScale::Documentation(),
NonUniformStaticScale::Documentation(),
StaticScale::Documentation(),
TimeDependentScale::Documentation(),
LuaTranslation::Documentation(),
StaticTranslation::Documentation(),
TimelineTranslation::Documentation(),
TimelineScale::Documentation(),
TimeFrameInterval::Documentation(),
TimeFrameUnion::Documentation(),
CameraLightSource::Documentation(),
SceneGraphLightSource::Documentation()
GlobeTranslation::Documentation(),
LuaTranslation::Documentation(),
MultiTranslation::Documentation(),
StaticTranslation::Documentation(),
TimelineTranslation::Documentation(),
ConvertModelTask::Documentation()
};
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -50,16 +50,18 @@ namespace {
"SourceType",
"Source Type",
"The type of position that is used as the triangle apex used to calculate the "
"angle. The default value is 'Camera'"
"angle.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SourceNodeNameInfo = {
"SourceNodeName",
"Source Node Name",
"If a scene graph node is selected as type, this value specifies the name of the "
"node that is to be used as the apex of the triangle used to calculate the "
"angle. The computed angle is the incident angle to Source in the triangle ("
"Source, Reference, Destination)"
constexpr openspace::properties::Property::PropertyInfo SourceNodeIdentifierInfo = {
"SourceNodeIdentifier",
"Source Node Identifier",
"If a scene graph node is selected as type, this value specifies the identifier "
"of the node that is to be used as the apex of the triangle used to calculate "
"the angle. The computed angle is the incident angle to Source in the triangle ("
"Source, Reference, Destination).",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ReferenceTypeInfo = {
@@ -67,14 +69,17 @@ namespace {
"Reference Type",
"The type of position that is used as the destination of the reference line used "
"to calculate the angle. The computed angle is the incident angle to Source in "
"the triangle (Source, Reference, Destination)"
"the triangle (Source, Reference, Destination).",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ReferenceNodeNameInfo = {
"ReferenceNodeName",
"Reference Node Name",
"If a scene graph node is selected as type, this value specifies the name of the "
"node that is to be used as the reference direction to compute the angle"
constexpr openspace::properties::Property::PropertyInfo ReferenceNodeIdentifierInfo =
{
"ReferenceNodeIdentifier",
"Reference Node Identifier",
"If a scene graph node is selected as type, this value specifies the identifier "
"of the node that is to be used as the reference direction to compute the angle.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DestinationTypeInfo = {
@@ -82,16 +87,27 @@ namespace {
"Destination Type",
"The type of position that is used as the destination to calculate the angle. "
"The computed angle is the incident angle to Source in the triangle ("
"Source, Reference, Destination). The default value for this is 'Focus'"
"Source, Reference, Destination).",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DestinationNodeNameInfo = {
"DestinationNodeName",
"Destination Node Name",
"If a scene graph node is selected as type, this value specifies the name of the "
"node that is to be used as the destination for computing the angle"
constexpr openspace::properties::Property::PropertyInfo
DestinationNodeIdentifierInfo =
{
"DestinationNodeIdentifier",
"Destination Node Identifier",
"If a scene graph node is selected as type, this value specifies the identifier "
"of the node that is to be used as the destination for computing the angle.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` shows the angle between the lines `Source`->`Reference` and
// `Source`->`Destination`. Each of `Source`, `Reference`, and `Destination` can be
// either the identifier of a node, the current focus node, or the position of the
// camera. The angle cannot be calculated if two of these three items are located in
// the same position, in which case an error message is printed. The `SourceNodeName`,
// `ReferenceNodeName`, and `DestinationNodeName` parameters are only used if the
// `SourceType`, `ReferenceType`, or `DestinationType` respectively is set to `Node`.
struct [[codegen::Dictionary(DashboardItemAngle)]] Parameters {
enum class [[codegen::map(Type)]] Type {
Node,
@@ -100,17 +116,17 @@ namespace {
};
// [[codegen::verbatim(SourceTypeInfo.description)]]
std::optional<Type> sourceType;
// [[codegen::verbatim(SourceNodeNameInfo.description)]]
std::optional<std::string> sourceNodeName;
Type sourceType;
// [[codegen::verbatim(SourceNodeIdentifierInfo.description)]]
std::optional<std::string> sourceNodeIdentifier;
// [[codegen::verbatim(ReferenceTypeInfo.description)]]
Type referenceType;
// [[codegen::verbatim(ReferenceNodeNameInfo.description)]]
std::optional<std::string> referenceNodeName;
// [[codegen::verbatim(ReferenceNodeIdentifierInfo.description)]]
std::optional<std::string> referenceNodeIdentifier;
// [[codegen::verbatim(DestinationTypeInfo.description)]]
std::optional<Type> destinationType;
// [[codegen::verbatim(DestinationNodeNameInfo.description)]]
std::optional<std::string> destinationNodeName;
Type destinationType;
// [[codegen::verbatim(DestinationNodeIdentifierInfo.description)]]
std::optional<std::string> destinationNodeIdentifier;
};
#include "dashboarditemangle_codegen.cpp"
} // namespace
@@ -118,46 +134,27 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemAngle::Documentation() {
documentation::Documentation doc =
codegen::doc<Parameters>("base_dashboarditem_angle");
// @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_angle",
DashboardTextItem::Documentation()
);
return doc;
}
DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _source{
properties::OptionProperty(
SourceTypeInfo,
properties::OptionProperty::DisplayType::Dropdown
),
properties::StringProperty(SourceNodeNameInfo),
properties::OptionProperty(SourceTypeInfo),
properties::StringProperty(SourceNodeIdentifierInfo),
nullptr
}
, _reference{
properties::OptionProperty(
ReferenceTypeInfo,
properties::OptionProperty::DisplayType::Dropdown
),
properties::StringProperty(ReferenceNodeNameInfo),
properties::OptionProperty(ReferenceTypeInfo),
properties::StringProperty(ReferenceNodeIdentifierInfo),
nullptr
}
, _destination{
properties::OptionProperty(
DestinationTypeInfo,
properties::OptionProperty::DisplayType::Dropdown
),
properties::StringProperty(DestinationNodeNameInfo),
properties::OptionProperty(DestinationTypeInfo),
properties::StringProperty(DestinationNodeIdentifierInfo),
nullptr
}
{
@@ -168,18 +165,13 @@ DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
{ Type::Focus, "Focus" },
{ Type::Camera, "Camera" }
});
if (p.sourceType.has_value()) {
_source.type = codegen::map<Type>(*p.sourceType);
}
else {
_source.type = Type::Camera;
}
_source.type = codegen::map<Type>(p.sourceType);
addProperty(_source.type);
_source.nodeName.onChange([this]() { _source.node = nullptr; });
_source.nodeIdentifier.onChange([this]() { _source.node = nullptr; });
if (_source.type == Type::Node) {
if (p.sourceNodeName.has_value()) {
_source.nodeName = *p.sourceNodeName;
if (p.sourceNodeIdentifier.has_value()) {
_source.nodeIdentifier = *p.sourceNodeIdentifier;
}
else {
LERRORC(
@@ -188,7 +180,7 @@ DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
);
}
}
addProperty(_source.nodeName);
addProperty(_source.nodeIdentifier);
_reference.type.addOptions({
@@ -199,10 +191,10 @@ DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
_reference.type = codegen::map<Type>(p.referenceType);
addProperty(_reference.type);
_reference.nodeName.onChange([this]() { _reference.node = nullptr; });
_reference.nodeIdentifier.onChange([this]() { _reference.node = nullptr; });
if (_reference.type == Type::Node) {
if (p.referenceNodeName.has_value()) {
_reference.nodeName = *p.referenceNodeName;
if (p.referenceNodeIdentifier.has_value()) {
_reference.nodeIdentifier = *p.referenceNodeIdentifier;
}
else {
LERRORC(
@@ -211,24 +203,19 @@ DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
);
}
}
addProperty(_reference.nodeName);
addProperty(_reference.nodeIdentifier);
_destination.type.addOptions({
{ Type::Node, "Node" },
{ Type::Focus, "Focus" },
{ Type::Camera, "Camera" }
});
if (p.destinationType.has_value()) {
_destination.type = codegen::map<Type>(*p.destinationType);
}
else {
_destination.type = Type::Focus;
}
_destination.type = codegen::map<Type>(p.destinationType);
addProperty(_destination.type);
_destination.nodeName.onChange([this]() { _destination.node = nullptr; });
_destination.nodeIdentifier.onChange([this]() { _destination.node = nullptr; });
if (_destination.type == Type::Node) {
if (p.destinationNodeName.has_value()) {
_destination.nodeName = *p.destinationNodeName;
if (p.destinationNodeIdentifier.has_value()) {
_destination.nodeIdentifier = *p.destinationNodeIdentifier;
}
else {
LERRORC(
@@ -237,22 +224,55 @@ DashboardItemAngle::DashboardItemAngle(const ghoul::Dictionary& dictionary)
);
}
}
addProperty(_destination.nodeName);
addProperty(_destination.nodeIdentifier);
_buffer.resize(128);
_localBuffer.resize(128);
}
std::pair<glm::dvec3, std::string> DashboardItemAngle::positionAndLabel(
Component& comp) const
{
void DashboardItemAngle::update() {
ZoneScoped;
std::pair<glm::dvec3, std::string> sourceInfo = positionAndLabel(_source);
std::pair<glm::dvec3, std::string> referenceInfo = positionAndLabel(_reference);
std::pair<glm::dvec3, std::string> destinationInfo = positionAndLabel(_destination);
const glm::dvec3 a = referenceInfo.first - sourceInfo.first;
const glm::dvec3 b = destinationInfo.first - sourceInfo.first;
std::fill(_localBuffer.begin(), _localBuffer.end(), char(0));
if (glm::length(a) == 0.0 || glm::length(b) == 0) {
char* end = std::format_to(
_localBuffer.data(),
"Could not compute angle at {} between {} and {}. At least two of the three "
"items are placed in the same location",
sourceInfo.second, destinationInfo.second, referenceInfo.second
);
_buffer = std::string(_localBuffer.data(), end - _localBuffer.data());
}
else {
const double angle = glm::degrees(
glm::acos(glm::dot(a, b) / (glm::length(a) * glm::length(b)))
);
char* end = std::format_to(
_localBuffer.data(),
"Angle at {} between {} and {}: {} degrees",
sourceInfo.second, destinationInfo.second, referenceInfo.second, angle
);
_buffer = std::string(_localBuffer.data(), end - _localBuffer.data());
}
}
std::pair<glm::dvec3, std::string> DashboardItemAngle::positionAndLabel(Component& comp) {
if (comp.type == Type::Node) {
if (!comp.node) {
comp.node = global::renderEngine->scene()->sceneGraphNode(comp.nodeName);
comp.node =
global::renderEngine->scene()->sceneGraphNode(comp.nodeIdentifier);
if (!comp.node) {
LERRORC(
"DashboardItemAngle",
"Could not find node '" + comp.nodeName.value() + "'"
"Could not find node '" + comp.nodeIdentifier.value() + "'"
);
return { glm::dvec3(0.0), "Node" };
}
@@ -278,48 +298,4 @@ std::pair<glm::dvec3, std::string> DashboardItemAngle::positionAndLabel(
}
}
void DashboardItemAngle::render(glm::vec2& penPosition) {
ZoneScoped
std::pair<glm::dvec3, std::string> sourceInfo = positionAndLabel(_source);
std::pair<glm::dvec3, std::string> referenceInfo = positionAndLabel(_reference);
std::pair<glm::dvec3, std::string> destinationInfo = positionAndLabel(_destination);
const glm::dvec3 a = referenceInfo.first - sourceInfo.first;
const glm::dvec3 b = destinationInfo.first - sourceInfo.first;
std::fill(_buffer.begin(), _buffer.end(), char(0));
if (glm::length(a) == 0.0 || glm::length(b) == 0) {
char* end = fmt::format_to(
_buffer.data(),
"Could not compute angle at {} between {} and {}",
sourceInfo.second, destinationInfo.second, referenceInfo.second
);
std::string_view text = std::string_view(_buffer.data(), end - _buffer.data());
RenderFont(*_font, penPosition, text);
penPosition.y -= _font->height();
}
else {
const double angle = glm::degrees(
glm::acos(glm::dot(a, b) / (glm::length(a) * glm::length(b)))
);
char* end = fmt::format_to(
_buffer.data(),
"Angle at {} between {} and {}: {} degrees",
sourceInfo.second, destinationInfo.second, referenceInfo.second, angle
);
std::string_view text = std::string_view(_buffer.data(), end - _buffer.data());
RenderFont(*_font, penPosition, text);
penPosition.y -= _font->height();
}
}
glm::vec2 DashboardItemAngle::size() const {
ZoneScoped
constexpr double Angle = 120;
return _font->boundingBox("Angle: " + std::to_string(Angle));
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <utility>
namespace openspace {
@@ -39,29 +39,27 @@ namespace documentation { struct Documentation; }
class DashboardItemAngle : public DashboardTextItem {
public:
DashboardItemAngle(const ghoul::Dictionary& dictionary);
explicit DashboardItemAngle(const ghoul::Dictionary& dictionary);
~DashboardItemAngle() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();
private:
struct Component {
properties::OptionProperty type;
properties::StringProperty nodeName;
properties::StringProperty nodeIdentifier;
SceneGraphNode* node;
};
std::pair<glm::dvec3, std::string> positionAndLabel(Component& comp) const;
static std::pair<glm::dvec3, std::string> positionAndLabel(Component& comp);
Component _source;
Component _reference;
Component _destination;
std::vector<char> _buffer;
std::vector<char> _localBuffer;
};
} // namespace openspace

View File

@@ -0,0 +1,71 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/dashboard/dashboarditemcameraorientation.h>
#include <openspace/camera/camera.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <ghoul/font/font.h>
namespace {
// This `DashboardItem` shows the current camera orientation in the yaw, pitch, and
// roll directions in degrees. Note that the camera's orientation is relative to the
// global coordinate system used in the system.
struct [[codegen::Dictionary(DashboardItemCameraOrientation)]] Parameters {
};
#include "dashboarditemcameraorientation_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation DashboardItemCameraOrientation::Documentation() {
return codegen::doc<Parameters>(
"base_dashboarditem_cameraorientation",
DashboardTextItem::Documentation()
);
}
DashboardItemCameraOrientation::DashboardItemCameraOrientation(
const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
{}
void DashboardItemCameraOrientation::update() {
ZoneScoped;
const Camera* camera = global::renderEngine->scene()->camera();
const glm::dquat orientation = camera->rotationQuaternion();
const glm::dvec3 pitchYawRoll = glm::eulerAngles(orientation);
const glm::dvec3 pitchYawRollDeg = glm::degrees(pitchYawRoll);
_buffer = std::format(
"Yaw: {:.2f}\nPitch: {:.2f}\nRoll: {:.2f}",
pitchYawRollDeg.y, pitchYawRollDeg.x, pitchYawRollDeg.z
);
}
} // namespace openspace

View File

@@ -0,0 +1,44 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___DASHBOARDITEMCAMERAORIENTATION___H__
#define __OPENSPACE_MODULE_BASE___DASHBOARDITEMCAMERAORIENTATION___H__
#include <openspace/rendering/dashboardtextitem.h>
namespace openspace {
class DashboardItemCameraOrientation : public DashboardTextItem {
public:
explicit DashboardItemCameraOrientation(const ghoul::Dictionary& dictionary);
~DashboardItemCameraOrientation() override = default;
void update() override;
static documentation::Documentation Documentation();
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___DASHBOARDITEMCAMERAORIENTATION___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -40,7 +40,9 @@ namespace {
"FormatString",
"Format String",
"The format text describing how this dashboard item renders its text. This text "
"must contain exactly one {} which is a placeholder that will contain the date"
"must contain exactly one {} which is a placeholder that will contain the date "
"in the format as specified by `TimeFormat`.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo TimeFormatInfo = {
@@ -49,9 +51,14 @@ namespace {
"The format string used for formatting the date/time before being passed to the "
"string in FormatString. See "
"https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/timout_c.html for full "
"information about how to structure this format"
"information about how to structure this format.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` shows the current in-game simulation time. The `FormatString`
// and the `TimeFormat` options provide the ability to customize the output that is
// printed. See these two parameters for more information on how to structure the
// inputs.
struct [[codegen::Dictionary(DashboardItemDate)]] Parameters {
// [[codegen::verbatim(FormatStringInfo.description)]]
std::optional<std::string> formatString;
@@ -65,13 +72,16 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemDate::Documentation() {
return codegen::doc<Parameters>("base_dashboarditem_date");
return codegen::doc<Parameters>(
"base_dashboarditem_date",
DashboardTextItem::Documentation()
);
}
DashboardItemDate::DashboardItemDate(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary, 15.f)
, _formatString(FormatStringInfo, "Date: {} UTC")
, _timeFormat(TimeFormatInfo, "YYYY MON DDTHR:MN:SC.### ::RND")
, _formatString(FormatStringInfo, "Date: {}")
, _timeFormat(TimeFormatInfo, "YYYY MON DD HR:MN:SC.### UTC ::RND")
{
const Parameters p = codegen::bake<Parameters>(dictionary);
@@ -82,8 +92,8 @@ DashboardItemDate::DashboardItemDate(const ghoul::Dictionary& dictionary)
addProperty(_timeFormat);
}
void DashboardItemDate::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemDate::update() {
ZoneScoped;
std::string time = SpiceManager::ref().dateFromEphemerisTime(
global::timeManager->time().j2000Seconds(),
@@ -91,23 +101,12 @@ void DashboardItemDate::render(glm::vec2& penPosition) {
);
try {
RenderFont(
*_font,
penPosition,
fmt::format(fmt::runtime(_formatString.value()), time)
);
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_formatString.value(), std::make_format_args(time));
}
catch (const fmt::format_error&) {
catch (const std::format_error&) {
LERRORC("DashboardItemDate", "Illegal format string");
}
penPosition.y -= _font->height();
}
glm::vec2 DashboardItemDate::size() const {
ZoneScoped
std::string_view time = global::timeManager->time().UTC();
return _font->boundingBox(fmt::format(fmt::runtime(_formatString.value()), time));
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,7 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/stringproperty.h>
namespace openspace {
@@ -35,12 +35,10 @@ namespace documentation { struct Documentation; }
class DashboardItemDate : public DashboardTextItem {
public:
DashboardItemDate(const ghoul::Dictionary& dictionary);
explicit DashboardItemDate(const ghoul::Dictionary& dictionary);
~DashboardItemDate() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -51,29 +51,33 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo SourceTypeInfo = {
"SourceType",
"Source Type",
"The type of position that is used as the source to calculate the distance. The "
"default value is 'Camera'"
"The type of position that is used as the source to calculate the distance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SourceNodeNameInfo = {
"SourceNodeName",
"Source Node Name",
"If a scene graph node is selected as type, this value specifies the name of the "
"node that is to be used as the source for computing the distance"
constexpr openspace::properties::Property::PropertyInfo SourceNodeIdentifierInfo = {
"SourceNodeIdentifier",
"Source Node Identifier",
"If a scene graph node is selected as type, this value specifies the identifier "
"of the node that is to be used as the source for computing the distance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DestinationTypeInfo = {
"DestinationType",
"Destination Type",
"The type of position that is used as the destination to calculate the distance. "
"The default value for this is 'Focus'"
"The type of position that is used as the destination to calculate the distance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DestinationNodeNameInfo = {
"DestinationNodeName",
"Destination Node Name",
"If a scene graph node is selected as type, this value specifies the name of the "
"node that is to be used as the destination for computing the distance"
constexpr openspace::properties::Property::PropertyInfo
DestinationNodeIdentifierInfo =
{
"DestinationNodeIdentifier",
"Destination Node Identifier",
"If a scene graph node is selected as type, this value specifies the identifier "
"of the node that is to be used as the destination for computing the distance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SimplificationInfo = {
@@ -81,14 +85,16 @@ namespace {
"Simplification",
"If this value is enabled, the distance is displayed in nuanced units, such as "
"km, AU, light years, parsecs, etc. If this value is disabled, the unit can be "
"explicitly requested"
"explicitly requested.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RequestedUnitInfo = {
"RequestedUnit",
"Requested Unit",
"If the simplification is disabled, this distance unit is used as a destination "
"to convert the meters into"
"to convert the meters into.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo FormatStringInfo = {
@@ -96,22 +102,18 @@ namespace {
"Format String",
"The format string that is used for formatting the distance string. This format "
"receives four parameters: The name of the source, the name of the destination "
"the value of the distance and the unit of the distance"
"the value of the distance and the unit of the distance.",
openspace::properties::Property::Visibility::AdvancedUser
};
std::vector<std::string> unitList() {
std::vector<std::string> res(openspace::DistanceUnits.size());
std::transform(
openspace::DistanceUnits.begin(),
openspace::DistanceUnits.end(),
res.begin(),
[](openspace::DistanceUnit unit) {
return std::string(nameForDistanceUnit(unit));
}
);
return res;
}
// This `DashboardItem` displays the distance between two points. The points can be
// defined either by the location of a scene graph node, the surface of a scene graph
// node's bounding sphere, the location of the current focus node, or the position of
// the camera. These definitions can be mixed and matched to calculate any combination
// of positions.
//
// The resulting text can be formatted in the `FormatString` and the measurement unit
// is chosed by changing the `Simplification` and `RequestedUnit` parameters.
struct [[codegen::Dictionary(DashboardItemDistance)]] Parameters {
enum class [[codegen::map(Type)]] TypeInfo {
Node,
@@ -121,22 +123,23 @@ namespace {
};
// [[codegen::verbatim(SourceTypeInfo.description)]]
std::optional<TypeInfo> sourceType;
TypeInfo sourceType;
// [[codegen::verbatim(SourceNodeNameInfo.description)]]
std::optional<std::string> sourceNodeName;
// [[codegen::verbatim(SourceNodeIdentifierInfo.description)]]
std::optional<std::string> sourceNodeIdentifier;
// [[codegen::verbatim(DestinationTypeInfo.description)]]
std::optional<TypeInfo> destinationType;
TypeInfo destinationType;
// [[codegen::verbatim(DestinationNodeNameInfo.description)]]
std::optional<std::string> destinationNodeName;
// [[codegen::verbatim(DestinationNodeIdentifierInfo.description)]]
std::optional<std::string> destinationNodeIdentifier;
// [[codegen::verbatim(SimplificationInfo.description)]]
std::optional<bool> simplification;
// [[codegen::verbatim(RequestedUnitInfo.description)]]
std::optional<std::string> requestedUnit [[codegen::inlist(unitList())]];
std::optional<std::string> requestedUnit
[[codegen::inlist(openspace::distanceUnitList())]];
// [[codegen::verbatim(FormatStringInfo.description)]]
std::optional<std::string> formatString;
@@ -156,22 +159,16 @@ documentation::Documentation DashboardItemDistance::Documentation() {
DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _doSimplification(SimplificationInfo, true)
, _requestedUnit(RequestedUnitInfo, properties::OptionProperty::DisplayType::Dropdown)
, _requestedUnit(RequestedUnitInfo)
, _formatString(FormatStringInfo, "Distance from {} to {}: {:f} {}")
, _source{
properties::OptionProperty(
SourceTypeInfo,
properties::OptionProperty::DisplayType::Dropdown
),
properties::StringProperty(SourceNodeNameInfo),
properties::OptionProperty(SourceTypeInfo),
properties::StringProperty(SourceNodeIdentifierInfo),
nullptr
}
, _destination{
properties::OptionProperty(
DestinationTypeInfo,
properties::OptionProperty::DisplayType::Dropdown
),
properties::StringProperty(DestinationNodeNameInfo),
properties::OptionProperty(DestinationTypeInfo),
properties::StringProperty(DestinationNodeIdentifierInfo),
nullptr
}
{
@@ -184,24 +181,19 @@ DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary
{ Type::Camera, "Camera" }
});
_source.type.onChange([this]() {
_source.nodeName.setVisibility(
_source.nodeIdentifier.setVisibility(
properties::Property::Visibility(
_source.type == Type::Node || _source.type == Type::NodeSurface
)
);
});
if (p.sourceType.has_value()) {
_source.type = codegen::map<Type>(*p.sourceType);
}
else {
_source.type = Type::Camera;
}
_source.type = codegen::map<Type>(p.sourceType);
addProperty(_source.type);
_source.nodeName.onChange([this]() { _source.node = nullptr; });
_source.nodeIdentifier.onChange([this]() { _source.node = nullptr; });
if (_source.type == Type::Node || _source.type == Type::NodeSurface) {
if (p.sourceNodeName.has_value()) {
_source.nodeName = *p.sourceNodeName;
if (p.sourceNodeIdentifier.has_value()) {
_source.nodeIdentifier = *p.sourceNodeIdentifier;
}
else {
LERRORC(
@@ -210,7 +202,7 @@ DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary
);
}
}
addProperty(_source.nodeName);
addProperty(_source.nodeIdentifier);
_destination.type.addOptions({
{ Type::Node, "Node" },
@@ -219,23 +211,18 @@ DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary
{ Type::Camera, "Camera" }
});
_destination.type.onChange([this]() {
_destination.nodeName.setVisibility(
_destination.nodeIdentifier.setVisibility(
properties::Property::Visibility(
_source.type == Type::Node || _source.type == Type::NodeSurface
)
);
});
if (p.destinationType.has_value()) {
_destination.type = codegen::map<Type>(*p.destinationType);
}
else {
_destination.type = Type::Focus;
}
_destination.type = codegen::map<Type>(p.destinationType);
addProperty(_destination.type);
_destination.nodeName.onChange([this]() { _destination.node = nullptr; });
_destination.nodeIdentifier.onChange([this]() { _destination.node = nullptr; });
if (_destination.type == Type::Node || _destination.type == Type::NodeSurface) {
if (p.destinationNodeName.has_value()) {
_destination.nodeName = *p.destinationNodeName;
if (p.destinationNodeIdentifier.has_value()) {
_destination.nodeIdentifier = *p.destinationNodeIdentifier;
}
else {
LERRORC(
@@ -244,19 +231,12 @@ DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary
);
}
}
addProperty(_destination.nodeName);
addProperty(_destination.nodeIdentifier);
_doSimplification = p.simplification.value_or(_doSimplification);
_doSimplification.onChange([this]() {
_requestedUnit.setVisibility(
_doSimplification ?
properties::Property::Visibility::Hidden :
properties::Property::Visibility::User
);
});
addProperty(_doSimplification);
for (DistanceUnit u : DistanceUnits) {
for (const DistanceUnit u : DistanceUnits) {
_requestedUnit.addOption(
static_cast<int>(u),
std::string(nameForDistanceUnit(u))
@@ -264,16 +244,15 @@ DashboardItemDistance::DashboardItemDistance(const ghoul::Dictionary& dictionary
}
_requestedUnit = static_cast<int>(DistanceUnit::Meter);
if (p.requestedUnit.has_value()) {
DistanceUnit unit = distanceUnitFromString(p.requestedUnit->c_str());
const DistanceUnit unit = distanceUnitFromString(*p.requestedUnit);
_requestedUnit = static_cast<int>(unit);
}
_requestedUnit.setVisibility(properties::Property::Visibility::Hidden);
addProperty(_requestedUnit);
_formatString = p.formatString.value_or(_formatString);
addProperty(_formatString);
_buffer.resize(256);
_localBuffer.resize(256);
}
std::pair<glm::dvec3, std::string> DashboardItemDistance::positionAndLabel(
@@ -281,15 +260,15 @@ std::pair<glm::dvec3, std::string> DashboardItemDistance::positionAndLabel(
Component& otherComp) const
{
if ((mainComp.type == Type::Node) || (mainComp.type == Type::NodeSurface)) {
if (!mainComp.node) {
if (!mainComp.node) [[unlikely]] {
mainComp.node = global::renderEngine->scene()->sceneGraphNode(
mainComp.nodeName
mainComp.nodeIdentifier
);
if (!mainComp.node) {
LERRORC(
"DashboardItemDistance",
"Could not find node '" + mainComp.nodeName.value() + "'"
"Could not find node '" + mainComp.nodeIdentifier.value() + "'"
);
return { glm::dvec3(0.0), "Node" };
}
@@ -313,9 +292,9 @@ std::pair<glm::dvec3, std::string> DashboardItemDistance::positionAndLabel(
const glm::dvec3 thisPos = mainComp.node->worldPosition();
const glm::dvec3 dir = glm::normalize(otherPos - thisPos);
glm::dvec3 dirLength = dir * glm::dvec3(mainComp.node->boundingSphere());
const glm::dvec3 dirLen = dir * glm::dvec3(mainComp.node->boundingSphere());
return { thisPos + dirLength, "surface of " + mainComp.node->guiName() };
return { thisPos + dirLen, "surface of " + mainComp.node->guiName() };
}
case Type::Focus: {
const SceneGraphNode* anchor =
@@ -334,8 +313,8 @@ std::pair<glm::dvec3, std::string> DashboardItemDistance::positionAndLabel(
}
}
void DashboardItemDistance::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemDistance::update() {
ZoneScoped;
std::pair<glm::dvec3, std::string> sourceInfo = positionAndLabel(
_source,
@@ -354,43 +333,28 @@ void DashboardItemDistance::render(glm::vec2& penPosition) {
else {
const DistanceUnit unit = static_cast<DistanceUnit>(_requestedUnit.value());
const double convertedD = convertMeters(d, unit);
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
dist = std::pair(convertedD, nameForDistanceUnit(unit, convertedD != 1.0));
}
std::fill(_buffer.begin(), _buffer.end(), char(0));
std::fill(_localBuffer.begin(), _localBuffer.end(), char(0));
try {
char* end = fmt::format_to(
_buffer.data(),
fmt::runtime(_formatString.value()),
sourceInfo.second, destinationInfo.second, dist.first, dist.second
// @CPP26(abock): This can be replaced with std::runtime_format
char* end = std::vformat_to(
_localBuffer.data(),
_formatString.value(),
std::make_format_args(
sourceInfo.second,
destinationInfo.second,
dist.first,
dist.second
)
);
std::string_view text = std::string_view(_buffer.data(), end - _buffer.data());
RenderFont(*_font, penPosition, text);
_buffer = std::string(_localBuffer.data(), end - _localBuffer.data());
}
catch (const fmt::format_error&) {
catch (const std::format_error&) {
LERRORC("DashboardItemDate", "Illegal format string");
}
penPosition.y -= _font->height();
}
glm::vec2 DashboardItemDistance::size() const {
ZoneScoped
const double d = glm::length(1e20);
std::pair<double, std::string_view> dist;
if (_doSimplification) {
dist = simplifyDistance(d);
}
else {
DistanceUnit unit = static_cast<DistanceUnit>(_requestedUnit.value());
double convertedD = convertMeters(d, unit);
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
}
return _font->boundingBox(
fmt::format("Distance from focus: {} {}", dist.first, dist.second)
);
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <utility>
@@ -40,19 +40,17 @@ namespace documentation { struct Documentation; }
class DashboardItemDistance : public DashboardTextItem {
public:
DashboardItemDistance(const ghoul::Dictionary& dictionary);
explicit DashboardItemDistance(const ghoul::Dictionary& dictionary);
~DashboardItemDistance() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();
private:
struct Component {
properties::OptionProperty type;
properties::StringProperty nodeName;
properties::StringProperty nodeIdentifier;
SceneGraphNode* node;
};
@@ -66,7 +64,7 @@ private:
Component _source;
Component _destination;
std::vector<char> _buffer;
std::vector<char> _localBuffer;
};
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -41,14 +41,16 @@ namespace {
"Format String",
"The format text describing how this dashboard item renders its text. This text "
"must contain exactly one {} which is a placeholder that will contain the value "
"of the elapsed time."
"of the elapsed time.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ReferenceTimeInfo = {
"ReferenceTime",
"Reference Time",
"The reference time relative to which the elapsed time is specified. The format "
"must be an ISO 8601-compliant date string"
"must be an ISO 8601-compliant date string.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SimplifyTimeInfo = {
@@ -56,22 +58,27 @@ namespace {
"Simplify Time",
"If this value is enabled, the elapsed time will be simplified into seconds, "
"minutes, hours, etc. If the value is disabled, the elapsed time is always "
"presented in seconds. The default value for this is 'true'."
"presented in seconds. The default value for this is 'true'.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LowestTimeUnitInfo = {
"LowestTimeUnit",
"Lowest Time Unit when Simplifying",
"If 'SimplifyTime' is enabled, this is the lowest time unit that will be shown. "
"All finer grained timesteps will be ignored."
"All finer grained timesteps will be ignored.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` displays the remaining time until a provided `ReferenceTime`
// or the elapsed time since the `ReferenceTime`. The output can be configured through
// the `FormatString` and the unit that is used to display the configurable as well.
struct [[codegen::Dictionary(DashboardItemElapsedTime)]] Parameters {
// [[codegen::verbatim(FormatStringInfo.description)]]
std::optional<std::string> formatString;
// [[codegen::verbatim(ReferenceTimeInfo.description)]]
std::string referenceTime;
std::string referenceTime [[codegen::datetime()]];
// [[codegen::verbatim(SimplifyTimeInfo.description)]]
std::optional<bool> simplifyTime;
@@ -97,7 +104,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemElapsedTime::Documentation() {
return codegen::doc<Parameters>("base_dashboarditem_elapsedtime");
return codegen::doc<Parameters>(
"base_dashboarditem_elapsedtime",
DashboardTextItem::Documentation()
);
}
DashboardItemElapsedTime::DashboardItemElapsedTime(const ghoul::Dictionary& dictionary)
@@ -110,6 +120,7 @@ DashboardItemElapsedTime::DashboardItemElapsedTime(const ghoul::Dictionary& dict
const Parameters p = codegen::bake<Parameters>(dictionary);
_formatString = p.formatString.value_or(_formatString);
addProperty(_formatString);
_referenceTime.onChange([this]() {
_referenceJ2000 = Time::convertTime(_referenceTime);
@@ -120,65 +131,48 @@ DashboardItemElapsedTime::DashboardItemElapsedTime(const ghoul::Dictionary& dict
_simplifyTime = p.simplifyTime.value_or(_simplifyTime);
addProperty(_simplifyTime);
for (TimeUnit u : TimeUnits) {
for (const TimeUnit u : TimeUnits) {
_lowestTimeUnit.addOption(static_cast<int>(u), std::string(nameForTimeUnit(u)));
}
_lowestTimeUnit = static_cast<int>(TimeUnit::Second);
TimeUnit u = codegen::map<TimeUnit>(
const TimeUnit u = codegen::map<TimeUnit>(
p.lowestTimeUnit.value_or(Parameters::TimeUnit::Second)
);
_lowestTimeUnit = static_cast<int>(u);
addProperty(_lowestTimeUnit);
}
void DashboardItemElapsedTime::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemElapsedTime::update() {
ZoneScoped;
const double delta = global::timeManager->time().j2000Seconds() - _referenceJ2000;
double delta = global::timeManager->time().j2000Seconds() - _referenceJ2000;
if (_simplifyTime) {
using namespace std::chrono;
TimeUnit lowestTime = TimeUnit(_lowestTimeUnit.value());
std::string_view lowestUnitS = nameForTimeUnit(lowestTime, false);
std::string_view lowestUnitP = nameForTimeUnit(lowestTime, true);
const TimeUnit lowestTime = TimeUnit(_lowestTimeUnit.value());
std::vector<std::pair<double, std::string_view>> ts = splitTime(delta);
const std::vector<std::pair<double, std::string_view>> ts = splitTime(delta);
std::string time;
for (const std::pair<double, std::string_view>& t : ts) {
time += fmt::format("{} {} ", t.first, t.second);
if (t.second == lowestUnitS || t.second == lowestUnitP) {
if (timeUnitFromString(t.second) < lowestTime) {
// We have reached the lowest unit the user was interested in
break;
}
time += std::format("{} {} ", t.first, t.second);
}
// Remove the " " at the end
time = time.substr(0, time.size() - 2);
time = time.substr(0, time.size() - 1);
RenderFont(
*_font,
penPosition,
fmt::format(fmt::runtime(_formatString.value()), time)
);
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_formatString.value(), std::make_format_args(time));
}
else {
std::string time = fmt::format("{} s", delta);
RenderFont(
*_font,
penPosition,
fmt::format(fmt::runtime(_formatString.value()), time)
);
std::string time = std::format("{} s", delta);
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_formatString.value(), std::make_format_args(time));
}
penPosition.y -= _font->height();
}
glm::vec2 DashboardItemElapsedTime::size() const {
ZoneScoped
const double delta = global::timeManager->time().j2000Seconds() - _referenceJ2000;
return _font->boundingBox(fmt::format(fmt::runtime(_formatString.value()), delta));
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,9 +27,9 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
namespace openspace {
@@ -37,12 +37,10 @@ namespace documentation { struct Documentation; }
class DashboardItemElapsedTime : public DashboardTextItem {
public:
DashboardItemElapsedTime(const ghoul::Dictionary& dictionary);
explicit DashboardItemElapsedTime(const ghoul::Dictionary& dictionary);
~DashboardItemElapsedTime() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();
@@ -56,4 +54,4 @@ private:
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___DASHBOARDITEMDATE___H__
#endif // __OPENSPACE_MODULE_BASE___DASHBOARDITEMELAPSEDTIME___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -36,41 +36,51 @@
namespace {
enum FrametimeType {
DtTimeAvg = 0,
DtTime = 0,
DtTimeAvg,
DtTimeExtremes,
DtStandardDeviation,
DtCoefficientOfVariation,
FPS,
FPSAvg,
None
FPSAvg
};
constexpr openspace::properties::Property::PropertyInfo FrametimeInfo = {
"FrametimeType",
"Type of the frame time display",
"This value determines the units in which the frame time is displayed"
"This value determines the units in which the frame time is displayed.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ClearCacheInfo = {
"ClearCache",
"Clear Cache",
"Clears the cache of this DashboardItemFramerate item. If the selected option "
"does not use any caching, this trigger does not do anything"
"does not use any caching, this trigger does not do anything.",
openspace::properties::Property::Visibility::AdvancedUser
};
[[ nodiscard ]] char* formatDt(std::vector<char>& buffer) {
return fmt::format_to(
[[nodiscard]] char* formatDt(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"Frametime: {:.2f} ms\0",
openspace::global::windowDelegate->deltaTime()
);
}
[[nodiscard]] char* formatDtAvg(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"Avg. Frametime: {:.2f} ms\0",
openspace::global::windowDelegate->averageDeltaTime() * 1000.0
);
}
[[ nodiscard ]] char* formatDtExtremes(std::vector<char>& buffer,
[[nodiscard]] char* formatDtExtremes(std::vector<char>& buffer,
double minFrametimeCache,
double maxFrametimeCache)
{
return fmt::format_to(
return std::format_to(
buffer.data(),
"Last frametimes between: {:.2f} and {:.2f} ms\n"
"Overall between: {:.2f} and {:.2f} ms\0",
@@ -81,16 +91,16 @@ namespace {
);
}
[[ nodiscard ]] char* formatDtStandardDeviation(std::vector<char>& buffer) {
return fmt::format_to(
[[nodiscard]] char* formatDtStandardDeviation(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"Frametime standard deviation : {:.2f} ms\0",
openspace::global::windowDelegate->deltaTimeStandardDeviation() * 1000.0
);
}
[[ nodiscard ]] char* formatDtCoefficientOfVariation(std::vector<char>& buffer) {
return fmt::format_to(
[[nodiscard]] char* formatDtCoefficientOfVariation(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"Frametime coefficient of variation : {:.2f} %\0",
openspace::global::windowDelegate->deltaTimeStandardDeviation() /
@@ -98,29 +108,31 @@ namespace {
);
}
[[ nodiscard ]] char* formatFps(std::vector<char>& buffer) {
return fmt::format_to(
[[nodiscard]] char* formatFps(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"FPS: {:3.2f}\0",
1.0 / openspace::global::windowDelegate->deltaTime()
);
}
[[ nodiscard ]] char* formatAverageFps(std::vector<char>& buffer) {
return fmt::format_to(
[[nodiscard]] char* formatAverageFps(std::vector<char>& buffer) {
return std::format_to(
buffer.data(),
"Avg. FPS: {:3.2f}\0",
1.0 / openspace::global::windowDelegate->averageDeltaTime()
);
}
[[ nodiscard ]] char* format(std::vector<char>& buffer, FrametimeType frametimeType,
[[nodiscard]] char* format(std::vector<char>& buffer, FrametimeType frametimeType,
double minFrametimeCache, double maxFrametimeCache)
{
using namespace openspace;
switch (frametimeType) {
case FrametimeType::DtTimeAvg:
case FrametimeType::DtTime:
return formatDt(buffer);
case FrametimeType::DtTimeAvg:
return formatDtAvg(buffer);
case FrametimeType::DtTimeExtremes:
return formatDtExtremes(buffer, minFrametimeCache, maxFrametimeCache);
case FrametimeType::DtStandardDeviation:
@@ -136,8 +148,29 @@ namespace {
}
}
// This `DashboardItem` provides information about the current framerate at which the
// rendering updates. The `FrametimeType` can have different values that will show
// different statistical aspects of the framerate.
//
// - `Deltatime`: Shows the time in milliseconds it took to render the previous
// frame
// - `Average Deltatime`: Shows the time that it took to render in milliseconds
// averaged over the last 100 or so frames
// - `Deltatime extremes`: Shows the minimum and maximum values of the render time
// in milliseconds over the last 100 or so frames
// - `Deltatime standard deviation`: Shows the standard deviation of the render time
// in milliseconds over the last 100 or so frames
// - `Deltatime coefficient of variation`: Shows the normalized root-mean-square
// deviation of the render time in
// milliseconds over the last 100 or so
// frames
// - `Frames per second`: Shows the inverse of the delta time it took the render the
// last frame.
// - `Average frames per second`: Shows average number of frames that have been
// presented over the last 100 or so frames
struct [[codegen::Dictionary(DashboardItemFramerate)]] Parameters {
enum class [[codegen::map(FrametimeType)]] Type {
DtTime [[codegen::key("Deltatime")]],
DtTimeAvg [[codegen::key("Average Deltatime")]],
DtTimeExtremes [[codegen::key("Deltatime extremes")]],
DtStandardDeviation [[codegen::key("Deltatime standard deviation")]],
@@ -151,7 +184,6 @@ namespace {
std::optional<Type> frametimeType;
};
#include "dashboarditemframerate_codegen.cpp"
} // namespace
namespace openspace {
@@ -165,12 +197,13 @@ documentation::Documentation DashboardItemFramerate::Documentation() {
DashboardItemFramerate::DashboardItemFramerate(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _frametimeType(FrametimeInfo, properties::OptionProperty::DisplayType::Dropdown)
, _frametimeType(FrametimeInfo)
, _clearCache(ClearCacheInfo)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_frametimeType.addOptions({
{ static_cast<int>(FrametimeType::DtTime), "Deltatime" },
{ static_cast<int>(FrametimeType::DtTimeAvg), "Average Deltatime" },
{ static_cast<int>(FrametimeType::DtTimeExtremes), "Deltatime extremes" },
{
@@ -182,8 +215,7 @@ DashboardItemFramerate::DashboardItemFramerate(const ghoul::Dictionary& dictiona
"Deltatime coefficient of variation"
},
{ static_cast<int>(FrametimeType::FPS), "Frames per second" },
{ static_cast<int>(FrametimeType::FPSAvg), "Average frames per second" },
{ static_cast<int>(FrametimeType::None), "None" }
{ static_cast<int>(FrametimeType::FPSAvg), "Average frames per second" }
});
if (p.frametimeType.has_value()) {
@@ -198,14 +230,13 @@ DashboardItemFramerate::DashboardItemFramerate(const ghoul::Dictionary& dictiona
_shouldClearCache = true;
});
addProperty(_clearCache);
_buffer.resize(128);
_localBuffer.resize(128);
}
void DashboardItemFramerate::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemFramerate::update() {
ZoneScoped;
if (_shouldClearCache) {
if (_shouldClearCache) [[unlikely]] {
_minDeltaTimeCache = 1.0;
_maxDeltaTimeCache = -1.0;
_shouldClearCache = false;
@@ -220,40 +251,16 @@ void DashboardItemFramerate::render(glm::vec2& penPosition) {
global::windowDelegate->maxDeltaTime() * 1000.0
);
FrametimeType frametimeType = FrametimeType(_frametimeType.value());
const FrametimeType frametimeType = FrametimeType(_frametimeType.value());
std::fill(_buffer.begin(), _buffer.end(), char(0));
std::fill(_localBuffer.begin(), _localBuffer.end(), char(0));
char* end = format(
_buffer,
_localBuffer,
frametimeType,
_minDeltaTimeCache,
_maxDeltaTimeCache
);
std::string_view output = std::string_view(_buffer.data(), end - _buffer.data());
int nLines = output.empty() ? 0 :
static_cast<int>((std::count(output.begin(), output.end(), '\n') + 1));
ghoul::fontrendering::FontRenderer::defaultRenderer().render(
*_font,
penPosition,
output
);
penPosition.y -= _font->height() * static_cast<float>(nLines);
}
glm::vec2 DashboardItemFramerate::size() const {
ZoneScoped
const FrametimeType t = FrametimeType(_frametimeType.value());
char* end = format(_buffer, t, _minDeltaTimeCache, _maxDeltaTimeCache);
std::string_view output = std::string_view(_buffer.data(), end - _buffer.data());
if (output.empty()) {
return { 0.f, 0.f };
}
return _font->boundingBox(output);
_buffer = std::string(_localBuffer.data(), end - _localBuffer.data());
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/triggerproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/triggerproperty.h>
namespace ghoul { class Dictionary; }
@@ -38,10 +38,10 @@ namespace documentation { struct Documentation; }
class DashboardItemFramerate : public DashboardTextItem {
public:
DashboardItemFramerate(const ghoul::Dictionary& dictionary);
explicit DashboardItemFramerate(const ghoul::Dictionary& dictionary);
void update() override;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
static documentation::Documentation Documentation();
private:
@@ -51,7 +51,7 @@ private:
double _minDeltaTimeCache = 1.0;
double _maxDeltaTimeCache = -1.0;
bool _shouldClearCache = true;
mutable std::vector<char> _buffer;
mutable std::vector<char> _localBuffer;
};
} // openspace

View File

@@ -0,0 +1,177 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/dashboard/dashboarditeminputstate.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <ghoul/font/font.h>
#include <ghoul/font/fontmanager.h>
#include <ghoul/font/fontrenderer.h>
#include <ghoul/misc/stringhelper.h>
namespace {
constexpr openspace::properties::Property::PropertyInfo ShowWhenEnabledInfo = {
"ShowWhenEnabled",
"Show when enabled",
"Show text when the input is enabled.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShowWhenDisabledInfo = {
"ShowWhenDisabled",
"Show when disabled",
"Show text when the input is disabled.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShowKeyboardInfo = {
"ShowKeyboard",
"Show Keyboard information",
"Display the state of the keyboard input.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShowMouseInfo = {
"ShowMouse",
"Show Mouse information",
"Display the state of the mouse input.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShowJoystickInfo = {
"ShowJoystick",
"Show Joystick information",
"Display the state of the joystick input.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` shows the current state of the different methods to provide
// user input: keyboard, mouse, and/or joystick.
//
// Each input method has the ability to be selectively disabled, meaning that all
// inputs from that input method are ignored by the system entirely.
struct [[codegen::Dictionary(DashboardItemInputState)]] Parameters {
// [[codegen::verbatim(ShowWhenEnabledInfo.description)]]
std::optional<bool> showWhenEnabled;
// [[codegen::verbatim(ShowWhenDisabledInfo.description)]]
std::optional<bool> showWhenDisabled;
// [[codegen::verbatim(ShowKeyboardInfo.description)]]
std::optional<bool> showKeyboard;
// [[codegen::verbatim(ShowMouseInfo.description)]]
std::optional<bool> showMouse;
// [[codegen::verbatim(ShowJoystickInfo.description)]]
std::optional<bool> showJoystick;
};
#include "dashboarditeminputstate_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation DashboardItemInputState::Documentation() {
return codegen::doc<Parameters>(
"base_dashboarditem_inputstate",
DashboardTextItem::Documentation()
);
}
DashboardItemInputState::DashboardItemInputState(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _showWhenEnabled(ShowWhenEnabledInfo, true)
, _showWhenDisabled(ShowWhenDisabledInfo, true)
, _showKeyboard(ShowKeyboardInfo, true)
, _showMouse(ShowMouseInfo, true)
, _showJoystick(ShowJoystickInfo, true)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_showWhenEnabled = p.showWhenEnabled.value_or(_showWhenEnabled);
addProperty(_showWhenEnabled);
_showWhenDisabled = p.showWhenDisabled.value_or(_showWhenDisabled);
addProperty(_showWhenDisabled);
_showKeyboard = p.showKeyboard.value_or(_showKeyboard);
addProperty(_showKeyboard);
_showMouse = p.showMouse.value_or(_showMouse);
addProperty(_showMouse);
_showJoystick = p.showJoystick.value_or(_showJoystick);
addProperty(_showJoystick);
}
void DashboardItemInputState::update() {
ZoneScoped;
std::vector<std::string> text;
if (_showKeyboard) {
if (global::navigationHandler->disabledKeybindings()) {
if (_showWhenDisabled) {
text.emplace_back("Keyboard shortcuts disabled");
}
}
else {
if (_showWhenEnabled) {
text.emplace_back("Keyboard shortcuts enabled");
}
}
}
if (_showMouse) {
if (global::navigationHandler->disabledMouse()) {
if (_showWhenDisabled) {
text.emplace_back("Mouse input disabled");
}
}
else {
if (_showWhenEnabled) {
text.emplace_back("Mouse input enabled");
}
}
}
if (_showJoystick) {
if (global::navigationHandler->disabledJoystick()) {
if (_showWhenDisabled) {
text.emplace_back("Joystick input disabled");
}
}
else {
if (_showWhenEnabled) {
text.emplace_back("Joystick input enabled");
}
}
}
_buffer = ghoul::join(std::move(text), "\n");
}
} // namespace openspace

View File

@@ -0,0 +1,57 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___DASHBOARDITEMINPUTSTATE___H__
#define __OPENSPACE_MODULE_BASE___DASHBOARDITEMINPUTSTATE___H__
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/scalar/boolproperty.h>
namespace openspace {
namespace properties { class Property; }
namespace documentation { struct Documentation; }
class DashboardItemInputState : public DashboardTextItem {
public:
explicit DashboardItemInputState(const ghoul::Dictionary& dictionary);
~DashboardItemInputState() override = default;
void update() override;
static documentation::Documentation Documentation();
private:
properties::BoolProperty _showWhenEnabled;
properties::BoolProperty _showWhenDisabled;
properties::BoolProperty _showKeyboard;
properties::BoolProperty _showMouse;
properties::BoolProperty _showJoystick;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___DASHBOARDITEMINPUTSTATE___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -51,28 +51,36 @@ namespace {
progress.append("|");
return progress;
}
// This `DashboardItem` shows information about the currently active mission. This
// includes information about the currently active mission phase, the next phase, and
// all subphases of the currently active phase.
struct [[codegen::Dictionary(DashboardItemMission)]] Parameters {};
#include "dashboarditemmission_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation DashboardItemMission::Documentation() {
documentation::Documentation doc = DashboardTextItem::Documentation();
doc.name = "DashboardItemMission";
doc.id = "base_dashboarditem_mission";
return doc;
return codegen::doc<Parameters>(
"base_dashboarditem_mission",
DashboardTextItem::Documentation()
);
}
DashboardItemMission::DashboardItemMission(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary, 15.f)
{}
void DashboardItemMission::update() {}
void DashboardItemMission::render(glm::vec2& penPosition) {
ZoneScoped
ZoneScoped;
if (!global::missionManager->hasCurrentMission()) {
return;
}
double currentTime = global::timeManager->time().j2000Seconds();
const double currentTime = global::timeManager->time().j2000Seconds();
const Mission& mission = global::missionManager->currentMission();
if (mission.phases().empty()) {
@@ -84,13 +92,7 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
static constexpr glm::vec4 nonCurrentMissionColor = glm::vec4(0.3f, 0.3f, 0.3f, 1.f);
// Add spacing
RenderFont(
*_font,
penPosition,
" ",
nonCurrentMissionColor,
ghoul::fontrendering::CrDirection::Down
);
penPosition.y -= _font->height();
MissionPhase::Trace phaseTrace = mission.phaseTrace(currentTime);
if (!phaseTrace.empty()) {
@@ -99,7 +101,7 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
penPosition.y -= _font->height();
RenderFont(*_font, penPosition, title, missionProgressColor);
double remaining = phase.timeRange().end - currentTime;
float t = static_cast<float>(
const float t = static_cast<float>(
1.0 - remaining / phase.timeRange().duration()
);
std::string progress = progressToStr(25, t);
@@ -107,7 +109,7 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
RenderFont(
*_font,
penPosition,
fmt::format("{:.0f} s {:s} {:.1f} %", remaining, progress, t * 100),
std::format("{:.0f} s {:s} {:.1f} %", remaining, progress, t * 100),
missionProgressColor
);
}
@@ -119,18 +121,21 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
RenderFont(
*_font,
penPosition,
fmt::format("{:.0f} s", remaining),
std::format("{:.0f} s", remaining),
nextMissionColor
);
}
bool showAllPhases = false;
// Add spacing
penPosition.y -= _font->height();
constexpr bool ShowAllPhases = false;
using PhaseWithDepth = std::pair<const MissionPhase*, int>;
std::stack<PhaseWithDepth> S;
constexpr int PixelIndentation = 20;
S.push({ &mission, 0 });
S.emplace(&mission, 0);
while (!S.empty()) {
const MissionPhase* phase = S.top().first;
const int depth = S.top().second;
@@ -145,16 +150,16 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
1.0 - remaining / phase->timeRange().duration()
);
const std::string progress = progressToStr(25, t);
penPosition.y -= _font->height();
RenderFont(
*_font,
penPosition,
fmt::format(
std::format(
"{:s} {:s} {:.1f} %",
phase->name(),progress,t * 100
),
currentMissionColor
);
penPosition.y -= _font->height();
}
else {
if (!phase->name().empty()) {
@@ -169,25 +174,18 @@ void DashboardItemMission::render(glm::vec2& penPosition) {
}
penPosition.x -= depth * PixelIndentation;
if (isCurrentPhase || showAllPhases) {
if (isCurrentPhase || ShowAllPhases) {
// phases are sorted increasingly by start time, and will be
// popped last-in-first-out from the stack, so add them in
// reversed order.
int indexLastPhase = static_cast<int>(
phase->phases().size()
) - 1;
const int indexLastPhase = static_cast<int>(phase->phases().size()) - 1;
for (int i = indexLastPhase; 0 <= i; --i) {
S.push({ &phase->phases()[i], depth + 1 });
S.emplace(&phase->phases()[i], depth + 1);
}
}
}
}
glm::vec2 DashboardItemMission::size() const {
ZoneScoped
// @TODO fix this up ---abock
return { 0.f, 0.f };
penPosition.y += _font->height();
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -33,13 +33,12 @@ namespace documentation { struct Documentation; }
class DashboardItemMission : public DashboardTextItem {
public:
DashboardItemMission(const ghoul::Dictionary& dictionary);
explicit DashboardItemMission(const ghoul::Dictionary& dictionary);
~DashboardItemMission() override = default;
void update() override;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
static documentation::Documentation Documentation();
};

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -32,17 +32,28 @@
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/font/font.h>
#include <ghoul/font/fontmanager.h>
#include <ghoul/font/fontrenderer.h>
#include <ghoul/misc/profiling.h>
namespace {
// This `DashboardItem` displays information about the status of the parallel
// connection, which is whether OpenSpace is directly connected to other OpenSpace
// instances and can either control those instances or be controlled by the master of
// the session. If OpenSpace is not connected, this `DashboardItem` will not display
// anything.
//
// The information presented contains how many clients are connected to the same
// session and whether this machine is currently the host of the session.
struct [[codegen::Dictionary(DashboardItemParallelConnection)]] Parameters {};
#include "dashboarditemparallelconnection_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation DashboardItemParallelConnection::Documentation() {
documentation::Documentation doc = DashboardTextItem::Documentation();
doc.name = "DashboardItemParallelConnection";
doc.id = "base_dashboarditem_parallelconnection";
return doc;
return codegen::doc<Parameters>(
"base_dashboarditem_parallelconnection",
DashboardTextItem::Documentation()
);
}
DashboardItemParallelConnection::DashboardItemParallelConnection(
@@ -50,109 +61,48 @@ DashboardItemParallelConnection::DashboardItemParallelConnection(
: DashboardTextItem(dictionary)
{}
void DashboardItemParallelConnection::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemParallelConnection::update() {
ZoneScoped;
const ParallelConnection::Status status = global::parallelPeer->status();
const size_t nConnections = global::parallelPeer->nConnections();
const std::string& hostName = global::parallelPeer->hostName();
int nLines = 1;
std::string connectionInfo;
int nClients = static_cast<int>(nConnections);
if (status == ParallelConnection::Status::Host) {
nClients--;
constexpr std::string_view Singular = "Hosting session with {} client";
constexpr std::string_view Plural = "Hosting session with {} clients";
connectionInfo =
_buffer =
(nClients == 1) ?
fmt::format(Singular, nClients) :
fmt::format(Plural, nClients);
std::format(Singular, nClients) :
std::format(Plural, nClients);
}
else if (status == ParallelConnection::Status::ClientWithHost) {
nClients--;
connectionInfo = "Session hosted by '" + hostName + "'";
_buffer = "Session hosted by '" + hostName + "'";
}
else if (status == ParallelConnection::Status::ClientWithoutHost) {
connectionInfo = "Host is disconnected";
_buffer = "Host is disconnected";
}
if (status == ParallelConnection::Status::ClientWithHost ||
status == ParallelConnection::Status::ClientWithoutHost)
{
connectionInfo += "\n";
_buffer += "\n";
if (nClients > 2) {
constexpr std::string_view Plural = "You and {} more clients are tuned in";
connectionInfo += fmt::format(Plural, nClients - 1);
_buffer += std::format(Plural, nClients - 1);
}
else if (nClients == 2) {
constexpr std::string_view Singular = "You and {} more client are tuned in";
connectionInfo += fmt::format(Singular, nClients - 1);
_buffer += std::format(Singular, nClients - 1);
}
else if (nClients == 1) {
connectionInfo += "You are the only client";
_buffer += "You are the only client";
}
nLines = 2;
}
if (!connectionInfo.empty()) {
RenderFont(*_font, penPosition, connectionInfo);
penPosition.y -= _font->height() * nLines;
}
}
glm::vec2 DashboardItemParallelConnection::size() const {
ZoneScoped
ParallelConnection::Status status = global::parallelPeer->status();
size_t nConnections = global::parallelPeer->nConnections();
const std::string& hostName = global::parallelPeer->hostName();
std::string connectionInfo;
int nClients = static_cast<int>(nConnections);
if (status == ParallelConnection::Status::Host) {
nClients--;
if (nClients == 1) {
connectionInfo = "Hosting session with 1 client";
}
else {
connectionInfo = fmt::format("Hosting session with {} clients", nClients);
}
}
else if (status == ParallelConnection::Status::ClientWithHost) {
nClients--;
connectionInfo = "Session hosted by '" + hostName + "'";
}
else if (status == ParallelConnection::Status::ClientWithoutHost) {
connectionInfo = "Host is disconnected";
}
if (status == ParallelConnection::Status::ClientWithHost ||
status == ParallelConnection::Status::ClientWithoutHost)
{
connectionInfo += "\n";
if (nClients > 2) {
constexpr std::string_view Plural = "You and {} more clients are tuned in";
connectionInfo += fmt::format(Plural, nClients);
}
else if (nClients == 2) {
constexpr std::string_view Singular = "You and {} more client are tuned in";
connectionInfo += fmt::format(Singular, nClients - 1);
}
else if (nClients == 1) {
connectionInfo += "You are the only client";
}
}
if (!connectionInfo.empty()) {
return _font->boundingBox(connectionInfo);
}
else {
return { 0.f, 0.f };
}
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -33,12 +33,10 @@ namespace documentation { struct Documentation; }
class DashboardItemParallelConnection : public DashboardTextItem {
public:
DashboardItemParallelConnection(const ghoul::Dictionary& dictionary);
explicit DashboardItemParallelConnection(const ghoul::Dictionary& dictionary);
~DashboardItemParallelConnection() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();
};

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,6 +27,24 @@
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/scalar/longproperty.h>
#include <openspace/properties/scalar/shortproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <openspace/properties/scalar/ulongproperty.h>
#include <openspace/properties/scalar/ushortproperty.h>
#include <openspace/properties/vector/dvec2property.h>
#include <openspace/properties/vector/dvec3property.h>
#include <openspace/properties/vector/dvec4property.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/ivec3property.h>
#include <openspace/properties/vector/ivec4property.h>
#include <openspace/properties/vector/uvec2property.h>
#include <openspace/properties/vector/uvec3property.h>
#include <openspace/properties/vector/uvec4property.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/properties/vector/vec4property.h>
#include <openspace/query/query.h>
#include <openspace/util/timemanager.h>
#include <ghoul/font/font.h>
@@ -38,17 +56,27 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo PropertyUriInfo = {
"URI",
"Property URI",
"The URI of the property that is displayed in this dashboarditem"
"The URI of the property that is displayed in this dashboard item.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DisplayStringInfo = {
"DisplayString",
"Display String",
"The String that is being displayed. It must either be empty (in which case only "
"the value itself will be displayed), or it must contain extact one instance of "
"{}, which will be replaced with the value of the property during rendering"
"the value itself will be displayed), or it must contain extact one or more "
"instances of {}, which will be replaced with the value(s) of the property "
"during rendering. For scalar types, there has to be exactly one instance of {}, "
"for vector types, there need to be as many {} as there are components in the "
"vector, for example two {} for vec2 types, three for vec3 types, etc. For more "
"information on how to structure the formatting string, see the documentation at "
"https://en.cppreference.com/w/cpp/utility/format/spec.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` will show the value of the provided property. Depending on the
// type of the property, the `DisplayString` will have to be adapted. See the
// documentation for the `DisplayString` for more information.
struct [[codegen::Dictionary(DashboardItemPropertyValue)]] Parameters {
// [[codegen::verbatim(PropertyUriInfo.description)]]
std::optional<std::string> uri [[codegen::key("URI")]];
@@ -84,29 +112,149 @@ DashboardItemPropertyValue::DashboardItemPropertyValue(
addProperty(_displayString);
}
void DashboardItemPropertyValue::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemPropertyValue::update() {
ZoneScoped;
if (_propertyIsDirty) {
if (_propertyIsDirty) [[unlikely]] {
_property = openspace::property(_propertyUri);
_propertyIsDirty = false;
}
if (_property) {
std::string value = _property->stringValue();
RenderFont(
*_font,
penPosition,
fmt::format(fmt::runtime(_displayString.value()), value)
if (!_property) {
return;
}
const std::string_view type = _property->className();
if (type == "DoubleProperty") {
double value = static_cast<properties::DoubleProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
else if (type == "FloatProperty") {
float value = static_cast<properties::FloatProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
else if (type == "IntProperty") {
int value = static_cast<properties::IntProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
else if (type == "LongProperty") {
long value = static_cast<properties::LongProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
else if (type == "ShortProperty") {
short value = static_cast<properties::ShortProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
else if (type == "UIntProperty") {
unsigned int v = static_cast<properties::UIntProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v));
}
else if (type == "ULongProperty") {
unsigned long v = static_cast<properties::ULongProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v));
}
else if (type == "UShortProperty") {
unsigned short v = static_cast<properties::UShortProperty*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v));
}
else if (type == "DVec2Property") {
glm::dvec2 v = static_cast<properties::DVec2Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v.x, v.y));
}
else if (type == "DVec3Property") {
glm::dvec3 v = static_cast<properties::DVec3Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z)
);
penPosition.y -= _font->height();
}
else if (type == "DVec4Property") {
glm::dvec4 v = static_cast<properties::DVec4Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z, v.w)
);
}
else if (type == "IVec2Property") {
glm::ivec2 v = static_cast<properties::IVec2Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v.x, v.y));
}
else if (type == "IVec3Property") {
glm::ivec3 v = static_cast<properties::IVec3Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z)
);
}
else if (type == "IVec4Property") {
glm::ivec4 v = static_cast<properties::IVec4Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z, v.w)
);
}
else if (type == "UVec2Property") {
glm::uvec2 v = static_cast<properties::UVec2Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v.x, v.y));
}
else if (type == "UVec3Property") {
glm::uvec3 v = static_cast<properties::UVec3Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z)
);
}
else if (type == "UVec4Property") {
glm::uvec4 v = static_cast<properties::UVec4Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z, v.w)
);
}
else if (type == "Vec2Property") {
glm::vec2 v = static_cast<properties::Vec2Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(v.x, v.y));
}
else if (type == "Vec3Property") {
glm::vec3 v = static_cast<properties::Vec3Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z)
);
}
else if (type == "Vec4Property") {
glm::vec4 v = static_cast<properties::Vec4Property*>(_property)->value();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_displayString.value(),
std::make_format_args(v.x, v.y, v.z, v.w)
);
}
else {
// Fallback if we don't have a special case above
std::string value = _property->stringValue();
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(_displayString.value(), std::make_format_args(value));
}
}
glm::vec2 DashboardItemPropertyValue::size() const {
ZoneScoped
return _font->boundingBox(_displayString.value());
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,7 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/stringproperty.h>
namespace openspace {
@@ -40,9 +40,7 @@ public:
DashboardItemPropertyValue(const ghoul::Dictionary& dictionary);
~DashboardItemPropertyValue() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -41,14 +41,16 @@ namespace {
"Time Simplification",
"If this value is enabled, the time is displayed in nuanced units, such as "
"minutes, hours, days, years, etc. If this value is disabled, it is always "
"displayed in seconds"
"displayed in seconds.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RequestedUnitInfo = {
"RequestedUnit",
"Requested Unit",
"If the simplification is disabled, this time unit is used as a destination to "
"convert the seconds into"
"convert the seconds into.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo TransitionFormatInfo = {
@@ -59,7 +61,10 @@ namespace {
"delta time. This format gets five parameters in this order: The target delta "
"time value, the target delta time unit, the string 'Paused' if the delta time "
"is paused or the empty string otherwise, the current delta time value, and the "
"current delta time unit"
"current delta time unit. More information about how to make use of the format "
"string, see the documentation at "
"https://en.cppreference.com/w/cpp/utility/format/spec.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RegularFormatInfo = {
@@ -68,7 +73,10 @@ namespace {
"The format string used to format the text if the target delta time is the same "
"as the current delta time. This format gets three parameters in this order: "
"The target delta value, the target delta unit, and the string 'Paused' if the "
"delta time is paused or the empty string otherwise"
"delta time is paused or the empty string otherwise. More information about how "
"to make use of the format string, see the documentation at "
"https://en.cppreference.com/w/cpp/utility/format/spec.",
openspace::properties::Property::Visibility::AdvancedUser
};
std::vector<std::string> unitList() {
@@ -84,6 +92,10 @@ namespace {
return res;
}
// This `DashboardItem` shows how fast the in-game time progresses. The display string
// for the `RegularFormat` is used when the current simulation increment is not
// changing, the `TransitionFormat` is used if the simulation increment is currently
// interpolating to a new value.
struct [[codegen::Dictionary(DashboardItemSimulationIncrement)]] Parameters {
// [[codegen::verbatim(SimplificationInfo.description)]]
std::optional<bool> simplification;
@@ -113,7 +125,7 @@ DashboardItemSimulationIncrement::DashboardItemSimulationIncrement(
const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _doSimplification(SimplificationInfo, true)
, _requestedUnit(RequestedUnitInfo, properties::OptionProperty::DisplayType::Dropdown)
, _requestedUnit(RequestedUnitInfo)
, _transitionFormat(
TransitionFormatInfo,
"Simulation increment: {:.1f} {:s} / second{:s} (current: {:.1f} {:s})"
@@ -132,13 +144,14 @@ DashboardItemSimulationIncrement::DashboardItemSimulationIncrement(
});
addProperty(_doSimplification);
for (TimeUnit u : TimeUnits) {
for (const TimeUnit u : TimeUnits) {
_requestedUnit.addOption(static_cast<int>(u), std::string(nameForTimeUnit(u)));
}
_requestedUnit = static_cast<int>(TimeUnit::Second);
if (p.requestedUnit.has_value()) {
TimeUnit unit = timeUnitFromString(p.requestedUnit->c_str());
const TimeUnit unit = timeUnitFromString(*p.requestedUnit);
_requestedUnit = static_cast<int>(unit);
_doSimplification = false;
}
_requestedUnit.setVisibility(properties::Property::Visibility::Hidden);
addProperty(_requestedUnit);
@@ -150,8 +163,8 @@ DashboardItemSimulationIncrement::DashboardItemSimulationIncrement(
addProperty(_regularFormat);
}
void DashboardItemSimulationIncrement::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemSimulationIncrement::update() {
ZoneScoped;
const double targetDt = global::timeManager->targetDeltaTime();
const double currentDt = global::timeManager->deltaTime();
@@ -167,17 +180,17 @@ void DashboardItemSimulationIncrement::render(glm::vec2& penPosition) {
const TimeUnit unit = static_cast<TimeUnit>(_requestedUnit.value());
const double convTarget = convertTime(targetDt, TimeUnit::Second, unit);
targetDeltaTime = {
targetDeltaTime = std::pair(
convTarget,
std::string(nameForTimeUnit(unit, convTarget != 1.0))
};
);
if (targetDt != currentDt) {
const double convCurrent = convertTime(currentDt, TimeUnit::Second, unit);
currentDeltaTime = {
currentDeltaTime = std::pair(
convCurrent,
std::string(nameForTimeUnit(unit, convCurrent != 1.0))
};
);
}
}
@@ -186,11 +199,10 @@ void DashboardItemSimulationIncrement::render(glm::vec2& penPosition) {
try {
if (targetDt != currentDt && !global::timeManager->isPaused()) {
// We are in the middle of a transition
RenderFont(
*_font,
penPosition,
fmt::format(
fmt::runtime(_transitionFormat.value()),
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_transitionFormat.value(),
std::make_format_args(
targetDeltaTime.first, targetDeltaTime.second,
pauseText,
currentDeltaTime.first, currentDeltaTime.second
@@ -198,45 +210,20 @@ void DashboardItemSimulationIncrement::render(glm::vec2& penPosition) {
);
}
else {
RenderFont(
*_font,
penPosition,
fmt::format(
fmt::runtime(_regularFormat.value()),
targetDeltaTime.first, targetDeltaTime.second, pauseText
// @CPP26(abock): This can be replaced with std::runtime_format
_buffer = std::vformat(
_regularFormat.value(),
std::make_format_args(
targetDeltaTime.first,
targetDeltaTime.second,
pauseText
)
);
}
}
catch (const fmt::format_error&) {
catch (const std::format_error&) {
LERRORC("DashboardItemDate", "Illegal format string");
}
penPosition.y -= _font->height();
}
glm::vec2 DashboardItemSimulationIncrement::size() const {
ZoneScoped
double t = global::timeManager->targetDeltaTime();
std::pair<double, std::string> deltaTime;
if (_doSimplification) {
deltaTime = simplifyTime(t);
}
else {
TimeUnit unit = static_cast<TimeUnit>(_requestedUnit.value());
double convertedT = convertTime(t, TimeUnit::Second, unit);
deltaTime = {
convertedT,
std::string(nameForTimeUnit(unit, convertedT != 1.0))
};
}
return _font->boundingBox(
fmt::format(
"Simulation increment: {:.1f} {:s} / second",
deltaTime.first, deltaTime.second
)
);
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
namespace openspace {
@@ -37,11 +37,10 @@ namespace documentation { struct Documentation; }
class DashboardItemSimulationIncrement : public DashboardTextItem {
public:
DashboardItemSimulationIncrement(const ghoul::Dictionary& dictionary);
explicit DashboardItemSimulationIncrement(const ghoul::Dictionary& dictionary);
~DashboardItemSimulationIncrement() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -33,9 +33,12 @@ namespace {
"Spacing",
"Spacing",
"This value determines the spacing (in pixels) that this item represents. The "
"default value is 15"
"default value is 15.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` adds a variable amount of spacing between two other
// `DashboardItem`s.
struct [[codegen::Dictionary(DashboardItemSpacing)]] Parameters {
// [[codegen::verbatim(SpacingInfo.description)]]
std::optional<float> spacing;
@@ -46,7 +49,10 @@ namespace {
namespace openspace {
documentation::Documentation DashboardItemSpacing::Documentation() {
return codegen::doc<Parameters>("base_dashboarditem_spacing");
return codegen::doc<Parameters>(
"base_dashboarditem_spacing",
DashboardItem::Documentation()
);
}
DashboardItemSpacing::DashboardItemSpacing(const ghoul::Dictionary& dictionary)
@@ -59,12 +65,10 @@ DashboardItemSpacing::DashboardItemSpacing(const ghoul::Dictionary& dictionary)
addProperty(_spacing);
}
void DashboardItemSpacing::update() {}
void DashboardItemSpacing::render(glm::vec2& penPosition) {
penPosition.y -= _spacing;
}
glm::vec2 DashboardItemSpacing::size() const {
return { 0.f, _spacing };
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -35,13 +35,12 @@ namespace documentation { struct Documentation; }
class DashboardItemSpacing : public DashboardItem {
public:
DashboardItemSpacing(const ghoul::Dictionary& dictionary);
explicit DashboardItemSpacing(const ghoul::Dictionary& dictionary);
~DashboardItemSpacing() override = default;
void update() override;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
static documentation::Documentation Documentation();
private:

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -28,8 +28,6 @@
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <ghoul/font/font.h>
#include <ghoul/font/fontmanager.h>
#include <ghoul/font/fontrenderer.h>
#include <ghoul/misc/profiling.h>
#include <optional>
@@ -37,7 +35,8 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo TextInfo = {
"Text",
"Text",
"The text to be displayed"
"The text to be displayed.",
openspace::properties::Property::Visibility::User
};
struct [[codegen::Dictionary(DashboardItemText)]] Parameters {
@@ -65,17 +64,8 @@ DashboardItemText::DashboardItemText(const ghoul::Dictionary& dictionary)
addProperty(_text);
}
void DashboardItemText::render(glm::vec2& penPosition) {
ZoneScoped
RenderFont(*_font, penPosition, _text.value());
penPosition.y -= _font->height();
}
glm::vec2 DashboardItemText::size() const {
ZoneScoped
return _font->boundingBox(_text.value());
void DashboardItemText::update() {
_buffer = _text.value();
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,7 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/stringproperty.h>
namespace openspace {
@@ -35,12 +35,10 @@ namespace documentation { struct Documentation; }
class DashboardItemText : public DashboardTextItem {
public:
DashboardItemText(const ghoul::Dictionary& dictionary);
explicit DashboardItemText(const ghoul::Dictionary& dictionary);
~DashboardItemText() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();

View File

@@ -0,0 +1,237 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/dashboard/dashboarditemtimevaryingtext.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/json.h>
#include <openspace/util/timemanager.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <fstream>
namespace {
constexpr openspace::properties::Property::PropertyInfo FormatStringInfo = {
"FormatString",
"Format String",
"The format text describing how this dashboard item renders its text. This text "
"must contain exactly one {} which is a placeholder that will be replaced "
"with the values read from the file provided in `DataFile`",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo DataFileInfo = {
"DataFile",
"Data File Path",
"The file path to the JSON data.",
openspace::properties::Property::Visibility::User
};
// This `DashboardItem` displays text based on the content of a provided data file.
// The value that is displayed depends on the current in-game simulation time.
//
// The JSON must contain a 'data' array with timestamp-value pairs. Example format:
// {\"data\": [[\"2024-05-10T00:00:00Z\", 2.33], [\"2024-05-10T03:00:00Z\", 3.0]]}
struct [[codegen::Dictionary(DashboardItemTimeVaryingText)]] Parameters {
// [[codegen::verbatim(FormatStringInfo.description)]]
std::optional<std::string> formatString;
// [[codegen::verbatim(DataFileInfo.description)]]
std::filesystem::path dataFile;
};
#include "dashboarditemtimevaryingtext_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation DashboardItemTimeVaryingText::Documentation() {
return codegen::doc<Parameters>(
"base_dashboarditem_timevaryingtext",
DashboardTextItem::Documentation()
);
}
DashboardItemTimeVaryingText::DashboardItemTimeVaryingText(
const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _formatString(FormatStringInfo, "{}")
, _dataFile(DataFileInfo, "")
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_formatString = p.formatString.value_or(_formatString);
addProperty(_formatString);
_dataFile.onChange([this]() { loadDataFromJson(_dataFile); });
_dataFile = p.dataFile.string();
addProperty(_dataFile);
}
void DashboardItemTimeVaryingText::update() {
if (_startTimes.empty()) {
_buffer.clear();
return;
}
double current = global::timeManager->time().j2000Seconds();
double first = _startTimes.front();
double last = _sequenceEndTime;
if (current >= first && current < last) {
int newIdx = updateActiveTriggerTimeIndex(current);
if (newIdx != _activeTriggerTimeIndex) {
_activeTriggerTimeIndex = newIdx;
double timeKey = _startTimes[_activeTriggerTimeIndex];
const nlohmann::json value = _data[timeKey];
try {
switch (value.type()) {
case nlohmann::json::value_t::null:
case nlohmann::json::value_t::discarded:
break;
case nlohmann::json::value_t::boolean: {
const bool v = value.get<bool>();
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::string: {
const std::string v = value.get<std::string>();
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::number_integer: {
const int v = value.get<int>();
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::number_unsigned: {
const unsigned v = value.get<unsigned>();
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::number_float: {
const double v = value.get<double>();
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::object:
case nlohmann::json::value_t::array: {
const std::string v = nlohmann::to_string(value);
_buffer = std::vformat(
_formatString.value(),
std::make_format_args(v)
);
break;
}
case nlohmann::json::value_t::binary: {
LWARNINGC(
"DashboardItemTimeVaryingText",
"Binary data is not supported"
);
break;
}
}
}
catch (const std::format_error&) {
LERRORC("DashboardItemTimeVaryingText", "Illegal format string");
}
}
}
else {
_activeTriggerTimeIndex = -1;
_buffer.clear();
}
}
void DashboardItemTimeVaryingText::loadDataFromJson(const std::string& filePath) {
std::ifstream file = std::ifstream(filePath);
if (!file.is_open()) {
throw ghoul::RuntimeError(std::format(
"Time varying text, '{}' is not a valid JSON file",
filePath
));
return;
}
nlohmann::json jsonData;
file >> jsonData;
if (jsonData.find("data") == jsonData.end()) {
throw ghoul::RuntimeError(std::format(
"Error loading JSON file. No 'data' was found in '{}'", filePath
));
}
_data.clear();
_startTimes.clear();
for (const nlohmann::json& item : jsonData["data"]) {
const std::string& timeString = item[0].get<std::string>();
double j2000Time = Time::convertTime(timeString);
_data[j2000Time] = item[1];
_startTimes.push_back(j2000Time);
}
std::sort(_startTimes.begin(), _startTimes.end());
computeSequenceEndTime();
}
void DashboardItemTimeVaryingText::computeSequenceEndTime() {
if (_startTimes.size() > 1) {
double first = _startTimes.front();
double last = _startTimes.back();
double avgDuration = (last - first) / static_cast<double>(_startTimes.size() - 1);
// Extend end time so the last value remains visible for one more interval
_sequenceEndTime = last + avgDuration;
}
}
int DashboardItemTimeVaryingText::updateActiveTriggerTimeIndex(double currentTime) const {
auto it = std::upper_bound(_startTimes.begin(), _startTimes.end(), currentTime);
if (it != _startTimes.end()) {
if (it != _startTimes.begin()) {
return static_cast<int>(std::distance(_startTimes.begin(), it)) - 1;
}
return 0;
}
return static_cast<int>(_startTimes.size()) - 1;
}
}// namespace openspace

View File

@@ -0,0 +1,63 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___DASHBOARDITEMTIMEVARYINGTEXT___H__
#define __OPENSPACE_MODULE_BASE___DASHBOARDITEMTIMEVARYINGTEXT___H__
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/json.h>
namespace openspace {
namespace documentation { struct Documentation; }
class DashboardItemTimeVaryingText : public DashboardTextItem {
public:
explicit DashboardItemTimeVaryingText(const ghoul::Dictionary& dictionary);
~DashboardItemTimeVaryingText() override = default;
void update() override;
static documentation::Documentation Documentation();
private:
void loadDataFromJson(const std::string& filePath);
void computeSequenceEndTime();
int updateActiveTriggerTimeIndex(double currentTime) const;
properties::StringProperty _formatString;
properties::StringProperty _dataFile;
std::unordered_map<double, nlohmann::json> _data;
std::vector<double> _startTimes;
int _activeTriggerTimeIndex = -1;
double _sequenceEndTime = 0.0;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___DASHBOARDITEMTIMEVARYINGTEXT___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -34,8 +34,6 @@
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/font/font.h>
#include <ghoul/font/fontmanager.h>
#include <ghoul/font/fontrenderer.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/profiling.h>
@@ -45,38 +43,33 @@ namespace {
"Simplification",
"If this value is enabled, the velocity is displayed in nuanced units, such as "
"m/s, AU/s, light years / s etc. If this value is disabled, the unit can be "
"explicitly requested"
"explicitly requested.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RequestedUnitInfo = {
"RequestedUnit",
"Requested Unit",
"If the simplification is disabled, this distance unit is used for the velocity "
"display"
"display.",
openspace::properties::Property::Visibility::User
};
std::vector<std::string> unitList() {
std::vector<std::string> res(openspace::DistanceUnits.size());
std::transform(
openspace::DistanceUnits.begin(),
openspace::DistanceUnits.end(),
res.begin(),
[](openspace::DistanceUnit unit) {
return std::string(nameForDistanceUnit(unit));
}
);
return res;
}
// This `DashboardItem` shows the velocity of the camera, that is how fast the camera
// has moved since the last frame in the amount of time it took to render that frame.
// The `Simplification` and `RequestedUnit` can be used to control which unit is used
// to display the speed. By default, `Simplification` is enabled, which means that the
// most natural unit, that is, the one that gives the least number of printed digits,
// is used.
struct [[codegen::Dictionary(DashboardItemVelocity)]] Parameters {
// [[codegen::verbatim(SimplificationInfo.description)]]
std::optional<bool> simplification;
// [[codegen::verbatim(RequestedUnitInfo.description)]]
std::optional<std::string> requestedUnit [[codegen::inlist(unitList())]];
std::optional<std::string> requestedUnit
[[codegen::inlist(openspace::distanceUnitList())]];
};
#include "dashboarditemvelocity_codegen.cpp"
} // namespace
namespace openspace {
@@ -91,10 +84,9 @@ documentation::Documentation DashboardItemVelocity::Documentation() {
DashboardItemVelocity::DashboardItemVelocity(const ghoul::Dictionary& dictionary)
: DashboardTextItem(dictionary)
, _doSimplification(SimplificationInfo, true)
, _requestedUnit(RequestedUnitInfo, properties::OptionProperty::DisplayType::Dropdown)
, _requestedUnit(RequestedUnitInfo)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_doSimplification = p.simplification.value_or(_doSimplification);
_doSimplification.onChange([this]() {
_requestedUnit.setVisibility(
_doSimplification ?
@@ -102,9 +94,10 @@ DashboardItemVelocity::DashboardItemVelocity(const ghoul::Dictionary& dictionary
properties::Property::Visibility::User
);
});
_doSimplification = p.simplification.value_or(_doSimplification);
addProperty(_doSimplification);
for (DistanceUnit u : DistanceUnits) {
for (const DistanceUnit u : DistanceUnits) {
_requestedUnit.addOption(
static_cast<int>(u),
std::string(nameForDistanceUnit(u))
@@ -112,22 +105,22 @@ DashboardItemVelocity::DashboardItemVelocity(const ghoul::Dictionary& dictionary
}
_requestedUnit = static_cast<int>(DistanceUnit::Meter);
if (p.requestedUnit.has_value()) {
DistanceUnit unit = distanceUnitFromString(p.requestedUnit->c_str());
const DistanceUnit unit = distanceUnitFromString(*p.requestedUnit);
_requestedUnit = static_cast<int>(unit);
_doSimplification = false;
}
_requestedUnit.setVisibility(properties::Property::Visibility::Hidden);
addProperty(_requestedUnit);
}
void DashboardItemVelocity::render(glm::vec2& penPosition) {
ZoneScoped
void DashboardItemVelocity::update() {
ZoneScoped;
const glm::dvec3 currentPos = global::renderEngine->scene()->camera()->positionVec3();
const glm::dvec3 dt = currentPos - _prevPosition;
_prevPosition = currentPos;
const double speedPerFrame = glm::length(dt);
const double secondsPerFrame = global::windowDelegate->averageDeltaTime();
const double speedPerSecond = speedPerFrame / secondsPerFrame;
std::pair<double, std::string_view> dist;
@@ -137,38 +130,10 @@ void DashboardItemVelocity::render(glm::vec2& penPosition) {
else {
const DistanceUnit unit = static_cast<DistanceUnit>(_requestedUnit.value());
const double convertedD = convertMeters(speedPerSecond, unit);
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
dist = std::pair(convertedD, nameForDistanceUnit(unit, convertedD != 1.0));
}
RenderFont(
*_font,
penPosition,
fmt::format(
"Camera velocity: {:.4f} {}/s", dist.first, dist.second
)
);
penPosition.y -= _font->height();
_prevPosition = currentPos;
}
glm::vec2 DashboardItemVelocity::size() const {
ZoneScoped
const double d = glm::length(1e20);
std::pair<double, std::string_view> dist;
if (_doSimplification) {
dist = simplifyDistance(d);
}
else {
DistanceUnit unit = static_cast<DistanceUnit>(_requestedUnit.value());
double convertedD = convertMeters(d, unit);
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
}
return _font->boundingBox(
fmt::format("Camera velocity: {} {}/s", dist.first, dist.second)
);
_buffer = std::format("Camera velocity: {:.4f} {}/s", dist.first, dist.second);
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,7 @@
#include <openspace/rendering/dashboardtextitem.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <utility>
@@ -39,12 +39,10 @@ namespace documentation { struct Documentation; }
class DashboardItemVelocity : public DashboardTextItem {
public:
DashboardItemVelocity(const ghoul::Dictionary& dictionary);
explicit DashboardItemVelocity(const ghoul::Dictionary& dictionary);
~DashboardItemVelocity() override = default;
void render(glm::vec2& penPosition) override;
glm::vec2 size() const override;
void update() override;
static documentation::Documentation Documentation();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -33,9 +33,13 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo IntensityInfo = {
"Intensity",
"Intensity",
"The intensity of this light source"
"The intensity of this light source.",
openspace::properties::Property::Visibility::NoviceUser
};
// This `LightSource` type represents a light source placed at the position of the
// camera. An object with this light source will always be illuminated from the
// current view direction.
struct [[codegen::Dictionary(CameraLightSource)]] Parameters {
// [[codegen::verbatim(IntensityInfo.description)]]
std::optional<float> intensity;
@@ -49,12 +53,6 @@ documentation::Documentation CameraLightSource::Documentation() {
return codegen::doc<Parameters>("base_camera_light_source");
}
CameraLightSource::CameraLightSource()
: _intensity(IntensityInfo, 1.f, 0.f, 1.f)
{
addProperty(_intensity);
}
CameraLightSource::CameraLightSource(const ghoul::Dictionary& dictionary)
: LightSource(dictionary)
, _intensity(IntensityInfo, 1.f, 0.f, 1.f)

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -28,7 +28,6 @@
#include <openspace/scene/lightsource.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/stringproperty.h>
namespace openspace {
@@ -36,14 +35,13 @@ namespace documentation { struct Documentation; }
class CameraLightSource : public LightSource {
public:
CameraLightSource();
CameraLightSource(const ghoul::Dictionary& dictionary);
static documentation::Documentation Documentation();
explicit CameraLightSource(const ghoul::Dictionary& dictionary);
glm::vec3 directionViewSpace(const RenderData& renderData) const override;
float intensity() const override;
static documentation::Documentation Documentation();
private:
properties::FloatProperty _intensity;
};

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -37,21 +37,31 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo IntensityInfo = {
"Intensity",
"Intensity",
"The intensity of this light source"
"The intensity of this light source.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo NodeInfo = {
"Node",
"Node",
"The identifier of the scene graph node to follow"
"The identifier of the scene graph node to follow.",
openspace::properties::Property::Visibility::AdvancedUser
};
// This `LightSource` type represents a light source placed at the position of a
// scene graph node. That is, the direction of the light will follow the position
// of an existing object in the scene. It will also update dynamically as the
// object moves.
//
// Note that the brightness of the light from the light source does not depend on
// the distance between the two scene graph nodes. Only the `Intensity` value has
// an impact on the brightness.
struct [[codegen::Dictionary(SceneGraphLightSource)]] Parameters {
// [[codegen::verbatim(IntensityInfo.description)]]
std::optional<float> intensity;
// [[codegen::verbatim(NodeInfo.description)]]
std::string node;
std::string node [[codegen::identifier()]];
};
#include "scenegraphlightsource_codegen.cpp"
} // namespace
@@ -62,28 +72,26 @@ documentation::Documentation SceneGraphLightSource::Documentation() {
return codegen::doc<Parameters>("base_scene_graph_light_source");
}
SceneGraphLightSource::SceneGraphLightSource()
: _intensity(IntensityInfo, 1.f, 0.f, 1.f)
SceneGraphLightSource::SceneGraphLightSource(const ghoul::Dictionary& dictionary)
: LightSource(dictionary)
, _intensity(IntensityInfo, 1.f, 0.f, 1.f)
, _sceneGraphNodeReference(NodeInfo, "")
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_intensity = p.intensity.value_or(_intensity);
addProperty(_intensity);
_sceneGraphNodeReference.onChange([this]() {
_sceneGraphNode =
global::renderEngine->scene()->sceneGraphNode(_sceneGraphNodeReference);
});
addProperty(_sceneGraphNodeReference);
}
SceneGraphLightSource::SceneGraphLightSource(const ghoul::Dictionary& dictionary)
: SceneGraphLightSource()
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_intensity = p.intensity.value_or(_intensity);
_sceneGraphNodeReference = p.node;
}
bool SceneGraphLightSource::initialize() {
ZoneScoped
ZoneScoped;
_sceneGraphNode =
global::renderEngine->scene()->sceneGraphNode(_sceneGraphNodeReference);

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/scene/lightsource.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/stringproperty.h>
namespace openspace {
@@ -36,8 +36,7 @@ namespace documentation { struct Documentation; }
class SceneGraphLightSource : public LightSource {
public:
SceneGraphLightSource();
SceneGraphLightSource(const ghoul::Dictionary& dictionary);
explicit SceneGraphLightSource(const ghoul::Dictionary& dictionary);
static documentation::Documentation Documentation();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,34 +39,28 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"This value determines the color of the grid lines that are rendered"
"The color used for the grid lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"This value specifies the line width of the spherical grid"
"The width of the grid lines. The larger number, the thicker the lines.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
"Size",
"Grid Size",
"This value species the size of each dimensions of the box"
};
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
"DrawLabels",
"Draw Labels",
"Determines whether labels should be drawn or hidden"
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
{
"Labels",
"Labels",
"The labels for the grid"
"The size of each dimension of the box, in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
// A RenderableBoxGrid creates a 3D box that is rendered using grid lines.
//
// Per default the box is given a uniform size of 1x1x1 meters. It can then be scaled
// to the desired size. Alternatively, the size in each dimension can be specified.
struct [[codegen::Dictionary(RenderableBoxGrid)]] Parameters {
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
@@ -76,13 +70,6 @@ namespace {
// [[codegen::verbatim(SizeInfo.description)]]
std::optional<glm::vec3> size;
// [[codegen::verbatim(DrawLabelInfo.description)]]
std::optional<bool> drawLabels;
// [[codegen::verbatim(LabelsInfo.description)]]
std::optional<ghoul::Dictionary> labels
[[codegen::reference("space_labelscomponent")]];
};
#include "renderableboxgrid_codegen.cpp"
} // namespace
@@ -97,13 +84,11 @@ RenderableBoxGrid::RenderableBoxGrid(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
, _size(SizeInfo, glm::vec3(1.f), glm::vec3(1.f), glm::vec3(100.f))
, _drawLabels(DrawLabelInfo, false)
, _size(SizeInfo, glm::vec3(1.f), glm::vec3(0.0001f), glm::vec3(100.f))
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
@@ -113,28 +98,12 @@ RenderableBoxGrid::RenderableBoxGrid(const ghoul::Dictionary& dictionary)
addProperty(_lineWidth);
_size = p.size.value_or(_size);
_size.onChange([&]() { _gridIsDirty = true; });
_size.onChange([this]() { _gridIsDirty = true; });
addProperty(_size);
if (p.labels.has_value()) {
_drawLabels = p.drawLabels.value_or(_drawLabels);
addProperty(_drawLabels);
_labels = std::make_unique<LabelsComponent>(*p.labels);
_hasLabels = true;
addPropertySubOwner(_labels.get());
}
}
bool RenderableBoxGrid::isReady() const {
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
}
void RenderableBoxGrid::initialize() {
if (_hasLabels) {
_labels->initialize();
_labels->loadLabels();
}
return _gridProgram != nullptr;
}
void RenderableBoxGrid::initializeGL() {
@@ -174,30 +143,23 @@ void RenderableBoxGrid::deinitializeGL() {
_gridProgram = nullptr;
}
void RenderableBoxGrid::render(const RenderData& data, RendererTasks&){
void RenderableBoxGrid::render(const RenderData& data, RendererTasks&) {
_gridProgram->activate();
glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
auto [modelTransform, modelViewTransform, modelViewProjectionTransform] =
calcAllTransforms(data);
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
_gridProgram->setUniform("MVPTransform", modelViewProjectionTransform);
_gridProgram->setUniform("opacity", opacity());
_gridProgram->setUniform("gridColor", _color);
// Change GL state:
#ifndef __APPLE__
glLineWidth(_lineWidth);
#else
#else // ^^^^ __APPLE__ // !__APPLE__ vvvv
glLineWidth(1.f);
#endif
#endif // __APPLE__
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnablei(GL_BLEND, 0);
glEnable(GL_LINE_SMOOTH);
@@ -213,35 +175,10 @@ void RenderableBoxGrid::render(const RenderData& data, RendererTasks&){
global::renderEngine->openglStateCache().resetBlendState();
global::renderEngine->openglStateCache().resetLineState();
global::renderEngine->openglStateCache().resetDepthState();
// Draw labels
if (_drawLabels && _hasLabels) {
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
glm::vec3 right = glm::cross(viewDirection, lookup);
const glm::vec3 up = glm::cross(right, viewDirection);
const glm::dmat4 worldToModelTransform = glm::inverse(modelTransform);
glm::vec3 orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
);
if (orthoRight == glm::vec3(0.0)) {
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
right = glm::cross(viewDirection, otherVector);
orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
);
}
const glm::vec3 orthoUp = glm::normalize(
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
);
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
}
}
void RenderableBoxGrid::update(const UpdateData&) {
if (_gridIsDirty) {
if (_gridIsDirty) [[unlikely]] {
const glm::vec3 llf = -_size.value() / 2.f;
const glm::vec3 urb = _size.value() / 2.f;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,7 +27,6 @@
#include <openspace/rendering/renderable.h>
#include <modules/space/labelscomponent.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <ghoul/opengl/ghoul_gl.h>
@@ -42,9 +41,8 @@ namespace openspace {
class RenderableBoxGrid : public Renderable {
public:
RenderableBoxGrid(const ghoul::Dictionary& dictionary);
explicit RenderableBoxGrid(const ghoul::Dictionary& dictionary);
void initialize() override;
void initializeGL() override;
void deinitializeGL() override;
@@ -73,11 +71,6 @@ protected:
GLenum _mode = GL_LINE_STRIP;
std::vector<Vertex> _varray;
// Labels
bool _hasLabels = false;
properties::BoolProperty _drawLabels;
std::unique_ptr<LabelsComponent> _labels;
};
}// namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,20 +39,22 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"This value determines the color of the grid lines that are rendered"
"The color of the grid lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo HighlightColorInfo = {
"HighlightColor",
"Highlight Color",
"This value determines the color of the highlighted lines in the grid"
"The color of the highlighted lines in the grid.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
"Segments",
"Number of Segments",
"This value specifies the number of segments that are used to render the "
"grid in each direction"
"The number of segments to split the grid into, in each direction (x and y).",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo HighlightRateInfo = {
@@ -60,40 +62,44 @@ namespace {
"Highlight Rate",
"The rate that the columns and rows are highlighted, counted with respect to the "
"center of the grid. If the number of segments in the grid is odd, the "
"highlighting might be offset from the center."
"highlighting might be offset from the center.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"This value specifies the line width of the grid"
"The width of the grid lines. The larger number, the thicker the lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo HighlightLineWidthInfo = {
"HighlightLineWidth",
"Highlight Line Width",
"This value specifies the line width of the highlighted lines in the grid"
"The width of the highlighted grid lines. The larger number, the thicker the "
"lines.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
"Size",
"Grid Size",
"This value species the size of each dimensions of the grid"
"The size of the grid (in the x and y direction), given in meters.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
"DrawLabels",
"Draw Labels",
"Determines whether labels should be drawn or hidden"
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
{
const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo = {
"Labels",
"Labels",
"The labels for the grid"
"The labels for the grid."
};
// This `Renderable` can be used to create a planar grid, to for example illustrate
// distances in 3D space.
//
// The grid is created by specifying a size and how many segments to split each
// dimension into. A secondary color can be used to highlight grid lines with a
// given interval.
struct [[codegen::Dictionary(RenderableGrid)]] Parameters {
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
@@ -116,12 +122,8 @@ namespace {
// [[codegen::verbatim(SizeInfo.description)]]
std::optional<glm::vec2> size;
// [[codegen::verbatim(DrawLabelInfo.description)]]
std::optional<bool> drawLabels;
// [[codegen::verbatim(LabelsInfo.description)]]
std::optional<ghoul::Dictionary> labels
[[codegen::reference("space_labelscomponent")]];
std::optional<ghoul::Dictionary> labels [[codegen::reference("labelscomponent")]];
};
#include "renderablegrid_codegen.cpp"
} // namespace
@@ -141,12 +143,10 @@ RenderableGrid::RenderableGrid(const ghoul::Dictionary& dictionary)
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
, _highlightLineWidth(HighlightLineWidthInfo, 0.5f, 1.f, 20.f)
, _size(SizeInfo, glm::vec2(1.f), glm::vec2(1.f), glm::vec2(1e11f))
, _drawLabels(DrawLabelInfo, false)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
@@ -158,11 +158,11 @@ RenderableGrid::RenderableGrid(const ghoul::Dictionary& dictionary)
addProperty(_highlightColor);
_segments = p.segments.value_or(_segments);
_segments.onChange([&]() { _gridIsDirty = true; });
_segments.onChange([this]() { _gridIsDirty = true; });
addProperty(_segments);
_highlightRate = p.highlightRate.value_or(_highlightRate);
_highlightRate.onChange([&]() { _gridIsDirty = true; });
_highlightRate.onChange([this]() { _gridIsDirty = true; });
addProperty(_highlightRate);
_lineWidth = p.lineWidth.value_or(_lineWidth);
@@ -174,27 +174,25 @@ RenderableGrid::RenderableGrid(const ghoul::Dictionary& dictionary)
_size.setExponent(10.f);
_size = p.size.value_or(_size);
_size.onChange([&]() { _gridIsDirty = true; });
_size.onChange([this]() { _gridIsDirty = true; });
addProperty(_size);
if (p.labels.has_value()) {
_drawLabels = p.drawLabels.value_or(_drawLabels);
addProperty(_drawLabels);
_labels = std::make_unique<LabelsComponent>(*p.labels);
_hasLabels = true;
addPropertySubOwner(_labels.get());
// Fading of the labels should also depend on the fading of the renderable
_labels->setParentFadeable(this);
}
}
bool RenderableGrid::isReady() const {
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
return _gridProgram && (_hasLabels ? _labels->isReady() : true);
}
void RenderableGrid::initialize() {
if (_hasLabels) {
_labels->initialize();
_labels->loadLabels();
}
}
@@ -244,15 +242,12 @@ void RenderableGrid::deinitializeGL() {
_gridProgram = nullptr;
}
void RenderableGrid::render(const RenderData& data, RendererTasks&){
void RenderableGrid::render(const RenderData& data, RendererTasks&) {
_gridProgram->activate();
const glm::dmat4 modelMatrix =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
const glm::dmat4 modelTransform = calcModelTransform(data);
const glm::dmat4 modelViewTransform = calcModelViewTransform(data, modelTransform);
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelMatrix;
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
@@ -262,13 +257,13 @@ void RenderableGrid::render(const RenderData& data, RendererTasks&){
glm::vec3 right = glm::cross(viewDirection, lookup);
const glm::vec3 up = glm::cross(right, viewDirection);
const glm::dmat4 worldToModelTransform = glm::inverse(modelMatrix);
const glm::mat4 worldToModelTransform = glm::inverse(modelTransform);
glm::vec3 orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
);
if (orthoRight == glm::vec3(0.0)) {
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
const glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
right = glm::cross(viewDirection, otherVector);
orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
@@ -283,9 +278,9 @@ void RenderableGrid::render(const RenderData& data, RendererTasks&){
// Change GL state:
#ifndef __APPLE__
glLineWidth(_lineWidth);
#else
#else // ^^^^ __APPLE__ // !__APPLE__ vvvv
glLineWidth(1.f);
#endif
#endif // __APPLE__
glEnablei(GL_BLEND, 0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
@@ -298,9 +293,9 @@ void RenderableGrid::render(const RenderData& data, RendererTasks&){
// Render major grid
#ifndef __APPLE__
glLineWidth(_highlightLineWidth);
#else
#else // ^^^^ __APPLE__ // !__APPLE__ vvvv
glLineWidth(1.f);
#endif
#endif // __APPLE__
_gridProgram->setUniform("gridColor", _highlightColor);
glBindVertexArray(_highlightVaoID);
@@ -314,16 +309,16 @@ void RenderableGrid::render(const RenderData& data, RendererTasks&){
global::renderEngine->openglStateCache().resetDepthState();
// Draw labels
if (_drawLabels && _hasLabels) {
if (_hasLabels && _labels->enabled()) {
const glm::vec3 orthoUp = glm::normalize(
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
);
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
}
}
void RenderableGrid::update(const UpdateData&) {
if (!_gridIsDirty) {
if (!_gridIsDirty) [[likely]] {
return;
}
@@ -342,8 +337,8 @@ void RenderableGrid::update(const UpdateData&) {
// If the number of segments are uneven the center won't be completly centered
const glm::uvec2 center = glm::uvec2(nSegments.x / 2.f, nSegments.y / 2.f);
for (unsigned int i = 0; i < nSegments.x; ++i) {
for (unsigned int j = 0; j < nSegments.y; ++j) {
for (unsigned int i = 0; i < nSegments.x; i++) {
for (unsigned int j = 0; j < nSegments.y; j++) {
const double y0 = -halfSize.y + j * step.y;
const double y1 = y0 + step.y;
@@ -353,8 +348,8 @@ void RenderableGrid::update(const UpdateData&) {
// Line in y direction
bool shouldHighlight = false;
if (_highlightRate.value().x != 0) {
int dist = abs(static_cast<int>(i) - static_cast<int>(center.x));
int rest = dist % _highlightRate.value().x;
const int dist = abs(static_cast<int>(i) - static_cast<int>(center.x));
const int rest = dist % _highlightRate.value().x;
shouldHighlight = rest == 0;
}
@@ -370,8 +365,8 @@ void RenderableGrid::update(const UpdateData&) {
// Line in x direction
shouldHighlight = false;
if (_highlightRate.value().y != 0) {
int dist = abs(static_cast<int>(j) - static_cast<int>(center.y));
int rest = dist % _highlightRate.value().y;
const int dist = abs(static_cast<int>(j) - static_cast<int>(center.y));
const int rest = dist % _highlightRate.value().y;
shouldHighlight = abs(rest) == 0;
}
@@ -387,15 +382,17 @@ void RenderableGrid::update(const UpdateData&) {
}
// last x row
for (unsigned int i = 0; i < nSegments.x; ++i) {
for (unsigned int i = 0; i < nSegments.x; i++) {
const double x0 = -halfSize.x + i * step.x;
const double x1 = x0 + step.x;
bool shouldHighlight = false;
if (_highlightRate.value().y != 0) {
int dist = abs(static_cast<int>(nSegments.y) - static_cast<int>(center.y));
int rest = dist % _highlightRate.value().y;
shouldHighlight = abs(rest) == 0;
const int dist = std::abs(
static_cast<int>(nSegments.y) - static_cast<int>(center.y)
);
const int rest = dist % _highlightRate.value().y;
shouldHighlight = std::abs(rest) == 0;
}
if (shouldHighlight) {
@@ -409,15 +406,17 @@ void RenderableGrid::update(const UpdateData&) {
}
// last y col
for (unsigned int j = 0; j < nSegments.y; ++j) {
for (unsigned int j = 0; j < nSegments.y; j++) {
const double y0 = -halfSize.y + j * step.y;
const double y1 = y0 + step.y;
bool shouldHighlight = false;
if (_highlightRate.value().x != 0) {
int dist = abs(static_cast<int>(nSegments.x) - static_cast<int>(center.x));
int rest = dist % _highlightRate.value().x;
shouldHighlight = abs(rest) == 0;
const int dist = std::abs(
static_cast<int>(nSegments.x) - static_cast<int>(center.x)
);
const int rest = dist % _highlightRate.value().x;
shouldHighlight = std::abs(rest) == 0;
}
if (shouldHighlight) {
_highlightArray.push_back({ halfSize.x, y0, 0.0 });

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,12 +27,12 @@
#include <openspace/rendering/renderable.h>
#include <modules/space/labelscomponent.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/rendering/labelscomponent.h>
#include <ghoul/opengl/ghoul_gl.h>
namespace ghoul::opengl { class ProgramObject; }
@@ -42,7 +42,7 @@ namespace openspace {
class RenderableGrid : public Renderable {
public:
RenderableGrid(const ghoul::Dictionary& dictionary);
explicit RenderableGrid(const ghoul::Dictionary& dictionary);
void initialize() override;
void initializeGL() override;
@@ -85,7 +85,6 @@ protected:
// Labels
bool _hasLabels = false;
properties::BoolProperty _drawLabels;
std::unique_ptr<LabelsComponent> _labels;
};

View File

@@ -3,7 +3,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -40,27 +40,30 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"This value determines the color of the grid lines that are rendered"
"The color used for the grid lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo GridSegmentsInfo = {
"GridSegments",
"Number of Grid Segments",
"Specifies the number of segments for the grid, in the radial and angular "
"direction respectively"
"direction respectively.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo CircleSegmentsInfo = {
"CircleSegments",
"Number of Circle Segments",
"This value specifies the number of segments that is used to render each circle "
"in the grid"
"The number of segments that is used to render each circle in the grid.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"This value specifies the line width of the spherical grid"
"The width of the grid lines. The larger number, the thicker the lines.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RadiiInfo = {
@@ -68,22 +71,22 @@ namespace {
"Inner and Outer Radius",
"The radii values that determine the size of the circular grid. The first value "
"is the radius of the inmost ring and the second is the radius of the outmost "
"ring"
"ring.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
"DrawLabels",
"Draw Labels",
"Determines whether labels should be drawn or hidden"
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
{
const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo = {
"Labels",
"Labels",
"The labels for the grid"
"The labels for the grid."
};
// This `Renderable` creates a planar circular grid with a given size. Optionally, it
// may have a hole in the center.
//
// The size is determined by two radii values: The first (inner) radius defines the
// hole in the center. The second (outer) radius defines the full grid size. To create
// a solid circle that connects at the center, set the inner radius to zero (default).
struct [[codegen::Dictionary(RenderableRadialGrid)]] Parameters {
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
@@ -100,12 +103,9 @@ namespace {
// [[codegen::verbatim(RadiiInfo.description)]]
std::optional<glm::vec2> radii;
// [[codegen::verbatim(DrawLabelInfo.description)]]
std::optional<bool> drawLabels;
// [[codegen::verbatim(LabelsInfo.description)]]
std::optional<ghoul::Dictionary> labels
[[codegen::reference("space_labelscomponent")]];
[[codegen::reference("labelscomponent")]];
};
#include "renderableradialgrid_codegen.cpp"
} // namespace
@@ -119,27 +119,25 @@ documentation::Documentation RenderableRadialGrid::Documentation() {
RenderableRadialGrid::RenderableRadialGrid(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
, _gridSegments(GridSegmentsInfo, glm::ivec2(1), glm::ivec2(1), glm::ivec2(200))
, _gridSegments(GridSegmentsInfo, glm::ivec2(10), glm::ivec2(1), glm::ivec2(200))
, _circleSegments(CircleSegmentsInfo, 36, 4, 200)
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
, _radii(RadiiInfo, glm::vec2(0.f, 1.f), glm::vec2(0.f), glm::vec2(20.f))
, _drawLabels(DrawLabelInfo, false)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_color);
_gridSegments = p.gridSegments.value_or(_gridSegments);
_gridSegments.onChange([&]() { _gridIsDirty = true; });
_gridSegments.onChange([this]() { _gridIsDirty = true; });
addProperty(_gridSegments);
_circleSegments = p.circleSegments.value_or(_circleSegments);
_circleSegments.onChange([&]() {
_circleSegments.onChange([this]() {
if (_circleSegments.value() % 2 == 1) {
_circleSegments = _circleSegments - 1;
}
@@ -152,28 +150,26 @@ RenderableRadialGrid::RenderableRadialGrid(const ghoul::Dictionary& dictionary)
_radii = p.radii.value_or(_radii);
_radii.setViewOption(properties::Property::ViewOptions::MinMaxRange);
_radii.onChange([&]() { _gridIsDirty = true; });
_radii.onChange([this]() { _gridIsDirty = true; });
addProperty(_radii);
if (p.labels.has_value()) {
_drawLabels = p.drawLabels.value_or(_drawLabels);
addProperty(_drawLabels);
_labels = std::make_unique<LabelsComponent>(*p.labels);
_hasLabels = true;
addPropertySubOwner(_labels.get());
// Fading of the labels should also depend on the fading of the renderable
_labels->setParentFadeable(this);
}
}
bool RenderableRadialGrid::isReady() const {
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
return _gridProgram && (_hasLabels ? _labels->isReady() : true);
}
void RenderableRadialGrid::initialize() {
if (_hasLabels) {
_labels->initialize();
_labels->loadLabels();
}
}
@@ -203,19 +199,11 @@ void RenderableRadialGrid::deinitializeGL() {
void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
_gridProgram->activate();
const glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
const glm::dmat4 modelViewTransform =
data.camera.combinedViewMatrix() * modelTransform;
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
auto [modelTransform, modelViewTransform, modelViewProjectionTransform] =
calcAllTransforms(data);
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
_gridProgram->setUniform("MVPTransform", modelViewProjectionTransform);
_gridProgram->setUniform("opacity", opacity());
_gridProgram->setUniform("gridColor", _color);
@@ -230,7 +218,7 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
glEnable(GL_LINE_SMOOTH);
glDepthMask(false);
for (GeometryData& c : _circles) {
for (const GeometryData& c : _circles) {
c.render();
}
@@ -244,7 +232,7 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
global::renderEngine->openglStateCache().resetDepthState();
// Draw labels
if (_drawLabels && _hasLabels) {
if (_hasLabels && _labels->enabled()) {
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
glm::vec3 right = glm::cross(viewDirection, lookup);
@@ -256,7 +244,7 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
);
if (orthoRight == glm::vec3(0.0)) {
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
const glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
right = glm::cross(viewDirection, otherVector);
orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
@@ -265,12 +253,12 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
const glm::vec3 orthoUp = glm::normalize(
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
);
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
_labels->render(data, modelViewProjectionTransform, orthoRight, orthoUp);
}
}
void RenderableRadialGrid::update(const UpdateData&) {
if (!_gridIsDirty) {
if (!_gridIsDirty) [[likely]] {
return;
}
@@ -292,8 +280,8 @@ void RenderableRadialGrid::update(const UpdateData&) {
std::vector<rendering::helper::Vertex> vertices =
rendering::helper::createRing(nSegments, radius);
_circles.push_back(GeometryData(GL_LINE_STRIP));
_circles.back().varray = rendering::helper::convert(vertices);
_circles.emplace_back(GL_LINE_STRIP);
_circles.back().varray = rendering::helper::convert(std::move(vertices));
_circles.back().update();
};
@@ -302,7 +290,7 @@ void RenderableRadialGrid::update(const UpdateData&) {
addRing(_circleSegments, innerRadius);
}
for (int i = 0; i < nRadialSegments; ++i) {
for (int i = 0; i < nRadialSegments; i++) {
const float ri = static_cast<float>(i + 1) * deltaRadius + innerRadius;
addRing(_circleSegments, ri);
}
@@ -321,7 +309,7 @@ void RenderableRadialGrid::update(const UpdateData&) {
std::vector<rendering::helper::Vertex> innerVertices =
rendering::helper::createRing(nLines, innerRadius);
for (int i = 0; i < nLines; ++i) {
for (int i = 0; i < nLines; i++) {
const rendering::helper::VertexXYZ vOut =
rendering::helper::convertToXYZ(outerVertices[i]);
@@ -352,7 +340,10 @@ RenderableRadialGrid::GeometryData::GeometryData(GLenum renderMode)
}
RenderableRadialGrid::GeometryData::GeometryData(GeometryData&& other) noexcept {
if (this == &other) return;
if (this == &other) {
return;
}
vao = other.vao;
vbo = other.vbo;
varray = std::move(other.varray);
@@ -385,7 +376,7 @@ RenderableRadialGrid::GeometryData::~GeometryData() {
vbo = 0;
}
void RenderableRadialGrid::GeometryData::update() {
void RenderableRadialGrid::GeometryData::update() const {
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(
@@ -405,7 +396,7 @@ void RenderableRadialGrid::GeometryData::update() {
);
}
void RenderableRadialGrid::GeometryData::render() {
void RenderableRadialGrid::GeometryData::render() const {
glBindVertexArray(vao);
glDrawArrays(mode, 0, static_cast<GLsizei>(varray.size()));
glBindVertexArray(0);

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,13 +27,13 @@
#include <openspace/rendering/renderable.h>
#include <modules/space/labelscomponent.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/labelscomponent.h>
#include <ghoul/opengl/ghoul_gl.h>
namespace ghoul::opengl { class ProgramObject; }
@@ -44,7 +44,7 @@ namespace openspace {
class RenderableRadialGrid : public Renderable {
public:
RenderableRadialGrid(const ghoul::Dictionary& dictionary);
explicit RenderableRadialGrid(const ghoul::Dictionary& dictionary);
~RenderableRadialGrid() override = default;
void initialize() override;
@@ -60,14 +60,14 @@ public:
protected:
struct GeometryData {
GeometryData(GLenum renderMode);
explicit GeometryData(GLenum renderMode);
GeometryData(GeometryData&& other) noexcept;
GeometryData& operator=(const GeometryData& other) = delete;
GeometryData& operator=(GeometryData&& other) noexcept;
~GeometryData();
void update();
void render();
void update() const;
void render() const;
std::vector<rendering::helper::VertexXYZ> varray;
GLuint vao = 0;
@@ -90,7 +90,6 @@ protected:
// Labels
bool _hasLabels = false;
properties::BoolProperty _drawLabels;
std::unique_ptr<LabelsComponent> _labels;
};

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,51 +39,76 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"This value determines the color of the grid lines that are rendered"
"The color of the grid lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
"Segments",
"Number of Segments",
"This value specifies the number of segments that are used to render the "
"surrounding sphere"
constexpr openspace::properties::Property::PropertyInfo LongSegmentsInfo = {
"LongSegments",
"Number of Longitudinal Segments",
"The number of longitudinal segments the sphere is split into. Determines the "
"resolution of the rendered sphere in a left/right direction when looking "
"straight at the equator. Should be an even value (if an odd value is provided, "
"the value will be set to the new value minus one). If the `Segments` value is "
"provided as well, it will have precedence over this value",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LatSegmentsInfo = {
"LatSegments",
"Number of Latitudinal Segments",
"The number of latitudinal segments the sphere is split into. Determines the "
"resolution of the rendered sphere in a up/down direction when looking "
"straight at the equator. Should be an even value (if an odd value is provided, "
"the value will be set to the new value minus one). If the `Segments` value is "
"provided as well, it will have precedence over this value",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"This value specifies the line width of the spherical grid"
"The width of the grid lines. The larger number, the thicker the lines.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
"DrawLabels",
"Draw Labels",
"Determines whether labels should be drawn or hidden"
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
{
const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo = {
"Labels",
"Labels",
"The labels for the grid"
"The labels for the grid."
};
// This `Renderable` creates a grid in the shape of a sphere. Note that the sphere
// will always be given a radius of one meter. To change its size, use a `Scale`
// transform, such as the [StaticScale](#base_transform_scale_static).
//
// The grid may be split up into equal segments in both directions using the `Segments`
// parameter, or different number of segments in the latitudal and longtudal direction
// using the `LatSegments` and `LongSegments` parameters.
struct [[codegen::Dictionary(RenderableSphericalGrid)]] Parameters {
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// [[codegen::verbatim(SegmentsInfo.description)]]
// [[codegen::verbatim(LongSegmentsInfo.description)]]
std::optional<int> longSegments;
// [[codegen::verbatim(LatSegmentsInfo.description)]]
std::optional<int> latSegments;
// The number of segments the sphere is split into. Determines the resolution
// of the rendered sphere. Should be an even value (if an odd value is provided,
// the value will be set to the new value minus one). Setting this value is equal
// to setting `LongSegments` and `LatSegments` to the same value. If this value is
// specified, it will overwrite the values provided in `LongSegments` and
//`LatSegments`.
std::optional<int> segments;
// [[codegen::verbatim(LineWidthInfo.description)]]
std::optional<float> lineWidth;
// [[codegen::verbatim(DrawLabelInfo.description)]]
std::optional<bool> drawLabels;
// [[codegen::verbatim(LabelsInfo.description)]]
std::optional<ghoul::Dictionary> labels
[[codegen::reference("space_labelscomponent")]];
[[codegen::reference("labelscomponent")]];
};
#include "renderablesphericalgrid_codegen.cpp"
} // namespace
@@ -98,27 +123,31 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio
: Renderable(dictionary)
, _gridProgram(nullptr)
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
, _segments(SegmentsInfo, 36, 4, 200)
, _longSegments(LongSegmentsInfo, 36, 4, 200)
, _latSegments(LatSegmentsInfo, 36, 4, 200)
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
, _drawLabels(DrawLabelInfo, false)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_color);
_segments = p.segments.value_or(_segments);
_segments.onChange([&]() {
if (_segments.value() % 2 == 1) {
_segments = _segments - 1;
auto gridDirty = [this]() {
if (_longSegments.value() % 2 == 1) {
_longSegments = _longSegments - 1;
}
_gridIsDirty = true;
});
addProperty(_segments);
};
_longSegments = p.segments.value_or(p.longSegments.value_or(_longSegments));
_longSegments.onChange(gridDirty);
addProperty(_longSegments);
_latSegments = p.segments.value_or(p.latSegments.value_or(_latSegments));
_latSegments.onChange(gridDirty);
addProperty(_latSegments);
_lineWidth = p.lineWidth.value_or(_lineWidth);
addProperty(_lineWidth);
@@ -127,23 +156,21 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio
setBoundingSphere(1.0);
if (p.labels.has_value()) {
_drawLabels = p.drawLabels.value_or(_drawLabels);
addProperty(_drawLabels);
_labels = std::make_unique<LabelsComponent>(*p.labels);
_hasLabels = true;
addPropertySubOwner(_labels.get());
// Fading of the labels should also depend on the fading of the renderable
_labels->setParentFadeable(this);
}
}
bool RenderableSphericalGrid::isReady() const {
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
return _gridProgram && (_hasLabels ? _labels->isReady() : true);
}
void RenderableSphericalGrid::initialize() {
if (_hasLabels) {
_labels->initialize();
_labels->loadLabels();
}
}
@@ -189,39 +216,31 @@ void RenderableSphericalGrid::deinitializeGL() {
_gridProgram = nullptr;
}
void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){
void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&) {
_gridProgram->activate();
const glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
const glm::dmat4 modelViewTransform =
data.camera.combinedViewMatrix() * modelTransform;
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
auto [modelTransform, modelViewTransform, modelViewProjectionTransform] =
calcAllTransforms(data);
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
_gridProgram->setUniform("MVPTransform", modelViewProjectionTransform);
_gridProgram->setUniform("opacity", opacity());
_gridProgram->setUniform("gridColor", _color);
// Change GL state:
#ifndef __APPLE__
glLineWidth(_lineWidth);
#else
#else // ^^^^ !__APPLE__ // __APPLE__ vvvv
glLineWidth(1.f);
#endif
#endif // __APPLE__
glEnablei(GL_BLEND, 0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glDepthMask(false);
glBindVertexArray(_vaoID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iBufferID);
glDrawElements(_mode, _isize, GL_UNSIGNED_INT, nullptr);
glDrawElements(GL_LINES, 6 * _longSegments * _latSegments, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
_gridProgram->deactivate();
@@ -232,7 +251,7 @@ void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){
global::renderEngine->openglStateCache().resetDepthState();
// Draw labels
if (_drawLabels && _hasLabels) {
if (_hasLabels && _labels->enabled()) {
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
glm::vec3 right = glm::cross(viewDirection, lookup);
@@ -240,105 +259,99 @@ void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){
const glm::dmat4 worldToModelTransform = glm::inverse(modelTransform);
glm::vec3 orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
glm::vec3(worldToModelTransform * glm::vec4(right, 0.f))
);
if (orthoRight == glm::vec3(0.0)) {
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
if (orthoRight == glm::vec3(0.f)) {
const glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
right = glm::cross(viewDirection, otherVector);
orthoRight = glm::normalize(
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
glm::vec3(worldToModelTransform * glm::vec4(right, 0.f))
);
}
const glm::vec3 orthoUp = glm::normalize(
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
);
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
_labels->render(data, modelViewProjectionTransform, orthoRight, orthoUp);
}
// Reset
global::renderEngine->openglStateCache().resetBlendState();
global::renderEngine->openglStateCache().resetLineState();
global::renderEngine->openglStateCache().resetDepthState();
}
void RenderableSphericalGrid::update(const UpdateData&) {
if (_gridIsDirty) {
_isize = 6 * _segments * _segments;
_vsize = (_segments + 1) * (_segments + 1);
_varray.resize(_vsize);
Vertex v = { 0.f, 0.f, 0.f };
std::fill(_varray.begin(), _varray.end(), v);
_iarray.resize(_isize);
std::fill(_iarray.begin(), _iarray.end(), 0);
int nr = 0;
const float fsegments = static_cast<float>(_segments);
for (int nSegment = 0; nSegment <= _segments; ++nSegment) {
// define an extra vertex around the y-axis due to texture mapping
for (int j = 0; j <= _segments; j++) {
const float fi = static_cast<float>(nSegment);
const float fj = static_cast<float>(j);
// inclination angle (north to south)
const float theta = fi * glm::pi<float>() / fsegments * 2.f; // 0 -> PI
// azimuth angle (east to west)
const float phi = fj * glm::pi<float>() * 2.0f / fsegments; // 0 -> 2*PI
const float x = sin(phi) * sin(theta); //
const float y = cos(theta); // up
const float z = cos(phi) * sin(theta); //
glm::vec3 normal = glm::vec3(x, y, z);
if (!(x == 0.f && y == 0.f && z == 0.f)) {
normal = glm::normalize(normal);
}
glm::vec4 tmp(x, y, z, 1.f);
glm::mat4 rot = glm::rotate(
glm::mat4(1.f),
glm::half_pi<float>(),
glm::vec3(1.f, 0.f, 0.f)
);
tmp = glm::vec4(glm::dmat4(rot) * glm::dvec4(tmp));
for (int i = 0; i < 3; i++) {
_varray[nr].location[i] = tmp[i];
}
++nr;
}
}
nr = 0;
// define indices for all triangles
for (int i = 1; i <= _segments; ++i) {
for (int j = 0; j < _segments; ++j) {
const int t = _segments + 1;
_iarray[nr] = t * (i - 1) + j + 0; ++nr;
_iarray[nr] = t * (i + 0) + j + 0; ++nr;
_iarray[nr] = t * (i + 0) + j + 1; ++nr;
_iarray[nr] = t * (i - 1) + j + 1; ++nr;
_iarray[nr] = t * (i - 1) + j + 0; ++nr;
}
}
glBindVertexArray(_vaoID);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferID);
glBufferData(
GL_ARRAY_BUFFER,
_vsize * sizeof(Vertex),
_varray.data(),
GL_STATIC_DRAW
);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iBufferID);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
_isize * sizeof(int),
_iarray.data(),
GL_STATIC_DRAW
);
_gridIsDirty = false;
if (!_gridIsDirty) [[likely]] {
return;
}
unsigned int vertSize = (_longSegments + 1) * (_latSegments + 1);
std::vector<Vertex> vert = std::vector<Vertex>(vertSize, { 0.f, 0.f, 0.f });
unsigned int idxSize = 6 * _longSegments * _latSegments;
std::vector<int> idx = std::vector<int>(idxSize, 0);
int nr = 0;
for (int lat = 0; lat <= _latSegments; ++lat) {
// define an extra vertex around the y-axis due to texture mapping
for (int lng = 0; lng <= _longSegments; lng++) {
// inclination angle (north to south)
const float theta = lat * glm::pi<float>() / _latSegments * 2.f; // 0 -> PI
// azimuth angle (east to west)
const float phi = lng * 2.f * glm::pi<float>() / _longSegments; // 0 -> 2*PI
const float x = std::sin(phi) * std::sin(theta); //
const float y = std::cos(theta); // up
const float z = std::cos(phi) * std::sin(theta); //
glm::vec3 normal = glm::vec3(x, y, z);
if (x != 0.f || y != 0.f || z != 0.f) {
normal = glm::normalize(normal);
}
glm::vec4 tmp = glm::vec4(x, y, z, 1.f);
const glm::mat4 rot = glm::rotate(
glm::mat4(1.f),
glm::half_pi<float>(),
glm::vec3(1.f, 0.f, 0.f)
);
tmp = glm::vec4(glm::dmat4(rot) * glm::dvec4(tmp));
for (int i = 0; i < 3; i++) {
vert[nr].location[i] = tmp[i];
}
++nr;
}
}
nr = 0;
// define indices for all triangles
for (int i = 1; i <= _latSegments; i++) {
for (int j = 0; j < _longSegments; j++) {
const int t = _longSegments + 1;
idx[nr] = t * (i - 1) + j + 0; ++nr;
idx[nr] = t * (i + 0) + j + 0; ++nr;
idx[nr] = t * (i + 0) + j + 1; ++nr;
idx[nr] = t * (i - 1) + j + 1; ++nr;
idx[nr] = t * (i - 1) + j + 0; ++nr;
}
}
glBindVertexArray(_vaoID);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferID);
glBufferData(GL_ARRAY_BUFFER, vertSize * sizeof(Vertex), vert.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iBufferID);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
idxSize * sizeof(int),
idx.data(), GL_STATIC_DRAW
);
_gridIsDirty = false;
}
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,21 +27,21 @@
#include <openspace/rendering/renderable.h>
#include <modules/space/labelscomponent.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/rendering/labelscomponent.h>
#include <ghoul/opengl/ghoul_gl.h>
namespace ghoul::opengl { class ProgramObject; }
namespace openspace::documentation { struct Documentation; }
namespace openspace {
namespace documentation { struct Documentation; }
class RenderableSphericalGrid : public Renderable {
public:
RenderableSphericalGrid(const ghoul::Dictionary& dictionary);
explicit RenderableSphericalGrid(const ghoul::Dictionary& dictionary);
~RenderableSphericalGrid() override = default;
void initialize() override;
@@ -63,24 +63,18 @@ protected:
ghoul::opengl::ProgramObject* _gridProgram;
properties::Vec3Property _color;
properties::IntProperty _segments;
properties::IntProperty _longSegments;
properties::IntProperty _latSegments;
properties::FloatProperty _lineWidth;
bool _gridIsDirty = true;
GLuint _vaoID = 0;
GLuint _vBufferID = 0;
GLuint _iBufferID = 0;
GLenum _mode = GL_LINES;
unsigned int _isize = 0;
unsigned int _vsize = 0;
std::vector<Vertex> _varray;
std::vector<int> _iarray;
bool _gridIsDirty = true;
// Labels
bool _hasLabels = false;
properties::BoolProperty _drawLabels;
std::unique_ptr<LabelsComponent> _labels;
};

View File

@@ -0,0 +1,588 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/rendering/pointcloud/renderableinterpolatedpoints.h>
#include <modules/base/basemodule.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scripting/scriptengine.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/glm.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/interpolator.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/opengl/texture.h>
#include <optional>
namespace {
constexpr std::string_view _loggerCat = "RenderableInterpolatedPoints";
void triggerInterpolation(std::string_view identifier, float v, float d) {
using namespace openspace;
std::string script = std::format(
"openspace.setPropertyValueSingle(\"{}\", {}, {})",
identifier, v, d
);
// No syncing, as this was triggered from a property change (which happened
// based on an already synced script)
global::scriptEngine->queueScript({
.code = script,
.synchronized = scripting::ScriptEngine::Script::ShouldBeSynchronized::No,
.sendToRemote = scripting::ScriptEngine::Script::ShouldSendToRemote::No
});
}
constexpr openspace::properties::Property::PropertyInfo InterpolationValueInfo = {
"Value",
"Value",
"The value used for interpolation. The max value is set from the number of "
"steps in the dataset, so a step of one corresponds to one step in the dataset "
"and values in-between will be determined using interpolation.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo StepsInfo = {
"NumberOfSteps",
"Number of Steps",
"The number of steps available in the dataset, including the initial positions.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo JumpToNextInfo = {
"JumpToNext",
"Jump to Next",
"Immediately set the interpolation value to correspond to the next set of point "
"positions compared to the current.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo JumpToPrevInfo = {
"JumpToPrevious",
"Jump to Previous",
"Immediately set the interpolation value to correspond to the previous set of "
"point positions compared to the current.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo InterpolateToNextInfo = {
"InterpolateToNext",
"Interpolate to Next",
"Trigger an interpolation to the next set of point positions. The duration of "
"the interpolation is set based on the Interpolaton Speed property.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo InterpolateToPrevInfo = {
"InterpolateToPrevious",
"Interpolate to Previous",
"Trigger an interpolation to the previous set of point positions. The duration "
"of the interpolation is set based on the Interpolaton Speed property.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo InterpolateToEndInfo = {
"InterpolateToEnd",
"Interpolate to End",
"Trigger an interpolation all the way to the final set of positions. The "
"duration of the interpolation is set based on the Interpolaton Speed property.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo InterpolateToStartInfo = {
"InterpolateToStart",
"Interpolate to Start",
"Trigger an inverted interpolation to the initial set of positions. The duration "
"of the interpolation is set based on the Interpolaton Speed property.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo InterpolationSpeedInfo = {
"Speed",
"Interpolation Speed",
"Affects how long the interpolation takes when triggered using one of the "
"trigger properties. A value of 1 means that a step takes 1 second.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo UseSplineInfo = {
"UseSplineInterpolation",
"Use Spline Interpolation",
"If true, the points will be interpolated using a Catmull-Rom spline instead of "
"linearly. This leads to a smoother transition at the breakpoints, i.e. between "
"each step.",
openspace::properties::Property::Visibility::AdvancedUser
};
// RenderableInterpolatedPoints is a version of the RenderablePointCloud class, where
// the dataset may contain multiple time steps that can be interpolated between. It
// supports interpolation of both of positions and data values used for color mapping
// or size.
//
// The dataset should be structured in a way so that the first N rows correspond to
// the first set of positions for the objects, the next N rows to the second set of
// positions, and so on. The number of objects in the dataset must be specified in the
// asset.
//
// MultiTexture:
// Note that if using multiple textures for the points based on values in the dataset,
// the used texture will be decided based on the first N set of points.
struct [[codegen::Dictionary(RenderableInterpolatedPoints)]] Parameters {
// The number of objects to read from the dataset. Every N:th datapoint will
// be interpreted as the same point, but at a different step in the interpolation.
int numberOfObjects [[codegen::greaterequal(1)]];
struct Interpolation {
// [[codegen::verbatim(InterpolationValueInfo.description)]]
std::optional<float> value;
// [[codegen::verbatim(InterpolationSpeedInfo.description)]]
std::optional<float> speed;
// [[codegen::verbatim(UseSplineInfo.description)]]
std::optional<bool> useSplineInterpolation;
};
// Initial settings for the interpolation.
std::optional<Interpolation> interpolation;
};
#include "renderableinterpolatedpoints_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation RenderableInterpolatedPoints::Documentation() {
return codegen::doc<Parameters>(
"base_renderableinterpolatedpoints",
RenderablePointCloud::Documentation()
);
}
RenderableInterpolatedPoints::Interpolation::Interpolation()
: properties::PropertyOwner({ "Interpolation", "Interpolation", "" })
, value(InterpolationValueInfo, 0.f, 0.f, 1.f)
, nSteps(StepsInfo, 1)
, goToNextStep(JumpToNextInfo)
, goToPrevStep(JumpToPrevInfo)
, interpolateToNextStep(InterpolateToNextInfo)
, interpolateToPrevStep(InterpolateToPrevInfo)
, interpolateToEnd(InterpolateToEndInfo)
, interpolateToStart(InterpolateToStartInfo)
, speed(InterpolationSpeedInfo, 1.f, 0.01f, 100.f)
, useSpline(UseSplineInfo, false)
{
addProperty(value);
interpolateToEnd.onChange([this]() {
const float remaining = value.maxValue() - value;
const float duration = remaining / speed;
triggerInterpolation(
value.uri(),
value.maxValue(),
duration
);
});
addProperty(interpolateToEnd);
interpolateToStart.onChange([this]() {
const float duration = value / speed;
triggerInterpolation(value.uri(), 0.f, duration);
});
addProperty(interpolateToStart);
interpolateToNextStep.onChange([this]() {
const float prevValue = glm::floor(value);
const float newValue = glm::min(prevValue + 1.f, value.maxValue());
const float duration = 1.f / speed;
triggerInterpolation(value.uri(), newValue, duration);
});
addProperty(interpolateToNextStep);
interpolateToPrevStep.onChange([this]() {
const float prevValue = glm::ceil(value);
const float newValue = glm::max(prevValue - 1.f, value.minValue());
const float duration = 1.f / speed;
triggerInterpolation(value.uri(), newValue, duration);
});
addProperty(interpolateToPrevStep);
addProperty(speed);
goToNextStep.onChange([this]() {
float prevValue = glm::floor(value);
value = glm::min(prevValue + 1.f, value.maxValue());
});
addProperty(goToNextStep);
goToPrevStep.onChange([this]() {
float prevValue = glm::ceil(value);
value = glm::max(prevValue - 1.f, value.minValue());
});
addProperty(goToPrevStep);
nSteps.setReadOnly(true);
addProperty(nSteps);
addProperty(useSpline);
}
RenderableInterpolatedPoints::RenderableInterpolatedPoints(
const ghoul::Dictionary& dictionary)
: RenderablePointCloud(dictionary)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addPropertySubOwner(_interpolation);
if (p.interpolation.has_value()) {
_interpolation.value = p.interpolation->value.value_or(_interpolation.value);
_interpolation.speed = p.interpolation->speed.value_or(_interpolation.speed);
_interpolation.useSpline = p.interpolation->useSplineInterpolation.value_or(
_interpolation.useSpline
);
}
_interpolation.value.onChange([this]() {
bool passedAKnot =
glm::ceil(_interpolation.value) != glm::ceil(_prevInterpolationValue);
if (passedAKnot) {
_dataIsDirty = true;
}
_prevInterpolationValue = _interpolation.value;
});
_interpolation.useSpline.onChange([this]() {
_dataIsDirty = true;
_shouldReinitializeBufferdata = true;
});
_nObjectsInDataset = static_cast<unsigned int>(p.numberOfObjects);
if (_skipFirstDataPoint) {
LWARNING(
"Found setting to skip first data point in asset. This is not supported for "
"interpolated point clouds. Ignoring"
);
_skipFirstDataPoint = false;
}
}
void RenderableInterpolatedPoints::initialize() {
RenderablePointCloud::initialize();
// At this point, the dataset has been loaded and we know how many data points it
// contains => we can compute the number of interpolation steps
if (_nDataPoints % _nObjectsInDataset != 0) {
LERROR(std::format(
"Mismatch between provided number of data entries and the specified number "
"of points. Expected the number of entries in the data file '{}' to be "
"evenly divisible by the number of objects", _dataFile
));
}
if (_nObjectsInDataset > 0) {
_interpolation.nSteps = _nDataPoints / _nObjectsInDataset;
}
_interpolation.value.setMaxValue(static_cast<float>(_interpolation.nSteps - 1));
// This is the property that is shown in the user interface, so update it so the user
// can get an idea of how many points will be rendered
_nDataPoints = _nObjectsInDataset;
}
void RenderableInterpolatedPoints::initializeShadersAndGlExtras() {
_program = BaseModule::ProgramObjectManager.request(
"RenderablePointCloud_Interpolated",
[]() {
std::filesystem::path path = absPath("${MODULE_BASE}/shaders/pointcloud");
return global::renderEngine->buildRenderProgram(
"RenderablePointCloud_Interpolated",
path / "pointcloud_interpolated_vs.glsl",
path / "pointcloud_fs.glsl",
path / "pointcloud_gs.glsl"
);
}
);
initializeBufferData();
}
void RenderableInterpolatedPoints::deinitializeShaders() {
BaseModule::ProgramObjectManager.release(
"RenderablePointCloud_Interpolated",
[](ghoul::opengl::ProgramObject* p) {
global::renderEngine->removeRenderProgram(p);
}
);
_program = nullptr;
}
void RenderableInterpolatedPoints::setExtraUniforms() {
float t0 = computeCurrentLowerValue();
float t = glm::clamp(_interpolation.value - t0, 0.f, 1.f);
_program->setUniform("interpolationValue", t);
_program->setUniform("useSpline", useSplineInterpolation());
}
void RenderableInterpolatedPoints::preUpdate() {
if (_shouldReinitializeBufferdata) [[unlikely]] {
initializeBufferData();
_shouldReinitializeBufferdata = false;
}
}
int RenderableInterpolatedPoints::nAttributesPerPoint() const {
int n = RenderablePointCloud::nAttributesPerPoint();
// Always at least three extra position values (xyz)
n += 3;
if (useSplineInterpolation()) {
// Use two more positions (xyz)
n += 2 * 3;
}
if (useOrientationData()) {
// Use one more orientation quaternion (wxyz)
n += 4;
}
// And potentially some more color and size data
n += hasColorData() ? 1 : 0;
n += hasSizeData() ? 1 : 0;
return n;
}
bool RenderableInterpolatedPoints::useSplineInterpolation() const {
return _interpolation.useSpline && _interpolation.nSteps > 1;
}
void RenderableInterpolatedPoints::addPositionDataForPoint(unsigned int index,
std::vector<float>& result,
double& maxRadius) const
{
auto [firstIndex, secondIndex] = interpolationIndices(index);
const dataloader::Dataset::Entry& e0 = _dataset.entries[firstIndex];
const dataloader::Dataset::Entry& e1 = _dataset.entries[secondIndex];
glm::dvec3 position0 = transformedPosition(e0);
glm::dvec3 position1 = transformedPosition(e1);
const double r = glm::max(glm::length(position0), glm::length(position1));
maxRadius = glm::max(maxRadius, r);
for (int j = 0; j < 3; ++j) {
result.push_back(static_cast<float>(position0[j]));
}
for (int j = 0; j < 3; ++j) {
result.push_back(static_cast<float>(position1[j]));
}
if (useSplineInterpolation()) {
// Compute the extra positions, before and after the other ones. But make sure
// we do not overflow the allowed bound for the current interpolation step
int beforeIndex = glm::max(static_cast<int>(firstIndex - _nDataPoints), 0);
int maxT = static_cast<int>(_interpolation.value.maxValue() - 1.f);
int maxAllowedindex = maxT * _nDataPoints + index;
int afterIndex = glm::min(
static_cast<int>(secondIndex + _nDataPoints),
maxAllowedindex
);
const dataloader::Dataset::Entry& e00 = _dataset.entries[beforeIndex];
const dataloader::Dataset::Entry& e11 = _dataset.entries[afterIndex];
glm::dvec3 positionBefore = transformedPosition(e00);
glm::dvec3 positionAfter = transformedPosition(e11);
for (int j = 0; j < 3; ++j) {
result.push_back(static_cast<float>(positionBefore[j]));
}
for (int j = 0; j < 3; ++j) {
result.push_back(static_cast<float>(positionAfter[j]));
}
}
}
void RenderableInterpolatedPoints::addColorAndSizeDataForPoint(unsigned int index,
std::vector<float>& result) const
{
auto [firstIndex, secondIndex] = interpolationIndices(index);
const dataloader::Dataset::Entry& e0 = _dataset.entries[firstIndex];
const dataloader::Dataset::Entry& e1 = _dataset.entries[secondIndex];
if (hasColorData()) {
const int colorParamIndex = currentColorParameterIndex();
result.push_back(e0.data[colorParamIndex]);
result.push_back(e1.data[colorParamIndex]);
}
if (hasSizeData()) {
const int sizeParamIndex = currentSizeParameterIndex();
// @TODO: Consider more detailed control over the scaling. Currently the value
// is multiplied with the value as is. Should have similar mapping properties
// as the color mapping
// Convert to diameter if data is given as radius
float multiplier = _sizeSettings.sizeMapping->isRadius ? 2.f : 1.f;
result.push_back(multiplier * e0.data[sizeParamIndex]);
result.push_back(multiplier * e1.data[sizeParamIndex]);
}
}
void RenderableInterpolatedPoints::addOrientationDataForPoint(unsigned int index,
std::vector<float>& result) const
{
auto [firstIndex, secondIndex] = interpolationIndices(index);
const dataloader::Dataset::Entry& e0 = _dataset.entries[firstIndex];
const dataloader::Dataset::Entry& e1 = _dataset.entries[secondIndex];
glm::quat q0 = orientationQuaternion(e0);
glm::quat q1 = orientationQuaternion(e1);
result.push_back(q0.x);
result.push_back(q0.y);
result.push_back(q0.z);
result.push_back(q0.w);
result.push_back(q1.x);
result.push_back(q1.y);
result.push_back(q1.z);
result.push_back(q1.w);
}
void RenderableInterpolatedPoints::initializeBufferData() {
if (_vao == 0) {
glGenVertexArrays(1, &_vao);
LDEBUG(std::format("Generating Vertex Array id '{}'", _vao));
}
if (_vbo == 0) {
glGenBuffers(1, &_vbo);
LDEBUG(std::format("Generating Vertex Buffer Object id '{}'", _vbo));
}
const int attibsPerPoint = nAttributesPerPoint();
const unsigned int bufferSize = attibsPerPoint * _nDataPoints * sizeof(float);
// Allocate the memory for the buffer (we will want to upload the data quite often)
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, bufferSize, nullptr, GL_DYNAMIC_DRAW);
int offset = 0;
offset = bufferVertexAttribute("in_position0", 3, attibsPerPoint, offset);
offset = bufferVertexAttribute("in_position1", 3, attibsPerPoint, offset);
if (useSplineInterpolation()) {
offset = bufferVertexAttribute("in_position_before", 3, attibsPerPoint, offset);
offset = bufferVertexAttribute("in_position_after", 3, attibsPerPoint, offset);
}
if (hasColorData()) {
offset = bufferVertexAttribute("in_colorParameter0", 1, attibsPerPoint, offset);
offset = bufferVertexAttribute("in_colorParameter1", 1, attibsPerPoint, offset);
}
if (hasSizeData()) {
offset = bufferVertexAttribute("in_scalingParameter0", 1, attibsPerPoint, offset);
offset = bufferVertexAttribute("in_scalingParameter1", 1, attibsPerPoint, offset);
}
if (useOrientationData()) {
offset = bufferVertexAttribute("in_orientation0", 4, attibsPerPoint, offset);
offset = bufferVertexAttribute("in_orientation1", 4, attibsPerPoint, offset);
}
if (_hasSpriteTexture) {
offset = bufferVertexAttribute("in_textureLayer", 1, attibsPerPoint, offset);
}
glBindVertexArray(0);
}
void RenderableInterpolatedPoints::updateBufferData() {
if (!_hasDataFile || _dataset.entries.empty()) {
return;
}
ZoneScopedN("Data dirty");
TracyGpuZone("Data dirty");
LDEBUG("Regenerating data");
// Regenerate data and update buffer
std::vector<float> slice = createDataSlice();
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, slice.size() * sizeof(float), slice.data());
glBindVertexArray(0);
_dataIsDirty = false;
}
bool RenderableInterpolatedPoints::isAtKnot() const {
float v = _interpolation.value;
return (v - glm::floor(v)) < std::numeric_limits<float>::epsilon();
}
float RenderableInterpolatedPoints::computeCurrentLowerValue() const {
float t0 = glm::floor(_interpolation.value);
if (isAtKnot()) {
t0 = t0 - 1.f;
}
const float maxTValue = _interpolation.value.maxValue();
const float maxAllowedT0 = glm::max(maxTValue - 1.f, 0.f);
t0 = glm::clamp(t0, 0.f, maxAllowedT0);
return t0;
}
float RenderableInterpolatedPoints::computeCurrentUpperValue() const {
const float t0 = computeCurrentLowerValue();
const float t1 = glm::clamp(t0 + 1.f, 0.f, _interpolation.value.maxValue());
return t1;
}
std::pair<size_t, size_t>
RenderableInterpolatedPoints::interpolationIndices(unsigned int index) const
{
const float t0 = computeCurrentLowerValue();
const float t1 = computeCurrentUpperValue();
const unsigned int t0Index = static_cast<unsigned int>(t0);
const unsigned int t1Index = static_cast<unsigned int>(t1);
const size_t lower = size_t(t0Index * _nDataPoints + index);
const size_t upper = size_t(t1Index * _nDataPoints + index);
return { lower, upper };
}
} // namespace openspace

View File

@@ -0,0 +1,122 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___RENDERABLEINTERPOLATEDPOINTS___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLEINTERPOLATEDPOINTS___H__
#include <modules/base/rendering/pointcloud/renderablepointcloud.h>
#include <openspace/properties/misc/triggerproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
namespace ghoul::opengl { class Texture; }
namespace openspace {
namespace documentation { struct Documentation; }
/**
* A specialization of the RenderablePointCloud that supports interpolation beetween
* different positions for the points.
*/
class RenderableInterpolatedPoints : public RenderablePointCloud {
public:
explicit RenderableInterpolatedPoints(const ghoul::Dictionary& dictionary);
~RenderableInterpolatedPoints() override = default;
static documentation::Documentation Documentation();
protected:
void initialize() override;
void initializeShadersAndGlExtras() override;
void deinitializeShaders() override;
void setExtraUniforms() override;
void preUpdate() override;
int nAttributesPerPoint() const override;
bool useSplineInterpolation() const;
/**
* Create the rendering data for the positions for the point with the given index
* and append that to the result. Compared to the base class, this class may require
* 2-4 positions, depending on if spline interpolation is used or not.
*
* The values are computed based on the current interpolation value.
*
* Also, compute the maxRadius to use for setting the bounding sphere.
*/
void addPositionDataForPoint(unsigned int index, std::vector<float>& result,
double& maxRadius) const override;
/**
* Create the rendering data for the color and size data for the point with the given
* index and append that to the result. Compared to the base class, this class require
* 2 values per data value, to use for interpolation.
*
* The values are computed based on the current interpolation value.
*/
void addColorAndSizeDataForPoint(unsigned int index,
std::vector<float>& result) const override;
void addOrientationDataForPoint(unsigned int index,
std::vector<float>& result) const override;
void initializeBufferData();
void updateBufferData() override;
private:
bool isAtKnot() const;
float computeCurrentLowerValue() const;
float computeCurrentUpperValue() const;
std::pair<size_t, size_t> interpolationIndices(unsigned int index) const;
struct Interpolation : public properties::PropertyOwner {
Interpolation();
properties::FloatProperty value;
properties::UIntProperty nSteps;
properties::TriggerProperty goToNextStep;
properties::TriggerProperty goToPrevStep;
properties::TriggerProperty interpolateToNextStep;
properties::TriggerProperty interpolateToPrevStep;
properties::TriggerProperty interpolateToEnd;
properties::TriggerProperty interpolateToStart;
properties::FloatProperty speed;
properties::BoolProperty useSpline;
};
Interpolation _interpolation;
float _prevInterpolationValue = 0.f;
bool _shouldReinitializeBufferdata = false;
unsigned int _nObjectsInDataset = 0;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLEINTERPOLATEDPOINTS___H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___RENDERABLEPOINTCLOUD___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLEPOINTCLOUD___H__
#include <openspace/rendering/renderable.h>
#include <modules/base/rendering/pointcloud/sizemappingcomponent.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/misc/triggerproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/rendering/colormappingcomponent.h>
#include <openspace/rendering/labelscomponent.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/uniformcache.h>
#include <filesystem>
#include <functional>
namespace ghoul::opengl {
class ProgramObject;
class Texture;
} // namespace ghoul::opengl
namespace openspace {
namespace documentation { struct Documentation; }
struct TextureFormat {
glm::uvec2 resolution;
bool useAlpha = false;
friend bool operator==(const TextureFormat& l, const TextureFormat& r);
};
struct TextureFormatHash {
size_t operator()(const TextureFormat& k) const;
};
/**
* This class describes a point cloud renderable that can be used to draw billboraded
* points based on a data file with 3D positions. Alternatively the points can also
* be colored and sized based on a separate column in the data file.
*/
class RenderablePointCloud : public Renderable {
public:
explicit RenderablePointCloud(const ghoul::Dictionary& dictionary);
~RenderablePointCloud() override = default;
void initialize() override;
void initializeGL() override;
void deinitializeGL() override;
bool isReady() const override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
void update(const UpdateData& data) override;
static documentation::Documentation Documentation();
protected:
enum class TextureInputMode {
Single = 0,
Multi,
Other // For subclasses that need to handle their own texture
};
virtual void initializeShadersAndGlExtras();
virtual void deinitializeShaders();
virtual void setExtraUniforms();
virtual void preUpdate();
glm::dvec3 transformedPosition(const dataloader::Dataset::Entry& e) const;
glm::quat orientationQuaternion(const dataloader::Dataset::Entry& e) const;
virtual int nAttributesPerPoint() const;
/**
* Helper function to buffer the vertex attribute with the given name and number
* of values. Assumes that the value is a float value.
*
* Returns the updated offset after this attribute is added
*/
int bufferVertexAttribute(const std::string& name, GLint nValues,
int nAttributesPerPoint, int offset) const;
virtual void updateBufferData();
void updateSpriteTexture();
/// Find the index of the currently chosen color parameter in the dataset
int currentColorParameterIndex() const;
/// Find the index of the currently chosen size parameter in the dataset
int currentSizeParameterIndex() const;
bool hasColorData() const;
bool hasSizeData() const;
bool hasMultiTextureData() const;
bool useOrientationData() const;
virtual void addPositionDataForPoint(unsigned int index, std::vector<float>& result,
double& maxRadius) const;
virtual void addColorAndSizeDataForPoint(unsigned int index,
std::vector<float>& result) const;
virtual void addOrientationDataForPoint(unsigned int index,
std::vector<float>& result) const;
std::vector<float> createDataSlice();
/**
* A function that subclasses could override to initialize their own textures to
* use for rendering, when the `_textureMode` is set to Other
*/
virtual void initializeCustomTexture();
void initializeSingleTexture();
void initializeMultiTextures();
void clearTextureDataStructures();
void loadTexture(const std::filesystem::path& path, int index);
void initAndAllocateTextureArray(unsigned int textureId,
glm::uvec2 resolution, size_t nLayers, bool useAlpha);
void fillAndUploadTextureLayer(unsigned int arrayindex, unsigned int layer,
size_t textureIndex, glm::uvec2 resolution, bool useAlpha, const void* pixelData);
void generateArrayTextures();
float computeDistanceFadeValue(const RenderData& data) const;
void renderPoints(const RenderData& data, const glm::dmat4& modelMatrix,
const glm::dvec3& orthoRight, const glm::dvec3& orthoUp, float fadeInVariable);
gl::GLenum internalGlFormat(bool useAlpha) const;
ghoul::opengl::Texture::Format glFormat(bool useAlpha) const;
bool _dataIsDirty = true;
bool _spriteTextureIsDirty = false;
bool _cmapIsDirty = true;
bool _hasSpriteTexture = false;
bool _hasDataFile = false;
bool _hasColorMapFile = false;
bool _hasDatavarSize = false;
bool _hasLabels = false;
struct SizeSettings : properties::PropertyOwner {
explicit SizeSettings(const ghoul::Dictionary& dictionary);
std::unique_ptr<SizeMappingComponent> sizeMapping;
properties::FloatProperty scaleExponent;
properties::FloatProperty scaleFactor;
properties::BoolProperty useMaxSizeControl;
properties::FloatProperty maxAngularSize;
};
SizeSettings _sizeSettings;
struct ColorSettings : properties::PropertyOwner {
explicit ColorSettings(const ghoul::Dictionary& dictionary);
properties::Vec3Property pointColor;
std::unique_ptr<ColorMappingComponent> colorMapping;
properties::BoolProperty enableOutline;
properties::Vec3Property outlineColor;
properties::FloatProperty outlineWidth;
properties::OptionProperty outlineStyle;
properties::BoolProperty applyCmapToOutline;
};
ColorSettings _colorSettings;
struct Fading : properties::PropertyOwner {
explicit Fading(const ghoul::Dictionary& dictionary);
properties::Vec2Property fadeInDistances;
properties::BoolProperty enabled;
properties::BoolProperty invert;
};
Fading _fading;
properties::BoolProperty _useAdditiveBlending;
properties::BoolProperty _useRotation;
properties::BoolProperty _drawElements;
properties::OptionProperty _renderOption;
properties::UIntProperty _nDataPoints;
properties::BoolProperty _hasOrientationData;
struct Texture : properties::PropertyOwner {
Texture();
properties::BoolProperty enabled;
properties::BoolProperty allowCompression;
properties::BoolProperty useAlphaChannel;
properties::StringProperty spriteTexturePath;
properties::StringProperty inputMode;
};
Texture _texture;
TextureInputMode _textureMode = TextureInputMode::Single;
std::filesystem::path _texturesDirectory;
ghoul::opengl::ProgramObject* _program = nullptr;
UniformCache(
cameraViewMatrix, projectionMatrix, modelMatrix, cameraPosition, cameraLookUp,
renderOption, maxAngularSize, color, opacity, scaleExponent, scaleFactor, up,
right, fadeInValue, hasSpriteTexture, spriteTexture, useColorMap, colorMapTexture,
cmapRangeMin, cmapRangeMax, nanColor, useNanColor, hideOutsideRange,
enableMaxSizeControl, aboveRangeColor, useAboveRangeColor, belowRangeColor,
useBelowRangeColor, hasDvarScaling, dvarScaleFactor, enableOutline, outlineColor,
outlineWeight, outlineStyle, useCmapOutline, aspectRatioScale, useOrientationData
) _uniformCache;
std::filesystem::path _dataFile;
DistanceUnit _unit = DistanceUnit::Parsec;
bool _useCaching = true;
bool _shouldComputeScaleExponent = false;
bool _createLabelsFromDataset = false;
bool _skipFirstDataPoint = false;
dataloader::Dataset _dataset;
dataloader::DataMapping _dataMapping;
std::unique_ptr<LabelsComponent> _labels;
glm::dmat4 _transformationMatrix = glm::dmat4(1.0);
GLuint _vao = 0;
GLuint _vbo = 0;
// List of (unique) loaded textures. The other maps refer to the index in this vector
std::vector<std::unique_ptr<ghoul::opengl::Texture>> _textures;
std::unordered_map<std::string, size_t> _textureNameToIndex;
// Texture index in dataset to index in vector of textures
std::unordered_map<int, size_t> _indexInDataToTextureIndex;
// Resolution/format to index in textures vector (used to generate one texture
// array per unique format)
std::unordered_map<TextureFormat, std::vector<size_t>, TextureFormatHash>
_textureMapByFormat;
// One per resolution above
struct TextureArrayInfo {
GLuint renderId;
GLint startOffset = -1;
int nPoints = -1;
glm::vec2 aspectRatioScale = glm::vec2(1.f);
};
std::vector<TextureArrayInfo> _textureArrays;
struct TextureId {
unsigned int arrayId;
unsigned int layer;
};
std::unordered_map<size_t, TextureId> _textureIndexToArrayMap;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLEPOINTCLOUD___H__

View File

@@ -0,0 +1,226 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/rendering/pointcloud/renderablepolygoncloud.h>
#include <openspace/documentation/documentation.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/glm.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/opengl/texture.h>
#include <optional>
namespace {
constexpr std::string_view _loggerCat = "RenderablePolygonCloud";
// A RenderablePolygonCloud is a RenderablePointCloud where the shape of the points
// is a uniform polygon with a given number of sides instead of a texture. For
// instance, PolygonSides = 5 results in the points being rendered as pentagons.
//
// Note that while this renderable inherits the texture component from
// RenderablePointCloud, any added texture information will be ignored in favor of the
// polygon shape.
//
// See documentation of RenderablePointCloud for details on the other parts of the
// point cloud rendering.
struct [[codegen::Dictionary(RenderablePolygonCloud)]] Parameters {
// The number of sides for the polygon used to represent each point. Default is
// 3, i.e. to use triangles.
std::optional<int> polygonSides [[codegen::greaterequal(3)]];
};
#include "renderablepolygoncloud_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation RenderablePolygonCloud::Documentation() {
return codegen::doc<Parameters>(
"base_renderablepolygoncloud",
RenderablePointCloud::Documentation()
);
}
RenderablePolygonCloud::RenderablePolygonCloud(const ghoul::Dictionary& dictionary)
: RenderablePointCloud(dictionary)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_nPolygonSides = p.polygonSides.value_or(_nPolygonSides);
// The texture to use for the rendering will be generated in initializeGl. Make sure
// we use it in the rendering
_hasSpriteTexture = true;
_textureMode = TextureInputMode::Other;
removePropertySubOwner(_texture);
}
void RenderablePolygonCloud::deinitializeGL() {
glDeleteBuffers(1, &_polygonVbo);
_polygonVbo = 0;
glDeleteVertexArrays(1, &_polygonVao);
_polygonVao = 0;
glDeleteTextures(1, &_pTexture);
RenderablePointCloud::deinitializeGL();
}
void RenderablePolygonCloud::initializeCustomTexture() {
ZoneScoped;
if (_textureIsInitialized) {
LWARNING("RenderablePolygonCloud texture cannot be updated during runtime");
return;
}
LDEBUG("Creating Polygon Texture");
constexpr gl::GLsizei TexSize = 512;
// We don't use the helper function for the format and internal format here,
// as we don't want the compression to be used for the polygon texture and we
// always want alpha. This is also why we do not need to update the texture
bool useAlpha = true;
gl::GLenum format = gl::GLenum(glFormat(useAlpha));
gl::GLenum internalFormat = GL_RGBA8;
glGenTextures(1, &_pTexture);
glBindTexture(GL_TEXTURE_2D, _pTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Stopped using a buffer object for GL_PIXEL_UNPACK_BUFFER
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glTexImage2D(
GL_TEXTURE_2D,
0,
internalFormat,
TexSize,
TexSize,
0,
format,
GL_UNSIGNED_BYTE,
nullptr
);
renderToTexture(_pTexture, TexSize, TexSize);
// Download the data and use it to intialize the data we need to rendering.
// Allocate memory: N channels, with one byte each
constexpr unsigned int nChannels = 4;
unsigned int arraySize = TexSize * TexSize * nChannels;
std::vector<GLubyte> pixelData;
pixelData.resize(arraySize);
glBindTexture(GL_TEXTURE_2D, _pTexture);
glGetTexImage(GL_TEXTURE_2D, 0, format, GL_UNSIGNED_BYTE, pixelData.data());
// Create array from data, size and format
unsigned int id = 0;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D_ARRAY, id);
initAndAllocateTextureArray(id, glm::uvec2(TexSize), 1, useAlpha);
fillAndUploadTextureLayer(0, 0, 0, glm::uvec2(TexSize), useAlpha, pixelData.data());
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
_textureIsInitialized = true;
}
void RenderablePolygonCloud::renderToTexture(GLuint textureToRenderTo,
GLuint textureWidth, GLuint textureHeight)
{
LDEBUG("Rendering to Texture");
// Saves initial Application's OpenGL State
GLint defaultFBO = 0;
std::array<GLint, 4> viewport;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
glGetIntegerv(GL_VIEWPORT, viewport.data());
GLuint textureFBO;
glGenFramebuffers(1, &textureFBO);
glBindFramebuffer(GL_FRAMEBUFFER, textureFBO);
const GLenum drawBuffers = GL_COLOR_ATTACHMENT0;
glDrawBuffers(1, &drawBuffers);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureToRenderTo, 0);
glViewport(viewport[0], viewport[1], textureWidth, textureHeight);
if (_polygonVao == 0) {
glGenVertexArrays(1, &_polygonVao);
}
if (_polygonVbo == 0) {
glGenBuffers(1, &_polygonVbo);
}
glBindVertexArray(_polygonVao);
glBindBuffer(GL_ARRAY_BUFFER, _polygonVbo);
constexpr std::array<GLfloat, 4> VertexData = {
// x y z w
0.f, 0.f, 0.f, 1.f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData), VertexData.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
renderPolygonGeometry(_polygonVao);
// Restores Applications' OpenGL State
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
glDeleteBuffers(1, &_polygonVbo);
glDeleteVertexArrays(1, &_polygonVao);
glDeleteFramebuffers(1, &textureFBO);
}
void RenderablePolygonCloud::renderPolygonGeometry(GLuint vao) const {
std::unique_ptr<ghoul::opengl::ProgramObject> program =
ghoul::opengl::ProgramObject::Build(
"RenderablePointCloud_Polygon",
absPath("${MODULE_BASE}/shaders/polygon_vs.glsl"),
absPath("${MODULE_BASE}/shaders/polygon_fs.glsl"),
absPath("${MODULE_BASE}/shaders/polygon_gs.glsl")
);
program->activate();
constexpr glm::vec4 Black = glm::vec4(0.f, 0.f, 0.f, 0.f);
glClearBufferfv(GL_COLOR, 0, glm::value_ptr(Black));
program->setUniform("sides", _nPolygonSides);
glBindVertexArray(vao);
glDrawArrays(GL_POINTS, 0, 1);
glBindVertexArray(0);
program->deactivate();
}
} // namespace openspace

View File

@@ -0,0 +1,69 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___RENDERABLEPOLYGONCLOUD___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLEPOLYGONCLOUD___H__
#include <modules/base/rendering/pointcloud/renderablepointcloud.h>
#include <ghoul/opengl/ghoul_gl.h>
namespace ghoul::opengl { class Texture; }
namespace openspace {
namespace documentation { struct Documentation; }
/**
* A billboarded point cloud, but with dynamically created uniform polygon shapes instead
* of a custom texture. Overwrites the sprite set in parent class, RenderablePointCloud
*/
class RenderablePolygonCloud : public RenderablePointCloud {
public:
explicit RenderablePolygonCloud(const ghoul::Dictionary& dictionary);
~RenderablePolygonCloud() override = default;
void deinitializeGL() override;
static documentation::Documentation Documentation();
private:
void initializeCustomTexture() override;
void renderToTexture(GLuint textureToRenderTo, GLuint textureWidth,
GLuint textureHeight);
void renderPolygonGeometry(GLuint vao) const;
int _nPolygonSides = 3;
GLuint _pTexture = 0;
GLuint _polygonVao = 0;
GLuint _polygonVbo = 0;
bool _textureIsInitialized = false;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLEPOLYGONCLOUD___H__

View File

@@ -0,0 +1,180 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/rendering/pointcloud/sizemappingcomponent.h>
#include <openspace/documentation/documentation.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "SizeMapping";
constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
"Enabled",
"Size Mapping Enabled",
"If this value is set to 'true' and at least one column was loaded as an option "
"for size mapping, the chosen data column will be used to scale the size of the "
"points. The first option in the list is selected per default.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo OptionInfo = {
"Parameter",
"Parameter Option",
"This value determines which parameter is used for scaling of the point. The "
"parameter value will be used as a multiplicative factor to scale the size of "
"the points. Note that they may however still be scaled by max size adjustment "
"effects.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ScaleFactorInfo = {
"ScaleFactor",
"Scale Factor",
"This value is a multiplicative factor that is applied to the data values that "
"are used to scale the points, when size mapping is applied.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo IsRadiusInfo = {
"IsRadius",
"Size is Radius",
"If true, the size value in the data is interpreted as the radius of the points. "
"Otherwise, it is interpreted as the diameter.",
openspace::properties::Property::Visibility::AdvancedUser
};
struct [[codegen::Dictionary(SizeMappingComponent)]] Parameters {
// [[codegen::verbatim(EnabledInfo.description)]]
std::optional<bool> enabled;
// A list specifying all parameters that may be used for size mapping, i.e.
// scaling the points based on the provided data columns.
std::optional<std::vector<std::string>> parameterOptions;
// [[codegen::verbatim(OptionInfo.description)]]
std::optional<std::string> parameter;
// [[codegen::verbatim(ScaleFactorInfo.description)]]
enum class [[codegen::map(openspace::DistanceUnit)]] ScaleUnit {
Nanometer,
Micrometer,
Millimeter,
Centimeter,
Decimeter,
Meter,
Kilometer,
AU,
Lighthour,
Lightday,
Lightmonth,
Lightyear,
Parsec,
Kiloparsec,
Megaparsec,
Gigaparsec,
Gigalightyear
};
// The scale used for the size values in the dataset, given as either a string
// representing a specific unit or a value to multiply all the datapoints with
// to convert the value to meter. The resulting value will be applied as a
// multiplicative factor. For example, if the size data is given in is in
// kilometers then specify either <code>ScaleFactor = 'Kilometer'</code> or
// <code>ScaleFactor = 1000.0</code>.
std::optional<std::variant<ScaleUnit, double>> scaleFactor;
// [[codegen::verbatim(IsRadiusInfo.description)]]
std::optional<bool> isRadius;
};
#include "sizemappingcomponent_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation SizeMappingComponent::Documentation() {
return codegen::doc<Parameters>("base_sizemappingcomponent");
}
SizeMappingComponent::SizeMappingComponent()
: properties::PropertyOwner({ "SizeMapping", "Size Mapping", "" })
, enabled(EnabledInfo, true)
, parameterOption(OptionInfo)
, scaleFactor(ScaleFactorInfo, 1.f, 0.f, 1000.f)
, isRadius(IsRadiusInfo, false)
{
addProperty(enabled);
addProperty(parameterOption);
addProperty(scaleFactor);
addProperty(isRadius);
}
SizeMappingComponent::SizeMappingComponent(const ghoul::Dictionary& dictionary)
: SizeMappingComponent()
{
const Parameters p = codegen::bake<Parameters>(dictionary);
enabled = p.enabled.value_or(enabled);
int indexOfProvidedOption = -1;
if (p.parameterOptions.has_value()) {
std::vector<std::string> opts = *p.parameterOptions;
for (size_t i = 0; i < opts.size(); ++i) {
// Note that options are added in order
parameterOption.addOption(static_cast<int>(i), opts[i]);
if (p.parameter.has_value() && *p.parameter == opts[i]) {
indexOfProvidedOption = static_cast<int>(i);
}
}
}
if (indexOfProvidedOption >= 0) {
parameterOption = indexOfProvidedOption;
}
else if (p.parameter.has_value()) {
LERROR(std::format(
"Error when reading Parameter. Could not find provided parameter '{}' in "
"list of parameter options. Using default.", *p.parameter
));
}
if (p.scaleFactor.has_value()) {
if (std::holds_alternative<Parameters::ScaleUnit>(*p.scaleFactor)) {
const Parameters::ScaleUnit scaleUnit =
std::get<Parameters::ScaleUnit>(*p.scaleFactor);
const DistanceUnit distanceUnit = codegen::map<DistanceUnit>(scaleUnit);
scaleFactor = static_cast<float>(toMeter(distanceUnit));
}
else if (std::holds_alternative<double>(*p.scaleFactor)) {
scaleFactor = static_cast<float>(std::get<double>(*p.scaleFactor));
}
}
isRadius = p.isRadius.value_or(isRadius);
}
} // namespace openspace

View File

@@ -0,0 +1,57 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___SIZEMAPPINGCOMPONENT___H__
#define __OPENSPACE_MODULE_BASE___SIZEMAPPINGCOMPONENT___H__
#include <openspace/properties/propertyowner.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
namespace openspace {
namespace documentation { struct Documentation; }
/**
* This is a component that can be used to hold parameters and properties for scaling
* point cloud points (or other data-based entities) based a parameter in a dataset.
*/
struct SizeMappingComponent : public properties::PropertyOwner {
SizeMappingComponent();
explicit SizeMappingComponent(const ghoul::Dictionary& dictionary);
~SizeMappingComponent() override = default;
static documentation::Documentation Documentation();
properties::BoolProperty enabled;
properties::OptionProperty parameterOption;
properties::FloatProperty scaleFactor;
properties::BoolProperty isRadius;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___SIZEMAPPINGCOMPONENT___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -40,21 +40,34 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo XColorInfo = {
"XColor",
"X Color",
"This value determines the color of the x axis"
"The color of the x-axis.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo YColorInfo = {
"YColor",
"Y Color",
"This value determines the color of the y axis"
"The color of the y-axis.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ZColorInfo = {
"ZColor",
"Z Color",
"This value determines the color of the z axis"
"The color of the z-axis.",
openspace::properties::Property::Visibility::NoviceUser
};
// The RenderableCartesianAxes can be used to render the local Cartesian coordinate
// system, or reference frame, of another scene graph node. The colors of the axes
// can be customized but are per default set to Red, Green and Blue, for the X-, Y-
// and Z-axis, respectively.
//
// To add the axes, create a scene graph node with the RenderableCartesianAxes
// renderable and add it as a child to the other scene graph node, i.e. specify the
// other node as the `Parent` of the node with this renderable. Also, the axes have to
// be scaled to match the parent object for the axes to be visible in the scene, for
// example using a [StaticScale](#base_transform_scale_static).
struct [[codegen::Dictionary(RenderableCartesianAxes)]] Parameters {
// [[codegen::verbatim(XColorInfo.description)]]
std::optional<glm::vec3> xColor [[codegen::color()]];
@@ -64,7 +77,6 @@ namespace {
// [[codegen::verbatim(ZColorInfo.description)]]
std::optional<glm::vec3> zColor [[codegen::color()]];
};
#include "renderablecartesianaxes_codegen.cpp"
} // namespace
@@ -83,6 +95,9 @@ RenderableCartesianAxes::RenderableCartesianAxes(const ghoul::Dictionary& dictio
, _zColor(ZColorInfo, glm::vec3(0.f, 0.f, 1.f), glm::vec3(0.f), glm::vec3(1.f))
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(Fadeable::_opacity);
_xColor = p.xColor.value_or(_xColor);
_xColor.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_xColor);
@@ -117,14 +132,14 @@ void RenderableCartesianAxes::initializeGL() {
glGenVertexArrays(1, &_vaoId);
glBindVertexArray(_vaoId);
std::vector<Vertex> vertices({
constexpr std::array<Vertex, 4> vertices = {
Vertex{0.f, 0.f, 0.f},
Vertex{1.f, 0.f, 0.f},
Vertex{0.f, 1.f, 0.f},
Vertex{0.f, 0.f, 1.f}
});
};
std::vector<int> indices = {
constexpr std::array<int, 6> indices = {
0, 1,
0, 2,
0, 3
@@ -172,16 +187,10 @@ void RenderableCartesianAxes::deinitializeGL() {
_program = nullptr;
}
void RenderableCartesianAxes::render(const RenderData& data, RendererTasks&){
void RenderableCartesianAxes::render(const RenderData& data, RendererTasks&) {
_program->activate();
const glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) *
glm::dmat4(data.modelTransform.rotation) *
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() *
modelTransform;
const glm::dmat4 modelViewTransform = calcModelViewTransform(data);
_program->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_program->setUniform("projectionTransform", data.camera.projectionMatrix());
@@ -189,12 +198,17 @@ void RenderableCartesianAxes::render(const RenderData& data, RendererTasks&){
_program->setUniform("xColor", _xColor);
_program->setUniform("yColor", _yColor);
_program->setUniform("zColor", _zColor);
_program->setUniform("opacity", opacity());
// Changes GL state:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnablei(GL_BLEND, 0);
glEnable(GL_LINE_SMOOTH);
glLineWidth(3.0);
#ifndef __APPLE__
glLineWidth(3.f);
#else // ^^^^ __APPLE__ // !__APPLE__ vvvv
glLineWidth(1.f);
#endif // __APPLE__
glBindVertexArray(_vaoId);
glDrawElements(GL_LINES, 6, GL_UNSIGNED_INT, nullptr);

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/renderable.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/matrix/dmat4property.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/vector/vec3property.h>
@@ -42,7 +42,7 @@ namespace openspace {
class RenderableCartesianAxes : public Renderable {
public:
RenderableCartesianAxes(const ghoul::Dictionary& dictionary);
explicit RenderableCartesianAxes(const ghoul::Dictionary& dictionary);
~RenderableCartesianAxes() override = default;
void initializeGL() override;

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,31 +39,36 @@
#include <optional>
namespace {
constexpr std::array<const char*, 4> UniformNames = {
"modelViewProjectionTransform", "opacity", "width", "colorTexture"
};
constexpr openspace::properties::Property::PropertyInfo TextureInfo = {
"Texture",
"Texture",
"This value is the path to a texture on disk that contains a one-dimensional "
"texture to be used for the color"
"The path to a file with a one-dimensional texture to be used for the disc "
"color. The leftmost color will be innermost color when rendering the disc, "
"and the rightmost color will be the outermost color.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
"Size",
"Size",
"This value specifies the outer radius of the disc in meter"
"The outer radius of the disc, in meters.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo WidthInfo = {
"Width",
"Width",
"This value is used to set the width of the disc. The actual width is set "
"based on the given size and this value should be set between 0 and 1. A value "
"of 1 results in a full circle and 0.5 a disc with an inner radius of 0.5*size"
"The disc width, given as a ratio of the full disc radius. For example, a value "
"of 1 results in a full circle, while 0.5 results in a disc where the inner "
"radius is half of the full radius.",
openspace::properties::Property::Visibility::AdvancedUser
};
// This renderable can be used to create a circular disc that is colored based on a
// one-dimensional texture.
//
// The disc will be filled i.e. a full circle, per default, but may also be made
// with a hole in the center using the `Width` parameter.
struct [[codegen::Dictionary(RenderableDisc)]] Parameters {
// [[codegen::verbatim(TextureInfo.description)]]
std::filesystem::path texture;
@@ -72,7 +77,7 @@ namespace {
std::optional<float> size;
// [[codegen::verbatim(WidthInfo.description)]]
std::optional<float> width;
std::optional<float> width [[codegen::inrange(0.0, 1.0)]];
};
#include "renderabledisc_codegen.cpp"
} // namespace
@@ -86,31 +91,33 @@ documentation::Documentation RenderableDisc::Documentation() {
RenderableDisc::RenderableDisc(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _texturePath(TextureInfo)
, _size(SizeInfo, 1.f, 0.f, 1e13f)
, _width(WidthInfo, 0.5f, 0.f, 1.f)
, _size(SizeInfo, 1.f, 0.001f, 1e13f)
, _width(WidthInfo, 1.f, 0.001f, 1.f)
, _plane(_size)
, _planeIsDirty(true)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(Fadeable::_opacity);
_texturePath = p.texture.string();
_texturePath.onChange([&]() { _texture->loadFromFile(_texturePath.value()); });
_texturePath.onChange([this]() { _texture->loadFromFile(_texturePath.value()); });
addProperty(_texturePath);
_size.setExponent(13.f);
_size.setExponent(7.f);
_size = p.size.value_or(_size);
setBoundingSphere(_size);
_size.onChange([&]() { _planeIsDirty = true; });
_size.onChange([this]() { _planeIsDirty = true; });
addProperty(_size);
_width = p.width.value_or(_width);
addProperty(_width);
addProperty(_opacity);
setRenderBin(Renderable::RenderBin::PostDeferredTransparent);
}
bool RenderableDisc::isReady() const {
return _shader && _texture && _plane;
return _shader && _texture;
}
void RenderableDisc::initialize() {
@@ -118,8 +125,6 @@ void RenderableDisc::initialize() {
_texture->setFilterMode(ghoul::opengl::Texture::FilterMode::AnisotropicMipMap);
_texture->setWrapping(ghoul::opengl::Texture::WrappingMode::ClampToEdge);
_texture->setShouldWatchFileForChanges(true);
_plane = std::make_unique<PlaneGeometry>(planeSize());
}
void RenderableDisc::initializeGL() {
@@ -128,12 +133,11 @@ void RenderableDisc::initializeGL() {
_texture->loadFromFile(_texturePath.value());
_texture->uploadToGpu();
_plane->initialize();
_plane.initialize();
}
void RenderableDisc::deinitializeGL() {
_plane->deinitialize();
_plane = nullptr;
_plane.deinitialize();
_texture = nullptr;
global::renderEngine->removeRenderProgram(_shader.get());
@@ -143,16 +147,12 @@ void RenderableDisc::deinitializeGL() {
void RenderableDisc::render(const RenderData& data, RendererTasks&) {
_shader->activate();
glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) *
glm::dmat4(data.modelTransform.rotation) *
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
const glm::dmat4 modelViewProjectionTransform =
calcModelViewProjectionTransform(data);
_shader->setUniform(
_uniformCache.modelViewProjection,
data.camera.projectionMatrix() * glm::mat4(modelViewTransform)
_uniformCache.modelViewProjectionTransform,
glm::mat4(modelViewProjectionTransform)
);
_shader->setUniform(_uniformCache.width, _width);
_shader->setUniform(_uniformCache.opacity, opacity());
@@ -160,14 +160,14 @@ void RenderableDisc::render(const RenderData& data, RendererTasks&) {
ghoul::opengl::TextureUnit unit;
unit.activate();
_texture->bind();
_shader->setUniform(_uniformCache.texture, unit);
_shader->setUniform(_uniformCache.colorTexture, unit);
glEnablei(GL_BLEND, 0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(false);
glDisable(GL_CULL_FACE);
_plane->render();
_plane.render();
_shader->deactivate();
@@ -178,13 +178,13 @@ void RenderableDisc::render(const RenderData& data, RendererTasks&) {
}
void RenderableDisc::update(const UpdateData&) {
if (_shader->isDirty()) {
if (_shader->isDirty()) [[unlikely]] {
_shader->rebuildFromFile();
updateUniformLocations();
}
if (_planeIsDirty) {
_plane->updateSize(planeSize());
if (_planeIsDirty) [[unlikely]] {
_plane.updateSize(planeSize());
_planeIsDirty = false;
}
@@ -201,7 +201,7 @@ void RenderableDisc::initializeShader() {
}
void RenderableDisc::updateUniformLocations() {
ghoul::opengl::updateUniformLocations(*_shader, _uniformCache, UniformNames);
ghoul::opengl::updateUniformLocations(*_shader, _uniformCache);
}
float RenderableDisc::planeSize() const {

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -25,9 +25,10 @@
#ifndef __OPENSPACE_MODULE_BASE___RENDERABLEDISC___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLEDISC___H__
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/rendering/renderable.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/rendering/texturecomponent.h>
#include <openspace/util/planegeometry.h>
#include <ghoul/opengl/uniformcache.h>
@@ -42,7 +43,7 @@ namespace documentation { struct Documentation; }
class RenderableDisc : public Renderable {
public:
RenderableDisc(const ghoul::Dictionary& dictionary);
explicit RenderableDisc(const ghoul::Dictionary& dictionary);
void initialize() override;
void initializeGL() override;
@@ -67,11 +68,12 @@ protected:
std::unique_ptr<ghoul::opengl::ProgramObject> _shader;
std::unique_ptr<PlaneGeometry> _plane;
PlaneGeometry _plane;
std::unique_ptr<TextureComponent> _texture;
private:
UniformCache(modelViewProjection, opacity, width, texture) _uniformCache;
UniformCache(modelViewProjectionTransform, opacity, width,
colorTexture) _uniformCache;
bool _planeIsDirty = false;
};

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -22,7 +22,7 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/vislab/rendering/renderabledistancelabel.h>
#include <modules/base/rendering/renderabledistancelabel.h>
#include <modules/base/rendering/renderablenodeline.h>
#include <openspace/documentation/documentation.h>
@@ -30,6 +30,7 @@
#include <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/logging/logmanager.h>
#include <optional>
#include <string>
@@ -40,33 +41,58 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo NodeLineInfo = {
"NodeLine",
"Node Line",
"Property to track a nodeline. When tracking the label text will be updating the "
"distance from the nodeline start and end"
"The identifier of a scene graph node with a RenderableNodeLine that this label "
"should track. The label text will be updating based on the distance from the "
"node line's start and end.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo DistanceUnitInfo = {
"DistanceUnit",
"Distance Unit",
"Property to define the unit in which the distance should be displayed"
"Defaults to 'km' if not specified"
"Display Distance Unit",
"The unit in which the distance value should be displayed. Defaults to 'km' if "
"not specified.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo CustomUnitDescriptorInfo = {
"CustomUnitDescriptor",
"Custom Unit Descriptor",
"Property to define a custom unit descriptor to use to describe the distance "
"value. Defaults to the units SI descriptor if not specified"
"value. Defaults to the selected unit's SI descriptor if not specified.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo PrecisionInfo = {
"Precision",
"Precision",
"The precision in which to to show the distance number, i.e. the number of "
"digits after the decimal point.",
openspace::properties::Property::Visibility::User
};
// This `Renderable` creates a label that shows the distance between two nodes, based
// on an existing [RenderableNodeLine](#base_renderable_nodeline). The label
// will be placed halfway between the two scene graph nodes that the line connects.
//
// The unit in which the distance is displayed can be customized, as well as the
// precision of the number.
struct [[codegen::Dictionary(RenderableDistanceLabel)]] Parameters {
// [[codegen::verbatim(NodeLineInfo.description)]]
// The identifier of a scene graph node with a
// [RenderableNodeLine](#base_renderable_nodeline) that this label
// should track. The label text will be updating based on the distance from the
// node line's start and end.
std::string nodeLine;
// [[codegen::verbatim(DistanceUnitInfo.description)]]
std::optional<int> distanceUnit;
std::optional<std::string> distanceUnit
[[codegen::inlist(openspace::distanceUnitList())]];
// [[codegen::verbatim(CustomUnitDescriptorInfo.description)]]
std::optional<std::string> customUnitDescriptor;
// [[codegen::verbatim(PrecisionInfo.description)]]
std::optional<int> precision [[codegen::greaterequal(0)]];
};
#include "renderabledistancelabel_codegen.cpp"
} // namespace
@@ -74,29 +100,46 @@ namespace {
namespace openspace {
documentation::Documentation RenderableDistanceLabel::Documentation() {
return codegen::doc<Parameters>("vislab_renderable_distance_label");
return codegen::doc<Parameters>("base_renderable_distancelabel");
}
RenderableDistanceLabel::RenderableDistanceLabel(const ghoul::Dictionary& dictionary)
: RenderableLabel(dictionary)
, _nodelineId(NodeLineInfo)
, _distanceUnit(DistanceUnitInfo, 1, 0, 11)
, _distanceUnit(DistanceUnitInfo)
, _customUnitDescriptor(CustomUnitDescriptorInfo)
, _precision(PrecisionInfo, 0, 0, 10)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_nodelineId = p.nodeLine;
addProperty(_nodelineId);
_distanceUnit = p.distanceUnit.value_or(_distanceUnit);
for (const DistanceUnit u : DistanceUnits) {
_distanceUnit.addOption(
static_cast<int>(u),
std::string(nameForDistanceUnit(u))
);
}
_distanceUnit = static_cast<int>(DistanceUnit::Kilometer);
if (p.distanceUnit.has_value()) {
const DistanceUnit unit = distanceUnitFromString(*p.distanceUnit);
_distanceUnit = static_cast<int>(unit);
}
addProperty(_distanceUnit);
_customUnitDescriptor = p.customUnitDescriptor.value_or(_customUnitDescriptor);
addProperty(_customUnitDescriptor);
_precision = p.precision.value_or(_precision);
addProperty(_precision);
// The text will be updated automatically, so set the property to readonly
_text.setReadOnly(true);
}
void RenderableDistanceLabel::update(const UpdateData&) {
if (_errorThrown) {
if (_errorThrown) [[unlikely]] {
return;
}
@@ -113,45 +156,43 @@ void RenderableDistanceLabel::update(const UpdateData&) {
return;
}
// Get used unit scale
const float scale = unit(_distanceUnit);
const DistanceUnit unit = static_cast<DistanceUnit>(_distanceUnit.value());
// Get unit descriptor text
std::string_view unitDescriptor = toString(_distanceUnit);
std::string_view unitDescriptor = abbreviationForDistanceUnit(unit);
if (!_customUnitDescriptor.value().empty()) {
unitDescriptor = _customUnitDescriptor;
}
// Get distance as string and remove fractional part
std::string distanceText = std::to_string(
std::round(nodeline->distance() / scale)
);
int pos = static_cast<int>(distanceText.find("."));
std::string subStr = distanceText.substr(pos);
distanceText.erase(pos, subStr.size());
// Get distance as string
const double convertedDistance = convertMeters(nodeline->distance(), unit);
std::string distanceText = std::format("{:.{}f}", convertedDistance, _precision.value());
// Create final label text and set it
const std::string finalText = fmt::format("{} {}", distanceText, unitDescriptor);
const std::string finalText = std::format("{} {}", distanceText, unitDescriptor);
setLabelText(finalText);
// Update placement of label with transformation matrix
SceneGraphNode* startNode = RE.scene()->sceneGraphNode(nodeline->start());
SceneGraphNode* endNode = RE.scene()->sceneGraphNode(nodeline->end());
if (startNode && endNode) {
glm::dvec3 start = startNode->worldPosition();
glm::dvec3 end = endNode->worldPosition();
glm::dvec3 goalPos = start + (end - start) / 2.0;
if (startNode && endNode) [[likely]] {
const glm::dvec3 start = startNode->worldPosition();
const glm::dvec3 end = endNode->worldPosition();
const glm::dvec3 goalPos = start + (end - start) / 2.0;
_transformationMatrix = glm::translate(glm::dmat4(1.0), goalPos);
}
else {
LERROR(fmt::format(
"Could not find scene graph node {} or {}",
LERROR(std::format(
"Could not find scene graph node '{}' or '{}'",
nodeline->start(), nodeline->end()
));
}
}
else {
LERROR(fmt::format("There is no scenegraph node with id {}", _nodelineId));
LERROR(std::format(
"There is no scenegraph node with id {}", _nodelineId.value()
));
_errorThrown = true;
}
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -22,29 +22,34 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_VISLAB___RENDERABLEDISTANCELABEL___H__
#define __OPENSPACE_MODULE_VISLAB___RENDERABLEDISTANCELABEL___H__
#ifndef __OPENSPACE_MODULE_BASE___RENDERABLEDISTANCELABEL___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLEDISTANCELABEL___H__
#include <modules/base/rendering/renderablelabel.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/intproperty.h>
namespace openspace {
namespace documentation { struct Documentation; }
class RenderableDistanceLabel : public RenderableLabel {
public:
RenderableDistanceLabel(const ghoul::Dictionary& dictionary);
explicit RenderableDistanceLabel(const ghoul::Dictionary& dictionary);
void update(const UpdateData& data) override;
static documentation::Documentation Documentation();
private:
properties::StringProperty _nodelineId;
properties::IntProperty _distanceUnit;
properties::OptionProperty _distanceUnit;
properties::StringProperty _customUnitDescriptor;
properties::IntProperty _precision;
bool _errorThrown = false;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_VISLAB___RENDERABLEDISTANCELABEL___H__
#endif // __OPENSPACE_MODULE_BASE___RENDERABLEDISTANCELABEL___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -92,55 +92,65 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo BlendModeInfo = {
"BlendMode",
"Blending Mode",
"This determines the blending mode that is applied to the renderable"
"This determines the blending mode that is applied to the renderable.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"The label text color"
"The label text color.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo FontSizeInfo = {
"FontSize",
"Font Size",
"The font size (in points) for the label"
"The font size (in points) for the label.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
"Size",
"Size",
"This value affects the size scale of the label"
"Scales the size of the label, exponentially. The value is used as the exponent "
"in a 10^x computation to scale the label size.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo TextInfo = {
"Text",
"Text",
"The text that will be displayed on screen"
"The text that will be displayed on screen.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo MinMaxSizeInfo = {
"MinMaxSize",
"Min and Max Size",
"The minimum and maximum size (in pixels) of the label"
"The minimum and maximum size (in pixels) of the label.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo TransformationMatrixInfo = {
"TransformationMatrix",
"Transformation Matrix",
"Transformation matrix to be applied to the label"
"Transformation matrix to be applied to the label.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OrientationOptionInfo = {
"OrientationOption",
"Orientation Option",
"Label orientation rendering mode"
"Label orientation rendering mode.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableFadingEffectInfo = {
"EnableFading",
"Enable/Disable Fade-in Effect",
"Enable/Disable the Fade-in effect"
"Enable/Disable the Fade-in effect.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo FadeWidthsInfo = {
@@ -150,20 +160,23 @@ namespace {
"The first value is the distance before the closest distance and the second "
"the one after the furthest distance. For example, with the unit Parsec (pc), "
"a value of {1, 2} will make the label being fully faded out 1 Parsec before "
"the closest distance and 2 Parsec away from the furthest distance"
"the closest distance and 2 Parsec away from the furthest distance.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo FadeDistancesInfo = {
"FadeDistances",
"Fade Distances",
"The distance range in which the labels should be fully opaque, specified in "
"the chosen unit. The distance from the position of the label to the camera"
"the chosen unit. The distance from the position of the label to the camera.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo FadeUnitOptionInfo = {
"FadeUnit",
"Fade Distance Unit",
"Distance unit for fade-in/-out distance calculations. Defaults to \"au\""
"Distance unit for fade-in/-out distance calculations. Defaults to \"au\".",
openspace::properties::Property::Visibility::AdvancedUser
};
struct [[codegen::Dictionary(RenderableLabel)]] Parameters {
@@ -238,35 +251,28 @@ documentation::Documentation RenderableLabel::Documentation() {
}
RenderableLabel::RenderableLabel(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _blendMode(BlendModeInfo, properties::OptionProperty::DisplayType::Dropdown)
: Renderable(dictionary, { .automaticallyUpdateRenderBin = false })
, _blendMode(BlendModeInfo)
, _text(TextInfo, "")
, _color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, _fontSize(FontSizeInfo, 50.f, 1.f, 100.f)
, _size(SizeInfo, 8.f, 0.5f, 30.f)
, _minMaxSize(MinMaxSizeInfo, glm::ivec2(8, 20), glm::ivec2(0), glm::ivec2(100))
, _text(TextInfo, "")
, _enableFadingEffect(EnableFadingEffectInfo, false)
, _fadeWidths(FadeWidthsInfo, glm::vec2(1.f), glm::vec2(0.f), glm::vec2(100.f))
, _fadeDistances(FadeDistancesInfo, glm::vec2(1.f), glm::vec2(0.f), glm::vec2(100.f))
, _fadeUnitOption(
FadeUnitOptionInfo,
properties::OptionProperty::DisplayType::Dropdown
)
, _orientationOption(
OrientationOptionInfo,
properties::OptionProperty::DisplayType::Dropdown
)
, _fadeUnitOption(FadeUnitOptionInfo)
, _orientationOption(OrientationOptionInfo)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
addProperty(Fadeable::_opacity);
_blendMode.addOptions({
{ BlendMode::Normal, "Normal" },
{ BlendMode::Additive, "Additive"}
{ BlendMode::Additive, "Additive" }
});
_blendMode.onChange([&]() {
_blendMode.onChange([this]() {
switch (_blendMode) {
case BlendMode::Normal:
setRenderBinFromOpacity();
@@ -274,8 +280,6 @@ RenderableLabel::RenderableLabel(const ghoul::Dictionary& dictionary)
case BlendMode::Additive:
setRenderBin(Renderable::RenderBin::PreDeferredTransparent);
break;
default:
throw ghoul::MissingCaseException();
}
});
@@ -302,7 +306,7 @@ RenderableLabel::RenderableLabel(const ghoul::Dictionary& dictionary)
addProperty(_color);
_fontSize = p.fontSize.value_or(_fontSize);
_fontSize.onChange([&]() {
_fontSize.onChange([this]() {
_font = global::fontManager->font(
"Mono",
_fontSize,
@@ -362,20 +366,18 @@ bool RenderableLabel::isReady() const {
}
void RenderableLabel::initialize() {
ZoneScoped
ZoneScoped;
setRenderBin(Renderable::RenderBin::PreDeferredTransparent);
}
void RenderableLabel::initializeGL() {
if (_font == nullptr) {
_font = global::fontManager->font(
"Mono",
_fontSize,
ghoul::fontrendering::FontManager::Outline::Yes,
ghoul::fontrendering::FontManager::LoadGlyphs::No
);
}
_font = global::fontManager->font(
"Mono",
_fontSize,
ghoul::fontrendering::FontManager::Outline::Yes,
ghoul::fontrendering::FontManager::LoadGlyphs::No
);
}
void RenderableLabel::deinitializeGL() {}
@@ -387,34 +389,34 @@ void RenderableLabel::render(const RenderData& data, RendererTasks&) {
float fadeInVariable = 1.f;
if (_enableFadingEffect) {
float distanceNodeToCamera = static_cast<float>(
const float distanceNodeToCamera = static_cast<float>(
glm::distance(data.camera.positionVec3(), data.modelTransform.translation)
);
fadeInVariable = computeFadeFactor(distanceNodeToCamera);
}
glm::dmat4 modelMatrix(1.0);
glm::dmat4 modelViewMatrix = data.camera.combinedViewMatrix() * modelMatrix;
glm::dmat4 projectionMatrix = glm::dmat4(data.camera.projectionMatrix());
const glm::dmat4 modelMatrix = glm::dmat4(1.0);
const glm::dmat4 modelViewProjectionTransform =
calcModelViewProjectionTransform(data, modelMatrix);
glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewMatrix;
glm::dvec3 cameraViewDirectionWorld = -data.camera.viewDirectionWorldSpace();
glm::dvec3 cameraUpDirectionWorld = data.camera.lookUpVectorWorldSpace();
const glm::dvec3 cameraViewDirectionWorld = -data.camera.viewDirectionWorldSpace();
const glm::dvec3 cameraUpDirectionWorld = data.camera.lookUpVectorWorldSpace();
glm::dvec3 orthoRight = glm::normalize(
glm::cross(cameraUpDirectionWorld, cameraViewDirectionWorld)
);
if (orthoRight == glm::dvec3(0.0)) {
glm::dvec3 otherVector(
const glm::dvec3 otherVector = glm::dvec3(
cameraUpDirectionWorld.y,
cameraUpDirectionWorld.x,
cameraUpDirectionWorld.z
);
orthoRight = glm::normalize(glm::cross(otherVector, cameraViewDirectionWorld));
}
glm::dvec3 orthoUp = glm::normalize(glm::cross(cameraViewDirectionWorld, orthoRight));
const glm::dvec3 orthoUp = glm::normalize(
glm::cross(cameraViewDirectionWorld, orthoRight)
);
renderLabels(data, modelViewProjectionMatrix, orthoRight, orthoUp, fadeInVariable);
renderLabels(data, modelViewProjectionTransform, orthoRight, orthoUp, fadeInVariable);
global::renderEngine->openglStateCache().resetBlendState();
global::renderEngine->openglStateCache().resetDepthState();
@@ -450,7 +452,7 @@ void RenderableLabel::renderLabels(const RenderData& data,
labelInfo.enableFalseDepth = false;
// We don't use spice rotation and scale
glm::vec3 transformedPos(
const glm::vec3 transformedPos = glm::vec3(
_transformationMatrix * glm::dvec4(data.modelTransform.translation, 1.0)
);
@@ -464,62 +466,62 @@ void RenderableLabel::renderLabels(const RenderData& data,
}
float RenderableLabel::computeFadeFactor(float distanceNodeToCamera) const {
float distanceUnit = unit(_fadeUnitOption);
const float distanceUnit = unit(_fadeUnitOption);
float x = distanceNodeToCamera;
float startX = _fadeDistances.value().x * distanceUnit;
float endX = _fadeDistances.value().y * distanceUnit;
const float x = distanceNodeToCamera;
const float startX = _fadeDistances.value().x * distanceUnit;
const float endX = _fadeDistances.value().y * distanceUnit;
// The distances over which the fading should happen
float fadingStartDistance = _fadeWidths.value().x * distanceUnit;
float fadingEndDistance = _fadeWidths.value().y * distanceUnit;
const float fadingStartDistance = _fadeWidths.value().x * distanceUnit;
const float fadingEndDistance = _fadeWidths.value().y * distanceUnit;
if (x <= startX) {
float f1 = 1.f - (startX - x) / fadingStartDistance;
const float f1 = 1.f - (startX - x) / fadingStartDistance;
return std::clamp(f1, 0.f, 1.f);
}
else if (x > startX && x < endX) {
return 1.f; // not faded
}
else { // x >= endX
float f2 = 1.f - (x - endX) / fadingEndDistance;
const float f2 = 1.f - (x - endX) / fadingEndDistance;
return std::clamp(f2, 0.f, 1.f);
}
}
float RenderableLabel::unit(int unit) const {
switch (static_cast<Unit>(unit)) {
case Meter: return 1.f;
case Kilometer: return 1e3f;
case Megameter: return 1e6f;
case Gigameter: return 1e9f;
case Meter: return 1.f;
case Kilometer: return 1e3f;
case Megameter: return 1e6f;
case Gigameter: return 1e9f;
case AstronomicalUnit: return 149597870700.f;
case Terameter: return 1e12f;
case Petameter: return 1e15f;
case Parsec: return static_cast<float>(PARSEC);
case KiloParsec: return static_cast<float>(1e3 * PARSEC);
case MegaParsec: return static_cast<float>(1e6 * PARSEC);
case GigaParsec: return static_cast<float>(1e9 * PARSEC);
case GigaLightyear: return static_cast<float>(306391534.73091 * PARSEC);
default: throw std::logic_error("Missing case label");
case Terameter: return 1e12f;
case Petameter: return 1e15f;
case Parsec: return static_cast<float>(PARSEC);
case KiloParsec: return static_cast<float>(1e3 * PARSEC);
case MegaParsec: return static_cast<float>(1e6 * PARSEC);
case GigaParsec: return static_cast<float>(1e9 * PARSEC);
case GigaLightyear: return static_cast<float>(306391534.73091 * PARSEC);
default: throw ghoul::MissingCaseException();
}
}
std::string_view RenderableLabel::toString(int unit) const {
switch (static_cast<Unit>(unit)) {
case Meter: return MeterUnit;
case Kilometer: return KilometerUnit;
case Megameter: return MegameterUnit;
case Gigameter: return GigameterUnit;
case Meter: return MeterUnit;
case Kilometer: return KilometerUnit;
case Megameter: return MegameterUnit;
case Gigameter: return GigameterUnit;
case AstronomicalUnit: return AstronomicalUnitUnit;
case Terameter: return TerameterUnit;
case Petameter: return PetameterUnit;
case Parsec: return ParsecUnit;
case KiloParsec: return KiloparsecUnit;
case MegaParsec: return MegaparsecUnit;
case GigaParsec: return GigaparsecUnit;
case GigaLightyear: return GigalightyearUnit;
default: throw std::logic_error("Missing case label");
case Terameter: return TerameterUnit;
case Petameter: return PetameterUnit;
case Parsec: return ParsecUnit;
case KiloParsec: return KiloparsecUnit;
case MegaParsec: return MegaparsecUnit;
case GigaParsec: return GigaparsecUnit;
case GigaLightyear: return GigalightyearUnit;
default: throw ghoul::MissingCaseException();
}
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,8 +27,8 @@
#include <openspace/rendering/renderable.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/ivec2property.h>
@@ -56,7 +56,7 @@ struct LinePoint;
class RenderableLabel : public Renderable {
public:
RenderableLabel(const ghoul::Dictionary& dictionary);
explicit RenderableLabel(const ghoul::Dictionary& dictionary);
void initialize() override;
void initializeGL() override;
@@ -73,6 +73,8 @@ public:
protected:
properties::OptionProperty _blendMode;
properties::StringProperty _text;
float unit(int unit) const;
std::string_view toString(int unit) const;
@@ -92,8 +94,6 @@ private:
properties::FloatProperty _size;
properties::IVec2Property _minMaxSize;
properties::StringProperty _text;
properties::BoolProperty _enableFadingEffect;
properties::Vec2Property _fadeWidths;
properties::Vec2Property _fadeDistances;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -26,14 +26,13 @@
#define __OPENSPACE_MODULE_BASE___RENDERABLEMODEL___H__
#include <openspace/rendering/renderable.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/matrix/dmat4property.h>
#include <openspace/properties/matrix/mat3property.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/util/distanceconversion.h>
#include <ghoul/misc/managedmemoryuniqueptr.h>
#include <ghoul/io/model/modelreader.h>
#include <ghoul/opengl/uniformcache.h>
@@ -56,7 +55,7 @@ namespace documentation { struct Documentation; }
class RenderableModel : public Renderable {
public:
RenderableModel(const ghoul::Dictionary& dictionary);
explicit RenderableModel(const ghoul::Dictionary& dictionary);
~RenderableModel() override = default;
void initialize() override;
@@ -79,13 +78,15 @@ private:
BounceInfinitely
};
std::filesystem::path _file;
std::unique_ptr<ghoul::modelgeometry::ModelGeometry> _geometry;
double _modelScale = 1.0;
bool _invertModelScale = false;
bool _forceRenderInvisible = false;
bool _notifyInvisibleDropped = true;
bool _modelHasAnimation = false;
std::string _animationStart;
AnimationMode _animationMode = AnimationMode::Once;
double _animationTimeScale = 1.0;
properties::BoolProperty _enableAnimation;
properties::FloatProperty _ambientIntensity;
@@ -93,21 +94,26 @@ private:
properties::FloatProperty _specularIntensity;
properties::BoolProperty _performShading;
properties::BoolProperty _disableFaceCulling;
properties::BoolProperty _enableFaceCulling;
properties::DMat4Property _modelTransform;
properties::Vec3Property _pivot;
properties::DoubleProperty _modelScale;
properties::Vec3Property _rotationVec;
properties::BoolProperty _disableDepthTest;
properties::BoolProperty _enableOpacityBlending;
properties::BoolProperty _enableDepthTest;
properties::OptionProperty _blendingFuncOption;
std::string _vertexShaderPath;
std::string _fragmentShaderPath;
std::filesystem::path _vertexShaderPath;
std::filesystem::path _fragmentShaderPath;
ghoul::opengl::ProgramObject* _program = nullptr;
UniformCache(opacity, nLightSources, lightDirectionsViewSpace, lightIntensities,
modelViewTransform, normalTransform, projectionTransform,
performShading, ambientIntensity, diffuseIntensity,
specularIntensity, opacityBlending) _uniformCache;
UniformCache(modelViewTransform, projectionTransform, normalTransform, meshTransform,
meshNormalTransform, ambientIntensity, diffuseIntensity,
specularIntensity, performShading, use_forced_color, has_texture_diffuse,
has_texture_normal, has_texture_specular, has_color_specular,
texture_diffuse, texture_normal, texture_specular, color_diffuse,
color_specular, opacity, nLightSources, lightDirectionsViewSpace,
lightIntensities, performManualDepthTest, gBufferDepthTexture, resolution
) _uniformCache;
std::vector<std::unique_ptr<LightSource>> _lightSources;
@@ -116,6 +122,20 @@ private:
std::vector<glm::vec3> _lightDirectionsViewSpaceBuffer;
properties::PropertyOwner _lightSourcePropertyOwner;
// Framebuffer and screen space quad
GLuint _framebuffer = 0;
GLuint _quadVao = 0;
GLuint _quadVbo = 0;
bool _shouldRenderTwice = false;
// Opacity program
ghoul::opengl::ProgramObject* _quadProgram = nullptr;
UniformCache(opacity, colorTexture, depthTexture, viewport,
resolution) _uniformOpacityCache;
// Store the original RenderBin
Renderable::RenderBin _originalRenderBin;
};
} // namespace openspace

View File

@@ -0,0 +1,529 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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/base/rendering/renderablenodearrow.h>
#include <modules/base/basemodule.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/query/query.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/translation.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/opengl/programobject.h>
#include <glm/gtx/projection.hpp>
#include <glm/gtx/transform.hpp>
namespace {
constexpr std::string_view _loggerCat = "RenderableNodeArrow";
constexpr openspace::properties::Property::PropertyInfo StartNodeInfo = {
"StartNode",
"Start Node",
"The identifier of the node the arrow starts from.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo EndNodeInfo = {
"EndNode",
"End Node",
"The identifier of the node the arrow should point towards.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"The RGB color for the arrow.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
"Segments",
"Number of Segments",
"The number of segments that the shapes of the arrow are divided into. A higher "
"number leads to a higher resolution and smoother shape.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo InvertInfo = {
"Invert",
"Invert Direction",
"If true, the arrow direction is inverted so that it points to the start node "
"instead of the end node.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ArrowHeadSizeInfo = {
"ArrowHeadSize",
"Arrow Head Size",
"The length of the arrow head, given in relative value of the entire length of "
"the arrow. For example, 0.1 makes the arrow head length be 10% of the full "
"arrow length.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ArrowHeadWidthInfo = {
"ArrowHeadWidthFactor",
"Arrow Head Width Factor",
"A factor that is multiplied with the width, or the arrow itself, to determine "
"the width of the base of the arrow head.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OffsetDistanceInfo = {
"Offset",
"Offset Distance",
"The distance from the center of the start node where the arrow starts. "
"If 'UseRelativeOffset' is true, the value should be given as a factor to "
"multiply with the bounding sphere of the node. Otherwise, the value is "
"specified in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RelativeOffsetInfo = {
"UseRelativeOffset",
"Use Relative Offset Distance",
"Decides whether to use relative distances for the offset distance. This means "
"that the offset distance will be computed as the provided 'Offset' value times "
"the bounding sphere of the start node. If false, meters is used.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo LengthInfo = {
"Length",
"Length",
"The length of the arrow, given either in meters or as a factor to be "
"multiplied with the bounding sphere of the start node (if "
"'UseRelativeLength' is true).",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RelativeLengthInfo = {
"UseRelativeLength",
"Use Relative Length",
"Decides whether to use relative size for the length of the arrow. This means "
"that the arrow length will be computed as the provided 'Length' value times "
"the bounding sphere of the start node. If false, meters is used.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo WidthInfo = {
"Width",
"Width",
"The width of the arrow, in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo AmbientIntensityInfo = {
"AmbientIntensity",
"Ambient Intensity",
"A multiplier for ambient lighting for the shading of the arrow.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DiffuseIntensityInfo = {
"DiffuseIntensity",
"Diffuse Intensity",
"A multiplier for diffuse lighting for the shading of the arrow.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SpecularIntensityInfo = {
"SpecularIntensity",
"Specular Intensity",
"A multiplier for specular lighting for the shading of the arrow.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShadingEnabledInfo = {
"PerformShading",
"Perform Shading",
"Determines whether shading should be applied to the arrow model.",
openspace::properties::Property::Visibility::User
};
void updateDistanceBasedOnRelativeValues(const std::string& nodeName,
bool useRelative,
openspace::properties::FloatProperty& prop)
{
using namespace::openspace;
SceneGraphNode* startNode = sceneGraphNode(nodeName);
if (!startNode) {
LERROR(std::format("Could not find start node '{}'", nodeName));
return;
}
const double boundingSphere = startNode->boundingSphere();
if (!useRelative) {
// Recompute distance (previous value was relative)
prop = static_cast<float>(prop * boundingSphere);
prop.setExponent(11.f);
prop.setMaxValue(1e20f);
}
else {
// Recompute distance (previous value was in meters)
if (boundingSphere < std::numeric_limits<double>::epsilon()) {
LERROR(std::format(
"Start node '{}' has invalid bounding sphere", nodeName
));
return;
}
prop = static_cast<float>(prop / boundingSphere);
prop.setExponent(3.f);
prop.setMaxValue(1000.f);
}
}
// A RenderableNodeArrow can be used to create a 3D arrow pointing in the direction
// of one scene graph node to another.
//
// The arrow will be placed at the `StartNode` at a distance of the provided
// `Offset` value. Per default, the `Length` and `Offset` of the arrow is specified
// in meters, but they may also be specified as a multiplier of the bounding sphere
// of the `StartNode`. The look of the arrow can be customized to change the width
// and length of both the arrow body and head.
struct [[codegen::Dictionary(RenderableNodeArrow)]] Parameters {
// [[codegen::verbatim(StartNodeInfo.description)]]
std::string startNode [[codegen::identifier()]];
// [[codegen::verbatim(EndNodeInfo.description)]]
std::string endNode [[codegen::identifier()]];
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// [[codegen::verbatim(SegmentsInfo.description)]]
std::optional<int> segments [[codegen::greaterequal(3)]];
// [[codegen::verbatim(InvertInfo.description)]]
std::optional<bool> invert;
// [[codegen::verbatim(ArrowHeadSizeInfo.description)]]
std::optional<float> arrowHeadSize [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(ArrowHeadWidthInfo.description)]]
std::optional<float> arrowHeadWidthFactor [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(OffsetDistanceInfo.description)]]
std::optional<float> offset;
// [[codegen::verbatim(RelativeOffsetInfo.description)]]
std::optional<bool> useRelativeOffset;
// [[codegen::verbatim(LengthInfo.description)]]
std::optional<float> length [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(RelativeLengthInfo.description)]]
std::optional<bool> useRelativeLength;
// [[codegen::verbatim(WidthInfo.description)]]
std::optional<float> width [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(ShadingEnabledInfo.description)]]
std::optional<bool> performShading;
// [[codegen::verbatim(AmbientIntensityInfo.description)]]
std::optional<float> ambientIntensity [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(DiffuseIntensityInfo.description)]]
std::optional<float> diffuseIntensity [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(SpecularIntensityInfo.description)]]
std::optional<float> specularIntensity [[codegen::greaterequal(0.f)]];
};
#include "renderablenodearrow_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation RenderableNodeArrow::Documentation() {
return codegen::doc<Parameters>("base_renderable_renderablenodearrow");
}
RenderableNodeArrow::Shading::Shading()
: properties::PropertyOwner({ "Shading" })
, enabled(ShadingEnabledInfo, true)
, ambientIntensity(AmbientIntensityInfo, 0.2f, 0.f, 1.f)
, diffuseIntensity(DiffuseIntensityInfo, 0.7f, 0.f, 1.f)
, specularIntensity(SpecularIntensityInfo, 0.f, 0.f, 1.f)
{
addProperty(enabled);
addProperty(ambientIntensity);
addProperty(diffuseIntensity);
addProperty(specularIntensity);
}
RenderableNodeArrow::RenderableNodeArrow(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _start(StartNodeInfo)
, _end(EndNodeInfo)
, _color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, _segments(SegmentsInfo, 10, 3, 100)
, _invertArrowDirection(InvertInfo, false)
, _arrowHeadSize(ArrowHeadSizeInfo, 0.1f, 0.f, 1.f)
, _arrowHeadWidthFactor(ArrowHeadWidthInfo, 2.f, 1.f, 100.f)
, _offsetDistance(OffsetDistanceInfo, 0.f, 0.f, 1e20f)
, _useRelativeOffset(RelativeOffsetInfo, false)
, _length(LengthInfo, 100.f, 0.f, 1e20f)
, _useRelativeLength(RelativeLengthInfo, false)
, _width(WidthInfo, 10.f, 0.f, 1e11f)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_shading.enabled = p.performShading.value_or(_shading.enabled);
_shading.ambientIntensity = p.ambientIntensity.value_or(_shading.ambientIntensity);
_shading.diffuseIntensity = p.diffuseIntensity.value_or(_shading.diffuseIntensity);
_shading.specularIntensity = p.specularIntensity.value_or(_shading.specularIntensity);
addPropertySubOwner(_shading);
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_color);
_start = p.startNode;
addProperty(_start);
_end = p.endNode;
addProperty(_end);
_segments = p.segments.value_or(_segments);
addProperty(_segments);
_invertArrowDirection = p.invert.value_or(_invertArrowDirection);
addProperty(_invertArrowDirection);
_arrowHeadSize = p.arrowHeadSize.value_or(_arrowHeadSize);
addProperty(_arrowHeadSize);
_arrowHeadWidthFactor = p.arrowHeadWidthFactor.value_or(_arrowHeadWidthFactor);
addProperty(_arrowHeadWidthFactor);
_width = p.width.value_or(_width);
_width.setExponent(10.f);
addProperty(_width);
_useRelativeLength.onChange([this]() {
updateDistanceBasedOnRelativeValues(_start, _useRelativeLength, _length);
});
_useRelativeLength = p.useRelativeLength.value_or(_useRelativeLength);
_length = p.length.value_or(_length);
if (!_useRelativeLength) {
_length.setExponent(11.f);
}
addProperty(_length);
_useRelativeOffset.onChange([this]() {
updateDistanceBasedOnRelativeValues(_start, _useRelativeOffset, _offsetDistance);
});
_useRelativeOffset = p.useRelativeOffset.value_or(_useRelativeOffset);
_offsetDistance = p.offset.value_or(_offsetDistance);
if (!_useRelativeOffset) {
_offsetDistance.setExponent(11.f);
}
addProperty(_offsetDistance);
addProperty(_useRelativeLength);
addProperty(_useRelativeOffset);
}
void RenderableNodeArrow::initializeGL() {
_shaderProgram = BaseModule::ProgramObjectManager.request(
"NodeDirectionLineProgram",
[]() -> std::unique_ptr<ghoul::opengl::ProgramObject> {
return global::renderEngine->buildRenderProgram(
"NodeDirectionLineProgram",
absPath("${MODULE_BASE}/shaders/arrow_vs.glsl"),
absPath("${MODULE_BASE}/shaders/arrow_fs.glsl")
);
}
);
}
void RenderableNodeArrow::deinitializeGL() {
BaseModule::ProgramObjectManager.release(
"NodeDirectionLineProgram",
[](ghoul::opengl::ProgramObject* p) {
global::renderEngine->removeRenderProgram(p);
}
);
_shaderProgram = nullptr;
}
bool RenderableNodeArrow::isReady() const {
return _shaderProgram != nullptr;
}
void RenderableNodeArrow::updateShapeTransforms(const RenderData& data) {
SceneGraphNode* startNode = sceneGraphNode(_start);
SceneGraphNode* endNode = sceneGraphNode(_end);
if (!startNode) {
LERROR(std::format("Could not find start node '{}'", _start.value()));
return;
}
if (!endNode) {
LERROR(std::format("Could not find end node '{}'", _end.value()));
return;
}
const double boundingSphere = startNode->boundingSphere();
const bool hasNoBoundingSphere =
boundingSphere < std::numeric_limits<double>::epsilon();
if (hasNoBoundingSphere && (_useRelativeLength || _useRelativeOffset)) {
LERROR(std::format(
"Node '{}' has no valid bounding sphere. Can not use relative values",
_end.value()
));
return;
}
double offset = static_cast<double>(_offsetDistance);
if (_useRelativeOffset) {
offset *= boundingSphere;
}
double length = static_cast<double>(_length);
if (_useRelativeLength) {
length *= boundingSphere;
}
// Take additional transformation scale into account
const glm::dmat4 s = glm::scale(
glm::dmat4(1.0),
glm::dvec3(data.modelTransform.scale)
);
// Update the position based on the arrowDirection of the nodes
const glm::dvec3 startNodePos = startNode->worldPosition();
const glm::dvec3 endNodePos = endNode->worldPosition();
glm::dvec3 arrowDirection = glm::normalize(endNodePos - startNodePos);
glm::dvec3 startPos = glm::dvec3(startNodePos + offset * arrowDirection);
glm::dvec3 endPos = glm::dvec3(startPos + length * arrowDirection);
if (_invertArrowDirection) {
std::swap(startPos, endPos);
arrowDirection *= -1.0;
}
const double coneLength = _arrowHeadSize * length;
const double cylinderLength = length - coneLength;
const double arrowHeadWidth = _width * _arrowHeadWidthFactor;
// Create transformation matrices to reshape to size and position
_cylinderTranslation = glm::translate(glm::dmat4(1.0), startPos);
const glm::dvec3 cylinderScale = glm::dvec3(
s * glm::dvec4(_width, _width, cylinderLength, 0.0)
);
_cylinderScale = glm::scale(glm::dmat4(1.0), cylinderScale);
// Adapt arrow head start to scaled size
const glm::dvec3 arrowHeadStartPos = startPos + cylinderScale.z * arrowDirection;
_coneTranslation = glm::translate(glm::dmat4(1.0), arrowHeadStartPos);
const glm::dvec3 coneScale = glm::dvec3(arrowHeadWidth, arrowHeadWidth, coneLength);
_coneScale = s * glm::scale(glm::dmat4(1.0), coneScale);
// Rotation to point at the end node
const glm::quat rotQuat = glm::rotation(glm::dvec3(0.0, 0.0, 1.0), arrowDirection);
_pointDirectionRotation = glm::dmat4(glm::toMat4(rotQuat));
setBoundingSphere(length + offset);
}
void RenderableNodeArrow::render(const RenderData& data, RendererTasks&) {
updateShapeTransforms(data);
// Cylinder transforms
glm::dmat4 modelTransform =
_cylinderTranslation * _pointDirectionRotation * _cylinderScale;
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform));
_shaderProgram->activate();
_shaderProgram->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_shaderProgram->setUniform("projectionTransform", data.camera.projectionMatrix());
_shaderProgram->setUniform("normalTransform", glm::mat3(normalTransform));
_shaderProgram->setUniform("color", _color);
_shaderProgram->setUniform("opacity", opacity());
_shaderProgram->setUniform("ambientIntensity", _shading.ambientIntensity);
_shaderProgram->setUniform("diffuseIntensity", _shading.diffuseIntensity);
_shaderProgram->setUniform("specularIntensity", _shading.specularIntensity);
_shaderProgram->setUniform("performShading", _shading.enabled);
// Change GL state:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnablei(GL_BLEND, 0);
// Draw cylinder
glBindVertexArray(rendering::helper::vertexObjects.cylinder.vao);
glDrawElements(
GL_TRIANGLES,
rendering::helper::vertexObjects.cylinder.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
// Update transforms and render cone
modelTransform = _coneTranslation * _pointDirectionRotation * _coneScale;
modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
normalTransform = glm::transpose(glm::inverse(modelViewTransform));
_shaderProgram->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_shaderProgram->setUniform("normalTransform", glm::mat3(normalTransform));
glBindVertexArray(rendering::helper::vertexObjects.cone.vao);
glDrawElements(
GL_TRIANGLES,
rendering::helper::vertexObjects.cone.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
// Restore GL State
glBindVertexArray(0);
global::renderEngine->openglStateCache().resetBlendState();
_shaderProgram->deactivate();
}
} // namespace openspace

View File

@@ -0,0 +1,103 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* 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_BASE___RENDERABLENODEARROW___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLENODEARROW___H__
#include <openspace/rendering/renderable.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/glm.h>
namespace ghoul::opengl { class ProgramObject; }
namespace openspace {
namespace documentation { struct Documentation; }
class Translation;
/**
* Generates an arrow shape that points from the start node to the end node.
*/
class RenderableNodeArrow : public Renderable {
public:
explicit RenderableNodeArrow(const ghoul::Dictionary& dictionary);
~RenderableNodeArrow() override = default;
void initializeGL() override;
void deinitializeGL() override;
bool isReady() const override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
static documentation::Documentation Documentation();
private:
struct Shading : properties::PropertyOwner {
Shading();
properties::BoolProperty enabled;
properties::FloatProperty ambientIntensity;
properties::FloatProperty diffuseIntensity;
properties::FloatProperty specularIntensity;
};
void updateShapeTransforms(const RenderData& data);
Shading _shading;
ghoul::opengl::ProgramObject* _shaderProgram = nullptr;
properties::StringProperty _start;
properties::StringProperty _end;
properties::Vec3Property _color;
properties::UIntProperty _segments;
properties::BoolProperty _invertArrowDirection;
properties::FloatProperty _arrowHeadSize;
properties::FloatProperty _arrowHeadWidthFactor;
properties::FloatProperty _offsetDistance;
properties::BoolProperty _useRelativeOffset;
properties::FloatProperty _length;
properties::BoolProperty _useRelativeLength;
properties::FloatProperty _width;
glm::dmat4 _cylinderTranslation = glm::dmat4(1.0);
glm::dmat4 _cylinderScale = glm::dmat4(1.0);
glm::dmat4 _coneTranslation = glm::dmat4(1.0);
glm::dmat4 _coneScale = glm::dmat4(1.0);
glm::dmat4 _pointDirectionRotation = glm::dmat4(1.0);
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLENODEARROW___H__

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,30 +39,63 @@
#include <ghoul/opengl/programobject.h>
namespace {
constexpr std::string_view _loggerCat = "RenderableNodeLine";
constexpr openspace::properties::Property::PropertyInfo StartNodeInfo = {
"StartNode",
"Start Node",
"The identifier of the node the line starts from. "
"Defaults to 'Root' if not specified. "
"The identifier of the node the line starts from. Defaults to 'Root' if not "
"specified.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EndNodeInfo = {
"EndNode",
"End Node",
"The identifier of the node the line ends at. "
"Defaults to 'Root' if not specified. "
"The identifier of the node the line ends at. Defaults to 'Root' if not "
"specified.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LineColorInfo = {
"Color",
"Color",
"This value determines the RGB color for the line"
"The RGB color for the line.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"This value specifies the line width"
"The width of the line. The larger number, the thicker the line.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo StartOffsetInfo = {
"StartOffset",
"Offset to Start Node",
"A distance from the start node at which the rendered line should begin. "
"By default it takes a value in meters, but if 'UseRelativeOffsets' is set "
"to true it is read as a multiplier times the bounding sphere of the node.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo EndOffsetInfo = {
"EndOffset",
"Offset to End Node",
"A distance to the end node at which the rendered line should end. "
"By default it takes a value in meters, but if 'UseRelativeOffsets' is set "
"to true it is read as a multiplier times the bounding sphere of the node.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RelativeOffsetsInfo = {
"UseRelativeOffsets",
"Use Relative Offsets",
"If true, the offset values are interpreted as relative values to be multiplied "
"with the bounding sphere of the start/end node. If false, the value is "
"interpreted as a distance in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
// Returns a position that is relative to the current anchor node. This is a method to
@@ -77,22 +110,38 @@ namespace {
if (nav.anchorNode()) {
anchorNodePos = nav.anchorNode()->worldPosition();
}
glm::dvec3 diffPos = worldPos - anchorNodePos;
const glm::dvec3 diffPos = worldPos - anchorNodePos;
return diffPos;
}
// This `Renderable` connects two scene graph nodes by drawing a line between them.
// The line will update dynamically if the position of the nodes change.
//
// One use case for the `RenderableNodeLine` is to visualize the distance between two
// objects. For this, a [RenderableDistanceLabel](#base_renderable_distancelabel) can
// also be added to show the distance as a number. That renderable is designed to show
// the distance between the start and end node for a given `RenderableNodeLine`.
struct [[codegen::Dictionary(RenderableNodeLine)]] Parameters {
// [[codegen::verbatim(StartNodeInfo.description)]]
std::optional<std::string> startNode;
std::optional<std::string> startNode [[codegen::identifier()]];
// [[codegen::verbatim(EndNodeInfo.description)]]
std::optional<std::string> endNode;
std::optional<std::string> endNode [[codegen::identifier()]];
// [[codegen::verbatim(LineColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// [[codegen::verbatim(LineWidthInfo.description)]]
std::optional<float> lineWidth;
// [[codegen::verbatim(StartOffsetInfo.description)]]
std::optional<float> startOffset;
// [[codegen::verbatim(EndOffsetInfo.description)]]
std::optional<float> endOffset;
// [[codegen::verbatim(RelativeOffsetsInfo.description)]]
std::optional<bool> useRelativeOffsets;
};
#include "renderablenodeline_codegen.cpp"
} // namespace
@@ -100,7 +149,7 @@ namespace {
namespace openspace {
documentation::Documentation RenderableNodeLine::Documentation() {
return codegen::doc<Parameters>("base_renderable_renderablenodeline");
return codegen::doc<Parameters>("base_renderable_nodeline");
}
RenderableNodeLine::RenderableNodeLine(const ghoul::Dictionary& dictionary)
@@ -109,14 +158,13 @@ RenderableNodeLine::RenderableNodeLine(const ghoul::Dictionary& dictionary)
, _end(EndNodeInfo, "Root")
, _lineColor(LineColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, _lineWidth(LineWidthInfo, 2.f, 1.f, 20.f)
, _startOffset(StartOffsetInfo, 0.f, 0.f, 1e13f)
, _endOffset(EndOffsetInfo, 0.f, 0.f, 1e13f)
, _useRelativeOffsets(RelativeOffsetsInfo, true)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_start = p.startNode.value_or(_start);
addProperty(_start);
_end = p.endNode.value_or(_end);
addProperty(_end);
addProperty(Fadeable::_opacity);
_lineColor = p.color.value_or(_lineColor);
_lineColor.setViewOption(properties::Property::ViewOptions::Color);
@@ -125,7 +173,82 @@ RenderableNodeLine::RenderableNodeLine(const ghoul::Dictionary& dictionary)
_lineWidth = p.lineWidth.value_or(_lineWidth);
addProperty(_lineWidth);
addProperty(_opacity);
_start = p.startNode.value_or(_start);
addProperty(_start);
_end = p.endNode.value_or(_end);
addProperty(_end);
addProperty(_startOffset);
_startOffset.setExponent(7.f);
_startOffset.onChange([&] {
if (_useRelativeOffsets) {
SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(_start);
if (!node || node->boundingSphere() > 0.0) {
return;
}
LWARNING(std::format(
"Setting StartOffset for node line '{}': Trying to use relative offsets "
"for start node '{}' that has no bounding sphere. This will result in no "
"offset. Use direct values by setting UseRelativeOffsets to false",
parent()->identifier(), _start.value()
));
}
});
addProperty(_endOffset);
_endOffset.setExponent(7.f);
_endOffset.onChange([&] {
if (_useRelativeOffsets) {
SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(_end);
if (!node || node->boundingSphere() > 0.0) {
return;
}
LWARNING(std::format(
"Setting EndOffset for node line '{}': Trying to use relative offsets "
"for end node '{}' that has no bounding sphere. This will result in no "
"offset. Use direct values by setting UseRelativeOffsets to false",
parent()->identifier(), _end.value()
));
}
});
addProperty(_useRelativeOffsets);
_useRelativeOffsets.onChange([this]() {
SceneGraphNode* startNode = global::renderEngine->scene()->sceneGraphNode(_start);
SceneGraphNode* endNode = global::renderEngine->scene()->sceneGraphNode(_end);
if (!startNode) {
LERROR(std::format(
"Error when recomputing node line offsets for scene graph node '{}'. "
"Could not find start node '{}'", parent()->identifier(), _start.value()
));
return;
}
if (!endNode) {
LERROR(std::format(
"Error when recomputing node line offsets for scene graph node '{}'. "
"Could not find end node '{}'", parent()->identifier(), _end.value()
));
return;
}
if (_useRelativeOffsets) {
// Recompute previous offsets to relative values
const double startBs = startNode->boundingSphere();
const double endBs = endNode->boundingSphere();
_startOffset =
static_cast<float>(startBs > 0.0 ? _startOffset / startBs : 0.0);
_endOffset =
static_cast<float>(endBs > 0.0 ? _endOffset / startBs : 0.0);
}
else {
// Recompute relative values to meters
_startOffset = static_cast<float>(_startOffset * startNode->boundingSphere());
_endOffset = static_cast<float>(_endOffset * endNode->boundingSphere());
}
});
}
double RenderableNodeLine::distance() const {
@@ -156,12 +279,14 @@ void RenderableNodeLine::initializeGL() {
glGenVertexArrays(1, &_vaoId);
glGenBuffers(1, &_vBufferId);
bindGL();
glBindVertexArray(_vaoId);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferId);
glVertexAttribPointer(_locVertex, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
glEnableVertexAttribArray(_locVertex);
unbindGL();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void RenderableNodeLine::deinitializeGL() {
@@ -186,29 +311,17 @@ bool RenderableNodeLine::isReady() const {
return ready;
}
void RenderableNodeLine::unbindGL() {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void RenderableNodeLine::bindGL() {
glBindVertexArray(_vaoId);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferId);
}
void RenderableNodeLine::updateVertexData() {
SceneGraphNode* startNode = global::renderEngine->scene()->sceneGraphNode(_start);
SceneGraphNode* endNode = global::renderEngine->scene()->sceneGraphNode(_end);
if (!startNode || !endNode) {
LERRORC(
"RenderableNodeLine",
fmt::format(
"Could not find starting '{}' or ending '{}'",
_start.value(), _end.value()
)
);
if (!startNode) {
LERROR(std::format("Could not find start node '{}'", _start.value()));
return;
}
if (!endNode) {
LERROR(std::format("Could not find end node '{}'", _end.value()));
return;
}
@@ -218,15 +331,29 @@ void RenderableNodeLine::updateVertexData() {
_startPos = coordinatePosFromAnchorNode(startNode->worldPosition());
_endPos = coordinatePosFromAnchorNode(endNode->worldPosition());
_vertexArray.push_back(static_cast<float>(_startPos.x));
_vertexArray.push_back(static_cast<float>(_startPos.y));
_vertexArray.push_back(static_cast<float>(_startPos.z));
// Handle relative values
double startOffset = static_cast<double>(_startOffset);
double endOffset = static_cast<double>(_endOffset);
if (_useRelativeOffsets) {
startOffset *= startNode->boundingSphere();
endOffset *= endNode->boundingSphere();
}
_vertexArray.push_back(static_cast<float>(_endPos.x));
_vertexArray.push_back(static_cast<float>(_endPos.y));
_vertexArray.push_back(static_cast<float>(_endPos.z));
// Compute line positions
const glm::dvec3 dir = glm::normalize(_endPos - _startPos);
const glm::dvec3 startPos = _startPos + startOffset * dir;
const glm::dvec3 endPos = _endPos - endOffset * dir;
bindGL();
_vertexArray.push_back(static_cast<float>(startPos.x));
_vertexArray.push_back(static_cast<float>(startPos.y));
_vertexArray.push_back(static_cast<float>(startPos.z));
_vertexArray.push_back(static_cast<float>(endPos.x));
_vertexArray.push_back(static_cast<float>(endPos.y));
_vertexArray.push_back(static_cast<float>(endPos.z));
glBindVertexArray(_vaoId);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferId);
glBufferData(
GL_ARRAY_BUFFER,
_vertexArray.size() * sizeof(float),
@@ -237,7 +364,8 @@ void RenderableNodeLine::updateVertexData() {
// update vertex attributes
glVertexAttribPointer(_locVertex, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
unbindGL();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void RenderableNodeLine::update(const UpdateData&) {
@@ -256,13 +384,9 @@ void RenderableNodeLine::render(const RenderData& data, RendererTasks&) {
);
}
const glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) *
glm::dmat4(data.modelTransform.rotation) *
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() *
modelTransform * anchorTranslation;
const glm::dmat4 modelTransform = calcModelTransform(data);
const glm::dmat4 modelViewTransform =
calcModelViewTransform(data, modelTransform) * anchorTranslation;
_program->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_program->setUniform("projectionTransform", data.camera.projectionMatrix());
@@ -275,11 +399,13 @@ void RenderableNodeLine::render(const RenderData& data, RendererTasks&) {
glLineWidth(_lineWidth);
// Bind and draw
bindGL();
glBindVertexArray(_vaoId);
glBindBuffer(GL_ARRAY_BUFFER, _vBufferId);
glDrawArrays(GL_LINES, 0, 2);
// Restore GL State
unbindGL();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
_program->deactivate();
global::renderEngine->openglStateCache().resetBlendState();
global::renderEngine->openglStateCache().resetLineState();

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -45,7 +45,7 @@ class Translation;
*/
class RenderableNodeLine : public Renderable {
public:
RenderableNodeLine(const ghoul::Dictionary& dictionary);
explicit RenderableNodeLine(const ghoul::Dictionary& dictionary);
~RenderableNodeLine() override = default;
static documentation::Documentation Documentation();
@@ -65,10 +65,7 @@ private:
void update(const UpdateData& data) override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
void unbindGL();
void bindGL();
ghoul::opengl::ProgramObject* _program;
ghoul::opengl::ProgramObject* _program = nullptr;
/// The vertex attribute location for position
/// must correlate to layout location in vertex shader
const GLuint _locVertex = 0;
@@ -83,6 +80,9 @@ private:
properties::StringProperty _end;
properties::Vec3Property _lineColor;
properties::FloatProperty _lineWidth;
properties::FloatProperty _startOffset;
properties::FloatProperty _endOffset;
properties::BoolProperty _useRelativeOffsets;
};
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -29,18 +29,16 @@
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/io/texture/texturereader.h>
#include <ghoul/misc/defer.h>
#include <ghoul/misc/profiling.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/opengl/texture.h>
#include <ghoul/opengl/textureunit.h>
#include <ghoul/glm.h>
#include <glm/gtx/string_cast.hpp>
#include <optional>
#include <variant>
namespace {
enum BlendMode {
@@ -50,39 +48,55 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo BillboardInfo = {
"Billboard",
"Billboard mode",
"This value specifies whether the plane is a billboard, which means that it is "
"always facing the camera. If this is false, it can be oriented using other "
"transformations"
"Billboard Mode",
"Specifies whether the plane should be a billboard, which means that it is "
"always facing the camera. If it is not, it can be oriented using other "
"transformations.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo MirrorBacksideInfo = {
"MirrorBackside",
"Mirror backside of image plane",
"If this value is set to false, the image plane will not be mirrored when "
"looking from the backside. This is usually desirable when the image shows "
"data at a specific location, but not if it is displaying text for example"
"Mirror Backside of Image Plane",
"If false, the image plane will not be mirrored when viewed from the backside. "
"This is usually desirable when the image shows data at a specific location, but "
"not if it is displaying text for example.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
"Size",
"Size (in meters)",
"This value specifies the size of the plane in meters"
"Size",
"The size of the plane in meters.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo AutoScaleInfo = {
"AutoScale",
"Auto Scale",
"Decides whether the plane should automatically adjust in size to match the "
"aspect ratio of the content. Otherwise it will remain in the given size."
};
constexpr openspace::properties::Property::PropertyInfo BlendModeInfo = {
"BlendMode",
"Blending Mode",
"This determines the blending mode that is applied to this plane"
"Determines the blending mode that is applied to this plane.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo MultiplyColorInfo = {
"MultiplyColor",
"Multiply Color",
"If set, the plane's texture is multiplied with this color. "
"Useful for applying a color grayscale images"
"An RGB color to multiply with the plane's texture. Useful for applying "
"a color to grayscale images.",
openspace::properties::Property::Visibility::User
};
// A `RenderablePlane` is a renderable that will shows some form of contents projected
// on a two-dimensional plane, which in turn is placed in three-dimensional space as
// any other `Renderable`. It is possible to specify the `Size` of the plane, whether
// it should always face the camera (`Billboard`), and other parameters shown below.
struct [[codegen::Dictionary(RenderablePlane)]] Parameters {
// [[codegen::verbatim(BillboardInfo.description)]]
std::optional<bool> billboard;
@@ -91,7 +105,10 @@ namespace {
std::optional<bool> mirrorBackside;
// [[codegen::verbatim(SizeInfo.description)]]
float size;
std::variant<float, glm::vec2> size;
// [[codegen::verbatim(AutoScaleInfo.description)]]
std::optional<bool> autoScale;
enum class [[codegen::map(BlendMode)]] BlendMode {
Normal,
@@ -113,61 +130,68 @@ documentation::Documentation RenderablePlane::Documentation() {
}
RenderablePlane::RenderablePlane(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _blendMode(BlendModeInfo, properties::OptionProperty::DisplayType::Dropdown)
: Renderable(dictionary, { .automaticallyUpdateRenderBin = false })
, _blendMode(BlendModeInfo)
, _billboard(BillboardInfo, false)
, _mirrorBackside(MirrorBacksideInfo, false)
, _size(SizeInfo, 10.f, 0.f, 1e25f)
, _size(SizeInfo, glm::vec2(10.f), glm::vec2(0.f), glm::vec2(1e25f))
, _autoScale(AutoScaleInfo, false)
, _multiplyColor(MultiplyColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
{
Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_opacity);
registerUpdateRenderBinFromOpacity();
_opacity.onChange([this]() {
if (_blendMode == static_cast<int>(BlendMode::Normal)) {
setRenderBinFromOpacity();
}
});
addProperty(Fadeable::_opacity);
_size = p.size;
_billboard = p.billboard.value_or(_billboard);
_mirrorBackside = p.mirrorBackside.value_or(_mirrorBackside);
if (std::holds_alternative<float>(p.size)) {
_size = glm::vec2(std::get<float>(p.size));
}
else {
_size = std::get<glm::vec2>(p.size);
}
_size.setExponent(15.f);
_size.onChange([this]() { _planeIsDirty = true; });
addProperty(_size);
_blendMode.addOptions({
{ static_cast<int>(BlendMode::Normal), "Normal" },
{ static_cast<int>(BlendMode::Additive), "Additive"}
{ static_cast<int>(BlendMode::Additive), "Additive" }
});
_blendMode.onChange([&]() {
switch (_blendMode) {
case static_cast<int>(BlendMode::Normal):
_blendMode.onChange([this]() {
const BlendMode m = static_cast<BlendMode>(_blendMode.value());
switch (m) {
case BlendMode::Normal:
setRenderBinFromOpacity();
break;
case static_cast<int>(BlendMode::Additive):
case BlendMode::Additive:
setRenderBin(Renderable::RenderBin::PreDeferredTransparent);
break;
default:
throw ghoul::MissingCaseException();
}
});
_opacity.onChange([&]() {
if (_blendMode == static_cast<int>(BlendMode::Normal)) {
setRenderBinFromOpacity();
}
});
if (p.blendMode.has_value()) {
_blendMode = codegen::map<BlendMode>(*p.blendMode);
}
addProperty(_blendMode);
_billboard = p.billboard.value_or(_billboard);
addProperty(_billboard);
_mirrorBackside = p.mirrorBackside.value_or(_mirrorBackside);
addProperty(_mirrorBackside);
_autoScale = p.autoScale.value_or(_autoScale);
addProperty(_autoScale);
_multiplyColor = p.multiplyColor.value_or(_multiplyColor);
_multiplyColor.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_billboard);
_size.setExponent(15.f);
addProperty(_size);
_size.onChange([this](){ _planeIsDirty = true; });
addProperty(_multiplyColor);
setBoundingSphere(_size);
setBoundingSphere(glm::compMax(_size.value()));
}
bool RenderablePlane::isReady() const {
@@ -175,7 +199,7 @@ bool RenderablePlane::isReady() const {
}
void RenderablePlane::initializeGL() {
ZoneScoped
ZoneScoped;
glGenVertexArrays(1, &_quad); // generate array
glGenBuffers(1, &_vertexPositionBuffer); // generate buffer
@@ -191,10 +215,12 @@ void RenderablePlane::initializeGL() {
);
}
);
ghoul::opengl::updateUniformLocations(*_shader, _uniformCache);
}
void RenderablePlane::deinitializeGL() {
ZoneScoped
ZoneScoped;
glDeleteVertexArrays(1, &_quad);
_quad = 0;
@@ -212,24 +238,24 @@ void RenderablePlane::deinitializeGL() {
}
void RenderablePlane::render(const RenderData& data, RendererTasks&) {
ZoneScoped
ZoneScoped;
_shader->activate();
_shader->setUniform("opacity", opacity());
_shader->setUniform(_uniformCache.opacity, opacity());
_shader->setUniform("mirrorBackside", _mirrorBackside);
_shader->setUniform(_uniformCache.mirrorBackside, _mirrorBackside);
glm::dvec3 objectPositionWorld = glm::dvec3(
const glm::dvec3 objPosWorld = glm::dvec3(
glm::translate(
glm::dmat4(1.0),
data.modelTransform.translation) * glm::dvec4(0.0, 0.0, 0.0, 1.0)
);
glm::dvec3 normal = glm::normalize(data.camera.positionVec3() - objectPositionWorld);
glm::dvec3 newRight = glm::normalize(
const glm::dvec3 normal = glm::normalize(data.camera.positionVec3() - objPosWorld);
const glm::dvec3 newRight = glm::normalize(
glm::cross(data.camera.lookUpVectorWorldSpace(), normal)
);
glm::dvec3 newUp = glm::cross(normal, newRight);
const glm::dvec3 newUp = glm::cross(normal, newRight);
glm::dmat4 cameraOrientedRotation = glm::dmat4(1.0);
cameraOrientedRotation[0] = glm::dvec4(newRight, 0.0);
@@ -240,30 +266,25 @@ void RenderablePlane::render(const RenderData& data, RendererTasks&) {
cameraOrientedRotation :
glm::dmat4(data.modelTransform.rotation);
const glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) *
rotationTransform *
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)) *
glm::dmat4(1.0);
const glm::dmat4 modelViewTransform =
data.camera.combinedViewMatrix() * modelTransform;
auto [modelTransform, modelViewTransform, modelViewProjectionTransform] =
calcAllTransforms(data, { .rotation = rotationTransform });
_shader->setUniform("modelViewProjectionTransform",
data.camera.projectionMatrix() * glm::mat4(modelViewTransform));
_shader->setUniform("modelViewTransform",
glm::mat4(data.camera.combinedViewMatrix() * glm::dmat4(modelViewTransform)));
_shader->setUniform(
_uniformCache.modelViewProjection,
glm::mat4(modelViewProjectionTransform)
);
_shader->setUniform(_uniformCache.modelViewTransform, glm::mat4(modelViewTransform));
ghoul::opengl::TextureUnit unit;
unit.activate();
bindTexture();
defer { unbindTexture(); };
_shader->setUniform("texture1", unit);
_shader->setUniform(_uniformCache.colorTexture, unit);
_shader->setUniform("multiplyColor", _multiplyColor);
_shader->setUniform(_uniformCache.multiplyColor, _multiplyColor);
bool additiveBlending = (_blendMode == static_cast<int>(BlendMode::Additive));
const bool additiveBlending = (_blendMode == static_cast<int>(BlendMode::Additive));
if (additiveBlending) {
glDepthMask(false);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
@@ -287,32 +308,34 @@ void RenderablePlane::bindTexture() {}
void RenderablePlane::unbindTexture() {}
void RenderablePlane::update(const UpdateData&) {
ZoneScoped
ZoneScoped;
if (_shader->isDirty()) {
if (_shader->isDirty()) [[unlikely]] {
_shader->rebuildFromFile();
ghoul::opengl::updateUniformLocations(*_shader, _uniformCache);
}
if (_planeIsDirty) {
if (_planeIsDirty) [[unlikely]] {
createPlane();
}
}
void RenderablePlane::createPlane() {
const GLfloat size = _size;
const GLfloat vertexData[] = {
// x y z w s t
-size, -size, 0.f, 0.f, 0.f, 0.f,
size, size, 0.f, 0.f, 1.f, 1.f,
-size, size, 0.f, 0.f, 0.f, 1.f,
-size, -size, 0.f, 0.f, 0.f, 0.f,
size, -size, 0.f, 0.f, 1.f, 0.f,
size, size, 0.f, 0.f, 1.f, 1.f,
const GLfloat sizeX = _size.value().x;
const GLfloat sizeY = _size.value().y;
const std::array<GLfloat, 36> vertexData = {
// x y z w s t
-sizeX, -sizeY, 0.f, 0.f, 0.f, 0.f,
sizeX, sizeY, 0.f, 0.f, 1.f, 1.f,
-sizeX, sizeY, 0.f, 0.f, 0.f, 1.f,
-sizeX, -sizeY, 0.f, 0.f, 0.f, 0.f,
sizeX, -sizeY, 0.f, 0.f, 1.f, 0.f,
sizeX, sizeY, 0.f, 0.f, 1.f, 1.f
};
glBindVertexArray(_quad);
glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, nullptr);

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -27,12 +27,11 @@
#include <openspace/rendering/renderable.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec3property.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/uniformcache.h>
namespace ghoul::filesystem { class File; }
@@ -52,7 +51,7 @@ struct LinePoint;
class RenderablePlane : public Renderable {
public:
RenderablePlane(const ghoul::Dictionary& dictionary);
explicit RenderablePlane(const ghoul::Dictionary& dictionary);
void initializeGL() override;
void deinitializeGL() override;
@@ -72,7 +71,8 @@ protected:
properties::OptionProperty _blendMode;
properties::BoolProperty _billboard;
properties::BoolProperty _mirrorBackside;
properties::FloatProperty _size;
properties::Vec2Property _size;
properties::BoolProperty _autoScale;
properties::Vec3Property _multiplyColor;
ghoul::opengl::ProgramObject* _shader = nullptr;
@@ -82,6 +82,9 @@ protected:
private:
bool _planeIsDirty = false;
UniformCache(modelViewProjection, modelViewTransform, colorTexture, opacity,
mirrorBackside, multiplyColor) _uniformCache;
};
} // namespace openspace

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -41,17 +41,19 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo TextureInfo = {
"Texture",
"Texture",
"This value specifies an image that is loaded from disk and is used as a texture "
"that is applied to this plane. This image has to be square"
"A path to an image file to use as a texture for the plane.",
openspace::properties::Property::Visibility::User
};
// A RenderablePlaneImageLocal creates a textured 3D plane, where the texture is
// provided by a local file on disk.
struct [[codegen::Dictionary(RenderablePlaneImageLocal)]] Parameters {
// [[codegen::verbatim(TextureInfo.description)]]
std::string texture;
// If this value is set to 'true', the image for this plane will not be loaded at
// If this value is set to true, the image for this plane will not be loaded at
// startup but rather when image is shown for the first time. Additionally, if the
// plane is hidden, the image will automatically be unloaded
// plane is hidden, the image will automatically be unloaded.
std::optional<bool> lazyLoading;
};
#include "renderableplaneimagelocal_codegen.cpp"
@@ -72,8 +74,6 @@ RenderablePlaneImageLocal::RenderablePlaneImageLocal(const ghoul::Dictionary& di
{
const Parameters p = codegen::bake<Parameters>(dictionary);
addProperty(_blendMode);
_texturePath = absPath(p.texture).string();
_textureFile = std::make_unique<ghoul::filesystem::File>(_texturePath.value());
@@ -93,10 +93,31 @@ RenderablePlaneImageLocal::RenderablePlaneImageLocal(const ghoul::Dictionary& di
}
});
}
}
bool RenderablePlaneImageLocal::isReady() const {
return RenderablePlane::isReady();
_autoScale.onChange([this]() {
if (!_autoScale) {
return;
}
// Shape the plane based on the aspect ration of the image
const glm::vec2 textureDim = glm::vec2(_texture->dimensions());
if (_textureDimensions != textureDim) {
const float aspectRatio = textureDim.x / textureDim.y;
const float planeAspectRatio = _size.value().x / _size.value().y;
if (std::abs(planeAspectRatio - aspectRatio) >
std::numeric_limits<float>::epsilon())
{
const glm::vec2 newSize =
aspectRatio > 0.f ?
glm::vec2(_size.value().x * aspectRatio, _size.value().y) :
glm::vec2(_size.value().x, _size.value().y * aspectRatio);
_size = newSize;
}
_textureDimensions = textureDim;
}
});
}
void RenderablePlaneImageLocal::initializeGL() {
@@ -119,36 +140,33 @@ void RenderablePlaneImageLocal::bindTexture() {
}
void RenderablePlaneImageLocal::update(const UpdateData& data) {
ZoneScoped
ZoneScoped;
RenderablePlane::update(data);
if (_textureIsDirty) {
if (_textureIsDirty) [[unlikely]] {
loadTexture();
_textureIsDirty = false;
}
}
void RenderablePlaneImageLocal::loadTexture() {
ZoneScoped
ZoneScoped;
if (!_texturePath.value().empty()) {
ghoul::opengl::Texture* t = _texture;
unsigned int hash = ghoul::hashCRC32File(_texturePath);
const unsigned int hash = ghoul::hashCRC32File(_texturePath);
_texture = BaseModule::TextureManager.request(
std::to_string(hash),
[path = _texturePath]() -> std::unique_ptr<ghoul::opengl::Texture> {
std::unique_ptr<ghoul::opengl::Texture> texture =
ghoul::io::TextureReader::ref().loadTexture(
absPath(path).string(),
2
);
ghoul::io::TextureReader::ref().loadTexture(absPath(path), 2);
LDEBUGC(
"RenderablePlaneImageLocal",
fmt::format("Loaded texture from {}", absPath(path))
std::format("Loaded texture from '{}'", absPath(path))
);
texture->uploadTexture();
texture->setFilter(ghoul::opengl::Texture::FilterMode::LinearMipMap);
@@ -162,6 +180,29 @@ void RenderablePlaneImageLocal::loadTexture() {
_textureFile = std::make_unique<ghoul::filesystem::File>(_texturePath.value());
_textureFile->setCallback([this]() { _textureIsDirty = true; });
if (!_autoScale) {
return;
}
// Shape the plane based on the aspect ration of the image
const glm::vec2 textureDim = glm::vec2(_texture->dimensions());
if (_textureDimensions != textureDim) {
const float aspectRatio = textureDim.x / textureDim.y;
const float planeAspectRatio = _size.value().x / _size.value().y;
if (std::abs(planeAspectRatio - aspectRatio) >
std::numeric_limits<float>::epsilon())
{
const glm::vec2 newSize =
aspectRatio > 0.f ?
glm::vec2(_size.value().x * aspectRatio, _size.value().y) :
glm::vec2(_size.value().x, _size.value().y * aspectRatio);
_size = newSize;
}
_textureDimensions = textureDim;
}
}
}

View File

@@ -2,7 +2,7 @@
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* Copyright (c) 2014-2025 *
* *
* 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 *
@@ -39,13 +39,11 @@ namespace documentation { struct Documentation; }
class RenderablePlaneImageLocal : public RenderablePlane {
public:
RenderablePlaneImageLocal(const ghoul::Dictionary& dictionary);
explicit RenderablePlaneImageLocal(const ghoul::Dictionary& dictionary);
void initializeGL() override;
void deinitializeGL() override;
bool isReady() const override;
void update(const UpdateData& data) override;
static documentation::Documentation Documentation();
@@ -58,6 +56,7 @@ private:
properties::StringProperty _texturePath;
ghoul::opengl::Texture* _texture = nullptr;
glm::vec2 _textureDimensions = glm::vec2(0.f);
std::unique_ptr<ghoul::filesystem::File> _textureFile;
bool _isLoadingLazily = false;

Some files were not shown because too many files have changed in this diff Show More