From 6cbfd5b977486fe419dcc218a3180d88c68f024a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20D=C3=A4hne?= Date: Mon, 20 Nov 2017 14:59:05 +0100 Subject: [PATCH] [glTF2] Implemented reading binary glTF2 (glb) files --- code/glTF2Asset.h | 29 +++++++++++-- code/glTF2Asset.inl | 99 ++++++++++++++++++++++++++++++++++++++---- code/glTF2Importer.cpp | 8 ++-- 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/code/glTF2Asset.h b/code/glTF2Asset.h index 877b59ba3..0283ea0d8 100644 --- a/code/glTF2Asset.h +++ b/code/glTF2Asset.h @@ -192,15 +192,31 @@ namespace glTF2 #include "./../include/assimp/Compiler/pushpack1.h" #endif + //! For binary .glb files + //! 12-byte header (+ the JSON + a "body" data section) + struct GLB_Header + { + uint8_t magic[4]; //!< Magic number: "glTF" + uint32_t version; //!< Version number (always 2 as of the last update) + uint32_t length; //!< Total length of the Binary glTF, including header, scene, and body, in bytes + } PACK_STRUCT; + + struct GLB_Chunk + { + uint32_t chunkLength; + uint32_t chunkType; + } PACK_STRUCT; + #ifdef ASSIMP_API #include "./../include/assimp/Compiler/poppack1.h" #endif - //! Values for the GLB_Header::sceneFormat field - enum SceneFormat + //! Values for the GLB_Chunk::chunkType field + enum ChunkType { - SceneFormat_JSON = 0 + ChunkType_JSON = 0x4E4F534A, + ChunkType_BIN = 0x004E4942 }; //! Values for the mesh primitive modes @@ -1086,7 +1102,10 @@ namespace glTF2 } //! Main function - void Load(const std::string& file); + void Load(const std::string& file, bool isBinary = false); + + //! Enables binary encoding on the asset + void SetAsBinary(); //! Search for an available name, starting from the given strings std::string FindUniqueID(const std::string& str, const char* suffix); @@ -1095,6 +1114,8 @@ namespace glTF2 { return mBodyBuffer; } private: + void ReadBinaryHeader(IOStream& stream, std::vector& sceneData); + void ReadExtensionsUsed(Document& doc); IOStream* OpenFile(std::string path, const char* mode, bool absolute = false); diff --git a/code/glTF2Asset.inl b/code/glTF2Asset.inl index 09b049add..72fdf9580 100644 --- a/code/glTF2Asset.inl +++ b/code/glTF2Asset.inl @@ -1037,7 +1037,72 @@ inline void AssetMetadata::Read(Document& doc) // Asset methods implementation // -inline void Asset::Load(const std::string& pFile) +inline void Asset::ReadBinaryHeader(IOStream& stream, std::vector& sceneData) +{ + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read the file header"); + } + + if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + throw DeadlyImportError("GLTF: Invalid binary glTF file"); + } + + AI_SWAP4(header.version); + asset.version = to_string(header.version); + if (header.version != 2) { + throw DeadlyImportError("GLTF: Unsupported binary glTF version"); + } + + GLB_Chunk chunk; + if (stream.Read(&chunk, sizeof(chunk), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read JSON chunk"); + } + + AI_SWAP4(chunk.chunkLength); + AI_SWAP4(chunk.chunkType); + + if (chunk.chunkType != ChunkType_JSON) { + throw DeadlyImportError("GLTF: JSON chunk missing"); + } + + // read the scene data + + mSceneLength = chunk.chunkLength; + sceneData.resize(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + + uint32_t padding = ((chunk.chunkLength + 3) & ~3) - chunk.chunkLength; + if (padding > 0) { + stream.Seek(padding, aiOrigin_CUR); + } + + AI_SWAP4(header.length); + mBodyOffset = 12 + 8 + chunk.chunkLength + padding + 8; + if (header.length >= mBodyOffset) { + if (stream.Read(&chunk, sizeof(chunk), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read BIN chunk"); + } + + AI_SWAP4(chunk.chunkLength); + AI_SWAP4(chunk.chunkType); + + if (chunk.chunkType != ChunkType_BIN) { + throw DeadlyImportError("GLTF: BIN chunk missing"); + } + + mBodyLength = chunk.chunkLength; + } + else { + mBodyOffset = mBodyLength = 0; + } +} + +inline void Asset::Load(const std::string& pFile, bool isBinary) { mCurrentAssetDir.clear(); int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); @@ -1048,16 +1113,25 @@ inline void Asset::Load(const std::string& pFile) throw DeadlyImportError("GLTF: Could not open file for reading"); } - mSceneLength = stream->FileSize(); - mBodyLength = 0; + // is binary? then read the header + std::vector sceneData; + if (isBinary) { + SetAsBinary(); // also creates the body buffer + ReadBinaryHeader(*stream, sceneData); + } + else { + mSceneLength = stream->FileSize(); + mBodyLength = 0; - // read the scene data - std::vector sceneData(mSceneLength + 1); - sceneData[mSceneLength] = '\0'; + // read the scene data - if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { - throw DeadlyImportError("GLTF: Could not read the file contents"); + sceneData.resize(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } } @@ -1110,6 +1184,15 @@ inline void Asset::Load(const std::string& pFile) } } +inline void Asset::SetAsBinary() +{ + if (!mBodyBuffer) { + mBodyBuffer = buffers.Create("binary_glTF"); + mBodyBuffer->MarkAsSpecial(); + } +} + + inline void Asset::ReadExtensionsUsed(Document& doc) { Value* extsUsed = FindArray(doc, "extensionsUsed"); diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index 5d40d75bc..297f2bc72 100644 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -74,7 +74,7 @@ static const aiImporterDesc desc = { "", "", "", - aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, 0, 0, 0, @@ -103,13 +103,13 @@ bool glTF2Importer::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool { const std::string &extension = GetExtension(pFile); - if (extension != "gltf") // We currently can't read glTF2 binary files (.glb), yet + if (extension != "gltf" && extension != "glb") return false; if (checkSig && pIOHandler) { glTF2::Asset asset(pIOHandler); try { - asset.Load(pFile); + asset.Load(pFile, extension == "glb"); std::string version = asset.asset.version; return !version.empty() && version[0] == '2'; } catch (...) { @@ -639,7 +639,7 @@ void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IO // read the asset file glTF2::Asset asset(pIOHandler); - asset.Load(pFile); + asset.Load(pFile, GetExtension(pFile) == "glb"); // // Copy the data out