1775 lines
63 KiB
C++
1775 lines
63 KiB
C++
#include "SimplePrepareRendererResource.h"
|
|
|
|
#include <Cesium3DTiles/GroupMetadata.h>
|
|
#include <Cesium3DTiles/MetadataQuery.h>
|
|
#include <Cesium3DTiles/Schema.h>
|
|
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
|
|
#include <Cesium3DTilesSelection/Tile.h>
|
|
#include <Cesium3DTilesSelection/TileContent.h>
|
|
#include <Cesium3DTilesSelection/TileLoadResult.h>
|
|
#include <Cesium3DTilesSelection/Tileset.h>
|
|
#include <Cesium3DTilesSelection/TilesetContentLoader.h>
|
|
#include <Cesium3DTilesSelection/TilesetExternals.h>
|
|
#include <Cesium3DTilesSelection/ViewState.h>
|
|
#include <Cesium3DTilesSelection/ViewUpdateResult.h>
|
|
#include <CesiumAsync/AsyncSystem.h>
|
|
#include <CesiumAsync/Future.h>
|
|
#include <CesiumAsync/Promise.h>
|
|
#include <CesiumGeospatial/BoundingRegion.h>
|
|
#include <CesiumGeospatial/Cartographic.h>
|
|
#include <CesiumGeospatial/Ellipsoid.h>
|
|
#include <CesiumGeospatial/GlobeRectangle.h>
|
|
#include <CesiumGeospatial/S2CellBoundingVolume.h>
|
|
#include <CesiumNativeTests/SimpleAssetAccessor.h>
|
|
#include <CesiumNativeTests/SimpleAssetRequest.h>
|
|
#include <CesiumNativeTests/SimpleAssetResponse.h>
|
|
#include <CesiumNativeTests/SimpleTaskProcessor.h>
|
|
#include <CesiumNativeTests/readFile.h>
|
|
#include <CesiumUtility/Math.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <glm/exponential.hpp>
|
|
#include <glm/ext/vector_double2.hpp>
|
|
#include <glm/ext/vector_double3.hpp>
|
|
#include <glm/geometric.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
using namespace CesiumAsync;
|
|
using namespace Cesium3DTilesSelection;
|
|
using namespace CesiumGeospatial;
|
|
using namespace CesiumUtility;
|
|
using namespace CesiumNativeTests;
|
|
|
|
static bool doesTileMeetSSE(
|
|
const ViewState& viewState,
|
|
const Tile& tile,
|
|
const Tileset& tileset) {
|
|
double distance = glm::sqrt(viewState.computeDistanceSquaredToBoundingVolume(
|
|
tile.getBoundingVolume()));
|
|
double sse =
|
|
viewState.computeScreenSpaceError(tile.getGeometricError(), distance);
|
|
return sse < tileset.getOptions().maximumScreenSpaceError;
|
|
}
|
|
|
|
static void initializeTileset(Tileset& tileset) {
|
|
// create a random view state so that we can able to load the tileset first
|
|
const Ellipsoid& ellipsoid = Ellipsoid::WGS84;
|
|
Cartographic viewPositionCartographic{
|
|
Math::degreesToRadians(118.0),
|
|
Math::degreesToRadians(32.0),
|
|
200.0};
|
|
Cartographic viewFocusCartographic{
|
|
viewPositionCartographic.longitude + Math::degreesToRadians(0.5),
|
|
viewPositionCartographic.latitude + Math::degreesToRadians(0.5),
|
|
0.0};
|
|
glm::dvec3 viewPosition =
|
|
ellipsoid.cartographicToCartesian(viewPositionCartographic);
|
|
glm::dvec3 viewFocus =
|
|
ellipsoid.cartographicToCartesian(viewFocusCartographic);
|
|
glm::dvec3 viewUp{0.0, 0.0, 1.0};
|
|
glm::dvec2 viewPortSize{500.0, 500.0};
|
|
double aspectRatio = viewPortSize.x / viewPortSize.y;
|
|
double horizontalFieldOfView = Math::degreesToRadians(60.0);
|
|
double verticalFieldOfView =
|
|
std::atan(std::tan(horizontalFieldOfView * 0.5) / aspectRatio) * 2.0;
|
|
ViewState viewState = ViewState(
|
|
viewPosition,
|
|
glm::normalize(viewFocus - viewPosition),
|
|
viewUp,
|
|
viewPortSize,
|
|
horizontalFieldOfView,
|
|
verticalFieldOfView,
|
|
Ellipsoid::WGS84);
|
|
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
}
|
|
|
|
static ViewState zoomToTile(const Tile& tile) {
|
|
const BoundingRegion* region =
|
|
std::get_if<BoundingRegion>(&tile.getBoundingVolume());
|
|
REQUIRE(region != nullptr);
|
|
|
|
const GlobeRectangle& rectangle = region->getRectangle();
|
|
double maxHeight = region->getMaximumHeight();
|
|
Cartographic center = rectangle.computeCenter();
|
|
Cartographic corner = rectangle.getNorthwest();
|
|
corner.height = maxHeight;
|
|
|
|
const Ellipsoid& ellipsoid = Ellipsoid::WGS84;
|
|
glm::dvec3 viewPosition = ellipsoid.cartographicToCartesian(corner);
|
|
glm::dvec3 viewFocus = ellipsoid.cartographicToCartesian(center);
|
|
glm::dvec3 viewUp{0.0, 0.0, 1.0};
|
|
glm::dvec2 viewPortSize{500.0, 500.0};
|
|
double aspectRatio = viewPortSize.x / viewPortSize.y;
|
|
double horizontalFieldOfView = Math::degreesToRadians(60.0);
|
|
double verticalFieldOfView =
|
|
std::atan(std::tan(horizontalFieldOfView * 0.5) / aspectRatio) * 2.0;
|
|
return ViewState(
|
|
viewPosition,
|
|
glm::normalize(viewFocus - viewPosition),
|
|
viewUp,
|
|
viewPortSize,
|
|
horizontalFieldOfView,
|
|
verticalFieldOfView,
|
|
Ellipsoid::WGS84);
|
|
}
|
|
|
|
static ViewState zoomToTileset(const Tileset& tileset) {
|
|
const Tile* root = tileset.getRootTile();
|
|
REQUIRE(root != nullptr);
|
|
|
|
return zoomToTile(*root);
|
|
}
|
|
|
|
TEST_CASE("Test replace refinement for render") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
// initialize REPLACE tileset
|
|
//
|
|
// parent.b3dm
|
|
//
|
|
// ll.b3dm lr.b3dm ul.b3dm ur.b3dm
|
|
//
|
|
// ll_ll.b3dm
|
|
//
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "ReplaceTileset";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"parent.b3dm",
|
|
"ll.b3dm",
|
|
"lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm",
|
|
"ll_ll.b3dm",
|
|
};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
// check the tiles status
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
|
|
const Tile* root = &pTilesetJson->getChildren()[0];
|
|
|
|
REQUIRE(root->getState() == TileLoadState::ContentLoading);
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Unloaded);
|
|
}
|
|
|
|
SUBCASE("No refinement happen when tile meet SSE") {
|
|
// Zoom to tileset. Expect the root will not meet sse in this configure
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
// Zoom out from the tileset a little bit to make sure the root meet sse
|
|
glm::dvec3 zoomOutPosition =
|
|
viewState.getPosition() - viewState.getDirection() * 2500.0;
|
|
ViewState zoomOutViewState = ViewState(
|
|
zoomOutPosition,
|
|
viewState.getDirection(),
|
|
viewState.getUp(),
|
|
viewState.getViewportSize(),
|
|
viewState.getHorizontalFieldOfView(),
|
|
viewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
// Check 1st and 2nd frame. Root should meet sse and render. No transitions
|
|
// are expected here
|
|
for (int frame = 0; frame < 2; ++frame) {
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomOutViewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root meet sse
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(doesTileMeetSSE(zoomOutViewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Unloaded);
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 1);
|
|
REQUIRE(result.tilesToRenderThisFrame.front() == root);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 2);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.mainThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
|
|
// check children state are still unloaded
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Unloaded);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Root doesn't meet sse but has to be rendered because children "
|
|
"cannot be rendered") {
|
|
// we should forbid hole first to let the checks below happen
|
|
tileset.getOptions().forbidHoles = true;
|
|
|
|
SUBCASE("Children cannot be rendered because of no response") {
|
|
// remove one of children completed response to mock network error
|
|
mockAssetAccessor->mockCompletedRequests["ll.b3dm"]->pResponse = nullptr;
|
|
mockAssetAccessor->mockCompletedRequests["lr.b3dm"]->pResponse = nullptr;
|
|
mockAssetAccessor->mockCompletedRequests["ul.b3dm"]->pResponse = nullptr;
|
|
mockAssetAccessor->mockCompletedRequests["ur.b3dm"]->pResponse = nullptr;
|
|
}
|
|
|
|
SUBCASE("Children cannot be rendered because response has an failed status "
|
|
"code") {
|
|
// remove one of children completed response to mock network error
|
|
mockAssetAccessor->mockCompletedRequests["ll.b3dm"]
|
|
->pResponse->mockStatusCode = 404;
|
|
mockAssetAccessor->mockCompletedRequests["lr.b3dm"]
|
|
->pResponse->mockStatusCode = 404;
|
|
mockAssetAccessor->mockCompletedRequests["ul.b3dm"]
|
|
->pResponse->mockStatusCode = 404;
|
|
mockAssetAccessor->mockCompletedRequests["ur.b3dm"]
|
|
->pResponse->mockStatusCode = 404;
|
|
}
|
|
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
// 1st frame. Root doesn't meet sse, so it goes to children. But because
|
|
// children haven't started loading, root should be rendered.
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root doesn't meet sse, but children does.
|
|
// Children begin loading as well
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(doesTileMeetSSE(viewState, child, tileset));
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 1);
|
|
REQUIRE(result.tilesToRenderThisFrame.front() == root);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 4);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 2nd frame. Because children receive failed response, so they will be
|
|
// rendered as empty tiles.
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root doesn't meet sse, but children does
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Failed);
|
|
REQUIRE(doesTileMeetSSE(viewState, child, tileset));
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 4);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 1);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Parent meets sse but not renderable") {
|
|
// Zoom to tileset. Expect the root will not meet sse in this configure
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
glm::dvec3 zoomInPosition =
|
|
viewState.getPosition() + viewState.getDirection() * 200.0;
|
|
ViewState zoomInViewState = ViewState(
|
|
zoomInPosition,
|
|
viewState.getDirection(),
|
|
viewState.getUp(),
|
|
viewState.getViewportSize(),
|
|
viewState.getHorizontalFieldOfView(),
|
|
viewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
// remove the ll.b3dm (one of the root's children) request to replicate
|
|
// network failure
|
|
mockAssetAccessor->mockCompletedRequests["ll.b3dm"]->pResponse = nullptr;
|
|
|
|
// 1st frame. Root doesn't meet sse, but none of the children finish
|
|
// loading. So we will render root
|
|
{
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomInViewState});
|
|
tileset.loadTiles();
|
|
|
|
// check tiles status. All the children should have loading status
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(zoomInViewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::ContentLoading);
|
|
}
|
|
|
|
const Tile& ll = root->getChildren().front();
|
|
REQUIRE(!doesTileMeetSSE(zoomInViewState, ll, tileset));
|
|
|
|
const Tile& ll_ll = ll.getChildren().front();
|
|
REQUIRE(ll_ll.getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(doesTileMeetSSE(zoomInViewState, ll_ll, tileset));
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 1);
|
|
REQUIRE(result.tilesToRenderThisFrame.front() == root);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 7);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 5);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 2nd frame. All the children finish loading, so they are ready to be
|
|
// rendered (except ll.b3dm tile since it doesn't meet sse)
|
|
{
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomInViewState});
|
|
tileset.loadTiles();
|
|
|
|
// check tiles status. All the children should have loading status
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(zoomInViewState, *root, tileset));
|
|
|
|
// the first child of root isn't rendered because it doesn't meet sse. It
|
|
// will be refined to its child which is ready to be rendered
|
|
const Tile& ll = root->getChildren().front();
|
|
REQUIRE(ll.getState() == TileLoadState::Failed);
|
|
REQUIRE(!doesTileMeetSSE(zoomInViewState, ll, tileset));
|
|
|
|
const Tile& ll_ll = ll.getChildren().front();
|
|
REQUIRE(ll_ll.getState() == TileLoadState::Done);
|
|
REQUIRE(doesTileMeetSSE(zoomInViewState, ll_ll, tileset));
|
|
|
|
// the rest of the root's children are ready to be rendered since they all
|
|
// meet sse
|
|
for (size_t i = 1; i < root->getChildren().size(); ++i) {
|
|
const Tile& child = root->getChildren()[i];
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
REQUIRE(doesTileMeetSSE(zoomInViewState, child, tileset));
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 4);
|
|
REQUIRE(result.tilesToRenderThisFrame[0] == &ll_ll);
|
|
REQUIRE(result.tilesToRenderThisFrame[1] == &root->getChildren()[1]);
|
|
REQUIRE(result.tilesToRenderThisFrame[2] == &root->getChildren()[2]);
|
|
REQUIRE(result.tilesToRenderThisFrame[3] == &root->getChildren()[3]);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 1);
|
|
|
|
REQUIRE(result.tilesVisited == 7);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 3d frame. Zoom out the camera so that ll.b3dm tile will meet sse.
|
|
// However, since its content is failed to load and in the last frame it is
|
|
// refined, its child will be rendered instead to prevent loss of detail
|
|
{
|
|
glm::dvec3 zoomOutPosition =
|
|
viewState.getPosition() - viewState.getDirection() * 100.0;
|
|
ViewState zoomOutViewState = ViewState(
|
|
zoomOutPosition,
|
|
viewState.getDirection(),
|
|
viewState.getUp(),
|
|
viewState.getViewportSize(),
|
|
viewState.getHorizontalFieldOfView(),
|
|
viewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomOutViewState});
|
|
tileset.loadTiles();
|
|
|
|
// check tiles status. All the children should have loading status
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(zoomOutViewState, *root, tileset));
|
|
|
|
// The first child of root isn't rendered because it is failed to load
|
|
// even though it meets sse. We will still render it even it's just an
|
|
// empty hole
|
|
const Tile& ll = root->getChildren().front();
|
|
REQUIRE(ll.getState() == TileLoadState::Failed);
|
|
REQUIRE(doesTileMeetSSE(zoomOutViewState, ll, tileset));
|
|
|
|
const Tile& ll_ll = ll.getChildren().front();
|
|
REQUIRE(ll_ll.getState() == TileLoadState::Done);
|
|
|
|
// the rest of the root's children are ready to be rendered since they all
|
|
// meet sse
|
|
for (size_t i = 1; i < root->getChildren().size(); ++i) {
|
|
const Tile& child = root->getChildren()[i];
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
REQUIRE(doesTileMeetSSE(zoomOutViewState, child, tileset));
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 4);
|
|
REQUIRE(result.tilesToRenderThisFrame[0] == &ll);
|
|
REQUIRE(result.tilesToRenderThisFrame[1] == &root->getChildren()[1]);
|
|
REQUIRE(result.tilesToRenderThisFrame[2] == &root->getChildren()[2]);
|
|
REQUIRE(result.tilesToRenderThisFrame[3] == &root->getChildren()[3]);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 1);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Child should be chosen when parent doesn't meet SSE") {
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
// 1st frame. Root doesn't meet sse and children does. However, because
|
|
// none of the children are loaded, root will be rendered instead and
|
|
// children transition from unloaded to loading in the mean time
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root doesn't meet sse, but children does
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(doesTileMeetSSE(viewState, child, tileset));
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 1);
|
|
REQUIRE(result.tilesToRenderThisFrame.front() == root);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 4);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 2nd frame. Children are finished loading and ready to be rendered. Root
|
|
// shouldn't be rendered in this frame
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
// check tile states
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 4);
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(
|
|
std::find(
|
|
result.tilesToRenderThisFrame.begin(),
|
|
result.tilesToRenderThisFrame.end(),
|
|
&child) != result.tilesToRenderThisFrame.end());
|
|
}
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 1);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
}
|
|
|
|
SUBCASE(
|
|
"updateViewGroupOffline does not get stuck in an endless loop when no "
|
|
"frustums are given") {
|
|
std::vector<ViewState> empty;
|
|
tileset.updateViewGroupOffline(tileset.getDefaultViewGroup(), empty);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test additive refinement") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "AddTileset";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"tileset2.json",
|
|
"parent.b3dm",
|
|
"lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm",
|
|
"tileset3/tileset3.json",
|
|
"tileset3/ll.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
// root is external tileset. Since its content is loading, we won't know if it
|
|
// has children or not
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
|
|
const Tile* root = &pTilesetJson->getChildren()[0];
|
|
REQUIRE(root->getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(root->getChildren().size() == 0);
|
|
|
|
SUBCASE("Load external tilesets") {
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
// 1st frame. Root, its child, and its four grandchildren will all be
|
|
// rendered because they meet SSE, even though they're not loaded yet.
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
const std::vector<Tile::ConstPointer>& ttr =
|
|
result.tilesToRenderThisFrame;
|
|
REQUIRE(ttr.size() == 7);
|
|
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
REQUIRE(root->getChildren().size() == 1);
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), pTilesetJson) != ttr.end());
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), root) != ttr.end());
|
|
|
|
const Tile& parentB3DM = root->getChildren().front();
|
|
REQUIRE(parentB3DM.getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(!doesTileMeetSSE(viewState, parentB3DM, tileset));
|
|
REQUIRE(parentB3DM.getChildren().size() == 4);
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), &parentB3DM) != ttr.end());
|
|
|
|
for (const Tile& child : parentB3DM.getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(doesTileMeetSSE(viewState, child, tileset));
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), &child) != ttr.end());
|
|
}
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 7);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 5);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 2nd frame
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
const std::vector<Tile::ConstPointer>& ttr =
|
|
result.tilesToRenderThisFrame;
|
|
REQUIRE(ttr.size() == 8);
|
|
|
|
// root is done loading and rendered.
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
REQUIRE(root->getChildren().size() == 1);
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), root) != ttr.end());
|
|
|
|
// root's child is done loading and rendered, too.
|
|
const Tile& parentB3DM = root->getChildren().front();
|
|
REQUIRE(parentB3DM.getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, parentB3DM, tileset));
|
|
REQUIRE(parentB3DM.getChildren().size() == 4);
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), &parentB3DM) != ttr.end());
|
|
|
|
for (const Tile& child : parentB3DM.getChildren()) {
|
|
// parentB3DM's children are all done loading and are rendered.
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
REQUIRE(std::find(ttr.begin(), ttr.end(), &child) != ttr.end());
|
|
|
|
if (*std::get_if<std::string>(&child.getTileID()) !=
|
|
"tileset3/tileset3.json") {
|
|
REQUIRE(doesTileMeetSSE(viewState, child, tileset));
|
|
} else {
|
|
// external tilesets get unconditionally refined
|
|
REQUIRE(root->getUnconditionallyRefine());
|
|
|
|
// expect the children to meet sse and begin loading the content,
|
|
// while also getting rendered.
|
|
REQUIRE(child.getChildren().size() == 1);
|
|
REQUIRE(
|
|
doesTileMeetSSE(viewState, child.getChildren().front(), tileset));
|
|
REQUIRE(
|
|
child.getChildren().front().getState() ==
|
|
TileLoadState::ContentLoading);
|
|
REQUIRE(
|
|
std::find(ttr.begin(), ttr.end(), &child.getChildren().front()) !=
|
|
ttr.end());
|
|
}
|
|
}
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 8);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 1);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 3rd frame. All the children finish loading. All should be rendered now
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 8);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
|
|
REQUIRE(result.tilesVisited == 8);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Render any tiles even when one of children can't be rendered for "
|
|
"additive refinement") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "ErrorChildrenAddTileset";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"parent.b3dm",
|
|
"error_lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm",
|
|
};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
const Tile* root = &pTilesetJson->getChildren()[0];
|
|
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
REQUIRE(root->getState() == TileLoadState::ContentLoading);
|
|
REQUIRE(root->getChildren().size() == 3);
|
|
|
|
// 1st frame. Root doesn't meet sse, so load children. But they are
|
|
// non-renderable, so render root only
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
for (const Tile& child : root->getChildren()) {
|
|
CHECK(child.getState() == TileLoadState::ContentLoading);
|
|
}
|
|
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 2);
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
REQUIRE(result.tilesVisited == 5);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 3);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
|
|
// 2nd frame. Root doesn't meet sse, so load children. Even one of the
|
|
// children is failed, render all of them even there is a hole
|
|
{
|
|
ViewUpdateResult result =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
|
|
REQUIRE(root->isRenderable());
|
|
|
|
// first child will have failed empty content, but other children
|
|
const auto& children = root->getChildren();
|
|
REQUIRE(children[0].getState() == TileLoadState::Failed);
|
|
REQUIRE(children[0].isRenderable());
|
|
for (const Tile& child : children.subspan(1)) {
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
REQUIRE(child.isRenderable());
|
|
}
|
|
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 5);
|
|
REQUIRE(result.tilesFadingOut.size() == 0);
|
|
REQUIRE(result.tilesVisited == 5);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
REQUIRE(result.culledTilesVisited == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test multiple frustums") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "ReplaceTileset";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"parent.b3dm",
|
|
"ll.b3dm",
|
|
"lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm",
|
|
"ll_ll.b3dm",
|
|
};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
// check the tiles status
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
const Tile* root = &pTilesetJson->getChildren()[0];
|
|
REQUIRE(root->getState() == TileLoadState::ContentLoading);
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Unloaded);
|
|
}
|
|
|
|
// Zoom to tileset. Expect the root will not meet sse in this configure
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
|
|
// Zoom out from the tileset a little bit to make sure the root meets sse
|
|
glm::dvec3 zoomOutPosition =
|
|
viewState.getPosition() - viewState.getDirection() * 2500.0;
|
|
ViewState zoomOutViewState = ViewState(
|
|
zoomOutPosition,
|
|
viewState.getDirection(),
|
|
viewState.getUp(),
|
|
viewState.getViewportSize(),
|
|
viewState.getHorizontalFieldOfView(),
|
|
viewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
SUBCASE("The frustum with the highest SSE should be used for deciding to "
|
|
"refine") {
|
|
|
|
// frame 1
|
|
{
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{viewState, zoomOutViewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root meets sse for only the zoomed out
|
|
// ViewState
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
REQUIRE(doesTileMeetSSE(zoomOutViewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::ContentLoading);
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 1);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 4);
|
|
REQUIRE(result.tilesToRenderThisFrame.front() == root);
|
|
}
|
|
|
|
// frame 2
|
|
{
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{viewState, zoomOutViewState});
|
|
tileset.loadTiles();
|
|
|
|
// Check tile state. Ensure root meets sse for only the zoomed out
|
|
// ViewState
|
|
REQUIRE(root->getState() == TileLoadState::Done);
|
|
REQUIRE(!doesTileMeetSSE(viewState, *root, tileset));
|
|
REQUIRE(doesTileMeetSSE(zoomOutViewState, *root, tileset));
|
|
for (const auto& child : root->getChildren()) {
|
|
REQUIRE(child.getState() == TileLoadState::Done);
|
|
}
|
|
|
|
// check result
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 4);
|
|
|
|
REQUIRE(result.tilesFadingOut.size() == 1);
|
|
REQUIRE(*result.tilesFadingOut.begin() == root);
|
|
|
|
REQUIRE(result.tilesVisited == 6);
|
|
REQUIRE(result.workerThreadTileLoadQueueLength == 0);
|
|
REQUIRE(result.tilesCulled == 0);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Tiles should be culled if all the cameras agree") {
|
|
|
|
REQUIRE(root->getChildren().size() == 4);
|
|
const Tile& firstChild = root->getChildren()[0];
|
|
const Tile& secondChild = root->getChildren()[1];
|
|
REQUIRE(firstChild.getChildren().size() == 1);
|
|
const Tile& grandChild = firstChild.getChildren()[0];
|
|
|
|
ViewState zoomToTileViewState = zoomToTile(firstChild);
|
|
|
|
// Expected to only contain the grand child
|
|
// (child of the first child of the root).
|
|
glm::dvec3 zoomInPosition = zoomToTileViewState.getPosition() +
|
|
zoomToTileViewState.getDirection() * 250.0;
|
|
ViewState zoomInViewState1 = ViewState(
|
|
zoomInPosition,
|
|
zoomToTileViewState.getDirection(),
|
|
zoomToTileViewState.getUp(),
|
|
zoomToTileViewState.getViewportSize(),
|
|
0.5 * zoomToTileViewState.getHorizontalFieldOfView(),
|
|
0.5 * zoomToTileViewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
zoomInPosition = zoomToTileViewState.getPosition() +
|
|
glm::dvec3(15.0, 0, 0) +
|
|
zoomToTileViewState.getDirection() * 243.0;
|
|
ViewState zoomInViewState2 = ViewState(
|
|
zoomInPosition,
|
|
zoomToTileViewState.getDirection(),
|
|
zoomToTileViewState.getUp(),
|
|
zoomToTileViewState.getViewportSize(),
|
|
0.5 * zoomToTileViewState.getHorizontalFieldOfView(),
|
|
0.5 * zoomToTileViewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
|
|
// frame 3 & 4
|
|
{
|
|
tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomInViewState1, zoomInViewState2});
|
|
tileset.loadTiles();
|
|
ViewUpdateResult result = tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomInViewState1, zoomInViewState2});
|
|
tileset.loadTiles();
|
|
|
|
// check result
|
|
// The grand child and the second child are the only ones rendered.
|
|
// The third and fourth children of the root are culled.
|
|
REQUIRE(result.tilesToRenderThisFrame.size() == 2);
|
|
REQUIRE(result.tilesVisited == 5);
|
|
REQUIRE(
|
|
std::find(
|
|
result.tilesToRenderThisFrame.begin(),
|
|
result.tilesToRenderThisFrame.end(),
|
|
&grandChild) != result.tilesToRenderThisFrame.end());
|
|
REQUIRE(
|
|
std::find(
|
|
result.tilesToRenderThisFrame.begin(),
|
|
result.tilesToRenderThisFrame.end(),
|
|
&secondChild) != result.tilesToRenderThisFrame.end());
|
|
REQUIRE(result.tilesCulled == 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Can load example tileset.json from 3DTILES_bounding_volume_S2 "
|
|
"documentation") {
|
|
std::string s = R"(
|
|
{
|
|
"asset": {
|
|
"version": "1.0"
|
|
},
|
|
"geometricError": 1000000,
|
|
"extensionsUsed": [
|
|
"3DTILES_bounding_volume_S2"
|
|
],
|
|
"extensionsRequired": [
|
|
"3DTILES_bounding_volume_S2"
|
|
],
|
|
"root": {
|
|
"boundingVolume": {
|
|
"extensions": {
|
|
"3DTILES_bounding_volume_S2": {
|
|
"token": "3",
|
|
"minimumHeight": 0,
|
|
"maximumHeight": 1000000
|
|
}
|
|
}
|
|
},
|
|
"refine": "REPLACE",
|
|
"geometricError": 50000,
|
|
"children": [
|
|
{
|
|
"boundingVolume": {
|
|
"extensions": {
|
|
"3DTILES_bounding_volume_S2": {
|
|
"token": "2c",
|
|
"minimumHeight": 0,
|
|
"maximumHeight": 500000
|
|
}
|
|
}
|
|
},
|
|
"refine": "REPLACE",
|
|
"geometricError": 500000,
|
|
"children": [
|
|
{
|
|
"boundingVolume": {
|
|
"extensions": {
|
|
"3DTILES_bounding_volume_S2": {
|
|
"token": "2f",
|
|
"minimumHeight": 0,
|
|
"maximumHeight": 250000
|
|
}
|
|
}
|
|
},
|
|
"refine": "REPLACE",
|
|
"geometricError": 250000,
|
|
"children": [
|
|
{
|
|
"boundingVolume": {
|
|
"extensions": {
|
|
"3DTILES_bounding_volume_S2": {
|
|
"token": "2ec",
|
|
"minimumHeight": 0,
|
|
"maximumHeight": 125000
|
|
}
|
|
}
|
|
},
|
|
"refine": "REPLACE",
|
|
"geometricError": 125000
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
})";
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
|
|
const std::byte* pStart = reinterpret_cast<const std::byte*>(s.c_str());
|
|
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::vector<std::byte>(pStart, pStart + s.size()));
|
|
mockCompletedRequests.insert(
|
|
{"tileset.json",
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
"tileset.json",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and wait for it to load.
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
while (!tileset.getRootTile()) {
|
|
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
|
|
}
|
|
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
const Tile* pRoot = &pTilesetJson->getChildren()[0];
|
|
|
|
const S2CellBoundingVolume* pS2 =
|
|
std::get_if<S2CellBoundingVolume>(&pRoot->getBoundingVolume());
|
|
REQUIRE(pS2);
|
|
|
|
CHECK(pS2->getCellID().toToken() == "3");
|
|
CHECK(pS2->getMinimumHeight() == 0.0);
|
|
CHECK(pS2->getMaximumHeight() == 1000000.0);
|
|
|
|
REQUIRE(pRoot->getChildren().size() == 1);
|
|
const Tile* pChild = &pRoot->getChildren()[0];
|
|
const S2CellBoundingVolume* pS2Child =
|
|
std::get_if<S2CellBoundingVolume>(&pChild->getBoundingVolume());
|
|
REQUIRE(pS2Child);
|
|
|
|
CHECK(pS2Child->getCellID().toToken() == "2c");
|
|
CHECK(pS2Child->getMinimumHeight() == 0.0);
|
|
CHECK(pS2Child->getMaximumHeight() == 500000.0);
|
|
|
|
REQUIRE(pChild->getChildren().size() == 1);
|
|
const Tile* pGrandchild = &pChild->getChildren()[0];
|
|
const S2CellBoundingVolume* pS2Grandchild =
|
|
std::get_if<S2CellBoundingVolume>(&pGrandchild->getBoundingVolume());
|
|
REQUIRE(pS2Grandchild);
|
|
|
|
CHECK(pS2Grandchild->getCellID().toToken() == "2f");
|
|
CHECK(pS2Grandchild->getMinimumHeight() == 0.0);
|
|
CHECK(pS2Grandchild->getMaximumHeight() == 250000.0);
|
|
|
|
REQUIRE(pGrandchild->getChildren().size() == 1);
|
|
const Tile* pGreatGrandchild = &pGrandchild->getChildren()[0];
|
|
const S2CellBoundingVolume* pS2GreatGrandchild =
|
|
std::get_if<S2CellBoundingVolume>(&pGreatGrandchild->getBoundingVolume());
|
|
REQUIRE(pS2GreatGrandchild);
|
|
|
|
CHECK(pS2GreatGrandchild->getCellID().toToken() == "2ec");
|
|
CHECK(pS2GreatGrandchild->getMinimumHeight() == 0.0);
|
|
CHECK(pS2GreatGrandchild->getMaximumHeight() == 125000.0);
|
|
|
|
REQUIRE(pGreatGrandchild->getChildren().empty());
|
|
}
|
|
|
|
TEST_CASE("Makes metadata available once root tile is loaded") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "WithMetadata";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"external-tileset.json",
|
|
"parent.b3dm",
|
|
"ll.b3dm",
|
|
"lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
const Tile* pRoot = tileset.getRootTile();
|
|
REQUIRE(pRoot);
|
|
|
|
const TileExternalContent* pExternal =
|
|
pRoot->getContent().getExternalContent();
|
|
REQUIRE(pExternal);
|
|
|
|
const TilesetMetadata& metadata = pExternal->metadata;
|
|
const std::optional<Cesium3DTiles::Schema>& schema = metadata.schema;
|
|
REQUIRE(schema);
|
|
CHECK(schema->id == "foo");
|
|
}
|
|
|
|
TEST_CASE("Makes metadata available on external tilesets") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "WithMetadata";
|
|
std::vector<std::string> files{
|
|
"tileset.json",
|
|
"external-tileset.json",
|
|
"parent.b3dm",
|
|
"ll.b3dm",
|
|
"lr.b3dm",
|
|
"ul.b3dm",
|
|
"ur.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
const Tile* pTilesetJson = tileset.getRootTile();
|
|
REQUIRE(pTilesetJson);
|
|
REQUIRE(pTilesetJson->getChildren().size() == 1);
|
|
|
|
const Tile* pRoot = &pTilesetJson->getChildren()[0];
|
|
REQUIRE(pRoot);
|
|
REQUIRE(pRoot->getChildren().size() == 5);
|
|
const Tile* pExternal = &pRoot->getChildren()[4];
|
|
|
|
const TileExternalContent* pExternalContent = nullptr;
|
|
|
|
for (int i = 0; i < 10 && pExternalContent == nullptr; ++i) {
|
|
ViewState zoomToTileViewState = zoomToTile(*pExternal);
|
|
tileset.updateViewGroup(
|
|
tileset.getDefaultViewGroup(),
|
|
{zoomToTileViewState});
|
|
tileset.loadTiles();
|
|
pExternalContent = pExternal->getContent().getExternalContent();
|
|
}
|
|
|
|
REQUIRE(pExternalContent);
|
|
|
|
REQUIRE(pExternalContent->metadata.groups.size() == 2);
|
|
CHECK(pExternalContent->metadata.groups[0].classProperty == "someClass");
|
|
CHECK(pExternalContent->metadata.groups[1].classProperty == "someClass");
|
|
}
|
|
|
|
TEST_CASE("Allows access to material variants") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "MaterialVariants";
|
|
std::vector<std::string> files{"tileset.json", "parent.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
const TilesetMetadata* pMetadata = tileset.getMetadata();
|
|
REQUIRE(pMetadata);
|
|
REQUIRE(pMetadata->schema);
|
|
REQUIRE(pMetadata->metadata);
|
|
|
|
std::optional<Cesium3DTiles::FoundMetadataProperty> found1 =
|
|
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
|
|
*pMetadata->schema,
|
|
*pMetadata->metadata,
|
|
"MATERIAL_VARIANTS");
|
|
REQUIRE(found1);
|
|
CHECK(found1->classIdentifier == "MaterialVariants");
|
|
CHECK(found1->classDefinition.properties.size() == 1);
|
|
CHECK(found1->propertyIdentifier == "material_variants");
|
|
CHECK(
|
|
found1->propertyDefinition.description ==
|
|
"Names of material variants to be expected in the glTF assets");
|
|
REQUIRE(found1->propertyValue.isArray());
|
|
|
|
std::vector<std::string> variants =
|
|
found1->propertyValue.getArrayOfStrings("");
|
|
REQUIRE(variants.size() == 4);
|
|
CHECK(variants[0] == "RGB");
|
|
CHECK(variants[1] == "RRR");
|
|
CHECK(variants[2] == "GGG");
|
|
CHECK(variants[3] == "BBB");
|
|
|
|
std::vector<std::vector<std::string>> variantsByGroup;
|
|
for (const Cesium3DTiles::GroupMetadata& group : pMetadata->groups) {
|
|
std::optional<Cesium3DTiles::FoundMetadataProperty> found2 =
|
|
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
|
|
*pMetadata->schema,
|
|
group,
|
|
"MATERIAL_VARIANTS");
|
|
REQUIRE(found2);
|
|
REQUIRE(found2->propertyValue.isArray());
|
|
|
|
variantsByGroup.emplace_back(found2->propertyValue.getArrayOfStrings(""));
|
|
}
|
|
|
|
std::vector<std::vector<std::string>> expected = {
|
|
{"RGB", "RRR"},
|
|
{"GGG", "BBB"}};
|
|
|
|
CHECK(variantsByGroup == expected);
|
|
}
|
|
|
|
TEST_CASE("Allows access to material variants in an external schema") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "MaterialVariants";
|
|
std::vector<std::string> files{
|
|
"tileset-external-schema.json",
|
|
"schema.json",
|
|
"parent.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
Tileset tileset(tilesetExternals, "tileset-external-schema.json");
|
|
|
|
// getMetadata returns nullptr before the root tile is loaded.
|
|
CHECK(tileset.getMetadata() == nullptr);
|
|
|
|
bool wasCalled = false;
|
|
tileset.loadMetadata().thenInMainThread(
|
|
[&wasCalled](const TilesetMetadata* pMetadata) {
|
|
wasCalled = true;
|
|
REQUIRE(pMetadata);
|
|
REQUIRE(pMetadata->schema);
|
|
REQUIRE(pMetadata->metadata);
|
|
|
|
std::optional<Cesium3DTiles::FoundMetadataProperty> found1 =
|
|
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
|
|
*pMetadata->schema,
|
|
*pMetadata->metadata,
|
|
"MATERIAL_VARIANTS");
|
|
REQUIRE(found1);
|
|
CHECK(found1->classIdentifier == "MaterialVariants");
|
|
CHECK(found1->classDefinition.properties.size() == 1);
|
|
CHECK(found1->propertyIdentifier == "material_variants");
|
|
CHECK(
|
|
found1->propertyDefinition.description ==
|
|
"Names of material variants to be expected in the glTF assets");
|
|
REQUIRE(found1->propertyValue.isArray());
|
|
|
|
std::vector<std::string> variants =
|
|
found1->propertyValue.getArrayOfStrings("");
|
|
REQUIRE(variants.size() == 4);
|
|
CHECK(variants[0] == "RGB");
|
|
CHECK(variants[1] == "RRR");
|
|
CHECK(variants[2] == "GGG");
|
|
CHECK(variants[3] == "BBB");
|
|
|
|
std::vector<std::vector<std::string>> variantsByGroup;
|
|
for (const Cesium3DTiles::GroupMetadata& group : pMetadata->groups) {
|
|
std::optional<Cesium3DTiles::FoundMetadataProperty> found2 =
|
|
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
|
|
*pMetadata->schema,
|
|
group,
|
|
"MATERIAL_VARIANTS");
|
|
REQUIRE(found2);
|
|
REQUIRE(found2->propertyValue.isArray());
|
|
variantsByGroup.emplace_back(
|
|
found2->propertyValue.getArrayOfStrings(""));
|
|
}
|
|
|
|
std::vector<std::vector<std::string>> expected = {
|
|
{"RGB", "RRR"},
|
|
{"GGG", "BBB"}};
|
|
|
|
CHECK(variantsByGroup == expected);
|
|
});
|
|
|
|
CHECK(!wasCalled);
|
|
initializeTileset(tileset);
|
|
CHECK(wasCalled);
|
|
}
|
|
|
|
TEST_CASE("Future from loadSchema rejects if schemaUri can't be loaded") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "MaterialVariants";
|
|
std::vector<std::string> files{"tileset-external-schema.json", "parent.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
mockCompletedRequests.insert(
|
|
{"schema.json",
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
"schema.json",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(404),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::vector<std::byte>()))});
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
Tileset tileset(tilesetExternals, "tileset-external-schema.json");
|
|
|
|
// getMetadata returns nullptr before the root tile is loaded.
|
|
CHECK(tileset.getMetadata() == nullptr);
|
|
|
|
bool wasResolved = false;
|
|
bool wasRejected = false;
|
|
tileset.loadMetadata()
|
|
.thenInMainThread(
|
|
[&wasResolved](const TilesetMetadata*) { wasResolved = true; })
|
|
.catchInMainThread([&wasRejected](const std::exception& exception) {
|
|
CHECK(std::string(exception.what()).find("") != std::string::npos);
|
|
wasRejected = true;
|
|
});
|
|
|
|
CHECK(!wasResolved);
|
|
CHECK(!wasRejected);
|
|
|
|
initializeTileset(tileset);
|
|
CHECK(!wasResolved);
|
|
CHECK(wasRejected);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void runUnconditionallyRefinedTestCase(const TilesetOptions& options) {
|
|
class CustomContentLoader : public TilesetContentLoader {
|
|
public:
|
|
Tile* _pRootTile = nullptr;
|
|
std::optional<Promise<TileLoadResult>> _grandchildPromise;
|
|
|
|
std::unique_ptr<Tile> createRootTile() {
|
|
auto pRootTile = std::make_unique<Tile>(this);
|
|
this->_pRootTile = pRootTile.get();
|
|
|
|
pRootTile->setTileID(CesiumGeometry::QuadtreeTileID(0, 0, 0));
|
|
|
|
Cartographic center = Cartographic::fromDegrees(118.0, 32.0, 0.0);
|
|
pRootTile->setBoundingVolume(CesiumGeospatial::BoundingRegion(
|
|
GlobeRectangle(
|
|
center.longitude - 0.001,
|
|
center.latitude - 0.001,
|
|
center.longitude + 0.001,
|
|
center.latitude + 0.001),
|
|
0.0,
|
|
10.0,
|
|
Ellipsoid::WGS84));
|
|
pRootTile->setGeometricError(100000000000.0);
|
|
|
|
Tile child(this);
|
|
child.setTileID(CesiumGeometry::QuadtreeTileID(1, 0, 0));
|
|
child.setBoundingVolume(pRootTile->getBoundingVolume());
|
|
child.setGeometricError(1e100);
|
|
|
|
std::vector<Tile> children;
|
|
children.emplace_back(std::move(child));
|
|
|
|
pRootTile->createChildTiles(std::move(children));
|
|
|
|
Tile grandchild(this);
|
|
grandchild.setTileID(CesiumGeometry::QuadtreeTileID(1, 0, 0));
|
|
grandchild.setBoundingVolume(pRootTile->getBoundingVolume());
|
|
grandchild.setGeometricError(0.1);
|
|
|
|
std::vector<Tile> grandchildren;
|
|
grandchildren.emplace_back(std::move(grandchild));
|
|
pRootTile->getChildren()[0].createChildTiles(std::move(grandchildren));
|
|
|
|
return pRootTile;
|
|
}
|
|
|
|
virtual CesiumAsync::Future<TileLoadResult>
|
|
loadTileContent(const TileLoadInput& input) override {
|
|
if (&input.tile == this->_pRootTile) {
|
|
TileLoadResult result{};
|
|
result.contentKind = CesiumGltf::Model();
|
|
return input.asyncSystem.createResolvedFuture(std::move(result));
|
|
} else if (input.tile.getParent() == this->_pRootTile) {
|
|
TileLoadResult result{};
|
|
result.contentKind = TileEmptyContent();
|
|
return input.asyncSystem.createResolvedFuture(std::move(result));
|
|
} else if (
|
|
input.tile.getParent() != nullptr &&
|
|
input.tile.getParent()->getParent() == this->_pRootTile) {
|
|
this->_grandchildPromise =
|
|
input.asyncSystem.createPromise<TileLoadResult>();
|
|
return this->_grandchildPromise->getFuture();
|
|
}
|
|
|
|
return input.asyncSystem.createResolvedFuture(
|
|
TileLoadResult::createFailedResult(input.pAssetAccessor, nullptr));
|
|
}
|
|
|
|
virtual TileChildrenResult createTileChildren(
|
|
const Tile&,
|
|
const CesiumGeospatial::Ellipsoid&) override {
|
|
return TileChildrenResult{{}, TileLoadResultState::Failed};
|
|
}
|
|
};
|
|
|
|
TilesetExternals tilesetExternals{
|
|
nullptr,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
auto pCustomLoader = std::make_unique<CustomContentLoader>();
|
|
CustomContentLoader* pRawLoader = pCustomLoader.get();
|
|
|
|
Tileset tileset(
|
|
tilesetExternals,
|
|
std::move(pCustomLoader),
|
|
pRawLoader->createRootTile(),
|
|
options);
|
|
|
|
TilesetViewGroup& viewGroup = tileset.getDefaultViewGroup();
|
|
|
|
// On the first update, we should refine down to the grandchild tile, even
|
|
// though no tiles are loaded yet.
|
|
initializeTileset(tileset);
|
|
const Tile& child = tileset.getRootTile()->getChildren()[0];
|
|
const Tile& grandchild = child.getChildren()[0];
|
|
|
|
auto states = viewGroup.getTraversalState().slowlyGetCurrentStates();
|
|
|
|
CHECK(
|
|
states[tileset.getRootTile()].getResult() ==
|
|
TileSelectionState::Result::Refined);
|
|
CHECK(states[&child].getResult() == TileSelectionState::Result::Refined);
|
|
CHECK(
|
|
states[&grandchild].getResult() == TileSelectionState::Result::Rendered);
|
|
|
|
// After the third update, the root and child tiles have been loaded, while
|
|
// the grandchild has not. But the child is unconditionally refined, so we
|
|
// can't render that one. Therefore the root tile should be rendered, after
|
|
// the child and grandchild are kicked.
|
|
initializeTileset(tileset);
|
|
initializeTileset(tileset);
|
|
|
|
states = viewGroup.getTraversalState().slowlyGetCurrentStates();
|
|
|
|
CHECK(
|
|
states[tileset.getRootTile()].getResult() ==
|
|
TileSelectionState::Result::Rendered);
|
|
CHECK(states[&child].getResult() != TileSelectionState::Result::Rendered);
|
|
CHECK(
|
|
states[&grandchild].getResult() != TileSelectionState::Result::Rendered);
|
|
|
|
REQUIRE(pRawLoader->_grandchildPromise);
|
|
|
|
// Once the grandchild is loaded, it should be rendered instead.
|
|
TileLoadResult result{};
|
|
result.contentKind = CesiumGltf::Model();
|
|
pRawLoader->_grandchildPromise->resolve(std::move(result));
|
|
|
|
initializeTileset(tileset);
|
|
|
|
states = viewGroup.getTraversalState().slowlyGetCurrentStates();
|
|
|
|
CHECK(
|
|
states[&grandchild].getResult() == TileSelectionState::Result::Rendered);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("An unconditionally-refined tile is not rendered") {
|
|
SUBCASE("With default settings") {
|
|
runUnconditionallyRefinedTestCase(TilesetOptions());
|
|
}
|
|
|
|
SUBCASE("With forbidHoles enabled") {
|
|
TilesetOptions options{};
|
|
options.forbidHoles = true;
|
|
runUnconditionallyRefinedTestCase(options);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Additive-refined tiles are added to the tilesFadingOut array") {
|
|
Cesium3DTilesContent::registerAllTileContentTypes();
|
|
|
|
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
|
testDataPath = testDataPath / "AdditiveThreeLevels";
|
|
std::vector<std::string> files{"tileset.json", "content.b3dm"};
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
|
|
mockCompletedRequests;
|
|
for (const auto& file : files) {
|
|
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
|
|
std::make_unique<SimpleAssetResponse>(
|
|
static_cast<uint16_t>(200),
|
|
"doesn't matter",
|
|
CesiumAsync::HttpHeaders{},
|
|
readFile(testDataPath / file));
|
|
mockCompletedRequests.insert(
|
|
{file,
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
file,
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(mockCompletedResponse))});
|
|
}
|
|
|
|
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
|
|
TilesetExternals tilesetExternals{
|
|
mockAssetAccessor,
|
|
std::make_shared<SimplePrepareRendererResource>(),
|
|
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
|
|
nullptr};
|
|
|
|
// create tileset and call updateView() to give it a chance to load
|
|
Tileset tileset(tilesetExternals, "tileset.json");
|
|
initializeTileset(tileset);
|
|
|
|
// Load until complete
|
|
ViewUpdateResult updateResult;
|
|
ViewState viewState = zoomToTileset(tileset);
|
|
while (tileset.getNumberOfTilesLoaded() == 0 ||
|
|
tileset.computeLoadProgress() < 100.0f) {
|
|
updateResult =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
|
|
tileset.loadTiles();
|
|
}
|
|
|
|
// All three tiles (plus the tileset.json) should be rendered.
|
|
CHECK(updateResult.tilesToRenderThisFrame.size() == 4);
|
|
|
|
// Zoom way out
|
|
glm::dvec3 position =
|
|
viewState.getPosition() - 100000.0 * viewState.getDirection();
|
|
|
|
ViewState zoomedOut = ViewState(
|
|
position,
|
|
viewState.getDirection(),
|
|
viewState.getUp(),
|
|
viewState.getViewportSize(),
|
|
viewState.getHorizontalFieldOfView(),
|
|
viewState.getVerticalFieldOfView(),
|
|
Ellipsoid::WGS84);
|
|
updateResult =
|
|
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {zoomedOut});
|
|
tileset.loadTiles();
|
|
|
|
// Only the root tile (plus the tileset.json) is visible now, and the other
|
|
// two are fading out.
|
|
CHECK(updateResult.tilesToRenderThisFrame.size() == 2);
|
|
CHECK(updateResult.tilesFadingOut.size() == 2);
|
|
}
|