521 lines
14 KiB
C++
521 lines
14 KiB
C++
#include <Cesium3DTiles/Buffer.h>
|
|
#include <Cesium3DTilesReader/SubtreeFileReader.h>
|
|
#include <Cesium3DTilesReader/SubtreeReader.h>
|
|
#include <Cesium3DTilesReader/SubtreesReader.h>
|
|
#include <Cesium3DTilesWriter/SubtreeWriter.h>
|
|
#include <CesiumJsonWriter/ExtensionWriterContext.h>
|
|
#include <CesiumNativeTests/SimpleAssetAccessor.h>
|
|
#include <CesiumNativeTests/SimpleAssetRequest.h>
|
|
#include <CesiumNativeTests/SimpleAssetResponse.h>
|
|
#include <CesiumNativeTests/SimpleTaskProcessor.h>
|
|
#include <CesiumUtility/ExtensibleObject.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <rapidjson/document.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace CesiumNativeTests;
|
|
|
|
namespace {
|
|
void check(const std::string& input, const std::string& expectedOutput) {
|
|
Cesium3DTilesReader::SubtreeReader reader;
|
|
auto readResult = reader.readFromJson(std::span(
|
|
reinterpret_cast<const std::byte*>(input.c_str()),
|
|
input.size()));
|
|
REQUIRE(readResult.errors.empty());
|
|
REQUIRE(readResult.warnings.empty());
|
|
REQUIRE(readResult.value.has_value());
|
|
|
|
Cesium3DTiles::Subtree& subtree = readResult.value.value();
|
|
|
|
Cesium3DTilesWriter::SubtreeWriter writer;
|
|
Cesium3DTilesWriter::SubtreeWriterResult writeResult =
|
|
writer.writeSubtreeJson(subtree);
|
|
const auto subtreeBytes = writeResult.subtreeBytes;
|
|
|
|
REQUIRE(writeResult.errors.empty());
|
|
REQUIRE(writeResult.warnings.empty());
|
|
|
|
const std::string subtreeString(
|
|
reinterpret_cast<const char*>(subtreeBytes.data()),
|
|
subtreeBytes.size());
|
|
|
|
rapidjson::Document subtreeJson;
|
|
subtreeJson.Parse(subtreeString.c_str());
|
|
|
|
rapidjson::Document expectedJson;
|
|
expectedJson.Parse(expectedOutput.c_str());
|
|
|
|
REQUIRE(subtreeJson == expectedJson);
|
|
}
|
|
|
|
bool hasSpaces(const std::string& input) {
|
|
return std::count_if(input.begin(), input.end(), [](unsigned char c) {
|
|
return std::isspace(c);
|
|
});
|
|
}
|
|
|
|
struct ExtensionSubtreeTest final : public CesiumUtility::ExtensibleObject {
|
|
static inline constexpr const char* ExtensionName = "PRIVATE_subtree_test";
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Writes subtree JSON") {
|
|
std::string string = R"(
|
|
{
|
|
"buffers": [
|
|
{
|
|
"name": "Availability Buffer",
|
|
"uri": "availability.bin",
|
|
"byteLength": 48
|
|
},
|
|
{
|
|
"name": "Metadata Buffer",
|
|
"uri": "metadata.bin",
|
|
"byteLength": 6512
|
|
}
|
|
],
|
|
"bufferViews": [
|
|
{ "buffer": 0, "byteOffset": 0, "byteLength": 11 },
|
|
{ "buffer": 0, "byteOffset": 16, "byteLength": 32 },
|
|
{ "buffer": 1, "byteOffset": 0, "byteLength": 2040 },
|
|
{ "buffer": 1, "byteOffset": 2040, "byteLength": 1530 },
|
|
{ "buffer": 1, "byteOffset": 3576, "byteLength": 344 },
|
|
{ "buffer": 1, "byteOffset": 3920, "byteLength": 1024 },
|
|
{ "buffer": 1, "byteOffset": 4944, "byteLength": 240 },
|
|
{ "buffer": 1, "byteOffset": 5184, "byteLength": 122 },
|
|
{ "buffer": 1, "byteOffset": 5312, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 5792, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 6272, "byteLength": 240 }
|
|
],
|
|
"propertyTables": [
|
|
{
|
|
"class": "tile",
|
|
"count": 85,
|
|
"properties": {
|
|
"horizonOcclusionPoint": {
|
|
"values": 2
|
|
},
|
|
"countries": {
|
|
"values": 3,
|
|
"arrayOffsets": 4,
|
|
"stringOffsets": 5
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"class": "content",
|
|
"count": 60,
|
|
"properties": {
|
|
"attributionIds": {
|
|
"values": 6,
|
|
"arrayOffsets": 7,
|
|
"arrayOffsetType": "UINT16"
|
|
},
|
|
"minimumHeight": {
|
|
"values": 8
|
|
},
|
|
"maximumHeight": {
|
|
"values": 9
|
|
},
|
|
"triangleCount": {
|
|
"values": 10,
|
|
"min": 520,
|
|
"max": 31902
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"tileAvailability": {
|
|
"constant": 1
|
|
},
|
|
"contentAvailability": [{
|
|
"bitstream": 0,
|
|
"availableCount": 60
|
|
}],
|
|
"childSubtreeAvailability": {
|
|
"bitstream": 1
|
|
},
|
|
"tileMetadata": 0,
|
|
"contentMetadata": [1],
|
|
"subtreeMetadata": {
|
|
"class": "subtree",
|
|
"properties": {
|
|
"attributionStrings": [
|
|
"Source A",
|
|
"Source B",
|
|
"Source C",
|
|
"Source D"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
check(string, string);
|
|
}
|
|
|
|
TEST_CASE("Writes subtree JSON with extras") {
|
|
std::string string = R"(
|
|
{
|
|
"tileAvailability": {
|
|
"constant": 1
|
|
},
|
|
"contentAvailability": [{
|
|
"constant": 1
|
|
}],
|
|
"childSubtreeAvailability": {
|
|
"constant": 1
|
|
},
|
|
"extras": {
|
|
"A": "Hello",
|
|
"B": 1234567,
|
|
"C": {
|
|
"C1": {},
|
|
"C2": [1,2,3,4,5],
|
|
"C3": true
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
check(string, string);
|
|
}
|
|
|
|
TEST_CASE("Writes subtree JSON with custom extension") {
|
|
std::string string = R"(
|
|
{
|
|
"tileAvailability": {
|
|
"constant": 1
|
|
},
|
|
"contentAvailability": [{
|
|
"constant": 1
|
|
}],
|
|
"childSubtreeAvailability": {
|
|
"constant": 1
|
|
},
|
|
"extensions": {
|
|
"A": {
|
|
"test": "Hello"
|
|
},
|
|
"B": {
|
|
"another": "Goodbye"
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
check(string, string);
|
|
}
|
|
|
|
TEST_CASE("Writes subtree JSON with unregistered extension") {
|
|
Cesium3DTiles::Subtree subtree;
|
|
subtree.addExtension<ExtensionSubtreeTest>();
|
|
|
|
SUBCASE("Reports a warning if the extension is enabled") {
|
|
Cesium3DTilesWriter::SubtreeWriter writer;
|
|
Cesium3DTilesWriter::SubtreeWriterResult result =
|
|
writer.writeSubtreeJson(subtree);
|
|
REQUIRE(!result.warnings.empty());
|
|
}
|
|
|
|
SUBCASE("Does not report a warning if the extension is disabled") {
|
|
Cesium3DTilesWriter::SubtreeWriter writer;
|
|
writer.getExtensions().setExtensionState(
|
|
ExtensionSubtreeTest::ExtensionName,
|
|
CesiumJsonWriter::ExtensionState::Disabled);
|
|
Cesium3DTilesWriter::SubtreeWriterResult result =
|
|
writer.writeSubtreeJson(subtree);
|
|
REQUIRE(result.warnings.empty());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Writes subtree JSON with default values removed") {
|
|
std::string string = R"(
|
|
{
|
|
"buffers": [
|
|
{
|
|
"name": "Availability Buffer",
|
|
"uri": "availability.bin",
|
|
"byteLength": 48
|
|
},
|
|
{
|
|
"name": "Metadata Buffer",
|
|
"uri": "metadata.bin",
|
|
"byteLength": 6512
|
|
}
|
|
],
|
|
"bufferViews": [
|
|
{ "buffer": 0, "byteOffset": 0, "byteLength": 11 },
|
|
{ "buffer": 0, "byteOffset": 16, "byteLength": 32 },
|
|
{ "buffer": 1, "byteOffset": 0, "byteLength": 2040 },
|
|
{ "buffer": 1, "byteOffset": 2040, "byteLength": 1530 },
|
|
{ "buffer": 1, "byteOffset": 3576, "byteLength": 344 },
|
|
{ "buffer": 1, "byteOffset": 3920, "byteLength": 1024 },
|
|
{ "buffer": 1, "byteOffset": 4944, "byteLength": 240 },
|
|
{ "buffer": 1, "byteOffset": 5184, "byteLength": 122 },
|
|
{ "buffer": 1, "byteOffset": 5312, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 5792, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 6272, "byteLength": 240 }
|
|
],
|
|
"propertyTables": [
|
|
{
|
|
"class": "tile",
|
|
"count": 85,
|
|
"properties": {
|
|
"horizonOcclusionPoint": {
|
|
"values": 2
|
|
},
|
|
"countries": {
|
|
"values": 3,
|
|
"arrayOffsets": 4,
|
|
"stringOffsets": 5,
|
|
"arrayOffsetType": "UINT32",
|
|
"stringOffsetType": "UINT32"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"class": "content",
|
|
"count": 60,
|
|
"properties": {
|
|
"attributionIds": {
|
|
"values": 6,
|
|
"arrayOffsets": 7,
|
|
"arrayOffsetType": "UINT16"
|
|
},
|
|
"minimumHeight": {
|
|
"values": 8
|
|
},
|
|
"maximumHeight": {
|
|
"values": 9
|
|
},
|
|
"triangleCount": {
|
|
"values": 10,
|
|
"min": 520,
|
|
"max": 31902
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"tileAvailability": {
|
|
"constant": 1
|
|
},
|
|
"contentAvailability": [{
|
|
"bitstream": 0,
|
|
"availableCount": 60
|
|
}],
|
|
"childSubtreeAvailability": {
|
|
"bitstream": 1
|
|
},
|
|
"tileMetadata": 0,
|
|
"contentMetadata": [1],
|
|
"subtreeMetadata": {
|
|
"class": "subtree",
|
|
"properties": {
|
|
"attributionStrings": [
|
|
"Source A",
|
|
"Source B",
|
|
"Source C",
|
|
"Source D"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
std::string expected = R"(
|
|
{
|
|
"buffers": [
|
|
{
|
|
"name": "Availability Buffer",
|
|
"uri": "availability.bin",
|
|
"byteLength": 48
|
|
},
|
|
{
|
|
"name": "Metadata Buffer",
|
|
"uri": "metadata.bin",
|
|
"byteLength": 6512
|
|
}
|
|
],
|
|
"bufferViews": [
|
|
{ "buffer": 0, "byteOffset": 0, "byteLength": 11 },
|
|
{ "buffer": 0, "byteOffset": 16, "byteLength": 32 },
|
|
{ "buffer": 1, "byteOffset": 0, "byteLength": 2040 },
|
|
{ "buffer": 1, "byteOffset": 2040, "byteLength": 1530 },
|
|
{ "buffer": 1, "byteOffset": 3576, "byteLength": 344 },
|
|
{ "buffer": 1, "byteOffset": 3920, "byteLength": 1024 },
|
|
{ "buffer": 1, "byteOffset": 4944, "byteLength": 240 },
|
|
{ "buffer": 1, "byteOffset": 5184, "byteLength": 122 },
|
|
{ "buffer": 1, "byteOffset": 5312, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 5792, "byteLength": 480 },
|
|
{ "buffer": 1, "byteOffset": 6272, "byteLength": 240 }
|
|
],
|
|
"propertyTables": [
|
|
{
|
|
"class": "tile",
|
|
"count": 85,
|
|
"properties": {
|
|
"horizonOcclusionPoint": {
|
|
"values": 2
|
|
},
|
|
"countries": {
|
|
"values": 3,
|
|
"arrayOffsets": 4,
|
|
"stringOffsets": 5
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"class": "content",
|
|
"count": 60,
|
|
"properties": {
|
|
"attributionIds": {
|
|
"values": 6,
|
|
"arrayOffsets": 7,
|
|
"arrayOffsetType": "UINT16"
|
|
},
|
|
"minimumHeight": {
|
|
"values": 8
|
|
},
|
|
"maximumHeight": {
|
|
"values": 9
|
|
},
|
|
"triangleCount": {
|
|
"values": 10,
|
|
"min": 520,
|
|
"max": 31902
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"tileAvailability": {
|
|
"constant": 1
|
|
},
|
|
"contentAvailability": [{
|
|
"bitstream": 0,
|
|
"availableCount": 60
|
|
}],
|
|
"childSubtreeAvailability": {
|
|
"bitstream": 1
|
|
},
|
|
"tileMetadata": 0,
|
|
"contentMetadata": [1],
|
|
"subtreeMetadata": {
|
|
"class": "subtree",
|
|
"properties": {
|
|
"attributionStrings": [
|
|
"Source A",
|
|
"Source B",
|
|
"Source C",
|
|
"Source D"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
check(string, expected);
|
|
}
|
|
|
|
TEST_CASE("Writes subtree JSON with prettyPrint") {
|
|
Cesium3DTiles::Subtree subtree;
|
|
|
|
Cesium3DTilesWriter::SubtreeWriter writer;
|
|
Cesium3DTilesWriter::SubtreeWriterOptions options;
|
|
options.prettyPrint = false;
|
|
|
|
Cesium3DTilesWriter::SubtreeWriterResult writeResult =
|
|
writer.writeSubtreeJson(subtree, options);
|
|
const std::vector<std::byte>& subtreeBytesCompact = writeResult.subtreeBytes;
|
|
|
|
std::string subtreeStringCompact(
|
|
reinterpret_cast<const char*>(subtreeBytesCompact.data()),
|
|
subtreeBytesCompact.size());
|
|
|
|
REQUIRE_FALSE(hasSpaces(subtreeStringCompact));
|
|
|
|
options.prettyPrint = true;
|
|
writeResult = writer.writeSubtreeJson(subtree, options);
|
|
const std::vector<std::byte>& subtreeBytesPretty = writeResult.subtreeBytes;
|
|
std::string subtreeStringPretty(
|
|
reinterpret_cast<const char*>(subtreeBytesPretty.data()),
|
|
subtreeBytesPretty.size());
|
|
|
|
REQUIRE(hasSpaces(subtreeStringPretty));
|
|
}
|
|
|
|
TEST_CASE("Writes subtree binary") {
|
|
const std::vector<std::byte> bufferData{
|
|
std::byte('H'),
|
|
std::byte('e'),
|
|
std::byte('l'),
|
|
std::byte('l'),
|
|
std::byte('o'),
|
|
std::byte('W'),
|
|
std::byte('o'),
|
|
std::byte('r'),
|
|
std::byte('l'),
|
|
std::byte('d'),
|
|
std::byte('!')};
|
|
|
|
Cesium3DTiles::Subtree subtree;
|
|
Cesium3DTiles::Buffer buffer;
|
|
buffer.byteLength = static_cast<int64_t>(bufferData.size());
|
|
subtree.buffers.push_back(buffer);
|
|
|
|
Cesium3DTilesWriter::SubtreeWriter writer;
|
|
Cesium3DTilesWriter::SubtreeWriterResult writeResult =
|
|
writer.writeSubtreeBinary(subtree, std::span(bufferData));
|
|
const std::vector<std::byte>& subtreeBytes = writeResult.subtreeBytes;
|
|
|
|
REQUIRE(writeResult.errors.empty());
|
|
REQUIRE(writeResult.warnings.empty());
|
|
|
|
// Now read the subtree back
|
|
auto pMockTaskProcessor = std::make_shared<SimpleTaskProcessor>();
|
|
CesiumAsync::AsyncSystem asyncSystem{pMockTaskProcessor};
|
|
|
|
auto pMockSubtreeResponse = std::make_unique<SimpleAssetResponse>(
|
|
uint16_t(200),
|
|
"0.subtree",
|
|
CesiumAsync::HttpHeaders{},
|
|
subtreeBytes);
|
|
|
|
auto pMockSubtreeRequest = std::make_unique<SimpleAssetRequest>(
|
|
"GET",
|
|
"0.subtree",
|
|
CesiumAsync::HttpHeaders{},
|
|
std::move(pMockSubtreeResponse));
|
|
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>> mapUrlToRequest{
|
|
{"0.subtree", std::move(pMockSubtreeRequest)}};
|
|
|
|
auto pMockAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(std::move(mapUrlToRequest));
|
|
|
|
Cesium3DTilesReader::SubtreeFileReader reader;
|
|
CesiumJsonReader::ReadJsonResult<Cesium3DTiles::Subtree> readResult =
|
|
reader.load(asyncSystem, pMockAssetAccessor, "0.subtree")
|
|
.waitInMainThread();
|
|
|
|
REQUIRE(readResult.errors.empty());
|
|
REQUIRE(readResult.warnings.empty());
|
|
REQUIRE(readResult.value.has_value());
|
|
|
|
Cesium3DTiles::Subtree& readSubtree = readResult.value.value();
|
|
const std::vector<std::byte> readSubtreeBuffer =
|
|
readSubtree.buffers[0].cesium.data;
|
|
|
|
REQUIRE(readSubtreeBuffer == bufferData);
|
|
REQUIRE(readSubtree.buffers[0].byteLength == 11);
|
|
}
|