diff --git a/CHANGES.md b/CHANGES.md index 6493094da..54dade1a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,13 +16,12 @@ - Added `transform` method to `CesiumGeometry::BoundingSphere`. - Added `toSphere`, `fromSphere`, and `fromAxisAligned` methods to `CesiumGeometry::OrientedBoundingBox`. - Added `TileTransform` class to `Cesium3DTilesContent`, making it easier to create a `glm::dmat4` from the `transform` property of a `Cesium3DTiles::Tile`. +- Added `ImplicitTilingUtilities` class to `Cesium3DTilesContent`. +- Added overloads of `isTileAvailable` and `isContentAvailable` on the `SubtreeAvailability` class that take the subtree root tile ID and the tile ID of interest, instead of a relative level and Morton index. ##### Fixes :wrench: - Fixed a bug in `OrientedBoundingBox::contains` where it didn't account for the bounding box's center. - -##### Fixes :wrench: - - Fixed compiler error when calling `PropertyAttributeView::forEachProperty`. ### v0.29.0 - 2023-11-01 diff --git a/Cesium3DTilesContent/CMakeLists.txt b/Cesium3DTilesContent/CMakeLists.txt index 649b0c327..1f0ec1b81 100644 --- a/Cesium3DTilesContent/CMakeLists.txt +++ b/Cesium3DTilesContent/CMakeLists.txt @@ -62,6 +62,12 @@ target_link_libraries(Cesium3DTilesContent CesiumUtility ) +target_link_libraries_system( + Cesium3DTilesContent + PRIVATE + libmorton +) + install(TARGETS Cesium3DTilesContent LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Cesium3DTilesContent diff --git a/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h b/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h new file mode 100644 index 000000000..16b56ef97 --- /dev/null +++ b/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h @@ -0,0 +1,351 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace CesiumGeospatial { +class BoundingRegion; +class S2CellBoundingVolume; +} // namespace CesiumGeospatial + +namespace CesiumGeometry { +class OrientedBoundingBox; +} + +namespace Cesium3DTilesContent { + +/** + * @brief A lightweight virtual container enumerating the quadtree IDs of the + * children of a given quadtree tile. + */ +class QuadtreeChildren { +public: + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = CesiumGeometry::QuadtreeTileID; + using difference_type = void; + using pointer = CesiumGeometry::QuadtreeTileID*; + using reference = CesiumGeometry::QuadtreeTileID&; + + explicit iterator( + const CesiumGeometry::QuadtreeTileID& parentTileID, + bool isFirst) noexcept; + + const CesiumGeometry::QuadtreeTileID& operator*() const { + return this->_current; + } + const CesiumGeometry::QuadtreeTileID* operator->() const { + return &this->_current; + } + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& rhs) const noexcept; + bool operator!=(const iterator& rhs) const noexcept; + + private: + CesiumGeometry::QuadtreeTileID _current; + }; + + using const_iterator = iterator; + + QuadtreeChildren(const CesiumGeometry::QuadtreeTileID& tileID) noexcept + : _tileID(tileID) {} + iterator begin() const noexcept; + iterator end() const noexcept; + constexpr int64_t size() const noexcept { return 4; } + +private: + CesiumGeometry::QuadtreeTileID _tileID; +}; + +/** + * @brief A lightweight virtual container enumerating the octree IDs of the + * children of a given octree tile. + */ +class OctreeChildren { +public: + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = CesiumGeometry::OctreeTileID; + using difference_type = void; + using pointer = CesiumGeometry::OctreeTileID*; + using reference = CesiumGeometry::OctreeTileID&; + + explicit iterator( + const CesiumGeometry::OctreeTileID& parentTileID, + bool isFirst) noexcept; + + const CesiumGeometry::OctreeTileID& operator*() const { + return this->_current; + } + const CesiumGeometry::OctreeTileID* operator->() const { + return &this->_current; + } + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& rhs) const noexcept; + bool operator!=(const iterator& rhs) const noexcept; + + private: + CesiumGeometry::OctreeTileID _current; + }; + + using const_iterator = iterator; + + OctreeChildren(const CesiumGeometry::OctreeTileID& tileID) noexcept + : _tileID(tileID) {} + iterator begin() const noexcept; + iterator end() const noexcept; + constexpr int64_t size() const noexcept { return 8; } + +private: + CesiumGeometry::OctreeTileID _tileID; +}; + +/** + * @brief Helper functions for working with 3D Tiles implicit tiling. + */ +class ImplicitTilingUtilities { +public: + /** + * @brief Resolves a templatized implicit tiling URL with a quadtree tile ID. + * + * @param baseUrl The base URL that is used to resolve the urlTemplate if it + * is a relative path. + * @param urlTemplate The templatized URL. + * @param quadtreeID The quadtree ID to use in resolving the parameters in the + * URL template. + * @return The resolved URL. + */ + static std::string resolveUrl( + const std::string& baseUrl, + const std::string& urlTemplate, + const CesiumGeometry::QuadtreeTileID& quadtreeID); + + /** + * @brief Resolves a templatized implicit tiling URL with an octree tile ID. + * + * @param baseUrl The base URL that is used to resolve the urlTemplate if it + * is a relative path. + * @param urlTemplate The templatized URL. + * @param octreeID The octree ID to use in resolving the parameters in the + * URL template. + * @return The resolved URL. + */ + static std::string resolveUrl( + const std::string& baseUrl, + const std::string& urlTemplate, + const CesiumGeometry::OctreeTileID& octreeID); + + /** + * @brief Computes the denominator for a given implicit tile level. + * + * Divide the root tile's geometric error by this value to get the standard + * geometric error for tiles on the level. Or divide each component of a + * bounding volume by this factor to get the size of the bounding volume along + * that axis for tiles of this level. + * + * @param level The tile level. + * @return The denominator for the level. + */ + static double computeLevelDenominator(uint32_t level) noexcept; + + /** + * @brief Computes the Morton index for a given quadtree tile within its + * level. + * + * @param tileID The ID of the tile. + * @return The Morton index. + */ + static uint64_t + computeMortonIndex(const CesiumGeometry::QuadtreeTileID& tileID); + + /** + * @brief Computes the Morton index for a given octree tile within its level. + * + * @param tileID The ID of the tile. + * @return The Morton index. + */ + static uint64_t + computeMortonIndex(const CesiumGeometry::OctreeTileID& tileID); + + /** + * @brief Computes the relative Morton index for a given quadtree tile within + * its level of a subtree root at the tile with the given quadtree ID. + * + * @param subtreeID The ID of the subtree the contains the tile. + * @param tileID The ID of the tile. + * @return The relative Morton index. + */ + static uint64_t computeRelativeMortonIndex( + const CesiumGeometry::QuadtreeTileID& subtreeID, + const CesiumGeometry::QuadtreeTileID& tileID); + + /** + * @brief Computes the relative Morton index for a given octree tile within + * its level of a subtree rooted at the tile with the given octree ID. + * + * @param subtreeID The ID of the subtree the contains the tile. + * @param tileID The ID of the tile. + * @return The relative Morton index. + */ + static uint64_t computeRelativeMortonIndex( + const CesiumGeometry::OctreeTileID& subtreeRootID, + const CesiumGeometry::OctreeTileID& tileID); + + /** + * @brief Gets the ID of the root tile of the subtree that contains a given + * tile. + * + * @param subtreeLevels The number of levels in each sub-tree. For example, if + * this parameter is 4, then the first subtree starts at level 0 and + * contains tiles in levels 0 through 3, and the next subtree starts at + * level 4 and contains tiles in levels 4 through 7. + * @param tileID The tile ID for each to find the subtree root. + * @return The ID of the root tile of the subtree. + */ + static CesiumGeometry::QuadtreeTileID getSubtreeRootID( + uint32_t subtreeLevels, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; + + /** + * @brief Gets the ID of the root tile of the subtree that contains a given + * tile. + * + * @param subtreeLevels The number of levels in each sub-tree. For example, if + * this parameter is 4, then the first subtree starts at level 0 and + * contains tiles in levels 0 through 3, and the next subtree starts at + * level 4 and contains tiles in levels 4 through 7. + * @param tileID The tile ID for each to find the subtree root. + * @return The ID of the root tile of the subtree. + */ + static CesiumGeometry::OctreeTileID getSubtreeRootID( + uint32_t subtreeLevels, + const CesiumGeometry::OctreeTileID& tileID) noexcept; + + /** + * @brief Converts an absolute tile ID to a tile ID relative to a given root + * tile. + * + * For example, if `rootID` and `tileID` are the same, this method returns + * `QuadtreeTileID(0, 0, 0)`. + * + * @param rootID The ID of the root tile that the returned ID should be + * relative to. + * @param tileID The absolute ID of the tile to compute a relative ID for. + * @return The relative tile ID. + */ + static CesiumGeometry::QuadtreeTileID absoluteTileIDToRelative( + const CesiumGeometry::QuadtreeTileID& rootID, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; + + /** + * @brief Converts an absolute tile ID to a tile ID relative to a given root + * tile. + * + * For example, if `rootID` and `tileID` are the same, this method returns + * `OctreeTileID(0, 0, 0, 0)`. + * + * @param rootID The ID of the root tile that the returned ID should be + * relative to. + * @param tileID The absolute ID of the tile to compute a relative ID for. + * @return The relative tile ID. + */ + static CesiumGeometry::OctreeTileID absoluteTileIDToRelative( + const CesiumGeometry::OctreeTileID& rootID, + const CesiumGeometry::OctreeTileID& tileID) noexcept; + + /** + * @brief Gets a lightweight virtual container for enumerating the quadtree + * IDs of the children of a given quadtree tile. + * + * @param tileID The tile ID of the parent tile for which to get children. + * @return The children. + */ + static QuadtreeChildren + getChildren(const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + return QuadtreeChildren{tileID}; + } + + /** + * @brief Gets a lightweight virtual container for enumerating the octree + * IDs of the children of a given octree tile. + * + * @param tileID The tile ID of the parent tile for which to get children. + * @return The children. + */ + static OctreeChildren + getChildren(const CesiumGeometry::OctreeTileID& tileID) noexcept { + return OctreeChildren{tileID}; + } + + /** + * @brief Computes the bounding volume for an implicit quadtree tile with the + * given ID as a bounding region. + * + * @param rootBoundingVolume The bounding region of the root tile. + * @param tileID The tile ID for which to compute the bounding region. + * @return The bounding region for the given implicit tile. + */ + static CesiumGeospatial::BoundingRegion computeBoundingVolume( + const CesiumGeospatial::BoundingRegion& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit octree tile with the + * given ID as a bounding region. + * + * @param rootBoundingVolume The bounding region of the root tile. + * @param tileID The tile ID for which to compute the bounding region. + * @return The bounding region for the given implicit tile. + */ + static CesiumGeospatial::BoundingRegion computeBoundingVolume( + const CesiumGeospatial::BoundingRegion& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit quadtree tile + * with the given ID as an oriented bounding box. + * + * @param rootBoundingVolume The oriented bounding box of the root tile. + * @param tileID The tile ID for which to compute the oriented bounding box. + * @return The oriented bounding box for the given implicit tile. + */ + static CesiumGeometry::OrientedBoundingBox computeBoundingVolume( + const CesiumGeometry::OrientedBoundingBox& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit octree tile with + * the given ID as an oriented bounding box. + * + * @param rootBoundingVolume The oriented bounding box of the root tile. + * @param tileID The tile ID for which to compute the oriented bounding box. + * @return The oriented bounding box for the given implicit tile. + */ + static CesiumGeometry::OrientedBoundingBox computeBoundingVolume( + const CesiumGeometry::OrientedBoundingBox& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit quadtree tile + * with the given ID as an S2 cell bounding volume. + * + * @param rootBoundingVolume The S2 cell bounding volume of the root tile. + * @param tileID The tile ID for which to compute the S2 cell bounding volume. + * @return The S2 cell bounding volume for the given implicit tile. + */ + static CesiumGeospatial::S2CellBoundingVolume computeBoundingVolume( + const CesiumGeospatial::S2CellBoundingVolume& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; +}; + +} // namespace Cesium3DTilesContent diff --git a/Cesium3DTilesContent/include/Cesium3DTilesContent/QuantizedMeshLoader.h b/Cesium3DTilesContent/include/Cesium3DTilesContent/QuantizedMeshLoader.h index 5ffe02940..2574243e3 100644 --- a/Cesium3DTilesContent/include/Cesium3DTilesContent/QuantizedMeshLoader.h +++ b/Cesium3DTilesContent/include/Cesium3DTilesContent/QuantizedMeshLoader.h @@ -70,10 +70,10 @@ class CESIUM3DTILESCONTENT_API QuantizedMeshLoader final { /** * @brief Create a {@link QuantizedMeshLoadResult} from the given data. * - * @param tileID The tile ID - * @param tileBoundingVoume The tile bounding volume - * @param url The URL - * @param data The actual input data + * @param tileID The tile ID. + * @param tileBoundingVoume The tile bounding volume. + * @param url The URL from which the data was loaded. + * @param data The actual tile data. * @return The {@link QuantizedMeshLoadResult} */ static QuantizedMeshLoadResult load( @@ -83,12 +83,32 @@ class CESIUM3DTILESCONTENT_API QuantizedMeshLoader final { const gsl::span& data, bool enableWaterMask); + /** + * @brief Parses the metadata (tile availability) from the given + * quantized-mesh terrain tile data. + * + * @param data The actual tile data. + * @param tileID The tile ID. + * @return The parsed metadata. + */ static QuantizedMeshMetadataResult loadMetadata( const gsl::span& data, const CesiumGeometry::QuadtreeTileID& tileID); + /** + * @brief Extracts tile availability information from a parsed layer.json + * or tile metadata extension. + * + * The actual availability information will be found in a property called + * `available`. + * + * @param layerJson The RapidJSON document containing the layer.json. + * @param startingLevel The first tile level number to which the availability + * information applies. + * @return The availability. + */ static QuantizedMeshMetadataResult loadAvailabilityRectangles( - const rapidjson::Document& metadata, + const rapidjson::Document& layerJson, uint32_t startingLevel); }; diff --git a/Cesium3DTilesContent/include/Cesium3DTilesContent/SubtreeAvailability.h b/Cesium3DTilesContent/include/Cesium3DTilesContent/SubtreeAvailability.h index 29812234a..334b71d6b 100644 --- a/Cesium3DTilesContent/include/Cesium3DTilesContent/SubtreeAvailability.h +++ b/Cesium3DTilesContent/include/Cesium3DTilesContent/SubtreeAvailability.h @@ -5,6 +5,11 @@ #include +namespace CesiumGeometry { +struct QuadtreeTileID; +struct OctreeTileID; +} // namespace CesiumGeometry + namespace Cesium3DTilesContent { struct SubtreeConstantAvailability { bool constant; @@ -26,10 +31,28 @@ class SubtreeAvailability { std::vector&& contentAvailability, std::vector>&& buffers); + bool isTileAvailable( + const CesiumGeometry::QuadtreeTileID& subtreeID, + const CesiumGeometry::QuadtreeTileID& tileID) const noexcept; + + bool isTileAvailable( + const CesiumGeometry::OctreeTileID& subtreeID, + const CesiumGeometry::OctreeTileID& tileID) const noexcept; + bool isTileAvailable( uint32_t relativeTileLevel, uint64_t relativeTileMortonId) const noexcept; + bool isContentAvailable( + const CesiumGeometry::QuadtreeTileID& subtreeID, + const CesiumGeometry::QuadtreeTileID& tileID, + uint64_t contentId) const noexcept; + + bool isContentAvailable( + const CesiumGeometry::OctreeTileID& subtreeID, + const CesiumGeometry::OctreeTileID& tileID, + uint64_t contentId) const noexcept; + bool isContentAvailable( uint32_t relativeTileLevel, uint64_t relativeTileMortonId, diff --git a/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp b/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp new file mode 100644 index 000000000..5dd19c5b8 --- /dev/null +++ b/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace CesiumGeometry; + +namespace Cesium3DTilesContent { + +std::string ImplicitTilingUtilities::resolveUrl( + const std::string& baseUrl, + const std::string& urlTemplate, + const QuadtreeTileID& quadtreeID) { + std::string url = CesiumUtility::Uri::substituteTemplateParameters( + urlTemplate, + [&quadtreeID](const std::string& placeholder) { + if (placeholder == "level") { + return std::to_string(quadtreeID.level); + } + if (placeholder == "x") { + return std::to_string(quadtreeID.x); + } + if (placeholder == "y") { + return std::to_string(quadtreeID.y); + } + + return placeholder; + }); + + return CesiumUtility::Uri::resolve(baseUrl, url); +} + +std::string ImplicitTilingUtilities::resolveUrl( + const std::string& baseUrl, + const std::string& urlTemplate, + const OctreeTileID& octreeID) { + std::string url = CesiumUtility::Uri::substituteTemplateParameters( + urlTemplate, + [&octreeID](const std::string& placeholder) { + if (placeholder == "level") { + return std::to_string(octreeID.level); + } + if (placeholder == "x") { + return std::to_string(octreeID.x); + } + if (placeholder == "y") { + return std::to_string(octreeID.y); + } + if (placeholder == "z") { + return std::to_string(octreeID.z); + } + + return placeholder; + }); + + return CesiumUtility::Uri::resolve(baseUrl, url); +} + +uint64_t ImplicitTilingUtilities::computeMortonIndex( + const CesiumGeometry::QuadtreeTileID& tileID) { + return libmorton::morton2D_64_encode(tileID.x, tileID.y); +} + +uint64_t ImplicitTilingUtilities::computeMortonIndex( + const CesiumGeometry::OctreeTileID& tileID) { + return libmorton::morton3D_64_encode(tileID.x, tileID.y, tileID.z); +} + +uint64_t ImplicitTilingUtilities::computeRelativeMortonIndex( + const QuadtreeTileID& subtreeID, + const QuadtreeTileID& tileID) { + return computeMortonIndex(absoluteTileIDToRelative(subtreeID, tileID)); +} + +uint64_t ImplicitTilingUtilities::computeRelativeMortonIndex( + const OctreeTileID& subtreeID, + const OctreeTileID& tileID) { + return computeMortonIndex(absoluteTileIDToRelative(subtreeID, tileID)); +} + +CesiumGeometry::QuadtreeTileID ImplicitTilingUtilities::getSubtreeRootID( + uint32_t subtreeLevels, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + uint32_t subtreeLevel = tileID.level / subtreeLevels; + uint32_t levelsLeft = tileID.level % subtreeLevels; + return QuadtreeTileID( + subtreeLevel * subtreeLevels, + tileID.x >> levelsLeft, + tileID.y >> levelsLeft); +} + +CesiumGeometry::OctreeTileID ImplicitTilingUtilities::getSubtreeRootID( + uint32_t subtreeLevels, + const CesiumGeometry::OctreeTileID& tileID) noexcept { + uint32_t subtreeLevel = tileID.level / subtreeLevels; + uint32_t levelsLeft = tileID.level % subtreeLevels; + return OctreeTileID( + subtreeLevel * subtreeLevels, + tileID.x >> levelsLeft, + tileID.y >> levelsLeft, + tileID.z >> levelsLeft); +} + +QuadtreeTileID ImplicitTilingUtilities::absoluteTileIDToRelative( + const QuadtreeTileID& rootID, + const QuadtreeTileID& tileID) noexcept { + uint32_t relativeTileLevel = tileID.level - rootID.level; + return QuadtreeTileID( + relativeTileLevel, + tileID.x - (rootID.x << relativeTileLevel), + tileID.y - (rootID.y << relativeTileLevel)); +} + +OctreeTileID ImplicitTilingUtilities::absoluteTileIDToRelative( + const OctreeTileID& rootID, + const OctreeTileID& tileID) noexcept { + uint32_t relativeTileLevel = tileID.level - rootID.level; + return OctreeTileID( + relativeTileLevel, + tileID.x - (rootID.x << relativeTileLevel), + tileID.y - (rootID.y << relativeTileLevel), + tileID.z - (rootID.z << relativeTileLevel)); +} + +namespace { + +CesiumGeospatial::GlobeRectangle subdivideRectangle( + const CesiumGeospatial::GlobeRectangle& rootRectangle, + const CesiumGeometry::QuadtreeTileID& tileID, + double denominator) { + double latSize = + (rootRectangle.getNorth() - rootRectangle.getSouth()) / denominator; + double longSize = + (rootRectangle.getEast() - rootRectangle.getWest()) / denominator; + + double childWest = rootRectangle.getWest() + longSize * tileID.x; + double childEast = rootRectangle.getWest() + longSize * (tileID.x + 1); + + double childSouth = rootRectangle.getSouth() + latSize * tileID.y; + double childNorth = rootRectangle.getSouth() + latSize * (tileID.y + 1); + + return CesiumGeospatial::GlobeRectangle( + childWest, + childSouth, + childEast, + childNorth); +} + +} // namespace + +CesiumGeospatial::BoundingRegion ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeospatial::BoundingRegion& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + double denominator = computeLevelDenominator(tileID.level); + return CesiumGeospatial::BoundingRegion{ + subdivideRectangle( + rootBoundingVolume.getRectangle(), + tileID, + denominator), + rootBoundingVolume.getMinimumHeight(), + rootBoundingVolume.getMaximumHeight()}; +} + +CesiumGeospatial::BoundingRegion ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeospatial::BoundingRegion& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept { + double denominator = computeLevelDenominator(tileID.level); + double heightSize = (rootBoundingVolume.getMaximumHeight() - + rootBoundingVolume.getMinimumHeight()) / + denominator; + + double childMinHeight = + rootBoundingVolume.getMinimumHeight() + heightSize * tileID.z; + double childMaxHeight = + rootBoundingVolume.getMinimumHeight() + heightSize * (tileID.z + 1); + + return CesiumGeospatial::BoundingRegion{ + subdivideRectangle( + rootBoundingVolume.getRectangle(), + QuadtreeTileID(tileID.level, tileID.x, tileID.y), + denominator), + childMinHeight, + childMaxHeight}; +} + +CesiumGeometry::OrientedBoundingBox +ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeometry::OrientedBoundingBox& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + const glm::dmat3& halfAxes = rootBoundingVolume.getHalfAxes(); + const glm::dvec3& center = rootBoundingVolume.getCenter(); + + double denominator = computeLevelDenominator(tileID.level); + glm::dvec3 min = center - halfAxes[0] - halfAxes[1] - halfAxes[2]; + + glm::dvec3 xDim = halfAxes[0] * 2.0 / denominator; + glm::dvec3 yDim = halfAxes[1] * 2.0 / denominator; + glm::dvec3 childMin = min + xDim * double(tileID.x) + yDim * double(tileID.y); + glm::dvec3 childMax = min + xDim * double(tileID.x + 1) + + yDim * double(tileID.y + 1) + halfAxes[2] * 2.0; + + return CesiumGeometry::OrientedBoundingBox( + (childMin + childMax) / 2.0, + glm::dmat3{xDim / 2.0, yDim / 2.0, halfAxes[2]}); +} + +CesiumGeometry::OrientedBoundingBox +ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeometry::OrientedBoundingBox& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept { + const glm::dmat3& halfAxes = rootBoundingVolume.getHalfAxes(); + const glm::dvec3& center = rootBoundingVolume.getCenter(); + + double denominator = computeLevelDenominator(tileID.level); + glm::dvec3 min = center - halfAxes[0] - halfAxes[1] - halfAxes[2]; + + glm::dvec3 xDim = halfAxes[0] * 2.0 / denominator; + glm::dvec3 yDim = halfAxes[1] * 2.0 / denominator; + glm::dvec3 zDim = halfAxes[2] * 2.0 / denominator; + glm::dvec3 childMin = min + xDim * double(tileID.x) + + yDim * double(tileID.y) + zDim * double(tileID.z); + glm::dvec3 childMax = min + xDim * double(tileID.x + 1) + + yDim * double(tileID.y + 1) + + zDim * double(tileID.z + 1); + + return CesiumGeometry::OrientedBoundingBox( + (childMin + childMax) / 2.0, + glm::dmat3{xDim / 2.0, yDim / 2.0, zDim / 2.0}); +} + +CesiumGeospatial::S2CellBoundingVolume +ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeospatial::S2CellBoundingVolume& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + return CesiumGeospatial::S2CellBoundingVolume( + CesiumGeospatial::S2CellID::fromQuadtreeTileID( + rootBoundingVolume.getCellID().getFace(), + tileID), + rootBoundingVolume.getMinimumHeight(), + rootBoundingVolume.getMaximumHeight()); +} + +double +ImplicitTilingUtilities::computeLevelDenominator(uint32_t level) noexcept { + return static_cast(1 << level); +} + +QuadtreeChildren::iterator::iterator( + const CesiumGeometry::QuadtreeTileID& parentTileID, + bool isEnd) noexcept + : _current( + parentTileID.level + 1, + parentTileID.x << 1, + parentTileID.y << 1) { + if (isEnd) { + this->_current.y += 2; + } +} + +QuadtreeChildren::iterator& QuadtreeChildren::iterator::operator++() { + // Put an indication of the child in the two low bits of `value`. + // Bit 0 indicates left child (0) or right child (1). + // Bit 1 indicates front child (0) or back child (1). + uint32_t value = ((this->_current.y & 1) << 1) | (this->_current.x & 1); + + // Add one to the current value to get the value for the next child. + // Bit cascade from addition gives us exactly what we need. + ++value; + + // Set the tile coordinates based on the new value. + // Bit 0 in value replaces bit 0 of the X coordinate. + const uint32_t one = 1; + this->_current.x = (this->_current.x & ~one) | (value & 1); + + // Value is then shifted right one bit, so it will be 0, 1, or 2. + // 0 and 1 are the bottom or top children, while 2 indicates "end" (one past + // the last child). So we just clear the low bit of the current Y coordinate + // and add this shifted value to produce the new Y coordinate. + this->_current.y = (this->_current.y & ~one) + (value >> 1); + + return *this; +} + +QuadtreeChildren::iterator QuadtreeChildren::iterator::operator++(int) { + iterator copy = *this; + ++copy; + return copy; +} + +bool QuadtreeChildren::iterator::operator==( + const iterator& rhs) const noexcept { + return this->_current == rhs._current; +} + +bool QuadtreeChildren::iterator::operator!=( + const iterator& rhs) const noexcept { + return this->_current != rhs._current; +} + +OctreeChildren::iterator::iterator( + const CesiumGeometry::OctreeTileID& parentTileID, + bool isEnd) noexcept + : _current( + parentTileID.level + 1, + parentTileID.x << 1, + parentTileID.y << 1, + parentTileID.z << 1) { + if (isEnd) { + this->_current.z += 2; + } +} + +OctreeChildren::iterator& OctreeChildren::iterator::operator++() { + // Put an indication of the child in the three low bits of `value`. + // Bit 0 indicates left child (0) or right child (1). + // Bit 1 indicates front child (0) or back child (1). + // Bit 2 indicates bottom child (0) or top child (1). + uint32_t value = ((this->_current.z & 1) << 2) | + ((this->_current.y & 1) << 1) | (this->_current.x & 1); + + // Add one to the current value to get the value for the next child. + // Bit cascade from addition gives us exactly what we need. + ++value; + + // Set the tile coordinates based on the new value. + // Bit 0 in value replaces bit 0 of the X coordinate. + // Bit 1 in the value replaces bit 0 of the Y coordinate. + const uint32_t one = 1; + this->_current.x = (this->_current.x & ~one) | (value & 1); + this->_current.y = (this->_current.y & ~one) | ((value >> 1) & 1); + + // Value is then shifted right one bit, so it will be 0, 1, or 2. + // 0 and 1 are the bottom or top children, while 2 indicates "end" (one past + // the last child). So we just clear the low bit of the current Z coordinate + // and add this shifted value to produce the new Z coordinate. + this->_current.z = (this->_current.z & ~one) + (value >> 2); + + return *this; +} + +OctreeChildren::iterator OctreeChildren::iterator::operator++(int) { + iterator copy = *this; + ++copy; + return copy; +} + +bool OctreeChildren::iterator::operator==(const iterator& rhs) const noexcept { + return this->_current == rhs._current; +} + +bool OctreeChildren::iterator::operator!=(const iterator& rhs) const noexcept { + return this->_current != rhs._current; +} + +QuadtreeChildren::const_iterator +Cesium3DTilesContent::QuadtreeChildren::begin() const noexcept { + return const_iterator(this->_tileID, false); +} + +QuadtreeChildren::const_iterator QuadtreeChildren::end() const noexcept { + return const_iterator(this->_tileID, true); +} + +OctreeChildren::const_iterator +Cesium3DTilesContent::OctreeChildren::begin() const noexcept { + return const_iterator(this->_tileID, false); +} + +OctreeChildren::const_iterator OctreeChildren::end() const noexcept { + return const_iterator(this->_tileID, true); +} + +} // namespace Cesium3DTilesContent diff --git a/Cesium3DTilesContent/src/SubtreeAvailability.cpp b/Cesium3DTilesContent/src/SubtreeAvailability.cpp index 6a89bfb2a..fadbc70aa 100644 --- a/Cesium3DTilesContent/src/SubtreeAvailability.cpp +++ b/Cesium3DTilesContent/src/SubtreeAvailability.cpp @@ -1,5 +1,8 @@ +#include #include #include +#include +#include #include #include @@ -462,6 +465,26 @@ SubtreeAvailability::SubtreeAvailability( "Only support quadtree and octree"); } +bool SubtreeAvailability::isTileAvailable( + const CesiumGeometry::QuadtreeTileID& subtreeID, + const CesiumGeometry::QuadtreeTileID& tileID) const noexcept { + uint64_t relativeTileMortonIdx = + ImplicitTilingUtilities::computeRelativeMortonIndex(subtreeID, tileID); + return this->isTileAvailable( + tileID.level - subtreeID.level, + relativeTileMortonIdx); +} + +bool SubtreeAvailability::isTileAvailable( + const CesiumGeometry::OctreeTileID& subtreeID, + const CesiumGeometry::OctreeTileID& tileID) const noexcept { + uint64_t relativeTileMortonIdx = + ImplicitTilingUtilities::computeRelativeMortonIndex(subtreeID, tileID); + return this->isTileAvailable( + tileID.level - subtreeID.level, + relativeTileMortonIdx); +} + bool SubtreeAvailability::isTileAvailable( uint32_t relativeTileLevel, uint64_t relativeTileMortonId) const noexcept { @@ -471,6 +494,30 @@ bool SubtreeAvailability::isTileAvailable( this->_tileAvailability); } +bool SubtreeAvailability::isContentAvailable( + const CesiumGeometry::QuadtreeTileID& subtreeID, + const CesiumGeometry::QuadtreeTileID& tileID, + uint64_t contentId) const noexcept { + uint64_t relativeTileMortonIdx = + ImplicitTilingUtilities::computeRelativeMortonIndex(subtreeID, tileID); + return this->isContentAvailable( + tileID.level - subtreeID.level, + relativeTileMortonIdx, + contentId); +} + +bool SubtreeAvailability::isContentAvailable( + const CesiumGeometry::OctreeTileID& subtreeID, + const CesiumGeometry::OctreeTileID& tileID, + uint64_t contentId) const noexcept { + uint64_t relativeTileMortonIdx = + ImplicitTilingUtilities::computeRelativeMortonIndex(subtreeID, tileID); + return this->isContentAvailable( + tileID.level - subtreeID.level, + relativeTileMortonIdx, + contentId); +} + bool SubtreeAvailability::isContentAvailable( uint32_t relativeTileLevel, uint64_t relativeTileMortonId, diff --git a/Cesium3DTilesContent/test/TestImplicitTilingUtilities.cpp b/Cesium3DTilesContent/test/TestImplicitTilingUtilities.cpp new file mode 100644 index 000000000..83a7085ef --- /dev/null +++ b/Cesium3DTilesContent/test/TestImplicitTilingUtilities.cpp @@ -0,0 +1,379 @@ +#include +#include +#include +#include + +#include +#include + +#include + +using namespace CesiumGeometry; +using namespace CesiumGeospatial; +using namespace Cesium3DTilesContent; + +TEST_CASE("ImplicitTilingUtilities child tile iteration") { + SECTION("QuadtreeTileID") { + QuadtreeTileID parent(11, 2, 3); + + QuadtreeChildren children = ImplicitTilingUtilities::getChildren(parent); + + // Check we can enumerate the children with a range-based for loop. + int count = 0; + for (const QuadtreeTileID& tileID : children) { + CHECK(tileID.level == 12); + CHECK((tileID.x == 4 || tileID.x == 5)); + CHECK((tileID.y == 6 || tileID.y == 7)); + ++count; + } + + CHECK(count == 4); + + // Check we have exactly the right children. + std::vector expected{ + QuadtreeTileID(12, 4, 6), + QuadtreeTileID(12, 5, 6), + QuadtreeTileID(12, 4, 7), + QuadtreeTileID(12, 5, 7)}; + auto mismatch = std::mismatch( + children.begin(), + children.end(), + expected.begin(), + expected.end()); + CHECK(mismatch.first == children.end()); + CHECK(mismatch.second == expected.end()); + } + + SECTION("OctreeTileID") { + OctreeTileID parent(11, 2, 3, 4); + + OctreeChildren children = ImplicitTilingUtilities::getChildren(parent); + + // Check we can enumerate the children with a range-based for loop. + int count = 0; + for (const OctreeTileID& tileID : children) { + CHECK(tileID.level == 12); + CHECK((tileID.x == 4 || tileID.x == 5)); + CHECK((tileID.y == 6 || tileID.y == 7)); + CHECK((tileID.z == 8 || tileID.z == 9)); + ++count; + } + + CHECK(count == 8); + + // Check we have exactly the right children. + std::vector expected{ + OctreeTileID(12, 4, 6, 8), + OctreeTileID(12, 5, 6, 8), + OctreeTileID(12, 4, 7, 8), + OctreeTileID(12, 5, 7, 8), + OctreeTileID(12, 4, 6, 9), + OctreeTileID(12, 5, 6, 9), + OctreeTileID(12, 4, 7, 9), + OctreeTileID(12, 5, 7, 9)}; + auto mismatch = std::mismatch( + children.begin(), + children.end(), + expected.begin(), + expected.end()); + CHECK(mismatch.first == children.end()); + CHECK(mismatch.second == expected.end()); + } +} + +TEST_CASE("ImplicitTilingUtilities::resolveUrl") { + SECTION("quadtree") { + QuadtreeTileID tileID(11, 2, 3); + std::string url = ImplicitTilingUtilities::resolveUrl( + "https://example.com", + "tiles/{level}/{x}/{y}", + tileID); + CHECK(url == "https://example.com/tiles/11/2/3"); + } + + SECTION("octree") { + OctreeTileID tileID(11, 2, 3, 4); + std::string url = ImplicitTilingUtilities::resolveUrl( + "https://example.com", + "tiles/{level}/{x}/{y}/{z}", + tileID); + CHECK(url == "https://example.com/tiles/11/2/3/4"); + } +} + +TEST_CASE("ImplicitTilingUtilities::computeMortonIndex") { + SECTION("quadtree") { + QuadtreeTileID tileID(11, 2, 3); + CHECK( + ImplicitTilingUtilities::computeMortonIndex(tileID) == + libmorton::morton2D_64_encode(2, 3)); + } + + SECTION("quadtree") { + OctreeTileID tileID(11, 2, 3, 4); + CHECK( + ImplicitTilingUtilities::computeMortonIndex(tileID) == + libmorton::morton3D_64_encode(2, 3, 4)); + } +} + +TEST_CASE("ImplicitTilingUtilities::computeRelativeMortonIndex") { + SECTION("quadtree") { + QuadtreeTileID rootID(11, 2, 3); + QuadtreeTileID tileID(12, 5, 6); + CHECK( + ImplicitTilingUtilities::computeRelativeMortonIndex(rootID, tileID) == + 1); + } + + SECTION("octree") { + OctreeTileID rootID(11, 2, 3, 4); + OctreeTileID tileID(12, 5, 6, 8); + CHECK( + ImplicitTilingUtilities::computeRelativeMortonIndex(rootID, tileID) == + 1); + } +} + +TEST_CASE("ImplicitTilingUtilities::getSubtreeRootID") { + SECTION("quadtree") { + QuadtreeTileID tileID(10, 2, 3); + CHECK( + ImplicitTilingUtilities::getSubtreeRootID(5, tileID) == + QuadtreeTileID(10, 2, 3)); + CHECK( + ImplicitTilingUtilities::getSubtreeRootID(4, tileID) == + QuadtreeTileID(8, 0, 0)); + } + + SECTION("octree") { + OctreeTileID tileID(10, 2, 3, 4); + CHECK( + ImplicitTilingUtilities::getSubtreeRootID(5, tileID) == + OctreeTileID(10, 2, 3, 4)); + CHECK( + ImplicitTilingUtilities::getSubtreeRootID(4, tileID) == + OctreeTileID(8, 0, 0, 1)); + } +} + +TEST_CASE("ImplicitTilingUtilities::absoluteTileIDToRelative") { + SECTION("quadtree") { + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + QuadtreeTileID(0, 0, 0), + QuadtreeTileID(11, 2, 3)) == QuadtreeTileID(11, 2, 3)); + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + QuadtreeTileID(11, 2, 3), + QuadtreeTileID(11, 2, 3)) == QuadtreeTileID(0, 0, 0)); + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + QuadtreeTileID(11, 2, 3), + QuadtreeTileID(12, 5, 7)) == QuadtreeTileID(1, 1, 1)); + } + + SECTION("octree") { + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + OctreeTileID(0, 0, 0, 0), + OctreeTileID(11, 2, 3, 4)) == OctreeTileID(11, 2, 3, 4)); + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + OctreeTileID(11, 2, 3, 4), + OctreeTileID(11, 2, 3, 4)) == OctreeTileID(0, 0, 0, 0)); + CHECK( + ImplicitTilingUtilities::absoluteTileIDToRelative( + OctreeTileID(11, 2, 3, 4), + OctreeTileID(12, 5, 7, 9)) == OctreeTileID(1, 1, 1, 1)); + } +} + +TEST_CASE("ImplicitTilingUtilities::computeLevelDenominator") { + CHECK(ImplicitTilingUtilities::computeLevelDenominator(0) == 1.0); + CHECK(ImplicitTilingUtilities::computeLevelDenominator(1) == 2.0); + CHECK(ImplicitTilingUtilities::computeLevelDenominator(2) == 4.0); +} + +TEST_CASE("ImplicitTilingUtilities::computeBoundingVolume") { + SECTION("OrientedBoundingBox") { + SECTION("quadtree") { + OrientedBoundingBox root(glm::dvec3(1.0, 2.0, 3.0), glm::dmat3(10.0)); + + OrientedBoundingBox l1x0y0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 0)); + CHECK(l1x0y0.getCenter() == glm::dvec3(-4.0, -3.0, 3.0)); + CHECK(l1x0y0.getLengths() == glm::dvec3(10.0, 10.0, 20.0)); + + OrientedBoundingBox l1x1y0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 1, 0)); + CHECK(l1x1y0.getCenter() == glm::dvec3(6.0, -3.0, 3.0)); + CHECK(l1x1y0.getLengths() == glm::dvec3(10.0, 10.0, 20.0)); + + OrientedBoundingBox l1x0y1 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 1)); + CHECK(l1x0y1.getCenter() == glm::dvec3(-4.0, 7.0, 3.0)); + CHECK(l1x0y1.getLengths() == glm::dvec3(10.0, 10.0, 20.0)); + } + + SECTION("octree") { + OrientedBoundingBox root(glm::dvec3(1.0, 2.0, 3.0), glm::dmat3(10.0)); + + OrientedBoundingBox l1x0y0z0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 0, 0)); + CHECK(l1x0y0z0.getCenter() == glm::dvec3(-4.0, -3.0, -2.0)); + CHECK(l1x0y0z0.getLengths() == glm::dvec3(10.0, 10.0, 10.0)); + + OrientedBoundingBox l1x1y0z0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 1, 0, 0)); + CHECK(l1x1y0z0.getCenter() == glm::dvec3(6.0, -3.0, -2.0)); + CHECK(l1x1y0z0.getLengths() == glm::dvec3(10.0, 10.0, 10.0)); + + OrientedBoundingBox l1x0y1z0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 1, 0)); + CHECK(l1x0y1z0.getCenter() == glm::dvec3(-4.0, 7.0, -2.0)); + CHECK(l1x0y1z0.getLengths() == glm::dvec3(10.0, 10.0, 10.0)); + + OrientedBoundingBox l1x0y0z1 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 0, 1)); + CHECK(l1x0y0z1.getCenter() == glm::dvec3(-4.0, -3.0, 8.0)); + CHECK(l1x0y0z1.getLengths() == glm::dvec3(10.0, 10.0, 10.0)); + } + } + + SECTION("BoundingRegion") { + SECTION("quadtree") { + BoundingRegion root(GlobeRectangle(1.0, 2.0, 3.0, 4.0), 10.0, 20.0); + + BoundingRegion l1x0y0 = ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 0)); + CHECK(l1x0y0.getRectangle().getWest() == 1.0); + CHECK(l1x0y0.getRectangle().getSouth() == 2.0); + CHECK(l1x0y0.getRectangle().getEast() == 2.0); + CHECK(l1x0y0.getRectangle().getNorth() == 3.0); + CHECK(l1x0y0.getMinimumHeight() == 10.0); + CHECK(l1x0y0.getMaximumHeight() == 20.0); + + BoundingRegion l1x1y0 = ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 1, 0)); + CHECK(l1x1y0.getRectangle().getWest() == 2.0); + CHECK(l1x1y0.getRectangle().getSouth() == 2.0); + CHECK(l1x1y0.getRectangle().getEast() == 3.0); + CHECK(l1x1y0.getRectangle().getNorth() == 3.0); + CHECK(l1x1y0.getMinimumHeight() == 10.0); + CHECK(l1x1y0.getMaximumHeight() == 20.0); + + BoundingRegion l1x0y1 = ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 1)); + CHECK(l1x0y1.getRectangle().getWest() == 1.0); + CHECK(l1x0y1.getRectangle().getSouth() == 3.0); + CHECK(l1x0y1.getRectangle().getEast() == 2.0); + CHECK(l1x0y1.getRectangle().getNorth() == 4.0); + CHECK(l1x0y1.getMinimumHeight() == 10.0); + CHECK(l1x0y1.getMaximumHeight() == 20.0); + } + + SECTION("octree") { + BoundingRegion root(GlobeRectangle(1.0, 2.0, 3.0, 4.0), 10.0, 20.0); + + BoundingRegion l1x0y0z0 = ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 0, 0)); + CHECK(l1x0y0z0.getRectangle().getWest() == 1.0); + CHECK(l1x0y0z0.getRectangle().getSouth() == 2.0); + CHECK(l1x0y0z0.getRectangle().getEast() == 2.0); + CHECK(l1x0y0z0.getRectangle().getNorth() == 3.0); + CHECK(l1x0y0z0.getMinimumHeight() == 10.0); + CHECK(l1x0y0z0.getMaximumHeight() == 15.0); + + BoundingRegion l1x1y0z0 = ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 1, 0, 0)); + CHECK(l1x1y0z0.getRectangle().getWest() == 2.0); + CHECK(l1x1y0z0.getRectangle().getSouth() == 2.0); + CHECK(l1x1y0z0.getRectangle().getEast() == 3.0); + CHECK(l1x1y0z0.getRectangle().getNorth() == 3.0); + CHECK(l1x1y0z0.getMinimumHeight() == 10.0); + CHECK(l1x1y0z0.getMaximumHeight() == 15.0); + + BoundingRegion l1x0y1z0 = ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 1, 0)); + CHECK(l1x0y1z0.getRectangle().getWest() == 1.0); + CHECK(l1x0y1z0.getRectangle().getSouth() == 3.0); + CHECK(l1x0y1z0.getRectangle().getEast() == 2.0); + CHECK(l1x0y1z0.getRectangle().getNorth() == 4.0); + CHECK(l1x0y1z0.getMinimumHeight() == 10.0); + CHECK(l1x0y1z0.getMaximumHeight() == 15.0); + + BoundingRegion l1x0y0z1 = ImplicitTilingUtilities::computeBoundingVolume( + root, + OctreeTileID(1, 0, 0, 1)); + CHECK(l1x0y0z1.getRectangle().getWest() == 1.0); + CHECK(l1x0y0z1.getRectangle().getSouth() == 2.0); + CHECK(l1x0y0z1.getRectangle().getEast() == 2.0); + CHECK(l1x0y0z1.getRectangle().getNorth() == 3.0); + CHECK(l1x0y0z1.getMinimumHeight() == 15.0); + CHECK(l1x0y0z1.getMaximumHeight() == 20.0); + } + } + + SECTION("S2") { + SECTION("quadtree") { + S2CellBoundingVolume root( + S2CellID::fromQuadtreeTileID(1, QuadtreeTileID(0, 0, 0)), + 10.0, + 20.0); + + S2CellBoundingVolume l1x0y0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 0)); + CHECK(l1x0y0.getCellID().getFace() == 1); + CHECK( + l1x0y0.getCellID().getID() == + S2CellID::fromQuadtreeTileID(1, QuadtreeTileID(1, 0, 0)).getID()); + CHECK(l1x0y0.getMinimumHeight() == 10.0); + CHECK(l1x0y0.getMaximumHeight() == 20.0); + + S2CellBoundingVolume l1x1y0 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 1, 0)); + CHECK(l1x1y0.getCellID().getFace() == 1); + CHECK( + l1x1y0.getCellID().getID() == + S2CellID::fromQuadtreeTileID(1, QuadtreeTileID(1, 1, 0)).getID()); + CHECK(l1x1y0.getMinimumHeight() == 10.0); + CHECK(l1x1y0.getMaximumHeight() == 20.0); + + S2CellBoundingVolume l1x0y1 = + ImplicitTilingUtilities::computeBoundingVolume( + root, + QuadtreeTileID(1, 0, 1)); + CHECK(l1x0y1.getCellID().getFace() == 1); + CHECK( + l1x0y1.getCellID().getID() == + S2CellID::fromQuadtreeTileID(1, QuadtreeTileID(1, 0, 1)).getID()); + CHECK(l1x0y1.getMinimumHeight() == 10.0); + CHECK(l1x0y1.getMaximumHeight() == 20.0); + } + } +} diff --git a/Cesium3DTilesSelection/src/ImplicitOctreeLoader.cpp b/Cesium3DTilesSelection/src/ImplicitOctreeLoader.cpp index 10e3c3080..04d0e6589 100644 --- a/Cesium3DTilesSelection/src/ImplicitOctreeLoader.cpp +++ b/Cesium3DTilesSelection/src/ImplicitOctreeLoader.cpp @@ -3,66 +3,26 @@ #include "logTileLoadResult.h" #include +#include #include #include #include -#include #include +#include + using namespace Cesium3DTilesContent; namespace Cesium3DTilesSelection { namespace { struct BoundingVolumeSubdivision { BoundingVolume operator()(const CesiumGeospatial::BoundingRegion& region) { - const CesiumGeospatial::GlobeRectangle& globeRect = region.getRectangle(); - double denominator = static_cast(1 << tileID.level); - double latSize = - (globeRect.getNorth() - globeRect.getSouth()) / denominator; - double longSize = (globeRect.getEast() - globeRect.getWest()) / denominator; - double heightSize = - (region.getMaximumHeight() - region.getMinimumHeight()) / denominator; - - double childWest = globeRect.getWest() + longSize * tileID.x; - double childEast = globeRect.getWest() + longSize * (tileID.x + 1); - - double childSouth = globeRect.getSouth() + latSize * tileID.y; - double childNorth = globeRect.getSouth() + latSize * (tileID.y + 1); - - double childMinHeight = region.getMinimumHeight() + heightSize * tileID.z; - double childMaxHeight = - region.getMinimumHeight() + heightSize * (tileID.z + 1); - - return CesiumGeospatial::BoundingRegion{ - CesiumGeospatial::GlobeRectangle( - childWest, - childSouth, - childEast, - childNorth), - childMinHeight, - childMaxHeight}; + return ImplicitTilingUtilities::computeBoundingVolume(region, this->tileID); } BoundingVolume operator()(const CesiumGeometry::OrientedBoundingBox& obb) { - const glm::dmat3& halfAxes = obb.getHalfAxes(); - const glm::dvec3& center = obb.getCenter(); - - double denominator = static_cast(1 << tileID.level); - glm::dvec3 min = center - halfAxes[0] - halfAxes[1] - halfAxes[2]; - - glm::dvec3 xDim = halfAxes[0] * 2.0 / denominator; - glm::dvec3 yDim = halfAxes[1] * 2.0 / denominator; - glm::dvec3 zDim = halfAxes[2] * 2.0 / denominator; - glm::dvec3 childMin = min + xDim * double(tileID.x) + - yDim * double(tileID.y) + zDim * double(tileID.z); - glm::dvec3 childMax = min + xDim * double(tileID.x + 1) + - yDim * double(tileID.y + 1) + - zDim * double(tileID.z + 1); - - return CesiumGeometry::OrientedBoundingBox( - (childMin + childMax) / 2.0, - glm::dmat3{xDim / 2.0, yDim / 2.0, zDim / 2.0}); + return ImplicitTilingUtilities::computeBoundingVolume(obb, this->tileID); } const CesiumGeometry::OctreeTileID& tileID; @@ -77,68 +37,59 @@ BoundingVolume subdivideBoundingVolume( std::vector populateSubtree( const SubtreeAvailability& subtreeAvailability, uint32_t subtreeLevels, - uint32_t relativeTileLevel, - uint64_t relativeTileMortonID, + const CesiumGeometry::OctreeTileID& subtreeRootID, const Tile& tile, ImplicitOctreeLoader& loader) { + const CesiumGeometry::OctreeTileID& octreeID = + std::get(tile.getTileID()); + + uint32_t relativeTileLevel = octreeID.level - subtreeRootID.level; if (relativeTileLevel >= subtreeLevels) { return {}; } - const CesiumGeometry::OctreeTileID& octreeID = - std::get(tile.getTileID()); + OctreeChildren childIDs = ImplicitTilingUtilities::getChildren(octreeID); std::vector children; - children.reserve(8); - for (uint16_t y = 0; y < 2; ++y) { - uint32_t childY = (octreeID.y << 1) | y; - for (uint16_t z = 0; z < 2; ++z) { - uint32_t childZ = (octreeID.z << 1) | z; - for (uint16_t x = 0; x < 2; ++x) { - uint32_t childX = (octreeID.x << 1) | x; - - CesiumGeometry::OctreeTileID childID{ - octreeID.level + 1, - childX, - childY, - childZ}; - - uint32_t childIndex = - static_cast(libmorton::morton3D_32_encode(x, y, z)); - uint64_t relativeChildMortonID = relativeTileMortonID << 3 | childIndex; - uint32_t relativeChildLevel = relativeTileLevel + 1; - if (relativeChildLevel == subtreeLevels) { - if (subtreeAvailability.isSubtreeAvailable(relativeChildMortonID)) { - Tile& child = children.emplace_back(&loader); - child.setTransform(tile.getTransform()); - child.setBoundingVolume( - subdivideBoundingVolume(childID, loader.getBoundingVolume())); - child.setGeometricError(tile.getGeometricError() * 0.5); - child.setRefine(tile.getRefine()); - child.setTileID(childID); - } + children.reserve(childIDs.size()); + + for (const CesiumGeometry::OctreeTileID& childID : childIDs) { + uint64_t relativeChildMortonID = + ImplicitTilingUtilities::computeRelativeMortonIndex( + subtreeRootID, + childID); + + uint32_t relativeChildLevel = relativeTileLevel + 1; + if (relativeChildLevel == subtreeLevels) { + if (subtreeAvailability.isSubtreeAvailable(relativeChildMortonID)) { + Tile& child = children.emplace_back(&loader); + child.setTransform(tile.getTransform()); + child.setBoundingVolume( + subdivideBoundingVolume(childID, loader.getBoundingVolume())); + child.setGeometricError(tile.getGeometricError() * 0.5); + child.setRefine(tile.getRefine()); + child.setTileID(childID); + } + } else { + if (subtreeAvailability.isTileAvailable( + relativeChildLevel, + relativeChildMortonID)) { + if (subtreeAvailability.isContentAvailable( + relativeChildLevel, + relativeChildMortonID, + 0)) { + children.emplace_back(&loader); } else { - if (subtreeAvailability.isTileAvailable( - relativeChildLevel, - relativeChildMortonID)) { - if (subtreeAvailability.isContentAvailable( - relativeChildLevel, - relativeChildMortonID, - 0)) { - children.emplace_back(&loader); - } else { - children.emplace_back(&loader, TileEmptyContent{}); - } - - Tile& child = children.back(); - child.setTransform(tile.getTransform()); - child.setBoundingVolume( - subdivideBoundingVolume(childID, loader.getBoundingVolume())); - child.setGeometricError(tile.getGeometricError() * 0.5); - child.setRefine(tile.getRefine()); - child.setTileID(childID); - } + children.emplace_back(&loader, TileEmptyContent{}); } + + Tile& child = children.back(); + child.setTransform(tile.getTransform()); + child.setBoundingVolume( + subdivideBoundingVolume(childID, loader.getBoundingVolume())); + child.setGeometricError(tile.getGeometricError() * 0.5); + child.setRefine(tile.getRefine()); + child.setTileID(childID); } } } @@ -146,21 +97,6 @@ std::vector populateSubtree( return children; } -bool isTileContentAvailable( - const CesiumGeometry::OctreeTileID& subtreeID, - const CesiumGeometry::OctreeTileID& octreeID, - const SubtreeAvailability& subtreeAvailability) { - uint32_t relativeTileLevel = octreeID.level - subtreeID.level; - uint64_t relativeTileMortonIdx = libmorton::morton3D_64_encode( - octreeID.x - (subtreeID.x << relativeTileLevel), - octreeID.y - (subtreeID.y << relativeTileLevel), - octreeID.z - (subtreeID.z << relativeTileLevel)); - return subtreeAvailability.isContentAvailable( - relativeTileLevel, - relativeTileMortonIdx, - 0); -} - CesiumAsync::Future requestTileContent( const std::shared_ptr& pLogger, const CesiumAsync::AsyncSystem& asyncSystem, @@ -251,31 +187,26 @@ ImplicitOctreeLoader::loadTileContent(const TileLoadInput& loadInput) { } // find the subtree ID - uint32_t subtreeLevelIdx = pOctreeID->level / this->_subtreeLevels; - if (subtreeLevelIdx >= this->_loadedSubtrees.size()) { - return asyncSystem.createResolvedFuture( + CesiumGeometry::OctreeTileID subtreeID = + ImplicitTilingUtilities::getSubtreeRootID( + this->_subtreeLevels, + *pOctreeID); + uint32_t subtreeLevelIdx = subtreeID.level / this->_subtreeLevels; + if (subtreeLevelIdx >= _loadedSubtrees.size()) { + return asyncSystem.createResolvedFuture( TileLoadResult::createFailedResult(nullptr)); } - uint64_t levelLeft = pOctreeID->level % this->_subtreeLevels; - uint32_t subtreeLevel = this->_subtreeLevels * subtreeLevelIdx; - uint32_t subtreeX = pOctreeID->x >> levelLeft; - uint32_t subtreeY = pOctreeID->y >> levelLeft; - uint32_t subtreeZ = pOctreeID->z >> levelLeft; - CesiumGeometry::OctreeTileID subtreeID{ - subtreeLevel, - subtreeX, - subtreeY, - subtreeZ}; - uint64_t subtreeMortonIdx = - libmorton::morton3D_64_encode(subtreeX, subtreeY, subtreeZ); + ImplicitTilingUtilities::computeMortonIndex(subtreeID); auto subtreeIt = this->_loadedSubtrees[subtreeLevelIdx].find(subtreeMortonIdx); if (subtreeIt == this->_loadedSubtrees[subtreeLevelIdx].end()) { // subtree is not loaded, so load it now. - std::string subtreeUrl = - resolveUrl(this->_baseUrl, this->_subtreeUrlTemplate, subtreeID); + std::string subtreeUrl = ImplicitTilingUtilities::resolveUrl( + this->_baseUrl, + this->_subtreeUrlTemplate, + subtreeID); return SubtreeAvailability::loadSubtree( 3, asyncSystem, @@ -298,7 +229,7 @@ ImplicitOctreeLoader::loadTileContent(const TileLoadInput& loadInput) { // subtree is available, so check if tile has content or not. If it has, then // request it - if (!isTileContentAvailable(subtreeID, *pOctreeID, subtreeIt->second)) { + if (!subtreeIt->second.isContentAvailable(subtreeID, *pOctreeID, 0)) { // check if tile has empty content return asyncSystem.createResolvedFuture(TileLoadResult{ TileEmptyContent{}, @@ -311,8 +242,10 @@ ImplicitOctreeLoader::loadTileContent(const TileLoadInput& loadInput) { TileLoadResultState::Success}); } - std::string tileUrl = - resolveUrl(this->_baseUrl, this->_contentUrlTemplate, *pOctreeID); + std::string tileUrl = ImplicitTilingUtilities::resolveUrl( + this->_baseUrl, + this->_contentUrlTemplate, + *pOctreeID); return requestTileContent( pLogger, asyncSystem, @@ -325,33 +258,29 @@ ImplicitOctreeLoader::loadTileContent(const TileLoadInput& loadInput) { TileChildrenResult ImplicitOctreeLoader::createTileChildren(const Tile& tile) { const CesiumGeometry::OctreeTileID* pOctreeID = std::get_if(&tile.getTileID()); - assert(pOctreeID != nullptr && "This loader only serves quadtree tile"); + assert(pOctreeID != nullptr && "This loader only serves octree tile"); // find the subtree ID - uint32_t subtreeLevelIdx = pOctreeID->level / this->_subtreeLevels; + CesiumGeometry::OctreeTileID subtreeID = + ImplicitTilingUtilities::getSubtreeRootID( + this->_subtreeLevels, + *pOctreeID); + + uint32_t subtreeLevelIdx = subtreeID.level / this->_subtreeLevels; if (subtreeLevelIdx >= this->_loadedSubtrees.size()) { return {{}, TileLoadResultState::Failed}; } - uint64_t levelLeft = pOctreeID->level % this->_subtreeLevels; - uint32_t subtreeX = pOctreeID->x >> levelLeft; - uint32_t subtreeY = pOctreeID->y >> levelLeft; - uint32_t subtreeZ = pOctreeID->z >> levelLeft; - uint64_t subtreeMortonIdx = - libmorton::morton3D_64_encode(subtreeX, subtreeY, subtreeZ); + ImplicitTilingUtilities::computeMortonIndex(subtreeID); + auto subtreeIt = this->_loadedSubtrees[subtreeLevelIdx].find(subtreeMortonIdx); if (subtreeIt != this->_loadedSubtrees[subtreeLevelIdx].end()) { - uint64_t relativeTileMortonIdx = libmorton::morton3D_64_encode( - pOctreeID->x - (subtreeX << levelLeft), - pOctreeID->y - (subtreeY << levelLeft), - pOctreeID->z - (subtreeZ << levelLeft)); auto children = populateSubtree( subtreeIt->second, this->_subtreeLevels, - static_cast(levelLeft), - relativeTileMortonIdx, + subtreeID, tile, *this); @@ -383,36 +312,10 @@ void ImplicitOctreeLoader::addSubtreeAvailability( } uint64_t subtreeMortonID = - libmorton::morton3D_64_encode(subtreeID.x, subtreeID.y, subtreeID.z); + ImplicitTilingUtilities::computeMortonIndex(subtreeID); this->_loadedSubtrees[levelIndex].insert_or_assign( subtreeMortonID, std::move(subtreeAvailability)); } - -std::string ImplicitOctreeLoader::resolveUrl( - const std::string& baseUrl, - const std::string& urlTemplate, - const CesiumGeometry::OctreeTileID& octreeID) { - std::string url = CesiumUtility::Uri::substituteTemplateParameters( - urlTemplate, - [&octreeID](const std::string& placeholder) { - if (placeholder == "level") { - return std::to_string(octreeID.level); - } - if (placeholder == "x") { - return std::to_string(octreeID.x); - } - if (placeholder == "y") { - return std::to_string(octreeID.y); - } - if (placeholder == "z") { - return std::to_string(octreeID.z); - } - - return placeholder; - }); - - return CesiumUtility::Uri::resolve(baseUrl, url); -} } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/ImplicitOctreeLoader.h b/Cesium3DTilesSelection/src/ImplicitOctreeLoader.h index bb6a88bc0..c49c59f95 100644 --- a/Cesium3DTilesSelection/src/ImplicitOctreeLoader.h +++ b/Cesium3DTilesSelection/src/ImplicitOctreeLoader.h @@ -53,11 +53,6 @@ class ImplicitOctreeLoader : public TilesetContentLoader { Cesium3DTilesContent::SubtreeAvailability&& subtreeAvailability); private: - static std::string resolveUrl( - const std::string& baseUrl, - const std::string& urlTemplate, - const CesiumGeometry::OctreeTileID& octreeID); - std::string _baseUrl; std::string _contentUrlTemplate; std::string _subtreeUrlTemplate; diff --git a/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.cpp b/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.cpp index 557596e00..e8dadf7df 100644 --- a/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.cpp +++ b/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.cpp @@ -3,15 +3,16 @@ #include "logTileLoadResult.h" #include +#include #include #include #include #include -#include #include #include +#include #include using namespace Cesium3DTilesContent; @@ -20,55 +21,18 @@ namespace Cesium3DTilesSelection { namespace { struct BoundingVolumeSubdivision { BoundingVolume operator()(const CesiumGeospatial::BoundingRegion& region) { - const CesiumGeospatial::GlobeRectangle& globeRect = region.getRectangle(); - double denominator = static_cast(1 << tileID.level); - double latSize = - (globeRect.getNorth() - globeRect.getSouth()) / denominator; - double longSize = (globeRect.getEast() - globeRect.getWest()) / denominator; - - double childWest = globeRect.getWest() + longSize * tileID.x; - double childEast = globeRect.getWest() + longSize * (tileID.x + 1); - - double childSouth = globeRect.getSouth() + latSize * tileID.y; - double childNorth = globeRect.getSouth() + latSize * (tileID.y + 1); - - return CesiumGeospatial::BoundingRegion{ - CesiumGeospatial::GlobeRectangle( - childWest, - childSouth, - childEast, - childNorth), - region.getMinimumHeight(), - region.getMaximumHeight()}; + return ImplicitTilingUtilities::computeBoundingVolume(region, this->tileID); } BoundingVolume operator()(const CesiumGeospatial::S2CellBoundingVolume& s2Volume) { - return CesiumGeospatial::S2CellBoundingVolume( - CesiumGeospatial::S2CellID::fromQuadtreeTileID( - s2Volume.getCellID().getFace(), - tileID), - s2Volume.getMinimumHeight(), - s2Volume.getMaximumHeight()); + return ImplicitTilingUtilities::computeBoundingVolume( + s2Volume, + this->tileID); } BoundingVolume operator()(const CesiumGeometry::OrientedBoundingBox& obb) { - const glm::dmat3& halfAxes = obb.getHalfAxes(); - const glm::dvec3& center = obb.getCenter(); - - double denominator = static_cast(1 << tileID.level); - glm::dvec3 min = center - halfAxes[0] - halfAxes[1] - halfAxes[2]; - - glm::dvec3 xDim = halfAxes[0] * 2.0 / denominator; - glm::dvec3 yDim = halfAxes[1] * 2.0 / denominator; - glm::dvec3 childMin = - min + xDim * double(tileID.x) + yDim * double(tileID.y); - glm::dvec3 childMax = min + xDim * double(tileID.x + 1) + - yDim * double(tileID.y + 1) + halfAxes[2] * 2.0; - - return CesiumGeometry::OrientedBoundingBox( - (childMin + childMax) / 2.0, - glm::dmat3{xDim / 2.0, yDim / 2.0, halfAxes[2]}); + return ImplicitTilingUtilities::computeBoundingVolume(obb, this->tileID); } const CesiumGeometry::QuadtreeTileID& tileID; @@ -83,63 +47,59 @@ BoundingVolume subdivideBoundingVolume( std::vector populateSubtree( const SubtreeAvailability& subtreeAvailability, uint32_t subtreeLevels, - uint32_t relativeTileLevel, - uint64_t relativeTileMortonID, + const CesiumGeometry::QuadtreeTileID& subtreeRootID, const Tile& tile, ImplicitQuadtreeLoader& loader) { + const CesiumGeometry::QuadtreeTileID& quadtreeID = + std::get(tile.getTileID()); + + uint32_t relativeTileLevel = quadtreeID.level - subtreeRootID.level; if (relativeTileLevel >= subtreeLevels) { return {}; } - const CesiumGeometry::QuadtreeTileID& quadtreeID = - std::get(tile.getTileID()); + QuadtreeChildren childIDs = ImplicitTilingUtilities::getChildren(quadtreeID); std::vector children; - children.reserve(4); - for (uint16_t y = 0; y < 2; ++y) { - uint32_t childY = (quadtreeID.y << 1) | y; - for (uint16_t x = 0; x < 2; ++x) { - uint32_t childX = (quadtreeID.x << 1) | x; - CesiumGeometry::QuadtreeTileID childID{ - quadtreeID.level + 1, - childX, - childY}; - - uint32_t childIndex = - static_cast(libmorton::morton2D_32_encode(x, y)); - uint64_t relativeChildMortonID = relativeTileMortonID << 2 | childIndex; - uint32_t relativeChildLevel = relativeTileLevel + 1; - if (relativeChildLevel == subtreeLevels) { - if (subtreeAvailability.isSubtreeAvailable(relativeChildMortonID)) { - Tile& child = children.emplace_back(&loader); - child.setTransform(tile.getTransform()); - child.setBoundingVolume( - subdivideBoundingVolume(childID, loader.getBoundingVolume())); - child.setGeometricError(tile.getGeometricError() * 0.5); - child.setRefine(tile.getRefine()); - child.setTileID(childID); - } - } else { - if (subtreeAvailability.isTileAvailable( + children.reserve(childIDs.size()); + + for (const CesiumGeometry::QuadtreeTileID& childID : childIDs) { + uint64_t relativeChildMortonID = + ImplicitTilingUtilities::computeRelativeMortonIndex( + subtreeRootID, + childID); + + uint32_t relativeChildLevel = relativeTileLevel + 1; + if (relativeChildLevel == subtreeLevels) { + if (subtreeAvailability.isSubtreeAvailable(relativeChildMortonID)) { + Tile& child = children.emplace_back(&loader); + child.setTransform(tile.getTransform()); + child.setBoundingVolume( + subdivideBoundingVolume(childID, loader.getBoundingVolume())); + child.setGeometricError(tile.getGeometricError() * 0.5); + child.setRefine(tile.getRefine()); + child.setTileID(childID); + } + } else { + if (subtreeAvailability.isTileAvailable( + relativeChildLevel, + relativeChildMortonID)) { + if (subtreeAvailability.isContentAvailable( relativeChildLevel, - relativeChildMortonID)) { - if (subtreeAvailability.isContentAvailable( - relativeChildLevel, - relativeChildMortonID, - 0)) { - children.emplace_back(&loader); - } else { - children.emplace_back(&loader, TileEmptyContent{}); - } - - Tile& child = children.back(); - child.setTransform(tile.getTransform()); - child.setBoundingVolume( - subdivideBoundingVolume(childID, loader.getBoundingVolume())); - child.setGeometricError(tile.getGeometricError() * 0.5); - child.setRefine(tile.getRefine()); - child.setTileID(childID); + relativeChildMortonID, + 0)) { + children.emplace_back(&loader); + } else { + children.emplace_back(&loader, TileEmptyContent{}); } + + Tile& child = children.back(); + child.setTransform(tile.getTransform()); + child.setBoundingVolume( + subdivideBoundingVolume(childID, loader.getBoundingVolume())); + child.setGeometricError(tile.getGeometricError() * 0.5); + child.setRefine(tile.getRefine()); + child.setTileID(childID); } } } @@ -147,20 +107,6 @@ std::vector populateSubtree( return children; } -bool isTileContentAvailable( - const CesiumGeometry::QuadtreeTileID& subtreeID, - const CesiumGeometry::QuadtreeTileID& quadtreeID, - const SubtreeAvailability& subtreeAvailability) { - uint32_t relativeTileLevel = quadtreeID.level - subtreeID.level; - uint64_t relativeTileMortonIdx = libmorton::morton2D_64_encode( - quadtreeID.x - (subtreeID.x << relativeTileLevel), - quadtreeID.y - (subtreeID.y << relativeTileLevel)); - return subtreeAvailability.isContentAvailable( - relativeTileLevel, - relativeTileMortonIdx, - 0); -} - CesiumAsync::Future requestTileContent( const std::shared_ptr& pLogger, const CesiumAsync::AsyncSystem& asyncSystem, @@ -269,18 +215,16 @@ ImplicitQuadtreeLoader::loadTileContent(const TileLoadInput& loadInput) { } // find the subtree ID - uint32_t subtreeLevelIdx = pQuadtreeID->level / this->_subtreeLevels; + CesiumGeometry::QuadtreeTileID subtreeID = + ImplicitTilingUtilities::getSubtreeRootID( + this->_subtreeLevels, + *pQuadtreeID); + uint32_t subtreeLevelIdx = subtreeID.level / this->_subtreeLevels; if (subtreeLevelIdx >= _loadedSubtrees.size()) { return asyncSystem.createResolvedFuture( TileLoadResult::createFailedResult(nullptr)); } - uint64_t levelLeft = pQuadtreeID->level % this->_subtreeLevels; - uint32_t subtreeLevel = this->_subtreeLevels * subtreeLevelIdx; - uint32_t subtreeX = pQuadtreeID->x >> levelLeft; - uint32_t subtreeY = pQuadtreeID->y >> levelLeft; - CesiumGeometry::QuadtreeTileID subtreeID{subtreeLevel, subtreeX, subtreeY}; - // the below morton index hash to the subtree assumes that tileID's components // x and y never exceed 32-bit. In other words, the max levels this loader can // support is 33 which will have 4^32 tiles in the level 32th. The 64-bit @@ -292,13 +236,16 @@ ImplicitQuadtreeLoader::loadTileContent(const TileLoadInput& loadInput) { // loader will serve up to 33 levels with the level 0 being relative to the // parent loader. The solution isn't implemented at the moment, as implicit // tilesets that exceeds 33 levels are expected to be very rare - uint64_t subtreeMortonIdx = libmorton::morton2D_64_encode(subtreeX, subtreeY); + uint64_t subtreeMortonIdx = + ImplicitTilingUtilities::computeMortonIndex(subtreeID); auto subtreeIt = this->_loadedSubtrees[subtreeLevelIdx].find(subtreeMortonIdx); if (subtreeIt == this->_loadedSubtrees[subtreeLevelIdx].end()) { // subtree is not loaded, so load it now. - std::string subtreeUrl = - resolveUrl(this->_baseUrl, this->_subtreeUrlTemplate, subtreeID); + std::string subtreeUrl = ImplicitTilingUtilities::resolveUrl( + this->_baseUrl, + this->_subtreeUrlTemplate, + subtreeID); return SubtreeAvailability::loadSubtree( 2, asyncSystem, @@ -321,7 +268,7 @@ ImplicitQuadtreeLoader::loadTileContent(const TileLoadInput& loadInput) { // subtree is available, so check if tile has content or not. If it has, then // request it - if (!isTileContentAvailable(subtreeID, *pQuadtreeID, subtreeIt->second)) { + if (!subtreeIt->second.isContentAvailable(subtreeID, *pQuadtreeID, 0)) { // check if tile has empty content return asyncSystem.createResolvedFuture(TileLoadResult{ TileEmptyContent{}, @@ -334,8 +281,10 @@ ImplicitQuadtreeLoader::loadTileContent(const TileLoadInput& loadInput) { TileLoadResultState::Success}); } - std::string tileUrl = - resolveUrl(this->_baseUrl, this->_contentUrlTemplate, *pQuadtreeID); + std::string tileUrl = ImplicitTilingUtilities::resolveUrl( + this->_baseUrl, + this->_contentUrlTemplate, + *pQuadtreeID); return requestTileContent( pLogger, asyncSystem, @@ -352,27 +301,26 @@ ImplicitQuadtreeLoader::createTileChildren(const Tile& tile) { assert(pQuadtreeID != nullptr && "This loader only serves quadtree tile"); // find the subtree ID - uint32_t subtreeLevelIdx = pQuadtreeID->level / this->_subtreeLevels; + CesiumGeometry::QuadtreeTileID subtreeID = + ImplicitTilingUtilities::getSubtreeRootID( + this->_subtreeLevels, + *pQuadtreeID); + + uint32_t subtreeLevelIdx = subtreeID.level / this->_subtreeLevels; if (subtreeLevelIdx >= this->_loadedSubtrees.size()) { return {{}, TileLoadResultState::Failed}; } - uint64_t levelLeft = pQuadtreeID->level % this->_subtreeLevels; - uint32_t subtreeX = pQuadtreeID->x >> levelLeft; - uint32_t subtreeY = pQuadtreeID->y >> levelLeft; + uint64_t subtreeMortonIdx = + ImplicitTilingUtilities::computeMortonIndex(subtreeID); - uint64_t subtreeMortonIdx = libmorton::morton2D_64_encode(subtreeX, subtreeY); auto subtreeIt = this->_loadedSubtrees[subtreeLevelIdx].find(subtreeMortonIdx); if (subtreeIt != this->_loadedSubtrees[subtreeLevelIdx].end()) { - uint64_t relativeTileMortonIdx = libmorton::morton2D_64_encode( - pQuadtreeID->x - (subtreeX << levelLeft), - pQuadtreeID->y - (subtreeY << levelLeft)); auto children = populateSubtree( subtreeIt->second, this->_subtreeLevels, - static_cast(levelLeft), - relativeTileMortonIdx, + subtreeID, tile, *this); @@ -404,33 +352,10 @@ void ImplicitQuadtreeLoader::addSubtreeAvailability( } uint64_t subtreeMortonID = - libmorton::morton2D_64_encode(subtreeID.x, subtreeID.y); + ImplicitTilingUtilities::computeMortonIndex(subtreeID); this->_loadedSubtrees[levelIndex].insert_or_assign( subtreeMortonID, std::move(subtreeAvailability)); } - -std::string ImplicitQuadtreeLoader::resolveUrl( - const std::string& baseUrl, - const std::string& urlTemplate, - const CesiumGeometry::QuadtreeTileID& quadtreeID) { - std::string url = CesiumUtility::Uri::substituteTemplateParameters( - urlTemplate, - [&quadtreeID](const std::string& placeholder) { - if (placeholder == "level") { - return std::to_string(quadtreeID.level); - } - if (placeholder == "x") { - return std::to_string(quadtreeID.x); - } - if (placeholder == "y") { - return std::to_string(quadtreeID.y); - } - - return placeholder; - }); - - return CesiumUtility::Uri::resolve(baseUrl, url); -} } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.h b/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.h index 60c962042..80c3dd36a 100644 --- a/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.h +++ b/Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.h @@ -55,11 +55,6 @@ class ImplicitQuadtreeLoader : public TilesetContentLoader { Cesium3DTilesContent::SubtreeAvailability&& subtreeAvailability); private: - static std::string resolveUrl( - const std::string& baseUrl, - const std::string& urlTemplate, - const CesiumGeometry::QuadtreeTileID& quadtreeID); - std::string _baseUrl; std::string _contentUrlTemplate; std::string _subtreeUrlTemplate; diff --git a/Cesium3DTilesSelection/test/TestImplicitOctreeLoader.cpp b/Cesium3DTilesSelection/test/TestImplicitOctreeLoader.cpp index c881e8119..f7f49045a 100644 --- a/Cesium3DTilesSelection/test/TestImplicitOctreeLoader.cpp +++ b/Cesium3DTilesSelection/test/TestImplicitOctreeLoader.cpp @@ -198,6 +198,30 @@ TEST_CASE("Test implicit octree loader") { } } +namespace { + +const Tile& +findTile(const gsl::span& children, const OctreeTileID& tileID) { + auto it = std::find_if( + children.begin(), + children.end(), + [tileID](const Tile& tile) { + const OctreeTileID* pID = std::get_if(&tile.getTileID()); + if (!pID) + return false; + return *pID == tileID; + }); + REQUIRE(it != children.end()); + return *it; +} + +const Tile& +findTile(const std::vector& children, const OctreeTileID& tileID) { + return findTile(gsl::span(children), tileID); +} + +} // namespace + TEST_CASE("Test tile subdivision for implicit octree loader") { Cesium3DTilesContent::registerAllTileContentTypes(); @@ -238,10 +262,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 8); - const auto& tile_1_0_0_0 = tileChildren[0]; - CHECK( - std::get(tile_1_0_0_0.getTileID()) == - OctreeTileID(1, 0, 0, 0)); + const auto& tile_1_0_0_0 = + findTile(tileChildren, OctreeTileID(1, 0, 0, 0)); const auto& box_1_0_0_0 = std::get(tile_1_0_0_0.getBoundingVolume()); CHECK(box_1_0_0_0.getCenter() == glm::dvec3(-10.0, -10.0, -10.0)); @@ -249,10 +271,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_0_0_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_0_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_1_0_0 = tileChildren[1]; - CHECK( - std::get(tile_1_1_0_0.getTileID()) == - OctreeTileID(1, 1, 0, 0)); + const auto& tile_1_1_0_0 = + findTile(tileChildren, OctreeTileID(1, 1, 0, 0)); const auto& box_1_1_0_0 = std::get(tile_1_1_0_0.getBoundingVolume()); CHECK(box_1_1_0_0.getCenter() == glm::dvec3(10.0, -10.0, -10.0)); @@ -260,10 +280,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_1_0_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_1_0_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_0_0_1 = tileChildren[2]; - CHECK( - std::get(tile_1_0_0_1.getTileID()) == - OctreeTileID(1, 0, 0, 1)); + const auto& tile_1_0_0_1 = + findTile(tileChildren, OctreeTileID(1, 0, 0, 1)); const auto& box_1_0_0_1 = std::get(tile_1_0_0_1.getBoundingVolume()); CHECK(box_1_0_0_1.getCenter() == glm::dvec3(-10.0, -10.0, 10.0)); @@ -271,10 +289,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_0_0_1.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_0_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_1_0_1 = tileChildren[3]; - CHECK( - std::get(tile_1_1_0_1.getTileID()) == - OctreeTileID(1, 1, 0, 1)); + const auto& tile_1_1_0_1 = + findTile(tileChildren, OctreeTileID(1, 1, 0, 1)); const auto& box_1_1_0_1 = std::get(tile_1_1_0_1.getBoundingVolume()); CHECK(box_1_1_0_1.getCenter() == glm::dvec3(10.0, -10.0, 10.0)); @@ -282,10 +298,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_1_0_1.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_1_0_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_0_1_0 = tileChildren[4]; - CHECK( - std::get(tile_1_0_1_0.getTileID()) == - OctreeTileID(1, 0, 1, 0)); + const auto& tile_1_0_1_0 = + findTile(tileChildren, OctreeTileID(1, 0, 1, 0)); const auto& box_1_0_1_0 = std::get(tile_1_0_1_0.getBoundingVolume()); CHECK(box_1_0_1_0.getCenter() == glm::dvec3(-10.0, 10.0, -10.0)); @@ -293,10 +307,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_0_1_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_1_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_1_1_0 = tileChildren[5]; - CHECK( - std::get(tile_1_1_1_0.getTileID()) == - OctreeTileID(1, 1, 1, 0)); + const auto& tile_1_1_1_0 = + findTile(tileChildren, OctreeTileID(1, 1, 1, 0)); const auto& box_1_1_1_0 = std::get(tile_1_1_1_0.getBoundingVolume()); CHECK(box_1_1_1_0.getCenter() == glm::dvec3(10.0, 10.0, -10.0)); @@ -304,10 +316,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_1_1_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_1_1_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_0_1_1 = tileChildren[6]; - CHECK( - std::get(tile_1_0_1_1.getTileID()) == - OctreeTileID(1, 0, 1, 1)); + const auto& tile_1_0_1_1 = + findTile(tileChildren, OctreeTileID(1, 0, 1, 1)); const auto& box_1_0_1_1 = std::get(tile_1_0_1_1.getBoundingVolume()); CHECK(box_1_0_1_1.getCenter() == glm::dvec3(-10.0, 10.0, 10.0)); @@ -315,10 +325,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_1_0_1_1.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_1_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 10.0)); - const auto& tile_1_1_1_1 = tileChildren[7]; - CHECK( - std::get(tile_1_1_1_1.getTileID()) == - OctreeTileID(1, 1, 1, 1)); + const auto& tile_1_1_1_1 = + findTile(tileChildren, OctreeTileID(1, 1, 1, 1)); const auto& box_1_1_1_1 = std::get(tile_1_1_1_1.getBoundingVolume()); CHECK(box_1_1_1_1.getCenter() == glm::dvec3(10.0, 10.0, 10.0)); @@ -331,7 +339,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { // check subdivide one of the root children { - auto& tile_1_1_0_0 = tile.getChildren()[1]; + const auto& tile_1_1_0_0 = + findTile(tile.getChildren(), OctreeTileID(1, 1, 0, 0)); auto tileChildrenResult = loader.createTileChildren(tile_1_1_0_0); CHECK(tileChildrenResult.state == TileLoadResultState::Success); @@ -339,10 +348,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 8); - const auto& tile_2_2_0_0 = tileChildren[0]; - CHECK( - std::get(tile_2_2_0_0.getTileID()) == - OctreeTileID(2, 2, 0, 0)); + const auto& tile_2_2_0_0 = + findTile(tileChildren, OctreeTileID(2, 2, 0, 0)); const auto& box_2_2_0_0 = std::get(tile_2_2_0_0.getBoundingVolume()); CHECK(box_2_2_0_0.getCenter() == glm::dvec3(5.0, -15.0, -15.0)); @@ -350,10 +357,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_2_0_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_0_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_3_0_0 = tileChildren[1]; - CHECK( - std::get(tile_2_3_0_0.getTileID()) == - OctreeTileID(2, 3, 0, 0)); + const auto& tile_2_3_0_0 = + findTile(tileChildren, OctreeTileID(2, 3, 0, 0)); const auto& box_2_3_0_0 = std::get(tile_2_3_0_0.getBoundingVolume()); CHECK(box_2_3_0_0.getCenter() == glm::dvec3(15.0, -15.0, -15.0)); @@ -361,10 +366,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_3_0_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_3_0_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_2_0_1 = tileChildren[2]; - CHECK( - std::get(tile_2_2_0_1.getTileID()) == - OctreeTileID(2, 2, 0, 1)); + const auto& tile_2_2_0_1 = + findTile(tileChildren, OctreeTileID(2, 2, 0, 1)); const auto& box_2_2_0_1 = std::get(tile_2_2_0_1.getBoundingVolume()); CHECK(box_2_2_0_1.getCenter() == glm::dvec3(5.0, -15.0, -5.0)); @@ -372,10 +375,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_2_0_1.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_0_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_3_0_1 = tileChildren[3]; - CHECK( - std::get(tile_2_3_0_1.getTileID()) == - OctreeTileID(2, 3, 0, 1)); + const auto& tile_2_3_0_1 = + findTile(tileChildren, OctreeTileID(2, 3, 0, 1)); const auto& box_2_3_0_1 = std::get(tile_2_3_0_1.getBoundingVolume()); CHECK(box_2_3_0_1.getCenter() == glm::dvec3(15.0, -15.0, -5.0)); @@ -383,10 +384,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_3_0_1.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_3_0_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_2_1_0 = tileChildren[4]; - CHECK( - std::get(tile_2_2_1_0.getTileID()) == - OctreeTileID(2, 2, 1, 0)); + const auto& tile_2_2_1_0 = + findTile(tileChildren, OctreeTileID(2, 2, 1, 0)); const auto& box_2_2_1_0 = std::get(tile_2_2_1_0.getBoundingVolume()); CHECK(box_2_2_1_0.getCenter() == glm::dvec3(5.0, -5.0, -15.0)); @@ -394,10 +393,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_2_1_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_1_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_3_1_0 = tileChildren[5]; - CHECK( - std::get(tile_2_3_1_0.getTileID()) == - OctreeTileID(2, 3, 1, 0)); + const auto& tile_2_3_1_0 = + findTile(tileChildren, OctreeTileID(2, 3, 1, 0)); const auto& box_2_3_1_0 = std::get(tile_2_3_1_0.getBoundingVolume()); CHECK(box_2_3_1_0.getCenter() == glm::dvec3(15.0, -5.0, -15.0)); @@ -405,10 +402,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_3_1_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_3_1_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_2_1_1 = tileChildren[6]; - CHECK( - std::get(tile_2_2_1_1.getTileID()) == - OctreeTileID(2, 2, 1, 1)); + const auto& tile_2_2_1_1 = + findTile(tileChildren, OctreeTileID(2, 2, 1, 1)); const auto& box_2_2_1_1 = std::get(tile_2_2_1_1.getBoundingVolume()); CHECK(box_2_2_1_1.getCenter() == glm::dvec3(5.0, -5.0, -5.0)); @@ -416,10 +411,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(box_2_2_1_1.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_1_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 5.0)); - const auto& tile_2_3_1_1 = tileChildren[7]; - CHECK( - std::get(tile_2_3_1_1.getTileID()) == - OctreeTileID(2, 3, 1, 1)); + const auto& tile_2_3_1_1 = + findTile(tileChildren, OctreeTileID(2, 3, 1, 1)); const auto& box_2_3_1_1 = std::get(tile_2_3_1_1.getBoundingVolume()); CHECK(box_2_3_1_1.getCenter() == glm::dvec3(15.0, -5.0, -5.0)); @@ -469,7 +462,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 8); - const auto& tile_1_0_0_0 = tileChildren[0]; + const auto& tile_1_0_0_0 = + findTile(tileChildren, OctreeTileID(1, 0, 0, 0)); const auto& region_1_0_0_0 = std::get(tile_1_0_0_0.getBoundingVolume()); CHECK(region_1_0_0_0.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -480,7 +474,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_0_0_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_0_0_0.getMaximumHeight() == Approx(50.0)); - const auto& tile_1_1_0_0 = tileChildren[1]; + const auto& tile_1_1_0_0 = + findTile(tileChildren, OctreeTileID(1, 1, 0, 0)); const auto& region_1_1_0_0 = std::get(tile_1_1_0_0.getBoundingVolume()); CHECK(region_1_1_0_0.getRectangle().getWest() == Approx(0.0)); @@ -491,7 +486,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_1_0_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_1_0_0.getMaximumHeight() == Approx(50.0)); - const auto& tile_1_0_0_1 = tileChildren[2]; + const auto& tile_1_0_0_1 = + findTile(tileChildren, OctreeTileID(1, 0, 0, 1)); const auto& region_1_0_0_1 = std::get(tile_1_0_0_1.getBoundingVolume()); CHECK(region_1_0_0_0.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -502,7 +498,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_0_0_1.getMinimumHeight() == Approx(50.0)); CHECK(region_1_0_0_1.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_1_0_1 = tileChildren[3]; + const auto& tile_1_1_0_1 = + findTile(tileChildren, OctreeTileID(1, 1, 0, 1)); const auto& region_1_1_0_1 = std::get(tile_1_1_0_1.getBoundingVolume()); CHECK(region_1_1_0_0.getRectangle().getWest() == Approx(0.0)); @@ -513,7 +510,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_1_0_1.getMinimumHeight() == Approx(50.0)); CHECK(region_1_1_0_1.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_0_1_0 = tileChildren[4]; + const auto& tile_1_0_1_0 = + findTile(tileChildren, OctreeTileID(1, 0, 1, 0)); const auto& region_1_0_1_0 = std::get(tile_1_0_1_0.getBoundingVolume()); CHECK(region_1_0_1_0.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -524,7 +522,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_0_1_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_0_1_0.getMaximumHeight() == Approx(50.0)); - const auto& tile_1_1_1_0 = tileChildren[5]; + const auto& tile_1_1_1_0 = + findTile(tileChildren, OctreeTileID(1, 1, 1, 0)); const auto& region_1_1_1_0 = std::get(tile_1_1_1_0.getBoundingVolume()); CHECK(region_1_1_1_0.getRectangle().getWest() == Approx(0.0)); @@ -535,7 +534,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_1_1_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_1_1_0.getMaximumHeight() == Approx(50.0)); - const auto& tile_1_0_1_1 = tileChildren[6]; + const auto& tile_1_0_1_1 = + findTile(tileChildren, OctreeTileID(1, 0, 1, 1)); const auto& region_1_0_1_1 = std::get(tile_1_0_1_1.getBoundingVolume()); CHECK(region_1_0_1_1.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -546,7 +546,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_1_0_1_1.getMinimumHeight() == Approx(50.0)); CHECK(region_1_0_1_1.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_1_1_1 = tileChildren[7]; + const auto& tile_1_1_1_1 = + findTile(tileChildren, OctreeTileID(1, 1, 1, 1)); const auto& region_1_1_1_1 = std::get(tile_1_1_1_1.getBoundingVolume()); CHECK(region_1_1_1_1.getRectangle().getWest() == Approx(0.0)); @@ -562,14 +563,16 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { // check subdivide one of the root children { - auto& tile_1_1_0_0 = tile.getChildren()[1]; + auto& tile_1_1_0_0 = + findTile(tile.getChildren(), OctreeTileID(1, 1, 0, 0)); auto tileChildrenResult = loader.createTileChildren(tile_1_1_0_0); CHECK(tileChildrenResult.state == TileLoadResultState::Success); const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 8); - const auto& tile_2_2_0_0 = tileChildren[0]; + const auto& tile_2_2_0_0 = + findTile(tileChildren, OctreeTileID(2, 2, 0, 0)); const auto& region_2_2_0_0 = std::get(tile_2_2_0_0.getBoundingVolume()); CHECK(region_2_2_0_0.getRectangle().getWest() == Approx(0.0)); @@ -582,7 +585,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_2_0_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_2_0_0.getMaximumHeight() == Approx(25.0)); - const auto& tile_2_3_0_0 = tileChildren[1]; + const auto& tile_2_3_0_0 = + findTile(tileChildren, OctreeTileID(2, 3, 0, 0)); const auto& region_2_3_0_0 = std::get(tile_2_3_0_0.getBoundingVolume()); CHECK(region_2_3_0_0.getRectangle().getWest() == Approx(Math::PiOverTwo)); @@ -595,7 +599,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_3_0_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_3_0_0.getMaximumHeight() == Approx(25.0)); - const auto& tile_2_2_0_1 = tileChildren[2]; + const auto& tile_2_2_0_1 = + findTile(tileChildren, OctreeTileID(2, 2, 0, 1)); const auto& region_2_2_0_1 = std::get(tile_2_2_0_1.getBoundingVolume()); CHECK(region_2_2_0_1.getRectangle().getWest() == Approx(0.0)); @@ -608,7 +613,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_2_0_1.getMinimumHeight() == Approx(25.0)); CHECK(region_2_2_0_1.getMaximumHeight() == Approx(50.0)); - const auto& tile_2_3_0_1 = tileChildren[3]; + const auto& tile_2_3_0_1 = + findTile(tileChildren, OctreeTileID(2, 3, 0, 1)); const auto& region_2_3_0_1 = std::get(tile_2_3_0_1.getBoundingVolume()); CHECK(region_2_3_0_1.getRectangle().getWest() == Approx(Math::PiOverTwo)); @@ -621,7 +627,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_3_0_1.getMinimumHeight() == Approx(25.0)); CHECK(region_2_3_0_1.getMaximumHeight() == Approx(50.0)); - const auto& tile_2_2_1_0 = tileChildren[4]; + const auto& tile_2_2_1_0 = + findTile(tileChildren, OctreeTileID(2, 2, 1, 0)); const auto& region_2_2_1_0 = std::get(tile_2_2_1_0.getBoundingVolume()); CHECK(region_2_2_1_0.getRectangle().getWest() == Approx(0.0)); @@ -634,7 +641,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_2_1_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_2_1_0.getMaximumHeight() == Approx(25.0)); - const auto& tile_2_3_1_0 = tileChildren[5]; + const auto& tile_2_3_1_0 = + findTile(tileChildren, OctreeTileID(2, 3, 1, 0)); const auto& region_2_3_1_0 = std::get(tile_2_3_1_0.getBoundingVolume()); CHECK(region_2_3_1_0.getRectangle().getWest() == Approx(Math::PiOverTwo)); @@ -646,7 +654,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_3_1_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_3_1_0.getMaximumHeight() == Approx(25.0)); - const auto& tile_2_2_1_1 = tileChildren[6]; + const auto& tile_2_2_1_1 = + findTile(tileChildren, OctreeTileID(2, 2, 1, 1)); const auto& region_2_2_1_1 = std::get(tile_2_2_1_1.getBoundingVolume()); CHECK(region_2_2_1_1.getRectangle().getWest() == Approx(0.0)); @@ -659,7 +668,8 @@ TEST_CASE("Test tile subdivision for implicit octree loader") { CHECK(region_2_2_1_1.getMinimumHeight() == Approx(25.0)); CHECK(region_2_2_1_1.getMaximumHeight() == Approx(50.0)); - const auto& tile_2_3_1_1 = tileChildren[7]; + const auto& tile_2_3_1_1 = + findTile(tileChildren, OctreeTileID(2, 3, 1, 1)); const auto& region_2_3_1_1 = std::get(tile_2_3_1_1.getBoundingVolume()); CHECK(region_2_3_1_1.getRectangle().getWest() == Approx(Math::PiOverTwo)); diff --git a/Cesium3DTilesSelection/test/TestImplicitQuadtreeLoader.cpp b/Cesium3DTilesSelection/test/TestImplicitQuadtreeLoader.cpp index 00209e323..5e7b1b145 100644 --- a/Cesium3DTilesSelection/test/TestImplicitQuadtreeLoader.cpp +++ b/Cesium3DTilesSelection/test/TestImplicitQuadtreeLoader.cpp @@ -198,6 +198,31 @@ TEST_CASE("Test implicit quadtree loader") { } } +namespace { + +const Tile& +findTile(const gsl::span& children, const QuadtreeTileID& tileID) { + auto it = std::find_if( + children.begin(), + children.end(), + [tileID](const Tile& tile) { + const QuadtreeTileID* pID = + std::get_if(&tile.getTileID()); + if (!pID) + return false; + return *pID == tileID; + }); + REQUIRE(it != children.end()); + return *it; +} + +const Tile& +findTile(const std::vector& children, const QuadtreeTileID& tileID) { + return findTile(gsl::span(children), tileID); +} + +} // namespace + TEST_CASE("Test tile subdivision for implicit quadtree loader") { Cesium3DTilesContent::registerAllTileContentTypes(); @@ -238,10 +263,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 4); - const auto& tile_1_0_0 = tileChildren[0]; - CHECK( - std::get(tile_1_0_0.getTileID()) == - QuadtreeTileID(1, 0, 0)); + const auto& tile_1_0_0 = findTile(tileChildren, QuadtreeTileID(1, 0, 0)); const auto& box_1_0_0 = std::get(tile_1_0_0.getBoundingVolume()); CHECK(box_1_0_0.getCenter() == glm::dvec3(-10.0, -10.0, 0.0)); @@ -249,10 +271,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_1_0_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_1_1_0 = tileChildren[1]; - CHECK( - std::get(tile_1_1_0.getTileID()) == - QuadtreeTileID(1, 1, 0)); + const auto& tile_1_1_0 = findTile(tileChildren, QuadtreeTileID(1, 1, 0)); const auto& box_1_1_0 = std::get(tile_1_1_0.getBoundingVolume()); CHECK(box_1_1_0.getCenter() == glm::dvec3(10.0, -10.0, 0.0)); @@ -260,10 +279,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_1_1_0.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_1_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_1_0_1 = tileChildren[2]; - CHECK( - std::get(tile_1_0_1.getTileID()) == - QuadtreeTileID(1, 0, 1)); + const auto& tile_1_0_1 = findTile(tileChildren, QuadtreeTileID(1, 0, 1)); const auto& box_1_0_1 = std::get(tile_1_0_1.getBoundingVolume()); CHECK(box_1_0_1.getCenter() == glm::dvec3(-10.0, 10.0, 0.0)); @@ -271,10 +287,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_1_0_1.getHalfAxes()[1] == glm::dvec3(0.0, 10.0, 0.0)); CHECK(box_1_0_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_1_1_1 = tileChildren[3]; - CHECK( - std::get(tile_1_1_1.getTileID()) == - QuadtreeTileID(1, 1, 1)); + const auto& tile_1_1_1 = findTile(tileChildren, QuadtreeTileID(1, 1, 1)); const auto& box_1_1_1 = std::get(tile_1_1_1.getBoundingVolume()); CHECK(box_1_1_1.getCenter() == glm::dvec3(10.0, 10.0, 0.0)); @@ -294,10 +307,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 4); - const auto& tile_2_2_0 = tileChildren[0]; - CHECK( - std::get(tile_2_2_0.getTileID()) == - QuadtreeTileID(2, 2, 0)); + const auto& tile_2_2_0 = findTile(tileChildren, QuadtreeTileID(2, 2, 0)); const auto& box_2_2_0 = std::get(tile_2_2_0.getBoundingVolume()); CHECK(box_2_2_0.getCenter() == glm::dvec3(5.0, -15.0, 0.0)); @@ -305,10 +315,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_2_2_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_2_3_0 = tileChildren[1]; - CHECK( - std::get(tile_2_3_0.getTileID()) == - QuadtreeTileID(2, 3, 0)); + const auto& tile_2_3_0 = findTile(tileChildren, QuadtreeTileID(2, 3, 0)); const auto& box_2_3_0 = std::get(tile_2_3_0.getBoundingVolume()); CHECK(box_2_3_0.getCenter() == glm::dvec3(15.0, -15.0, 0.0)); @@ -316,10 +323,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_2_3_0.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_3_0.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_2_2_1 = tileChildren[2]; - CHECK( - std::get(tile_2_2_1.getTileID()) == - QuadtreeTileID(2, 2, 1)); + const auto& tile_2_2_1 = findTile(tileChildren, QuadtreeTileID(2, 2, 1)); const auto& box_2_2_1 = std::get(tile_2_2_1.getBoundingVolume()); CHECK(box_2_2_1.getCenter() == glm::dvec3(5.0, -5.0, 0.0)); @@ -327,10 +331,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(box_2_2_1.getHalfAxes()[1] == glm::dvec3(0.0, 5.0, 0.0)); CHECK(box_2_2_1.getHalfAxes()[2] == glm::dvec3(0.0, 0.0, 20.0)); - const auto& tile_2_3_1 = tileChildren[3]; - CHECK( - std::get(tile_2_3_1.getTileID()) == - QuadtreeTileID(2, 3, 1)); + const auto& tile_2_3_1 = findTile(tileChildren, QuadtreeTileID(2, 3, 1)); const auto& box_2_3_1 = std::get(tile_2_3_1.getBoundingVolume()); CHECK(box_2_3_1.getCenter() == glm::dvec3(15.0, -5.0, 0.0)); @@ -379,7 +380,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 4); - const auto& tile_1_0_0 = tileChildren[0]; + const auto& tile_1_0_0 = findTile(tileChildren, QuadtreeTileID(1, 0, 0)); const auto& region_1_0_0 = std::get(tile_1_0_0.getBoundingVolume()); CHECK(region_1_0_0.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -389,7 +390,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_1_0_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_0_0.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_1_0 = tileChildren[1]; + const auto& tile_1_1_0 = findTile(tileChildren, QuadtreeTileID(1, 1, 0)); const auto& region_1_1_0 = std::get(tile_1_1_0.getBoundingVolume()); CHECK(region_1_1_0.getRectangle().getWest() == Approx(0.0)); @@ -399,7 +400,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_1_1_0.getMinimumHeight() == Approx(0.0)); CHECK(region_1_1_0.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_0_1 = tileChildren[2]; + const auto& tile_1_0_1 = findTile(tileChildren, QuadtreeTileID(1, 0, 1)); const auto& region_1_0_1 = std::get(tile_1_0_1.getBoundingVolume()); CHECK(region_1_0_1.getRectangle().getWest() == Approx(-Math::OnePi)); @@ -409,7 +410,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_1_0_1.getMinimumHeight() == Approx(0.0)); CHECK(region_1_0_1.getMaximumHeight() == Approx(100.0)); - const auto& tile_1_1_1 = tileChildren[3]; + const auto& tile_1_1_1 = findTile(tileChildren, QuadtreeTileID(1, 1, 1)); const auto& region_1_1_1 = std::get(tile_1_1_1.getBoundingVolume()); CHECK(region_1_1_1.getRectangle().getWest() == Approx(0.0)); @@ -431,7 +432,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 4); - const auto& tile_2_2_0 = tileChildren[0]; + const auto& tile_2_2_0 = findTile(tileChildren, QuadtreeTileID(2, 2, 0)); const auto& region_2_2_0 = std::get(tile_2_2_0.getBoundingVolume()); CHECK(region_2_2_0.getRectangle().getWest() == Approx(0.0)); @@ -442,7 +443,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_2_2_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_2_0.getMaximumHeight() == Approx(100.0)); - const auto& tile_2_3_0 = tileChildren[1]; + const auto& tile_2_3_0 = findTile(tileChildren, QuadtreeTileID(2, 3, 0)); const auto& region_2_3_0 = std::get(tile_2_3_0.getBoundingVolume()); CHECK(region_2_3_0.getRectangle().getWest() == Approx(Math::PiOverTwo)); @@ -453,7 +454,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_2_3_0.getMinimumHeight() == Approx(0.0)); CHECK(region_2_3_0.getMaximumHeight() == Approx(100.0)); - const auto& tile_2_2_1 = tileChildren[2]; + const auto& tile_2_2_1 = findTile(tileChildren, QuadtreeTileID(2, 2, 1)); const auto& region_2_2_1 = std::get(tile_2_2_1.getBoundingVolume()); CHECK(region_2_2_1.getRectangle().getWest() == Approx(0.0)); @@ -464,7 +465,7 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { CHECK(region_2_2_1.getMinimumHeight() == Approx(0.0)); CHECK(region_2_2_1.getMaximumHeight() == Approx(100.0)); - const auto& tile_2_3_1 = tileChildren[3]; + const auto& tile_2_3_1 = findTile(tileChildren, QuadtreeTileID(2, 3, 1)); const auto& region_2_3_1 = std::get(tile_2_3_1.getBoundingVolume()); CHECK(region_2_3_1.getRectangle().getWest() == Approx(Math::PiOverTwo)); @@ -511,34 +512,23 @@ TEST_CASE("Test tile subdivision for implicit quadtree loader") { const auto& tileChildren = tileChildrenResult.children; CHECK(tileChildren.size() == 4); - const auto& tile_1_0_0 = tileChildren[0]; - CHECK( - std::get(tile_1_0_0.getTileID()) == - QuadtreeTileID(1, 0, 0)); + const auto& tile_1_0_0 = findTile(tileChildren, QuadtreeTileID(1, 0, 0)); const auto& box_1_0_0 = std::get(tile_1_0_0.getBoundingVolume()); CHECK(box_1_0_0.getCellID().toToken() == "04"); - const auto& tile_1_1_0 = tileChildren[1]; - CHECK( - std::get(tile_1_1_0.getTileID()) == - QuadtreeTileID(1, 1, 0)); + const auto& tile_1_1_0 = findTile(tileChildren, QuadtreeTileID(1, 1, 0)); const auto& box_1_1_0 = std::get(tile_1_1_0.getBoundingVolume()); CHECK(box_1_1_0.getCellID().toToken() == "1c"); - const auto& tile_1_0_1 = tileChildren[2]; - CHECK( - std::get(tile_1_0_1.getTileID()) == - QuadtreeTileID(1, 0, 1)); + const auto& tile_1_0_1 = findTile(tileChildren, QuadtreeTileID(1, 0, 1)); + ; const auto& box_1_0_1 = std::get(tile_1_0_1.getBoundingVolume()); CHECK(box_1_0_1.getCellID().toToken() == "0c"); - const auto& tile_1_1_1 = tileChildren[3]; - CHECK( - std::get(tile_1_1_1.getTileID()) == - QuadtreeTileID(1, 1, 1)); + const auto& tile_1_1_1 = findTile(tileChildren, QuadtreeTileID(1, 1, 1)); const auto& box_1_1_1 = std::get(tile_1_1_1.getBoundingVolume()); CHECK(box_1_1_1.getCellID().toToken() == "14");