cesium-native/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp

351 lines
11 KiB
C++

#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
#include <Cesium3DTilesSelection/EllipsoidTilesetLoader.h>
#include <Cesium3DTilesSelection/SampleHeightResult.h>
#include <Cesium3DTilesSelection/Tileset.h>
#include <Cesium3DTilesSelection/TilesetExternals.h>
#include <CesiumAsync/AsyncSystem.h>
#include <CesiumAsync/Future.h>
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumGeospatial/Cartographic.h>
#include <CesiumNativeTests/FileAccessor.h>
#include <CesiumNativeTests/SimpleTaskProcessor.h>
#include <CesiumUtility/Math.h>
#include <CesiumUtility/StringHelpers.h>
#include <CesiumUtility/Uri.h>
#include <doctest/doctest.h>
#include <filesystem>
#include <memory>
#include <string>
using namespace Cesium3DTilesContent;
using namespace Cesium3DTilesSelection;
using namespace CesiumAsync;
using namespace CesiumGeospatial;
using namespace CesiumNativeTests;
using namespace CesiumUtility;
namespace {
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
}
TEST_CASE("Tileset height queries") {
// The coordinates and expected heights in this file were determined in Cesium
// for Unreal Engine by adding the tileset, putting a cube above the location
// of interest, adding a CesiumGlobeAnchor to it, and pressing the "End" key
// to drop it onto terrain. The coordinates were then copied out of the globe
// anchor, subtracting 0.5 from the height to account for "End" placing the
// bottom of the cube on the surface instead of its center.
registerAllTileContentTypes();
std::shared_ptr<IAssetAccessor> pAccessor =
std::make_shared<CesiumNativeTests::FileAccessor>();
AsyncSystem asyncSystem(std::make_shared<SimpleTaskProcessor>());
TilesetExternals externals{pAccessor, nullptr, asyncSystem, nullptr};
SUBCASE("Additive-refined tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "Tileset" / "tileset.json").u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "parent.b3dm", which should only be included
// because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SUBCASE("Replace-refined tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "ReplaceTileset" / "tileset.json").u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "parent.b3dm", which should not be
// included because this tileset is replace-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(!results.sampleSuccess[0]);
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SUBCASE("External tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "AddTileset" / "tileset.json").u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "0/0/0.b3dm", which should only be
// included because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SUBCASE("Implicit tileset") {
std::string url =
"file://" + Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "ImplicitTileset" / "tileset_1.1.json")
.u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "0/0/0.b3dm", which should only be
// included because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SUBCASE("Instanced model is not yet supported") {
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "i3dm" / "InstancedWithBatchTable" / "tileset.json")
.u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
REQUIRE(results.warnings.size() == 1);
REQUIRE(results.positions.size() == 1);
CHECK(!results.sampleSuccess[0]);
CHECK(
results.warnings[0].find("EXT_mesh_gpu_instancing") !=
std::string::npos);
}
SUBCASE("broken tileset") {
Tileset tileset(externals, "http://localhost/notgonnawork");
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
REQUIRE(results.warnings.size() == 1);
REQUIRE(results.positions.size() == 1);
REQUIRE(results.sampleSuccess.size() == 1);
CHECK(!results.sampleSuccess[0]);
CHECK(results.warnings[0].find("failed to load") != std::string::npos);
}
SUBCASE("ellipsoid tileset") {
std::unique_ptr<Tileset> pTileset =
EllipsoidTilesetLoader::createTileset(externals);
Future<SampleHeightResult> future = pTileset->sampleHeightMostDetailed(
{Cartographic::fromDegrees(-75.612559, 40.042183, 1.0)});
while (!future.isReady()) {
pTileset->loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
REQUIRE(results.warnings.size() == 0);
REQUIRE(results.positions.size() == 1);
REQUIRE(results.sampleSuccess.size() == 1);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].longitude,
Math::degreesToRadians(-75.612559),
0.0,
Math::Epsilon4));
CHECK(Math::equalsEpsilon(
results.positions[0].latitude,
Math::degreesToRadians(40.042183),
0.0,
Math::Epsilon4));
CHECK(Math::equalsEpsilon(
results.positions[0].height,
0.0,
0.0,
Math::Epsilon4));
}
SUBCASE("stacked-cubes") {
// This tileset has two cubes on top of each other, each in a different
// tile, so we can test that the height of the top one is returned.
// The bottom cube has a height of 78.0 meters, the upper cube has a height
// of 83.0 meters.
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "stacked-cubes" / "tileset.json").u8string()));
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
{Cartographic::fromDegrees(10.0, 45.0, 0.0)});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 1);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
83.0,
0.0,
Math::Epsilon1));
}
SUBCASE("stacked-cubes on custom ellipsoid") {
// This tileset has two cubes on top of each other, each in a different
// tile, so we can test that the height of the top one is returned.
// Relative to the WGS84 ellipsoid, the bottom cube has a height of 78.0
// meters, and the upper cube has a height of 83.0 meters.
std::string url =
"file://" +
Uri::nativePathToUriPath(StringHelpers::toStringUtf8(
(testDataPath / "stacked-cubes" / "tileset.json").u8string()));
CesiumGeospatial::Ellipsoid ellipsoid(
CesiumGeospatial::Ellipsoid::WGS84.getRadii() - glm::dvec3(15.0));
TilesetOptions options;
options.ellipsoid = ellipsoid;
Tileset tileset(externals, url, options);
Cartographic samplePosition = Cartographic::fromDegrees(10.0, 45.0, 0.0);
Future<SampleHeightResult> future =
tileset.sampleHeightMostDetailed({samplePosition});
while (!future.isReady()) {
tileset.loadTiles();
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 1);
glm::dvec3 rayDirection = -ellipsoid.geodeticSurfaceNormal(samplePosition);
glm::dvec3 difference = glm::dvec3(15.0) * rayDirection;
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
83.0 + glm::length(difference),
0.0,
Math::Epsilon1));
}
}