diff --git a/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h b/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h index 92aa5af56..de4ff8a72 100644 --- a/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h +++ b/CesiumGltfReader/include/CesiumGltfReader/GltfReader.h @@ -159,6 +159,24 @@ class CESIUMGLTFREADER_API GltfReader { const gsl::span& data, const GltfReaderOptions& options = GltfReaderOptions()) const; + /** + * @brief Reads a glTF or binary glTF file from a URL and resolves external + * buffers and images. + * + * @param asyncSystem The async system to use for resolving external data. + * @param url The url for reading the file. + * @param headers http headers needed to make the request. + * @param pAssetAccessor The asset accessor to use to make the necessary + * requests. + * @param options Options for how to read the glTF. + */ + CesiumAsync::Future loadGltf( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::string& url, + const std::vector& headers, + const std::shared_ptr& pAssetAccessor, + const GltfReaderOptions& options = GltfReaderOptions()) const; + /** * @brief Accepts the result of {@link readGltf} and resolves any remaining * external buffers and images. diff --git a/CesiumGltfReader/src/GltfReader.cpp b/CesiumGltfReader/src/GltfReader.cpp index 4ddaba649..b6833120d 100644 --- a/CesiumGltfReader/src/GltfReader.cpp +++ b/CesiumGltfReader/src/GltfReader.cpp @@ -373,6 +373,61 @@ GltfReaderResult GltfReader::readGltf( return result; } +CesiumAsync::Future GltfReader::loadGltf( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::string& uri, + const std::vector& headers, + const std::shared_ptr& pAssetAccessor, + const GltfReaderOptions& options) const { + return pAssetAccessor->get(asyncSystem, uri, headers) + .thenInWorkerThread( + [this, options, asyncSystem, pAssetAccessor, uri]( + std::shared_ptr&& pRequest) { + const CesiumAsync::IAssetResponse* pResponse = pRequest->response(); + + if (!pResponse) { + return asyncSystem.createResolvedFuture(GltfReaderResult{ + std::nullopt, + {fmt::format("Request for {} failed.", uri)}, + {}}); + } + + uint16_t statusCode = pResponse->statusCode(); + if (statusCode != 0 && (statusCode < 200 || statusCode >= 300)) { + return asyncSystem.createResolvedFuture(GltfReaderResult{ + std::nullopt, + {fmt::format( + "Request for {} failed with code {}", + uri, + pResponse->statusCode())}, + {}}); + } + + const CesiumJsonReader::JsonReaderOptions& context = + this->getExtensions(); + GltfReaderResult result = + isBinaryGltf(pResponse->data()) + ? readBinaryGltf(context, pResponse->data()) + : readJsonGltf(context, pResponse->data()); + + if (!result.model) { + return asyncSystem.createResolvedFuture(std::move(result)); + } + + return resolveExternalData( + asyncSystem, + uri, + pRequest->headers(), + pAssetAccessor, + options, + std::move(result)); + }) + .thenInWorkerThread([options, this](GltfReaderResult&& result) { + postprocess(*this, result, options); + return std::move(result); + }); +} + /*static*/ Future GltfReader::resolveExternalData( AsyncSystem asyncSystem, diff --git a/CesiumGltfReader/test/TestGltfReader.cpp b/CesiumGltfReader/test/TestGltfReader.cpp index 24453ae10..d430f91d5 100644 --- a/CesiumGltfReader/test/TestGltfReader.cpp +++ b/CesiumGltfReader/test/TestGltfReader.cpp @@ -1,8 +1,12 @@ #include "CesiumGltfReader/GltfReader.h" +#include #include #include #include +#include +#include +#include #include #include @@ -15,9 +19,11 @@ #include #include +using namespace CesiumAsync; using namespace CesiumGltf; using namespace CesiumGltfReader; using namespace CesiumUtility; +using namespace CesiumNativeTests; namespace { std::vector readFile(const std::filesystem::path& fileName) { @@ -681,3 +687,61 @@ TEST_CASE("Decodes images with data uris") { CHECK(image.height == 256); CHECK(!image.pixelData.empty()); } + +TEST_CASE("GltfReader::loadGltf") { + auto pMockTaskProcessor = std::make_shared(); + CesiumAsync::AsyncSystem asyncSystem{pMockTaskProcessor}; + + std::filesystem::path dataDir(CesiumGltfReader_TEST_DATA_DIR); + + std::map> mapUrlToRequest; + + for (const auto& entry : std::filesystem::recursive_directory_iterator( + dataDir / "DracoCompressed")) { + if (!entry.is_regular_file()) + continue; + auto pResponse = std::make_unique( + uint16_t(200), + "application/binary", + CesiumAsync::HttpHeaders{}, + readFile(entry.path())); + std::string url = "file:///" + entry.path().generic_u8string(); + auto pRequest = std::make_unique( + "GET", + url, + CesiumAsync::HttpHeaders{}, + std::move(pResponse)); + mapUrlToRequest[url] = std::move(pRequest); + } + + auto pMockAssetAccessor = + std::make_shared(std::move(mapUrlToRequest)); + + GltfReader reader{}; + Future future = reader.loadGltf( + asyncSystem, + "file:///" + std::filesystem::directory_entry( + dataDir / "DracoCompressed" / "CesiumMilkTruck.gltf") + .path() + .generic_u8string(), + {}, + pMockAssetAccessor); + GltfReaderResult result = waitForFuture(asyncSystem, std::move(future)); + REQUIRE(result.model); + CHECK(result.errors.empty()); + // There will be warnings, because this model has accessors that don't match + // the Draco-decoded size. It seems to be ambiguous whether this is + // technically allowed or not. See: + // https://github.com/KhronosGroup/glTF/issues/1342 + + REQUIRE(result.model->images.size() == 1); + const CesiumGltf::Image& image = result.model->images[0]; + CHECK(image.cesium.width == 2048); + CHECK(image.cesium.height == 2048); + CHECK(image.cesium.pixelData.size() == 2048 * 2048 * 4); + + CHECK(!result.model->buffers.empty()); + for (const CesiumGltf::Buffer& buffer : result.model->buffers) { + CHECK(!buffer.cesium.data.empty()); + } +} diff --git a/CesiumGltfReader/test/data/DracoCompressed/0.bin b/CesiumGltfReader/test/data/DracoCompressed/0.bin new file mode 100644 index 000000000..c4996c515 Binary files /dev/null and b/CesiumGltfReader/test/data/DracoCompressed/0.bin differ diff --git a/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.gltf b/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.gltf new file mode 100644 index 000000000..f9edc433d --- /dev/null +++ b/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.gltf @@ -0,0 +1,592 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0, + "children": [ + 3, + 1 + ], + "matrix": [ + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + -1, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ] + }, + { + "children": [ + 2 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -1.352329969406128, + 0.4277220070362091, + -2.98022992950564e-8, + 1 + ] + }, + { + "mesh": 1, + "rotation": [ + 0, + 0, + 0.08848590403795242, + -0.9960774183273317 + ] + }, + { + "children": [ + 4 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 1.432669997215271, + 0.4277220070362091, + -2.98022992950564e-8, + 1 + ] + }, + { + "mesh": 1, + "rotation": [ + 0, + 0, + 0.08848590403795242, + -0.9960774183273317 + ] + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 5, + "POSITION": 6, + "TEXCOORD_0": 7 + }, + "indices": 4, + "mode": 4, + "material": 0, + "extensions": { + "KHR_draco_mesh_compression": { + "bufferView": 2, + "attributes": { + "NORMAL": 0, + "POSITION": 1, + "TEXCOORD_0": 2 + } + } + } + }, + { + "attributes": { + "NORMAL": 9, + "POSITION": 10 + }, + "indices": 8, + "mode": 4, + "material": 1, + "extensions": { + "KHR_draco_mesh_compression": { + "bufferView": 3, + "attributes": { + "NORMAL": 0, + "POSITION": 1 + } + } + } + }, + { + "attributes": { + "NORMAL": 12, + "POSITION": 13 + }, + "indices": 11, + "mode": 4, + "material": 2, + "extensions": { + "KHR_draco_mesh_compression": { + "bufferView": 4, + "attributes": { + "NORMAL": 0, + "POSITION": 1 + } + } + } + } + ], + "name": "Cesium_Milk_Truck" + }, + { + "primitives": [ + { + "attributes": { + "NORMAL": 15, + "POSITION": 16, + "TEXCOORD_0": 17 + }, + "indices": 14, + "mode": 4, + "material": 3, + "extensions": { + "KHR_draco_mesh_compression": { + "bufferView": 5, + "attributes": { + "NORMAL": 0, + "POSITION": 1, + "TEXCOORD_0": 2 + } + } + } + } + ], + "name": "Wheels" + } + ], + "animations": [ + { + "channels": [ + { + "sampler": 0, + "target": { + "node": 4, + "path": "rotation" + } + }, + { + "sampler": 1, + "target": { + "node": 2, + "path": "rotation" + } + } + ], + "samplers": [ + { + "input": 0, + "interpolation": "LINEAR", + "output": 1 + }, + { + "input": 2, + "interpolation": "LINEAR", + "output": 3 + } + ] + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 31, + "max": [ + 1.25 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 31, + "max": [ + 0, + 0, + 0.9990190863609314, + 1 + ], + "min": [ + 0, + 0, + 0, + -0.9960774183273317 + ], + "type": "VEC4" + }, + { + "bufferView": 0, + "byteOffset": 124, + "componentType": 5126, + "count": 31, + "max": [ + 1.25 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 496, + "componentType": 5126, + "count": 31, + "max": [ + 0, + 0, + 0.9990190863609314, + 1 + ], + "min": [ + 0, + 0, + 0, + -0.9960774183273317 + ], + "type": "VEC4" + }, + { + "componentType": 5123, + "count": 5232, + "max": [ + 1855 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "componentType": 5126, + "count": 1856, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 1856, + "max": [ + 2.437999963760376, + 2.5843698978424072, + 1.3960000276565552 + ], + "min": [ + -2.430910110473633, + 0.2667999863624573, + -1.3960000276565552 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 1856, + "max": [ + 0.8964580297470093, + 0.997245192527771 + ], + "min": [ + 0.002956389915198088, + 0.015672028064727783 + ], + "type": "VEC2" + }, + { + "componentType": 5123, + "count": 168, + "max": [ + 71 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "componentType": 5126, + "count": 72, + "max": [ + 0.957480013370514, + 0.28850099444389343, + 1 + ], + "min": [ + -1, + 0, + -1 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 72, + "max": [ + 1.6011799573898315, + 2.3545401096343994, + 1.3960000276565552 + ], + "min": [ + 0.22885000705718997, + 1.631850004196167, + -1.3960000276565552 + ], + "type": "VEC3" + }, + { + "componentType": 5123, + "count": 864, + "max": [ + 463 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "componentType": 5126, + "count": 464, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 464, + "max": [ + 1.62267005443573, + 2.3919999599456787, + 1.100000023841858 + ], + "min": [ + 0.1932000070810318, + 1.5961999893188477, + -1.1100000143051147 + ], + "type": "VEC3" + }, + { + "componentType": 5123, + "count": 2304, + "max": [ + 585 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "componentType": 5126, + "count": 586, + "max": [ + 0.9990389943122864, + 0.9990379810333252, + 1 + ], + "min": [ + -0.9990379810333252, + -0.9990379810333252, + -1 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 586, + "max": [ + 0.4277999997138977, + 0.4277999997138977, + 1.0579999685287476 + ], + "min": [ + -0.4277999997138977, + -0.4277999997138977, + -1.0579999685287476 + ], + "type": "VEC3" + }, + { + "componentType": 5126, + "count": 586, + "max": [ + 0.9936569929122924, + 0.9895756244659424 + ], + "min": [ + 0.6050930023193359, + 0.00905001163482666 + ], + "type": "VEC2" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0 + }, + "metallicFactor": 0 + }, + "name": "truck" + }, + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0.04050629958510399, + 0.021240700036287308, + 1 + ], + "metallicFactor": 0 + }, + "name": "glass" + }, + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.06400000303983688, + 0.06400000303983688, + 0.06400000303983688, + 1 + ], + "metallicFactor": 0 + }, + "name": "window_trim" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1 + }, + "metallicFactor": 0 + }, + "name": "wheels" + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ], + "images": [ + { + "uri": "CesiumMilkTruck.png" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9986, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 248 + }, + { + "buffer": 0, + "byteOffset": 248, + "byteLength": 992 + }, + { + "buffer": 0, + "byteOffset": 1240, + "byteLength": 7871 + }, + { + "buffer": 0, + "byteOffset": 9111, + "byteLength": 474 + }, + { + "buffer": 0, + "byteOffset": 9585, + "byteLength": 1249 + }, + { + "buffer": 0, + "byteOffset": 10834, + "byteLength": 3137 + } + ], + "buffers": [ + { + "byteLength": 13971, + "uri": "0.bin" + } + ], + "extensionsRequired": [ + "KHR_draco_mesh_compression" + ], + "extensionsUsed": [ + "KHR_draco_mesh_compression" + ] +} diff --git a/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.png b/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.png new file mode 100644 index 000000000..390ef28f1 Binary files /dev/null and b/CesiumGltfReader/test/data/DracoCompressed/CesiumMilkTruck.png differ