903 lines
34 KiB
C++
903 lines
34 KiB
C++
#include "TestTilesetJsonLoader.h"
|
|
|
|
#include "ImplicitQuadtreeLoader.h"
|
|
#include "SimplePrepareRendererResource.h"
|
|
#include "TilesetJsonLoader.h"
|
|
|
|
#include <Cesium3DTiles/ExtensionContent3dTilesContentVoxels.h>
|
|
#include <Cesium3DTiles/Schema.h>
|
|
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
|
|
#include <Cesium3DTilesSelection/Tile.h>
|
|
#include <Cesium3DTilesSelection/TileContent.h>
|
|
#include <Cesium3DTilesSelection/TileLoadResult.h>
|
|
#include <Cesium3DTilesSelection/TileRefine.h>
|
|
#include <Cesium3DTilesSelection/TilesetContentLoader.h>
|
|
#include <Cesium3DTilesSelection/TilesetContentLoaderResult.h>
|
|
#include <Cesium3DTilesSelection/TilesetMetadata.h>
|
|
#include <CesiumAsync/AsyncSystem.h>
|
|
#include <CesiumGeometry/Axis.h>
|
|
#include <CesiumGeometry/BoundingSphere.h>
|
|
#include <CesiumGeometry/OrientedBoundingBox.h>
|
|
#include <CesiumGeospatial/BoundingRegion.h>
|
|
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
|
|
#include <CesiumGltf/Model.h>
|
|
#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>
|
|
#include <CesiumUtility/CreditSystem.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <glm/ext/matrix_double3x3.hpp>
|
|
#include <glm/ext/matrix_double4x4.hpp>
|
|
#include <spdlog/sinks/ringbuffer_sink.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
using namespace doctest;
|
|
using namespace CesiumAsync;
|
|
using namespace Cesium3DTilesSelection;
|
|
using namespace CesiumGltf;
|
|
using namespace CesiumNativeTests;
|
|
using namespace CesiumUtility;
|
|
|
|
namespace {
|
|
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>{});
|
|
}
|
|
auto pMockCompletedRequest = std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
tilePath.filename().string(),
|
|
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>()};
|
|
|
|
TileLoadInput loadInput{
|
|
tile,
|
|
{},
|
|
asyncSystem,
|
|
pMockAssetAccessor,
|
|
spdlog::default_logger(),
|
|
{}};
|
|
|
|
auto tileLoadResultFuture = loader.loadTileContent(loadInput);
|
|
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
return tileLoadResultFuture.wait();
|
|
}
|
|
} // namespace
|
|
|
|
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)};
|
|
}
|
|
|
|
TEST_CASE("Test creating tileset json loader") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
SUBCASE("Create valid tileset json with REPLACE refinement") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "ReplaceTileset" / "tileset.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
|
|
// check root tile
|
|
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);
|
|
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
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Create valid tileset json with ADD refinement") {
|
|
auto loaderResult =
|
|
createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset2.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
|
|
// check root tile
|
|
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);
|
|
CHECK(pRootTile->getChildren().size() == 4);
|
|
CHECK(pRootTile->getGeometricError() == 70.0);
|
|
CHECK(pRootTile->getRefine() == TileRefine::Add);
|
|
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 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
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset has tile with sphere bounding volume") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"SphereBoundingVolumeTileset.json");
|
|
|
|
REQUIRE(loaderResult.pRootTile);
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
|
|
const Tile& rootTile = loaderResult.pRootTile->getChildren()[0];
|
|
const CesiumGeometry::BoundingSphere& sphere =
|
|
std::get<CesiumGeometry::BoundingSphere>(rootTile.getBoundingVolume());
|
|
CHECK(sphere.getCenter() == glm::dvec3(0.0, 0.0, 10.0));
|
|
CHECK(sphere.getRadius() == 141.4214);
|
|
|
|
// check loader up axis
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset has tile with box bounding volume") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"BoxBoundingVolumeTileset.json");
|
|
|
|
REQUIRE(loaderResult.pRootTile);
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
|
|
const Tile& rootTile = loaderResult.pRootTile->getChildren()[0];
|
|
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));
|
|
}
|
|
|
|
SUBCASE("Tileset has tile with no bounding volume field") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"NoBoundingVolumeTileset.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
REQUIRE(loaderResult.pRootTile);
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
|
|
CHECK(pRootTile->getChildren().empty());
|
|
|
|
// check loader up axis
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset has tile with no geometric error field") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"NoGeometricErrorTileset.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
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
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset has tile with no capitalized Refinement field") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"NoCapitalizedRefineTileset.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
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
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Scale geometric error along with tile transform") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"ScaleGeometricErrorTileset.json");
|
|
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
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
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset with empty tile") {
|
|
std::shared_ptr<SimpleAssetAccessor> pMockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>());
|
|
AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
|
|
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" / "EmptyTileTileset.json");
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
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);
|
|
|
|
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);
|
|
CHECK(child.isEmptyContent());
|
|
|
|
// check loader up axis
|
|
CHECK(loaderResult.pLoader->getUpAxis() == CesiumGeometry::Axis::Y);
|
|
}
|
|
|
|
SUBCASE("Tileset with quadtree implicit tile") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"QuadtreeImplicitTileset.json");
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
REQUIRE(loaderResult.pRootTile);
|
|
CHECK(loaderResult.pRootTile->isExternalContent());
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
|
|
CHECK(pRootTile->isExternalContent());
|
|
REQUIRE(pRootTile->getChildren().size() == 1);
|
|
CHECK(pRootTile->getTransform() == glm::dmat4(glm::dmat3(2.0)));
|
|
|
|
const Tile& child = pRootTile->getChildren().front();
|
|
CHECK(child.getLoader() != loaderResult.pLoader.get());
|
|
CHECK(child.getGeometricError() == pRootTile->getGeometricError());
|
|
CHECK(child.getRefine() == pRootTile->getRefine());
|
|
CHECK(child.getTransform() == pRootTile->getTransform());
|
|
CHECK(
|
|
std::get<CesiumGeometry::QuadtreeTileID>(child.getTileID()) ==
|
|
CesiumGeometry::QuadtreeTileID(0, 0, 0));
|
|
}
|
|
|
|
SUBCASE("Tileset with octree implicit tile") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "MultipleKindsOfTilesets" /
|
|
"OctreeImplicitTileset.json");
|
|
CHECK(!loaderResult.errors.hasErrors());
|
|
REQUIRE(loaderResult.pRootTile);
|
|
CHECK(loaderResult.pRootTile->isExternalContent());
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
|
|
CHECK(pRootTile->isExternalContent());
|
|
REQUIRE(pRootTile->getChildren().size() == 1);
|
|
CHECK(pRootTile->getTransform() == glm::dmat4(glm::dmat3(2.0)));
|
|
|
|
const Tile& child = pRootTile->getChildren().front();
|
|
CHECK(child.getLoader() != loaderResult.pLoader.get());
|
|
CHECK(child.getGeometricError() == pRootTile->getGeometricError());
|
|
CHECK(child.getRefine() == pRootTile->getRefine());
|
|
CHECK(child.getTransform() == pRootTile->getTransform());
|
|
CHECK(
|
|
std::get<CesiumGeometry::OctreeTileID>(child.getTileID()) ==
|
|
CesiumGeometry::OctreeTileID(0, 0, 0, 0));
|
|
}
|
|
|
|
SUBCASE("Tileset with metadata") {
|
|
auto loaderResult =
|
|
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");
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test loading individual tile of tileset json") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
SUBCASE("Load tile that has render content") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "ReplaceTileset" / "tileset.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 == "parent.b3dm");
|
|
|
|
// check tile content
|
|
auto tileLoadResult = loadTileContent(
|
|
testDataPath / "ReplaceTileset" / tileID,
|
|
*loaderResult.pLoader,
|
|
*pRootTile);
|
|
CHECK(
|
|
std::holds_alternative<CesiumGltf::Model>(tileLoadResult.contentKind));
|
|
CHECK(tileLoadResult.updatedBoundingVolume == std::nullopt);
|
|
CHECK(tileLoadResult.updatedContentBoundingVolume == std::nullopt);
|
|
CHECK(tileLoadResult.state == TileLoadResultState::Success);
|
|
CHECK(!tileLoadResult.tileInitializer);
|
|
}
|
|
|
|
SUBCASE("Load tile that has external content") {
|
|
auto loaderResult =
|
|
createTilesetJsonLoader(testDataPath / "AddTileset" / "tileset.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 == "tileset2.json");
|
|
|
|
// check tile content
|
|
auto tileLoadResult = loadTileContent(
|
|
testDataPath / "AddTileset" / tileID,
|
|
*loaderResult.pLoader,
|
|
*pRootTile);
|
|
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
|
|
pRootTile->getContent().setContentKind(
|
|
std::make_unique<TileExternalContent>(
|
|
std::get<TileExternalContent>(tileLoadResult.contentKind)));
|
|
tileLoadResult.tileInitializer(*pRootTile);
|
|
const auto& children = pRootTile->getChildren();
|
|
REQUIRE(children.size() == 1);
|
|
|
|
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()));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Load tile that has external content with implicit tiling") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "ImplicitTileset" / "tileset_1.1.json");
|
|
|
|
REQUIRE(loaderResult.pRootTile);
|
|
CHECK(loaderResult.pRootTile->isExternalContent());
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
|
|
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
|
|
REQUIRE(pRootTile->getChildren().size() == 1);
|
|
|
|
auto& implicitTile = pRootTile->getChildren().front();
|
|
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{
|
|
implicitTile,
|
|
{},
|
|
asyncSystem,
|
|
pMockAssetAccessor,
|
|
spdlog::default_logger(),
|
|
{}};
|
|
auto implicitContentResultFuture =
|
|
loaderResult.pLoader->loadTileContent(loadInput);
|
|
|
|
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{
|
|
implicitTile,
|
|
{},
|
|
asyncSystem,
|
|
pMockAssetAccessor,
|
|
spdlog::default_logger(),
|
|
{}};
|
|
auto implicitContentResultFuture =
|
|
loaderResult.pLoader->loadTileContent(loadInput);
|
|
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
auto implicitContentResult = implicitContentResultFuture.wait();
|
|
CHECK(std::holds_alternative<CesiumGltf::Model>(
|
|
implicitContentResult.contentKind));
|
|
CHECK(!implicitContentResult.updatedBoundingVolume);
|
|
CHECK(!implicitContentResult.updatedContentBoundingVolume);
|
|
CHECK(implicitContentResult.state == TileLoadResultState::Success);
|
|
CHECK(!implicitContentResult.tileInitializer);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Check that tile with legacy implicit tiling extension still works") {
|
|
auto loaderResult = createTilesetJsonLoader(
|
|
testDataPath / "ImplicitTileset" / "tileset_1.0.json");
|
|
|
|
REQUIRE(loaderResult.pRootTile);
|
|
CHECK(loaderResult.pRootTile->isExternalContent());
|
|
REQUIRE(loaderResult.pRootTile->getChildren().size() == 1);
|
|
|
|
auto pRootTile = &loaderResult.pRootTile->getChildren()[0];
|
|
REQUIRE(pRootTile->getChildren().size() == 1);
|
|
|
|
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"));
|
|
}
|
|
|
|
SUBCASE("Tile with complex structural metadata") {
|
|
auto loaderResult =
|
|
createTilesetJsonLoader(testDataPath / "ComplexTypes" / "tileset.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 == "ComplexTypes.gltf");
|
|
|
|
// 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);
|
|
|
|
ExtensionModelExtStructuralMetadata* pMetadata =
|
|
pModel->getExtension<ExtensionModelExtStructuralMetadata>();
|
|
REQUIRE(pMetadata);
|
|
REQUIRE(pMetadata->schema);
|
|
REQUIRE(pMetadata->propertyTables.size() == 1);
|
|
|
|
PropertyTable& propertyTable = pMetadata->propertyTables.front();
|
|
REQUIRE(propertyTable.properties.size() == 4);
|
|
|
|
PropertyTableView view(*pModel, propertyTable);
|
|
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
|
|
|
|
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 =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"example_variable_length_ARRAY_normalized_UINT8");
|
|
REQUIRE(
|
|
varLenUintPropertyView.status() ==
|
|
PropertyTablePropertyViewStatus::Valid);
|
|
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]);
|
|
}
|
|
}
|
|
|
|
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}};
|
|
const PropertyTablePropertyView<PropertyArrayView<bool>, false>
|
|
fixedLenBoolPropertyView =
|
|
view.getPropertyView<PropertyArrayView<bool>, false>(
|
|
"example_fixed_length_ARRAY_BOOLEAN");
|
|
REQUIRE(
|
|
fixedLenBoolPropertyView.status() ==
|
|
PropertyTablePropertyViewStatus::Valid);
|
|
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]);
|
|
}
|
|
}
|
|
|
|
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"}};
|
|
const PropertyTablePropertyView<PropertyArrayView<std::string_view>, false>
|
|
varLenStringPropertyView =
|
|
view.getPropertyView<PropertyArrayView<std::string_view>, false>(
|
|
"example_variable_length_ARRAY_STRING");
|
|
REQUIRE(
|
|
varLenStringPropertyView.status() ==
|
|
PropertyTablePropertyViewStatus::Valid);
|
|
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]);
|
|
}
|
|
}
|
|
|
|
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},
|
|
};
|
|
const PropertyTablePropertyView<PropertyArrayView<uint16_t>, false>
|
|
fixenLenEnumPropertyView =
|
|
view.getPropertyView<PropertyArrayView<uint16_t>, false>(
|
|
"example_fixed_length_ARRAY_ENUM");
|
|
REQUIRE(
|
|
fixenLenEnumPropertyView.status() ==
|
|
PropertyTablePropertyViewStatus::Valid);
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
}
|