diff --git a/.vscode/launch.json b/.vscode/launch.json index 78671df7d..02fdb5d9f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,7 @@ { "name": "Test", "program": "${workspaceFolder}/build-debug/CesiumNativeTests/cesium-native-tests", + "args": ["*custom*"], "cwd": "${workspaceFolder}", "type": "cppdbg", "request": "launch", diff --git a/Cesium3DTilesReader/test/TestReader.cpp b/Cesium3DTilesReader/test/TestReader.cpp index 070054559..a46e123c9 100644 --- a/Cesium3DTilesReader/test/TestReader.cpp +++ b/Cesium3DTilesReader/test/TestReader.cpp @@ -30,7 +30,7 @@ std::vector readFile(const std::filesystem::path& fileName) { } } // namespace -TEST_CASE("Cesium3DTiles::TilesetReader") { +TEST_CASE("Reads tileset JSON") { using namespace std::string_literals; std::filesystem::path tilesetFile = Cesium3DTilesReader_TEST_DATA_DIR; @@ -118,7 +118,87 @@ TEST_CASE("Cesium3DTiles::TilesetReader") { CHECK_FALSE(child.viewerRequestVolume); } -TEST_CASE("Can deserialize 3DTILES_content_gltf") { +TEST_CASE("Reads tileset JSON with extras") { + std::string s = R"( + { + "asset": { + "version": "1.0" + }, + "geometricError": 45.0, + "root": { + "boundingVolume": { + "box": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] + }, + "geometricError": 15.0, + "refine": "ADD", + "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + "extras": { + "D": "Goodbye" + } + }, + "extras": { + "A": "Hello", + "B": 1234567, + "C": { + "C1": {}, + "C2": [1,2,3,4,5], + "C3": true + } + } + } + )"; + + Cesium3DTiles::TilesetReader reader; + TilesetReaderResult result = reader.readTileset( + gsl::span(reinterpret_cast(s.c_str()), s.size())); + REQUIRE(result.errors.empty()); + REQUIRE(result.warnings.empty()); + REQUIRE(result.tileset.has_value()); + + Tileset& tileset = result.tileset.value(); + + auto ait = tileset.extras.find("A"); + REQUIRE(ait != tileset.extras.end()); + CHECK(ait->second.isString()); + CHECK(ait->second.getStringOrDefault("") == "Hello"); + + auto bit = tileset.extras.find("B"); + REQUIRE(bit != tileset.extras.end()); + CHECK(bit->second.isNumber()); + CHECK(bit->second.getUint64() == 1234567); + + auto cit = tileset.extras.find("C"); + REQUIRE(cit != tileset.extras.end()); + + JsonValue* pC1 = cit->second.getValuePtrForKey("C1"); + REQUIRE(pC1 != nullptr); + CHECK(pC1->isObject()); + CHECK(pC1->getObject().empty()); + + JsonValue* pC2 = cit->second.getValuePtrForKey("C2"); + REQUIRE(pC2 != nullptr); + + CHECK(pC2->isArray()); + JsonValue::Array array = pC2->getArray(); + CHECK(array.size() == 5); + CHECK(array[0].getSafeNumber() == 1.0); + CHECK(array[1].getSafeNumber() == 2); + CHECK(array[2].getSafeNumber() == 3); + CHECK(array[3].getSafeNumber() == 4); + CHECK(array[4].getSafeNumber() == 5); + + JsonValue* pC3 = cit->second.getValuePtrForKey("C3"); + REQUIRE(pC3 != nullptr); + CHECK(pC3->isBool()); + CHECK(pC3->getBool()); + + auto dit = tileset.root.extras.find("D"); + REQUIRE(dit != tileset.root.extras.end()); + CHECK(dit->second.isString()); + CHECK(dit->second.getStringOrDefault("") == "Goodbye"); +} + +TEST_CASE("Reads tileset JSON with 3DTILES_content_gltf extension") { std::string s = R"( { "asset": { @@ -176,7 +256,7 @@ TEST_CASE("Can deserialize 3DTILES_content_gltf") { CHECK(contentGltf->extensionsRequired == gltfExtensionsRequired); } -TEST_CASE("Can deserialize custom extension") { +TEST_CASE("Reads tileset JSON with custom extension") { std::string s = R"( { "asset": { @@ -184,10 +264,10 @@ TEST_CASE("Can deserialize custom extension") { }, "extensions": { "A": { - "test": "Hello World" + "test": "Hello" }, "B": { - "another": "Goodbye World" + "another": "Goodbye" } } } @@ -207,13 +287,11 @@ TEST_CASE("Can deserialize custom extension") { REQUIRE(pB != nullptr); REQUIRE(pA->getValuePtrForKey("test")); - REQUIRE( - pA->getValuePtrForKey("test")->getStringOrDefault("") == "Hello World"); + REQUIRE(pA->getValuePtrForKey("test")->getStringOrDefault("") == "Hello"); REQUIRE(pB->getValuePtrForKey("another")); REQUIRE( - pB->getValuePtrForKey("another")->getStringOrDefault("") == - "Goodbye World"); + pB->getValuePtrForKey("another")->getStringOrDefault("") == "Goodbye"); // Repeat test but this time the extension should be skipped. reader.getExtensions().setExtensionState( diff --git a/Cesium3DTilesWriter/generated/TilesetWriter.cpp b/Cesium3DTilesWriter/generated/TilesetWriter.cpp index e014c3a1b..a7c2bbec0 100644 --- a/Cesium3DTilesWriter/generated/TilesetWriter.cpp +++ b/Cesium3DTilesWriter/generated/TilesetWriter.cpp @@ -189,7 +189,6 @@ void writeJson( } if (!obj.extensions.empty()) { - jsonWriter.Key("extensions"); writeJsonExtensions(obj, jsonWriter, context); } diff --git a/Cesium3DTilesWriter/generated/TilesetWriter.h b/Cesium3DTilesWriter/generated/TilesetWriter.h index 88b98c923..d57d691d8 100644 --- a/Cesium3DTilesWriter/generated/TilesetWriter.h +++ b/Cesium3DTilesWriter/generated/TilesetWriter.h @@ -10,10 +10,14 @@ namespace Cesium3DTiles { struct Extension3dTilesContentGltfWriter { + using ValueType = Extension3dTilesContentGltf; + static void write( const Extension3dTilesContentGltf& obj, CesiumJsonWriter::JsonWriter& jsonWriter, const CesiumJsonWriter::ExtensionWriterContext& context); + + static inline constexpr const char* ExtensionName = "3DTILES_content_gltf"; }; struct TilesetWriter { diff --git a/Cesium3DTilesWriter/src/Cesium3DTilesWriter.cpp b/Cesium3DTilesWriter/src/Cesium3DTilesWriter.cpp index 5ac20a6a7..913593c8b 100644 --- a/Cesium3DTilesWriter/src/Cesium3DTilesWriter.cpp +++ b/Cesium3DTilesWriter/src/Cesium3DTilesWriter.cpp @@ -11,8 +11,8 @@ using namespace CesiumJsonWriter; using namespace CesiumUtility; Cesium3DTilesWriter::Cesium3DTilesWriter() { - this->_context - .registerExtension(); + this->_context + .registerExtension(); } CesiumJsonWriter::ExtensionWriterContext& Cesium3DTilesWriter::getExtensions() { diff --git a/Cesium3DTilesWriter/test/TestTilesetWriter.cpp b/Cesium3DTilesWriter/test/TestTilesetWriter.cpp index 4d54863ca..6f3b2219b 100644 --- a/Cesium3DTilesWriter/test/TestTilesetWriter.cpp +++ b/Cesium3DTilesWriter/test/TestTilesetWriter.cpp @@ -1,6 +1,7 @@ #include "Cesium3DTiles/Cesium3DTilesWriter.h" #include +#include #include @@ -8,74 +9,43 @@ using namespace Cesium3DTiles; using namespace CesiumUtility; namespace { -std::string removeWhitespace(const std::string& string) { - std::string compact = string; +std::string removeWhitespace(const std::string& s) { + std::string compact = s; compact.erase( remove_if(compact.begin(), compact.end(), isspace), compact.end()); return compact; } -} // namespace - -TEST_CASE("Writes tileset JSON") { - using namespace std::string_literals; - - Content content1; - content1.uri = "1.gltf"; - - BoundingVolume contentBoundingVolume; - contentBoundingVolume.sphere = {30, 31, 32, 33}; - - Content content2; - content2.uri = "2.gltf"; - content2.boundingVolume = contentBoundingVolume; - - Tile tile1, tile2, tile3; - tile1.boundingVolume.box = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - tile1.geometricError = 15.0; - tile1.refine = Tile::Refine::ADD; - tile1.content = content1; - - tile2.boundingVolume.box = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}; - tile2.viewerRequestVolume = BoundingVolume(); - tile2.viewerRequestVolume - ->box = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}; - tile2.geometricError = 25.0; - tile2.content = content2; - - tile3.boundingVolume.box = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - tile3.geometricError = 35.0; - tile3.children = {tile1, tile2}; - tile3.refine = Tile::Refine::REPLACE; +void check(const std::string& input, const std::string& expectedOutput) { + Cesium3DTiles::TilesetReader reader; + TilesetReaderResult readResult = reader.readTileset(gsl::span( + reinterpret_cast(input.c_str()), + input.size())); + REQUIRE(readResult.errors.empty()); + REQUIRE(readResult.warnings.empty()); + REQUIRE(readResult.tileset.has_value()); - Asset asset; - asset.version = "1.0"; - asset.tilesetVersion = "1.2.3"; + Tileset& tileset = readResult.tileset.value(); - Properties property1, property2; - property1.maximum = 10.0; - property1.minimum = 0.0; - property2.maximum = 5.0; - property2.minimum = 1.0; + Cesium3DTilesWriter writer; + TilesetWriterResult writeResult = writer.writeTileset(tileset); + const auto asBytes = writeResult.tilesetBytes; - std::map properties = { - {"property1", property1}, - {"property2", property2}}; + REQUIRE(writeResult.errors.empty()); + REQUIRE(writeResult.warnings.empty()); - Tileset tileset; - tileset.asset = asset; - tileset.root = tile3; - tileset.properties = properties; - tileset.geometricError = 45.0; + std::string expectedString = removeWhitespace(expectedOutput); - Cesium3DTilesWriter writer; - TilesetWriterResult result = writer.writeTileset(tileset); - const auto asBytes = result.tilesetBytes; + const std::string extractedString( + reinterpret_cast(asBytes.data()), + asBytes.size()); - REQUIRE(result.errors.empty()); - REQUIRE(result.warnings.empty()); + REQUIRE(expectedString == extractedString); +} +} // namespace - std::string expectedString = removeWhitespace(R"( +TEST_CASE("Writes tileset JSON") { + std::string string = R"( { "asset": { "version": "1.0", @@ -94,33 +64,39 @@ TEST_CASE("Writes tileset JSON") { "geometricError": 45.0, "root": { "boundingVolume": { - "box": [20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0] + "box": + [20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0] }, "geometricError": 35.0, "refine": "REPLACE", - "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - "children": [ + "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0], "children": [ { "boundingVolume": { - "box": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] + "box": + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }, "geometricError": 15.0, "refine": "ADD", - "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - "content": { + "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], "content": { "uri": "1.gltf" } }, { "boundingVolume": { - "box": [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0] + "box": + [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, + 21.0] }, "viewerRequestVolume": { - "box": [30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0] + "box": + [30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, + 41.0] }, "geometricError": 25.0, - "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - "content": { + "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], "content": { "boundingVolume": { "sphere": [30.0, 31.0, 32.0, 33.0] }, @@ -130,47 +106,13 @@ TEST_CASE("Writes tileset JSON") { ] } } - )"); - - const std::string extractedString( - reinterpret_cast(asBytes.data()), - asBytes.size()); + )"; - REQUIRE(expectedString == extractedString); + check(string, string); } TEST_CASE("Writes tileset JSON with extras") { - using namespace std::string_literals; - - Tile root; - root.boundingVolume.box = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - root.geometricError = 15.0; - root.refine = Tile::Refine::ADD; - root.extras = {{"year", 2001}, {"resolution", 0.5}}; - - Asset asset; - asset.version = "1.0"; - - Tileset tileset; - tileset.asset = asset; - tileset.root = root; - tileset.geometricError = 45.0; - - std::map nestedExtras = { - {"type", "tileset"}, - {"authors", {"A", "B", "C"}}, - {"year", 2000}}; - - tileset.extras = {{"final", true}, {"info", nestedExtras}}; - - Cesium3DTilesWriter writer; - TilesetWriterResult result = writer.writeTileset(tileset); - const auto asBytes = result.tilesetBytes; - - CHECK(result.errors.empty()); - CHECK(result.warnings.empty()); - - std::string expectedString = removeWhitespace(R"( + std::string string = R"( { "asset": { "version": "1.0" @@ -184,65 +126,26 @@ TEST_CASE("Writes tileset JSON with extras") { "refine": "ADD", "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], "extras": { - "resolution": 0.5, - "year": 2001 + "D": "Goodbye" } }, "extras": { - "final": true, - "info": { - "authors": ["A","B","C"], - "type": "tileset", - "year": 2000 + "A": "Hello", + "B": 1234567, + "C": { + "C1": {}, + "C2": [1,2,3,4,5], + "C3": true } } } - )"); + )"; - const std::string extractedString( - reinterpret_cast(asBytes.data()), - asBytes.size()); - - REQUIRE(expectedString == extractedString); + check(string, string); } TEST_CASE("Writes tileset JSON with 3DTILES_content_gltf extension") { - using namespace std::string_literals; - - Content content; - content.uri = "root.glb"; - - Tile root; - root.boundingVolume.box = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - root.geometricError = 15.0; - root.refine = Tile::Refine::ADD; - root.content = content; - - Asset asset; - asset.version = "1.0"; - - Tileset tileset; - tileset.asset = asset; - tileset.root = root; - tileset.geometricError = 45.0; - tileset.extensionsUsed = {"3DTILES_content_gltf"}; - tileset.extensionsRequired = {"3DTILES_content_gltf"}; - - Extension3dTilesContentGltf& contentGltfExtension = - tileset.addExtension(); - contentGltfExtension.extensionsUsed = { - "KHR_draco_mesh_compression", - "KHR_materials_unlit"}; - contentGltfExtension.extensionsRequired = {"KHR_draco_mesh_compression"}; - - Cesium3DTilesWriter writer; - TilesetWriterResult result = writer.writeTileset(tileset); - const auto asBytes = result.tilesetBytes; - - CHECK(result.errors.empty()); - CHECK(result.warnings.empty()); - - std::string expectedString = removeWhitespace(R"( + std::string string = R"( { "asset": { "version": "1.0" @@ -255,25 +158,59 @@ TEST_CASE("Writes tileset JSON with 3DTILES_content_gltf extension") { "geometricError": 15.0, "refine": "ADD", "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - "extras": { - "resolution": 0.5, - "year": 2001 + "content": { + "uri": "root.glb" } }, - "extras": { - "final": true, - "info": { - "authors": ["A","B","C"], - "type": "tileset", - "year": 2000 + "extensionsUsed": [ + "3DTILES_content_gltf" + ], + "extensionsRequired": [ + "3DTILES_content_gltf" + ], + "extensions": { + "3DTILES_content_gltf": { + "extensionsUsed": [ + "KHR_draco_mesh_compression", + "KHR_materials_unlit" + ], + "extensionsRequired": [ + "KHR_draco_mesh_compression" + ] } } } - )"); + )"; - const std::string extractedString( - reinterpret_cast(asBytes.data()), - asBytes.size()); + check(string, string); +} - REQUIRE(expectedString == extractedString); +TEST_CASE("Writes tileset JSON with custom extension") { + std::string string = R"( + { + "asset": { + "version": "1.0" + }, + "geometricError": 45.0, + "root": { + "boundingVolume": { + "box": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] + }, + "geometricError": 15.0, + "refine": "ADD", + "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] + }, + "extensionsUsed": ["A", "B"], + "extensions": { + "A": { + "test": "Hello" + }, + "B": { + "another": "Goodbye" + } + } + } + )"; + + check(string, string); } diff --git a/CesiumGltfReader/test/TestReader.cpp b/CesiumGltfReader/test/TestReader.cpp index 2c82e7656..2a6c5a258 100644 --- a/CesiumGltfReader/test/TestReader.cpp +++ b/CesiumGltfReader/test/TestReader.cpp @@ -141,7 +141,7 @@ TEST_CASE("Read BoxTexturedWebp (with error messages)") { REQUIRE(result.errors.size() > 0); } -TEST_CASE("Nested extras serializes properly") { +TEST_CASE("Nested extras deserializes properly") { const std::string s = R"( { "asset" : { diff --git a/CesiumJsonWriter/include/CesiumJsonWriter/writeJsonExtensions.h b/CesiumJsonWriter/include/CesiumJsonWriter/writeJsonExtensions.h index 2118afbba..667828966 100644 --- a/CesiumJsonWriter/include/CesiumJsonWriter/writeJsonExtensions.h +++ b/CesiumJsonWriter/include/CesiumJsonWriter/writeJsonExtensions.h @@ -9,15 +9,24 @@ void writeJsonExtensions( const TExtended& obj, JsonWriter& jsonWriter, const ExtensionWriterContext& context) { - jsonWriter.StartObject(); + bool startedObject = false; + for (const auto& item : obj.extensions) { auto handler = context.createExtensionHandler(item.first, TExtended::TypeName); - if (!handler) + if (!handler) { continue; + } + if (!startedObject) { + jsonWriter.Key("extensions"); + jsonWriter.StartObject(); + startedObject = true; + } jsonWriter.Key(item.first); handler(item.second, jsonWriter, context); } - jsonWriter.EndObject(); + if (startedObject) { + jsonWriter.EndObject(); + } } } // namespace CesiumJsonWriter diff --git a/CesiumJsonWriter/src/ExtensionWriterContext.cpp b/CesiumJsonWriter/src/ExtensionWriterContext.cpp index 99a34a53d..6b9eae9a8 100644 --- a/CesiumJsonWriter/src/ExtensionWriterContext.cpp +++ b/CesiumJsonWriter/src/ExtensionWriterContext.cpp @@ -1,11 +1,20 @@ #include "CesiumJsonWriter/ExtensionWriterContext.h" +#include "CesiumJsonWriter/JsonObjectWriter.h" #include "CesiumJsonWriter/JsonWriter.h" +#include + using namespace CesiumJsonWriter; +using namespace CesiumUtility; namespace { -void noOpWriter(const std::any&, JsonWriter&, const ExtensionWriterContext&) {} +void objWriter( + const std::any& obj, + JsonWriter& jsonWriter, + const ExtensionWriterContext& /* context */) { + writeJsonValue(std::any_cast(obj), jsonWriter); +} } // namespace ExtensionWriterContext::ExtensionHandler @@ -18,18 +27,20 @@ ExtensionWriterContext::createExtensionHandler( auto stateIt = this->_extensionStates.find(extensionNameString); if (stateIt != this->_extensionStates.end()) { if (stateIt->second == ExtensionState::Disabled) { - return noOpWriter; + return nullptr; + } else if (stateIt->second == ExtensionState::JsonOnly) { + return objWriter; } } auto extensionNameIt = this->_extensions.find(extensionNameString); if (extensionNameIt == this->_extensions.end()) { - return noOpWriter; + return objWriter; } auto objectTypeIt = extensionNameIt->second.find(extendedObjectType); if (objectTypeIt == extensionNameIt->second.end()) { - return noOpWriter; + return objWriter; } return objectTypeIt->second; diff --git a/CesiumUtility/include/CesiumUtility/ExtensibleObject.h b/CesiumUtility/include/CesiumUtility/ExtensibleObject.h index 44daec94d..0f6e7eb1f 100644 --- a/CesiumUtility/include/CesiumUtility/ExtensibleObject.h +++ b/CesiumUtility/include/CesiumUtility/ExtensibleObject.h @@ -38,7 +38,7 @@ struct CESIUMUTILITY_API ExtensibleObject { * @brief Gets a generic extension with the given name as a * {@link CesiumUtility::JsonValue}. * - * If the extension exists but has a static type, this method will retur + * If the extension exists but has a static type, this method will return * nullptr. Use {@link getExtension} to retrieve a statically-typed extension. * * @param extensionName The name of the extension.