1836 lines
68 KiB
C++
1836 lines
68 KiB
C++
#include "SimplePrepareRendererResource.h"
|
|
#include "TestTilesetJsonLoader.h"
|
|
#include "TilesetContentManager.h"
|
|
#include "TilesetJsonLoader.h"
|
|
|
|
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
|
|
#include <Cesium3DTilesSelection/RasterOverlayCollection.h>
|
|
#include <Cesium3DTilesSelection/Tile.h>
|
|
#include <Cesium3DTilesSelection/TileLoadResult.h>
|
|
#include <Cesium3DTilesSelection/TileRefine.h>
|
|
#include <Cesium3DTilesSelection/TilesetContentLoader.h>
|
|
#include <Cesium3DTilesSelection/TilesetExternals.h>
|
|
#include <Cesium3DTilesSelection/TilesetOptions.h>
|
|
#include <CesiumAsync/Future.h>
|
|
#include <CesiumAsync/IAssetAccessor.h>
|
|
#include <CesiumGeometry/Axis.h>
|
|
#include <CesiumGeometry/QuadtreeTileID.h>
|
|
#include <CesiumGeometry/Rectangle.h>
|
|
#include <CesiumGeospatial/Cartographic.h>
|
|
#include <CesiumGeospatial/Ellipsoid.h>
|
|
#include <CesiumGeospatial/GeographicProjection.h>
|
|
#include <CesiumGeospatial/GlobeRectangle.h>
|
|
#include <CesiumGeospatial/Projection.h>
|
|
#include <CesiumGltf/Accessor.h>
|
|
#include <CesiumGltf/AccessorView.h>
|
|
#include <CesiumGltf/AccessorWriter.h>
|
|
#include <CesiumGltf/Buffer.h>
|
|
#include <CesiumGltf/BufferView.h>
|
|
#include <CesiumGltf/ImageAsset.h>
|
|
#include <CesiumGltf/Mesh.h>
|
|
#include <CesiumGltf/MeshPrimitive.h>
|
|
#include <CesiumGltf/Model.h>
|
|
#include <CesiumGltf/Node.h>
|
|
#include <CesiumGltf/Scene.h>
|
|
#include <CesiumGltfReader/GltfReader.h>
|
|
#include <CesiumNativeTests/SimpleAssetAccessor.h>
|
|
#include <CesiumNativeTests/SimpleAssetRequest.h>
|
|
#include <CesiumNativeTests/SimpleAssetResponse.h>
|
|
#include <CesiumNativeTests/SimpleTaskProcessor.h>
|
|
#include <CesiumNativeTests/readFile.h>
|
|
#include <CesiumRasterOverlays/DebugColorizeTilesRasterOverlay.h>
|
|
#include <CesiumRasterOverlays/IPrepareRasterOverlayRendererResources.h>
|
|
#include <CesiumRasterOverlays/RasterOverlay.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayDetails.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayTile.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayTileProvider.h>
|
|
#include <CesiumUtility/CreditSystem.h>
|
|
#include <CesiumUtility/IntrusivePointer.h>
|
|
#include <CesiumUtility/Math.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <glm/common.hpp>
|
|
#include <glm/ext/vector_double3.hpp>
|
|
#include <glm/ext/vector_float2.hpp>
|
|
#include <glm/ext/vector_float3.hpp>
|
|
#include <glm/trigonometric.hpp>
|
|
#include <spdlog/logger.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
using namespace doctest;
|
|
using namespace Cesium3DTilesSelection;
|
|
using namespace CesiumGeospatial;
|
|
using namespace CesiumGeometry;
|
|
using namespace CesiumUtility;
|
|
using namespace CesiumNativeTests;
|
|
using namespace CesiumRasterOverlays;
|
|
|
|
namespace {
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
|
|
class SimpleTilesetContentLoader : public TilesetContentLoader {
|
|
public:
|
|
CesiumAsync::Future<TileLoadResult>
|
|
loadTileContent(const TileLoadInput& input) override {
|
|
return input.asyncSystem.createResolvedFuture(
|
|
std::move(mockLoadTileContent));
|
|
}
|
|
|
|
TileChildrenResult createTileChildren(
|
|
[[maybe_unused]] const Tile& tile,
|
|
[[maybe_unused]] const Ellipsoid& ellipsoid) override {
|
|
return std::move(mockCreateTileChildren);
|
|
}
|
|
|
|
TileLoadResult mockLoadTileContent;
|
|
TileChildrenResult mockCreateTileChildren;
|
|
};
|
|
|
|
std::shared_ptr<SimpleAssetRequest>
|
|
createMockRequest(const std::filesystem::path& path) {
|
|
auto pMockCompletedResponse = std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(path));
|
|
|
|
auto pMockCompletedRequest = std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(pMockCompletedResponse));
|
|
|
|
return pMockCompletedRequest;
|
|
}
|
|
|
|
CesiumGltf::Model createGlobeGrid(
|
|
const Cartographic& beginPoint,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
double dimension) {
|
|
const auto& ellipsoid = Ellipsoid::WGS84;
|
|
std::vector<uint32_t> indices;
|
|
glm::dvec3 min = ellipsoid.cartographicToCartesian(beginPoint);
|
|
glm::dvec3 max = min;
|
|
|
|
std::vector<glm::dvec3> positions;
|
|
indices.reserve(static_cast<size_t>(6 * (width - 1) * (height - 1)));
|
|
positions.reserve(static_cast<size_t>(width * height));
|
|
for (uint32_t y = 0; y < height; ++y) {
|
|
for (uint32_t x = 0; x < width; ++x) {
|
|
double longitude = beginPoint.longitude + x * dimension;
|
|
double latitude = beginPoint.latitude + y * dimension;
|
|
Cartographic currPoint{longitude, latitude, beginPoint.height};
|
|
positions.emplace_back(ellipsoid.cartographicToCartesian(currPoint));
|
|
min = glm::min(positions.back(), min);
|
|
max = glm::max(positions.back(), max);
|
|
if (y != height - 1 && x != width - 1) {
|
|
indices.emplace_back(y * width + x);
|
|
indices.emplace_back(y * width + x + 1);
|
|
indices.emplace_back((y + 1) * width + x);
|
|
|
|
indices.emplace_back(y * width + x + 1);
|
|
indices.emplace_back((y + 1) * width + x + 1);
|
|
indices.emplace_back((y + 1) * width + x);
|
|
}
|
|
}
|
|
}
|
|
|
|
glm::dvec3 center = (min + max) / 2.0;
|
|
std::vector<glm::vec3> relToCenterPositions;
|
|
relToCenterPositions.reserve(positions.size());
|
|
for (const auto& position : positions) {
|
|
relToCenterPositions.emplace_back(
|
|
static_cast<glm::vec3>(position - center));
|
|
}
|
|
|
|
CesiumGltf::Model model;
|
|
|
|
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
|
CesiumGltf::MeshPrimitive& meshPrimitive = mesh.primitives.emplace_back();
|
|
|
|
{
|
|
CesiumGltf::Buffer& positionBuffer = model.buffers.emplace_back();
|
|
positionBuffer.byteLength =
|
|
static_cast<int64_t>(relToCenterPositions.size() * sizeof(glm::vec3));
|
|
positionBuffer.cesium.data.resize(
|
|
static_cast<size_t>(positionBuffer.byteLength));
|
|
std::memcpy(
|
|
positionBuffer.cesium.data.data(),
|
|
relToCenterPositions.data(),
|
|
static_cast<size_t>(positionBuffer.byteLength));
|
|
|
|
CesiumGltf::BufferView& positionBufferView =
|
|
model.bufferViews.emplace_back();
|
|
positionBufferView.buffer = int32_t(model.buffers.size() - 1);
|
|
positionBufferView.byteOffset = 0;
|
|
positionBufferView.byteLength = positionBuffer.byteLength;
|
|
positionBufferView.target = CesiumGltf::BufferView::Target::ARRAY_BUFFER;
|
|
|
|
CesiumGltf::Accessor& positionAccessor = model.accessors.emplace_back();
|
|
positionAccessor.bufferView = int32_t(model.bufferViews.size() - 1);
|
|
positionAccessor.byteOffset = 0;
|
|
positionAccessor.componentType = CesiumGltf::Accessor::ComponentType::FLOAT;
|
|
positionAccessor.count = int64_t(relToCenterPositions.size());
|
|
positionAccessor.type = CesiumGltf::Accessor::Type::VEC3;
|
|
|
|
meshPrimitive.attributes["POSITION"] = int32_t(model.accessors.size() - 1);
|
|
}
|
|
|
|
{
|
|
CesiumGltf::Buffer& indicesBuffer = model.buffers.emplace_back();
|
|
indicesBuffer.byteLength =
|
|
static_cast<int64_t>(indices.size() * sizeof(uint32_t));
|
|
indicesBuffer.cesium.data.resize(
|
|
static_cast<size_t>(indicesBuffer.byteLength));
|
|
std::memcpy(
|
|
indicesBuffer.cesium.data.data(),
|
|
indices.data(),
|
|
static_cast<size_t>(indicesBuffer.byteLength));
|
|
|
|
CesiumGltf::BufferView& indicesBufferView =
|
|
model.bufferViews.emplace_back();
|
|
indicesBufferView.buffer = int32_t(model.buffers.size() - 1);
|
|
indicesBufferView.byteOffset = 0;
|
|
indicesBufferView.byteLength = indicesBuffer.byteLength;
|
|
indicesBufferView.target = CesiumGltf::BufferView::Target::ARRAY_BUFFER;
|
|
|
|
CesiumGltf::Accessor& indicesAccessor = model.accessors.emplace_back();
|
|
indicesAccessor.bufferView = int32_t(model.bufferViews.size() - 1);
|
|
indicesAccessor.byteOffset = 0;
|
|
indicesAccessor.componentType =
|
|
CesiumGltf::Accessor::ComponentType::UNSIGNED_INT;
|
|
indicesAccessor.count = int64_t(indices.size());
|
|
indicesAccessor.type = CesiumGltf::Accessor::Type::SCALAR;
|
|
|
|
meshPrimitive.indices = int32_t(model.accessors.size() - 1);
|
|
}
|
|
|
|
CesiumGltf::Node& node = model.nodes.emplace_back();
|
|
node.translation = {center.x, center.y, center.z};
|
|
node.mesh = int32_t(model.meshes.size() - 1);
|
|
|
|
CesiumGltf::Scene& scene = model.scenes.emplace_back();
|
|
scene.nodes.emplace_back(int32_t(model.nodes.size() - 1));
|
|
|
|
return model;
|
|
}
|
|
|
|
// Creates a model with two triangles in opposite corners of the given
|
|
// rectangle. The triangles extend slightly into the other two quadrants.
|
|
CesiumGltf::Model createSparseMesh(const GlobeRectangle& rectangle) {
|
|
const auto& ellipsoid = Ellipsoid::WGS84;
|
|
|
|
double width = rectangle.computeWidth();
|
|
double height = rectangle.computeHeight();
|
|
|
|
// First triangle in southwest corner
|
|
glm::dvec3 t0p0 = ellipsoid.cartographicToCartesian(rectangle.getSouthwest());
|
|
glm::dvec3 t0p1 = ellipsoid.cartographicToCartesian(Cartographic(
|
|
rectangle.getWest() + width * 0.55,
|
|
rectangle.getSouth() + height * 0.1,
|
|
0.0));
|
|
glm::dvec3 t0p2 = ellipsoid.cartographicToCartesian(Cartographic(
|
|
rectangle.getWest(),
|
|
rectangle.getSouth() + height * 0.2,
|
|
0.0));
|
|
|
|
// Second triangle in northeast corner
|
|
glm::dvec3 t1p0 = ellipsoid.cartographicToCartesian(rectangle.getNortheast());
|
|
glm::dvec3 t1p1 = ellipsoid.cartographicToCartesian(Cartographic(
|
|
rectangle.getEast() - width * 0.55,
|
|
rectangle.getNorth() - height * 0.1,
|
|
0.0));
|
|
glm::dvec3 t1p2 = ellipsoid.cartographicToCartesian(Cartographic(
|
|
rectangle.getEast(),
|
|
rectangle.getNorth() - height * 0.2,
|
|
0.0));
|
|
|
|
std::vector<glm::dvec3> positions{t0p0, t0p1, t0p2, t1p0, t1p1, t1p2};
|
|
glm::dvec3 center = (t0p0 + t1p0) * 0.5;
|
|
|
|
CesiumGltf::Model model;
|
|
model.asset.version = "2.0";
|
|
|
|
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
|
CesiumGltf::MeshPrimitive& meshPrimitive = mesh.primitives.emplace_back();
|
|
|
|
{
|
|
CesiumGltf::Buffer& positionBuffer = model.buffers.emplace_back();
|
|
positionBuffer.byteLength =
|
|
static_cast<int64_t>(positions.size() * sizeof(glm::vec3));
|
|
positionBuffer.cesium.data.resize(
|
|
static_cast<size_t>(positionBuffer.byteLength));
|
|
|
|
CesiumGltf::BufferView& positionBufferView =
|
|
model.bufferViews.emplace_back();
|
|
positionBufferView.buffer = int32_t(model.buffers.size() - 1);
|
|
positionBufferView.byteOffset = 0;
|
|
positionBufferView.byteLength = positionBuffer.byteLength;
|
|
positionBufferView.target = CesiumGltf::BufferView::Target::ARRAY_BUFFER;
|
|
|
|
CesiumGltf::Accessor& positionAccessor = model.accessors.emplace_back();
|
|
positionAccessor.bufferView = int32_t(model.bufferViews.size() - 1);
|
|
positionAccessor.byteOffset = 0;
|
|
positionAccessor.componentType = CesiumGltf::Accessor::ComponentType::FLOAT;
|
|
positionAccessor.count = int64_t(positions.size());
|
|
positionAccessor.type = CesiumGltf::Accessor::Type::VEC3;
|
|
|
|
CesiumGltf::AccessorWriter<glm::vec3> writer(model, positionAccessor);
|
|
CHECK(writer.size() == int64_t(positions.size()));
|
|
|
|
for (size_t i = 0; i < positions.size(); ++i) {
|
|
writer[int64_t(i)] = glm::vec3(positions[i] - center);
|
|
}
|
|
|
|
meshPrimitive.attributes["POSITION"] = int32_t(model.accessors.size() - 1);
|
|
}
|
|
|
|
{
|
|
CesiumGltf::Buffer& indicesBuffer = model.buffers.emplace_back();
|
|
indicesBuffer.byteLength = static_cast<int64_t>(6 * sizeof(uint8_t));
|
|
indicesBuffer.cesium.data.resize(
|
|
static_cast<size_t>(indicesBuffer.byteLength));
|
|
|
|
CesiumGltf::BufferView& indicesBufferView =
|
|
model.bufferViews.emplace_back();
|
|
indicesBufferView.buffer = int32_t(model.buffers.size() - 1);
|
|
indicesBufferView.byteOffset = 0;
|
|
indicesBufferView.byteLength = indicesBuffer.byteLength;
|
|
indicesBufferView.target =
|
|
CesiumGltf::BufferView::Target::ELEMENT_ARRAY_BUFFER;
|
|
|
|
CesiumGltf::Accessor& indicesAccessor = model.accessors.emplace_back();
|
|
indicesAccessor.bufferView = int32_t(model.bufferViews.size() - 1);
|
|
indicesAccessor.byteOffset = 0;
|
|
indicesAccessor.componentType =
|
|
CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE;
|
|
indicesAccessor.count = 6;
|
|
indicesAccessor.type = CesiumGltf::Accessor::Type::SCALAR;
|
|
|
|
CesiumGltf::AccessorWriter<uint8_t> writer(model, indicesAccessor);
|
|
CHECK(writer.size() == 6);
|
|
|
|
for (int64_t i = 0; i < writer.size(); ++i) {
|
|
writer[i] = uint8_t(i);
|
|
}
|
|
|
|
meshPrimitive.indices = int32_t(model.accessors.size() - 1);
|
|
}
|
|
|
|
CesiumGltf::Node& node = model.nodes.emplace_back();
|
|
node.translation = {center.x, center.y, center.z};
|
|
node.mesh = int32_t(model.meshes.size() - 1);
|
|
|
|
CesiumGltf::Scene& scene = model.scenes.emplace_back();
|
|
scene.nodes.emplace_back(int32_t(model.nodes.size() - 1));
|
|
|
|
return model;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Test the manager can be initialized with correct loaders") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
// create mock tileset externals
|
|
auto pMockedAssetAccessor = std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>{});
|
|
auto pMockedPrepareRendererResources =
|
|
std::make_shared<SimplePrepareRendererResource>();
|
|
CesiumAsync::AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
|
|
auto pMockedCreditSystem = std::make_shared<CreditSystem>();
|
|
|
|
TilesetExternals externals{
|
|
pMockedAssetAccessor,
|
|
pMockedPrepareRendererResources,
|
|
asyncSystem,
|
|
pMockedCreditSystem};
|
|
|
|
SUBCASE("Initialize manager with tileset.json url") {
|
|
// create mock request
|
|
pMockedAssetAccessor->mockCompletedRequests.insert(
|
|
{"tileset.json",
|
|
createMockRequest(testDataPath / "Tileset" / "tileset.json")});
|
|
|
|
// construct manager with tileset.json format
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager(externals, {}, "tileset.json");
|
|
TilesetContentManager& manager = *pManager;
|
|
CHECK(manager.getNumberOfTilesLoading() == 1);
|
|
|
|
manager.waitUntilIdle();
|
|
CHECK(manager.getNumberOfTilesLoading() == 0);
|
|
CHECK(manager.getNumberOfTilesLoaded() == 1);
|
|
|
|
// check root
|
|
const Tile* pTilesetJson = manager.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
const Tile* pRootTile = &pTilesetJson->getChildren()[0];
|
|
CHECK(std::get<std::string>(pRootTile->getTileID()) == "parent.b3dm");
|
|
CHECK(pRootTile->getGeometricError() == 70.0);
|
|
CHECK(pRootTile->getRefine() == TileRefine::Add);
|
|
}
|
|
|
|
SUBCASE("Initialize manager with layer.json url") {
|
|
// create mock request
|
|
pMockedAssetAccessor->mockCompletedRequests.insert(
|
|
{"layer.json",
|
|
createMockRequest(
|
|
testDataPath / "CesiumTerrainTileJson" /
|
|
"QuantizedMesh.tile.json")});
|
|
|
|
// construct manager with tileset.json format
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager(externals, {}, "layer.json");
|
|
TilesetContentManager& manager = *pManager;
|
|
CHECK(manager.getNumberOfTilesLoading() == 1);
|
|
|
|
manager.waitUntilIdle();
|
|
CHECK(manager.getNumberOfTilesLoading() == 0);
|
|
CHECK(manager.getNumberOfTilesLoaded() == 1);
|
|
|
|
// check root
|
|
const Tile* pRootTile = manager.getRootTile();
|
|
CHECK(pRootTile);
|
|
CHECK(pRootTile->getRefine() == TileRefine::Replace);
|
|
|
|
const std::span<const Tile> children = pRootTile->getChildren();
|
|
CHECK(
|
|
std::get<QuadtreeTileID>(children[0].getTileID()) ==
|
|
QuadtreeTileID(0, 0, 0));
|
|
CHECK(
|
|
std::get<QuadtreeTileID>(children[1].getTileID()) ==
|
|
QuadtreeTileID(0, 1, 0));
|
|
}
|
|
|
|
SUBCASE("Initialize manager with wrong format") {
|
|
pMockedAssetAccessor->mockCompletedRequests.insert(
|
|
{"layer.json",
|
|
createMockRequest(
|
|
testDataPath / "CesiumTerrainTileJson" /
|
|
"WithAttribution.tile.json")});
|
|
|
|
// construct manager with tileset.json format
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager(externals, {}, "layer.json");
|
|
TilesetContentManager& manager = *pManager;
|
|
CHECK(manager.getNumberOfTilesLoading() == 1);
|
|
|
|
manager.waitUntilIdle();
|
|
CHECK(manager.getNumberOfTilesLoading() == 0);
|
|
CHECK(manager.getNumberOfTilesLoaded() == 1);
|
|
|
|
// check root
|
|
const Tile* pRootTile = manager.getRootTile();
|
|
CHECK(!pRootTile);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test tile state machine") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
// create mock tileset externals
|
|
auto pMockedAssetAccessor = std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>{});
|
|
auto pMockedPrepareRendererResources =
|
|
std::make_shared<SimplePrepareRendererResource>();
|
|
CesiumAsync::AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
|
|
auto pMockedCreditSystem = std::make_shared<CreditSystem>();
|
|
|
|
TilesetExternals externals{
|
|
pMockedAssetAccessor,
|
|
pMockedPrepareRendererResources,
|
|
asyncSystem,
|
|
pMockedCreditSystem};
|
|
|
|
SUBCASE("Load content successfully") {
|
|
// create mock loader
|
|
bool initializerCall = false;
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) { initializerCall = true; },
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Success};
|
|
pMockedLoader->mockCreateTileChildren.children.emplace_back(
|
|
pMockedLoader.get(),
|
|
TileID(),
|
|
TileEmptyContent());
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
TilesetOptions options{};
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// test manager loading
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, options);
|
|
|
|
SUBCASE("Load tile from ContentLoading -> Done") {
|
|
// Unloaded -> ContentLoading
|
|
// check the state of the tile before main thread get called
|
|
CHECK(pManager->getNumberOfTilesLoading() == 1);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().isExternalContent());
|
|
CHECK(!tile.getContent().isEmptyContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
CHECK(!initializerCall);
|
|
|
|
// ContentLoading -> ContentLoaded
|
|
// check the state of the tile after main thread get called
|
|
pManager->waitUntilIdle();
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
CHECK(tile.getContent().isRenderContent());
|
|
CHECK(tile.getContent().getRenderContent()->getRenderResources());
|
|
CHECK(initializerCall);
|
|
|
|
// ContentLoaded -> Done
|
|
// update tile content to move from ContentLoaded -> Done
|
|
pManager->updateTileContent(tile, options);
|
|
CHECK(tile.getState() == TileLoadState::Done);
|
|
CHECK(tile.getChildren().size() == 1);
|
|
CHECK(tile.getChildren().front().getContent().isEmptyContent());
|
|
CHECK(tile.getContent().isRenderContent());
|
|
CHECK(tile.getContent().getRenderContent()->getRenderResources());
|
|
CHECK(initializerCall);
|
|
|
|
// Done -> Unloaded
|
|
pManager->unloadTileContent(tile);
|
|
CHECK(tile.getState() == TileLoadState::Unloaded);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
}
|
|
|
|
SUBCASE("Try to unload tile when it's still loading") {
|
|
// unload tile to move from Done -> Unload
|
|
pManager->unloadTileContent(tile);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 1);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().isExternalContent());
|
|
CHECK(!tile.getContent().isEmptyContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
|
|
pManager->waitUntilIdle();
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
CHECK(tile.getContent().isRenderContent());
|
|
CHECK(tile.getContent().getRenderContent()->getRenderResources());
|
|
|
|
pManager->unloadTileContent(tile);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getState() == TileLoadState::Unloaded);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
}
|
|
}
|
|
|
|
SUBCASE("Loader requests retry later") {
|
|
// create mock loader
|
|
bool initializerCall = false;
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) { initializerCall = true; },
|
|
TileLoadResultState::RetryLater,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Success};
|
|
pMockedLoader->mockCreateTileChildren.children.emplace_back(
|
|
pMockedLoader.get(),
|
|
TileID(),
|
|
TileEmptyContent());
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
TilesetOptions options{};
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// test manager loading
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, options);
|
|
|
|
// Unloaded -> ContentLoading
|
|
CHECK(pManager->getNumberOfTilesLoading() == 1);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
CHECK(tile.getChildren().empty());
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
|
|
// ContentLoading -> FailedTemporarily
|
|
pManager->waitUntilIdle();
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getChildren().empty());
|
|
CHECK(tile.getState() == TileLoadState::FailedTemporarily);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
CHECK(!initializerCall);
|
|
|
|
// FailedTemporarily -> FailedTemporarily
|
|
// tile is failed temporarily but the loader can still add children to it
|
|
pManager->updateTileContent(tile, options);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getChildren().size() == 1);
|
|
CHECK(tile.getChildren().front().isEmptyContent());
|
|
CHECK(tile.getState() == TileLoadState::FailedTemporarily);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
CHECK(!initializerCall);
|
|
|
|
// FailedTemporarily -> ContentLoading
|
|
pManager->loadTileContent(tile, options);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 1);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
}
|
|
|
|
SUBCASE("Loader requests failed") {
|
|
// create mock loader
|
|
bool initializerCall = false;
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) { initializerCall = true; },
|
|
TileLoadResultState::Failed,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Success};
|
|
pMockedLoader->mockCreateTileChildren.children.emplace_back(
|
|
pMockedLoader.get(),
|
|
TileID(),
|
|
TileEmptyContent());
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
TilesetOptions options{};
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// test manager loading
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, options);
|
|
|
|
// Unloaded -> ContentLoading
|
|
CHECK(pManager->getNumberOfTilesLoading() == 1);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
CHECK(tile.getChildren().empty());
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
|
|
// ContentLoading -> Failed
|
|
pManager->waitUntilIdle();
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getChildren().empty());
|
|
CHECK(tile.getState() == TileLoadState::Failed);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
CHECK(!initializerCall);
|
|
|
|
// Failed -> Failed
|
|
// tile is failed but the loader can still add children to it
|
|
pManager->updateTileContent(tile, options);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getChildren().size() == 1);
|
|
CHECK(tile.getChildren().front().isEmptyContent());
|
|
CHECK(tile.getState() == TileLoadState::Failed);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
CHECK(!initializerCall);
|
|
|
|
// cannot transition from Failed -> ContentLoading
|
|
pManager->loadTileContent(tile, options);
|
|
CHECK(pManager->getNumberOfTilesLoading() == 0);
|
|
CHECK(tile.getState() == TileLoadState::Failed);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().isExternalContent());
|
|
CHECK(!tile.getContent().isEmptyContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
|
|
// Failed -> Unloaded
|
|
pManager->unloadTileContent(tile);
|
|
CHECK(tile.getState() == TileLoadState::Unloaded);
|
|
CHECK(tile.getContent().isUnknownContent());
|
|
CHECK(!tile.getContent().isRenderContent());
|
|
CHECK(!tile.getContent().isExternalContent());
|
|
CHECK(!tile.getContent().isEmptyContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
}
|
|
|
|
SUBCASE("Make sure the manager loads parent first before loading upsampled "
|
|
"child") {
|
|
// create mock loader
|
|
bool initializerCall = false;
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
auto pMockedLoaderRaw = pMockedLoader.get();
|
|
|
|
pMockedLoader->mockLoadTileContent = {
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) { initializerCall = true; },
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoaderRaw);
|
|
pRootTile->setTileID(QuadtreeTileID(0, 0, 0));
|
|
|
|
// create upsampled children. We put it in the scope since upsampledTile
|
|
// will be moved to the parent afterward, and we don't want the tests below
|
|
// to use it accidentally
|
|
{
|
|
std::vector<Tile> children;
|
|
children.emplace_back(pMockedLoaderRaw);
|
|
Tile& upsampledTile = children.back();
|
|
upsampledTile.setTileID(UpsampledQuadtreeNode{QuadtreeTileID(1, 1, 1)});
|
|
pRootTile->createChildTiles(std::move(children));
|
|
}
|
|
|
|
// create manager
|
|
TilesetOptions options{};
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
Tile& tile = *pManager->getRootTile();
|
|
Tile& upsampledTile = tile.getChildren().back();
|
|
|
|
// test manager loading upsample tile
|
|
pManager->loadTileContent(upsampledTile, options);
|
|
|
|
// since parent is not yet loaded, it will load the parent first.
|
|
// The upsampled tile will not be loaded at the moment
|
|
CHECK(upsampledTile.getState() == TileLoadState::Unloaded);
|
|
CHECK(tile.getState() == TileLoadState::ContentLoading);
|
|
|
|
// parent moves from ContentLoading -> ContentLoaded
|
|
pManager->waitUntilIdle();
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
CHECK(tile.isRenderContent());
|
|
CHECK(initializerCall);
|
|
|
|
// try again with upsample tile, but still not able to load it
|
|
// because parent is not done yet
|
|
pManager->loadTileContent(upsampledTile, options);
|
|
CHECK(upsampledTile.getState() == TileLoadState::Unloaded);
|
|
|
|
// parent moves from ContentLoaded -> Done
|
|
pManager->updateTileContent(tile, options);
|
|
CHECK(tile.getState() == TileLoadState::Done);
|
|
CHECK(tile.getChildren().size() == 1);
|
|
CHECK(&tile.getChildren().back() == &upsampledTile);
|
|
CHECK(tile.isRenderContent());
|
|
CHECK(initializerCall);
|
|
|
|
// load the upsampled tile again: Unloaded -> ContentLoading
|
|
initializerCall = false;
|
|
pMockedLoaderRaw->mockLoadTileContent = {
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) { initializerCall = true; },
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoaderRaw->mockCreateTileChildren = {
|
|
{},
|
|
TileLoadResultState::Failed};
|
|
pManager->loadTileContent(upsampledTile, options);
|
|
CHECK(upsampledTile.getState() == TileLoadState::ContentLoading);
|
|
|
|
// trying to unload parent while upsampled children is loading while put the
|
|
// tile into the Unloading state but not unload the render content.
|
|
CHECK(pManager->unloadTileContent(tile) == UnloadTileContentResult::Keep);
|
|
CHECK(tile.getState() == TileLoadState::Unloading);
|
|
CHECK(tile.isRenderContent());
|
|
|
|
// Unloading again will have the same result.
|
|
CHECK(pManager->unloadTileContent(tile) == UnloadTileContentResult::Keep);
|
|
CHECK(tile.getState() == TileLoadState::Unloading);
|
|
CHECK(tile.isRenderContent());
|
|
|
|
// Attempting to load won't do anything - unloading must finish first.
|
|
pManager->loadTileContent(tile, options);
|
|
CHECK(tile.getState() == TileLoadState::Unloading);
|
|
|
|
// upsampled tile: ContentLoading -> ContentLoaded
|
|
pManager->waitUntilIdle();
|
|
CHECK(upsampledTile.getState() == TileLoadState::ContentLoaded);
|
|
CHECK(upsampledTile.isRenderContent());
|
|
|
|
// trying to unload parent will work now since the upsampled tile is already
|
|
// in the main thread
|
|
CHECK(pManager->unloadTileContent(tile) == UnloadTileContentResult::Remove);
|
|
CHECK(tile.getState() == TileLoadState::Unloaded);
|
|
CHECK(!tile.isRenderContent());
|
|
CHECK(!tile.getContent().getRenderContent());
|
|
|
|
// unload upsampled tile: ContentLoaded -> Done
|
|
CHECK(
|
|
pManager->unloadTileContent(upsampledTile) ==
|
|
UnloadTileContentResult::Remove);
|
|
CHECK(upsampledTile.getState() == TileLoadState::Unloaded);
|
|
CHECK(!upsampledTile.isRenderContent());
|
|
CHECK(!upsampledTile.getContent().getRenderContent());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test the tileset content manager's post processing for gltf") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
// create mock tileset externals
|
|
auto pMockedAssetAccessor = std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>{});
|
|
auto pMockedPrepareRendererResources =
|
|
std::make_shared<SimplePrepareRendererResource>();
|
|
CesiumAsync::AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
|
|
auto pMockedCreditSystem = std::make_shared<CreditSystem>();
|
|
|
|
TilesetExternals externals{
|
|
pMockedAssetAccessor,
|
|
pMockedPrepareRendererResources,
|
|
asyncSystem,
|
|
pMockedCreditSystem};
|
|
|
|
SUBCASE("Resolve external buffers") {
|
|
// create mock loader
|
|
CesiumGltfReader::GltfReader gltfReader;
|
|
std::vector<std::byte> gltfBoxFile =
|
|
readFile(testDataPath / "gltf" / "box" / "Box.gltf");
|
|
auto modelReadResult = gltfReader.readGltf(gltfBoxFile);
|
|
|
|
// check that this model has external buffer and it's not loaded
|
|
{
|
|
CHECK(modelReadResult.errors.empty());
|
|
CHECK(modelReadResult.warnings.empty());
|
|
CHECK(modelReadResult.model);
|
|
const auto& buffers = modelReadResult.model->buffers;
|
|
CHECK(buffers.size() == 1);
|
|
|
|
const auto& buffer = buffers.front();
|
|
CHECK(buffer.uri == "Box0.bin");
|
|
CHECK(buffer.byteLength == 648);
|
|
CHECK(buffer.cesium.data.size() == 0);
|
|
}
|
|
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
std::move(*modelReadResult.model),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
pMockedAssetAccessor,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// add external buffer to the completed request
|
|
pMockedAssetAccessor->mockCompletedRequests.insert(
|
|
{"Box0.bin",
|
|
createMockRequest(testDataPath / "gltf" / "box" / "Box0.bin")});
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
{},
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// test the gltf model
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
// check the buffer is already loaded
|
|
{
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
CHECK(tile.isRenderContent());
|
|
|
|
const auto& renderContent = tile.getContent().getRenderContent();
|
|
const auto& buffers = renderContent->getModel().buffers;
|
|
CHECK(buffers.size() == 1);
|
|
|
|
const auto& buffer = buffers.front();
|
|
CHECK(buffer.uri == std::nullopt);
|
|
CHECK(buffer.cesium.data.size() == 648);
|
|
}
|
|
|
|
// unload the tile content
|
|
pManager->unloadTileContent(tile);
|
|
}
|
|
|
|
SUBCASE("Ensure the loader generate smooth normal when the mesh doesn't have "
|
|
"normal") {
|
|
CesiumGltfReader::GltfReader gltfReader;
|
|
std::vector<std::byte> gltfBoxFile =
|
|
readFile(testDataPath / "gltf" / "embedded_box" / "Box.glb");
|
|
auto modelReadResult = gltfReader.readGltf(gltfBoxFile);
|
|
CHECK(modelReadResult.errors.empty());
|
|
CHECK(modelReadResult.model);
|
|
|
|
// retrieve expected accessor index and remove normal attribute
|
|
CesiumGltf::Mesh& prevMesh = modelReadResult.model->meshes.front();
|
|
CesiumGltf::MeshPrimitive& prevPrimitive = prevMesh.primitives.front();
|
|
int32_t expectedAccessor = prevPrimitive.attributes.at("NORMAL");
|
|
prevPrimitive.attributes.erase("NORMAL");
|
|
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
std::move(*modelReadResult.model),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
TilesetOptions options;
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// test the gltf model
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, options);
|
|
pManager->waitUntilIdle();
|
|
|
|
// check that normal is generated
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
const auto& renderContent = tile.getContent().getRenderContent();
|
|
CHECK(renderContent->getModel().meshes.size() == 1);
|
|
const CesiumGltf::Mesh& mesh = renderContent->getModel().meshes.front();
|
|
CHECK(mesh.primitives.size() == 1);
|
|
const CesiumGltf::MeshPrimitive& primitive = mesh.primitives.front();
|
|
CHECK(primitive.attributes.find("NORMAL") != primitive.attributes.end());
|
|
|
|
CesiumGltf::AccessorView<glm::vec3> normalView{
|
|
renderContent->getModel(),
|
|
primitive.attributes.at("NORMAL")};
|
|
CHECK(normalView.size() == 8);
|
|
|
|
CesiumGltf::AccessorView<glm::vec3> expectedNormalView{
|
|
renderContent->getModel(),
|
|
expectedAccessor};
|
|
CHECK(expectedNormalView.size() == 8);
|
|
|
|
for (int64_t i = 0; i < expectedNormalView.size(); ++i) {
|
|
const glm::vec3& expectedNorm = expectedNormalView[i];
|
|
const glm::vec3& norm = normalView[i];
|
|
CHECK(expectedNorm.x == Approx(norm.x).epsilon(1e-4));
|
|
CHECK(expectedNorm.y == Approx(norm.y).epsilon(1e-4));
|
|
CHECK(expectedNorm.z == Approx(norm.z).epsilon(1e-4));
|
|
}
|
|
|
|
// unload tile
|
|
pManager->unloadTileContent(tile);
|
|
}
|
|
|
|
SUBCASE("Embed gltf up axis to extra") {
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
CesiumGltf::Model{},
|
|
CesiumGeometry::Axis::Z,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
{},
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
const auto& renderContent = tile.getContent().getRenderContent();
|
|
CHECK(renderContent);
|
|
|
|
auto gltfUpAxisIt = renderContent->getModel().extras.find("gltfUpAxis");
|
|
CHECK(gltfUpAxisIt != renderContent->getModel().extras.end());
|
|
CHECK(gltfUpAxisIt->second.getInt64() == 2);
|
|
|
|
pManager->unloadTileContent(tile);
|
|
}
|
|
|
|
SUBCASE("Generate raster overlay projections") {
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
Cartographic beginCarto{glm::radians(32.0), glm::radians(48.0), 100.0};
|
|
pMockedLoader->mockLoadTileContent = {
|
|
createGlobeGrid(beginCarto, 10, 10, 0.01),
|
|
CesiumGeometry::Axis::Z,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
{},
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// add raster overlay
|
|
pManager->getRasterOverlayCollection().add(
|
|
new DebugColorizeTilesRasterOverlay("DebugOverlay"));
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
SUBCASE(
|
|
"Generate raster overlay details when tile doesn't have loose region") {
|
|
// test the gltf model
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
const TileContent& tileContent = tile.getContent();
|
|
CHECK(tileContent.isRenderContent());
|
|
const RasterOverlayDetails& rasterOverlayDetails =
|
|
tileContent.getRenderContent()->getRasterOverlayDetails();
|
|
|
|
// ensure the raster overlay details has geographic projection
|
|
GeographicProjection geographicProjection{Ellipsoid::WGS84};
|
|
auto existingProjectionIt = std::find(
|
|
rasterOverlayDetails.rasterOverlayProjections.begin(),
|
|
rasterOverlayDetails.rasterOverlayProjections.end(),
|
|
Projection{geographicProjection});
|
|
CHECK(
|
|
existingProjectionIt !=
|
|
rasterOverlayDetails.rasterOverlayProjections.end());
|
|
|
|
// check the rectangle
|
|
const auto& projectionRectangle =
|
|
rasterOverlayDetails.rasterOverlayRectangles.front();
|
|
auto globeRectangle = geographicProjection.unproject(projectionRectangle);
|
|
CHECK(globeRectangle.getWest() == Approx(beginCarto.longitude));
|
|
CHECK(globeRectangle.getSouth() == Approx(beginCarto.latitude));
|
|
CHECK(
|
|
globeRectangle.getEast() == Approx(beginCarto.longitude + 9 * 0.01));
|
|
CHECK(
|
|
globeRectangle.getNorth() == Approx(beginCarto.latitude + 9 * 0.01));
|
|
|
|
// check the UVs
|
|
const auto& renderContent = tileContent.getRenderContent();
|
|
const auto& mesh = renderContent->getModel().meshes.front();
|
|
const auto& meshPrimitive = mesh.primitives.front();
|
|
CesiumGltf::AccessorView<glm::vec2> uv{
|
|
renderContent->getModel(),
|
|
meshPrimitive.attributes.at("_CESIUMOVERLAY_0")};
|
|
CHECK(uv.status() == CesiumGltf::AccessorViewStatus::Valid);
|
|
int64_t uvIdx = 0;
|
|
for (int y = 0; y < 10; ++y) {
|
|
for (int x = 0; x < 10; ++x) {
|
|
CHECK(CesiumUtility::Math::equalsEpsilon(
|
|
uv[uvIdx].x,
|
|
x * 0.01 / globeRectangle.computeWidth(),
|
|
CesiumUtility::Math::Epsilon7));
|
|
CHECK(CesiumUtility::Math::equalsEpsilon(
|
|
uv[uvIdx].y,
|
|
y * 0.01 / globeRectangle.computeHeight(),
|
|
CesiumUtility::Math::Epsilon7));
|
|
++uvIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Generate raster overlay details when tile has loose region") {
|
|
Tile& tile = *pManager->getRootTile();
|
|
auto originalLooseRegion =
|
|
BoundingRegionWithLooseFittingHeights{BoundingRegion{
|
|
GeographicProjection::MAXIMUM_GLOBE_RECTANGLE,
|
|
-1000.0,
|
|
9000.0,
|
|
Ellipsoid::WGS84}};
|
|
tile.setBoundingVolume(originalLooseRegion);
|
|
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
const TileContent& tileContent = tile.getContent();
|
|
CHECK(tileContent.isRenderContent());
|
|
const RasterOverlayDetails& rasterOverlayDetails =
|
|
tileContent.getRenderContent()->getRasterOverlayDetails();
|
|
|
|
// ensure the raster overlay details has geographic projection
|
|
GeographicProjection geographicProjection{Ellipsoid::WGS84};
|
|
auto existingProjectionIt = std::find(
|
|
rasterOverlayDetails.rasterOverlayProjections.begin(),
|
|
rasterOverlayDetails.rasterOverlayProjections.end(),
|
|
Projection{geographicProjection});
|
|
CHECK(
|
|
existingProjectionIt !=
|
|
rasterOverlayDetails.rasterOverlayProjections.end());
|
|
|
|
// check the rectangle
|
|
const auto& projectionRectangle =
|
|
rasterOverlayDetails.rasterOverlayRectangles.front();
|
|
auto globeRectangle = geographicProjection.unproject(projectionRectangle);
|
|
CHECK(globeRectangle.getWest() == Approx(-CesiumUtility::Math::OnePi));
|
|
CHECK(
|
|
globeRectangle.getSouth() == Approx(-CesiumUtility::Math::PiOverTwo));
|
|
CHECK(globeRectangle.getEast() == Approx(CesiumUtility::Math::OnePi));
|
|
CHECK(
|
|
globeRectangle.getNorth() == Approx(CesiumUtility::Math::PiOverTwo));
|
|
|
|
// check the tile whole region which will be more fitted
|
|
const BoundingRegion& tileRegion =
|
|
std::get<BoundingRegion>(tile.getBoundingVolume());
|
|
CHECK(
|
|
tileRegion.getRectangle().getWest() == Approx(beginCarto.longitude));
|
|
CHECK(
|
|
tileRegion.getRectangle().getSouth() == Approx(beginCarto.latitude));
|
|
CHECK(
|
|
tileRegion.getRectangle().getEast() ==
|
|
Approx(beginCarto.longitude + 9 * 0.01));
|
|
CHECK(
|
|
tileRegion.getRectangle().getNorth() ==
|
|
Approx(beginCarto.latitude + 9 * 0.01));
|
|
|
|
// check the UVs
|
|
const auto& renderContent = tileContent.getRenderContent();
|
|
const auto& mesh = renderContent->getModel().meshes.front();
|
|
const auto& meshPrimitive = mesh.primitives.front();
|
|
CesiumGltf::AccessorView<glm::vec2> uv{
|
|
renderContent->getModel(),
|
|
meshPrimitive.attributes.at("_CESIUMOVERLAY_0")};
|
|
CHECK(uv.status() == CesiumGltf::AccessorViewStatus::Valid);
|
|
|
|
const auto& looseRectangle =
|
|
originalLooseRegion.getBoundingRegion().getRectangle();
|
|
int64_t uvIdx = 0;
|
|
for (int y = 0; y < 10; ++y) {
|
|
for (int x = 0; x < 10; ++x) {
|
|
double expectedX = (beginCarto.longitude + x * 0.01 -
|
|
(-CesiumUtility::Math::OnePi)) /
|
|
looseRectangle.computeWidth();
|
|
|
|
double expectedY = (beginCarto.latitude + y * 0.01 -
|
|
(-CesiumUtility::Math::PiOverTwo)) /
|
|
looseRectangle.computeHeight();
|
|
|
|
CHECK(CesiumUtility::Math::equalsEpsilon(
|
|
uv[uvIdx].x,
|
|
expectedX,
|
|
CesiumUtility::Math::Epsilon7));
|
|
CHECK(CesiumUtility::Math::equalsEpsilon(
|
|
uv[uvIdx].y,
|
|
expectedY,
|
|
CesiumUtility::Math::Epsilon7));
|
|
++uvIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Automatically calculate fit bounding region when tile has loose "
|
|
"region") {
|
|
auto pRemovedOverlay =
|
|
pManager->getRasterOverlayCollection().begin()->get();
|
|
pManager->getRasterOverlayCollection().remove(pRemovedOverlay);
|
|
CHECK(pManager->getRasterOverlayCollection().size() == 0);
|
|
|
|
Tile& tile = *pManager->getRootTile();
|
|
auto originalLooseRegion =
|
|
BoundingRegionWithLooseFittingHeights{BoundingRegion{
|
|
GeographicProjection::MAXIMUM_GLOBE_RECTANGLE,
|
|
-1000.0,
|
|
9000.0,
|
|
Ellipsoid::WGS84}};
|
|
tile.setBoundingVolume(originalLooseRegion);
|
|
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
CHECK(tile.getState() == TileLoadState::ContentLoaded);
|
|
|
|
// check the tile whole region which will be more fitted
|
|
const BoundingRegion& tileRegion =
|
|
std::get<BoundingRegion>(tile.getBoundingVolume());
|
|
CHECK(
|
|
tileRegion.getRectangle().getWest() == Approx(beginCarto.longitude));
|
|
CHECK(
|
|
tileRegion.getRectangle().getSouth() == Approx(beginCarto.latitude));
|
|
CHECK(
|
|
tileRegion.getRectangle().getEast() ==
|
|
Approx(beginCarto.longitude + 9 * 0.01));
|
|
CHECK(
|
|
tileRegion.getRectangle().getNorth() ==
|
|
Approx(beginCarto.latitude + 9 * 0.01));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Upsamples sparse tile for raster overlays") {
|
|
class AlwaysMoreDetailProvider : public RasterOverlayTileProvider {
|
|
public:
|
|
AlwaysMoreDetailProvider(
|
|
const CesiumUtility::IntrusivePointer<const RasterOverlay>& pOwner,
|
|
const CesiumAsync::AsyncSystem& asyncSystem,
|
|
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
|
|
const std::shared_ptr<CesiumUtility::CreditSystem>& pCreditSystem,
|
|
std::optional<CesiumUtility::Credit> credit,
|
|
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
|
|
pPrepareRendererResources,
|
|
const std::shared_ptr<spdlog::logger>& pLogger,
|
|
const CesiumGeospatial::Projection& projection,
|
|
const CesiumGeometry::Rectangle& coverageRectangle)
|
|
: RasterOverlayTileProvider(
|
|
pOwner,
|
|
asyncSystem,
|
|
pAssetAccessor,
|
|
pCreditSystem,
|
|
credit,
|
|
pPrepareRendererResources,
|
|
pLogger,
|
|
projection,
|
|
coverageRectangle) {}
|
|
|
|
CesiumAsync::Future<LoadedRasterOverlayImage>
|
|
loadTileImage(const RasterOverlayTile& overlayTile) override {
|
|
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> pImage;
|
|
CesiumGltf::ImageAsset& image = pImage.emplace();
|
|
image.width = 1;
|
|
image.height = 1;
|
|
image.channels = 1;
|
|
image.bytesPerChannel = 1;
|
|
image.pixelData.resize(1, std::byte(255));
|
|
|
|
return this->getAsyncSystem().createResolvedFuture(
|
|
LoadedRasterOverlayImage{
|
|
std::move(pImage),
|
|
overlayTile.getRectangle(),
|
|
{},
|
|
{},
|
|
true});
|
|
}
|
|
};
|
|
|
|
class AlwaysMoreDetailRasterOverlay : public RasterOverlay {
|
|
public:
|
|
AlwaysMoreDetailRasterOverlay() : RasterOverlay("AlwaysMoreDetail") {}
|
|
|
|
CesiumAsync::Future<CreateTileProviderResult> createTileProvider(
|
|
const CesiumAsync::AsyncSystem& asyncSystem,
|
|
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
|
|
const std::shared_ptr<
|
|
CesiumUtility::CreditSystem>& /* pCreditSystem */,
|
|
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
|
|
pPrepareRendererResources,
|
|
const std::shared_ptr<spdlog::logger>& pLogger,
|
|
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner)
|
|
const override {
|
|
return asyncSystem.createResolvedFuture(CreateTileProviderResult(
|
|
CesiumUtility::IntrusivePointer<RasterOverlayTileProvider>(
|
|
new AlwaysMoreDetailProvider(
|
|
pOwner ? pOwner : this,
|
|
asyncSystem,
|
|
pAssetAccessor,
|
|
nullptr,
|
|
std::nullopt,
|
|
pPrepareRendererResources,
|
|
pLogger,
|
|
CesiumGeospatial::GeographicProjection(),
|
|
projectRectangleSimple(
|
|
CesiumGeospatial::GeographicProjection(),
|
|
GlobeRectangle::MAXIMUM)))));
|
|
}
|
|
};
|
|
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
GlobeRectangle tileRectangle =
|
|
GlobeRectangle::fromDegrees(0.0010, 0.0011, 0.0012, 0.0013);
|
|
pMockedLoader->mockLoadTileContent = {
|
|
createSparseMesh(tileRectangle),
|
|
CesiumGeometry::Axis::Z,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Success};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
{},
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
pManager->getRasterOverlayCollection().add(
|
|
new AlwaysMoreDetailRasterOverlay());
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
SUBCASE(
|
|
"Generate raster overlay details when tile doesn't have loose region") {
|
|
// test the gltf model
|
|
Tile& tile = *pManager->getRootTile();
|
|
|
|
auto loadUntilChildrenExist = [pManager, &asyncSystem](Tile& tile) {
|
|
while (tile.getChildren().empty()) {
|
|
pManager->loadTileContent(tile, {});
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
pManager->updateTileContent(tile, {});
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
}
|
|
};
|
|
|
|
loadUntilChildrenExist(tile);
|
|
|
|
CHECK(tile.getState() == TileLoadState::Done);
|
|
const TileContent& tileContent = tile.getContent();
|
|
CHECK(tileContent.isRenderContent());
|
|
const RasterOverlayDetails& rasterOverlayDetails =
|
|
tileContent.getRenderContent()->getRasterOverlayDetails();
|
|
|
|
// ensure the raster overlay details has geographic projection
|
|
GeographicProjection geographicProjection{Ellipsoid::WGS84};
|
|
auto existingProjectionIt = std::find(
|
|
rasterOverlayDetails.rasterOverlayProjections.begin(),
|
|
rasterOverlayDetails.rasterOverlayProjections.end(),
|
|
Projection{geographicProjection});
|
|
CHECK(
|
|
existingProjectionIt !=
|
|
rasterOverlayDetails.rasterOverlayProjections.end());
|
|
|
|
// Both the raster overlay boundingRegion and rectangle should match the
|
|
// tile rectangle.
|
|
REQUIRE(!rasterOverlayDetails.rasterOverlayRectangles.empty());
|
|
const Rectangle& projectionRectangle =
|
|
rasterOverlayDetails.rasterOverlayRectangles.front();
|
|
GlobeRectangle globeRectangle =
|
|
geographicProjection.unproject(projectionRectangle);
|
|
CHECK(GlobeRectangle::equalsEpsilon(
|
|
globeRectangle,
|
|
tileRectangle,
|
|
Math::Epsilon13));
|
|
CHECK(GlobeRectangle::equalsEpsilon(
|
|
rasterOverlayDetails.boundingRegion.getRectangle(),
|
|
tileRectangle,
|
|
Math::Epsilon13));
|
|
|
|
// Load the southeast child
|
|
REQUIRE(tile.getChildren().size() == 4);
|
|
Tile& se = tile.getChildren()[1];
|
|
REQUIRE(std::get_if<UpsampledQuadtreeNode>(&se.getTileID()) != nullptr);
|
|
REQUIRE(
|
|
std::get<UpsampledQuadtreeNode>(se.getTileID()).tileID ==
|
|
QuadtreeTileID(1, 1, 0));
|
|
|
|
loadUntilChildrenExist(se);
|
|
|
|
// Verify the bounding volume is sensible
|
|
const BoundingRegion* pRegion =
|
|
std::get_if<BoundingRegion>(&se.getBoundingVolume());
|
|
REQUIRE(pRegion != nullptr);
|
|
CHECK(
|
|
pRegion->getRectangle().getEast() >
|
|
pRegion->getRectangle().getWest());
|
|
CHECK(
|
|
pRegion->getRectangle().getNorth() >
|
|
pRegion->getRectangle().getSouth());
|
|
|
|
// The tight-fitting bounding region from the raster overlay process
|
|
// should be sensible and smaller than the _original_ tile rectangle.
|
|
TileRenderContent* pRenderContent = se.getContent().getRenderContent();
|
|
REQUIRE(pRenderContent != nullptr);
|
|
const GlobeRectangle& tightRectangle =
|
|
pRenderContent->getRasterOverlayDetails()
|
|
.boundingRegion.getRectangle();
|
|
CHECK(tightRectangle.getEast() > tightRectangle.getWest());
|
|
CHECK(tightRectangle.getNorth() > tightRectangle.getSouth());
|
|
CHECK(
|
|
tightRectangle.computeWidth() * 2.0 <
|
|
tileRectangle.computeWidth() * 0.5);
|
|
CHECK(
|
|
tightRectangle.computeHeight() * 2.0 <
|
|
tileRectangle.computeHeight() * 0.5);
|
|
|
|
// The rectangle used for texture coordinates should also be sensible and
|
|
// match the southeast quadrant of the original parent tile rectangle.
|
|
REQUIRE(!pRenderContent->getRasterOverlayDetails()
|
|
.rasterOverlayRectangles.empty());
|
|
const Rectangle& overlayRectangle =
|
|
pRenderContent->getRasterOverlayDetails()
|
|
.rasterOverlayRectangles.front();
|
|
GlobeRectangle overlayGlobeRectangle =
|
|
unprojectRectangleSimple(GeographicProjection(), overlayRectangle);
|
|
|
|
GlobeRectangle seQuadrant(
|
|
tileRectangle.computeCenter().longitude,
|
|
tileRectangle.getSouth(),
|
|
tileRectangle.getEast(),
|
|
tileRectangle.computeCenter().latitude);
|
|
CHECK(GlobeRectangle::equalsEpsilon(
|
|
overlayGlobeRectangle,
|
|
seQuadrant,
|
|
Math::Epsilon13));
|
|
|
|
// The tile should have a raster overlay mapped to it.
|
|
REQUIRE(se.getMappedRasterTiles().size() == 1);
|
|
|
|
// Load the southeast child's southwest child
|
|
REQUIRE(se.getChildren().size() == 4);
|
|
Tile& sw = se.getChildren()[0];
|
|
REQUIRE(std::get_if<UpsampledQuadtreeNode>(&sw.getTileID()) != nullptr);
|
|
REQUIRE(
|
|
std::get<UpsampledQuadtreeNode>(sw.getTileID()).tileID ==
|
|
QuadtreeTileID(2, 2, 0));
|
|
|
|
loadUntilChildrenExist(sw);
|
|
|
|
// Verify the bounding volume is sensible
|
|
pRegion = std::get_if<BoundingRegion>(&sw.getBoundingVolume());
|
|
REQUIRE(pRegion != nullptr);
|
|
CHECK(
|
|
pRegion->getRectangle().getEast() >
|
|
pRegion->getRectangle().getWest());
|
|
CHECK(
|
|
pRegion->getRectangle().getNorth() >
|
|
pRegion->getRectangle().getSouth());
|
|
|
|
// The tight-fitting bounding region from the raster overlay process
|
|
// should be sensible and smaller than the _original_ tile rectangle.
|
|
pRenderContent = sw.getContent().getRenderContent();
|
|
REQUIRE(pRenderContent != nullptr);
|
|
const GlobeRectangle& swTightRectangle =
|
|
pRenderContent->getRasterOverlayDetails()
|
|
.boundingRegion.getRectangle();
|
|
CHECK(swTightRectangle.getEast() > swTightRectangle.getWest());
|
|
CHECK(swTightRectangle.getNorth() > swTightRectangle.getSouth());
|
|
CHECK(
|
|
swTightRectangle.computeWidth() * 2.0 <
|
|
tileRectangle.computeWidth() * 0.25);
|
|
CHECK(
|
|
swTightRectangle.computeHeight() * 2.0 <
|
|
tileRectangle.computeHeight() * 0.25);
|
|
|
|
// The rectangle used for texture coordinates should also be sensible and
|
|
// match the southwest quadrant of the southeast quadrant of the original
|
|
// parent tile rectangle.
|
|
REQUIRE(!pRenderContent->getRasterOverlayDetails()
|
|
.rasterOverlayRectangles.empty());
|
|
const Rectangle& swOverlayRectangle =
|
|
pRenderContent->getRasterOverlayDetails()
|
|
.rasterOverlayRectangles.front();
|
|
GlobeRectangle swOverlayGlobeRectangle =
|
|
unprojectRectangleSimple(GeographicProjection(), swOverlayRectangle);
|
|
CHECK(GlobeRectangle::equalsEpsilon(
|
|
swOverlayGlobeRectangle,
|
|
GlobeRectangle(
|
|
seQuadrant.getWest(),
|
|
seQuadrant.getSouth(),
|
|
seQuadrant.computeCenter().longitude,
|
|
seQuadrant.computeCenter().latitude),
|
|
Math::Epsilon13));
|
|
|
|
// The tile should have a raster overlay mapped to it.
|
|
REQUIRE(sw.getMappedRasterTiles().size() == 1);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Don't generate raster overlay for existing projection") {
|
|
// create gltf grid
|
|
Cartographic beginCarto{glm::radians(32.0), glm::radians(48.0), 100.0};
|
|
CesiumGltf::Model model = createGlobeGrid(beginCarto, 10, 10, 0.01);
|
|
model.extras["gltfUpAxis"] =
|
|
static_cast<std::underlying_type_t<CesiumGeometry::Axis>>(
|
|
CesiumGeometry::Axis::Z);
|
|
|
|
// mock raster overlay detail
|
|
GeographicProjection projection(Ellipsoid::WGS84);
|
|
RasterOverlayDetails rasterOverlayDetails;
|
|
rasterOverlayDetails.rasterOverlayProjections.emplace_back(projection);
|
|
rasterOverlayDetails.rasterOverlayRectangles.emplace_back(
|
|
projection.project(GeographicProjection::MAXIMUM_GLOBE_RECTANGLE));
|
|
rasterOverlayDetails.boundingRegion = BoundingRegion{
|
|
GeographicProjection::MAXIMUM_GLOBE_RECTANGLE,
|
|
-1000.0,
|
|
9000.0,
|
|
Ellipsoid::WGS84};
|
|
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = {
|
|
std::move(model),
|
|
CesiumGeometry::Axis::Z,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::move(rasterOverlayDetails),
|
|
nullptr,
|
|
nullptr,
|
|
{},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Failed};
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
{},
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
// add raster overlay
|
|
pManager->getRasterOverlayCollection().add(
|
|
new DebugColorizeTilesRasterOverlay("DebugOverlay"));
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, {});
|
|
pManager->waitUntilIdle();
|
|
|
|
const auto& renderContent = tile.getContent().getRenderContent();
|
|
CHECK(renderContent);
|
|
|
|
// Check that manager doesn't generate raster overlay for duplicate
|
|
// projection. Because the mock rasterOverlayDetails uses the same
|
|
// projection as rasterOverlayCollection, the manager should not generate
|
|
// any extra UVs attribute in the post process. Because we never generate
|
|
// _CESIUMOVERLAY_0 to begin with, so this test only successes when no
|
|
// _CESIUMOVERLAY_0 is found. Otherwise, the manager did generates UV using
|
|
// the duplicated projection
|
|
const CesiumGltf::Model& tileModel = renderContent->getModel();
|
|
CHECK(!tileModel.meshes.empty());
|
|
for (const CesiumGltf::Mesh& tileMesh : tileModel.meshes) {
|
|
CHECK(!tileMesh.primitives.empty());
|
|
for (const CesiumGltf::MeshPrimitive& tilePrimitive :
|
|
tileMesh.primitives) {
|
|
CHECK(
|
|
tilePrimitive.attributes.find("_CESIUMOVERLAY_0") ==
|
|
tilePrimitive.attributes.end());
|
|
}
|
|
}
|
|
|
|
pManager->unloadTileContent(tile);
|
|
}
|
|
|
|
SUBCASE("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
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
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);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("IPrepareRendererResources::prepareInLoadThread parameters") {
|
|
// create mock tileset externals
|
|
auto pMockedAssetAccessor = std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>{});
|
|
auto pMockedPrepareRendererResources =
|
|
std::make_shared<SimplePrepareRendererResource>();
|
|
CesiumAsync::AsyncSystem asyncSystem{std::make_shared<SimpleTaskProcessor>()};
|
|
auto pMockedCreditSystem = std::make_shared<CreditSystem>();
|
|
|
|
TilesetExternals externals{
|
|
pMockedAssetAccessor,
|
|
pMockedPrepareRendererResources,
|
|
asyncSystem,
|
|
pMockedCreditSystem};
|
|
|
|
SUBCASE("Passes initial bounding volumes correctly") {
|
|
const BoundingVolume boundingVolume =
|
|
BoundingSphere(glm::dvec3(1, 2, 3), 4);
|
|
const BoundingVolume contentBoundingVolume =
|
|
BoundingSphere(glm::dvec3(5, 6, 7), 8);
|
|
|
|
pMockedPrepareRendererResources->prepareInLoadThreadTestCallback =
|
|
[](const TileLoadResult& result) {
|
|
REQUIRE(result.initialBoundingVolume);
|
|
REQUIRE(result.initialContentBoundingVolume);
|
|
|
|
const BoundingSphere* boundingSphere =
|
|
std::get_if<BoundingSphere>(&*result.initialBoundingVolume);
|
|
const BoundingSphere* contentBoundingSphere =
|
|
std::get_if<BoundingSphere>(
|
|
&*result.initialContentBoundingVolume);
|
|
|
|
REQUIRE(boundingSphere);
|
|
REQUIRE(contentBoundingSphere);
|
|
|
|
CHECK(boundingSphere->getCenter() == glm::dvec3(1, 2, 3));
|
|
CHECK(boundingSphere->getRadius() == 4);
|
|
CHECK(contentBoundingSphere->getCenter() == glm::dvec3(5, 6, 7));
|
|
CHECK(contentBoundingSphere->getRadius() == 8);
|
|
};
|
|
|
|
// create mock loader
|
|
auto pMockedLoader = std::make_unique<SimpleTilesetContentLoader>();
|
|
pMockedLoader->mockLoadTileContent = TileLoadResult{
|
|
CesiumGltf::Model(),
|
|
CesiumGeometry::Axis::Y,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
nullptr,
|
|
nullptr,
|
|
[&](Tile&) {},
|
|
TileLoadResultState::Success,
|
|
Ellipsoid::WGS84,
|
|
boundingVolume,
|
|
contentBoundingVolume};
|
|
pMockedLoader->mockCreateTileChildren = {{}, TileLoadResultState::Success};
|
|
pMockedLoader->mockCreateTileChildren.children.emplace_back(
|
|
pMockedLoader.get(),
|
|
TileID(),
|
|
TileEmptyContent());
|
|
|
|
// create tile
|
|
auto pRootTile = std::make_unique<Tile>(pMockedLoader.get());
|
|
pRootTile->setBoundingVolume(boundingVolume);
|
|
pRootTile->setContentBoundingVolume(contentBoundingVolume);
|
|
|
|
// Give the tile an ID so it is eligible for unloading.
|
|
pRootTile->setTileID("foo");
|
|
|
|
// create manager
|
|
TilesetOptions options{};
|
|
options.contentOptions.generateMissingNormalsSmooth = true;
|
|
|
|
IntrusivePointer<TilesetContentManager> pManager =
|
|
new TilesetContentManager{
|
|
externals,
|
|
options,
|
|
std::move(pMockedLoader),
|
|
std::move(pRootTile)};
|
|
|
|
Tile& tile = *pManager->getRootTile();
|
|
pManager->loadTileContent(tile, options);
|
|
pManager->waitUntilIdle();
|
|
pManager->unloadTileContent(tile);
|
|
}
|
|
}
|