Files
OpenSpace/modules/globebrowsing/src/layer.cpp
2025-10-27 17:50:06 +01:00

548 lines
20 KiB
C++

/*****************************************************************************************
* *
* 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/globebrowsing/src/layer.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/scripting/scriptengine.h>
#include <modules/globebrowsing/src/layergroup.h>
#include <modules/globebrowsing/src/layermanager.h>
#include <modules/globebrowsing/src/tileindex.h>
#include <modules/globebrowsing/src/tiletextureinitdata.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/profiling.h>
namespace openspace::globebrowsing {
namespace {
constexpr std::string_view _loggerCat = "Layer";
constexpr std::string_view KeyIdentifier = "Identifier";
constexpr std::string_view KeyName = "Name";
constexpr std::string_view KeyDesc = "Description";
constexpr std::string_view KeyLayerGroupID = "LayerGroupID";
constexpr openspace::properties::Property::PropertyInfo TypeInfo = {
"Type",
"Type",
"The type of this Layer. This value is a read-only property and thus cannot be "
"changed.",
openspace::properties::Property::Visibility::Developer
};
constexpr openspace::properties::Property::PropertyInfo BlendModeInfo = {
"BlendMode",
"Blend mode",
"This value specifies the blend mode that is applied to this layer. The blend "
"mode determines how this layer is added to the underlying layers beneath.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
"Enabled",
"Enabled",
"If this value is enabled, the layer will be used for the final composition of "
"the planet. If this value is disabled, the layer will be ignored in the "
"composition.",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ResetInfo = {
"Reset",
"Reset",
"If this value is triggered, this layer will be reset. This will delete the "
"local cache for this layer and will trigger a fresh load of all tiles.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RemoveInfo = {
"Remove",
"Remove",
"If this value is triggered, a script will be executed that will remove this "
"layer before the next frame.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"If the 'Type' of this layer is a solid color, this value determines what this "
"solid color is.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ZIndexInfo = {
"ZIndex",
"Z-Index",
"Determines where the layer is placed in the list of available layers. Layers "
"are applied in the order of their Z indices, with higher indices obscuring "
"layers with lower values.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo GuiDescriptionInfo = {
"GuiDescription",
"Gui description",
"This is the description for the scene graph node to be shown in the gui "
"example: Earth is a special place.",
openspace::properties::Property::Visibility::Hidden
};
struct [[codegen::Dictionary(Layer), codegen::noexhaustive()]] Parameters {
// The unique identifier for this layer.
std::string identifier [[codegen::identifier()]];
// A human-readable name for the user interface. If this is omitted, the
// identifier is used instead
std::optional<std::string> name;
// A human-readable description of the layer to be used in informational texts
// presented to the user
std::optional<std::string> description;
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// Specifies the type of layer that is to be added. If this value is not
// specified, the layer is a DefaultTileProvider
std::optional<std::string> type [[codegen::inlist("DefaultTileProvider",
"SingleImageProvider", "ImageSequenceTileProvider",
"SizeReferenceTileProvider", "TemporalTileProvider", "TileIndexTileProvider",
"TileProviderByDate", "TileProviderByIndex", "TileProviderByLevel",
"SolidColor", "SpoutImageProvider", "VideoTileProvider")]];
// Determine whether the layer is enabled or not. If this value is not specified,
// the layer is disabled
std::optional<bool> enabled;
// [[codegen::verbatim(ZIndexInfo.description)]]
std::optional<int> zIndex [[codegen::greater(0)]];
// The opacity value of the layer
std::optional<float> opacity [[codegen::inrange(0.0, 1.0)]];
struct Settings {
// The gamma value that is applied to each pixel of the layer
std::optional<float> gamma;
// The multiplicative factor that is applied to each pixel of the layer
std::optional<float> multiplier;
// An additive offset that is applied to each pixel of the layer
std::optional<float> offset;
};
// Specifies the render settings that should be applied to this layer
std::optional<Settings> settings;
// Parameters that set individual adjustment parameters for this layer
std::optional<ghoul::Dictionary> adjustment
[[codegen::reference("globebrowsing_layeradjustment")]];
enum class BlendMode {
Normal,
Multiply,
Add,
Subtract,
Color
};
// Sets the blend mode of this layer to determine how it interacts with other
// layers on top of this
std::optional<BlendMode> blendMode;
};
#include "layer_codegen.cpp"
} // namespace
documentation::Documentation Layer::Documentation() {
return codegen::doc<Parameters>("globebrowsing_layer");
}
Layer::Layer(layers::Group::ID id, const ghoul::Dictionary& layerDict, LayerGroup& parent)
: properties::PropertyOwner({
layerDict.value<std::string>(KeyIdentifier),
layerDict.hasKey(KeyName) ? layerDict.value<std::string>(KeyName) : "",
layerDict.hasKey(KeyDesc) ? layerDict.value<std::string>(KeyDesc) : ""
})
, _parent(parent)
, _typeOption(TypeInfo)
, _blendModeOption(BlendModeInfo)
, _enabled(EnabledInfo, false)
, _reset(ResetInfo)
, _remove(RemoveInfo)
, _guiDescription(GuiDescriptionInfo)
, _solidColor(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, _layerGroupId(id)
{
const Parameters p = codegen::bake<Parameters>(layerDict);
const layers::Layer::ID typeID =
p.type.has_value() ?
ghoul::from_string<layers::Layer::ID>(*p.type) :
layers::Layer::ID::DefaultTileProvider;
initializeBasedOnType(typeID, layerDict);
_enabled = p.enabled.value_or(_enabled);
_hasManualZIndex = p.zIndex.has_value();
if (_hasManualZIndex) {
_zIndex = p.zIndex.value_or(_zIndex);
}
else {
const std::vector<Layer*> siblings = _parent.layers();
if (siblings.empty()) {
// If this layer is the first to be added, the index becomes 1
_zIndex = 1;
}
else {
// Find the previous layer in the layer group that this is part of
Layer* prevLayer = siblings.back();
if (!prevLayer->_hasManualZIndex) {
// If the layer before does not have a given z-index, then set this
// layer's value to the same index
_zIndex = prevLayer->_zIndex;
}
else {
// Take the previous layer's z index and add 1 to get this layer's value
_zIndex = prevLayer->_zIndex + 1;
}
}
}
if (p.description.has_value()) {
_guiDescription = description();
addProperty(_guiDescription);
}
_opacity = p.opacity.value_or(_opacity);
addProperty(Fadeable::_opacity);
addProperty(Fadeable::_fade);
if (p.settings.has_value()) {
_renderSettings.gamma = p.settings->gamma.value_or(_renderSettings.gamma);
_renderSettings.multiplier =
p.settings->multiplier.value_or(_renderSettings.multiplier);
_renderSettings.offset = p.settings->offset.value_or(_renderSettings.offset);
}
if (p.adjustment.has_value()) {
_layerAdjustment.setValuesFromDictionary(*p.adjustment);
}
// Add options to option properties
for (const layers::Layer& li : layers::Layers) {
_typeOption.addOption(static_cast<int>(li.id), std::string(li.identifier));
}
_typeOption.setValue(static_cast<int>(typeID));
_typeId = static_cast<layers::Layer::ID>(_typeOption.value());
for (const layers::Blend& bi : layers::Blends) {
_blendModeOption.addOption(static_cast<int>(bi.id), std::string(bi.identifier));
}
// Initialize blend mode
if (p.blendMode.has_value()) {
switch (*p.blendMode) {
case Parameters::BlendMode::Normal:
_blendModeOption = static_cast<int>(layers::Blend::ID::Normal);
break;
case Parameters::BlendMode::Multiply:
_blendModeOption = static_cast<int>(layers::Blend::ID::Multiply);
break;
case Parameters::BlendMode::Add:
_blendModeOption = static_cast<int>(layers::Blend::ID::Add);
break;
case Parameters::BlendMode::Subtract:
_blendModeOption = static_cast<int>(layers::Blend::ID::Subtract);
break;
case Parameters::BlendMode::Color:
_blendModeOption = static_cast<int>(layers::Blend::ID::Color);
break;
}
}
else {
_blendModeOption = static_cast<int>(layers::Blend::ID::Normal);
}
// On change callbacks definitions
_enabled.onChange([this]() {
if (_onChangeCallback) {
_onChangeCallback(this);
}
});
_reset.onChange([this]() {
if (_tileProvider) {
_tileProvider->reset();
}
});
_remove.onChange([this]() {
if (_tileProvider) {
_tileProvider->reset();
_parent.scheduleDeleteLayer(identifier());
}
});
_renderSettings.onChange([this]() {
// Only if we are a height layer will anyone care about these settings changing as
// that will change the overall bounding box of the layer and thus require culling
if (_parent.isHeightLayer() && _onChangeCallback) {
_onChangeCallback(this);
}
});
_typeOption.onChange([this]() {
switch (type()) {
// Intentional fall through. Same for all tile layers
case layers::Layer::ID::DefaultTileProvider:
case layers::Layer::ID::SingleImageProvider:
case layers::Layer::ID::SpoutImageProvider:
case layers::Layer::ID::ImageSequenceTileProvider:
case layers::Layer::ID::SizeReferenceTileProvider:
case layers::Layer::ID::TemporalTileProvider:
case layers::Layer::ID::TileIndexTileProvider:
case layers::Layer::ID::TileProviderByDate:
case layers::Layer::ID::TileProviderByIndex:
case layers::Layer::ID::TileProviderByLevel:
case layers::Layer::ID::VideoTileProvider:
if (_tileProvider) {
removePropertySubOwner(*_tileProvider);
}
break;
case layers::Layer::ID::SolidColor:
removeProperty(_solidColor);
break;
}
_typeId = static_cast<layers::Layer::ID>(_typeOption.value());
initializeBasedOnType(type(), {});
addVisibleProperties();
if (_onChangeCallback) {
_onChangeCallback(this);
}
});
_blendModeOption.onChange([this]() {
if (_onChangeCallback) {
_onChangeCallback(this);
}
});
_layerAdjustment.onChange([this]() {
if (_onChangeCallback) {
_onChangeCallback(this);
}
});
_typeOption.setReadOnly(true);
// Add the properties
addProperty(_typeOption);
addProperty(_blendModeOption);
addProperty(_enabled);
addProperty(_reset);
addProperty(_remove);
_solidColor.setViewOption(properties::Property::ViewOptions::Color);
addVisibleProperties();
addPropertySubOwner(_renderSettings);
addPropertySubOwner(_layerAdjustment);
}
void Layer::initialize() {
ZoneScoped;
if (_tileProvider) {
_tileProvider->initialize();
}
_isInitialized = true;
}
void Layer::deinitialize() {
if (_tileProvider) {
_tileProvider->deinitialize();
}
}
ChunkTilePile Layer::chunkTilePile(const TileIndex& tileIndex, int pileSize) const {
ZoneScoped;
if (_tileProvider) {
return _tileProvider->chunkTilePile(tileIndex, pileSize);
}
else {
ChunkTilePile chunkTilePile;
std::fill(chunkTilePile.begin(), chunkTilePile.end(), std::nullopt);
for (int i = 0; i < pileSize; i++) {
ChunkTile tile;
tile.uvTransform = TileUvTransform{ { 0, 0 }, { 1, 1 } };
chunkTilePile[i] = tile;
}
return chunkTilePile;
}
}
Tile::Status Layer::tileStatus(const TileIndex& index) const {
return _tileProvider ?
_tileProvider->tileStatus(index) :
Tile::Status::Unavailable;
}
layers::Layer::ID Layer::type() const {
return _typeId;
}
layers::Blend::ID Layer::blendMode() const {
return static_cast<layers::Blend::ID>(_blendModeOption.value());
}
TileDepthTransform Layer::depthTransform() const {
return _tileProvider ?
_tileProvider->depthTransform() :
TileDepthTransform{ 1.f, 0.f };
}
void Layer::setEnabled(bool enabled) {
_enabled = enabled;
}
// @NOTE (malej, 2024-05-08): This function does not automatically re-sort any layer list
// depending on the new z-index, it is up to the caller to make sure the re-sort happen
// when it is needed
void Layer::setZIndex(unsigned int value) {
_zIndex = value;
_hasManualZIndex = true;
}
bool Layer::enabled() const {
return _enabled;
}
bool Layer::isInitialized() const {
return _isInitialized;
}
TileProvider* Layer::tileProvider() const {
return _tileProvider.get();
}
glm::vec3 Layer::solidColor() const {
return _solidColor;
}
const LayerRenderSettings& Layer::renderSettings() const {
return _renderSettings;
}
const LayerAdjustment& Layer::layerAdjustment() const {
return _layerAdjustment;
}
unsigned int Layer::zIndex() const {
return _zIndex;
}
void Layer::onChange(std::function<void(Layer*)> callback) {
_onChangeCallback = std::move(callback);
}
void Layer::update() {
ZoneScoped;
if (_tileProvider) {
_tileProvider->update();
}
}
glm::vec2 Layer::tileUvToTextureSamplePosition(const TileUvTransform& uvTransform,
const glm::vec2& tileUV)
{
const glm::vec2 uv = uvTransform.uvOffset + uvTransform.uvScale * tileUV;
return uv;
}
void Layer::initializeBasedOnType(layers::Layer::ID id, ghoul::Dictionary initDict) {
switch (id) {
// Intentional fall through. Same for all tile layers
case layers::Layer::ID::DefaultTileProvider:
case layers::Layer::ID::SingleImageProvider:
case layers::Layer::ID::SpoutImageProvider:
case layers::Layer::ID::ImageSequenceTileProvider:
case layers::Layer::ID::SizeReferenceTileProvider:
case layers::Layer::ID::TemporalTileProvider:
case layers::Layer::ID::TileIndexTileProvider:
case layers::Layer::ID::TileProviderByDate:
case layers::Layer::ID::TileProviderByIndex:
case layers::Layer::ID::TileProviderByLevel:
case layers::Layer::ID::VideoTileProvider:
// We add the id to the dictionary since it needs to be known by
// the tile provider
initDict.setValue(
std::string(KeyLayerGroupID),
static_cast<int>(_layerGroupId)
);
if (initDict.hasKey(KeyName) && initDict.hasValue<std::string>(KeyName)) {
const std::string name = initDict.value<std::string>(KeyName);
LDEBUG("Initializing tile provider for layer: '" + name + "'");
}
_tileProvider = TileProvider::createFromDictionary(initDict);
break;
case layers::Layer::ID::SolidColor:
if (initDict.hasValue<glm::dvec3>(ColorInfo.identifier)) {
_solidColor = initDict.value<glm::dvec3>(ColorInfo.identifier);
}
break;
}
}
void Layer::addVisibleProperties() {
switch (_typeId) {
// Intentional fall through. Same for all tile layers
case layers::Layer::ID::DefaultTileProvider:
case layers::Layer::ID::SingleImageProvider:
case layers::Layer::ID::SpoutImageProvider:
case layers::Layer::ID::ImageSequenceTileProvider:
case layers::Layer::ID::SizeReferenceTileProvider:
case layers::Layer::ID::TemporalTileProvider:
case layers::Layer::ID::TileIndexTileProvider:
case layers::Layer::ID::TileProviderByDate:
case layers::Layer::ID::TileProviderByIndex:
case layers::Layer::ID::TileProviderByLevel:
case layers::Layer::ID::VideoTileProvider:
if (_tileProvider) {
addPropertySubOwner(*_tileProvider);
}
break;
case layers::Layer::ID::SolidColor:
addProperty(_solidColor);
break;
}
}
} // namespace openspace::globebrowsing