From 359cf9950e2d20b03eb4958d21658607e994ff5c Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 30 Apr 2024 08:20:40 +0200 Subject: [PATCH] Add documentation and example for the `TileProviderByIndex` (#3220) --- .../tileproviderbyindex/byindex.asset | 59 +++++++++++++ modules/globebrowsing/src/layer.cpp | 4 - modules/globebrowsing/src/tileindex.cpp | 24 ++++++ modules/globebrowsing/src/tileindex.h | 1 + .../src/tileprovider/tileproviderbyindex.cpp | 86 ++++++++++++++----- .../src/tileprovider/tileproviderbylevel.cpp | 10 ++- 6 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 data/assets/examples/tileprovider/tileproviderbyindex/byindex.asset diff --git a/data/assets/examples/tileprovider/tileproviderbyindex/byindex.asset b/data/assets/examples/tileprovider/tileproviderbyindex/byindex.asset new file mode 100644 index 0000000000..daf5ccd7ed --- /dev/null +++ b/data/assets/examples/tileprovider/tileproviderbyindex/byindex.asset @@ -0,0 +1,59 @@ +-- Basic +-- This example file adds a layer to a globe that has a base layer and then replaces one +-- hemisphere of the planet with a single image. Recommended reading for this example is +-- the documentation on the DefaultTileProvider. + +-- Download some example images that we can use +local images = asset.resource({ + Name = "Earth Textures", + Type = "HttpSynchronization", + Identifier = "earth_textures", + Version = 3 +}) + +-- Define the TileProvider +local TileProvider = { + Identifier = "Example", + Type = "TileProviderByIndex", + Enabled = true, + -- The default tile provider that is used for the whole globe + DefaultTileProvider = { + Identifier = "Blue_Marble", + FilePath = images .. "earth_bluemarble.jpg" + }, + TileProviders = { + -- We only define one additional tile provider that overwrites the right + -- hemisphere of the globe + { + Index = { X = 0, Y = 0, Level = 2 }, + TileProvider = { + Identifier = "Blue_Marble_Night", + FilePath = images .. "earth_night.png" + } + } + } +} + +-- Define the scene graph node +local Node = { + Identifier = "TileProviderByIndex_Example", + Renderable = { + Type = "RenderableGlobe", + Layers = { + -- The globe has exactly one layer, which is the one we defined above + ColorLayers = { TileProvider } + } + }, + GUI = { + Name = "Basic", + Path = "/Examples/TileProviderByIndex" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/modules/globebrowsing/src/layer.cpp b/modules/globebrowsing/src/layer.cpp index d9d3d92494..762c89e8fd 100644 --- a/modules/globebrowsing/src/layer.cpp +++ b/modules/globebrowsing/src/layer.cpp @@ -174,10 +174,6 @@ namespace { // Sets the blend mode of this layer to determine how it interacts with other // layers on top of this std::optional blendMode; - - // If the primary layer creation fails, this layer is used as a fallback - std::optional - fallback [[codegen::reference("globebrowsing_layer")]]; }; #include "layer_codegen.cpp" } // namespace diff --git a/modules/globebrowsing/src/tileindex.cpp b/modules/globebrowsing/src/tileindex.cpp index 6bf83b9877..7378563131 100644 --- a/modules/globebrowsing/src/tileindex.cpp +++ b/modules/globebrowsing/src/tileindex.cpp @@ -36,6 +36,30 @@ TileIndex::TileIndex(uint32_t x_, uint32_t y_, uint8_t level_) , level(level_) {} +TileIndex::TileIndex(TileHashKey hash) { + // Extract the level as the lowest 5 bits + uint64_t hlevel = hash & ((1 << 5) - 1); + ghoul_assert(hlevel < (1 << 5), "Error in hashing for level"); + // And then shift the remainder down so that the next value is in the lowest bits + hash -= hlevel; + hash = hash >> 5; + + // Extract the x as the lowest 30 bits + uint64_t hx = hash & ((1 << 30) - 1); + ghoul_assert(hx < (1 << 30), "Error in hashing for x"); + // And then shift the remainder down so that the next value is in the lowest bits + hash -= hx; + hash = hash >> 30; + + // The remainder in the hash must then be the y index + uint64_t hy = hash; + ghoul_assert(hy < (1 << 29), "Error in hashing for y"); + + level = static_cast(hlevel); + x = static_cast(hx); + y = static_cast(hy); +} + TileIndex TileIndex::child(Quad q) const { return TileIndex(2 * x + q % 2, 2 * y + q / 2, level + 1); } diff --git a/modules/globebrowsing/src/tileindex.h b/modules/globebrowsing/src/tileindex.h index 3a0098a4ad..594fc867e3 100644 --- a/modules/globebrowsing/src/tileindex.h +++ b/modules/globebrowsing/src/tileindex.h @@ -35,6 +35,7 @@ struct TileIndex { using TileHashKey = uint64_t; TileIndex(uint32_t x_, uint32_t y_, uint8_t level_); + explicit TileIndex(TileHashKey hash); uint32_t x = 0; uint32_t y = 0; diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp index a64fb1f2e1..50caabde7f 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp +++ b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp @@ -27,33 +27,50 @@ #include namespace { + // This TileProvider provides the ability to override the contents for tiles at + // specific indices. A default tile provider has to be specified that is used by + // default for the entire globe. If a tile provider is specified for a specific tile, + // then the default tile provide is used for all other indices and the specialized + // tile provider `P` is used for the specified index. Any number of specialized tile + // providers can be provided to overwrite specific locations on the globe. + // + // This tile provider can be used to, for example, show an inset image that is merged + // with a larger globe-spanning image. struct [[codegen::Dictionary(TileProviderByIndex)]] Parameters { - ghoul::Dictionary defaultProvider; + ghoul::Dictionary defaultTileProvider + [[codegen::reference("globebrowsing_layer")]]; + // An IndexProvider is a tile provider that is only valid for a specific + // combination of x, y, and level. Whenever a globe tries to render a tile and + // this tile provider has an IndexProvider of that index, it will use the + // specialized tile provider instead. struct IndexProvider { struct Index { // The x coordinate for this index. This specifies the horizontal - // direction (longitude) component + // direction (longitude) component. Acceptable values for this coordinate + // have to be smaller than $2 * 2^{level}$. int x [[codegen::greaterequal(0)]]; // The y coordinate for this index. This specifies the vertical direction - // (latitude) component + // (latitude) component. Acceptable values for this coordinate have to be + // smaller than $2^{level}$. int y [[codegen::greaterequal(0)]]; // The z-level which corresponds to the depth of the tile pyramid, which - // directly impacts the applied resolution of the tileprovider shown here - int level [[codegen::inrange(0, 255)]]; + // directly impacts the applied resolution of the tileprovider shown here. + // Not that _in general_ the level would start at 2. + int level [[codegen::inrange(0, 23)]]; }; - // The index for which the provided tile provider is used - Index tileIndex; + // The index for which the provided tile provider is used. + Index index; - // The dictionary that described the tileprovider to be used by the provided - // index - ghoul::Dictionary tileProvider; + // The dictionary that describes the TileProvider to be used by the provided + // `index`. + ghoul::Dictionary tileProvider [[codegen::reference("globebrowsing_layer")]]; }; - // The list of all tileprovides and the indices at which they are used - std::vector indexTileProviders; + // The list of all TileProviders and the indices at which they are used. + std::vector tileProviders; }; #include "tileproviderbyindex_codegen.cpp" } // namespace @@ -67,21 +84,30 @@ documentation::Documentation TileProviderByIndex::Documentation() { TileProviderByIndex::TileProviderByIndex(const ghoul::Dictionary& dictionary) { ZoneScoped; - const Parameters p = codegen::bake(dictionary); + Parameters p = codegen::bake(dictionary); + + // For now we need to inject the LayerGroupID this way. We don't want it to be part of + // the parameters struct as that would mean it would be visible to the end user, which + // we don't want since this value just comes from whoever creates it, not the user + ghoul_assert(dictionary.hasValue("LayerGroupID"), "No Layer Group ID provided"); + const layers::Group::ID group = static_cast( + dictionary.value("LayerGroupID") + ); layers::Layer::ID typeID = layers::Layer::ID::DefaultTileProvider; - if (p.defaultProvider.hasValue("Type")) { - const std::string type = p.defaultProvider.value("Type"); + if (p.defaultTileProvider.hasValue("Type")) { + const std::string type = p.defaultTileProvider.value("Type"); typeID = ghoul::from_string(type); } - _defaultTileProvider = createFromDictionary(typeID, p.defaultProvider); + p.defaultTileProvider.setValue("LayerGroupID", static_cast(group)); + _defaultTileProvider = createFromDictionary(typeID, p.defaultTileProvider); - for (const Parameters::IndexProvider& ip : p.indexTileProviders) { + for (Parameters::IndexProvider& ip : p.tileProviders) { const TileIndex tileIndex = TileIndex( - ip.tileIndex.x, - ip.tileIndex.y, - static_cast(ip.tileIndex.level) + ip.index.x, + ip.index.y, + static_cast(ip.index.level) ); layers::Layer::ID providerID = layers::Layer::ID::DefaultTileProvider; @@ -90,6 +116,7 @@ TileProviderByIndex::TileProviderByIndex(const ghoul::Dictionary& dictionary) { providerID = ghoul::from_string(type); } + ip.tileProvider.setValue("LayerGroupID", static_cast(group)); std::unique_ptr stp = createFromDictionary( providerID, ip.tileProvider @@ -103,13 +130,17 @@ Tile TileProviderByIndex::tile(const TileIndex& tileIndex) { ZoneScoped; const auto it = _providers.find(tileIndex.hashKey()); const bool hasProvider = it != _providers.end(); - return hasProvider ? it->second->tile(tileIndex) : Tile(); + return hasProvider ? + it->second->tile(tileIndex) : + _defaultTileProvider->tile(tileIndex); } Tile::Status TileProviderByIndex::tileStatus(const TileIndex& index) { const auto it = _providers.find(index.hashKey()); const bool hasProvider = it != _providers.end(); - return hasProvider ? it->second->tileStatus(index) : Tile::Status::Unavailable; + return hasProvider ? + it->second->tileStatus(index) : + _defaultTileProvider->tileStatus(index); } TileDepthTransform TileProviderByIndex::depthTransform() { @@ -139,7 +170,16 @@ int TileProviderByIndex::minLevel() { } int TileProviderByIndex::maxLevel() { - return _defaultTileProvider->maxLevel(); + int result = _defaultTileProvider->maxLevel(); + + using K = TileIndex::TileHashKey; + using V = std::unique_ptr; + for (std::pair& it : _providers) { + TileIndex index = TileIndex(it.first); + result = std::max(result, static_cast(index.level)); + } + + return result; } float TileProviderByIndex::noDataValueAsFloat() { diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp index 3413212e2d..95625a3655 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp +++ b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp @@ -28,8 +28,6 @@ namespace { struct [[codegen::Dictionary(TileProviderByLevel)]] Parameters { - int layerGroupID; - struct Provider { int maxLevel [[codegen::greaterequal(0)]]; ghoul::Dictionary tileProvider; @@ -50,7 +48,13 @@ TileProviderByLevel::TileProviderByLevel(const ghoul::Dictionary& dictionary) { const Parameters p = codegen::bake(dictionary); - const layers::Group::ID group = static_cast(p.layerGroupID); + // For now we need to inject the LayerGroupID this way. We don't want it to be part of + // the parameters struct as that would mean it would be visible to the end user, which + // we don't want since this value just comes from whoever creates it, not the user + ghoul_assert(dictionary.hasValue("LayerGroupID"), "No Layer Group ID provided"); + const layers::Group::ID group = static_cast( + dictionary.value("LayerGroupID") + ); for (Parameters::Provider provider : p.levelTileProviders) { ghoul::Dictionary& tileProviderDict = provider.tileProvider;