diff --git a/CHANGES.md b/CHANGES.md index b06ef026c..87c146ffb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,11 +5,34 @@ ##### Breaking Changes :mega: - Renamed `CesiumUtility/Gunzip.h` to `CesiumUtility/Gzip.h`. +- Renamed `ImageCesium` to `ImageAsset`. +- The `cesium` field in `CesiumGltf::Image` is now named `pAsset` and is an `IntrusivePointer` to an `ImageAsset`. +- The `image` field in `LoadedRasterOverlayImage` is now named `pImage` and is an `IntrusivePointer` to an `ImageAsset`. +- Deprecated the `readImage` and `generateMipMaps` methods on `GltfReader`. These methods are now found on `ImageDecoder`. ##### Additions :tada: - Added `CesiumUtility::gzip`. - Added `CesiumGeometry::Transforms::getUpAxisTransform` to get the transform that converts from one up axis to another. +- Added `TilesetSharedAssetSystem` to `Cesium3DTilesSelection` and `GltfSharedAssetSystem` to `CesiumGltfReader`. +- Added `SharedAsset` to `CesiumUtility` to serve as the base class for assets such as `ImageAsset`. +- Added `SharedAssetDepot` to `CesiumAsync` for managing assets, such as images, that can be shared among multiple models or other objects. +- Added `NetworkAssetDescriptor` and `NetworkImageAssetDescriptor`. +- `ImageAsset` (formerly `ImageCesium`) is now an `ExtensibleObject`. +- Added `VertexAttributeSemantics` to `CesiumGltf`. +- Added `ImageDecoder` to `CesiumGltfReader`. +- Added `DoublyLinkedListAdvanced` to `CesiumUtility`. It is equivalent to `DoublyLinkedList` except it allows the next and previous pointers to be in a base class of the node class. +- Added `contains` method to `DoublyLinkedList` (and `DoublyLinkedListAdvanced`). +- Added static `error` and `warning` methods to `ErrorList`, making it easy to create an instance with a single error or warning. +- `ExtensibleObject::addExtension` now takes arguments that are passed through to the extension's constructor. +- Added `Hash` to `CesiumUtility`. +- Added `emplace` and `reset` methods to `IntrusivePointer`. +- Added `Result` and `ResultPointer` classes to represent the result of an operation that might complete with warnings and errors. + +##### Fixes :wrench: + +- Fixed a bug in `AsyncSystem::all` where the resolved values of individual futures were copied instead of moved into the output array. +- Improved the hash function for `QuadtreeTileID`. ### v0.40.1 - 2024-10-01 diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/IPrepareRendererResources.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/IPrepareRendererResources.h index 19efe0537..d6654fee3 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/IPrepareRendererResources.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/IPrepareRendererResources.h @@ -20,7 +20,6 @@ struct Rectangle; } namespace CesiumGltf { -struct ImageCesium; struct Model; } // namespace CesiumGltf diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index efa32e830..8bfd9d2ac 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -23,10 +23,12 @@ #include namespace Cesium3DTilesSelection { + class TilesetContentManager; class TilesetMetadata; class TilesetHeightQuery; class TilesetHeightRequest; +class TilesetSharedAssetSystem; /** * @brief A @@ -69,6 +70,13 @@ class CESIUM3DTILESSELECTION_API TilesetExternals final { */ std::shared_ptr pTileOcclusionProxyPool = nullptr; + + /** + * @brief The shared asset system used to facilitate sharing of common assets, + * such as images, between and within tilesets. + */ + CesiumUtility::IntrusivePointer pSharedAssetSystem = + TilesetSharedAssetSystem::getDefault(); }; } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetSharedAssetSystem.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetSharedAssetSystem.h new file mode 100644 index 000000000..2b2908a4a --- /dev/null +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetSharedAssetSystem.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Cesium3DTilesSelection { + +/** + * @brief Contains assets that are potentially shared across multiple Tilesets. + */ +class TilesetSharedAssetSystem + : public CesiumGltfReader::GltfSharedAssetSystem { +public: + static CesiumUtility::IntrusivePointer getDefault(); + + virtual ~TilesetSharedAssetSystem() = default; +}; + +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 721ba5b63..d48fdefe2 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -175,7 +175,9 @@ int64_t Tile::computeByteSize() const noexcept { // sizeBytes is set in TilesetContentManager::ContentKindSetter, if not // sooner (e.g., by the renderer implementation). - bytes += image.cesium.sizeBytes; + if (image.pAsset) { + bytes += image.pAsset->sizeBytes; + } } } diff --git a/Cesium3DTilesSelection/src/TileContentLoadInfo.cpp b/Cesium3DTilesSelection/src/TileContentLoadInfo.cpp index b492e134c..136051632 100644 --- a/Cesium3DTilesSelection/src/TileContentLoadInfo.cpp +++ b/Cesium3DTilesSelection/src/TileContentLoadInfo.cpp @@ -9,6 +9,8 @@ TileContentLoadInfo::TileContentLoadInfo( const std::shared_ptr& pPrepareRendererResources_, const std::shared_ptr& pLogger_, + const CesiumUtility::IntrusivePointer& + pSharedAssetSystem_, const TilesetContentOptions& contentOptions_, const Tile& tile) : asyncSystem(asyncSystem_), @@ -18,6 +20,7 @@ TileContentLoadInfo::TileContentLoadInfo( tileID(tile.getTileID()), tileBoundingVolume(tile.getBoundingVolume()), tileContentBoundingVolume(tile.getContentBoundingVolume()), + pSharedAssetSystem{pSharedAssetSystem_}, tileRefine(tile.getRefine()), tileGeometricError(tile.getGeometricError()), tileTransform(tile.getTransform()), diff --git a/Cesium3DTilesSelection/src/TileContentLoadInfo.h b/Cesium3DTilesSelection/src/TileContentLoadInfo.h index 85ee52187..261693b87 100644 --- a/Cesium3DTilesSelection/src/TileContentLoadInfo.h +++ b/Cesium3DTilesSelection/src/TileContentLoadInfo.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,8 @@ struct TileContentLoadInfo { const std::shared_ptr& pPrepareRendererResources, const std::shared_ptr& pLogger, + const CesiumUtility::IntrusivePointer& + pSharedAssetSystem, const TilesetContentOptions& contentOptions, const Tile& tile); @@ -40,6 +43,7 @@ struct TileContentLoadInfo { BoundingVolume tileBoundingVolume; std::optional tileContentBoundingVolume; + CesiumUtility::IntrusivePointer pSharedAssetSystem; TileRefine tileRefine; diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index ee9d6f9ca..0b7df6cd0 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -149,6 +149,14 @@ const RasterOverlayCollection& Tileset::getOverlays() const noexcept { return this->_pTilesetContentManager->getRasterOverlayCollection(); } +TilesetSharedAssetSystem& Tileset::getSharedAssetSystem() noexcept { + return *this->_pTilesetContentManager->getSharedAssetSystem(); +} + +const TilesetSharedAssetSystem& Tileset::getSharedAssetSystem() const noexcept { + return *this->_pTilesetContentManager->getSharedAssetSystem(); +} + static bool operator<(const FogDensityAtHeight& fogDensity, double height) noexcept { return fogDensity.cameraHeight < height; diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 693d09067..805d4d6b9 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -49,12 +49,15 @@ struct ContentKindSetter { void operator()(CesiumGltf::Model&& model) { for (CesiumGltf::Image& image : model.images) { + if (!image.pAsset) + continue; + // If the image size hasn't been overridden, store the pixelData // size now. We'll be adding this number to our total memory usage soon, // and remove it when the tile is later unloaded, and we must use // the same size in each case. - if (image.cesium.sizeBytes < 0) { - image.cesium.sizeBytes = int64_t(image.cesium.pixelData.size()); + if (image.pAsset->sizeBytes < 0) { + image.pAsset->sizeBytes = int64_t(image.pAsset->pixelData.size()); } } @@ -562,6 +565,9 @@ postProcessContentInWorkerThread( tileLoadInfo.contentOptions.ktx2TranscodeTargets; gltfOptions.applyTextureTransform = tileLoadInfo.contentOptions.applyTextureTransform; + if (tileLoadInfo.pSharedAssetSystem) { + gltfOptions.pSharedAssetSystem = tileLoadInfo.pSharedAssetSystem; + } auto asyncSystem = tileLoadInfo.asyncSystem; auto pAssetAccessor = tileLoadInfo.pAssetAccessor; @@ -599,12 +605,12 @@ postProcessContentInWorkerThread( "Warning when resolving external gltf buffers from " "{}:\n- {}", result.pCompletedRequest->url(), - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + CesiumUtility::joinToString(gltfResult.warnings, "\n- ")); } else { SPDLOG_LOGGER_ERROR( tileLoadInfo.pLogger, "Warning resolving external glTF buffers:\n- {}", - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + CesiumUtility::joinToString(gltfResult.warnings, "\n- ")); } } @@ -660,6 +666,7 @@ TilesetContentManager::TilesetContentManager( _tileLoadsInProgress{0}, _loadedTilesCount{0}, _tilesDataUsed{0}, + _pSharedAssetSystem(externals.pSharedAssetSystem), _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ this->_destructionCompletePromise.getFuture().share()}, @@ -689,6 +696,7 @@ TilesetContentManager::TilesetContentManager( _tileLoadsInProgress{0}, _loadedTilesCount{0}, _tilesDataUsed{0}, + _pSharedAssetSystem(externals.pSharedAssetSystem), _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ this->_destructionCompletePromise.getFuture().share()}, @@ -840,6 +848,7 @@ TilesetContentManager::TilesetContentManager( _tileLoadsInProgress{0}, _loadedTilesCount{0}, _tilesDataUsed{0}, + _pSharedAssetSystem(externals.pSharedAssetSystem), _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ this->_destructionCompletePromise.getFuture().share()}, @@ -985,6 +994,7 @@ void TilesetContentManager::loadTileContent( this->_externals.pAssetAccessor, this->_externals.pPrepareRendererResources, this->_externals.pLogger, + this->_pSharedAssetSystem, tilesetOptions.contentOptions, tile}; @@ -1227,6 +1237,11 @@ TilesetContentManager::getTilesetCredits() const noexcept { return this->_tilesetCredits; } +const CesiumUtility::IntrusivePointer& +TilesetContentManager::getSharedAssetSystem() const noexcept { + return this->_pSharedAssetSystem; +} + int32_t TilesetContentManager::getNumberOfTilesLoading() const noexcept { return this->_tileLoadsInProgress; } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index 43ecb94f7..0a4af3831 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -18,6 +18,8 @@ namespace Cesium3DTilesSelection { +class TilesetSharedAssetSystem; + class TilesetContentManager : public CesiumUtility::ReferenceCountedNonThreadSafe< TilesetContentManager> { @@ -115,6 +117,9 @@ class TilesetContentManager const std::vector& getTilesetCredits() const noexcept; + const CesiumUtility::IntrusivePointer& + getSharedAssetSystem() const noexcept; + int32_t getNumberOfTilesLoading() const noexcept; int32_t getNumberOfTilesLoaded() const noexcept; @@ -167,6 +172,9 @@ class TilesetContentManager int32_t _loadedTilesCount; int64_t _tilesDataUsed; + // Stores assets that might be shared between tiles. + CesiumUtility::IntrusivePointer _pSharedAssetSystem; + CesiumAsync::Promise _destructionCompletePromise; CesiumAsync::SharedFuture _destructionCompleteFuture; diff --git a/Cesium3DTilesSelection/src/TilesetJsonLoader.h b/Cesium3DTilesSelection/src/TilesetJsonLoader.h index 13d21cb94..f70ce9b7c 100644 --- a/Cesium3DTilesSelection/src/TilesetJsonLoader.h +++ b/Cesium3DTilesSelection/src/TilesetJsonLoader.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -55,6 +56,7 @@ class TilesetJsonLoader : public TilesetContentLoader { private: std::string _baseUrl; CesiumGeospatial::Ellipsoid _ellipsoid; + CesiumUtility::IntrusivePointer _pSharedAssetSystem; /** * @brief The axis that was declared as the "up-axis" for glTF content. diff --git a/Cesium3DTilesSelection/src/TilesetSharedAssetSystem.cpp b/Cesium3DTilesSelection/src/TilesetSharedAssetSystem.cpp new file mode 100644 index 000000000..95a8d384d --- /dev/null +++ b/Cesium3DTilesSelection/src/TilesetSharedAssetSystem.cpp @@ -0,0 +1,32 @@ +#include + +using namespace Cesium3DTilesSelection; +using namespace CesiumGltfReader; +using namespace CesiumUtility; + +namespace { + +CesiumUtility::IntrusivePointer createDefault() { + CesiumUtility::IntrusivePointer p = + new TilesetSharedAssetSystem(); + + CesiumUtility::IntrusivePointer pGltf = + GltfSharedAssetSystem::getDefault(); + + p->pImage = pGltf->pImage; + + return p; +} + +} // namespace + +namespace Cesium3DTilesSelection { + +/*static*/ CesiumUtility::IntrusivePointer +TilesetSharedAssetSystem::getDefault() { + static CesiumUtility::IntrusivePointer pDefault = + createDefault(); + return pDefault; +} + +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/test/SimplePrepareRendererResource.h b/Cesium3DTilesSelection/test/SimplePrepareRendererResource.h index 19126357c..e980a0057 100644 --- a/Cesium3DTilesSelection/test/SimplePrepareRendererResource.h +++ b/Cesium3DTilesSelection/test/SimplePrepareRendererResource.h @@ -68,7 +68,7 @@ class SimplePrepareRendererResource } virtual void* prepareRasterInLoadThread( - CesiumGltf::ImageCesium& /*image*/, + CesiumGltf::ImageAsset& /*image*/, const std::any& /*rendererOptions*/) override { return new AllocationResult{totalAllocation}; } diff --git a/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp b/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp index 83e35dfda..ca08932f4 100644 --- a/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp @@ -1,5 +1,7 @@ #include "SimplePrepareRendererResource.h" +#include "TestTilesetJsonLoader.h" #include "TilesetContentManager.h" +#include "TilesetJsonLoader.h" #include #include @@ -1296,7 +1298,8 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { CesiumAsync::Future loadTileImage(RasterOverlayTile& overlayTile) override { - CesiumGltf::ImageCesium image{}; + CesiumUtility::IntrusivePointer pImage; + CesiumGltf::ImageAsset& image = pImage.emplace(); image.width = 1; image.height = 1; image.channels = 1; @@ -1305,7 +1308,7 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { return this->getAsyncSystem().createResolvedFuture( LoadedRasterOverlayImage{ - std::move(image), + std::move(pImage), overlayTile.getRectangle(), {}, {}, @@ -1625,4 +1628,82 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { pManager->unloadTileContent(tile); } + + SECTION("Resolve external images, with deduplication") { + std::filesystem::path dirPath(testDataPath / "SharedImages"); + + // mock the requests for all files + for (const auto& entry : std::filesystem::directory_iterator(dirPath)) { + pMockedAssetAccessor->mockCompletedRequests.insert( + {entry.path().filename().string(), createMockRequest(entry.path())}); + } + + std::filesystem::path tilesetPath(dirPath / "tileset.json"); + auto pExternals = createMockJsonTilesetExternals( + tilesetPath.string(), + pMockedAssetAccessor); + + auto pJsonLoaderFuture = + TilesetJsonLoader::createLoader(pExternals, tilesetPath.string(), {}); + + externals.asyncSystem.dispatchMainThreadTasks(); + + auto loaderResult = pJsonLoaderFuture.wait(); + + REQUIRE(loaderResult.pRootTile); + REQUIRE(loaderResult.pRootTile->getChildren().size() == 1); + + auto& rootTile = *loaderResult.pRootTile; + auto& containerTile = rootTile.getChildren()[0]; + + REQUIRE(containerTile.getChildren().size() == 100); + + // create manager + Tile::LoadedLinkedList loadedTiles; + IntrusivePointer pManager = + new TilesetContentManager{ + externals, + {}, + RasterOverlayCollection{loadedTiles, externals}, + {}, + std::move(loaderResult.pLoader), + std::move(loaderResult.pRootTile)}; + + for (auto& child : containerTile.getChildren()) { + pManager->loadTileContent(child, {}); + externals.asyncSystem.dispatchMainThreadTasks(); + pManager->waitUntilIdle(); + + CHECK(child.getState() == TileLoadState::ContentLoaded); + CHECK(child.isRenderContent()); + + const auto& renderContent = child.getContent().getRenderContent(); + const auto& images = renderContent->getModel().images; + CHECK(images.size() == 1); + } + + CHECK( + pManager->getSharedAssetSystem() + ->pImage->getInactiveAssetTotalSizeBytes() == 0); + CHECK(pManager->getSharedAssetSystem()->pImage->getAssetCount() == 2); + CHECK(pManager->getSharedAssetSystem()->pImage->getActiveAssetCount() == 2); + CHECK( + pManager->getSharedAssetSystem()->pImage->getInactiveAssetCount() == 0); + + // unload the tile content + for (auto& child : containerTile.getChildren()) { + pManager->unloadTileContent(child); + } + + // Both of the assets will become inactive, and one of them will be + // destroyed, in order to bring the total under the limit. + CHECK( + pManager->getSharedAssetSystem() + ->pImage->getInactiveAssetTotalSizeBytes() <= + pManager->getSharedAssetSystem()->pImage->inactiveAssetSizeLimitBytes); + CHECK(pManager->getSharedAssetSystem()->pImage->getAssetCount() == 1); + CHECK(pManager->getSharedAssetSystem()->pImage->getActiveAssetCount() == 0); + CHECK( + pManager->getSharedAssetSystem()->pImage->getInactiveAssetCount() == 1); + } } diff --git a/Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp b/Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp index b2dc6e382..ccad9aa18 100644 --- a/Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp @@ -1,8 +1,11 @@ +#include "TestTilesetJsonLoader.h" + #include "ImplicitQuadtreeLoader.h" #include "SimplePrepareRendererResource.h" #include "TilesetJsonLoader.h" #include +#include #include #include #include @@ -12,6 +15,7 @@ #include #include +#include #include #include @@ -23,52 +27,6 @@ using namespace CesiumUtility; namespace { std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR; -TilesetExternals createMockTilesetExternals(const std::string& tilesetPath) { - auto tilesetContent = readFile(tilesetPath); - auto pMockCompletedResponse = std::make_unique( - static_cast(200), - "doesn't matter", - CesiumAsync::HttpHeaders{}, - std::move(tilesetContent)); - - auto pMockCompletedRequest = std::make_shared( - "GET", - "tileset.json", - CesiumAsync::HttpHeaders{}, - std::move(pMockCompletedResponse)); - - std::map> - mockCompletedRequests; - mockCompletedRequests.insert({tilesetPath, std::move(pMockCompletedRequest)}); - - std::shared_ptr pMockAssetAccessor = - std::make_shared(std::move(mockCompletedRequests)); - - auto pMockPrepareRendererResource = - std::make_shared(); - - auto pMockCreditSystem = std::make_shared(); - - AsyncSystem asyncSystem{std::make_shared()}; - - return TilesetExternals{ - std::move(pMockAssetAccessor), - std::move(pMockPrepareRendererResource), - std::move(asyncSystem), - std::move(pMockCreditSystem)}; -} - -TilesetContentLoaderResult -createLoader(const std::filesystem::path& tilesetPath) { - std::string tilesetPathStr = tilesetPath.string(); - auto externals = createMockTilesetExternals(tilesetPathStr); - auto loaderResultFuture = - TilesetJsonLoader::createLoader(externals, tilesetPathStr, {}); - externals.asyncSystem.dispatchMainThreadTasks(); - - return loaderResultFuture.wait(); -} - TileLoadResult loadTileContent( const std::filesystem::path& tilePath, TilesetContentLoader& loader, @@ -114,8 +72,8 @@ TEST_CASE("Test creating tileset json loader") { Cesium3DTilesContent::registerAllTileContentTypes(); SECTION("Create valid tileset json with REPLACE refinement") { - auto loaderResult = - createLoader(testDataPath / "ReplaceTileset" / "tileset.json"); + auto loaderResult = createTilesetJsonLoader( + testDataPath / "ReplaceTileset" / "tileset.json"); CHECK(!loaderResult.errors.hasErrors()); @@ -182,7 +140,7 @@ TEST_CASE("Test creating tileset json loader") { SECTION("Create valid tileset json with ADD refinement") { auto loaderResult = - createLoader(testDataPath / "AddTileset" / "tileset2.json"); + createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset2.json"); CHECK(!loaderResult.errors.hasErrors()); @@ -229,7 +187,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset has tile with sphere bounding volume") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "SphereBoundingVolumeTileset.json"); @@ -247,7 +205,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset has tile with box bounding volume") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "BoxBoundingVolumeTileset.json"); @@ -266,7 +224,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset has tile with no bounding volume field") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "NoBoundingVolumeTileset.json"); @@ -281,7 +239,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset has tile with no geometric error field") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "NoGeometricErrorTileset.json"); @@ -300,7 +258,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset has tile with no capitalized Refinement field") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "NoCapitalizedRefineTileset.json"); @@ -321,7 +279,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Scale geometric error along with tile transform") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "ScaleGeometricErrorTileset.json"); @@ -340,7 +298,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset with empty tile") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "EmptyTileTileset.json"); CHECK(!loaderResult.errors.hasErrors()); REQUIRE(loaderResult.pRootTile); @@ -357,7 +315,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset with quadtree implicit tile") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "QuadtreeImplicitTileset.json"); CHECK(!loaderResult.errors.hasErrors()); @@ -380,7 +338,7 @@ TEST_CASE("Test creating tileset json loader") { } SECTION("Tileset with octree implicit tile") { - auto loaderResult = createLoader( + auto loaderResult = createTilesetJsonLoader( testDataPath / "MultipleKindsOfTilesets" / "OctreeImplicitTileset.json"); CHECK(!loaderResult.errors.hasErrors()); @@ -404,7 +362,7 @@ TEST_CASE("Test creating tileset json loader") { SECTION("Tileset with metadata") { auto loaderResult = - createLoader(testDataPath / "WithMetadata" / "tileset.json"); + createTilesetJsonLoader(testDataPath / "WithMetadata" / "tileset.json"); CHECK(!loaderResult.errors.hasErrors()); REQUIRE(loaderResult.pLoader); @@ -425,8 +383,8 @@ TEST_CASE("Test loading individual tile of tileset json") { Cesium3DTilesContent::registerAllTileContentTypes(); SECTION("Load tile that has render content") { - auto loaderResult = - createLoader(testDataPath / "ReplaceTileset" / "tileset.json"); + auto loaderResult = createTilesetJsonLoader( + testDataPath / "ReplaceTileset" / "tileset.json"); REQUIRE(loaderResult.pRootTile); REQUIRE(loaderResult.pRootTile->getChildren().size() == 1); @@ -450,7 +408,7 @@ TEST_CASE("Test loading individual tile of tileset json") { SECTION("Load tile that has external content") { auto loaderResult = - createLoader(testDataPath / "AddTileset" / "tileset.json"); + createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset.json"); REQUIRE(loaderResult.pRootTile); REQUIRE(loaderResult.pRootTile->getChildren().size() == 1); @@ -501,8 +459,8 @@ TEST_CASE("Test loading individual tile of tileset json") { } SECTION("Load tile that has external content with implicit tiling") { - auto loaderResult = - createLoader(testDataPath / "ImplicitTileset" / "tileset_1.1.json"); + auto loaderResult = createTilesetJsonLoader( + testDataPath / "ImplicitTileset" / "tileset_1.1.json"); REQUIRE(loaderResult.pRootTile); CHECK(loaderResult.pRootTile->isExternalContent()); @@ -600,8 +558,8 @@ TEST_CASE("Test loading individual tile of tileset json") { } SECTION("Check that tile with legacy implicit tiling extension still works") { - auto loaderResult = - createLoader(testDataPath / "ImplicitTileset" / "tileset_1.0.json"); + auto loaderResult = createTilesetJsonLoader( + testDataPath / "ImplicitTileset" / "tileset_1.0.json"); REQUIRE(loaderResult.pRootTile); CHECK(loaderResult.pRootTile->isExternalContent()); @@ -622,3 +580,52 @@ TEST_CASE("Test loading individual tile of tileset json") { CHECK(pLoader->getAvailableLevels() == 2); } } +Cesium3DTilesSelection::TilesetContentLoaderResult +Cesium3DTilesSelection::createTilesetJsonLoader( + const std::filesystem::path& tilesetPath) { + std::string tilesetPathStr = tilesetPath.string(); + auto pAccessor = std::make_shared( + std::map>()); + auto externals = createMockJsonTilesetExternals(tilesetPathStr, pAccessor); + auto loaderResultFuture = + TilesetJsonLoader::createLoader(externals, tilesetPathStr, {}); + externals.asyncSystem.dispatchMainThreadTasks(); + + return loaderResultFuture.wait(); +} +Cesium3DTilesSelection::TilesetExternals +Cesium3DTilesSelection::createMockJsonTilesetExternals( + const std::string& tilesetPath, + std::shared_ptr& pAssetAccessor) { + auto tilesetContent = readFile(tilesetPath); + auto pMockCompletedResponse = + std::make_unique( + static_cast(200), + "doesn't matter", + CesiumAsync::HttpHeaders{}, + std::move(tilesetContent)); + + auto pMockCompletedRequest = + std::make_shared( + "GET", + "tileset.json", + CesiumAsync::HttpHeaders{}, + std::move(pMockCompletedResponse)); + + pAssetAccessor->mockCompletedRequests.insert( + {tilesetPath, std::move(pMockCompletedRequest)}); + + auto pMockPrepareRendererResource = + std::make_shared(); + + auto pMockCreditSystem = std::make_shared(); + + CesiumAsync::AsyncSystem asyncSystem{ + std::make_shared()}; + + return TilesetExternals{ + std::move(pAssetAccessor), + std::move(pMockPrepareRendererResource), + std::move(asyncSystem), + std::move(pMockCreditSystem)}; +} diff --git a/Cesium3DTilesSelection/test/TestTilesetJsonLoader.h b/Cesium3DTilesSelection/test/TestTilesetJsonLoader.h new file mode 100644 index 000000000..ed3a1b34a --- /dev/null +++ b/Cesium3DTilesSelection/test/TestTilesetJsonLoader.h @@ -0,0 +1,28 @@ +#pragma once + +#include "SimplePrepareRendererResource.h" +#include "TilesetJsonLoader.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Cesium3DTilesSelection { + +Cesium3DTilesSelection::TilesetExternals createMockJsonTilesetExternals( + const std::string& tilesetPath, + std::shared_ptr& pAssetAccessor); + +TilesetContentLoaderResult +createTilesetJsonLoader(const std::filesystem::path& tilesetPath); + +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/test/data/SharedImages/_Sandcastle.js b/Cesium3DTilesSelection/test/data/SharedImages/_Sandcastle.js new file mode 100644 index 000000000..2e33de316 --- /dev/null +++ b/Cesium3DTilesSelection/test/data/SharedImages/_Sandcastle.js @@ -0,0 +1,33 @@ +const viewer = new Cesium.Viewer("cesiumContainer", { + globe: false +}); + +// Create the tileset in the viewer +const tileset = viewer.scene.primitives.add( + await Cesium.Cesium3DTileset.fromUrl( + "http://localhost:8003/tileset.json", { + debugShowBoundingVolume: true, + }) +); + +// Move the tileset to a certain position on the globe, +// and scale it up +const transform = Cesium.Transforms.eastNorthUpToFixedFrame( + Cesium.Cartesian3.fromDegrees(-75.152408, 39.946975, 20) +); +const scale = 15.0; +const modelMatrix = Cesium.Matrix4.multiplyByUniformScale( + transform, + scale, + new Cesium.Matrix4() +); +tileset.modelMatrix = modelMatrix; + +// Zoom to the tileset, with a small offset so that +// it is fully visible +const offset = new Cesium.HeadingPitchRange( + Cesium.Math.toRadians(-22.5), + Cesium.Math.toRadians(-22.5), + 60.0 +); +viewer.zoomTo(tileset, offset); \ No newline at end of file diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-0.glb new file mode 100644 index 000000000..b3ac6b99d Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-1.glb new file mode 100644 index 000000000..83856f07b Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-2.glb new file mode 100644 index 000000000..2d9a67161 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-3.glb new file mode 100644 index 000000000..e69c717e9 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-4.glb new file mode 100644 index 000000000..54e8f1574 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-5.glb new file mode 100644 index 000000000..b81e52b3c Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-6.glb new file mode 100644 index 000000000..0927e2f83 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-7.glb new file mode 100644 index 000000000..42f64e1c3 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-8.glb new file mode 100644 index 000000000..307a4df5d Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-0-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-9.glb new file mode 100644 index 000000000..e351c7812 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-0-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-0.glb new file mode 100644 index 000000000..53f8c6193 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-1.glb new file mode 100644 index 000000000..655d287f6 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-2.glb new file mode 100644 index 000000000..b5047f461 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-3.glb new file mode 100644 index 000000000..1c796fb90 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-4.glb new file mode 100644 index 000000000..f98cc59d0 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-5.glb new file mode 100644 index 000000000..fed618ea9 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-6.glb new file mode 100644 index 000000000..783d80d00 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-7.glb new file mode 100644 index 000000000..d143bd58e Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-8.glb new file mode 100644 index 000000000..c94389505 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-1-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-9.glb new file mode 100644 index 000000000..3f2257e8a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-1-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-0.glb new file mode 100644 index 000000000..582ddad0f Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-1.glb new file mode 100644 index 000000000..21e649921 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-2.glb new file mode 100644 index 000000000..661b2c5dc Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-3.glb new file mode 100644 index 000000000..a46831507 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-4.glb new file mode 100644 index 000000000..8439d8c08 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-5.glb new file mode 100644 index 000000000..3651e61c4 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-6.glb new file mode 100644 index 000000000..ce6314000 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-7.glb new file mode 100644 index 000000000..b4a800a3b Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-8.glb new file mode 100644 index 000000000..aa370f486 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-2-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-9.glb new file mode 100644 index 000000000..e7b474231 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-2-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-0.glb new file mode 100644 index 000000000..b7052e366 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-1.glb new file mode 100644 index 000000000..9d0729308 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-2.glb new file mode 100644 index 000000000..6c65018bd Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-3.glb new file mode 100644 index 000000000..888ce0366 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-4.glb new file mode 100644 index 000000000..a0885a798 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-5.glb new file mode 100644 index 000000000..d9ac63cbf Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-6.glb new file mode 100644 index 000000000..2779bf347 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-7.glb new file mode 100644 index 000000000..b3912f565 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-8.glb new file mode 100644 index 000000000..f4ac1a048 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-3-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-9.glb new file mode 100644 index 000000000..73333b217 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-3-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-0.glb new file mode 100644 index 000000000..17b6f8b5e Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-1.glb new file mode 100644 index 000000000..69b3a0cc9 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-2.glb new file mode 100644 index 000000000..3666813a6 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-3.glb new file mode 100644 index 000000000..81484dcb5 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-4.glb new file mode 100644 index 000000000..0e156d7f1 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-5.glb new file mode 100644 index 000000000..df09670e5 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-6.glb new file mode 100644 index 000000000..fa3029fbd Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-7.glb new file mode 100644 index 000000000..e1d4befdd Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-8.glb new file mode 100644 index 000000000..be9fb192a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-4-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-9.glb new file mode 100644 index 000000000..b4369264d Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-4-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-0.glb new file mode 100644 index 000000000..c95fcb4cf Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-1.glb new file mode 100644 index 000000000..16c81779b Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-2.glb new file mode 100644 index 000000000..27cad8dfb Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-3.glb new file mode 100644 index 000000000..1f782400d Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-4.glb new file mode 100644 index 000000000..f85acea09 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-5.glb new file mode 100644 index 000000000..d3cd1259d Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-6.glb new file mode 100644 index 000000000..c33df9153 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-7.glb new file mode 100644 index 000000000..b55420778 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-8.glb new file mode 100644 index 000000000..92281dcf2 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-5-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-9.glb new file mode 100644 index 000000000..38bf32231 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-5-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-0.glb new file mode 100644 index 000000000..685e742d7 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-1.glb new file mode 100644 index 000000000..2c03127ad Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-2.glb new file mode 100644 index 000000000..d41d5b589 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-3.glb new file mode 100644 index 000000000..91e683543 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-4.glb new file mode 100644 index 000000000..7336fd653 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-5.glb new file mode 100644 index 000000000..5283d5b7c Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-6.glb new file mode 100644 index 000000000..ac1867056 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-7.glb new file mode 100644 index 000000000..0717d3250 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-8.glb new file mode 100644 index 000000000..2a1b3ae5e Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-6-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-9.glb new file mode 100644 index 000000000..975cf35dc Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-6-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-0.glb new file mode 100644 index 000000000..afccd12c8 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-1.glb new file mode 100644 index 000000000..cdc5ee49b Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-2.glb new file mode 100644 index 000000000..4277e4d44 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-3.glb new file mode 100644 index 000000000..a16497fd9 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-4.glb new file mode 100644 index 000000000..df1f59836 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-5.glb new file mode 100644 index 000000000..770968c60 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-6.glb new file mode 100644 index 000000000..ab3813b5a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-7.glb new file mode 100644 index 000000000..00de2f419 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-8.glb new file mode 100644 index 000000000..5d110b0a7 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-7-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-9.glb new file mode 100644 index 000000000..ba7296144 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-7-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-0.glb new file mode 100644 index 000000000..5a67e1ba0 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-1.glb new file mode 100644 index 000000000..7faca0a65 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-2.glb new file mode 100644 index 000000000..996dcf6ea Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-3.glb new file mode 100644 index 000000000..c01f821a0 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-4.glb new file mode 100644 index 000000000..7c325c9d3 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-5.glb new file mode 100644 index 000000000..8916d5b2a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-6.glb new file mode 100644 index 000000000..c67cffc23 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-7.glb new file mode 100644 index 000000000..1b60bb7f7 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-8.glb new file mode 100644 index 000000000..1caddfcf3 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-8-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-9.glb new file mode 100644 index 000000000..469cb3393 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-8-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-0.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-0.glb new file mode 100644 index 000000000..c4125e2b0 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-0.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-1.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-1.glb new file mode 100644 index 000000000..9ea21e350 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-1.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-2.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-2.glb new file mode 100644 index 000000000..290e75336 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-2.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-3.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-3.glb new file mode 100644 index 000000000..5f40c7b6e Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-3.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-4.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-4.glb new file mode 100644 index 000000000..c9eb72fcd Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-4.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-5.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-5.glb new file mode 100644 index 000000000..22909852b Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-5.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-6.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-6.glb new file mode 100644 index 000000000..7a0d4be8a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-6.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-7.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-7.glb new file mode 100644 index 000000000..77b088bfe Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-7.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-8.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-8.glb new file mode 100644 index 000000000..86ddb7035 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-8.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/plane-9-9.glb b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-9.glb new file mode 100644 index 000000000..7089d91a0 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/plane-9-9.glb differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/texture0.png b/Cesium3DTilesSelection/test/data/SharedImages/texture0.png new file mode 100644 index 000000000..14200f61a Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/texture0.png differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/texture1.png b/Cesium3DTilesSelection/test/data/SharedImages/texture1.png new file mode 100644 index 000000000..959452509 Binary files /dev/null and b/Cesium3DTilesSelection/test/data/SharedImages/texture1.png differ diff --git a/Cesium3DTilesSelection/test/data/SharedImages/tileset.json b/Cesium3DTilesSelection/test/data/SharedImages/tileset.json new file mode 100644 index 000000000..b7569c61b --- /dev/null +++ b/Cesium3DTilesSelection/test/data/SharedImages/tileset.json @@ -0,0 +1,2228 @@ +{ + "asset": { + "version": "1.1" + }, + "geometricError": 4096, + "root": { + "boundingVolume": { + "box": [ + 5.4500005, + 0, + 5.4500005, + 5.450000286102295, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5.450000286102295 + ] + }, + "geometricError": 1024, + "children": [ + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-9.glb" + } + } + ], + "refine": "ADD" + } +} \ No newline at end of file diff --git a/CesiumAsync/include/CesiumAsync/AsyncSystem.h b/CesiumAsync/include/CesiumAsync/AsyncSystem.h index 43100011a..23bc64fd3 100644 --- a/CesiumAsync/include/CesiumAsync/AsyncSystem.h +++ b/CesiumAsync/include/CesiumAsync/AsyncSystem.h @@ -345,7 +345,7 @@ class CESIUMASYNC_API AsyncSystem final { results.reserve(tasks.size()); for (auto it = tasks.begin(); it != tasks.end(); ++it) { - results.emplace_back(it->get()); + results.emplace_back(std::move(it->get())); } return results; } diff --git a/CesiumAsync/include/CesiumAsync/IAssetAccessor.h b/CesiumAsync/include/CesiumAsync/IAssetAccessor.h index 415e9e63c..695c395a9 100644 --- a/CesiumAsync/include/CesiumAsync/IAssetAccessor.h +++ b/CesiumAsync/include/CesiumAsync/IAssetAccessor.h @@ -1,6 +1,6 @@ #pragma once -#include "AsyncSystem.h" +#include "Future.h" #include "IAssetRequest.h" #include "Library.h" @@ -12,6 +12,7 @@ #include namespace CesiumAsync { + class AsyncSystem; /** diff --git a/CesiumAsync/include/CesiumAsync/NetworkAssetDescriptor.h b/CesiumAsync/include/CesiumAsync/NetworkAssetDescriptor.h new file mode 100644 index 000000000..de2c71778 --- /dev/null +++ b/CesiumAsync/include/CesiumAsync/NetworkAssetDescriptor.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace CesiumAsync { + +class AsyncSystem; + +/** + * @brief A description of an asset that can be loaded from the network using an + * {@link IAssetAccessor}. This includes a URL and any headers to be included + * in the request. + */ +struct NetworkAssetDescriptor { + /** + * @brief The URL from which this network asset is downloaded. + */ + std::string url; + + /** + * @brief The HTTP headers used in requesting this asset. + */ + std::vector headers; + + /** + * @brief Determines if this descriptor is identical to another one. + */ + bool operator==(const NetworkAssetDescriptor& rhs) const noexcept; + + /** + * @brief Request this asset from the network using the provided asset + * accessor. + * + * @param asyncSystem The async system. + * @param pAssetAccessor The asset accessor. + * @return A future that resolves to the request once it is complete. + */ + Future> loadFromNetwork( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const; + + /** + * @brief Request this asset from the network using the provided asset + * accessor and return the downloaded bytes. + * + * @param asyncSystem The async system. + * @param pAssetAccessor The asset accessor. + * @return A future that resolves to the downloaded bytes once the request is + * complete. + */ + Future>> loadBytesFromNetwork( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const; +}; + +} // namespace CesiumAsync + +template <> struct std::hash { + std::size_t + operator()(const CesiumAsync::NetworkAssetDescriptor& key) const noexcept; +}; diff --git a/CesiumAsync/include/CesiumAsync/SharedAssetDepot.h b/CesiumAsync/include/CesiumAsync/SharedAssetDepot.h new file mode 100644 index 000000000..236655d03 --- /dev/null +++ b/CesiumAsync/include/CesiumAsync/SharedAssetDepot.h @@ -0,0 +1,446 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CesiumUtility { +template class SharedAsset; +} + +namespace CesiumAsync { + +/** + * @brief A depot for {@link SharedAsset} instances, which are potentially shared between multiple objects. + * + * @tparam TAssetType The type of asset stored in this depot. This should + * be derived from {@link SharedAsset}. + */ +template +class CESIUMASYNC_API SharedAssetDepot + : public CesiumUtility::ReferenceCountedThreadSafe< + SharedAssetDepot>, + public CesiumUtility::IDepotOwningAsset { +public: + /** + * @brief The maximum total byte usage of assets that have been loaded but are + * no longer needed. + * + * When cached assets are no longer needed, they're marked as + * candidates for deletion. However, this deletion doesn't actually occur + * until the total byte usage of deletion candidates exceeds this threshold. + * At that point, assets are cleaned up in the order that they were marked for + * deletion until the total dips below this threshold again. + * + * Default is 16MiB. + */ + int64_t inactiveAssetSizeLimitBytes = 16 * 1024 * 1024; + + using FactorySignature = + CesiumAsync::Future>( + const AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const TAssetKey& key); + + SharedAssetDepot(std::function factory) + : _assets(), + _assetsByPointer(), + _deletionCandidates(), + _totalDeletionCandidateMemoryUsage(0), + _mutex(), + _factory(std::move(factory)), + _pKeepAlive(nullptr) {} + + virtual ~SharedAssetDepot() { + // Ideally, when the depot is destroyed, all the assets it owns would become + // independent assets. But this is extremely difficult to manage in a + // thread-safe manner. + + // Since we're in the destructor, we can be sure no one has a reference to + // this instance anymore. That means that no other thread can be executing + // `getOrCreate`, and no async asset creations are in progress. + + // However, if assets owned by this depot are still alive, then other + // threads can still be calling addReference / releaseReference on some of + // our assets even while we're running the depot's destructor. Which means + // that we can end up in `markDeletionCandidate` at the same time the + // destructor is running. And in fact it's possible for a `SharedAsset` with + // especially poor timing to call into a `SharedAssetDepot` just after it is + // destroyed. + + // To avoid this, we use the _pKeepAlive field to maintain an artificial + // reference to this depot whenever it owns live assets. This should keep + // this destructor from being called except when all of its assets are also + // in the _deletionCandidates list. + + CESIUM_ASSERT(this->_assets.size() == this->_deletionCandidates.size()); + } + + /** + * @brief Gets an asset from the depot if it already exists, or creates it + * using the depot's factory if it does not. + * + * @param asyncSystem The async system. + * @param pAssetAccessor The asset accessor to use to download assets, if + * necessary. + * @param assetKey The key uniquely identifying the asset to get or create. + * @return A shared future that resolves when the asset is ready or fails. + */ + SharedFuture> getOrCreate( + const AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const TAssetKey& assetKey) { + // We need to take care here to avoid two assets starting to load before the + // first asset has added an entry and set its maybePendingAsset field. + std::unique_lock lock(this->_mutex); + + auto existingIt = this->_assets.find(assetKey); + if (existingIt != this->_assets.end()) { + // We've already loaded (or are loading) an asset with this ID - we can + // just use that. + const AssetEntry& entry = *existingIt->second; + if (entry.maybePendingAsset) { + // Asset is currently loading. + return *entry.maybePendingAsset; + } else { + return asyncSystem.createResolvedFuture(entry.toResultUnderLock()) + .share(); + } + } + + // Calling the factory function while holding the mutex unnecessarily + // limits parallelism. It can even lead to a bug in the scenario where the + // `thenInWorkerThread` continuation is invoked immediately in the current + // thread, before `thenInWorkerThread` itself returns. That would result + // in an attempt to lock the mutex recursively, which is not allowed. + + // So we jump through some hoops here to publish "this thread is working + // on it", then unlock the mutex, and _then_ actually call the factory + // function. + Promise promise = asyncSystem.createPromise(); + + // We haven't loaded or started to load this asset yet. + // Let's do that now. + CesiumUtility::IntrusivePointer> + pDepot = this; + CesiumUtility::IntrusivePointer pEntry = + new AssetEntry(assetKey); + + auto future = + promise.getFuture() + .thenImmediately([pDepot, pEntry, asyncSystem, pAssetAccessor]() { + return pDepot->_factory(asyncSystem, pAssetAccessor, pEntry->key); + }) + .catchImmediately([](std::exception&& e) { + return CesiumUtility::Result< + CesiumUtility::IntrusivePointer>( + CesiumUtility::ErrorList::error( + std::string("Error creating asset: ") + e.what())); + }) + .thenInWorkerThread( + [pDepot, pEntry]( + CesiumUtility::Result< + CesiumUtility::IntrusivePointer>&& result) { + std::lock_guard lock(pDepot->_mutex); + + if (result.pValue) { + result.pValue->_pDepot = pDepot.get(); + pDepot->_assetsByPointer[result.pValue.get()] = + pEntry.get(); + } + + // Now that this asset is owned by the depot, we exclusively + // control its lifetime with a std::unique_ptr. + pEntry->pAsset = + std::unique_ptr(result.pValue.get()); + pEntry->errorsAndWarnings = std::move(result.errors); + pEntry->maybePendingAsset.reset(); + + // The asset is initially live because we have an + // IntrusivePointer to it right here. So make sure the depot + // stays alive, too. + pDepot->_pKeepAlive = pDepot; + + return pEntry->toResultUnderLock(); + }); + + SharedFuture> sharedFuture = + std::move(future).share(); + + pEntry->maybePendingAsset = sharedFuture; + + auto [it, added] = this->_assets.emplace(assetKey, pEntry); + + // Should always be added successfully, because we checked above that the + // asset key doesn't exist in the map yet. + CESIUM_ASSERT(added); + + // Unlock the mutex and then call the factory function. + lock.unlock(); + promise.resolve(); + + return sharedFuture; + } + + /** + * @brief Returns the total number of distinct assets contained in this depot, + * including both active and inactive assets. + */ + size_t getAssetCount() const { + std::lock_guard lock(this->_mutex); + return this->_assets.size(); + } + + /** + * @brief Gets the number of assets owned by this depot that are active, + * meaning that they are currently being used in one or more places. + */ + size_t getActiveAssetCount() const { + std::lock_guard lock(this->_mutex); + return this->_assets.size() - this->_deletionCandidates.size(); + } + + /** + * @brief Gets the number of assets owned by this depot that are inactive, + * meaning that they are not currently being used. + */ + size_t getInactiveAssetCount() const { + std::lock_guard lock(this->_mutex); + return this->_deletionCandidates.size(); + } + + /** + * @brief Gets the total bytes used by inactive (unused) assets owned by this + * depot. + */ + int64_t getInactiveAssetTotalSizeBytes() const { + std::lock_guard lock(this->_mutex); + return this->_totalDeletionCandidateMemoryUsage; + } + +private: + // Disable copy + void operator=(const SharedAssetDepot& other) = delete; + + /** + * @brief Marks the given asset as a candidate for deletion. + * Should only be called by {@link SharedAsset}. May be called from any thread. + * + * @param asset The asset to mark for deletion. + * @param threadOwnsDepotLock True if the calling thread already owns the + * depot lock; otherwise, false. + */ + void markDeletionCandidate(const TAssetType& asset, bool threadOwnsDepotLock) + override { + if (threadOwnsDepotLock) { + this->markDeletionCandidateUnderLock(asset); + } else { + std::lock_guard lock(this->_mutex); + this->markDeletionCandidateUnderLock(asset); + } + } + + void markDeletionCandidateUnderLock(const TAssetType& asset) { + auto it = this->_assetsByPointer.find(const_cast(&asset)); + CESIUM_ASSERT(it != this->_assetsByPointer.end()); + if (it == this->_assetsByPointer.end()) { + return; + } + + CESIUM_ASSERT(it->second != nullptr); + + AssetEntry& entry = *it->second; + entry.sizeInDeletionList = asset.getSizeBytes(); + this->_totalDeletionCandidateMemoryUsage += entry.sizeInDeletionList; + + this->_deletionCandidates.insertAtTail(entry); + + if (this->_totalDeletionCandidateMemoryUsage > + this->inactiveAssetSizeLimitBytes) { + // Delete the deletion candidates until we're below the limit. + while (this->_deletionCandidates.size() > 0 && + this->_totalDeletionCandidateMemoryUsage > + this->inactiveAssetSizeLimitBytes) { + AssetEntry* pOldEntry = this->_deletionCandidates.head(); + this->_deletionCandidates.remove(*pOldEntry); + + this->_totalDeletionCandidateMemoryUsage -= + pOldEntry->sizeInDeletionList; + + CESIUM_ASSERT( + pOldEntry->pAsset == nullptr || + pOldEntry->pAsset->_referenceCount == 0); + + if (pOldEntry->pAsset) { + this->_assetsByPointer.erase(pOldEntry->pAsset.get()); + } + + // This will actually delete the asset. + this->_assets.erase(pOldEntry->key); + } + } + + // If this depot is not managing any live assets, then we no longer need to + // keep it alive. + if (this->_assets.size() == this->_deletionCandidates.size()) { + this->_pKeepAlive.reset(); + } + } + + /** + * @brief Unmarks the given asset as a candidate for deletion. + * Should only be called by {@link SharedAsset}. May be called from any thread. + * + * @param asset The asset to unmark for deletion. + * @param threadOwnsDepotLock True if the calling thread already owns the + * depot lock; otherwise, false. + */ + void unmarkDeletionCandidate( + const TAssetType& asset, + bool threadOwnsDepotLock) override { + if (threadOwnsDepotLock) { + this->unmarkDeletionCandidateUnderLock(asset); + } else { + std::lock_guard lock(this->_mutex); + this->unmarkDeletionCandidateUnderLock(asset); + } + } + + void unmarkDeletionCandidateUnderLock(const TAssetType& asset) { + auto it = this->_assetsByPointer.find(const_cast(&asset)); + CESIUM_ASSERT(it != this->_assetsByPointer.end()); + if (it == this->_assetsByPointer.end()) { + return; + } + + CESIUM_ASSERT(it->second != nullptr); + + AssetEntry& entry = *it->second; + bool isFound = this->_deletionCandidates.contains(entry); + + CESIUM_ASSERT(isFound); + + if (isFound) { + this->_totalDeletionCandidateMemoryUsage -= entry.sizeInDeletionList; + this->_deletionCandidates.remove(entry); + } + + // This depot is now managing at least one live asset, so keep it alive. + this->_pKeepAlive = this; + } + + /** + * @brief An entry for an asset owned by this depot. This is reference counted + * so that we can keep it alive during async operations. + */ + struct AssetEntry + : public CesiumUtility::ReferenceCountedThreadSafe { + AssetEntry(TAssetKey&& key_) + : CesiumUtility::ReferenceCountedThreadSafe(), + key(std::move(key_)), + pAsset(), + maybePendingAsset(), + errorsAndWarnings(), + sizeInDeletionList(0), + deletionListPointers() {} + + AssetEntry(const TAssetKey& key_) : AssetEntry(TAssetKey(key_)) {} + + /** + * @brief The unique key identifying this asset. + */ + TAssetKey key; + + /** + * @brief A pointer to the asset. This may be nullptr if the asset is still + * being loaded, or if it failed to load. + */ + std::unique_ptr pAsset; + + /** + * @brief If this asset is currently loading, this field holds a shared + * future that will resolve when the asset load is complete. This field will + * be empty if the asset finished loading, including if it failed to load. + */ + std::optional>> + maybePendingAsset; + + /** + * @brief The errors and warnings that occurred while loading this asset. + * This will not contain any errors or warnings if the asset has not + * finished loading yet. + */ + CesiumUtility::ErrorList errorsAndWarnings; + + /** + * @brief The size of this asset when it was added to the + * _deletionCandidates list. This is stored so that the exact same size can + * be subtracted later. The value of this field is undefined if the asset is + * not currently in the _deletionCandidates list. + */ + int64_t sizeInDeletionList; + + /** + * @brief The next and previous pointers to entries in the + * _deletionCandidates list. + */ + CesiumUtility::DoublyLinkedListPointers deletionListPointers; + + CesiumUtility::ResultPointer toResultUnderLock() const { + // This method is called while the calling thread already owns the depot + // mutex. So we must take care not to lock it again, which could happen if + // the asset is currently unreferenced and we naively create an + // IntrusivePointer for it. + pAsset->addReference(true); + CesiumUtility::IntrusivePointer p = pAsset.get(); + pAsset->releaseReference(true); + return CesiumUtility::ResultPointer(p, errorsAndWarnings); + } + }; + + // Maps asset keys to AssetEntry instances. This collection owns the asset + // entries. + std::unordered_map> + _assets; + + // Maps asset pointers to AssetEntry instances. The values in this map refer + // to instances owned by the _assets map. + std::unordered_map _assetsByPointer; + + // List of assets that are being considered for deletion, in the order that + // they became unused. + CesiumUtility::DoublyLinkedList + _deletionCandidates; + + // The total amount of memory used by all assets in the _deletionCandidates + // list. + int64_t _totalDeletionCandidateMemoryUsage; + + // Mutex serializing access to _assets, _assetsByPointer, _deletionCandidates, + // and any AssetEntry owned by this depot. + mutable std::mutex _mutex; + + // The factory used to create new AssetType instances. + std::function _factory; + + // This instance keeps a reference to itself whenever it is managing active + // assets, preventing it from being destroyed even if all other references to + // it are dropped. + CesiumUtility::IntrusivePointer> + _pKeepAlive; +}; + +} // namespace CesiumAsync diff --git a/CesiumAsync/src/NetworkAssetDescriptor.cpp b/CesiumAsync/src/NetworkAssetDescriptor.cpp new file mode 100644 index 000000000..e0e62edae --- /dev/null +++ b/CesiumAsync/src/NetworkAssetDescriptor.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +using namespace CesiumUtility; + +std::size_t std::hash::operator()( + const CesiumAsync::NetworkAssetDescriptor& key) const noexcept { + std::hash hash{}; + + size_t result = hash(key.url); + + for (const CesiumAsync::IAssetAccessor::THeader& header : key.headers) { + result = Hash::combine(result, hash(header.first)); + result = Hash::combine(result, hash(header.second)); + } + + return result; +} + +namespace CesiumAsync { + +bool NetworkAssetDescriptor::operator==( + const NetworkAssetDescriptor& rhs) const noexcept { + if (this->url != rhs.url || this->headers.size() != rhs.headers.size()) + return false; + + for (size_t i = 0; i < this->headers.size(); ++i) { + if (this->headers[i].first != rhs.headers[i].first || + this->headers[i].second != rhs.headers[i].second) + return false; + } + + return true; +} + +Future> +NetworkAssetDescriptor::loadFromNetwork( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const { + return pAssetAccessor->get(asyncSystem, this->url, this->headers); +} + +Future>> +NetworkAssetDescriptor::loadBytesFromNetwork( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const { + return this->loadFromNetwork(asyncSystem, pAssetAccessor) + .thenInWorkerThread( + [](std::shared_ptr&& pRequest) + -> Result> { + const CesiumAsync::IAssetResponse* pResponse = pRequest->response(); + if (!pResponse) { + return ErrorList::error( + fmt::format("Request for {} failed.", pRequest->url())); + } + + uint16_t statusCode = pResponse->statusCode(); + if (statusCode != 0 && (statusCode < 200 || statusCode >= 300)) { + return ErrorList::error(fmt::format( + "Request for {} failed with code {}", + pRequest->url(), + pResponse->statusCode())); + } + + return std::vector( + pResponse->data().begin(), + pResponse->data().end()); + }); +} + +} // namespace CesiumAsync diff --git a/CesiumAsync/test/MockAssetAccessor.h b/CesiumAsync/test/MockAssetAccessor.h index 175ce7909..0a8b1df65 100644 --- a/CesiumAsync/test/MockAssetAccessor.h +++ b/CesiumAsync/test/MockAssetAccessor.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/CesiumAsync/test/TestSharedAssetDepot.cpp b/CesiumAsync/test/TestSharedAssetDepot.cpp new file mode 100644 index 000000000..474b834f5 --- /dev/null +++ b/CesiumAsync/test/TestSharedAssetDepot.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include + +#include + +using namespace CesiumAsync; +using namespace CesiumNativeTests; +using namespace CesiumUtility; + +namespace { + +class TestAsset : public SharedAsset { +public: + std::string someValue; + + int64_t getSizeBytes() const { return int64_t(this->someValue.size()); } +}; + +IntrusivePointer> createDepot() { + return new SharedAssetDepot( + [](const AsyncSystem& asyncSystem, + const std::shared_ptr& /* pAssetAccessor */, + const std::string& assetKey) { + IntrusivePointer p = new TestAsset(); + p->someValue = assetKey; + return asyncSystem.createResolvedFuture(ResultPointer(p)); + }); +} + +} // namespace + +TEST_CASE("SharedAssetDepot") { + std::shared_ptr pTaskProcessor = + std::make_shared(); + AsyncSystem asyncSystem(pTaskProcessor); + + SECTION("getOrCreate can create assets") { + auto pDepot = createDepot(); + + ResultPointer assetOne = + pDepot->getOrCreate(asyncSystem, nullptr, "one").waitInMainThread(); + + REQUIRE(assetOne.pValue != nullptr); + } + + SECTION("getOrCreate returns the same asset when called a second time with " + "the same key") { + auto pDepot = createDepot(); + + auto futureOne = pDepot->getOrCreate(asyncSystem, nullptr, "one"); + auto futureTwo = pDepot->getOrCreate(asyncSystem, nullptr, "one"); + + ResultPointer assetOne = futureOne.waitInMainThread(); + ResultPointer assetTwo = futureTwo.waitInMainThread(); + + REQUIRE(assetOne.pValue != nullptr); + CHECK(assetOne.pValue == assetTwo.pValue); + } + + SECTION("unreferenced assets become inactive") { + auto pDepot = createDepot(); + + ResultPointer assetOne = + pDepot->getOrCreate(asyncSystem, nullptr, "one").waitInMainThread(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 1); + CHECK(pDepot->getInactiveAssetCount() == 0); + + assetOne.pValue.reset(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 0); + CHECK(pDepot->getInactiveAssetCount() == 1); + } + + SECTION("re-referenced assets become active again") { + auto pDepot = createDepot(); + + ResultPointer assetOne = + pDepot->getOrCreate(asyncSystem, nullptr, "one").waitInMainThread(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 1); + CHECK(pDepot->getInactiveAssetCount() == 0); + + assetOne.pValue.reset(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 0); + CHECK(pDepot->getInactiveAssetCount() == 1); + + ResultPointer assetTwo = + pDepot->getOrCreate(asyncSystem, nullptr, "one").waitInMainThread(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 1); + CHECK(pDepot->getInactiveAssetCount() == 0); + } + + SECTION("inactive assets are deleted when size threshold is exceeded") { + auto pDepot = createDepot(); + + pDepot->inactiveAssetSizeLimitBytes = + int64_t(std::string("one").size() + 1); + + ResultPointer assetOne = + pDepot->getOrCreate(asyncSystem, nullptr, "one").waitInMainThread(); + ResultPointer assetTwo = + pDepot->getOrCreate(asyncSystem, nullptr, "two").waitInMainThread(); + + assetOne.pValue.reset(); + + CHECK(pDepot->getAssetCount() == 2); + CHECK(pDepot->getActiveAssetCount() == 1); + CHECK(pDepot->getInactiveAssetCount() == 1); + + assetTwo.pValue.reset(); + + CHECK(pDepot->getAssetCount() == 1); + CHECK(pDepot->getActiveAssetCount() == 0); + CHECK(pDepot->getInactiveAssetCount() == 1); + } +} diff --git a/CesiumGeometry/include/CesiumGeometry/QuadtreeTileID.h b/CesiumGeometry/include/CesiumGeometry/QuadtreeTileID.h index 71cf1fa42..9d1ebad2d 100644 --- a/CesiumGeometry/include/CesiumGeometry/QuadtreeTileID.h +++ b/CesiumGeometry/include/CesiumGeometry/QuadtreeTileID.h @@ -111,10 +111,6 @@ template <> struct hash { * @brief A specialization of the `std::hash` template for * {@link CesiumGeometry::QuadtreeTileID} objects. */ - size_t operator()(const CesiumGeometry::QuadtreeTileID& key) const noexcept { - // TODO: is this hash function any good? Probably not. - std::hash h; - return h(key.level) ^ (h(key.x) << 1) ^ (h(key.y) << 2); - } + size_t operator()(const CesiumGeometry::QuadtreeTileID& key) const noexcept; }; } // namespace std diff --git a/CesiumGeometry/src/QuadtreeTileID.cpp b/CesiumGeometry/src/QuadtreeTileID.cpp index 908b86879..0058a37be 100644 --- a/CesiumGeometry/src/QuadtreeTileID.cpp +++ b/CesiumGeometry/src/QuadtreeTileID.cpp @@ -1,5 +1,7 @@ #include "CesiumGeometry/QuadtreeTilingScheme.h" +#include + namespace CesiumGeometry { uint32_t QuadtreeTileID::computeInvertedY( @@ -9,3 +11,16 @@ uint32_t QuadtreeTileID::computeInvertedY( } } // namespace CesiumGeometry + +namespace std { + +size_t hash::operator()( + const CesiumGeometry::QuadtreeTileID& key) const noexcept { + std::hash h; + size_t result = h(key.level); + result = CesiumUtility::Hash::combine(result, h(key.x)); + result = CesiumUtility::Hash::combine(result, h(key.y)); + return result; +} + +} // namespace std diff --git a/CesiumGltf/include/CesiumGltf/FeatureIdTextureView.h b/CesiumGltf/include/CesiumGltf/FeatureIdTextureView.h index 807838618..0200ee192 100644 --- a/CesiumGltf/include/CesiumGltf/FeatureIdTextureView.h +++ b/CesiumGltf/include/CesiumGltf/FeatureIdTextureView.h @@ -1,17 +1,9 @@ #pragma once #include "CesiumGltf/FeatureIdTexture.h" -#include "CesiumGltf/Image.h" -#include "CesiumGltf/ImageCesium.h" -#include "CesiumGltf/KhrTextureTransform.h" -#include "CesiumGltf/Texture.h" #include "CesiumGltf/TextureView.h" -#include -#include -#include #include -#include namespace CesiumGltf { diff --git a/CesiumGltf/include/CesiumGltf/Image.h b/CesiumGltf/include/CesiumGltf/Image.h index 8ddfa19be..a27bf02cc 100644 --- a/CesiumGltf/include/CesiumGltf/Image.h +++ b/CesiumGltf/include/CesiumGltf/Image.h @@ -1,16 +1,17 @@ #pragma once -#include "CesiumGltf/ImageCesium.h" +#include "CesiumGltf/ImageAsset.h" #include "CesiumGltf/ImageSpec.h" #include "CesiumGltf/Library.h" +#include "CesiumUtility/IntrusivePointer.h" namespace CesiumGltf { /** @copydoc ImageSpec */ struct CESIUMGLTF_API Image final : public ImageSpec { /** - * @brief Holds properties that are specific to the glTF loader rather than - * part of the glTF spec. + * @brief The loaded image asset. When an image is loaded from a URL, multiple + * `Image` instances may all point to the same `ImageAsset` instance. */ - ImageCesium cesium; + CesiumUtility::IntrusivePointer pAsset; }; } // namespace CesiumGltf diff --git a/CesiumGltf/include/CesiumGltf/ImageCesium.h b/CesiumGltf/include/CesiumGltf/ImageAsset.h similarity index 81% rename from CesiumGltf/include/CesiumGltf/ImageCesium.h rename to CesiumGltf/include/CesiumGltf/ImageAsset.h index 939ada0ae..864680af8 100644 --- a/CesiumGltf/include/CesiumGltf/ImageCesium.h +++ b/CesiumGltf/include/CesiumGltf/ImageAsset.h @@ -2,6 +2,7 @@ #include "CesiumGltf/Ktx2TranscodeTargets.h" #include "CesiumGltf/Library.h" +#include "CesiumUtility/SharedAsset.h" #include #include @@ -13,7 +14,7 @@ namespace CesiumGltf { /** * @brief The byte range within a buffer where this mip exists. */ -struct CESIUMGLTF_API ImageCesiumMipPosition { +struct CESIUMGLTF_API ImageAssetMipPosition { /** * @brief The byte index where this mip begins. */ @@ -26,10 +27,11 @@ struct CESIUMGLTF_API ImageCesiumMipPosition { }; /** - * @brief Holds {@link Image} properties that are specific to the glTF loader - * rather than part of the glTF spec. + * @brief A 2D image asset, including its pixel data. The image may have + * mipmaps, and it may be encoded in a GPU compression format. */ -struct CESIUMGLTF_API ImageCesium final { +struct CESIUMGLTF_API ImageAsset final + : public CesiumUtility::SharedAsset { /** * @brief The width of the image in pixels. */ @@ -65,7 +67,7 @@ struct CESIUMGLTF_API ImageCesium final { * biggest and etc. If this is empty, assume the entire buffer is a single * image, the mip map will need to be generated on the client in this case. */ - std::vector mipPositions; + std::vector mipPositions; /** * @brief The pixel data. @@ -110,5 +112,15 @@ struct CESIUMGLTF_API ImageCesium final { * this image. */ int64_t sizeBytes = -1; + + /** + * @brief Gets the size of this asset, in bytes. + * + * If {@link sizeBytes} is greater than or equal to zero, it is returned. + * Otherwise, the size of the {@link pixelData} array is returned. + */ + int64_t getSizeBytes() const { + return this->sizeBytes >= 0 ? this->sizeBytes : this->pixelData.size(); + } }; } // namespace CesiumGltf diff --git a/CesiumGltf/include/CesiumGltf/PropertyTexturePropertyView.h b/CesiumGltf/include/CesiumGltf/PropertyTexturePropertyView.h index f3dcd168e..d35b46e7a 100644 --- a/CesiumGltf/include/CesiumGltf/PropertyTexturePropertyView.h +++ b/CesiumGltf/include/CesiumGltf/PropertyTexturePropertyView.h @@ -1,6 +1,6 @@ #pragma once -#include "CesiumGltf/ImageCesium.h" +#include "CesiumGltf/ImageAsset.h" #include "CesiumGltf/KhrTextureTransform.h" #include "CesiumGltf/PropertyTextureProperty.h" #include "CesiumGltf/PropertyTransformations.h" @@ -290,7 +290,7 @@ class PropertyTexturePropertyView * @param property The {@link PropertyTextureProperty} * @param classProperty The {@link ClassProperty} this property conforms to. * @param sampler The {@link Sampler} used by the property. - * @param image The {@link ImageCesium} used by the property. + * @param image The {@link ImageAsset} used by the property. * @param channels The value of {@link PropertyTextureProperty::channels}. * @param options The options for constructing the view. */ @@ -298,7 +298,7 @@ class PropertyTexturePropertyView const PropertyTextureProperty& property, const ClassProperty& classProperty, const Sampler& sampler, - const ImageCesium& image, + const ImageAsset& image, const TextureViewOptions& options = TextureViewOptions()) noexcept : PropertyView(classProperty, property), TextureView( @@ -523,7 +523,7 @@ class PropertyTexturePropertyView * @param property The {@link PropertyTextureProperty} * @param classProperty The {@link ClassProperty} this property conforms to. * @param sampler The {@link Sampler} used by the property. - * @param image The {@link ImageCesium} used by the property. + * @param image The {@link ImageAsset} used by the property. * @param channels The value of {@link PropertyTextureProperty::channels}. * @param options The options for constructing the view. */ @@ -531,7 +531,7 @@ class PropertyTexturePropertyView const PropertyTextureProperty& property, const ClassProperty& classProperty, const Sampler& sampler, - const ImageCesium& image, + const ImageAsset& image, const TextureViewOptions& options = TextureViewOptions()) noexcept : PropertyView(classProperty, property), TextureView( diff --git a/CesiumGltf/include/CesiumGltf/PropertyTextureView.h b/CesiumGltf/include/CesiumGltf/PropertyTextureView.h index 2f2ac0d72..4b535c7b2 100644 --- a/CesiumGltf/include/CesiumGltf/PropertyTextureView.h +++ b/CesiumGltf/include/CesiumGltf/PropertyTextureView.h @@ -748,15 +748,16 @@ class PropertyTextureView { return PropertyTexturePropertyView(status); } - const ImageCesium& image = _pModel->images[imageIndex].cesium; + const CesiumUtility::IntrusivePointer& pImage = + _pModel->images[imageIndex].pAsset; const std::vector& channels = propertyTextureProperty.channels; - status = checkChannels(channels, image); + status = checkChannels(channels, *pImage); if (status != PropertyTexturePropertyViewStatus::Valid) { return PropertyTexturePropertyView(status); } - if (channels.size() * image.bytesPerChannel != elementSize) { + if (channels.size() * pImage->bytesPerChannel != elementSize) { return PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch; } @@ -764,7 +765,7 @@ class PropertyTextureView { propertyTextureProperty, classProperty, _pModel->samplers[samplerIndex], - image, + *pImage, propertyOptions); } @@ -780,7 +781,7 @@ class PropertyTextureView { PropertyViewStatusType checkChannels( const std::vector& channels, - const ImageCesium& image) const noexcept; + const ImageAsset& image) const noexcept; const Model* _pModel; const PropertyTexture* _pPropertyTexture; diff --git a/CesiumGltf/include/CesiumGltf/TextureView.h b/CesiumGltf/include/CesiumGltf/TextureView.h index 9eb15af19..ef681dd74 100644 --- a/CesiumGltf/include/CesiumGltf/TextureView.h +++ b/CesiumGltf/include/CesiumGltf/TextureView.h @@ -1,9 +1,10 @@ #pragma once -#include "CesiumGltf/ImageCesium.h" -#include "CesiumGltf/KhrTextureTransform.h" -#include "CesiumGltf/Sampler.h" -#include "CesiumGltf/TextureInfo.h" +#include +#include +#include +#include +#include #include @@ -112,10 +113,10 @@ class TextureView { /** * @brief Constructs a view of the texture specified by the given {@link Sampler} - * and {@link ImageCesium}. + * and {@link ImageAsset}. * * @param sampler The {@link Sampler} used by the texture. - * @param image The {@link ImageCesium} used by the texture. + * @param image The {@link ImageAsset} used by the texture. * @param textureCoordinateSetIndex The set index for the `TEXCOORD_n` * attribute used to sample this texture. * @param pKhrTextureTransformExtension A pointer to the KHR_texture_transform @@ -124,7 +125,7 @@ class TextureView { */ TextureView( const Sampler& sampler, - const ImageCesium& image, + const ImageAsset& image, int64_t textureCoordinateSetIndex, const ExtensionKhrTextureTransform* pKhrTextureTransformExtension = nullptr, @@ -172,11 +173,11 @@ class TextureView { * This will be nullptr if the texture view runs into * problems during construction. */ - const ImageCesium* getImage() const noexcept { - if (this->_imageCopy) { - return &(this->_imageCopy.value()); + const ImageAsset* getImage() const noexcept { + if (this->_pImageCopy) { + return this->_pImageCopy.get(); } - return this->_pImage; + return this->_pImage.get(); } /** @@ -210,12 +211,12 @@ class TextureView { TextureViewStatus _textureViewStatus; const Sampler* _pSampler; - const ImageCesium* _pImage; + CesiumUtility::IntrusivePointer _pImage; int64_t _texCoordSetIndex; bool _applyTextureTransform; std::optional _textureTransform; - std::optional _imageCopy; + CesiumUtility::IntrusivePointer _pImageCopy; }; } // namespace CesiumGltf diff --git a/CesiumGltf/include/CesiumGltf/VertexAttributeSemantics.h b/CesiumGltf/include/CesiumGltf/VertexAttributeSemantics.h new file mode 100644 index 000000000..03ea6a84f --- /dev/null +++ b/CesiumGltf/include/CesiumGltf/VertexAttributeSemantics.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +namespace CesiumGltf { + +/** + * @brief The standard glTF vertex attribute semantics from the specification. + */ +struct VertexAttributeSemantics { + /** + * @brief Unitless XYZ vertex positions. + */ + static const std::string POSITION; + + /** + * @brief Normalized XYZ vertex normals. + */ + static const std::string NORMAL; + + /** + * @brief XYZW vertex tangents where the XYZ portion is normalized, and the W + * component is a sign value (-1 or +1) indicating handedness of the tangent + * basis. + */ + static const std::string TANGENT; + + /** + * @brief ST texture coordinates + */ + static const std::array TEXCOORD_n; + + /** + * @brief RGB or RGBA vertex color linear multiplier. + */ + static const std::array COLOR_n; + + /** + * @brief The indices of the joints from the corresponding skin.joints array + * that affect the vertex. + */ + static const std::array JOINTS_n; + + /** + * @brief The weights indicating how strongly the joint influences the vertex. + */ + static const std::array WEIGHTS_n; +}; + +} // namespace CesiumGltf diff --git a/CesiumGltf/src/PropertyTextureView.cpp b/CesiumGltf/src/PropertyTextureView.cpp index f5ac76a11..c9968f4b0 100644 --- a/CesiumGltf/src/PropertyTextureView.cpp +++ b/CesiumGltf/src/PropertyTextureView.cpp @@ -80,14 +80,14 @@ PropertyTextureView::checkImage(const int32_t imageIndex) const noexcept { return PropertyTexturePropertyViewStatus::ErrorInvalidImage; } - const ImageCesium& image = - _pModel->images[static_cast(imageIndex)].cesium; + const CesiumUtility::IntrusivePointer& pImage = + _pModel->images[static_cast(imageIndex)].pAsset; - if (image.width < 1 || image.height < 1) { + if (!pImage || pImage->width < 1 || pImage->height < 1) { return PropertyTexturePropertyViewStatus::ErrorEmptyImage; } - if (image.bytesPerChannel > 1) { + if (pImage->bytesPerChannel > 1) { return PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel; } @@ -96,7 +96,7 @@ PropertyTextureView::checkImage(const int32_t imageIndex) const noexcept { PropertyViewStatusType PropertyTextureView::checkChannels( const std::vector& channels, - const ImageCesium& image) const noexcept { + const ImageAsset& image) const noexcept { if (channels.size() <= 0 || channels.size() > 4) { return PropertyTexturePropertyViewStatus::ErrorInvalidChannels; } diff --git a/CesiumGltf/src/TextureView.cpp b/CesiumGltf/src/TextureView.cpp index e1b267491..39ad5630e 100644 --- a/CesiumGltf/src/TextureView.cpp +++ b/CesiumGltf/src/TextureView.cpp @@ -14,7 +14,7 @@ TextureView::TextureView() noexcept _texCoordSetIndex(-1), _applyTextureTransform(false), _textureTransform(std::nullopt), - _imageCopy(std::nullopt) {} + _pImageCopy(nullptr) {} TextureView::TextureView( const Model& model, @@ -26,7 +26,7 @@ TextureView::TextureView( _texCoordSetIndex(textureInfo.texCoord), _applyTextureTransform(options.applyKhrTextureTransformExtension), _textureTransform(std::nullopt), - _imageCopy(std::nullopt) { + _pImageCopy(nullptr) { int32_t textureIndex = textureInfo.index; if (textureIndex < 0 || static_cast(textureIndex) >= model.textures.size()) { @@ -41,8 +41,8 @@ TextureView::TextureView( return; } - this->_pImage = &model.images[static_cast(texture.source)].cesium; - if (this->_pImage->width < 1 || this->_pImage->height < 1) { + this->_pImage = model.images[static_cast(texture.source)].pAsset; + if (!this->_pImage || this->_pImage->width < 1 || this->_pImage->height < 1) { this->_textureViewStatus = TextureViewStatus::ErrorEmptyImage; return; } @@ -71,7 +71,8 @@ TextureView::TextureView( } if (options.makeImageCopy) { - this->_imageCopy = *this->_pImage; + this->_pImageCopy = + this->_pImage ? new ImageAsset(*this->_pImage) : nullptr; } this->_textureViewStatus = TextureViewStatus::Valid; @@ -79,17 +80,17 @@ TextureView::TextureView( TextureView::TextureView( const Sampler& sampler, - const ImageCesium& image, + const ImageAsset& image, int64_t textureCoordinateSetIndex, const ExtensionKhrTextureTransform* pKhrTextureTransformExtension, const TextureViewOptions& options) noexcept : _textureViewStatus(TextureViewStatus::ErrorUninitialized), _pSampler(&sampler), - _pImage(&image), + _pImage(new ImageAsset(image)), _texCoordSetIndex(textureCoordinateSetIndex), _applyTextureTransform(options.applyKhrTextureTransformExtension), _textureTransform(std::nullopt), - _imageCopy(std::nullopt) { + _pImageCopy(nullptr) { // TODO: once compressed texture support is merged, check that the image is // decompressed here. @@ -104,7 +105,8 @@ TextureView::TextureView( } if (options.makeImageCopy) { - this->_imageCopy = *this->_pImage; + this->_pImageCopy = + this->_pImage ? new ImageAsset(*this->_pImage) : nullptr; } this->_textureViewStatus = TextureViewStatus::Valid; @@ -130,7 +132,8 @@ std::vector TextureView::sampleNearestPixel( u = applySamplerWrapS(u, this->_pSampler->wrapS); v = applySamplerWrapT(v, this->_pSampler->wrapT); - const ImageCesium& image = this->_imageCopy.value_or(*this->_pImage); + const ImageAsset& image = + this->_pImageCopy != nullptr ? *this->_pImageCopy : *this->_pImage; // For nearest filtering, std::floor is used instead of std::round. // This is because filtering is supposed to consider the pixel centers. But diff --git a/CesiumGltf/src/VertexAttributeSemantics.cpp b/CesiumGltf/src/VertexAttributeSemantics.cpp new file mode 100644 index 000000000..932a3e002 --- /dev/null +++ b/CesiumGltf/src/VertexAttributeSemantics.cpp @@ -0,0 +1,75 @@ +#include + +namespace CesiumGltf { + +/** + * @brief Unitless XYZ vertex positions. + */ +const std::string VertexAttributeSemantics::POSITION = "POSITION"; + +/** + * @brief Normalized XYZ vertex normals. + */ +const std::string VertexAttributeSemantics::NORMAL = "NORMAL"; + +/** + * @brief XYZW vertex tangents where the XYZ portion is normalized, and the W + * component is a sign value (-1 or +1) indicating handedness of the tangent + * basis. + */ +const std::string VertexAttributeSemantics::TANGENT = "TANGENT"; + +/** + * @brief ST texture coordinates + */ +const std::array VertexAttributeSemantics::TEXCOORD_n = { + "TEXCOORD_0", + "TEXCOORD_1", + "TEXCOORD_2", + "TEXCOORD_3", + "TEXCOORD_4", + "TEXCOORD_5", + "TEXCOORD_6", + "TEXCOORD_7"}; + +/** + * @brief RGB or RGBA vertex color linear multiplier. + */ +const std::array VertexAttributeSemantics::COLOR_n = { + "COLOR_0", + "COLOR_1", + "COLOR_2", + "COLOR_3", + "COLOR_4", + "COLOR_5", + "COLOR_6", + "COLOR_7"}; + +/** + * @brief The indices of the joints from the corresponding skin.joints array + * that affect the vertex. + */ +const std::array VertexAttributeSemantics::JOINTS_n = { + "JOINTS_0", + "JOINTS_1", + "JOINTS_2", + "JOINTS_3", + "JOINTS_4", + "JOINTS_5", + "JOINTS_6", + "JOINTS_7"}; + +/** + * @brief The weights indicating how strongly the joint influences the vertex. + */ +const std::array VertexAttributeSemantics::WEIGHTS_n = { + "WEIGHTS_0", + "WEIGHTS_1", + "WEIGHTS_2", + "WEIGHTS_3", + "WEIGHTS_4", + "WEIGHTS_5", + "WEIGHTS_6", + "WEIGHTS_7"}; + +} // namespace CesiumGltf diff --git a/CesiumGltf/test/TestFeatureIdTextureView.cpp b/CesiumGltf/test/TestFeatureIdTextureView.cpp index 2b1857e81..f94480f86 100644 --- a/CesiumGltf/test/TestFeatureIdTextureView.cpp +++ b/CesiumGltf/test/TestFeatureIdTextureView.cpp @@ -75,8 +75,9 @@ TEST_CASE("Test FeatureIdTextureView on feature ID texture with empty image") { sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 0; - image.cesium.height = 0; + image.pAsset.emplace(); + image.pAsset->width = 0; + image.pAsset->height = 0; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -107,9 +108,10 @@ TEST_CASE("Test FeatureIdTextureView on feature ID texture with too many bytes " sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; - image.cesium.bytesPerChannel = 2; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; + image.pAsset->bytesPerChannel = 2; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -142,8 +144,9 @@ TEST_CASE( sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -174,8 +177,9 @@ TEST_CASE( sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -206,8 +210,9 @@ TEST_CASE("Test FeatureIdTextureView on feature ID texture with out of range " sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -237,8 +242,9 @@ TEST_CASE("Test FeatureIdTextureView on valid feature ID texture") { sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -269,8 +275,9 @@ TEST_CASE("Test FeatureIdTextureView with applyKhrTextureTransformExtension = " sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -318,8 +325,9 @@ TEST_CASE("Test FeatureIdTextureView with applyKhrTextureTransformExtension = " sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -371,13 +379,14 @@ TEST_CASE("Test FeatureIdTextureView with makeImageCopy = true") { sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -403,14 +412,14 @@ TEST_CASE("Test FeatureIdTextureView with makeImageCopy = true") { // Clear the original image data. std::vector emptyData; - image.cesium.pixelData.swap(emptyData); + image.pAsset->pixelData.swap(emptyData); - const ImageCesium* pImage = view.getImage(); + const ImageAsset* pImage = view.getImage(); REQUIRE(pImage); - REQUIRE(pImage->width == image.cesium.width); - REQUIRE(pImage->height == image.cesium.height); - REQUIRE(pImage->channels == image.cesium.channels); - REQUIRE(pImage->bytesPerChannel == image.cesium.bytesPerChannel); + REQUIRE(pImage->width == image.pAsset->width); + REQUIRE(pImage->height == image.pAsset->height); + REQUIRE(pImage->channels == image.pAsset->channels); + REQUIRE(pImage->bytesPerChannel == image.pAsset->bytesPerChannel); REQUIRE(pImage->pixelData.size() == featureIDs.size()); } @@ -423,8 +432,9 @@ TEST_CASE("Test getFeatureID on invalid feature ID texture view") { sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 1; - image.cesium.height = 1; + image.pAsset.emplace(); + image.pAsset->width = 1; + image.pAsset->height = 1; Texture& texture = model.textures.emplace_back(); texture.sampler = 0; @@ -457,13 +467,14 @@ TEST_CASE("Test getFeatureID on valid feature ID texture view") { std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -502,13 +513,14 @@ TEST_CASE("Test getFeatureID on view with applyKhrTextureTransformExtension = " std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -553,13 +565,14 @@ TEST_CASE( std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -609,13 +622,14 @@ TEST_CASE("Test getFeatureId on view with makeImageCopy = true") { sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -641,7 +655,7 @@ TEST_CASE("Test getFeatureId on view with makeImageCopy = true") { // Clear the original image data. std::vector emptyData; - image.cesium.pixelData.swap(emptyData); + image.pAsset->pixelData.swap(emptyData); REQUIRE(view.getFeatureID(0, 0) == 1); REQUIRE(view.getFeatureID(1, 0) == 2); @@ -660,13 +674,14 @@ TEST_CASE("Test getFeatureID rounds to nearest pixel") { std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(featureIDs.size()); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(featureIDs.size()); std::memcpy( - image.cesium.pixelData.data(), + image.pAsset->pixelData.data(), featureIDs.data(), featureIDs.size()); @@ -704,12 +719,13 @@ TEST_CASE("Test getFeatureID clamps values") { std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; - auto& data = image.cesium.pixelData; + auto& data = image.pAsset->pixelData; data.resize(featureIDs.size() * sizeof(uint8_t)); std::memcpy(data.data(), featureIDs.data(), data.size()); @@ -747,12 +763,13 @@ TEST_CASE("Test getFeatureID handles multiple channels") { std::vector featureIDs{260, 512, 8, 17}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 2; - image.cesium.bytesPerChannel = 1; + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 2; + image.pAsset->bytesPerChannel = 1; - auto& data = image.cesium.pixelData; + auto& data = image.pAsset->pixelData; data.resize(featureIDs.size() * sizeof(uint16_t)); std::memcpy(data.data(), featureIDs.data(), data.size()); @@ -788,12 +805,13 @@ TEST_CASE("Check FeatureIdTextureView sampling with different wrap values") { std::vector featureIDs{1, 2, 0, 7}; Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = 1; - image.cesium.bytesPerChannel = 1; + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = 1; + image.pAsset->bytesPerChannel = 1; - auto& data = image.cesium.pixelData; + auto& data = image.pAsset->pixelData; data.resize(featureIDs.size() * sizeof(uint8_t)); std::memcpy(data.data(), featureIDs.data(), data.size()); diff --git a/CesiumGltf/test/TestPropertyTexturePropertyView.cpp b/CesiumGltf/test/TestPropertyTexturePropertyView.cpp index ae3a815fb..eb71c2ac3 100644 --- a/CesiumGltf/test/TestPropertyTexturePropertyView.cpp +++ b/CesiumGltf/test/TestPropertyTexturePropertyView.cpp @@ -27,7 +27,7 @@ void checkTextureValues( convertPropertyComponentTypeToString(componentType); Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = static_cast(sizeof(T)); @@ -99,7 +99,7 @@ void checkTextureValues( classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = static_cast(sizeof(T)); @@ -173,7 +173,7 @@ void checkNormalizedTextureValues( classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = static_cast(sizeof(T)); @@ -243,7 +243,7 @@ void checkTextureArrayValues( classProperty.count = count; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = @@ -329,7 +329,7 @@ void checkTextureArrayValues( classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = @@ -429,7 +429,7 @@ void checkNormalizedTextureArrayValues( classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = @@ -1397,7 +1397,7 @@ TEST_CASE("Check that PropertyTextureProperty values override class property " classProperty.max = 10.0f; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1474,7 +1474,7 @@ TEST_CASE("Check that non-adjacent channels resolve to expected output") { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1516,7 +1516,7 @@ TEST_CASE("Check that non-adjacent channels resolve to expected output") { classProperty.componentType = ClassProperty::ComponentType::UINT16; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1557,7 +1557,7 @@ TEST_CASE("Check that non-adjacent channels resolve to expected output") { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1603,7 +1603,7 @@ TEST_CASE("Check that non-adjacent channels resolve to expected output") { classProperty.array = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1660,7 +1660,7 @@ TEST_CASE("Check sampling with different sampler values") { classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -1793,7 +1793,7 @@ TEST_CASE("Test PropertyTextureProperty constructs with " sampler.wrapS = Sampler::WrapS::REPEAT; sampler.wrapT = Sampler::WrapT::REPEAT; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -1866,7 +1866,7 @@ TEST_CASE("Test normalized PropertyTextureProperty constructs with " sampler.wrapS = Sampler::WrapS::REPEAT; sampler.wrapT = Sampler::WrapT::REPEAT; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -1940,7 +1940,7 @@ TEST_CASE("Test PropertyTextureProperty constructs with " sampler.wrapS = Sampler::WrapS::REPEAT; sampler.wrapT = Sampler::WrapT::REPEAT; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -1963,7 +1963,7 @@ TEST_CASE("Test PropertyTextureProperty constructs with " std::vector emptyData; image.pixelData.swap(emptyData); - const ImageCesium* pImage = view.getImage(); + const ImageAsset* pImage = view.getImage(); REQUIRE(pImage); REQUIRE(pImage->width == image.width); REQUIRE(pImage->height == image.height); @@ -2007,7 +2007,7 @@ TEST_CASE("Test normalized PropertyTextureProperty constructs with " sampler.wrapS = Sampler::WrapS::REPEAT; sampler.wrapT = Sampler::WrapT::REPEAT; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -2030,7 +2030,7 @@ TEST_CASE("Test normalized PropertyTextureProperty constructs with " std::vector emptyData; image.pixelData.swap(emptyData); - const ImageCesium* pImage = view.getImage(); + const ImageAsset* pImage = view.getImage(); REQUIRE(pImage); REQUIRE(pImage->width == image.width); REQUIRE(pImage->height == image.height); diff --git a/CesiumGltf/test/TestPropertyTextureView.cpp b/CesiumGltf/test/TestPropertyTextureView.cpp index 8c7aeef72..5aa773bf2 100644 --- a/CesiumGltf/test/TestPropertyTextureView.cpp +++ b/CesiumGltf/test/TestPropertyTextureView.cpp @@ -19,12 +19,13 @@ void addTextureToModel( int32_t channels, const std::vector& data) { Image& image = model.images.emplace_back(); - image.cesium.width = width; - image.cesium.height = height; - image.cesium.channels = channels; - image.cesium.bytesPerChannel = 1; + image.pAsset.emplace(); + image.pAsset->width = width; + image.pAsset->height = height; + image.pAsset->channels = channels; + image.pAsset->bytesPerChannel = 1; - std::vector& imageData = image.cesium.pixelData; + std::vector& imageData = image.pAsset->pixelData; imageData.resize(data.size()); std::memcpy(imageData.data(), data.data(), data.size()); @@ -248,7 +249,7 @@ TEST_CASE("Test scalar PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -308,7 +309,7 @@ TEST_CASE("Test scalar PropertyTextureProperty") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 2; + model.images[imageIndex].pAsset->channels = 2; propertyTextureProperty.channels = {0, 1}; PropertyTexturePropertyView uint8Property = view.getPropertyView("TestClassProperty"); @@ -336,7 +337,7 @@ TEST_CASE("Test scalar PropertyTextureProperty") { } SECTION("Invalid bytes per channel") { - model.images[imageIndex].cesium.bytesPerChannel = 2; + model.images[imageIndex].pAsset->bytesPerChannel = 2; PropertyTexturePropertyView uint8Property = view.getPropertyView("TestClassProperty"); REQUIRE( @@ -345,7 +346,7 @@ TEST_CASE("Test scalar PropertyTextureProperty") { } SECTION("Empty image") { - model.images[imageIndex].cesium.width = 0; + model.images[imageIndex].pAsset->width = 0; PropertyTexturePropertyView uint8Property = view.getPropertyView("TestClassProperty"); REQUIRE( @@ -493,7 +494,7 @@ TEST_CASE("Test scalar PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -556,7 +557,7 @@ TEST_CASE("Test scalar PropertyTextureProperty (normalized)") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 2; + model.images[imageIndex].pAsset->channels = 2; propertyTextureProperty.channels = {0, 1}; PropertyTexturePropertyView uint8Property = view.getPropertyView("TestClassProperty"); @@ -689,7 +690,7 @@ TEST_CASE("Test vecN PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -750,7 +751,7 @@ TEST_CASE("Test vecN PropertyTextureProperty") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 4; + model.images[imageIndex].pAsset->channels = 4; propertyTextureProperty.channels = {0, 1, 2, 3}; PropertyTexturePropertyView u8vec2Property = view.getPropertyView("TestClassProperty"); @@ -769,7 +770,7 @@ TEST_CASE("Test vecN PropertyTextureProperty") { } SECTION("Invalid bytes per channel") { - model.images[imageIndex].cesium.bytesPerChannel = 2; + model.images[imageIndex].pAsset->bytesPerChannel = 2; PropertyTexturePropertyView u8vec2Property = view.getPropertyView("TestClassProperty"); REQUIRE( @@ -903,7 +904,7 @@ TEST_CASE("Test vecN PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -973,7 +974,7 @@ TEST_CASE("Test vecN PropertyTextureProperty (normalized)") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 4; + model.images[imageIndex].pAsset->channels = 4; propertyTextureProperty.channels = {0, 1, 2, 3}; PropertyTexturePropertyView u8vec2Property = view.getPropertyView("TestClassProperty"); @@ -1151,7 +1152,7 @@ TEST_CASE("Test array PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -1220,7 +1221,7 @@ TEST_CASE("Test array PropertyTextureProperty") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 4; + model.images[imageIndex].pAsset->channels = 4; propertyTextureProperty.channels = {0, 1, 2, 3}; PropertyTexturePropertyView> uint8ArrayProperty = view.getPropertyView>("TestClassProperty"); @@ -1239,7 +1240,7 @@ TEST_CASE("Test array PropertyTextureProperty") { } SECTION("Invalid bytes per channel") { - model.images[imageIndex].cesium.bytesPerChannel = 2; + model.images[imageIndex].pAsset->bytesPerChannel = 2; PropertyTexturePropertyView> uint8ArrayProperty = view.getPropertyView>("TestClassProperty"); REQUIRE( @@ -1422,7 +1423,7 @@ TEST_CASE("Test array PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap(emptyData); + model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData); std::vector texCoords{ glm::dvec2(0, 0), @@ -1492,7 +1493,7 @@ TEST_CASE("Test array PropertyTextureProperty (normalized)") { } SECTION("Channel and type mismatch") { - model.images[imageIndex].cesium.channels = 4; + model.images[imageIndex].pAsset->channels = 4; propertyTextureProperty.channels = {0, 1, 2, 3}; PropertyTexturePropertyView, true> uint8ArrayProperty = @@ -2244,7 +2245,7 @@ TEST_CASE("Test callback for scalar PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { @@ -2366,7 +2367,7 @@ TEST_CASE("Test callback for scalar PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { @@ -2497,7 +2498,7 @@ TEST_CASE("Test callback for vecN PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { @@ -2629,7 +2630,7 @@ TEST_CASE("Test callback for vecN PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { @@ -2782,7 +2783,7 @@ TEST_CASE("Test callback for array PropertyTextureProperty") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { @@ -2952,7 +2953,7 @@ TEST_CASE("Test callback for array PropertyTextureProperty (normalized)") { // Clear the original image data. std::vector emptyData; - model.images[model.images.size() - 1].cesium.pixelData.swap( + model.images[model.images.size() - 1].pAsset->pixelData.swap( emptyData); for (size_t i = 0; i < expected.size(); ++i) { diff --git a/CesiumGltfContent/include/CesiumGltfContent/ImageManipulation.h b/CesiumGltfContent/include/CesiumGltfContent/ImageManipulation.h index 82b3e2466..2f350629b 100644 --- a/CesiumGltfContent/include/CesiumGltfContent/ImageManipulation.h +++ b/CesiumGltfContent/include/CesiumGltfContent/ImageManipulation.h @@ -7,7 +7,7 @@ // Forward declarations namespace CesiumGltf { -struct ImageCesium; +struct ImageAsset; } namespace CesiumGltfContent { @@ -91,9 +91,9 @@ class CESIUMGLTFCONTENT_API ImageManipulation { * incompatible formats. */ static bool blitImage( - CesiumGltf::ImageCesium& target, + CesiumGltf::ImageAsset& target, const PixelRectangle& targetPixels, - const CesiumGltf::ImageCesium& source, + const CesiumGltf::ImageAsset& source, const PixelRectangle& sourcePixels); /** @@ -103,7 +103,7 @@ class CESIUMGLTFCONTENT_API ImageManipulation { * @return The byte buffer containing the image. If the buffer is empty, the * image could not be written. */ - static std::vector savePng(const CesiumGltf::ImageCesium& image); + static std::vector savePng(const CesiumGltf::ImageAsset& image); /** * @brief Saves an image to an existing byte buffer in PNG format. @@ -114,7 +114,7 @@ class CESIUMGLTFCONTENT_API ImageManipulation { * could not be written. */ static void - savePng(const CesiumGltf::ImageCesium& image, std::vector& output); + savePng(const CesiumGltf::ImageAsset& image, std::vector& output); }; } // namespace CesiumGltfContent diff --git a/CesiumGltfContent/src/ImageManipulation.cpp b/CesiumGltfContent/src/ImageManipulation.cpp index c8de11e06..9bd129a3a 100644 --- a/CesiumGltfContent/src/ImageManipulation.cpp +++ b/CesiumGltfContent/src/ImageManipulation.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -43,9 +43,9 @@ void ImageManipulation::unsafeBlitImage( } bool ImageManipulation::blitImage( - CesiumGltf::ImageCesium& target, + CesiumGltf::ImageAsset& target, const PixelRectangle& targetPixels, - const CesiumGltf::ImageCesium& source, + const CesiumGltf::ImageAsset& source, const PixelRectangle& sourcePixels) { if (sourcePixels.x < 0 || sourcePixels.y < 0 || sourcePixels.width < 0 || @@ -134,7 +134,7 @@ void writePngToVector(void* context, void* data, int size) { } // namespace /*static*/ void ImageManipulation::savePng( - const CesiumGltf::ImageCesium& image, + const CesiumGltf::ImageAsset& image, std::vector& output) { if (image.bytesPerChannel != 1) { // Only 8-bit images can be written. @@ -152,7 +152,7 @@ void writePngToVector(void* context, void* data, int size) { } /*static*/ std::vector -ImageManipulation::savePng(const CesiumGltf::ImageCesium& image) { +ImageManipulation::savePng(const CesiumGltf::ImageAsset& image) { std::vector result; savePng(image, result); return result; diff --git a/CesiumGltfContent/test/TestImageManipulation.cpp b/CesiumGltfContent/test/TestImageManipulation.cpp index 38833411b..227e9a8c6 100644 --- a/CesiumGltfContent/test/TestImageManipulation.cpp +++ b/CesiumGltfContent/test/TestImageManipulation.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -128,7 +128,7 @@ TEST_CASE("ImageManipulation::unsafeBlitImage subset of source") { } TEST_CASE("ImageManipulation::blitImage") { - ImageCesium target; + ImageAsset target; target.bytesPerChannel = 2; target.channels = 4; target.width = 15; @@ -139,7 +139,7 @@ TEST_CASE("ImageManipulation::blitImage") { target.bytesPerChannel), std::byte(1)); - ImageCesium source; + ImageAsset source; source.bytesPerChannel = 2; source.channels = 4; source.width = 10; diff --git a/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h b/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h index 69e695d17..62a496dcf 100644 --- a/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h +++ b/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h @@ -1,14 +1,16 @@ #pragma once +#include "CesiumGltfReader/ImageDecoder.h" #include "CesiumGltfReader/Library.h" #include #include #include #include -#include +#include #include #include +#include #include #include @@ -43,30 +45,6 @@ struct CESIUMGLTFREADER_API GltfReaderResult { std::vector warnings; }; -/** - * @brief The result of reading an image with - * {@link GltfReader::readImage}. - */ -struct CESIUMGLTFREADER_API ImageReaderResult { - - /** - * @brief The {@link ImageCesium} that was read. - * - * This will be `std::nullopt` if the image could not be read. - */ - std::optional image; - - /** - * @brief Error messages that occurred while trying to read the image. - */ - std::vector errors; - - /** - * @brief Warning messages that occurred while reading the image. - */ - std::vector warnings; -}; - /** * @brief Options for how to read a glTF. */ @@ -131,6 +109,13 @@ struct CESIUMGLTFREADER_API GltfReaderOptions { * the ideal target gpu-compressed pixel format to transcode to. */ CesiumGltf::Ktx2TranscodeTargets ktx2TranscodeTargets; + + /** + * The shared asset system that will be used to store all of the shared assets + * that might appear in this glTF. + */ + CesiumUtility::IntrusivePointer pSharedAssetSystem = + GltfSharedAssetSystem::getDefault(); }; /** @@ -213,32 +198,22 @@ class CESIUMGLTFREADER_API GltfReader { GltfReaderResult&& result); /** - * @brief Reads an image from a buffer. - * - * The [stb_image](https://github.com/nothings/stb) library is used to decode - * images in `JPG`, `PNG`, `TGA`, `BMP`, `PSD`, `GIF`, `HDR`, or `PIC` format. - * - * @param data The buffer from which to read the image. - * @param ktx2TranscodeTargetFormat The compression format to transcode - * KTX v2 textures into. If this is std::nullopt, KTX v2 textures will be - * fully decompressed into raw pixels. - * @return The result of reading the image. + * @brief Reads an Image from a buffer. + * @deprecated Use {@link ImageDecoder::readImage} instead. */ - static ImageReaderResult readImage( + [[deprecated( + "Use ImageDecoder::readImage instead.")]] static ImageReaderResult + readImage( const gsl::span& data, const CesiumGltf::Ktx2TranscodeTargets& ktx2TranscodeTargets); /** * @brief Generate mipmaps for this image. - * - * Does nothing if mipmaps already exist or the compressedPixelFormat is not - * GpuCompressedPixelFormat::NONE. - * - * @param image The image to generate mipmaps for. * - * @return A string describing the error, if unable to generate mipmaps. + * @deprecated Use {@link ImageDecoder::generateMipMaps} instead. */ - static std::optional - generateMipMaps(CesiumGltf::ImageCesium& image); + [[deprecated("Use ImageDecoder::generateMipMaps instead.")]] static std:: + optional + generateMipMaps(CesiumGltf::ImageAsset& image); private: CesiumJsonReader::JsonReaderOptions _context; diff --git a/CesiumGltfReader/include/CesiumGltfReader/GltfSharedAssetSystem.h b/CesiumGltfReader/include/CesiumGltfReader/GltfSharedAssetSystem.h new file mode 100644 index 000000000..f28eaafa1 --- /dev/null +++ b/CesiumGltfReader/include/CesiumGltfReader/GltfSharedAssetSystem.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace CesiumGltfReader { + +/** + * @brief Contains assets that are potentially shared across multiple glTF + * models. + */ +class GltfSharedAssetSystem + : public CesiumUtility::ReferenceCountedThreadSafe { +public: + static CesiumUtility::IntrusivePointer getDefault(); + + virtual ~GltfSharedAssetSystem() = default; + + using ImageDepot = CesiumAsync:: + SharedAssetDepot; + + /** + * @brief The asset depot for images. + */ + CesiumUtility::IntrusivePointer pImage; +}; + +} // namespace CesiumGltfReader diff --git a/CesiumGltfReader/include/CesiumGltfReader/ImageDecoder.h b/CesiumGltfReader/include/CesiumGltfReader/ImageDecoder.h new file mode 100644 index 000000000..c3bb0b300 --- /dev/null +++ b/CesiumGltfReader/include/CesiumGltfReader/ImageDecoder.h @@ -0,0 +1,73 @@ +#pragma once + +#include "CesiumGltf/ImageAsset.h" +#include "CesiumGltfReader/Library.h" + +#include + +#include + +#include +#include +#include + +namespace CesiumGltfReader { + +/** + * @brief The result of reading an image with {@link ImageDecoder::readImage}. + */ +struct CESIUMGLTFREADER_API ImageReaderResult { + + /** + * @brief The {@link ImageAsset} that was read. + * + * This will be `std::nullopt` if the image could not be read. + */ + CesiumUtility::IntrusivePointer pImage; + + /** + * @brief Error messages that occurred while trying to read the image. + */ + std::vector errors; + + /** + * @brief Warning messages that occurred while reading the image. + */ + std::vector warnings; +}; + +/** + * @brief Contains methods for reading and manipulating images. + */ +class ImageDecoder { +public: + /** + * @brief Reads an image from a buffer. + * + * The [stb_image](https://github.com/nothings/stb) library is used to decode + * images in `JPG`, `PNG`, `TGA`, `BMP`, `PSD`, `GIF`, `HDR`, or `PIC` format. + * + * @param data The buffer from which to read the image. + * @param ktx2TranscodeTargetFormat The compression format to transcode + * KTX v2 textures into. If this is std::nullopt, KTX v2 textures will be + * fully decompressed into raw pixels. + * @return The result of reading the image. + */ + static ImageReaderResult readImage( + const gsl::span& data, + const CesiumGltf::Ktx2TranscodeTargets& ktx2TranscodeTargets); + + /** + * @brief Generate mipmaps for this image. + * + * Does nothing if mipmaps already exist or the compressedPixelFormat is not + * GpuCompressedPixelFormat::NONE. + * + * @param image The image to generate mipmaps for. * + * @return A string describing the error, if unable to generate mipmaps. + */ + static std::optional + generateMipMaps(CesiumGltf::ImageAsset& image); +}; + +} // namespace CesiumGltfReader diff --git a/CesiumGltfReader/include/CesiumGltfReader/NetworkImageAssetDescriptor.h b/CesiumGltfReader/include/CesiumGltfReader/NetworkImageAssetDescriptor.h new file mode 100644 index 000000000..2c2c30f03 --- /dev/null +++ b/CesiumGltfReader/include/CesiumGltfReader/NetworkImageAssetDescriptor.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace CesiumAsync { +class AsyncSystem; +} + +namespace CesiumGltfReader { + +/** + * @brief A description of an image asset that can be loaded from the network + * using an {@link IAssetAccessor}. This includes a URL, any headers to be + * included in the request, and the set of supported GPU texture formats for + * KTX2 decoding. + */ +struct NetworkImageAssetDescriptor + : public CesiumAsync::NetworkAssetDescriptor { + /** + * @brief The supported GPU texture formats used for KTX2 decoding. + */ + CesiumGltf::Ktx2TranscodeTargets ktx2TranscodeTargets{}; + + /** + * @brief Determines if this descriptor is identical to another one. + */ + bool operator==(const NetworkImageAssetDescriptor& rhs) const noexcept; + + /** + * @brief Request this asset from the network using the provided asset + * accessor and return the loaded {@link ImageAsset}. + * + * @param asyncSystem The async system. + * @param pAssetAccessor The asset accessor. + * @return A future that resolves to the image asset once the request is + * complete. + */ + CesiumAsync::Future> + load( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const; +}; + +} // namespace CesiumGltfReader + +template <> struct std::hash { + std::size_t operator()( + const CesiumGltfReader::NetworkImageAssetDescriptor& key) const noexcept; +}; diff --git a/CesiumGltfReader/src/GltfReader.cpp b/CesiumGltfReader/src/GltfReader.cpp index fc1a1afce..28a84b941 100644 --- a/CesiumGltfReader/src/GltfReader.cpp +++ b/CesiumGltfReader/src/GltfReader.cpp @@ -29,27 +29,11 @@ #include #include -#define STBI_FAILURE_USERMSG - -namespace Cesium { -// Use STB resize in our own namespace to avoid conflicts from other libs -#define STBIRDEF -#define STB_IMAGE_RESIZE_IMPLEMENTATION -#include -#undef STBIRDEF -} // namespace Cesium - -#define STB_IMAGE_STATIC -#define STB_IMAGE_IMPLEMENTATION -#include -#include - using namespace CesiumAsync; using namespace CesiumGltf; using namespace CesiumGltfReader; using namespace CesiumJsonReader; using namespace CesiumUtility; -using namespace Cesium; namespace { #pragma pack(push, 1) @@ -246,10 +230,7 @@ GltfReaderResult readBinaryGltf( return result; } -void postprocess( - const GltfReader& reader, - GltfReaderResult& readGltf, - const GltfReaderOptions& options) { +void postprocess(GltfReaderResult& readGltf, const GltfReaderOptions& options) { Model& model = readGltf.model.value(); auto extFeatureMetadataIter = std::find( @@ -265,7 +246,7 @@ void postprocess( } if (options.decodeDataUrls) { - decodeDataUrls(reader, readGltf, options); + decodeDataUrls(readGltf, options); } if (options.decodeEmbeddedImages) { @@ -277,7 +258,7 @@ void postprocess( } // Image has already been decoded - if (!image.cesium.pixelData.empty()) { + if (image.pAsset && !image.pAsset->pixelData.empty()) { continue; } @@ -302,7 +283,7 @@ void postprocess( static_cast(bufferView.byteOffset), static_cast(bufferView.byteLength)); ImageReaderResult imageResult = - GltfReader::readImage(bufferViewSpan, options.ktx2TranscodeTargets); + ImageDecoder::readImage(bufferViewSpan, options.ktx2TranscodeTargets); readGltf.warnings.insert( readGltf.warnings.end(), imageResult.warnings.begin(), @@ -311,8 +292,8 @@ void postprocess( readGltf.errors.end(), imageResult.errors.begin(), imageResult.errors.end()); - if (imageResult.image) { - image.cesium = std::move(imageResult.image.value()); + if (imageResult.pImage) { + image.pAsset = imageResult.pImage; } else { if (image.mimeType) { readGltf.errors.emplace_back( @@ -393,7 +374,7 @@ GltfReaderResult GltfReader::readGltf( : readJsonGltf(context, data); if (result.model) { - postprocess(*this, result, options); + postprocess(result, options); } return result; @@ -448,8 +429,8 @@ CesiumAsync::Future GltfReader::loadGltf( options, std::move(result)); }) - .thenInWorkerThread([options, this](GltfReaderResult&& result) { - postprocess(*this, result, options); + .thenInWorkerThread([options](GltfReaderResult&& result) { + postprocess(result, options); return std::move(result); }); } @@ -458,7 +439,7 @@ void CesiumGltfReader::GltfReader::postprocessGltf( GltfReaderResult& readGltf, const GltfReaderOptions& options) { if (readGltf.model) { - postprocess(*this, readGltf, options); + postprocess(readGltf, options); } } @@ -501,6 +482,7 @@ void CesiumGltfReader::GltfReader::postprocessGltf( struct ExternalBufferLoadResult { bool success = false; std::string bufferUri; + ErrorList warningsAndErrors; }; std::vector> resolvedBuffers; @@ -515,53 +497,88 @@ void CesiumGltfReader::GltfReader::postprocessGltf( resolvedBuffers.push_back( pAssetAccessor ->get(asyncSystem, Uri::resolve(baseUrl, *buffer.uri), tHeaders) - .thenInWorkerThread( - [pBuffer = - &buffer](std::shared_ptr&& pRequest) { - const IAssetResponse* pResponse = pRequest->response(); - - std::string bufferUri = *pBuffer->uri; - - if (pResponse) { - pBuffer->uri = std::nullopt; - pBuffer->cesium.data = std::vector( - pResponse->data().begin(), - pResponse->data().end()); - return ExternalBufferLoadResult{true, bufferUri}; - } - - return ExternalBufferLoadResult{false, bufferUri}; - })); + .thenInWorkerThread([pBuffer = + &buffer](std::shared_ptr&& + pRequest) { + std::string bufferUri = *pBuffer->uri; + + const IAssetResponse* pResponse = pRequest->response(); + if (!pResponse) { + return ExternalBufferLoadResult{ + false, + bufferUri, + ErrorList::error("Request failed.")}; + } + + uint16_t statusCode = pResponse->statusCode(); + if (statusCode != 0 && + (statusCode < 200 || statusCode >= 300)) { + return ExternalBufferLoadResult{ + false, + bufferUri, + ErrorList::error( + fmt::format("Received status code {}.", statusCode))}; + } + + pBuffer->uri = std::nullopt; + pBuffer->cesium.data = std::vector( + pResponse->data().begin(), + pResponse->data().end()); + return ExternalBufferLoadResult{true, bufferUri, ErrorList()}; + })); } } if (options.resolveExternalImages) { for (Image& image : pResult->model->images) { if (image.uri && image.uri->substr(0, dataPrefixLength) != dataPrefix) { - resolvedBuffers.push_back( - pAssetAccessor - ->get(asyncSystem, Uri::resolve(baseUrl, *image.uri), tHeaders) - .thenInWorkerThread( - [pImage = &image, - ktx2TranscodeTargets = options.ktx2TranscodeTargets]( - std::shared_ptr&& pRequest) { - const IAssetResponse* pResponse = pRequest->response(); - - std::string imageUri = *pImage->uri; - - if (pResponse) { - pImage->uri = std::nullopt; - - ImageReaderResult imageResult = - readImage(pResponse->data(), ktx2TranscodeTargets); - if (imageResult.image) { - pImage->cesium = std::move(*imageResult.image); - return ExternalBufferLoadResult{true, imageUri}; - } - } - - return ExternalBufferLoadResult{false, imageUri}; - })); + const std::string uri = Uri::resolve(baseUrl, *image.uri); + + auto getAsset = + [&options]( + const AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const std::string& uri, + const std::vector& headers) + -> SharedFuture> { + NetworkImageAssetDescriptor assetKey{ + {uri, headers}, + options.ktx2TranscodeTargets}; + + if (options.pSharedAssetSystem == nullptr || + options.pSharedAssetSystem->pImage == nullptr) { + // We don't have a depot, so fetch this asset directly. + return assetKey.load(asyncSystem, pAssetAccessor).share(); + } else { + // We have a depot, so fetch this asset via that depot. + return options.pSharedAssetSystem->pImage->getOrCreate( + asyncSystem, + pAssetAccessor, + assetKey); + } + }; + + SharedFuture> future = + getAsset(asyncSystem, pAssetAccessor, uri, tHeaders); + + resolvedBuffers.push_back(future.thenInWorkerThread( + [pImage = &image](const ResultPointer& loadedImage) { + std::string imageUri = *pImage->uri; + pImage->uri = std::nullopt; + + if (loadedImage.pValue) { + pImage->pAsset = loadedImage.pValue; + return ExternalBufferLoadResult{ + true, + imageUri, + loadedImage.errors}; + } + + return ExternalBufferLoadResult{ + false, + imageUri, + loadedImage.errors}; + })); } } } @@ -573,420 +590,40 @@ void CesiumGltfReader::GltfReader::postprocessGltf( for (auto& bufferResult : loadResults) { if (!bufferResult.success) { pResult->warnings.push_back( - "Could not load the external gltf buffer: " + + "Could not load the external glTF buffer: " + bufferResult.bufferUri); } - } - return std::move(*pResult.release()); - }); -} -bool isKtx(const gsl::span& data) { - const size_t ktxMagicByteLength = 12; - if (data.size() < ktxMagicByteLength) { - return false; - } - - const uint8_t ktxMagic[ktxMagicByteLength] = - {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}; + if (!bufferResult.warningsAndErrors.errors.empty()) { + pResult->warnings.emplace_back(fmt::format( + "Errors while loading external glTF buffer: {}\n- {}", + bufferResult.bufferUri, + CesiumUtility::joinToString( + bufferResult.warningsAndErrors.errors, + "\n- "))); + } - return memcmp(data.data(), ktxMagic, ktxMagicByteLength) == 0; -} + if (!bufferResult.warningsAndErrors.warnings.empty()) { + pResult->warnings.emplace_back(fmt::format( + "Warnings while loading external glTF buffer: {}\n- {}", + bufferResult.bufferUri, + CesiumUtility::joinToString( + bufferResult.warningsAndErrors.warnings, + "\n- "))); + } + } -bool isWebP(const gsl::span& data) { - if (data.size() < 12) { - return false; - } - const uint32_t magic1 = *reinterpret_cast(data.data()); - const uint32_t magic2 = *reinterpret_cast(data.data() + 8); - return magic1 == 0x46464952 && magic2 == 0x50424557; + return std::move(*pResult.release()); + }); } -/*static*/ -ImageReaderResult GltfReader::readImage( +/*static*/ ImageReaderResult GltfReader::readImage( const gsl::span& data, const Ktx2TranscodeTargets& ktx2TranscodeTargets) { - CESIUM_TRACE("CesiumGltfReader::readImage"); - - ImageReaderResult result; - - result.image.emplace(); - ImageCesium& image = result.image.value(); - - if (isKtx(data)) { - ktxTexture2* pTexture = nullptr; - KTX_error_code errorCode; - - errorCode = ktxTexture2_CreateFromMemory( - reinterpret_cast(data.data()), - data.size(), - KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &pTexture); - - if (errorCode == KTX_SUCCESS) { - if (ktxTexture2_NeedsTranscoding(pTexture)) { - - CESIUM_TRACE("Transcode KTXv2"); - - image.channels = - static_cast(ktxTexture2_GetNumComponents(pTexture)); - GpuCompressedPixelFormat transcodeTargetFormat = - GpuCompressedPixelFormat::NONE; - - if (pTexture->supercompressionScheme == KTX_SS_BASIS_LZ) { - switch (image.channels) { - case 1: - transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_R; - break; - case 2: - transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RG; - break; - case 3: - transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RGB; - break; - // case 4: - default: - transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RGBA; - } - } else { - switch (image.channels) { - case 1: - transcodeTargetFormat = ktx2TranscodeTargets.UASTC_R; - break; - case 2: - transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RG; - break; - case 3: - transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RGB; - break; - // case 4: - default: - transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RGBA; - } - } - - ktx_transcode_fmt_e transcodeTargetFormat_ = KTX_TTF_RGBA32; - switch (transcodeTargetFormat) { - case GpuCompressedPixelFormat::ETC1_RGB: - transcodeTargetFormat_ = KTX_TTF_ETC1_RGB; - break; - case GpuCompressedPixelFormat::ETC2_RGBA: - transcodeTargetFormat_ = KTX_TTF_ETC2_RGBA; - break; - case GpuCompressedPixelFormat::BC1_RGB: - transcodeTargetFormat_ = KTX_TTF_BC1_RGB; - break; - case GpuCompressedPixelFormat::BC3_RGBA: - transcodeTargetFormat_ = KTX_TTF_BC3_RGBA; - break; - case GpuCompressedPixelFormat::BC4_R: - transcodeTargetFormat_ = KTX_TTF_BC4_R; - break; - case GpuCompressedPixelFormat::BC5_RG: - transcodeTargetFormat_ = KTX_TTF_BC5_RG; - break; - case GpuCompressedPixelFormat::BC7_RGBA: - transcodeTargetFormat_ = KTX_TTF_BC7_RGBA; - break; - case GpuCompressedPixelFormat::PVRTC1_4_RGB: - transcodeTargetFormat_ = KTX_TTF_PVRTC1_4_RGB; - break; - case GpuCompressedPixelFormat::PVRTC1_4_RGBA: - transcodeTargetFormat_ = KTX_TTF_PVRTC1_4_RGBA; - break; - case GpuCompressedPixelFormat::ASTC_4x4_RGBA: - transcodeTargetFormat_ = KTX_TTF_ASTC_4x4_RGBA; - break; - case GpuCompressedPixelFormat::PVRTC2_4_RGB: - transcodeTargetFormat_ = KTX_TTF_PVRTC2_4_RGB; - break; - case GpuCompressedPixelFormat::PVRTC2_4_RGBA: - transcodeTargetFormat_ = KTX_TTF_PVRTC2_4_RGBA; - break; - case GpuCompressedPixelFormat::ETC2_EAC_R11: - transcodeTargetFormat_ = KTX_TTF_ETC2_EAC_R11; - break; - case GpuCompressedPixelFormat::ETC2_EAC_RG11: - transcodeTargetFormat_ = KTX_TTF_ETC2_EAC_RG11; - break; - // case NONE: - default: - transcodeTargetFormat_ = KTX_TTF_RGBA32; - break; - }; - - errorCode = - ktxTexture2_TranscodeBasis(pTexture, transcodeTargetFormat_, 0); - if (errorCode != KTX_SUCCESS) { - transcodeTargetFormat_ = KTX_TTF_RGBA32; - transcodeTargetFormat = GpuCompressedPixelFormat::NONE; - errorCode = - ktxTexture2_TranscodeBasis(pTexture, transcodeTargetFormat_, 0); - } - if (errorCode == KTX_SUCCESS) { - image.compressedPixelFormat = transcodeTargetFormat; - image.width = static_cast(pTexture->baseWidth); - image.height = static_cast(pTexture->baseHeight); - - if (transcodeTargetFormat == GpuCompressedPixelFormat::NONE) { - // We fully decompressed the texture in this case. - image.bytesPerChannel = 1; - image.channels = 4; - } - - // In the KTX2 spec, there's a distinction between "this image has no - // mipmaps, so they should be generated at runtime" and "this - // image has no mipmaps because it makes no sense to create a mipmap - // for this type of image." It is, confusingly, encoded in the - // `levelCount` property: - // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_levelcount - // - // With `levelCount=0`, mipmaps should be generated. With - // `levelCount=1`, mipmaps make no sense. So when `levelCount=0`, we - // want to leave the `mipPositions` array _empty_. With - // `levelCount=1`, we want to populate it with a single mip level. - // - // However, this `levelCount` property is not directly exposed by the - // KTX2 loader API we're using here. Instead, there is a `numLevels` - // property, but it will _never_ have the value 0, because it - // represents the number of levels of actual pixel data we have. When - // the API sees `levelCount=0`, it will assign the value 1 to - // `numLevels`, but it will _also_ set `generateMipmaps` to true. - // - // The API docs say that `numLevels` will always be 1 when - // `generateMipmaps` is true. - // - // So, in summary, when `generateMipmaps=false`, we populate - // `mipPositions` with whatever mip levels the KTX provides and we - // don't generate any more. When it's true, we treat all the image - // data as belonging to a single base-level image and generate mipmaps - // from that if necessary. - if (!pTexture->generateMipmaps) { - // Copy over the positions of each mip within the buffer. - image.mipPositions.resize(pTexture->numLevels); - for (ktx_uint32_t level = 0; level < pTexture->numLevels; ++level) { - ktx_size_t imageOffset; - ktxTexture_GetImageOffset( - ktxTexture(pTexture), - level, - 0, - 0, - &imageOffset); - ktx_size_t imageSize = - ktxTexture_GetImageSize(ktxTexture(pTexture), level); - - image.mipPositions[level] = {imageOffset, imageSize}; - } - } else { - CESIUM_ASSERT(pTexture->numLevels == 1); - } - - // Copy over the entire buffer, including all mips. - ktx_uint8_t* pixelData = ktxTexture_GetData(ktxTexture(pTexture)); - ktx_size_t pixelDataSize = - ktxTexture_GetDataSize(ktxTexture(pTexture)); - - image.pixelData.resize(pixelDataSize); - std::uint8_t* u8Pointer = - reinterpret_cast(image.pixelData.data()); - std::copy(pixelData, pixelData + pixelDataSize, u8Pointer); - - ktxTexture_Destroy(ktxTexture(pTexture)); - - return result; - } - } - } - - result.image.reset(); - result.errors.emplace_back( - "KTX2 loading failed with error: " + - std::string(ktxErrorString(errorCode))); - - return result; - } else if (isWebP(data)) { - if (WebPGetInfo( - reinterpret_cast(data.data()), - data.size(), - &image.width, - &image.height)) { - image.channels = 4; - image.bytesPerChannel = 1; - uint8_t* pImage = NULL; - const auto bufferSize = image.width * image.height * image.channels; - image.pixelData.resize(static_cast(bufferSize)); - pImage = WebPDecodeRGBAInto( - reinterpret_cast(data.data()), - data.size(), - reinterpret_cast(image.pixelData.data()), - image.pixelData.size(), - image.width * image.channels); - if (!pImage) { - result.image.reset(); - result.errors.emplace_back("Unable to decode WebP"); - } - return result; - } - } - - { - tjhandle tjInstance = tjInitDecompress(); - int inSubsamp, inColorspace; - if (!tjDecompressHeader3( - tjInstance, - reinterpret_cast(data.data()), - static_cast(data.size()), - &image.width, - &image.height, - &inSubsamp, - &inColorspace)) { - CESIUM_TRACE("Decode JPG"); - image.bytesPerChannel = 1; - image.channels = 4; - const auto lastByte = - image.width * image.height * image.channels * image.bytesPerChannel; - image.pixelData.resize(static_cast(lastByte)); - if (tjDecompress2( - tjInstance, - reinterpret_cast(data.data()), - static_cast(data.size()), - reinterpret_cast(image.pixelData.data()), - image.width, - 0, - image.height, - TJPF_RGBA, - 0)) { - result.errors.emplace_back("Unable to decode JPEG"); - result.image.reset(); - } - } else { - CESIUM_TRACE("Decode PNG"); - image.bytesPerChannel = 1; - image.channels = 4; - - int channelsInFile; - stbi_uc* pImage = stbi_load_from_memory( - reinterpret_cast(data.data()), - static_cast(data.size()), - &image.width, - &image.height, - &channelsInFile, - image.channels); - if (pImage) { - CESIUM_TRACE( - "copy image " + std::to_string(image.width) + "x" + - std::to_string(image.height) + "x" + - std::to_string(image.channels) + "x" + - std::to_string(image.bytesPerChannel)); - // std::uint8_t is not implicitly convertible to std::byte, so we must - // use reinterpret_cast to (safely) force the conversion. - const auto lastByte = - image.width * image.height * image.channels * image.bytesPerChannel; - image.pixelData.resize(static_cast(lastByte)); - std::uint8_t* u8Pointer = - reinterpret_cast(image.pixelData.data()); - std::copy(pImage, pImage + lastByte, u8Pointer); - stbi_image_free(pImage); - } else { - result.image.reset(); - result.errors.emplace_back(stbi_failure_reason()); - } - } - tjDestroy(tjInstance); - } - return result; + return ImageDecoder::readImage(data, ktx2TranscodeTargets); } -/*static*/ -std::optional GltfReader::generateMipMaps(ImageCesium& image) { - if (!image.mipPositions.empty() || - image.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { - // No error message needed, since this is not technically a failure. - return std::nullopt; - } - - if (image.pixelData.empty()) { - return "Unable to generate mipmaps, an empty image was provided."; - } - - CESIUM_TRACE( - "generate mipmaps " + std::to_string(image.width) + "x" + - std::to_string(image.height) + "x" + std::to_string(image.channels) + - "x" + std::to_string(image.bytesPerChannel)); - - int32_t mipWidth = image.width; - int32_t mipHeight = image.height; - int32_t totalPixelCount = mipWidth * mipHeight; - size_t mipCount = 1; - while (mipWidth > 1 || mipHeight > 1) { - ++mipCount; - - if (mipWidth > 1) { - mipWidth >>= 1; - } - - if (mipHeight > 1) { - mipHeight >>= 1; - } - - // Total pixels in the final mipmap. - totalPixelCount += mipWidth * mipHeight; - } - - // Byte size of the base image. - const size_t imageByteSize = static_cast( - image.width * image.height * image.channels * image.bytesPerChannel); - - image.mipPositions.resize(mipCount); - image.mipPositions[0].byteOffset = 0; - image.mipPositions[0].byteSize = imageByteSize; - - image.pixelData.resize(static_cast( - totalPixelCount * image.channels * image.bytesPerChannel)); - - mipWidth = image.width; - mipHeight = image.height; - size_t mipIndex = 0; - size_t byteOffset = 0; - size_t byteSize = imageByteSize; - while (mipWidth > 1 || mipHeight > 1) { - size_t lastByteOffset = byteOffset; - byteOffset += byteSize; - ++mipIndex; - - int32_t lastWidth = mipWidth; - if (mipWidth > 1) { - mipWidth >>= 1; - } - - int32_t lastHeight = mipHeight; - if (mipHeight > 1) { - mipHeight >>= 1; - } - - byteSize = static_cast( - mipWidth * mipHeight * image.channels * image.bytesPerChannel); - - image.mipPositions[mipIndex].byteOffset = byteOffset; - image.mipPositions[mipIndex].byteSize = byteSize; - - if (!stbir_resize_uint8( - reinterpret_cast( - &image.pixelData[lastByteOffset]), - lastWidth, - lastHeight, - 0, - reinterpret_cast(&image.pixelData[byteOffset]), - mipWidth, - mipHeight, - 0, - image.channels)) { - // Remove any added mipmaps. - image.mipPositions.clear(); - image.pixelData.resize(imageByteSize); - return stbi_failure_reason(); - } - } - - return std::nullopt; +/*static*/ std::optional +GltfReader::generateMipMaps(CesiumGltf::ImageAsset& image) { + return ImageDecoder::generateMipMaps(image); } diff --git a/CesiumGltfReader/src/GltfSharedAssetSystem.cpp b/CesiumGltfReader/src/GltfSharedAssetSystem.cpp new file mode 100644 index 000000000..ffe97c6cd --- /dev/null +++ b/CesiumGltfReader/src/GltfSharedAssetSystem.cpp @@ -0,0 +1,36 @@ +#include + +using namespace CesiumAsync; +using namespace CesiumGltf; +using namespace CesiumGltfReader; +using namespace CesiumUtility; + +namespace { + +CesiumUtility::IntrusivePointer createDefault() { + CesiumUtility::IntrusivePointer p = + new GltfSharedAssetSystem(); + + p->pImage.emplace(std::function( + [](const AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const NetworkImageAssetDescriptor& key) + -> Future> { + return key.load(asyncSystem, pAssetAccessor); + })); + + return p; +} + +} // namespace + +namespace CesiumGltfReader { + +/*static*/ CesiumUtility::IntrusivePointer +GltfSharedAssetSystem::getDefault() { + static CesiumUtility::IntrusivePointer pDefault = + createDefault(); + return pDefault; +} + +} // namespace CesiumGltfReader diff --git a/CesiumGltfReader/src/ImageDecoder.cpp b/CesiumGltfReader/src/ImageDecoder.cpp new file mode 100644 index 000000000..664f7910c --- /dev/null +++ b/CesiumGltfReader/src/ImageDecoder.cpp @@ -0,0 +1,460 @@ +#include "CesiumGltfReader/ImageDecoder.h" + +#include "ModelJsonHandler.h" +#include "applyKhrTextureTransform.h" +#include "decodeDataUrls.h" +#include "decodeDraco.h" +#include "decodeMeshOpt.h" +#include "dequantizeMeshData.h" +#include "registerReaderExtensions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define STBI_FAILURE_USERMSG + +namespace Cesium { +// Use STB resize in our own namespace to avoid conflicts from other libs +#define STBIRDEF +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include +#undef STBIRDEF +}; // namespace Cesium + +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#include +#include + +namespace CesiumGltfReader { + +using namespace CesiumGltf; +using namespace Cesium; + +namespace { + +bool isKtx(const gsl::span& data) { + const size_t ktxMagicByteLength = 12; + if (data.size() < ktxMagicByteLength) { + return false; + } + + const uint8_t ktxMagic[ktxMagicByteLength] = + {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}; + + return memcmp(data.data(), ktxMagic, ktxMagicByteLength) == 0; +} + +bool isWebP(const gsl::span& data) { + if (data.size() < 12) { + return false; + } + const uint32_t magic1 = *reinterpret_cast(data.data()); + const uint32_t magic2 = *reinterpret_cast(data.data() + 8); + return magic1 == 0x46464952 && magic2 == 0x50424557; +} + +} // namespace + +/*static*/ +ImageReaderResult ImageDecoder::readImage( + const gsl::span& data, + const Ktx2TranscodeTargets& ktx2TranscodeTargets) { + CESIUM_TRACE("CesiumGltfReader::readImage"); + + ImageReaderResult result; + + CesiumGltf::ImageAsset& image = result.pImage.emplace(); + + if (isKtx(data)) { + ktxTexture2* pTexture = nullptr; + KTX_error_code errorCode; + + errorCode = ktxTexture2_CreateFromMemory( + reinterpret_cast(data.data()), + data.size(), + KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &pTexture); + + if (errorCode == KTX_SUCCESS) { + if (ktxTexture2_NeedsTranscoding(pTexture)) { + + CESIUM_TRACE("Transcode KTXv2"); + + image.channels = + static_cast(ktxTexture2_GetNumComponents(pTexture)); + GpuCompressedPixelFormat transcodeTargetFormat = + GpuCompressedPixelFormat::NONE; + + if (pTexture->supercompressionScheme == KTX_SS_BASIS_LZ) { + switch (image.channels) { + case 1: + transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_R; + break; + case 2: + transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RG; + break; + case 3: + transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RGB; + break; + // case 4: + default: + transcodeTargetFormat = ktx2TranscodeTargets.ETC1S_RGBA; + } + } else { + switch (image.channels) { + case 1: + transcodeTargetFormat = ktx2TranscodeTargets.UASTC_R; + break; + case 2: + transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RG; + break; + case 3: + transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RGB; + break; + // case 4: + default: + transcodeTargetFormat = ktx2TranscodeTargets.UASTC_RGBA; + } + } + + ktx_transcode_fmt_e transcodeTargetFormat_ = KTX_TTF_RGBA32; + switch (transcodeTargetFormat) { + case GpuCompressedPixelFormat::ETC1_RGB: + transcodeTargetFormat_ = KTX_TTF_ETC1_RGB; + break; + case GpuCompressedPixelFormat::ETC2_RGBA: + transcodeTargetFormat_ = KTX_TTF_ETC2_RGBA; + break; + case GpuCompressedPixelFormat::BC1_RGB: + transcodeTargetFormat_ = KTX_TTF_BC1_RGB; + break; + case GpuCompressedPixelFormat::BC3_RGBA: + transcodeTargetFormat_ = KTX_TTF_BC3_RGBA; + break; + case GpuCompressedPixelFormat::BC4_R: + transcodeTargetFormat_ = KTX_TTF_BC4_R; + break; + case GpuCompressedPixelFormat::BC5_RG: + transcodeTargetFormat_ = KTX_TTF_BC5_RG; + break; + case GpuCompressedPixelFormat::BC7_RGBA: + transcodeTargetFormat_ = KTX_TTF_BC7_RGBA; + break; + case GpuCompressedPixelFormat::PVRTC1_4_RGB: + transcodeTargetFormat_ = KTX_TTF_PVRTC1_4_RGB; + break; + case GpuCompressedPixelFormat::PVRTC1_4_RGBA: + transcodeTargetFormat_ = KTX_TTF_PVRTC1_4_RGBA; + break; + case GpuCompressedPixelFormat::ASTC_4x4_RGBA: + transcodeTargetFormat_ = KTX_TTF_ASTC_4x4_RGBA; + break; + case GpuCompressedPixelFormat::PVRTC2_4_RGB: + transcodeTargetFormat_ = KTX_TTF_PVRTC2_4_RGB; + break; + case GpuCompressedPixelFormat::PVRTC2_4_RGBA: + transcodeTargetFormat_ = KTX_TTF_PVRTC2_4_RGBA; + break; + case GpuCompressedPixelFormat::ETC2_EAC_R11: + transcodeTargetFormat_ = KTX_TTF_ETC2_EAC_R11; + break; + case GpuCompressedPixelFormat::ETC2_EAC_RG11: + transcodeTargetFormat_ = KTX_TTF_ETC2_EAC_RG11; + break; + // case NONE: + default: + transcodeTargetFormat_ = KTX_TTF_RGBA32; + break; + }; + + errorCode = + ktxTexture2_TranscodeBasis(pTexture, transcodeTargetFormat_, 0); + if (errorCode != KTX_SUCCESS) { + transcodeTargetFormat_ = KTX_TTF_RGBA32; + transcodeTargetFormat = GpuCompressedPixelFormat::NONE; + errorCode = + ktxTexture2_TranscodeBasis(pTexture, transcodeTargetFormat_, 0); + } + if (errorCode == KTX_SUCCESS) { + image.compressedPixelFormat = transcodeTargetFormat; + image.width = static_cast(pTexture->baseWidth); + image.height = static_cast(pTexture->baseHeight); + + if (transcodeTargetFormat == GpuCompressedPixelFormat::NONE) { + // We fully decompressed the texture in this case. + image.bytesPerChannel = 1; + image.channels = 4; + } + + // In the KTX2 spec, there's a distinction between "this image has no + // mipmaps, so they should be generated at runtime" and "this + // image has no mipmaps because it makes no sense to create a mipmap + // for this type of image." It is, confusingly, encoded in the + // `levelCount` property: + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_levelcount + // + // With `levelCount=0`, mipmaps should be generated. With + // `levelCount=1`, mipmaps make no sense. So when `levelCount=0`, we + // want to leave the `mipPositions` array _empty_. With + // `levelCount=1`, we want to populate it with a single mip level. + // + // However, this `levelCount` property is not directly exposed by the + // KTX2 loader API we're using here. Instead, there is a `numLevels` + // property, but it will _never_ have the value 0, because it + // represents the number of levels of actual pixel data we have. When + // the API sees `levelCount=0`, it will assign the value 1 to + // `numLevels`, but it will _also_ set `generateMipmaps` to true. + // + // The API docs say that `numLevels` will always be 1 when + // `generateMipmaps` is true. + // + // So, in summary, when `generateMipmaps=false`, we populate + // `mipPositions` with whatever mip levels the KTX provides and we + // don't generate any more. When it's true, we treat all the image + // data as belonging to a single base-level image and generate mipmaps + // from that if necessary. + if (!pTexture->generateMipmaps) { + // Copy over the positions of each mip within the buffer. + image.mipPositions.resize(pTexture->numLevels); + for (ktx_uint32_t level = 0; level < pTexture->numLevels; ++level) { + ktx_size_t imageOffset; + ktxTexture_GetImageOffset( + ktxTexture(pTexture), + level, + 0, + 0, + &imageOffset); + ktx_size_t imageSize = + ktxTexture_GetImageSize(ktxTexture(pTexture), level); + + image.mipPositions[level] = {imageOffset, imageSize}; + } + } else { + CESIUM_ASSERT(pTexture->numLevels == 1); + } + + // Copy over the entire buffer, including all mips. + ktx_uint8_t* pixelData = ktxTexture_GetData(ktxTexture(pTexture)); + ktx_size_t pixelDataSize = + ktxTexture_GetDataSize(ktxTexture(pTexture)); + + image.pixelData.resize(pixelDataSize); + std::uint8_t* u8Pointer = + reinterpret_cast(image.pixelData.data()); + std::copy(pixelData, pixelData + pixelDataSize, u8Pointer); + + ktxTexture_Destroy(ktxTexture(pTexture)); + + return result; + } + } + } + + result.pImage = nullptr; + result.errors.emplace_back( + "KTX2 loading failed with error: " + + std::string(ktxErrorString(errorCode))); + + return result; + } else if (isWebP(data)) { + if (WebPGetInfo( + reinterpret_cast(data.data()), + data.size(), + &image.width, + &image.height)) { + image.channels = 4; + image.bytesPerChannel = 1; + uint8_t* pImage = NULL; + const auto bufferSize = image.width * image.height * image.channels; + image.pixelData.resize(static_cast(bufferSize)); + pImage = WebPDecodeRGBAInto( + reinterpret_cast(data.data()), + data.size(), + reinterpret_cast(image.pixelData.data()), + image.pixelData.size(), + image.width * image.channels); + if (!pImage) { + result.pImage = nullptr; + result.errors.emplace_back("Unable to decode WebP"); + } + return result; + } + } + + { + tjhandle tjInstance = tjInitDecompress(); + int inSubsamp, inColorspace; + if (!tjDecompressHeader3( + tjInstance, + reinterpret_cast(data.data()), + static_cast(data.size()), + &image.width, + &image.height, + &inSubsamp, + &inColorspace)) { + CESIUM_TRACE("Decode JPG"); + image.bytesPerChannel = 1; + image.channels = 4; + const auto lastByte = + image.width * image.height * image.channels * image.bytesPerChannel; + image.pixelData.resize(static_cast(lastByte)); + if (tjDecompress2( + tjInstance, + reinterpret_cast(data.data()), + static_cast(data.size()), + reinterpret_cast(image.pixelData.data()), + image.width, + 0, + image.height, + TJPF_RGBA, + 0)) { + result.errors.emplace_back("Unable to decode JPEG"); + result.pImage = nullptr; + } + } else { + CESIUM_TRACE("Decode PNG"); + image.bytesPerChannel = 1; + image.channels = 4; + + int channelsInFile; + stbi_uc* pImage = stbi_load_from_memory( + reinterpret_cast(data.data()), + static_cast(data.size()), + &image.width, + &image.height, + &channelsInFile, + image.channels); + if (pImage) { + CESIUM_TRACE( + "copy image " + std::to_string(image.width) + "x" + + std::to_string(image.height) + "x" + + std::to_string(image.channels) + "x" + + std::to_string(image.bytesPerChannel)); + // std::uint8_t is not implicitly convertible to std::byte, so we must + // use reinterpret_cast to (safely) force the conversion. + const auto lastByte = + image.width * image.height * image.channels * image.bytesPerChannel; + image.pixelData.resize(static_cast(lastByte)); + std::uint8_t* u8Pointer = + reinterpret_cast(image.pixelData.data()); + std::copy(pImage, pImage + lastByte, u8Pointer); + stbi_image_free(pImage); + } else { + result.pImage = nullptr; + result.errors.emplace_back(stbi_failure_reason()); + } + } + tjDestroy(tjInstance); + } + return result; +} + +/*static*/ +std::optional ImageDecoder::generateMipMaps(ImageAsset& image) { + if (!image.mipPositions.empty() || + image.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { + // No error message needed, since this is not technically a failure. + return std::nullopt; + } + + if (image.pixelData.empty()) { + return "Unable to generate mipmaps, an empty image was provided."; + } + + CESIUM_TRACE( + "generate mipmaps " + std::to_string(image.width) + "x" + + std::to_string(image.height) + "x" + std::to_string(image.channels) + + "x" + std::to_string(image.bytesPerChannel)); + + int32_t mipWidth = image.width; + int32_t mipHeight = image.height; + int32_t totalPixelCount = mipWidth * mipHeight; + size_t mipCount = 1; + while (mipWidth > 1 || mipHeight > 1) { + ++mipCount; + + if (mipWidth > 1) { + mipWidth >>= 1; + } + + if (mipHeight > 1) { + mipHeight >>= 1; + } + + // Total pixels in the final mipmap. + totalPixelCount += mipWidth * mipHeight; + } + + // Byte size of the base image. + const size_t imageByteSize = static_cast( + image.width * image.height * image.channels * image.bytesPerChannel); + + image.mipPositions.resize(mipCount); + image.mipPositions[0].byteOffset = 0; + image.mipPositions[0].byteSize = imageByteSize; + + image.pixelData.resize(static_cast( + totalPixelCount * image.channels * image.bytesPerChannel)); + + mipWidth = image.width; + mipHeight = image.height; + size_t mipIndex = 0; + size_t byteOffset = 0; + size_t byteSize = imageByteSize; + while (mipWidth > 1 || mipHeight > 1) { + size_t lastByteOffset = byteOffset; + byteOffset += byteSize; + ++mipIndex; + + int32_t lastWidth = mipWidth; + if (mipWidth > 1) { + mipWidth >>= 1; + } + + int32_t lastHeight = mipHeight; + if (mipHeight > 1) { + mipHeight >>= 1; + } + + byteSize = static_cast( + mipWidth * mipHeight * image.channels * image.bytesPerChannel); + + image.mipPositions[mipIndex].byteOffset = byteOffset; + image.mipPositions[mipIndex].byteSize = byteSize; + + if (!stbir_resize_uint8( + reinterpret_cast( + &image.pixelData[lastByteOffset]), + lastWidth, + lastHeight, + 0, + reinterpret_cast(&image.pixelData[byteOffset]), + mipWidth, + mipHeight, + 0, + image.channels)) { + // Remove any added mipmaps. + image.mipPositions.clear(); + image.pixelData.resize(imageByteSize); + return stbi_failure_reason(); + } + } + + return std::nullopt; +} + +} // namespace CesiumGltfReader diff --git a/CesiumGltfReader/src/NetworkImageAssetDescriptor.cpp b/CesiumGltfReader/src/NetworkImageAssetDescriptor.cpp new file mode 100644 index 000000000..2284bff7c --- /dev/null +++ b/CesiumGltfReader/src/NetworkImageAssetDescriptor.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +using namespace CesiumAsync; +using namespace CesiumGltf; +using namespace CesiumGltfReader; +using namespace CesiumUtility; + +namespace CesiumGltfReader { + +bool NetworkImageAssetDescriptor::operator==( + const NetworkImageAssetDescriptor& rhs) const noexcept { + if (!NetworkAssetDescriptor::operator==(rhs)) + return false; + + return this->ktx2TranscodeTargets.ETC1S_R == + rhs.ktx2TranscodeTargets.ETC1S_R && + this->ktx2TranscodeTargets.ETC1S_RG == + rhs.ktx2TranscodeTargets.ETC1S_RG && + this->ktx2TranscodeTargets.ETC1S_RGB == + rhs.ktx2TranscodeTargets.ETC1S_RGB && + this->ktx2TranscodeTargets.ETC1S_RGBA == + rhs.ktx2TranscodeTargets.ETC1S_RGBA && + this->ktx2TranscodeTargets.UASTC_R == + rhs.ktx2TranscodeTargets.UASTC_R && + this->ktx2TranscodeTargets.UASTC_RG == + rhs.ktx2TranscodeTargets.UASTC_RG && + this->ktx2TranscodeTargets.UASTC_RGB == + rhs.ktx2TranscodeTargets.UASTC_RGB && + this->ktx2TranscodeTargets.UASTC_RGBA == + rhs.ktx2TranscodeTargets.UASTC_RGBA; +} + +Future> NetworkImageAssetDescriptor::load( + const AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor) const { + return this->loadBytesFromNetwork(asyncSystem, pAssetAccessor) + .thenInWorkerThread([ktx2TranscodeTargets = this->ktx2TranscodeTargets]( + Result>&& result) { + if (!result.value) { + return ResultPointer(result.errors); + } + + ImageReaderResult imageResult = + ImageDecoder::readImage(*result.value, ktx2TranscodeTargets); + + result.errors.merge( + ErrorList{imageResult.errors, imageResult.warnings}); + + return ResultPointer( + imageResult.pImage, + std::move(result.errors)); + }); +} + +} // namespace CesiumGltfReader + +std::size_t std::hash::operator()( + const NetworkImageAssetDescriptor& key) const noexcept { + std::hash baseHash{}; + std::hash ktxHash{}; + + size_t result = baseHash(key); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.ETC1S_R)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.ETC1S_RG)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.ETC1S_RGB)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.ETC1S_RGBA)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.UASTC_R)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.UASTC_RG)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.UASTC_RGB)); + result = Hash::combine(result, ktxHash(key.ktx2TranscodeTargets.UASTC_RGBA)); + return result; +} diff --git a/CesiumGltfReader/src/decodeDataUrls.cpp b/CesiumGltfReader/src/decodeDataUrls.cpp index d81a68cd3..e76d03c43 100644 --- a/CesiumGltfReader/src/decodeDataUrls.cpp +++ b/CesiumGltfReader/src/decodeDataUrls.cpp @@ -1,8 +1,8 @@ #include "decodeDataUrls.h" -#include "CesiumGltfReader/GltfReader.h" - #include +#include +#include #include #include @@ -87,7 +87,6 @@ std::optional tryDecode(const std::string& uri) { } // namespace void decodeDataUrls( - const GltfReader& reader, GltfReaderResult& readGltf, const GltfReaderOptions& options) { CESIUM_TRACE("CesiumGltfReader::decodeDataUrls"); @@ -134,14 +133,15 @@ void decodeDataUrls( continue; } - ImageReaderResult imageResult = - reader.readImage(decoded.value().data, options.ktx2TranscodeTargets); + ImageReaderResult imageResult = ImageDecoder::readImage( + decoded.value().data, + options.ktx2TranscodeTargets); - if (!imageResult.image) { + if (!imageResult.pImage) { continue; } - image.cesium = std::move(imageResult.image.value()); + image.pAsset = imageResult.pImage; if (options.clearDecodedDataUrls) { image.uri.reset(); diff --git a/CesiumGltfReader/src/decodeDataUrls.h b/CesiumGltfReader/src/decodeDataUrls.h index 98c217110..5b9d2d01e 100644 --- a/CesiumGltfReader/src/decodeDataUrls.h +++ b/CesiumGltfReader/src/decodeDataUrls.h @@ -7,7 +7,6 @@ struct GltfReaderOptions; class GltfReader; void decodeDataUrls( - const GltfReader& reader, GltfReaderResult& readGltf, const GltfReaderOptions& clearDecodedDataUrls); } // namespace CesiumGltfReader diff --git a/CesiumGltfReader/test/TestGltfReader.cpp b/CesiumGltfReader/test/TestGltfReader.cpp index 3e15d1213..10b62fd78 100644 --- a/CesiumGltfReader/test/TestGltfReader.cpp +++ b/CesiumGltfReader/test/TestGltfReader.cpp @@ -563,67 +563,6 @@ TEST_CASE("Can apply RTC CENTER if model uses Cesium RTC extension") { CHECK(cesiumRTC->center == rtcCenter); } -TEST_CASE("Can correctly interpret mipmaps in KTX2 files") { - { - // This KTX2 file has a single mip level and no further mip levels should be - // generated. `mipPositions` should reflect this single mip level. - std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; - ktx2File /= "ktx2/kota-onelevel.ktx2"; - std::vector data = readFile(ktx2File.string()); - ImageReaderResult imageResult = - GltfReader::readImage(data, Ktx2TranscodeTargets{}); - REQUIRE(imageResult.image.has_value()); - - const ImageCesium& image = *imageResult.image; - REQUIRE(image.mipPositions.size() == 1); - CHECK(image.mipPositions[0].byteOffset == 0); - CHECK(image.mipPositions[0].byteSize > 0); - CHECK( - image.mipPositions[0].byteSize == - size_t(image.width * image.height * image.channels)); - CHECK(image.mipPositions[0].byteSize == image.pixelData.size()); - } - - { - // This KTX2 file has only a base image but further mip levels can be - // generated. This image effectively has no mip levels. - std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; - ktx2File /= "ktx2/kota-automipmap.ktx2"; - std::vector data = readFile(ktx2File.string()); - ImageReaderResult imageResult = - GltfReader::readImage(data, Ktx2TranscodeTargets{}); - REQUIRE(imageResult.image.has_value()); - - const ImageCesium& image = *imageResult.image; - REQUIRE(image.mipPositions.size() == 0); - CHECK(image.pixelData.size() > 0); - } - - { - // This KTX2 file has a complete mip chain. - std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; - ktx2File /= "ktx2/kota-mipmaps.ktx2"; - std::vector data = readFile(ktx2File.string()); - ImageReaderResult imageResult = - GltfReader::readImage(data, Ktx2TranscodeTargets{}); - REQUIRE(imageResult.image.has_value()); - - const ImageCesium& image = *imageResult.image; - REQUIRE(image.mipPositions.size() == 9); - CHECK(image.mipPositions[0].byteSize > 0); - CHECK( - image.mipPositions[0].byteSize == - size_t(image.width * image.height * image.channels)); - CHECK(image.mipPositions[0].byteSize < image.pixelData.size()); - - size_t smallerThan = image.mipPositions[0].byteSize; - for (size_t i = 1; i < image.mipPositions.size(); ++i) { - CHECK(image.mipPositions[i].byteSize < smallerThan); - smallerThan = image.mipPositions[i].byteSize; - } - } -} - TEST_CASE("Can read unknown properties from a glTF") { const std::string s = R"( { @@ -689,7 +628,7 @@ TEST_CASE("Decodes images with data uris") { REQUIRE(model.images.size() == 1); - const ImageCesium& image = model.images.front().cesium; + const ImageAsset& image = *model.images.front().pAsset; CHECK(image.width == 256); CHECK(image.height == 256); @@ -782,9 +721,9 @@ TEST_CASE("GltfReader::loadGltf") { REQUIRE(result.model->images.size() == 1); const CesiumGltf::Image& image = result.model->images[0]; - CHECK(image.cesium.width == 2048); - CHECK(image.cesium.height == 2048); - CHECK(image.cesium.pixelData.size() == 2048 * 2048 * 4); + CHECK(image.pAsset->width == 2048); + CHECK(image.pAsset->height == 2048); + CHECK(image.pAsset->pixelData.size() == 2048 * 2048 * 4); CHECK(!result.model->buffers.empty()); for (const CesiumGltf::Buffer& buffer : result.model->buffers) { @@ -810,7 +749,7 @@ TEST_CASE("GltfReader::loadGltf") { REQUIRE(result.model->images.size() == 1); const CesiumGltf::Image& image = result.model->images[0]; CHECK(image.uri.has_value()); - CHECK(image.cesium.pixelData.empty()); + CHECK(!image.pAsset); } } diff --git a/CesiumGltfReader/test/TestImageDecoder.cpp b/CesiumGltfReader/test/TestImageDecoder.cpp new file mode 100644 index 000000000..6cebfe41c --- /dev/null +++ b/CesiumGltfReader/test/TestImageDecoder.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include + +#include + +using namespace CesiumGltf; +using namespace CesiumGltfReader; + +TEST_CASE("CesiumGltfReader::ImageDecoder") { + SECTION("Can correctly interpret mipmaps in KTX2 files") { + { + // This KTX2 file has a single mip level and no further mip levels should + // be generated. `mipPositions` should reflect this single mip level. + std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; + ktx2File /= "ktx2/kota-onelevel.ktx2"; + std::vector data = readFile(ktx2File.string()); + ImageReaderResult imageResult = + ImageDecoder::readImage(data, Ktx2TranscodeTargets{}); + REQUIRE(imageResult.pImage); + + const ImageAsset& image = *imageResult.pImage; + REQUIRE(image.mipPositions.size() == 1); + CHECK(image.mipPositions[0].byteOffset == 0); + CHECK(image.mipPositions[0].byteSize > 0); + CHECK( + image.mipPositions[0].byteSize == + size_t(image.width * image.height * image.channels)); + CHECK(image.mipPositions[0].byteSize == image.pixelData.size()); + } + + { + // This KTX2 file has only a base image but further mip levels can be + // generated. This image effectively has no mip levels. + std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; + ktx2File /= "ktx2/kota-automipmap.ktx2"; + std::vector data = readFile(ktx2File.string()); + ImageReaderResult imageResult = + ImageDecoder::readImage(data, Ktx2TranscodeTargets{}); + REQUIRE(imageResult.pImage); + + const ImageAsset& image = *imageResult.pImage; + REQUIRE(image.mipPositions.size() == 0); + CHECK(image.pixelData.size() > 0); + } + + { + // This KTX2 file has a complete mip chain. + std::filesystem::path ktx2File = CesiumGltfReader_TEST_DATA_DIR; + ktx2File /= "ktx2/kota-mipmaps.ktx2"; + std::vector data = readFile(ktx2File.string()); + ImageReaderResult imageResult = + ImageDecoder::readImage(data, Ktx2TranscodeTargets{}); + REQUIRE(imageResult.pImage); + + const ImageAsset& image = *imageResult.pImage; + REQUIRE(image.mipPositions.size() == 9); + CHECK(image.mipPositions[0].byteSize > 0); + CHECK( + image.mipPositions[0].byteSize == + size_t(image.width * image.height * image.channels)); + CHECK(image.mipPositions[0].byteSize < image.pixelData.size()); + + size_t smallerThan = image.mipPositions[0].byteSize; + for (size_t i = 1; i < image.mipPositions.size(); ++i) { + CHECK(image.mipPositions[i].byteSize < smallerThan); + smallerThan = image.mipPositions[i].byteSize; + } + } + } +} diff --git a/CesiumGltfWriter/include/CesiumGltfWriter/GltfWriter.h b/CesiumGltfWriter/include/CesiumGltfWriter/GltfWriter.h index da7efde9a..97a2e6df5 100644 --- a/CesiumGltfWriter/include/CesiumGltfWriter/GltfWriter.h +++ b/CesiumGltfWriter/include/CesiumGltfWriter/GltfWriter.h @@ -75,7 +75,7 @@ class CESIUMGLTFWRITER_API GltfWriter { * @brief Serializes the provided model into a glTF JSON byte vector. * * @details Ignores internal data such as {@link CesiumGltf::BufferCesium} - * and {@link CesiumGltf::ImageCesium} when serializing the glTF. Internal + * and {@link CesiumGltf::ImageAsset} when serializing the glTF. Internal * data must either be converted to data uris or saved as external files. The * buffer.uri and image.uri fields must be set accordingly prior to calling * this function. @@ -93,7 +93,7 @@ class CESIUMGLTFWRITER_API GltfWriter { * * @details The first buffer object implicitly refers to the GLB binary chunk * and should not have a uri. Ignores internal data such as - * {@link CesiumGltf::BufferCesium} and {@link CesiumGltf::ImageCesium}. + * {@link CesiumGltf::BufferCesium} and {@link CesiumGltf::ImageAsset}. * * @param model The model. * @param bufferData The buffer data to store in the GLB binary chunk. diff --git a/CesiumNativeTests/include/CesiumNativeTests/SimpleAssetAccessor.h b/CesiumNativeTests/include/CesiumNativeTests/SimpleAssetAccessor.h index 4c0943a50..2bfd636cc 100644 --- a/CesiumNativeTests/include/CesiumNativeTests/SimpleAssetAccessor.h +++ b/CesiumNativeTests/include/CesiumNativeTests/SimpleAssetAccessor.h @@ -3,6 +3,7 @@ #include "SimpleAssetRequest.h" #include "SimpleAssetResponse.h" +#include #include #include diff --git a/CesiumNativeTests/src/FileAccessor.cpp b/CesiumNativeTests/src/FileAccessor.cpp index b59a53963..0484a27d2 100644 --- a/CesiumNativeTests/src/FileAccessor.cpp +++ b/CesiumNativeTests/src/FileAccessor.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/CesiumQuantizedMeshTerrain/src/QuantizedMeshLoader.cpp b/CesiumQuantizedMeshTerrain/src/QuantizedMeshLoader.cpp index f3b594b98..eb2e420b7 100644 --- a/CesiumQuantizedMeshTerrain/src/QuantizedMeshLoader.cpp +++ b/CesiumQuantizedMeshTerrain/src/QuantizedMeshLoader.cpp @@ -1073,13 +1073,14 @@ static std::vector generateNormals( const size_t waterMaskImageId = model.images.size(); model.images.emplace_back(); CesiumGltf::Image& waterMaskImage = model.images[waterMaskImageId]; - waterMaskImage.cesium.width = 256; - waterMaskImage.cesium.height = 256; - waterMaskImage.cesium.channels = 1; - waterMaskImage.cesium.bytesPerChannel = 1; - waterMaskImage.cesium.pixelData.resize(65536); + waterMaskImage.pAsset.emplace(); + waterMaskImage.pAsset->width = 256; + waterMaskImage.pAsset->height = 256; + waterMaskImage.pAsset->channels = 1; + waterMaskImage.pAsset->bytesPerChannel = 1; + waterMaskImage.pAsset->pixelData.resize(65536); std::memcpy( - waterMaskImage.cesium.pixelData.data(), + waterMaskImage.pAsset->pixelData.data(), meshView->waterMaskBuffer.data(), 65536); diff --git a/CesiumRasterOverlays/include/CesiumRasterOverlays/IPrepareRasterOverlayRendererResources.h b/CesiumRasterOverlays/include/CesiumRasterOverlays/IPrepareRasterOverlayRendererResources.h index d9d88878f..7272fb16d 100644 --- a/CesiumRasterOverlays/include/CesiumRasterOverlays/IPrepareRasterOverlayRendererResources.h +++ b/CesiumRasterOverlays/include/CesiumRasterOverlays/IPrepareRasterOverlayRendererResources.h @@ -5,7 +5,7 @@ #include namespace CesiumGltf { -struct ImageCesium; +struct ImageAsset; } namespace CesiumRasterOverlays { @@ -28,7 +28,7 @@ class CESIUMRASTEROVERLAYS_API IPrepareRasterOverlayRendererResources { * `pLoadThreadResult` parameter. */ virtual void* prepareRasterInLoadThread( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, const std::any& rendererOptions) = 0; /** diff --git a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlay.h b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlay.h index a7e681d7c..47a64f3dd 100644 --- a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlay.h +++ b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlay.h @@ -3,6 +3,7 @@ #include "Library.h" #include "RasterOverlayLoadFailureDetails.h" +#include #include #include #include diff --git a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTile.h b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTile.h index f74023e16..48c2c13a5 100644 --- a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTile.h +++ b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTile.h @@ -192,8 +192,9 @@ class RasterOverlayTile final * * @return The image data. */ - const CesiumGltf::ImageCesium& getImage() const noexcept { - return this->_image; + CesiumUtility::IntrusivePointer + getImage() const noexcept { + return this->_pImage; } /** @@ -204,7 +205,9 @@ class RasterOverlayTile final * * @return The image data. */ - CesiumGltf::ImageCesium& getImage() noexcept { return this->_image; } + CesiumUtility::IntrusivePointer getImage() noexcept { + return this->_pImage; + } /** * @brief Create the renderer resources for the loaded image. @@ -256,7 +259,7 @@ class RasterOverlayTile final CesiumGeometry::Rectangle _rectangle; std::vector _tileCredits; LoadState _state; - CesiumGltf::ImageCesium _image; + CesiumUtility::IntrusivePointer _pImage; void* _pRendererResources; MoreDetailAvailable _moreDetailAvailable; }; diff --git a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h index f232c5b8d..b104e1b1d 100644 --- a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h +++ b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h @@ -32,7 +32,7 @@ struct CESIUMRASTEROVERLAYS_API LoadedRasterOverlayImage { * This will be an empty optional if the loading failed. In this case, * the `errors` vector will contain the corresponding error messages. */ - std::optional image{}; + CesiumUtility::IntrusivePointer pImage{nullptr}; /** * @brief The projected rectangle defining the bounds of this image. @@ -414,7 +414,5 @@ class CESIUMRASTEROVERLAYS_API RasterOverlayTileProvider CESIUM_TRACE_DECLARE_TRACK_SET( _loadingSlots, "Raster Overlay Tile Loading Slot"); - - static CesiumGltfReader::GltfReader _gltfReader; }; } // namespace CesiumRasterOverlays diff --git a/CesiumRasterOverlays/src/DebugColorizeTilesRasterOverlay.cpp b/CesiumRasterOverlays/src/DebugColorizeTilesRasterOverlay.cpp index 6ca6b7da1..d3a9e96ef 100644 --- a/CesiumRasterOverlays/src/DebugColorizeTilesRasterOverlay.cpp +++ b/CesiumRasterOverlays/src/DebugColorizeTilesRasterOverlay.cpp @@ -42,7 +42,7 @@ class DebugTileProvider : public RasterOverlayTileProvider { result.rectangle = overlayTile.getRectangle(); - ImageCesium& image = result.image.emplace(); + ImageAsset& image = result.pImage.emplace(); image.width = 1; image.height = 1; image.channels = 4; diff --git a/CesiumRasterOverlays/src/QuadtreeRasterOverlayTileProvider.cpp b/CesiumRasterOverlays/src/QuadtreeRasterOverlayTileProvider.cpp index 28d407801..1c4499757 100644 --- a/CesiumRasterOverlays/src/QuadtreeRasterOverlayTileProvider.cpp +++ b/CesiumRasterOverlays/src/QuadtreeRasterOverlayTileProvider.cpp @@ -328,21 +328,21 @@ QuadtreeRasterOverlayTileProvider::getQuadtreeTile( asyncSystem = this->getAsyncSystem(), loadParentTile = std::move(loadParentTile)]( LoadedRasterOverlayImage&& loaded) { - if (loaded.image && !loaded.errorList.hasErrors() && - loaded.image->width > 0 && loaded.image->height > 0) { + if (loaded.pImage && !loaded.errorList.hasErrors() && + loaded.pImage->width > 0 && loaded.pImage->height > 0) { // Successfully loaded, continue. - cachedBytes += int64_t(loaded.image->pixelData.size()); + cachedBytes += int64_t(loaded.pImage->pixelData.size()); #if SHOW_TILE_BOUNDARIES // Highlight the edges in red to show tile boundaries. gsl::span pixels = reintepretCastSpan( loaded.image->pixelData); - for (int32_t j = 0; j < loaded.image->height; ++j) { - for (int32_t i = 0; i < loaded.image->width; ++i) { - if (i == 0 || j == 0 || i == loaded.image->width - 1 || - j == loaded.image->height - 1) { - pixels[j * loaded.image->width + i] = 0xFF0000FF; + for (int32_t j = 0; j < loaded.pImage->height; ++j) { + for (int32_t i = 0; i < loaded.pImage->width; ++i) { + if (i == 0 || j == 0 || i == loaded.pImage->width - 1 || + j == loaded.pImage->height - 1) { + pixels[j * loaded.pImage->width + i] = 0xFF0000FF; } } } @@ -381,7 +381,7 @@ QuadtreeRasterOverlayTileProvider::getQuadtreeTile( namespace { PixelRectangle computePixelRectangle( - const ImageCesium& image, + const ImageAsset& image, const Rectangle& totalRectangle, const Rectangle& partRectangle) { // Pixel coordinates are measured from the top left. @@ -417,9 +417,9 @@ PixelRectangle computePixelRectangle( // source image where the source subset rectangle overlaps the target // rectangle is copied to the target image. void blitImage( - ImageCesium& target, + ImageAsset& target, const Rectangle& targetRectangle, - const ImageCesium& source, + const ImageAsset& source, const Rectangle& sourceRectangle, const std::optional& sourceSubset) { const Rectangle sourceToCopy = sourceSubset.value_or(sourceRectangle); @@ -462,8 +462,7 @@ QuadtreeRasterOverlayTileProvider::loadTileImage( images.begin(), images.end(), [](const LoadedQuadtreeImage& image) { - return image.pLoaded->image.has_value() && - !image.subset.has_value(); + return image.pLoaded->pImage && !image.subset.has_value(); }); if (!haveAnyUsefulImageData) { @@ -479,7 +478,7 @@ QuadtreeRasterOverlayTileProvider::loadTileImage( } } return LoadedRasterOverlayImage{ - ImageCesium(), + new ImageAsset(), Rectangle(), {}, std::move(errors), @@ -523,8 +522,8 @@ void QuadtreeRasterOverlayTileProvider::unloadCachedTiles() { // If this is the last use of this data, it will be freed when the shared // pointer goes out of scope, so reduce the cachedBytes accordingly. if (pImage.use_count() == 1) { - if (pImage->image) { - this->_cachedBytes -= int64_t(pImage->image->pixelData.size()); + if (pImage->pImage) { + this->_cachedBytes -= int64_t(pImage->pImage->pixelData.size()); CESIUM_ASSERT(this->_cachedBytes >= 0); } } @@ -550,28 +549,28 @@ QuadtreeRasterOverlayTileProvider::measureCombinedImage( int32_t bytesPerChannel = -1; for (const LoadedQuadtreeImage& image : images) { const LoadedRasterOverlayImage& loaded = *image.pLoaded; - if (!loaded.image || loaded.image->width <= 0 || - loaded.image->height <= 0) { + if (!loaded.pImage || loaded.pImage->width <= 0 || + loaded.pImage->height <= 0) { continue; } projectedWidthPerPixel = glm::min( projectedWidthPerPixel, - loaded.rectangle.computeWidth() / loaded.image->width); + loaded.rectangle.computeWidth() / loaded.pImage->width); projectedHeightPerPixel = glm::min( projectedHeightPerPixel, - loaded.rectangle.computeHeight() / loaded.image->height); + loaded.rectangle.computeHeight() / loaded.pImage->height); - channels = glm::max(channels, loaded.image->channels); - bytesPerChannel = glm::max(bytesPerChannel, loaded.image->bytesPerChannel); + channels = glm::max(channels, loaded.pImage->channels); + bytesPerChannel = glm::max(bytesPerChannel, loaded.pImage->bytesPerChannel); } std::optional combinedRectangle; for (const LoadedQuadtreeImage& image : images) { const LoadedRasterOverlayImage& loaded = *image.pLoaded; - if (!loaded.image || loaded.image->width <= 0 || - loaded.image->height <= 0) { + if (!loaded.pImage || loaded.pImage->width <= 0 || + loaded.pImage->height <= 0) { continue; } @@ -669,7 +668,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( if (targetImageBytes <= 0) { // Target image has no pixels, so our work here is done. return LoadedRasterOverlayImage{ - std::nullopt, + nullptr, targetRectangle, {}, std::move(errors), @@ -682,7 +681,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( result.moreDetailAvailable = false; result.errorList = std::move(errors); - ImageCesium& target = result.image.emplace(); + ImageAsset& target = result.pImage.emplace(); target.bytesPerChannel = measurements.bytesPerChannel; target.channels = measurements.channels; target.width = measurements.widthPixels; @@ -692,7 +691,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( for (auto it = images.begin(); it != images.end(); ++it) { const LoadedRasterOverlayImage& loaded = *it->pLoaded; - if (!loaded.image) { + if (!loaded.pImage) { continue; } @@ -701,7 +700,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( blitImage( target, result.rectangle, - *loaded.image, + *loaded.pImage, loaded.rectangle, it->subset); } @@ -709,7 +708,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( size_t combinedCreditsCount = 0; for (auto it = images.begin(); it != images.end(); ++it) { const LoadedRasterOverlayImage& loaded = *it->pLoaded; - if (!loaded.image) { + if (!loaded.pImage) { continue; } @@ -719,7 +718,7 @@ QuadtreeRasterOverlayTileProvider::combineImages( result.credits.reserve(combinedCreditsCount); for (auto it = images.begin(); it != images.end(); ++it) { const LoadedRasterOverlayImage& loaded = *it->pLoaded; - if (!loaded.image) { + if (!loaded.pImage) { continue; } @@ -731,12 +730,12 @@ QuadtreeRasterOverlayTileProvider::combineImages( // Highlight the edges in yellow to show tile boundaries. #if SHOW_TILE_BOUNDARIES gsl::span pixels = - reintepretCastSpan(result.image->pixelData); - for (int32_t j = 0; j < result.image->height; ++j) { - for (int32_t i = 0; i < result.image->width; ++i) { - if (i == 0 || j == 0 || i == result.image->width - 1 || - j == result.image->height - 1) { - pixels[j * result.image->width + i] = 0xFF00FFFF; + reintepretCastSpan(result.pImage->pixelData); + for (int32_t j = 0; j < result.pImage->height; ++j) { + for (int32_t i = 0; i < result.pImage->width; ++i) { + if (i == 0 || j == 0 || i == result.pImage->width - 1 || + j == result.pImage->height - 1) { + pixels[j * result.pImage->width + i] = 0xFF00FFFF; } } } diff --git a/CesiumRasterOverlays/src/RasterOverlayTile.cpp b/CesiumRasterOverlays/src/RasterOverlayTile.cpp index 722ef553b..2b9c19207 100644 --- a/CesiumRasterOverlays/src/RasterOverlayTile.cpp +++ b/CesiumRasterOverlays/src/RasterOverlayTile.cpp @@ -17,7 +17,7 @@ RasterOverlayTile::RasterOverlayTile( _rectangle(CesiumGeometry::Rectangle(0.0, 0.0, 0.0, 0.0)), _tileCredits(), _state(LoadState::Placeholder), - _image(), + _pImage(nullptr), _pRendererResources(nullptr), _moreDetailAvailable(MoreDetailAvailable::Unknown) {} @@ -30,7 +30,7 @@ RasterOverlayTile::RasterOverlayTile( _rectangle(rectangle), _tileCredits(), _state(LoadState::Unloaded), - _image(), + _pImage(nullptr), _pRendererResources(nullptr), _moreDetailAvailable(MoreDetailAvailable::Unknown) {} diff --git a/CesiumRasterOverlays/src/RasterOverlayTileProvider.cpp b/CesiumRasterOverlays/src/RasterOverlayTileProvider.cpp index 834cb789c..33feb4e15 100644 --- a/CesiumRasterOverlays/src/RasterOverlayTileProvider.cpp +++ b/CesiumRasterOverlays/src/RasterOverlayTileProvider.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -18,9 +18,6 @@ using namespace CesiumUtility; namespace CesiumRasterOverlays { -/*static*/ CesiumGltfReader::GltfReader - RasterOverlayTileProvider::_gltfReader{}; - RasterOverlayTileProvider::RasterOverlayTileProvider( const CesiumUtility::IntrusivePointer& pOwner, const CesiumAsync::AsyncSystem& asyncSystem, @@ -91,8 +88,9 @@ RasterOverlayTileProvider::getTile( void RasterOverlayTileProvider::removeTile(RasterOverlayTile* pTile) noexcept { CESIUM_ASSERT(pTile->getReferenceCount() == 0); - - this->_tileDataBytes -= pTile->getImage().sizeBytes; + if (pTile->getImage()) { + this->_tileDataBytes -= pTile->getImage()->sizeBytes; + } } CesiumAsync::Future @@ -140,7 +138,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( errors.emplaceError( fmt::format("Image request for {} failed.", pRequest->url())); return LoadedRasterOverlayImage{ - std::nullopt, + nullptr, options.rectangle, std::move(options.credits), std::move(errors), @@ -156,7 +154,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( pResponse->statusCode(), pRequest->url())); return LoadedRasterOverlayImage{ - std::nullopt, + nullptr, options.rectangle, std::move(options.credits), std::move(errors), @@ -166,7 +164,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( if (pResponse->data().empty()) { if (options.allowEmptyImages) { return LoadedRasterOverlayImage{ - CesiumGltf::ImageCesium(), + new CesiumGltf::ImageAsset(), options.rectangle, std::move(options.credits), {}, @@ -178,7 +176,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( "Image response for {} is empty.", pRequest->url())); return LoadedRasterOverlayImage{ - std::nullopt, + nullptr, options.rectangle, std::move(options.credits), std::move(errors), @@ -188,9 +186,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( const gsl::span data = pResponse->data(); CesiumGltfReader::ImageReaderResult loadedImage = - RasterOverlayTileProvider::_gltfReader.readImage( - data, - Ktx2TranscodeTargets); + ImageDecoder::readImage(data, Ktx2TranscodeTargets); if (!loadedImage.errors.empty()) { loadedImage.errors.push_back("Image url: " + pRequest->url()); @@ -200,7 +196,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( } return LoadedRasterOverlayImage{ - loadedImage.image, + loadedImage.pImage, options.rectangle, std::move(options.credits), ErrorList{ @@ -213,7 +209,7 @@ RasterOverlayTileProvider::loadTileImageFromUrl( namespace { struct LoadResult { RasterOverlayTile::LoadState state = RasterOverlayTile::LoadState::Unloaded; - CesiumGltf::ImageCesium image = {}; + CesiumUtility::IntrusivePointer pImage = nullptr; CesiumGeometry::Rectangle rectangle = {}; std::vector credits = {}; void* pRendererResources = nullptr; @@ -248,7 +244,7 @@ static LoadResult createLoadResultFromLoadedImage( const std::shared_ptr& pLogger, LoadedRasterOverlayImage&& loadedImage, const std::any& rendererOptions) { - if (!loadedImage.image.has_value()) { + if (!loadedImage.pImage) { loadedImage.errorList.logError(pLogger, "Failed to load image for tile"); LoadResult result; result.state = RasterOverlayTile::LoadState::Failed; @@ -267,7 +263,7 @@ static LoadResult createLoadResultFromLoadedImage( "Warnings while loading image for tile"); } - CesiumGltf::ImageCesium& image = loadedImage.image.value(); + CesiumGltf::ImageAsset& image = *loadedImage.pImage; const int32_t bytesPerPixel = image.channels * image.bytesPerChannel; const int64_t requiredBytes = @@ -288,7 +284,7 @@ static LoadResult createLoadResultFromLoadedImage( LoadResult result; result.state = RasterOverlayTile::LoadState::Loaded; - result.image = std::move(image); + result.pImage = loadedImage.pImage; result.rectangle = loadedImage.rectangle; result.credits = std::move(loadedImage.credits); result.pRendererResources = pRendererResources; @@ -341,7 +337,7 @@ CesiumAsync::Future RasterOverlayTileProvider::doLoad( [thiz, pTile, isThrottledLoad](LoadResult&& result) noexcept { pTile->_rectangle = result.rectangle; pTile->_pRendererResources = result.pRendererResources; - pTile->_image = std::move(result.image); + pTile->_pImage = std::move(result.pImage); pTile->_tileCredits = std::move(result.credits); pTile->_moreDetailAvailable = result.moreDetailAvailable @@ -349,17 +345,19 @@ CesiumAsync::Future RasterOverlayTileProvider::doLoad( : RasterOverlayTile::MoreDetailAvailable::No; pTile->setState(result.state); - ImageCesium& imageCesium = pTile->getImage(); + if (pTile->getImage() != nullptr) { + ImageAsset& imageCesium = *pTile->getImage(); - // If the image size hasn't been overridden, store the pixelData - // size now. We'll add this number to our total memory usage now, - // and remove it when the tile is later unloaded, and we must use - // the same size in each case. - if (imageCesium.sizeBytes < 0) { - imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - } + // If the image size hasn't been overridden, store the pixelData + // size now. We'll add this number to our total memory usage now, + // and remove it when the tile is later unloaded, and we must use + // the same size in each case. + if (imageCesium.sizeBytes < 0) { + imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); + } - thiz->_tileDataBytes += imageCesium.sizeBytes; + thiz->_tileDataBytes += imageCesium.sizeBytes; + } thiz->finalizeTileLoad(isThrottledLoad); @@ -368,7 +366,7 @@ CesiumAsync::Future RasterOverlayTileProvider::doLoad( .catchInMainThread( [thiz, pTile, isThrottledLoad](const std::exception& /*e*/) { pTile->_pRendererResources = nullptr; - pTile->_image = {}; + pTile->_pImage = nullptr; pTile->_tileCredits = {}; pTile->_moreDetailAvailable = RasterOverlayTile::MoreDetailAvailable::No; diff --git a/CesiumRasterOverlays/src/RasterizedPolygonsOverlay.cpp b/CesiumRasterOverlays/src/RasterizedPolygonsOverlay.cpp index 6a46e4eba..8b8868ba9 100644 --- a/CesiumRasterOverlays/src/RasterizedPolygonsOverlay.cpp +++ b/CesiumRasterOverlays/src/RasterizedPolygonsOverlay.cpp @@ -24,7 +24,7 @@ void rasterizePolygons( const std::vector& cartographicPolygons, bool invertSelection) { - CesiumGltf::ImageCesium& image = loaded.image.emplace(); + CesiumGltf::ImageAsset& image = loaded.pImage.emplace(); std::byte insideColor; std::byte outsideColor; diff --git a/CesiumRasterOverlays/src/TileMapServiceRasterOverlay.cpp b/CesiumRasterOverlays/src/TileMapServiceRasterOverlay.cpp index b81b103ce..42634aaab 100644 --- a/CesiumRasterOverlays/src/TileMapServiceRasterOverlay.cpp +++ b/CesiumRasterOverlays/src/TileMapServiceRasterOverlay.cpp @@ -95,7 +95,7 @@ class TileMapServiceTileProvider final errors.emplaceError("Failed to load image from TMS."); return this->getAsyncSystem() .createResolvedFuture( - {std::nullopt, + {nullptr, options.rectangle, {}, std::move(errors), diff --git a/CesiumRasterOverlays/test/TestAddRasterOverlayToGltf.cpp b/CesiumRasterOverlays/test/TestAddRasterOverlayToGltf.cpp index c442dbfa0..065e2bca6 100644 --- a/CesiumRasterOverlays/test/TestAddRasterOverlayToGltf.cpp +++ b/CesiumRasterOverlays/test/TestAddRasterOverlayToGltf.cpp @@ -186,7 +186,7 @@ TEST_CASE("Add raster overlay to glTF") { // PNG-encode the raster overlay image and store it in the main // buffer. ImageManipulation::savePng( - loadResult.pTile->getImage(), + *loadResult.pTile->getImage(), buffer.cesium.data); BufferView& bufferView = gltf.bufferViews.emplace_back(); @@ -266,7 +266,8 @@ TEST_CASE("Add raster overlay to glTF") { const Model& gltfBack = *resultBack.model; REQUIRE(gltfBack.images.size() == 1); - CHECK(!gltfBack.images[0].cesium.pixelData.empty()); + REQUIRE(gltfBack.images[0].pAsset != nullptr); + CHECK(!gltfBack.images[0].pAsset->pixelData.empty()); REQUIRE(!gltfBack.meshes.empty()); REQUIRE(!gltfBack.meshes[0].primitives.empty()); diff --git a/CesiumRasterOverlays/test/TestQuadtreeRasterOverlayTileProvider.cpp b/CesiumRasterOverlays/test/TestQuadtreeRasterOverlayTileProvider.cpp index 594014ddf..1abd0189d 100644 --- a/CesiumRasterOverlays/test/TestQuadtreeRasterOverlayTileProvider.cpp +++ b/CesiumRasterOverlays/test/TestQuadtreeRasterOverlayTileProvider.cpp @@ -63,12 +63,12 @@ class TestTileProvider : public QuadtreeRasterOverlayTileProvider { } else { // Return an image where every component of every pixel is equal to the // tile level. - result.image.emplace(); - result.image->width = int32_t(this->getWidth()); - result.image->height = int32_t(this->getHeight()); - result.image->bytesPerChannel = 1; - result.image->channels = 4; - result.image->pixelData.resize( + result.pImage.emplace(); + result.pImage->width = int32_t(this->getWidth()); + result.pImage->height = int32_t(this->getHeight()); + result.pImage->bytesPerChannel = 1; + result.pImage->channels = 4; + result.pImage->pixelData.resize( this->getWidth() * this->getHeight() * 4, std::byte(tileID.level)); } @@ -170,7 +170,9 @@ TEST_CASE("QuadtreeRasterOverlayTileProvider getTile") { CHECK(pTile->getState() == RasterOverlayTile::LoadState::Loaded); - const ImageCesium& image = pTile->getImage(); + REQUIRE(pTile->getImage()); + + const ImageAsset& image = *pTile->getImage(); CHECK(image.width > 0); CHECK(image.height > 0); CHECK(image.pixelData.size() > 0); @@ -224,7 +226,9 @@ TEST_CASE("QuadtreeRasterOverlayTileProvider getTile") { CHECK(pTile->getState() == RasterOverlayTile::LoadState::Loaded); - const ImageCesium& image = pTile->getImage(); + REQUIRE(pTile->getImage()); + + const ImageAsset& image = *pTile->getImage(); CHECK(image.width > 0); CHECK(image.height > 0); CHECK(image.pixelData.size() > 0); diff --git a/CesiumRasterOverlays/test/TestTileMapServiceRasterOverlay.cpp b/CesiumRasterOverlays/test/TestTileMapServiceRasterOverlay.cpp index bf131b6b8..0c9a802cc 100644 --- a/CesiumRasterOverlays/test/TestTileMapServiceRasterOverlay.cpp +++ b/CesiumRasterOverlays/test/TestTileMapServiceRasterOverlay.cpp @@ -72,7 +72,9 @@ TEST_CASE("TileMapServiceRasterOverlay") { REQUIRE(pTile); waitForFuture(asyncSystem, pTileProvider->loadTile(*pTile)); - ImageCesium& image = pTile->getImage(); + REQUIRE(pTile->getImage()); + + const ImageAsset& image = *pTile->getImage(); CHECK(image.width > 0); CHECK(image.height > 0); } diff --git a/CesiumUtility/include/CesiumUtility/DoublyLinkedList.h b/CesiumUtility/include/CesiumUtility/DoublyLinkedList.h index 4785b7947..83099ce31 100644 --- a/CesiumUtility/include/CesiumUtility/DoublyLinkedList.h +++ b/CesiumUtility/include/CesiumUtility/DoublyLinkedList.h @@ -36,9 +36,10 @@ template class DoublyLinkedListPointers final { private: template < - class TElement, - DoublyLinkedListPointers(TElement::*Pointers)> - friend class DoublyLinkedList; + typename TElement, + typename TElementBase, + DoublyLinkedListPointers(TElementBase::*Pointers)> + friend class DoublyLinkedListAdvanced; T* pNext; T* pPrevious; @@ -54,8 +55,11 @@ template class DoublyLinkedListPointers final { * @tparam (T::*Pointers) A member pointer to the field that holds the links to * the previous and next nodes. */ -template (T::*Pointers)> -class DoublyLinkedList final { +template < + typename T, + typename TPointerBase, + DoublyLinkedListPointers(TPointerBase::*Pointers)> +class DoublyLinkedListAdvanced final { public: /** * @brief Removes the given node from this list. @@ -238,10 +242,29 @@ class DoublyLinkedList final { return pNode ? this->previous(*pNode) : this->_pTail; } + /** + * @brief Determines if this list contains a given node in constant time. In + * order to avoid a full list scan, this method assumes that if the node has + * any next or previous node, then it is contained in this list. Do not use + * this method to determine which of multiple lists contain this node. + * + * @param node The node to check. + * @return True if this node is the head of the list, or if the node has next + * or previous nodes. False if the node does not have next or previous nodes + * and it is not the head of this list. + */ + bool contains(const T& node) const { + return this->next(node) != nullptr || this->previous(node) != nullptr || + this->_pHead == &node; + } + private: size_t _size = 0; T* _pHead = nullptr; T* _pTail = nullptr; }; +template (T::*Pointers)> +using DoublyLinkedList = DoublyLinkedListAdvanced; + } // namespace CesiumUtility diff --git a/CesiumUtility/include/CesiumUtility/ErrorList.h b/CesiumUtility/include/CesiumUtility/ErrorList.h index 3626368e6..f544073d1 100644 --- a/CesiumUtility/include/CesiumUtility/ErrorList.h +++ b/CesiumUtility/include/CesiumUtility/ErrorList.h @@ -15,6 +15,22 @@ namespace CesiumUtility { * or glTF content */ struct CESIUMUTILITY_API ErrorList { + /** + * @brief Creates an {@link ErrorList} containing a single error. + * + * @param errorMessage The error message. + * @return The new list containing the single error. + */ + static ErrorList error(std::string errorMessage); + + /** + * @brief Creates an {@link ErrorList} containing a single warning. + * + * @param warningMessage The warning message. + * @return The new list containing the single warning. + */ + static ErrorList warning(std::string warningMessage); + /** * @brief Merge the errors and warnings from other ErrorList together * diff --git a/CesiumUtility/include/CesiumUtility/ExtensibleObject.h b/CesiumUtility/include/CesiumUtility/ExtensibleObject.h index 012323f5f..c8e56755b 100644 --- a/CesiumUtility/include/CesiumUtility/ExtensibleObject.h +++ b/CesiumUtility/include/CesiumUtility/ExtensibleObject.h @@ -70,9 +70,14 @@ struct CESIUMUTILITY_API ExtensibleObject { * @tparam T The type of the extension to add. * @return The added extension. */ - template T& addExtension() { + template + T& addExtension(ConstructorArgumentTypes&&... constructorArguments) { std::any& extension = - extensions.try_emplace(T::ExtensionName, std::make_any()) + extensions + .try_emplace( + T::ExtensionName, + std::make_any(std::forward( + constructorArguments)...)) .first->second; return std::any_cast(extension); } diff --git a/CesiumUtility/include/CesiumUtility/Hash.h b/CesiumUtility/include/CesiumUtility/Hash.h new file mode 100644 index 000000000..950d2cacf --- /dev/null +++ b/CesiumUtility/include/CesiumUtility/Hash.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace CesiumUtility { + +/** + * @brief Contains functions for working with hashes. + * + */ +struct Hash { + /** + * @brief Combines two hash values, usually generated using `std::hash`, to + * form a single hash value. + * + * @param first The first hash value. + * @param second The second hash value. + * @return A new hash value which is a combination of the two. + */ + static std::size_t combine(std::size_t first, std::size_t second); +}; + +} // namespace CesiumUtility diff --git a/CesiumUtility/include/CesiumUtility/IDepotOwningAsset.h b/CesiumUtility/include/CesiumUtility/IDepotOwningAsset.h new file mode 100644 index 000000000..b5a43c82e --- /dev/null +++ b/CesiumUtility/include/CesiumUtility/IDepotOwningAsset.h @@ -0,0 +1,43 @@ +#pragma once + +namespace CesiumUtility { + +/** + * @brief An interface representing the depot that owns a {@link SharedAsset}. + * This interface is an implementation detail of the shared asset system and + * should not be used directly. + * + * {@link SharedAsset} has a pointer to the asset depot that owns it using this + * interface, rather than a complete {@link CesiumAsync::SharedAssetDepot}, in + * order to "erase" the type of the asset key. This allows SharedAsset to be + * templatized only on the asset type, not on the asset key type. + */ +template class IDepotOwningAsset { +public: + virtual ~IDepotOwningAsset() {} + + /** + * @brief Marks the given asset as a candidate for deletion. + * Should only be called by {@link SharedAsset}. May be called from any thread. + * + * @param asset The asset to mark for deletion. + * @param threadOwnsDepotLock True if the calling thread already owns the + * depot lock; otherwise, false. + */ + virtual void + markDeletionCandidate(const TAssetType& asset, bool threadOwnsDepotLock) = 0; + + /** + * @brief Unmarks the given asset as a candidate for deletion. + * Should only be called by {@link SharedAsset}. May be called from any thread. + * + * @param asset The asset to unmark for deletion. + * @param threadOwnsDepotLock True if the calling thread already owns the + * depot lock; otherwise, false. + */ + virtual void unmarkDeletionCandidate( + const TAssetType& asset, + bool threadOwnsDepotLock) = 0; +}; + +} // namespace CesiumUtility diff --git a/CesiumUtility/include/CesiumUtility/IntrusivePointer.h b/CesiumUtility/include/CesiumUtility/IntrusivePointer.h index 5a0169d5f..24bb22d0f 100644 --- a/CesiumUtility/include/CesiumUtility/IntrusivePointer.h +++ b/CesiumUtility/include/CesiumUtility/IntrusivePointer.h @@ -68,6 +68,27 @@ template class IntrusivePointer final { */ ~IntrusivePointer() noexcept { this->releaseReference(); } + /** + * @brief Constructs a new instance and assigns it to this IntrusivePointer. + * If this IntrusivePointer already points to another instance, + * {@link releaseReference} is called on it. + * + * @param constructorArguments The arguments to the constructor to create the + * new instance. + * @return A reference to the newly-created instance. + */ + template + T& emplace(ConstructorArgumentTypes&&... constructorArguments) { + *this = + new T(std::forward(constructorArguments)...); + return *this->get(); + } + + /** + * @brief Reset this pointer to nullptr. + */ + void reset() { *this = nullptr; } + /** * @brief Assignment operator. */ diff --git a/CesiumUtility/include/CesiumUtility/Result.h b/CesiumUtility/include/CesiumUtility/Result.h new file mode 100644 index 000000000..71789e20a --- /dev/null +++ b/CesiumUtility/include/CesiumUtility/Result.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +#include + +namespace CesiumUtility { + +/** + * @brief Holds the result of an operation. If the operation succeeds, it will + * provide a value. It may also provide errors and warnings. + * + * @tparam T The type of value included in the result. + */ +template struct Result { + Result(T value_) noexcept : value(std::move(value_)), errors() {} + + Result(T value_, ErrorList errors_) noexcept + : value(std::move(value_)), errors(std::move(errors_)) {} + + Result(ErrorList errors_) noexcept : value(), errors(std::move(errors_)) {} + + /** + * @brief The value, if the operation succeeded to the point where it can + * provide one. + * + * If a value is not provided because the operation failed, then there should + * be at least one error in {@link errors} indicating what went wrong. + */ + std::optional value; + + /** + * @brief The errors and warnings that occurred during the operation. + * + * If a {@link value} is provided, there should not be any errors in this + * list, but there may be warnings. If a value is not provided, there should + * be at least one error in this list. + */ + ErrorList errors; +}; + +/** + * @brief Holds the result of an operation. If the operation succeeds, it will + * provide a value. It may also provide errors and warnings. + * + * @tparam T The type of value included in the result. + */ +template struct Result> { + Result(CesiumUtility::IntrusivePointer pValue_) noexcept + : pValue(std::move(pValue_)), errors() {} + + Result(CesiumUtility::IntrusivePointer pValue_, ErrorList errors_) noexcept + : pValue(std::move(pValue_)), errors(std::move(errors_)) {} + + Result(ErrorList errors_) noexcept + : pValue(nullptr), errors(std::move(errors_)) {} + + /** + * @brief The value, if the operation succeeded to the point where it can + * provide one. + * + * If a value is not provided because the operation failed, then there should + * be at least one error in {@link errors} indicating what went wrong. + */ + CesiumUtility::IntrusivePointer pValue; + + /** + * @brief The errors and warnings that occurred during the operation. + * + * If a {@link pValue} is provided, there should not be any errors in this + * list, but there may be warnings. If a pValue is not provided, there should + * be at least one error in this list. + */ + ErrorList errors; +}; + +/** + * @brief A convenient shortcut for + * `CesiumUtility::Result>`. + * + * @tparam T The type of object that the IntrusivePointer points to. + */ +template using ResultPointer = Result>; + +} // namespace CesiumUtility diff --git a/CesiumUtility/include/CesiumUtility/SharedAsset.h b/CesiumUtility/include/CesiumUtility/SharedAsset.h new file mode 100644 index 000000000..907e63a29 --- /dev/null +++ b/CesiumUtility/include/CesiumUtility/SharedAsset.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace CesiumAsync { +template class SharedAssetDepot; +} + +namespace CesiumUtility { + +/** + * @brief An asset that is potentially shared between multiple objects, such as + * an image shared between multiple glTF models. This is intended to be the base + * class for such assets. + * + * The lifetime of instances of this class should be managed by reference + * counting with {@link IntrusivePointer}. + * + * @tparam T The type that is _deriving_ from this class. For example, you + * should declare your class as + * `class MyClass : public SharedAsset { ... };` + * + * @remarks @parblock + * A `SharedAsset` can be in one of three possible states: + * + * **Independent Asset** + * An independent asset isn't affiliated with an asset depot at all. + * Its lifetime is controlled exclusively by IntrusivePointer / reference + * counting. When the asset's reference count goes to zero, it deletes itself. + * An independent asset's {@link getDepot} returns nullptr. + * + * **Active Depot Asset** + * This is an asset that is owned by an asset depot and that is in use, meaning + * it has a reference count greater than zero. The asset depot owns the asset + * via an `std::unique_ptr`, not via adding to the reference count. So when the + * reference count goes to zero, only the asset depot itself still has a + * reference to it, so it becomes an inactive depot asset. + * + * **Inactive Depot Asset** + * This is also an asset that is owned by the asset depot, but there are no + * other references to it (it has a reference count of zero). It is found in the + * asset depot's `deletionCandidates` list. When a reference to it is added, it + * is removed from `deletionCandidates` and it becomes an active depot asset. + * @endparblock + */ +template +class CESIUMUTILITY_API SharedAsset : public CesiumUtility::ExtensibleObject { +public: + /** + * @brief Adds a counted reference to this object. Use + * {@link CesiumUtility::IntrusivePointer} instead of calling this method + * directly. + */ + void addReference() const noexcept { this->addReference(false); } + + /** + * @brief Removes a counted reference from this object. When the last + * reference is removed, this method will delete this instance. Use + * {@link CesiumUtility::IntrusivePointer} instead of calling this method + * directly. + */ + void releaseReference() const noexcept { this->releaseReference(false); } + + /** + * @brief Gets the shared asset depot that owns this asset, or nullptr if this + * asset is independent of an asset depot. + */ + const IDepotOwningAsset* getDepot() const { return this->_pDepot; } + + /** + * @brief Gets the shared asset depot that owns this asset, or nullptr if this + * asset is independent of an asset depot. + */ + IDepotOwningAsset* getDepot() { return this->_pDepot; } + +protected: + SharedAsset() = default; + ~SharedAsset() { CESIUM_ASSERT(this->_referenceCount == 0); } + + /** + * Assets can be copied, but the fresh instance has no references and is not + * in the asset depot. + */ + SharedAsset(const SharedAsset& rhs) + : ExtensibleObject(rhs), _referenceCount(0), _pDepot(nullptr) {} + + /** + * After a move construction, the content of the asset is moved to the new + * instance, but the asset depot still references the old instance. + */ + SharedAsset(SharedAsset&& rhs) + : ExtensibleObject(std::move(rhs)), + _referenceCount(0), + _pDepot(nullptr) {} + + /** + * Assignment does not affect the asset's relationship with the depot, but is + * useful to assign the data in derived classes. + */ + SharedAsset& operator=(const SharedAsset& rhs) { + CesiumUtility::ExtensibleObject::operator=(rhs); + return *this; + } + + SharedAsset& operator=(SharedAsset&& rhs) { + CesiumUtility::ExtensibleObject::operator=(std::move(rhs)); + return *this; + } + +private: + void addReference(bool threadOwnsDepotLock) const noexcept { + const int32_t prevReferences = this->_referenceCount++; + if (this->_pDepot && prevReferences <= 0) { + this->_pDepot->unmarkDeletionCandidate( + *static_cast(this), + threadOwnsDepotLock); + } + } + + void releaseReference(bool threadOwnsDepotLock) const noexcept { + CESIUM_ASSERT(this->_referenceCount > 0); + const int32_t references = --this->_referenceCount; + if (references == 0) { + IDepotOwningAsset* pDepot = this->_pDepot; + if (pDepot) { + // Let the depot manage this object's lifetime. + pDepot->markDeletionCandidate( + *static_cast(this), + threadOwnsDepotLock); + } else { + // No depot, so destroy this object directly. + delete static_cast(this); + } + } + } + + mutable std::atomic _referenceCount{0}; + IDepotOwningAsset* _pDepot{nullptr}; + + // To allow the depot to modify _pDepot. + template + friend class CesiumAsync::SharedAssetDepot; +}; + +} // namespace CesiumUtility diff --git a/CesiumUtility/src/ErrorList.cpp b/CesiumUtility/src/ErrorList.cpp index 4e157c1fd..b1f3a1257 100644 --- a/CesiumUtility/src/ErrorList.cpp +++ b/CesiumUtility/src/ErrorList.cpp @@ -2,6 +2,14 @@ namespace CesiumUtility { +/*static*/ ErrorList ErrorList::error(std::string errorMessage) { + return ErrorList{{std::move(errorMessage)}, {}}; +} + +/*static*/ ErrorList ErrorList::warning(std::string warningMessage) { + return ErrorList{{}, {std::move(warningMessage)}}; +} + void ErrorList::merge(const ErrorList& errorList) { errors.insert(errors.end(), errorList.errors.begin(), errorList.errors.end()); warnings.insert( diff --git a/CesiumUtility/src/Hash.cpp b/CesiumUtility/src/Hash.cpp new file mode 100644 index 000000000..927614105 --- /dev/null +++ b/CesiumUtility/src/Hash.cpp @@ -0,0 +1,79 @@ +#include + +namespace CesiumUtility { + +// This function is adapted from Boost v1.86.0, `hash_mix_impl<64>` function. +// +// Copyright 2022 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// hash_mix for 64 bit size_t +// +// The general "xmxmx" form of state of the art 64 bit mixers originates +// from Murmur3 by Austin Appleby, which uses the following function as +// its "final mix": +// +// k ^= k >> 33; +// k *= 0xff51afd7ed558ccd; +// k ^= k >> 33; +// k *= 0xc4ceb9fe1a85ec53; +// k ^= k >> 33; +// +// (https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp) +// +// It has subsequently been improved multiple times by different authors +// by changing the constants. The most well known improvement is the +// so-called "variant 13" function by David Stafford: +// +// k ^= k >> 30; +// k *= 0xbf58476d1ce4e5b9; +// k ^= k >> 27; +// k *= 0x94d049bb133111eb; +// k ^= k >> 31; +// +// (https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) +// +// This mixing function is used in the splitmix64 RNG: +// http://xorshift.di.unimi.it/splitmix64.c +// +// We use Jon Maiga's implementation from +// http://jonkagstrom.com/mx3/mx3_rev2.html +// +// x ^= x >> 32; +// x *= 0xe9846af9b1a615d; +// x ^= x >> 32; +// x *= 0xe9846af9b1a615d; +// x ^= x >> 28; +// +// An equally good alternative is Pelle Evensen's Moremur: +// +// x ^= x >> 27; +// x *= 0x3C79AC492BA7B653; +// x ^= x >> 33; +// x *= 0x1C69B3F74AC4AE35; +// x ^= x >> 27; +// +// (https://mostlymangling.blogspot.com/2019/12/stronger-better-morer-moremur-better.html) +namespace { + +inline std::size_t mix(std::size_t x) { + std::size_t const m = 0xe9846af9b1a615d; + + x ^= x >> 32; + x *= m; + x ^= x >> 32; + x *= m; + x ^= x >> 28; + + return x; +} + +} // namespace + +// This function is adapted from Boost's `hash_combine`. +std::size_t Hash::combine(std::size_t first, std::size_t second) { + return mix(first + 0x9e3779b9 + second); +} + +} // namespace CesiumUtility