cesium-native/Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp

903 lines
34 KiB
C++
Raw Permalink Normal View History

2024-07-24 02:03:06 +08:00
#include "TestTilesetJsonLoader.h"
#include "ImplicitQuadtreeLoader.h"
2022-07-08 04:16:39 +08:00
#include "SimplePrepareRendererResource.h"
#include "TilesetJsonLoader.h"
2022-07-14 03:24:51 +08:00
2025-06-13 23:32:33 +08:00
#include <Cesium3DTiles/ExtensionContent3dTilesContentVoxels.h>
2024-12-21 00:56:49 +08:00
#include <Cesium3DTiles/Schema.h>
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
2024-12-21 01:00:09 +08:00
#include <Cesium3DTilesSelection/Tile.h>
#include <Cesium3DTilesSelection/TileContent.h>
#include <Cesium3DTilesSelection/TileLoadResult.h>
#include <Cesium3DTilesSelection/TileRefine.h>
#include <Cesium3DTilesSelection/TilesetContentLoader.h>
#include <Cesium3DTilesSelection/TilesetContentLoaderResult.h>
2024-12-21 01:00:09 +08:00
#include <Cesium3DTilesSelection/TilesetMetadata.h>
2024-12-21 00:56:49 +08:00
#include <CesiumAsync/AsyncSystem.h>
#include <CesiumGeometry/Axis.h>
#include <CesiumGeometry/BoundingSphere.h>
#include <CesiumGeometry/OrientedBoundingBox.h>
#include <CesiumGeospatial/BoundingRegion.h>
2025-03-01 05:43:48 +08:00
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
2024-12-18 06:27:03 +08:00
#include <CesiumGltf/Model.h>
2025-03-01 05:43:48 +08:00
#include <CesiumGltf/PropertyTablePropertyView.h>
#include <CesiumGltf/PropertyTableView.h>
#include <CesiumNativeTests/SimpleAssetAccessor.h>
#include <CesiumNativeTests/SimpleAssetRequest.h>
#include <CesiumNativeTests/SimpleAssetResponse.h>
#include <CesiumNativeTests/SimpleTaskProcessor.h>
#include <CesiumNativeTests/readFile.h>
2024-12-21 00:56:49 +08:00
#include <CesiumUtility/CreditSystem.h>
2022-07-08 04:16:39 +08:00
2025-01-16 05:58:03 +08:00
#include <doctest/doctest.h>
2024-12-18 06:27:03 +08:00
#include <glm/ext/matrix_double3x3.hpp>
#include <glm/ext/matrix_double4x4.hpp>
#include <spdlog/sinks/ringbuffer_sink.h>
2024-12-18 06:27:03 +08:00
#include <spdlog/spdlog.h>
2022-07-08 04:16:39 +08:00
2024-12-18 06:27:03 +08:00
#include <array>
2022-07-08 04:16:14 +08:00
#include <cstddef>
2024-12-18 06:27:03 +08:00
#include <cstdint>
2024-07-24 02:03:06 +08:00
#include <filesystem>
2024-12-18 06:27:03 +08:00
#include <map>
2022-07-08 04:16:39 +08:00
#include <memory>
2024-12-18 06:27:03 +08:00
#include <optional>
2022-07-08 04:16:39 +08:00
#include <string>
2024-12-18 06:27:03 +08:00
#include <utility>
#include <variant>
#include <vector>
2022-07-08 04:16:14 +08:00
2025-01-16 05:58:03 +08:00
using namespace doctest;
2022-07-08 04:16:14 +08:00
using namespace CesiumAsync;
using namespace Cesium3DTilesSelection;
2025-03-01 04:32:21 +08:00
using namespace CesiumGltf;
using namespace CesiumNativeTests;
2023-11-14 15:29:47 +08:00
using namespace CesiumUtility;
2022-07-08 04:16:14 +08:00
namespace {
2022-07-11 23:49:02 +08:00
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
TileLoadResult loadTileContent(
const std::filesystem::path& tilePath,
TilesetContentLoader& loader,
Tile& tile) {
std::unique_ptr<SimpleAssetResponse> pMockCompletedResponse;
if (std::filesystem::exists(tilePath)) {
pMockCompletedResponse = std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
readFile(tilePath));
} else {
pMockCompletedResponse = std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(404),
"doesn't matter",
CesiumAsync::HttpHeaders{},
std::vector<std::byte>{});
}
2022-07-11 23:49:02 +08:00
auto pMockCompletedRequest = std::make_shared<SimpleAssetRequest>(
"GET",
tilePath.filename().string(),
2022-07-11 23:49:02 +08:00
CesiumAsync::HttpHeaders{},
std::move(pMockCompletedResponse));
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
mockCompletedRequests;
mockCompletedRequests.insert(
{tilePath.filename().string(), std::move(pMockCompletedRequest)});
std::shared_ptr<SimpleAssetAccessor> pMockAssetAccessor =
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
2022-07-12 12:06:03 +08:00
TileLoadInput loadInput{
2022-07-12 12:06:03 +08:00
tile,
{},
asyncSystem,
pMockAssetAccessor,
spdlog::default_logger(),
{}};
auto tileLoadResultFuture = loader.loadTileContent(loadInput);
2022-07-12 12:06:03 +08:00
asyncSystem.dispatchMainThreadTasks();
return tileLoadResultFuture.wait();
2022-07-11 23:49:02 +08:00
}
2022-07-08 04:16:14 +08:00
} // namespace
2025-03-01 04:32:21 +08:00
Cesium3DTilesSelection::TilesetContentLoaderResult<TilesetJsonLoader>
Cesium3DTilesSelection::createTilesetJsonLoader(
const std::filesystem::path& tilesetPath) {
std::string tilesetPathStr = tilesetPath.string();
auto pAccessor = std::make_shared<SimpleAssetAccessor>(
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>());
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<CesiumNativeTests::SimpleAssetAccessor>& pAssetAccessor) {
auto tilesetContent = readFile(tilesetPath);
auto pMockCompletedResponse =
std::make_unique<CesiumNativeTests::SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
std::move(tilesetContent));
auto pMockCompletedRequest =
std::make_shared<CesiumNativeTests::SimpleAssetRequest>(
"GET",
"tileset.json",
CesiumAsync::HttpHeaders{},
std::move(pMockCompletedResponse));
pAssetAccessor->mockCompletedRequests.insert(
{tilesetPath, std::move(pMockCompletedRequest)});
auto pMockPrepareRendererResource =
std::make_shared<SimplePrepareRendererResource>();
auto pMockCreditSystem = std::make_shared<CesiumUtility::CreditSystem>();
CesiumAsync::AsyncSystem asyncSystem{
std::make_shared<CesiumNativeTests::SimpleTaskProcessor>()};
return TilesetExternals{
std::move(pAssetAccessor),
std::move(pMockPrepareRendererResource),
std::move(asyncSystem),
std::move(pMockCreditSystem)};
}
2022-07-08 04:16:14 +08:00
TEST_CASE("Test creating tileset json loader") {
Cesium3DTilesContent::registerAllTileContentTypes();
2022-07-08 04:16:14 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Create valid tileset json with REPLACE refinement") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
testDataPath / "ReplaceTileset" / "tileset.json");
2022-07-08 04:16:14 +08:00
CHECK(!loaderResult.errors.hasErrors());
// check root tile
2023-08-21 16:36:49 +08:00
auto pTilesetJson = loaderResult.pRootTile.get();
REQUIRE(pTilesetJson);
REQUIRE(pTilesetJson->getChildren().size() == 1);
CHECK(pTilesetJson->getParent() == nullptr);
auto pRootTile = &pTilesetJson->getChildren()[0];
CHECK(pRootTile->getParent() == pTilesetJson);
REQUIRE(pRootTile->getChildren().size() == 4);
2022-07-08 04:16:14 +08:00
CHECK(pRootTile->getGeometricError() == 70.0);
CHECK(pRootTile->getRefine() == TileRefine::Replace);
CHECK(std::get<std::string>(pRootTile->getTileID()) == "parent.b3dm");
const auto& boundingVolume = pRootTile->getBoundingVolume();
const auto* pRegion =
std::get_if<CesiumGeospatial::BoundingRegion>(&boundingVolume);
CHECK(pRegion != nullptr);
CHECK(pRegion->getMinimumHeight() == Approx(0.0));
CHECK(pRegion->getMaximumHeight() == Approx(88.0));
CHECK(pRegion->getRectangle().getWest() == Approx(-1.3197209591796106));
CHECK(pRegion->getRectangle().getEast() == Approx(-1.3196390408203893));
CHECK(pRegion->getRectangle().getSouth() == Approx(0.6988424218));
CHECK(pRegion->getRectangle().getNorth() == Approx(0.6989055782));
// check root children
auto children = pRootTile->getChildren();
CHECK(children[0].getParent() == pRootTile);
CHECK(children[0].getChildren().size() == 1);
CHECK(children[0].getGeometricError() == 5.0);
CHECK(children[0].getRefine() == TileRefine::Replace);
CHECK(std::get<std::string>(children[0].getTileID()) == "ll.b3dm");
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
children[0].getBoundingVolume()));
CHECK(children[1].getParent() == pRootTile);
CHECK(children[1].getChildren().size() == 0);
CHECK(children[1].getGeometricError() == 0.0);
CHECK(children[1].getRefine() == TileRefine::Replace);
CHECK(std::get<std::string>(children[1].getTileID()) == "lr.b3dm");
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
children[1].getBoundingVolume()));
CHECK(children[2].getParent() == pRootTile);
CHECK(children[2].getChildren().size() == 0);
CHECK(children[2].getGeometricError() == 0.0);
CHECK(children[2].getRefine() == TileRefine::Replace);
CHECK(std::get<std::string>(children[2].getTileID()) == "ur.b3dm");
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
children[2].getBoundingVolume()));
CHECK(children[3].getParent() == pRootTile);
CHECK(children[3].getChildren().size() == 0);
CHECK(children[3].getGeometricError() == 0.0);
CHECK(children[3].getRefine() == TileRefine::Replace);
CHECK(std::get<std::string>(children[3].getTileID()) == "ul.b3dm");
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
children[3].getBoundingVolume()));
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
2022-07-08 04:16:14 +08:00
}
2025-01-16 05:58:03 +08:00
SUBCASE("Create valid tileset json with ADD refinement") {
2022-07-12 12:38:56 +08:00
auto loaderResult =
2024-07-24 02:03:06 +08:00
createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset2.json");
2022-07-08 04:16:14 +08:00
CHECK(!loaderResult.errors.hasErrors());
// check root tile
2023-08-21 16:36:49 +08:00
auto pTilesetJson = loaderResult.pRootTile.get();
REQUIRE(pTilesetJson != nullptr);
CHECK(pTilesetJson->getParent() == nullptr);
REQUIRE(pTilesetJson->getChildren().size() == 1);
auto pRootTile = &pTilesetJson->getChildren()[0];
CHECK(pRootTile->getParent() == pTilesetJson);
2022-07-08 04:56:44 +08:00
CHECK(pRootTile->getChildren().size() == 4);
2022-07-08 04:16:14 +08:00
CHECK(pRootTile->getGeometricError() == 70.0);
CHECK(pRootTile->getRefine() == TileRefine::Add);
2022-07-08 04:56:44 +08:00
CHECK(std::get<std::string>(pRootTile->getTileID()) == "parent.b3dm");
2022-07-08 04:16:14 +08:00
const auto& boundingVolume = pRootTile->getBoundingVolume();
const auto* pRegion =
std::get_if<CesiumGeospatial::BoundingRegion>(&boundingVolume);
CHECK(pRegion != nullptr);
CHECK(pRegion->getMinimumHeight() == Approx(0.0));
CHECK(pRegion->getMaximumHeight() == Approx(88.0));
CHECK(pRegion->getRectangle().getWest() == Approx(-1.3197209591796106));
CHECK(pRegion->getRectangle().getEast() == Approx(-1.3196390408203893));
CHECK(pRegion->getRectangle().getSouth() == Approx(0.6988424218));
CHECK(pRegion->getRectangle().getNorth() == Approx(0.6989055782));
2022-07-08 04:56:44 +08:00
// check children
std::array<std::string, 4> expectedUrl{
{"tileset3/tileset3.json", "lr.b3dm", "ur.b3dm", "ul.b3dm"}};
auto expectedUrlIt = expectedUrl.begin();
for (const Tile& child : pRootTile->getChildren()) {
CHECK(child.getParent() == pRootTile);
CHECK(child.getChildren().size() == 0);
CHECK(child.getGeometricError() == 0.0);
CHECK(child.getRefine() == TileRefine::Add);
CHECK(std::get<std::string>(child.getTileID()) == *expectedUrlIt);
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
child.getBoundingVolume()));
++expectedUrlIt;
}
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
2022-07-08 04:16:14 +08:00
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset has tile with sphere bounding volume") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-13 01:01:58 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"SphereBoundingVolumeTileset.json");
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2022-07-13 01:01:58 +08:00
2023-08-21 16:36:49 +08:00
const Tile& rootTile = loaderResult.pRootTile->getChildren()[0];
2022-07-13 01:01:58 +08:00
const CesiumGeometry::BoundingSphere& sphere =
2022-07-14 03:24:51 +08:00
std::get<CesiumGeometry::BoundingSphere>(rootTile.getBoundingVolume());
2022-07-13 01:01:58 +08:00
CHECK(sphere.getCenter() == glm::dvec3(0.0, 0.0, 10.0));
CHECK(sphere.getRadius() == 141.4214);
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
2022-07-13 01:01:58 +08:00
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset has tile with box bounding volume") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-13 01:01:58 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"BoxBoundingVolumeTileset.json");
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2022-07-13 01:01:58 +08:00
2023-08-21 16:36:49 +08:00
const Tile& rootTile = loaderResult.pRootTile->getChildren()[0];
2022-07-13 01:01:58 +08:00
const CesiumGeometry::OrientedBoundingBox& box =
std::get<CesiumGeometry::OrientedBoundingBox>(
rootTile.getBoundingVolume());
const glm::dmat3& halfAxis = box.getHalfAxes();
CHECK(halfAxis[0] == glm::dvec3(100.0, 0.0, 0.0));
CHECK(halfAxis[1] == glm::dvec3(0.0, 100.0, 0.0));
CHECK(halfAxis[2] == glm::dvec3(0.0, 0.0, 10.0));
CHECK(box.getCenter() == glm::dvec3(0.0, 0.0, 10.0));
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset has tile with no bounding volume field") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"NoBoundingVolumeTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->getChildren().empty());
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset has tile with no geometric error field") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"NoGeometricErrorTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->getGeometricError() == Approx(70.0));
CHECK(pRootTile->getChildren().size() == 4);
for (const Tile& child : pRootTile->getChildren()) {
CHECK(child.getGeometricError() == Approx(35.0));
}
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset has tile with no capitalized Refinement field") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"NoCapitalizedRefineTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->getGeometricError() == Approx(70.0));
CHECK(pRootTile->getRefine() == TileRefine::Add);
CHECK(pRootTile->getChildren().size() == 4);
for (const Tile& child : pRootTile->getChildren()) {
CHECK(child.getGeometricError() == Approx(5.0));
CHECK(child.getRefine() == TileRefine::Replace);
}
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
}
2025-01-16 05:58:03 +08:00
SUBCASE("Scale geometric error along with tile transform") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"ScaleGeometricErrorTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->getGeometricError() == Approx(210.0));
CHECK(pRootTile->getChildren().size() == 4);
for (const Tile& child : pRootTile->getChildren()) {
CHECK(child.getGeometricError() == Approx(15.0));
}
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
}
2022-07-08 23:05:48 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset with empty tile") {
2025-02-26 02:56:23 +08:00
std::shared_ptr<SimpleAssetAccessor> pMockAssetAccessor =
std::make_shared<SimpleAssetAccessor>(
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>());
AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" / "EmptyTileTileset.json");
2022-07-08 23:05:48 +08:00
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->getGeometricError() == Approx(70.0));
CHECK(pRootTile->getChildren().size() == 1);
2022-07-08 23:05:48 +08:00
2025-02-26 02:56:23 +08:00
Tile& child = pRootTile->getChildren().front();
auto future = loaderResult.pLoader->loadTileContent(TileLoadInput{
child,
{},
asyncSystem,
pMockAssetAccessor,
spdlog::default_logger(),
{}});
TileLoadResult result = future.wait();
REQUIRE(result.state == TileLoadResultState::Success);
TileEmptyContent* emptyContent =
std::get_if<TileEmptyContent>(&result.contentKind);
REQUIRE(emptyContent);
child.getContent().setContentKind(*emptyContent);
2022-07-08 23:05:48 +08:00
CHECK(child.isEmptyContent());
// check loader up axis
2022-08-23 06:32:00 +08:00
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
2022-07-08 23:05:48 +08:00
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset with quadtree implicit tile") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"QuadtreeImplicitTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
2022-07-09 02:20:39 +08:00
CHECK(loaderResult.pRootTile->isExternalContent());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->isExternalContent());
REQUIRE(pRootTile->getChildren().size() == 1);
2023-08-21 16:36:49 +08:00
CHECK(pRootTile->getTransform() == glm::dmat4(glm::dmat3(2.0)));
2022-07-09 02:20:39 +08:00
2023-08-21 16:36:49 +08:00
const Tile& child = pRootTile->getChildren().front();
2022-07-23 02:04:47 +08:00
CHECK(child.getLoader() != loaderResult.pLoader.get());
2023-08-21 16:36:49 +08:00
CHECK(child.getGeometricError() == pRootTile->getGeometricError());
CHECK(child.getRefine() == pRootTile->getRefine());
CHECK(child.getTransform() == pRootTile->getTransform());
2022-07-09 02:20:39 +08:00
CHECK(
std::get<CesiumGeometry::QuadtreeTileID>(child.getTileID()) ==
CesiumGeometry::QuadtreeTileID(0, 0, 0));
}
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset with octree implicit tile") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
2022-07-09 02:20:39 +08:00
testDataPath / "MultipleKindsOfTilesets" /
"OctreeImplicitTileset.json");
CHECK(!loaderResult.errors.hasErrors());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
2022-07-09 02:20:39 +08:00
CHECK(loaderResult.pRootTile->isExternalContent());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
CHECK(pRootTile->isExternalContent());
REQUIRE(pRootTile->getChildren().size() == 1);
2023-08-21 16:36:49 +08:00
CHECK(pRootTile->getTransform() == glm::dmat4(glm::dmat3(2.0)));
2022-07-08 23:05:48 +08:00
2023-08-21 16:36:49 +08:00
const Tile& child = pRootTile->getChildren().front();
2022-07-23 02:04:47 +08:00
CHECK(child.getLoader() != loaderResult.pLoader.get());
2023-08-21 16:36:49 +08:00
CHECK(child.getGeometricError() == pRootTile->getGeometricError());
CHECK(child.getRefine() == pRootTile->getRefine());
CHECK(child.getTransform() == pRootTile->getTransform());
2022-07-09 02:20:39 +08:00
CHECK(
std::get<CesiumGeometry::OctreeTileID>(child.getTileID()) ==
CesiumGeometry::OctreeTileID(0, 0, 0, 0));
}
2023-08-18 19:56:48 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Tileset with metadata") {
auto loaderResult =
2024-07-24 02:03:06 +08:00
createTilesetJsonLoader(testDataPath / "WithMetadata" / "tileset.json");
CHECK(!loaderResult.errors.hasErrors());
REQUIRE(loaderResult.pLoader);
REQUIRE(loaderResult.pRootTile);
TileExternalContent* pExternal =
loaderResult.pRootTile->getContent().getExternalContent();
REQUIRE(pExternal);
const TilesetMetadata& metadata = pExternal->metadata;
const std::optional<Cesium3DTiles::Schema>& schema = metadata.schema;
REQUIRE(schema);
CHECK(schema->id == "foo");
}
2025-06-13 23:32:33 +08:00
SUBCASE("Tileset with 3DTILES_content_voxels") {
auto loaderResult =
createTilesetJsonLoader(testDataPath / "Voxels" / "tileset.json");
CHECK(!loaderResult.errors.hasErrors());
REQUIRE(loaderResult.pLoader);
REQUIRE(loaderResult.pRootTile);
TileExternalContent* pExternal =
loaderResult.pRootTile->getContent().getExternalContent();
REQUIRE(pExternal);
CHECK(pExternal->hasExtension<
Cesium3DTiles::ExtensionContent3dTilesContentVoxels>());
const TilesetMetadata& metadata = pExternal->metadata;
const std::optional<Cesium3DTiles::Schema>& schema = metadata.schema;
REQUIRE(schema);
CHECK(schema->id == "voxel");
}
2022-07-08 04:16:14 +08:00
}
TEST_CASE("Test loading individual tile of tileset json") {
Cesium3DTilesContent::registerAllTileContentTypes();
2022-07-13 04:51:13 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Load tile that has render content") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
testDataPath / "ReplaceTileset" / "tileset.json");
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2022-07-12 03:36:15 +08:00
2023-08-21 16:36:49 +08:00
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
const auto& tileID = std::get<std::string>(pRootTile->getTileID());
2022-07-12 03:36:15 +08:00
CHECK(tileID == "parent.b3dm");
// check tile content
auto tileLoadResult = loadTileContent(
testDataPath / "ReplaceTileset" / tileID,
*loaderResult.pLoader,
2023-08-21 16:36:49 +08:00
*pRootTile);
2022-08-09 03:02:03 +08:00
CHECK(
2022-08-11 23:33:15 +08:00
std::holds_alternative<CesiumGltf::Model>(tileLoadResult.contentKind));
2022-07-12 03:36:15 +08:00
CHECK(tileLoadResult.updatedBoundingVolume == std::nullopt);
CHECK(tileLoadResult.updatedContentBoundingVolume == std::nullopt);
CHECK(tileLoadResult.state == TileLoadResultState::Success);
CHECK(!tileLoadResult.tileInitializer);
}
2022-07-08 04:16:14 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Load tile that has external content") {
2022-07-14 03:24:51 +08:00
auto loaderResult =
2024-07-24 02:03:06 +08:00
createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset.json");
2022-07-11 23:49:02 +08:00
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2022-07-12 03:36:15 +08:00
2023-08-21 16:36:49 +08:00
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
const auto& tileID = std::get<std::string>(pRootTile->getTileID());
2022-07-12 03:36:15 +08:00
CHECK(tileID == "tileset2.json");
2022-07-11 23:49:02 +08:00
// check tile content
auto tileLoadResult = loadTileContent(
2022-07-12 03:36:15 +08:00
testDataPath / "AddTileset" / tileID,
2022-07-11 23:49:02 +08:00
*loaderResult.pLoader,
2023-08-21 16:36:49 +08:00
*pRootTile);
2022-07-11 23:49:02 +08:00
CHECK(tileLoadResult.updatedBoundingVolume == std::nullopt);
CHECK(tileLoadResult.updatedContentBoundingVolume == std::nullopt);
CHECK(std::holds_alternative<TileExternalContent>(
tileLoadResult.contentKind));
CHECK(tileLoadResult.state == TileLoadResultState::Success);
CHECK(tileLoadResult.tileInitializer);
// check tile is really an external tile
2023-08-21 16:36:49 +08:00
pRootTile->getContent().setContentKind(
std::make_unique<TileExternalContent>(
std::get<TileExternalContent>(tileLoadResult.contentKind)));
tileLoadResult.tileInitializer(*pRootTile);
const auto& children = pRootTile->getChildren();
REQUIRE(children.size() == 1);
2022-07-11 23:49:02 +08:00
const Tile& parentB3dmTile = children[0];
CHECK(std::get<std::string>(parentB3dmTile.getTileID()) == "parent.b3dm");
CHECK(parentB3dmTile.getGeometricError() == Approx(70.0));
std::vector<std::string> expectedChildUrls{
"tileset3/tileset3.json",
"lr.b3dm",
"ur.b3dm",
"ul.b3dm"};
const auto& parentB3dmChildren = parentB3dmTile.getChildren();
for (std::size_t i = 0; i < parentB3dmChildren.size(); ++i) {
const Tile& child = parentB3dmChildren[i];
CHECK(std::get<std::string>(child.getTileID()) == expectedChildUrls[i]);
CHECK(child.getGeometricError() == Approx(0.0));
CHECK(child.getRefine() == TileRefine::Add);
CHECK(std::holds_alternative<CesiumGeospatial::BoundingRegion>(
child.getBoundingVolume()));
}
}
2022-07-08 04:16:14 +08:00
2025-01-16 05:58:03 +08:00
SUBCASE("Load tile that has external content with implicit tiling") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
testDataPath / "ImplicitTileset" / "tileset_1.1.json");
2022-07-12 12:06:03 +08:00
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
2022-07-12 12:06:03 +08:00
CHECK(loaderResult.pRootTile->isExternalContent());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2022-07-12 12:06:03 +08:00
2023-08-21 16:36:49 +08:00
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
REQUIRE(pRootTile->getChildren().size() == 1);
auto& implicitTile = pRootTile->getChildren().front();
2022-07-12 12:06:03 +08:00
const auto& tileID =
std::get<CesiumGeometry::QuadtreeTileID>(implicitTile.getTileID());
CHECK(tileID == CesiumGeometry::QuadtreeTileID(0, 0, 0));
// mock subtree content response
auto subtreePath =
testDataPath / "ImplicitTileset" / "subtrees" / "0.0.0.json";
auto pMockSubtreeResponse = std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
readFile(subtreePath));
auto pMockSubtreeRequest = std::make_shared<SimpleAssetRequest>(
"GET",
"doesn't matter",
CesiumAsync::HttpHeaders{},
std::move(pMockSubtreeResponse));
// mock tile content response
auto tilePath =
testDataPath / "ImplicitTileset" / "content" / "0" / "0" / "0.b3dm";
auto pMockTileResponse = std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
readFile(tilePath));
auto pMockTileRequest = std::make_shared<SimpleAssetRequest>(
"GET",
"doesn't matter",
CesiumAsync::HttpHeaders{},
std::move(pMockTileResponse));
// create mock asset accessor
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
mockCompletedRequests{
{"subtrees/0.0.0.json", std::move(pMockSubtreeRequest)},
{"content/0/0/0.b3dm", std::move(pMockTileRequest)}};
std::shared_ptr<SimpleAssetAccessor> pMockAssetAccessor =
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
{
// loader will tell to retry later since it needs subtree
TileLoadInput loadInput{
2022-07-12 12:06:03 +08:00
implicitTile,
{},
asyncSystem,
pMockAssetAccessor,
spdlog::default_logger(),
{}};
2022-07-30 05:38:14 +08:00
auto implicitContentResultFuture =
loaderResult.pLoader->loadTileContent(loadInput);
2022-07-12 12:06:03 +08:00
asyncSystem.dispatchMainThreadTasks();
auto implicitContentResult = implicitContentResultFuture.wait();
CHECK(implicitContentResult.state == TileLoadResultState::RetryLater);
}
{
// loader will be able to load the tile the second time around
TileLoadInput loadInput{
2022-07-12 12:06:03 +08:00
implicitTile,
{},
asyncSystem,
pMockAssetAccessor,
spdlog::default_logger(),
{}};
auto implicitContentResultFuture =
loaderResult.pLoader->loadTileContent(loadInput);
2022-07-12 12:06:03 +08:00
asyncSystem.dispatchMainThreadTasks();
auto implicitContentResult = implicitContentResultFuture.wait();
2022-08-11 23:33:15 +08:00
CHECK(std::holds_alternative<CesiumGltf::Model>(
2022-08-09 03:02:03 +08:00
implicitContentResult.contentKind));
2022-07-12 12:06:03 +08:00
CHECK(!implicitContentResult.updatedBoundingVolume);
CHECK(!implicitContentResult.updatedContentBoundingVolume);
CHECK(implicitContentResult.state == TileLoadResultState::Success);
CHECK(!implicitContentResult.tileInitializer);
}
}
2025-01-16 05:58:03 +08:00
SUBCASE("Check that tile with legacy implicit tiling extension still works") {
2024-07-24 02:03:06 +08:00
auto loaderResult = createTilesetJsonLoader(
testDataPath / "ImplicitTileset" / "tileset_1.0.json");
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile);
CHECK(loaderResult.pRootTile->isExternalContent());
2023-08-21 16:36:49 +08:00
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
REQUIRE(pRootTile->getChildren().size() == 1);
2023-08-21 16:36:49 +08:00
auto& implicitTile = pRootTile->getChildren().front();
const auto& tileID =
std::get<CesiumGeometry::QuadtreeTileID>(implicitTile.getTileID());
CHECK(tileID == CesiumGeometry::QuadtreeTileID(0, 0, 0));
const auto pLoader =
dynamic_cast<const ImplicitQuadtreeLoader*>(implicitTile.getLoader());
CHECK(pLoader);
CHECK(pLoader->getSubtreeLevels() == 2);
CHECK(pLoader->getAvailableLevels() == 2);
}
SUBCASE("Tile with missing content") {
auto pLog = std::make_shared<spdlog::sinks::ringbuffer_sink_mt>(3);
spdlog::default_logger()->sinks().emplace_back(pLog);
auto loaderResult = createTilesetJsonLoader(
testDataPath / "MultipleKindsOfTilesets" /
"ErrorMissingContentTileset.json");
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
const auto& tileID = std::get<std::string>(pRootTile->getTileID());
CHECK(tileID == "nonexistent.b3dm");
// check tile content
auto tileLoadResult = loadTileContent(
testDataPath / "MultipleKindsOfTilesets" / tileID,
*loaderResult.pLoader,
*pRootTile);
CHECK(tileLoadResult.state == TileLoadResultState::Failed);
std::vector<std::string> logMessages = pLog->last_formatted();
REQUIRE(logMessages.size() == 1);
REQUIRE(
logMessages.back()
.substr(0, logMessages.back().find_last_not_of("\n\r") + 1)
.ends_with(
"Received status code 404 for tile content nonexistent.b3dm"));
}
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
SUBCASE("Tile with complex structural metadata") {
2025-03-01 05:43:48 +08:00
auto loaderResult =
createTilesetJsonLoader(testDataPath / "ComplexTypes" / "tileset.json");
2025-03-01 04:32:21 +08:00
REQUIRE(loaderResult.pRootTile);
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
const auto& tileID = std::get<std::string>(pRootTile->getTileID());
CHECK(tileID == "ComplexTypes.gltf");
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
// check tile content
auto tileLoadResult = loadTileContent(
testDataPath / "ComplexTypes" / tileID,
*loaderResult.pLoader,
*pRootTile);
Model* pModel = std::get_if<CesiumGltf::Model>(&tileLoadResult.contentKind);
REQUIRE(pModel);
CHECK(tileLoadResult.updatedBoundingVolume == std::nullopt);
CHECK(tileLoadResult.updatedContentBoundingVolume == std::nullopt);
CHECK(tileLoadResult.state == TileLoadResultState::Success);
CHECK(!tileLoadResult.tileInitializer);
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
ExtensionModelExtStructuralMetadata* pMetadata =
pModel->getExtension<ExtensionModelExtStructuralMetadata>();
REQUIRE(pMetadata);
REQUIRE(pMetadata->schema);
REQUIRE(pMetadata->propertyTables.size() == 1);
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
PropertyTable& propertyTable = pMetadata->propertyTables.front();
REQUIRE(propertyTable.properties.size() == 4);
2024-07-24 02:03:06 +08:00
2025-03-01 04:32:21 +08:00
PropertyTableView view(*pModel, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
2025-03-01 05:43:48 +08:00
const std::array<std::vector<uint8_t>, 4> expectedUint8{
std::vector<uint8_t>{0, 255},
std::vector<uint8_t>{0, 128, 255},
std::vector<uint8_t>{0, 85, 170, 255},
std::vector<uint8_t>{0, 64, 128, 192, 255}};
const PropertyTablePropertyView<PropertyArrayView<uint8_t>, true>
varLenUintPropertyView =
2025-03-01 04:32:21 +08:00
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
2025-03-01 05:43:48 +08:00
"example_variable_length_ARRAY_normalized_UINT8");
2025-03-01 04:32:21 +08:00
REQUIRE(
2025-03-01 05:43:48 +08:00
varLenUintPropertyView.status() ==
2025-03-01 04:32:21 +08:00
PropertyTablePropertyViewStatus::Valid);
2025-03-01 05:43:48 +08:00
REQUIRE(varLenUintPropertyView.size() == expectedUint8.size());
for (size_t i = 0; i < expectedUint8.size(); i++) {
const auto& value =
varLenUintPropertyView.getRaw(static_cast<int64_t>(i));
for (int64_t j = 0; j < value.size(); j++) {
CHECK(expectedUint8[i][static_cast<size_t>(j)] == value[j]);
}
}
2025-03-01 04:32:21 +08:00
2025-03-01 05:43:48 +08:00
const std::array<std::vector<bool>, 4> expectedBool{
std::vector<bool>{
true,
false,
true,
false,
true,
false,
true,
false,
true,
false},
std::vector<bool>{
true,
true,
false,
false,
true,
true,
false,
false,
true,
true},
std::vector<bool>{
false,
false,
true,
true,
false,
false,
true,
true,
false,
false},
std::vector<bool>{
false,
true,
false,
true,
false,
true,
false,
true,
false,
true}};
2025-03-01 04:32:21 +08:00
const PropertyTablePropertyView<PropertyArrayView<bool>, false>
2025-03-01 05:43:48 +08:00
fixedLenBoolPropertyView =
2025-03-01 04:32:21 +08:00
view.getPropertyView<PropertyArrayView<bool>, false>(
2025-03-01 05:43:48 +08:00
"example_fixed_length_ARRAY_BOOLEAN");
2025-03-01 04:32:21 +08:00
REQUIRE(
2025-03-01 05:43:48 +08:00
fixedLenBoolPropertyView.status() ==
2025-03-01 04:32:21 +08:00
PropertyTablePropertyViewStatus::Valid);
2025-03-01 05:43:48 +08:00
REQUIRE(fixedLenBoolPropertyView.size() == expectedBool.size());
for (size_t i = 0; i < expectedBool.size(); i++) {
const auto& value = fixedLenBoolPropertyView.get(static_cast<int64_t>(i));
REQUIRE(value);
for (int64_t j = 0; j < value->size(); j++) {
CHECK(expectedBool[i][static_cast<size_t>(j)] == (*value)[j]);
}
}
2025-03-01 04:32:21 +08:00
2025-03-01 05:43:48 +08:00
const std::array<std::vector<std::string>, 4> expectedString{
std::vector<std::string>{"One"},
std::vector<std::string>{"One", "Two"},
std::vector<std::string>{"One", "Two", "Three"},
std::vector<std::string>{"One", "Two", "Theee", "Four"}};
2025-03-01 04:32:21 +08:00
const PropertyTablePropertyView<PropertyArrayView<std::string_view>, false>
2025-03-01 05:43:48 +08:00
varLenStringPropertyView =
2025-03-01 04:32:21 +08:00
view.getPropertyView<PropertyArrayView<std::string_view>, false>(
2025-03-01 05:43:48 +08:00
"example_variable_length_ARRAY_STRING");
2025-03-01 04:32:21 +08:00
REQUIRE(
2025-03-01 05:43:48 +08:00
varLenStringPropertyView.status() ==
2025-03-01 04:32:21 +08:00
PropertyTablePropertyViewStatus::Valid);
2025-03-01 05:43:48 +08:00
REQUIRE(varLenStringPropertyView.size() == expectedString.size());
for (size_t i = 0; i < expectedString.size(); i++) {
const auto& value = varLenStringPropertyView.get(static_cast<int64_t>(i));
REQUIRE(value);
for (int64_t j = 0; j < value->size(); j++) {
CHECK(expectedString[i][static_cast<size_t>(j)] == (*value)[j]);
}
}
2025-03-01 04:32:21 +08:00
2025-03-01 05:43:48 +08:00
const std::array<std::vector<uint16_t>, 4> expectedEnum{
std::vector<uint16_t>{0, 1},
std::vector<uint16_t>{1, 2},
std::vector<uint16_t>{2, 0},
std::vector<uint16_t>{1, 2},
};
2025-03-01 04:32:21 +08:00
const PropertyTablePropertyView<PropertyArrayView<uint16_t>, false>
2025-03-01 05:43:48 +08:00
fixenLenEnumPropertyView =
2025-03-01 04:32:21 +08:00
view.getPropertyView<PropertyArrayView<uint16_t>, false>(
"example_fixed_length_ARRAY_ENUM");
REQUIRE(
2025-03-01 05:43:48 +08:00
fixenLenEnumPropertyView.status() ==
2025-03-01 04:32:21 +08:00
PropertyTablePropertyViewStatus::Valid);
2025-03-01 05:43:48 +08:00
REQUIRE(fixenLenEnumPropertyView.size() == expectedEnum.size());
for (size_t i = 0; i < expectedEnum.size(); i++) {
const auto& value = fixenLenEnumPropertyView.get(static_cast<int64_t>(i));
REQUIRE(value);
for (int64_t j = 0; j < value->size(); j++) {
CHECK(expectedEnum[i][static_cast<size_t>(j)] == (*value)[j]);
}
}
2025-03-01 04:32:21 +08:00
}
2024-07-24 02:03:06 +08:00
}