diff --git a/Documentation/release/dev/gltf_load_from_stream.md b/Documentation/release/dev/gltf_load_from_stream.md new file mode 100644 index 0000000000000000000000000000000000000000..2729aa04a0b917d5d72c3748388f401b7063657d --- /dev/null +++ b/Documentation/release/dev/gltf_load_from_stream.md @@ -0,0 +1,5 @@ +## Import GLTF scenes from in-memory streams + +The `vtkGLTFImporter` now accepts a resource stream to load the scene from. + + diff --git a/IO/Import/Testing/Cxx/CMakeLists.txt b/IO/Import/Testing/Cxx/CMakeLists.txt index adbc456562801b2d364facd6f040f3e0a54a7a50..76cc48a52377dcae9183fe38b55136e1aa19562a 100644 --- a/IO/Import/Testing/Cxx/CMakeLists.txt +++ b/IO/Import/Testing/Cxx/CMakeLists.txt @@ -98,32 +98,41 @@ vtk_add_test_cxx(vtkIOImportCxxTests tests ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporter,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} -1 1 0 0 + TestGLTFImporter,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} 0 -1 1 0 0 +) + +vtk_add_test_cxx(vtkIOImportCxxTests tests + TestGLTFImporterStream,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} 1 -1 1 0 0 +) + +vtk_add_test_cxx(vtkIOImportCxxTests tests + LOOSE_VALID + TestGLTFImporterPBR,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} 0 -1 1 0 0 ) vtk_add_test_cxx(vtkIOImportCxxTests tests LOOSE_VALID - TestGLTFImporterPBR,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} -1 1 0 0 + TestGLTFImporterPBRStream,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} 1 -1 1 0 0 ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporterKHRLightsPunctual,TestGLTFImporter.cxx DATA{../Data/glTF/Lights/lights.gltf} 0 1 5 1 + TestGLTFImporterKHRLightsPunctual,TestGLTFImporter.cxx DATA{../Data/glTF/Lights/lights.gltf} 0 0 1 5 1 ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporterCamera,TestGLTFImporter.cxx DATA{../Data/glTF/Cameras/Cameras.gltf} 1 1 0 2 + TestGLTFImporterCamera,TestGLTFImporter.cxx DATA{../Data/glTF/Cameras/Cameras.gltf} 0 1 1 0 2 ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporterUnlit,TestGLTFImporter.cxx DATA{../Data/glTF/UnlitTest/UnlitTest.glb} -1 2 0 0 + TestGLTFImporterUnlit,TestGLTFImporter.cxx DATA{../Data/glTF/UnlitTest/UnlitTest.glb} 0 -1 2 0 0 ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporterArmature,TestGLTFImporter.cxx DATA{../Data/glTF/Armature.gltf} -1 1 0 0 + TestGLTFImporterArmature,TestGLTFImporter.cxx DATA{../Data/glTF/Armature.gltf} 0 -1 1 0 0 ) vtk_add_test_cxx(vtkIOImportCxxTests tests - TestGLTFImporterURITexture,TestGLTFImporter.cxx DATA{../Data/glTF/Lantern/Lantern.gltf} -1 3 0 0 + TestGLTFImporterURITexture,TestGLTFImporter.cxx DATA{../Data/glTF/Lantern/Lantern.gltf} 0 -1 3 0 0 DATA{../Data/glTF/Lantern/Lantern.bin} DATA{../Data/glTF/Lantern/Lantern_baseColor.png} DATA{../Data/glTF/Lantern/Lantern_emissive.png} diff --git a/IO/Import/Testing/Cxx/TestGLTFImporter.cxx b/IO/Import/Testing/Cxx/TestGLTFImporter.cxx index 400a08d27072a693d0acf7d2cf3aca62ffe88a28..428a1804b0b9f34390f2da72aedbaef6919f76c4 100644 --- a/IO/Import/Testing/Cxx/TestGLTFImporter.cxx +++ b/IO/Import/Testing/Cxx/TestGLTFImporter.cxx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause +#include <vtkFileResourceStream.h> #include <vtkGLTFImporter.h> #include <vtkLightCollection.h> #include <vtkRegressionTestImage.h> @@ -11,19 +12,42 @@ int TestGLTFImporter(int argc, char* argv[]) { - if (argc < 6) + if (argc < 7) { - std::cout << "Usage: " << argv[0] - << " <gltf file> <camera index> <expected nb of actors> <expected nb of lights> " - "<expected nb of cameras>" - << std::endl; + std::cout + << "Usage: " << argv[0] + << " <gltf file> <use_stream> <camera index> <expected nb of actors> <expected nb of lights> " + "<expected nb of cameras>" + << std::endl; return EXIT_FAILURE; } - vtkIdType cameraIndex = atoi(argv[2]); + vtkIdType cameraIndex = atoi(argv[3]); vtkNew<vtkGLTFImporter> importer; - importer->SetFileName(argv[1]); + if (atoi(argv[2]) > 0) + { + bool is_binary = false; + std::string extension = vtksys::SystemTools::GetFilenameLastExtension(argv[1]); + if (extension == ".glb") + { + is_binary = true; + } + vtkNew<vtkFileResourceStream> file; + file->Open(argv[1]); + if (file->EndOfStream()) + { + std::cerr << "Can not open test file " << argv[1] << std::endl; + return EXIT_FAILURE; + } + importer->SetStream(file); + importer->SetStreamIsBinary(is_binary); + } + else + { + importer->SetFileName(argv[1]); + } + importer->ImportArmatureOn(); vtkNew<vtkRenderWindow> renderWindow; @@ -43,19 +67,19 @@ int TestGLTFImporter(int argc, char* argv[]) return EXIT_FAILURE; } - if (importer->GetImportedActors()->GetNumberOfItems() != atoi(argv[3])) + if (importer->GetImportedActors()->GetNumberOfItems() != atoi(argv[4])) { std::cerr << "ERROR: Unexpected number of imported actors: " << importer->GetImportedActors()->GetNumberOfItems() << "\n"; return EXIT_FAILURE; } - if (importer->GetImportedLights()->GetNumberOfItems() != atoi(argv[4])) + if (importer->GetImportedLights()->GetNumberOfItems() != atoi(argv[5])) { std::cerr << "ERROR: Unexpected number of imported lights: " << importer->GetImportedActors()->GetNumberOfItems() << "\n"; return EXIT_FAILURE; } - if (importer->GetImportedCameras()->GetNumberOfItems() != atoi(argv[5])) + if (importer->GetImportedCameras()->GetNumberOfItems() != atoi(argv[6])) { std::cerr << "ERROR: Unexpected number of imported cameras: " << importer->GetImportedActors()->GetNumberOfItems() << "\n"; diff --git a/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512 b/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512 new file mode 100644 index 0000000000000000000000000000000000000000..62bcd42965736512ddb48103aa12fab14da064af --- /dev/null +++ b/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512 @@ -0,0 +1 @@ +c59b73eff93b98942a8bd18743d71ca6d0d4de001c0c0765a7684b0bc4ca1d3b8b46805854b1f77616407fbdc8fcded7ea777cff4ea511a585a6864970ae5d71 diff --git a/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512 b/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512 new file mode 100644 index 0000000000000000000000000000000000000000..3178a03cbab456700f1943e79e0c9efdde10fb4a --- /dev/null +++ b/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512 @@ -0,0 +1 @@ +a40ba06e652140ed031e7cda5597b2cfa84b42ab2ae05ca068c015432e5302f00c72bea03b2bb9e6532181fb0a33ec4abc7f178ad2292d57cb82d6bc9ee97bf7 diff --git a/IO/Import/vtk.module b/IO/Import/vtk.module index 8da982b9a6f48c78e64913f030e33479b7b32526..ef9f131c0f34aca0c730de99e0495d7443ebb431 100644 --- a/IO/Import/vtk.module +++ b/IO/Import/vtk.module @@ -12,6 +12,7 @@ DEPENDS VTK::CommonCore VTK::CommonExecutionModel VTK::CommonMisc + VTK::IOCore VTK::RenderingCore VTK::vtksys PRIVATE_DEPENDS diff --git a/IO/Import/vtkGLTFImporter.cxx b/IO/Import/vtkGLTFImporter.cxx index f5197e6ba6988e94f49b7a6057e012143f331f99..43cd4fb94280fa0cf13d3bea871b8c32d3a57c44 100644 --- a/IO/Import/vtkGLTFImporter.cxx +++ b/IO/Import/vtkGLTFImporter.cxx @@ -403,9 +403,9 @@ void vtkGLTFImporter::InitializeLoader() int vtkGLTFImporter::ImportBegin() { // Make sure we have a file to read. - if (!this->FileName) + if (!this->Stream && !this->FileName) { - vtkErrorMacro("A FileName must be specified."); + vtkErrorMacro("Neither FileName nor Stream has been specified."); return 0; } @@ -422,21 +422,44 @@ int vtkGLTFImporter::ImportBegin() // Check extension std::vector<char> glbBuffer; - std::string extension = vtksys::SystemTools::GetFilenameLastExtension(this->FileName); - if (extension == ".glb") + if (this->Stream != nullptr) { - if (!this->Loader->LoadFileBuffer(this->FileName, glbBuffer)) + // this->Stream is defined. + if (this->StreamIsBinary) { - vtkErrorMacro("Error loading binary data"); + if (!this->Loader->LoadStreamBuffer(this->Stream, glbBuffer)) + { + vtkErrorMacro("Error loading binary data"); + return 0; + } + } + + if (!this->Loader->LoadModelMetaDataFromStream(this->Stream, this->StreamURILoader)) + { + vtkErrorMacro("Error loading model metadata"); return 0; } } - - if (!this->Loader->LoadModelMetaDataFromFile(this->FileName)) + else { - vtkErrorMacro("Error loading model metadata"); - return 0; + // this->FileName is defined. + std::string extension = vtksys::SystemTools::GetFilenameLastExtension(this->FileName); + if (extension == ".glb") + { + if (!this->Loader->LoadFileBuffer(this->FileName, glbBuffer)) + { + vtkErrorMacro("Error loading binary data"); + return 0; + } + } + + if (!this->Loader->LoadModelMetaDataFromFile(this->FileName)) + { + vtkErrorMacro("Error loading model metadata"); + return 0; + } } + if (!this->Loader->LoadModelData(glbBuffer)) { vtkErrorMacro("Error loading model data"); @@ -1037,7 +1060,16 @@ bool vtkGLTFImporter::GetTemporalInformation(vtkIdType animationIndex, double fr void vtkGLTFImporter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); - os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << "\n"; + os << indent; + if (this->Stream != nullptr) + { + os << "Stream (" << (this->StreamIsBinary ? "binary" : "ascii") << ")"; + } + else + { + os << "File Name: " << (this->FileName ? this->FileName : "(none)"); + } + os << "\n"; } //------------------------------------------------------------------------------ diff --git a/IO/Import/vtkGLTFImporter.h b/IO/Import/vtkGLTFImporter.h index 239d1d7fb75aa01a2a609d2144dbb2e387543cc4..79ccc4e3b23971836d07b22906ab8db6b91d5d67 100644 --- a/IO/Import/vtkGLTFImporter.h +++ b/IO/Import/vtkGLTFImporter.h @@ -51,16 +51,21 @@ #include "vtkIOImportModule.h" // For export macro #include "vtkImporter.h" -#include "vtkSmartPointer.h" // For SmartPointer +#include "vtkResourceStream.h" // For Stream +#include "vtkSmartPointer.h" // For SmartPointer +#include "vtkURILoader.h" // For URILoader #include <map> // For map #include <vector> // For vector VTK_ABI_NAMESPACE_BEGIN + +// Forward declarations class vtkActor; class vtkCamera; class vtkGLTFDocumentLoader; class vtkTexture; +class vtkURILoader; class VTKIOIMPORT_EXPORT vtkGLTFImporter : public vtkImporter { @@ -78,6 +83,40 @@ public: vtkGetFilePathMacro(FileName); ///@} + ///@{ + /** + * Specify the glTF source stream to read from. When selecting the input method, `Stream` has a + * higher priority than `FileName` i.e. if a stream is provided, the filename is ignored. + * + * \note If the stream contains non-data URIs, specifying a custom uri loader is crucial. + * \sa SetStreamURILoader() + * + * \sa SetStreamIsBinary() + */ + vtkSetSmartPointerMacro(Stream, vtkResourceStream); + vtkGetSmartPointerMacro(Stream, vtkResourceStream); + ///@} + + ///@{ + /** + * Specify a custom URI loader for non-data URIs in the input stream. + * \sa SetStream(), SetStreamIsBinary() + */ + vtkSetSmartPointerMacro(StreamURILoader, vtkURILoader); + vtkGetSmartPointerMacro(StreamURILoader, vtkURILoader); + ///@} + + ///@{ + /** + * Set/Get whether the input stream is binary + * + * \sa SetStream() + */ + vtkSetMacro(StreamIsBinary, bool); + vtkGetMacro(StreamIsBinary, bool); + vtkBooleanMacro(StreamIsBinary, bool); + ///@} + /** * glTF defines multiple camera objects, but no default behavior for which camera should be * used. The importer will by default apply the asset's first camera. This accessor lets you use @@ -167,6 +206,9 @@ protected: virtual void ApplyArmatureProperties(vtkActor* actor); char* FileName = nullptr; + vtkSmartPointer<vtkResourceStream> Stream; + vtkSmartPointer<vtkURILoader> StreamURILoader; + bool StreamIsBinary = false; std::map<int, vtkSmartPointer<vtkCamera>> Cameras; std::map<int, vtkSmartPointer<vtkTexture>> Textures;