From 148a20a703a614424b4b10c34c40f35a8deba495 Mon Sep 17 00:00:00 2001 From: Otger Date: Thu, 26 Nov 2015 02:36:22 +0100 Subject: [PATCH] Experimental support for glTF --- code/CMakeLists.txt | 11 +- code/ImporterRegistry.cpp | 6 + code/glTFFileData.h | 122 ++++ code/glTFImporter.cpp | 1138 ++++++++++++++++++++++++++++++++++++- code/glTFImporter.h | 32 +- code/glTFUtil.cpp | 164 ++++++ code/glTFUtil.h | 67 +++ 7 files changed, 1509 insertions(+), 31 deletions(-) create mode 100644 code/glTFFileData.h create mode 100644 code/glTFUtil.cpp create mode 100644 code/glTFUtil.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 33de95cb6..9a577fa1b 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -562,9 +562,12 @@ ADD_ASSIMP_IMPORTER(X XFileExporter.cpp ) -ADD_ASSIMP_IMPORTER( glFT - glTFImporter.cpp - glTFImporter.h +ADD_ASSIMP_IMPORTER(GLTF + glTFImporter.cpp + glTFImporter.h + glTFUtil.cpp + glTFUtil.h + glTFFileData.h ) SET( Step_SRCS @@ -654,6 +657,8 @@ SET( picojson_SRCS ) SOURCE_GROUP( picojson FILES ${picojson_SRCS} ) +INCLUDE_DIRECTORIES( "../contrib/rapidjson/include" ) + # VC2010 fixes if(MSVC10) option( VC10_STDINT_FIX "Fix for VC10 Compiler regarding pstdint.h redefinition errors" OFF ) diff --git a/code/ImporterRegistry.cpp b/code/ImporterRegistry.cpp index 289587b04..06e8ca822 100644 --- a/code/ImporterRegistry.cpp +++ b/code/ImporterRegistry.cpp @@ -167,6 +167,9 @@ corresponding preprocessor flag to selectively disable formats. #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER # include "FBXImporter.h" #endif +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER +# include "glTFImporter.h" +#endif #ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER # include "AssbinLoader.h" #endif @@ -302,6 +305,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out) #if ( !defined ASSIMP_BUILD_NO_FBX_IMPORTER ) out.push_back( new FBXImporter() ); #endif +#if ( !defined ASSIMP_BUILD_NO_GLTF_IMPORTER ) + out.push_back( new glTFImporter() ); +#endif #if ( !defined ASSIMP_BUILD_NO_ASSBIN_IMPORTER ) out.push_back( new AssbinImporter() ); #endif diff --git a/code/glTFFileData.h b/code/glTFFileData.h new file mode 100644 index 000000000..2a124d377 --- /dev/null +++ b/code/glTFFileData.h @@ -0,0 +1,122 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLTFFILEDATA_H_INC +#define AI_GLTFFILEDATA_H_INC + +#include + +namespace Assimp { +namespace glTF { + + +//! Magic number for GLB files +#define AI_GLB_MAGIC_NUMBER "glTF" + + +#include "./../include/assimp/Compiler/pushpack1.h" + +// KHR_binary_glTF (binary .glb file) +// 20-byte header (+ the JSON + a "body" data section) +struct GLB_Header +{ + //! Magic number: "glTF" + unsigned char magic[4]; // "glTF" + + //! Version number (always 1 as of the last update) + uint32_t version; + + //! Total length of the Binary glTF, including header, scene, and body, in bytes + uint32_t length; + + //! Length, in bytes, of the glTF scene + uint32_t sceneLength; + + //! Specifies the format of the glTF scene (see the SceneFormat enum) + uint32_t sceneFormat; +} PACK_STRUCT; + +#include "./../include/assimp/Compiler/poppack1.h" + + + +//! Values for the GLB_Header::sceneFormat field +enum SceneFormat +{ + SceneFormat_JSON = 0 +}; + + +//! Values for the mesh primitive modes +enum PrimitiveMode +{ + PrimitiveMode_POINTS = 0, + PrimitiveMode_LINES = 1, + PrimitiveMode_LINE_LOOP = 2, + PrimitiveMode_LINE_STRIP = 3, + PrimitiveMode_TRIANGLES = 4, + PrimitiveMode_TRIANGLE_STRIP = 5, + PrimitiveMode_TRIANGLE_FAN = 6 +}; + + +//! Values for the accessors component type field +enum ComponentType +{ + ComponentType_BYTE = 5120, + ComponentType_UNSIGNED_BYTE = 5121, + ComponentType_SHORT = 5122, + ComponentType_UNSIGNED_SHORT = 5123, + ComponentType_FLOAT = 5126 +}; + + +//! Will hold the enabled extensions +struct Extensions +{ + bool KHR_binary_glTF; +}; + + +} // end namespaces +} + + +#endif // AI_GLTFFILEDATA_H_INC + diff --git a/code/glTFImporter.cpp b/code/glTFImporter.cpp index 82c5c798a..374556af1 100644 --- a/code/glTFImporter.cpp +++ b/code/glTFImporter.cpp @@ -42,32 +42,1046 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "DefaultIOSystem.h" #include + #include #include #include #include -#include "../contrib/picojson/picojson.h" +#include "glTFFileData.h" +#include "glTFUtil.h" -namespace Assimp { +#include +#include +#include - static const aiImporterDesc desc = { - "glTF Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, - 0, - 0, - 0, - 0, - "gltf" - }; +using namespace rapidjson; + +using namespace Assimp; +using namespace Assimp::glTF; + + +using boost::shared_ptr; +using boost::scoped_ptr; + +// (cannot typedef' templated classes, and "using" is c++11) +#define Ptr shared_ptr + +// (used everywhere, and cannot use "auto") +typedef rapidjson::Value::MemberIterator MemIt; + + +// +// JSON Value reading helpers +// + +inline static void Getf(const Value& v, float& out) +{ + if (v.IsNumber()) out = static_cast(v.GetDouble()); +} + +template +struct ReadHelper { }; + +template<> struct ReadHelper { static bool Read(Value& val, int& out) { + return val.IsInt() ? val.GetInt(), true : false; +}}; + +template<> struct ReadHelper { static bool Read(Value& val, unsigned int& out) { + return val.IsInt() ? out = static_cast(val.GetInt()), true : false; +}}; + +template<> struct ReadHelper { static bool Read(Value& val, float& out) { + return val.IsNumber() ? out = static_cast(val.GetDouble()), true : false; +}}; + +template<> struct ReadHelper { static bool Read(Value& val, const char*& out) { + return val.IsString() ? out = val.GetString(), true : false; +}}; + +template<> struct ReadHelper { static bool Read(Value& val, std::string& out) { + return val.IsString() ? out = val.GetString(), true : false; +}}; + +template<> struct ReadHelper { static bool Read(Value& v, aiColor3D& out) { + if (!v.IsArray() || v.Size() < 3) return false; + Getf(v[0], out.r); Getf(v[1], out.g); Getf(v[2], out.b); + return true; +}}; + +template<> struct ReadHelper { static bool Read(Value& v, aiVector3D& out) { + if (!v.IsArray() || v.Size() != 3) return false; + Getf(v[0], out.x); Getf(v[1], out.y); Getf(v[2], out.z); + return true; +}}; + +template<> struct ReadHelper { static bool Read(Value& v, aiQuaternion& out) { + if (!v.IsArray() || v.Size() != 4) return false; + Getf(v[0], out.x); Getf(v[1], out.y); Getf(v[2], out.z); Getf(v[3], out.w); + return true; +}}; + +template<> struct ReadHelper { static bool Read(Value& v, aiMatrix4x4& o) { + if (!v.IsArray() || v.Size() != 16) return false; + Getf(v[ 0], o.a1); Getf(v[ 1], o.b1); Getf(v[ 2], o.c1); Getf(v[ 3], o.d1); + Getf(v[ 4], o.a2); Getf(v[ 5], o.b2); Getf(v[ 6], o.c2); Getf(v[ 7], o.d2); + Getf(v[ 8], o.a3); Getf(v[ 9], o.b3); Getf(v[10], o.c3); Getf(v[11], o.d3); + Getf(v[12], o.a4); Getf(v[13], o.b4); Getf(v[14], o.c4); Getf(v[15], o.d4); + return true; +}}; + +template +inline static bool Read(Value& val, T& out) +{ + return ReadHelper::Read(val, out); +} + +template +inline static bool ReadMember(Value& obj, const char* id, T& out) +{ + MemIt it = obj.FindMember(id); + if (it != obj.MemberEnd()) { + return ReadHelper::Read(it->value, out); + } + return false; +} + +template +inline static T TryReadMember(Value& obj, const char* id, T defaultValue) +{ + T out; + return ReadMember(obj, id, out) ? out : defaultValue; +} + + +//! References a sequence of loaded elements (e.g. meshes) +typedef std::pair Range; + + + +// +// glTFReader class +// + +class glTFReader; + +//! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID +template +class LazyDict +{ + typedef typename std::gltf_unordered_map Map; + + Value* mDict; //! JSON dictionary object + const char* mDictId; //! ID of the dictionary object + glTFReader& mInstance; //! The glTFReader instance + Map mReadObjs; //! The read objects + +public: + LazyDict(glTFReader& instance, const char* dictId) + : mDictId(dictId), mInstance(instance) + { + Document& doc = mInstance.GetDocument(); + + MemIt it = doc.FindMember(dictId); + mDict = (it != doc.MemberEnd() && it->value.IsObject()) ? &it->value : nullptr; + } + + T Get(const char* id) + { + if (!mDict) return T(); // section was missing + + Map::iterator it = mReadObjs.find(id); + if (it != mReadObjs.end()) { // already created? + return it->second; + } + + // read it from the JSON object + MemIt obj = mDict->FindMember(id); + if (obj == mDict->MemberEnd()) { + throw DeadlyImportError("Missing object with id \"" + std::string(id) + "\" in \"" + mDictId + "\""); + } + + // create an instance of the given type + T val = (mInstance.*FACTORY_FN)(id, obj->value); + mReadObjs[id] = val; + return val; + } +}; + +struct Buffer; +struct BufferView; +struct Accessor; + +struct Image; +struct Texture; + +//! Handles the reading of the glTF JSON document +class glTFReader +{ + aiScene* mScene; + Document& mDoc; + IOSystem& mIO; + + // Vectors of imported objects, will be copied to mScene + std::vector mImpMaterials; + std::vector mImpMeshes; + std::vector mImpTextures; + + Extensions mExtensions; + + Ptr mBodyBuffer; //! Special buffer containing the body data + + + Ptr LoadBuffer(const char* id, Value& obj); + Ptr LoadBufferView(const char* id, Value& obj); + Ptr LoadAccessor(const char* id, Value& obj); + + Ptr LoadImage(const char* id, Value& obj); + Ptr LoadTexture(const char* id, Value& obj); + + aiNode* LoadNode(const char* id, Value& node); + Range LoadMesh(const char* id, Value& mesh); + unsigned int LoadMaterial(const char* id, Value& material); + + typedef glTFReader T; // (to shorten next declarations) + + LazyDict, &T::LoadAccessor> mAccessors; + //LazyDict mAnimations; + //LazyDict mAssets; + LazyDict, &T::LoadBuffer> mBuffers; + LazyDict, &T::LoadBufferView> mBufferViews; + //LazyDict mCameras; + LazyDict, &T::LoadImage> mImages; + LazyDict mMaterials; + LazyDict mMeshes; + LazyDict mNodes; + //LazyDict, &T::LoadProgram> mPrograms; + //LazyDict, &T::LoadSampler> mSamplers; + //LazyDict, &T::LoadShader> mShaders; + //LazyDict, &T::LoadSkin> mSkins; + //LazyDict,&T::LoadTechnique> mTechniques; + LazyDict, &T::LoadTexture> mTextures; + + + void LoadScene(Value& scene) + { + MemIt nodesm = scene.FindMember("nodes"); + if (nodesm != scene.MemberEnd() && nodesm->value.IsArray()) { + Value& nodes = nodesm->value; + + unsigned int numRootNodes = nodes.Size(); + if (numRootNodes == 1) { // a single root node: use it + if (nodes[0].IsString()) { + mScene->mRootNode = mNodes.Get(nodes[0].GetString()); + } + } + else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode* root = new aiNode("ROOT"); + root->mChildren = new aiNode*[numRootNodes]; + for (unsigned int i = 0; i < numRootNodes; ++i) { + if (nodes[i].IsString()) { + aiNode* node = mNodes.Get(nodes[i].GetString()); + if (node) { + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + } + } + mScene->mRootNode = root; + } + } + } + + void SetMaterialColorProperty(aiMaterial* mat, Value& vals, const char* propName, aiTextureType texType, + const char* pKey, unsigned int type, unsigned int idx); + + void CopyData() + { + // TODO: it does not split the loaded vertices, should it? + mScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; + + if (mImpMaterials.empty()) { + mImpMaterials.push_back(new aiMaterial()); + } + + if (mImpMaterials.size()) { + mScene->mNumMaterials = mImpMaterials.size(); + mScene->mMaterials = new aiMaterial*[mImpMaterials.size()]; + std::swap_ranges(mImpMaterials.begin(), mImpMaterials.end(), mScene->mMaterials); + } + + if (mImpMeshes.size()) { + mScene->mNumMeshes = mImpMeshes.size(); + mScene->mMeshes = new aiMesh*[mImpMeshes.size()]; + std::swap_ranges(mImpMeshes.begin(), mImpMeshes.end(), mScene->mMeshes); + } + + if (mImpTextures.size()) { + mScene->mNumTextures = mImpTextures.size(); + mScene->mTextures = new aiTexture*[mImpTextures.size()]; + std::swap_ranges(mImpTextures.begin(), mImpTextures.end(), mScene->mTextures); + } + } + +public: + glTFReader(aiScene* scene, Document& document, IOSystem& iohandler, shared_ptr& bodyBuff) : + mScene(scene), + mDoc(document), + mIO(iohandler), + mBodyBuffer(bodyBuff), + mAccessors(*this, "accessors"), + //mAnimations(*this, "animations"), + //mAssets(*this, "assets"), + mBuffers(*this, "buffers"), + mBufferViews(*this, "bufferViews"), + //mCameras(*this, "cameras"), + mImages(*this, "images"), + mMaterials(*this, "materials"), + mMeshes(*this, "meshes"), + mNodes(*this, "nodes"), + //mPrograms(*this, "programs"), + //mSamplers(*this, "samplers"), + //mShaders(*this, "shaders"), + //mSkins(*this, "skins"), + //mTechniques(*this, "techniques"), + mTextures(*this, "textures") + { + memset(&mExtensions, 0, sizeof(mExtensions)); + } + + Document& GetDocument() + { + return mDoc; + } + + //! Main function + void Load() + { + // read the used extensions + MemIt extensionsUsed = mDoc.FindMember("extensionsUsed"); + if (extensionsUsed != mDoc.MemberEnd() && extensionsUsed->value.IsArray()) { + std::gltf_unordered_map exts; + + for (unsigned int i = 0; i < extensionsUsed->value.Size(); ++i) { + if (extensionsUsed->value[i].IsString()) { + exts[extensionsUsed->value[i].GetString()] = true; + } + } + + if (exts.find("KHR_binary_glTF") != exts.end()) { + mExtensions.KHR_binary_glTF = true; + } + } + + + const char* sceneId = 0; + + // the "scene" property specifies which scene to load + { + MemIt scene = mDoc.FindMember("scene"); + if (scene != mDoc.MemberEnd() && scene->value.IsString()) { + sceneId = scene->value.GetString(); + } + } + + MemIt scene; + + MemIt scenes = mDoc.FindMember("scenes"); + if (scenes != mDoc.MemberEnd() && scenes->value.IsObject()) { + if (sceneId) { + scene = scenes->value.FindMember(sceneId); + if (scene == scenes->value.MemberEnd()) { + //ThrowException("Missing scene!"); + } + } + else { // if not specified, use the first one + scene = scenes->value.MemberBegin(); + } + } + + if (scene != scenes->value.MemberEnd()) { + LoadScene(scene->value); + } + + CopyData(); + } +}; + +struct Buffer +{ +private: + std::size_t byteLength; + shared_ptr data; + +public: + Buffer(shared_ptr& d, std::size_t length) + : data(d), byteLength(length) + { } + + std::size_t GetLength() const + { + return byteLength; + } + + uint8_t* GetPointer() + { + return data.get(); + } + + static Buffer* FromStream(IOStream& stream, std::size_t length = 0, std::size_t baseOffset = 0) + { + if (!length) { + length = stream.FileSize(); + } + + if (baseOffset) { + stream.Seek(baseOffset, aiOrigin_SET); + } + + shared_ptr data(new uint8_t[length]); + + + if (stream.Read(data.get(), length, 1) != 1) { + throw DeadlyImportError("Unable to load buffer from file!"); + } + + return new Buffer(data, length); + } +}; + + +Ptr glTFReader::LoadBuffer(const char* id, Value& obj) +{ + if (!obj.IsObject()) return Ptr(); + + if (mExtensions.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { + return mBodyBuffer; + } + else { + const char* uri = TryReadMember(obj, "uri", 0); + + Buffer* b = 0; + if (IsDataURI(uri)) { + const char* comma = strchr(uri, ','); + *const_cast(comma) = '\0'; + + bool isBase64 = (strstr(uri, "base64") != 0); + if (isBase64) { + uint8_t* data; + std::size_t dataLen = DecodeBase64(comma + 1, data); + b = new Buffer(shared_ptr(data), dataLen); + } + } + else if (uri) { // Local file + unsigned int byteLength = TryReadMember(obj, "byteLength", 0u); + + scoped_ptr file(mIO.Open(uri)); + b = Buffer::FromStream(*file.get(), byteLength); + } + return Ptr(b); + } +} + + +struct BufferView +{ + Ptr buffer; + unsigned int byteOffset; + unsigned int byteLength; + + BufferView() {} + + BufferView(Value& obj) + { + Read(obj); + } + + void Read(Value& obj) + { + if (!obj.IsObject()) return; + + } + +}; + +Ptr glTFReader::LoadBufferView(const char* id, Value& obj) +{ + if (!obj.IsObject()) return Ptr(); + + + const char* bufferId = TryReadMember(obj, "buffer", 0); + if (!bufferId) return Ptr(); + + BufferView* bv = new BufferView(); + + bv->buffer = mBuffers.Get(bufferId); + bv->byteOffset = TryReadMember(obj, "byteOffset", 0u); + bv->byteLength = TryReadMember(obj, "byteLength", 0u); + + return Ptr(bv); +} + + +struct Accessor +{ + Ptr bufferView; + unsigned int byteOffset; + unsigned int byteStride; + ComponentType componentType; + unsigned int count; + std::string type; // "SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4" + //unsigned int max; + ///unsigned int min; + + unsigned int numComponents; + unsigned int bytesPerComponent; + unsigned int elemSize; + + uint8_t* data; + + inline uint8_t* GetPointer() + { + if (!bufferView || !bufferView->buffer) return 0; + + std::size_t offset = byteOffset + bufferView->byteOffset; + return bufferView->buffer->GetPointer() + offset; + } + + template + void ExtractData(T*& outData, unsigned int* outCount = 0, unsigned int* outComponents = 0) + { + if (!data) return; + + const std::size_t totalSize = elemSize * count; + + const std::size_t targetElemSize = sizeof(T); + ai_assert(elemSize <= targetElemSize); + + outData = new T[count]; + if (byteStride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } + else { + for (std::size_t i = 0; i < count; ++i) { + memcpy(outData + i, data + i*byteStride, elemSize); + } + } + + if (outCount) *outCount = count; + if (outComponents) *outComponents = numComponents; + } + + template + T GetValue(int i) + { + T value = T(); + memcpy(&value, data + i*byteStride, elemSize); + //value >>= 8 * (sizeof(T) - elemSize); + return value; + } +}; + +Ptr glTFReader::LoadAccessor(const char* id, Value& obj) +{ + if (!obj.IsObject()) return Ptr(); + + Accessor* a = new Accessor(); + + const char* bufferViewId = TryReadMember(obj, "bufferView", 0); + if (bufferViewId) { + a->bufferView = mBufferViews.Get(bufferViewId); + } + + int compType = TryReadMember(obj, "componentType", unsigned(ComponentType_BYTE)); + + a->byteOffset = TryReadMember(obj, "byteOffset", 0u); + a->byteStride = TryReadMember(obj, "byteStride", 0u); + a->componentType = static_cast(compType); + a->count = TryReadMember(obj, "count", 0u); + a->type = TryReadMember(obj, "type", ""); + + a->numComponents = 1; // "SCALAR" + if (a->type == "VEC2") a->numComponents = 2; + else if (a->type == "VEC3") a->numComponents = 3; + else if (a->type == "VEC4") a->numComponents = 4; + else if (a->type == "MAT2") a->numComponents = 4; + else if (a->type == "MAT3") a->numComponents = 9; + else if (a->type == "MAT4") a->numComponents = 16; + + switch (a->componentType) { + case ComponentType_SHORT: + case ComponentType_UNSIGNED_SHORT: + a->bytesPerComponent = 2; + break; + + case ComponentType_FLOAT: + a->bytesPerComponent = 4; + break; + + //case Accessor::ComponentType_BYTE: + //case Accessor::ComponentType_UNSIGNED_BYTE: + default: + a->bytesPerComponent = 1; + } + + a->elemSize = a->numComponents * a->bytesPerComponent; + if (!a->byteStride) a->byteStride = a->elemSize; + + a->data = a->GetPointer(); + + return Ptr(a); +} + +static inline void setFace(aiFace& face, int a) +{ + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; +} + +static inline void setFace(aiFace& face, int a, int b) +{ + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = a; + face.mIndices[1] = b; +} + +static inline void setFace(aiFace& face, int a, int b, int c) +{ + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = a; + face.mIndices[1] = b; + face.mIndices[2] = c; +} + +Range glTFReader::LoadMesh(const char* id, Value& mesh) +{ + Range range; + range.first = mImpMeshes.size(); + range.second = mImpMeshes.size(); + + MemIt primitives = mesh.FindMember("primitives"); + if (primitives != mesh.MemberEnd() && primitives->value.IsArray()) { + for (unsigned int i = 0; i < primitives->value.Size(); ++i) { + Value& primitive = primitives->value[i]; + + aiMesh* aimesh = new aiMesh(); + mImpMeshes.push_back(aimesh); + ++range.second; + + MemIt mode = primitive.FindMember("mode"); + if (mode != primitive.MemberEnd() && mode->value.IsInt()) { + switch (mode->value.GetInt()) { + case PrimitiveMode_POINTS: + aimesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aimesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aimesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } + } + + MemIt attrs = primitive.FindMember("attributes"); + if (attrs != primitive.MemberEnd() && attrs->value.IsObject()) { + for (MemIt it = attrs->value.MemberBegin(); it != attrs->value.MemberEnd(); ++it) { + if (!it->value.IsString()) continue; + const char* attr = it->name.GetString(); + const char* accessorId = it->value.GetString(); + + Ptr accessor = mAccessors.Get(accessorId); + if (!accessor) continue; + + if (strcmp(attr, "POSITION") == 0) { + accessor->ExtractData(aimesh->mVertices, &aimesh->mNumVertices); + } + else if (strcmp(attr, "NORMAL") == 0) { + accessor->ExtractData(aimesh->mNormals); + } + else if (strncmp(attr, "TEXCOORD_", 9) == 0) { + int idx = attr[9] - '0'; + accessor->ExtractData(aimesh->mTextureCoords[idx], 0, &aimesh->mNumUVComponents[idx]); + } + } + } + + MemIt indices = primitive.FindMember("indices"); + if (indices != primitive.MemberEnd() && indices->value.IsString()) { + Ptr acc = mAccessors.Get(indices->value.GetString()); + if (acc) { + aiFace* faces = 0; + std::size_t nFaces = 0; + + int primitiveMode = mode->value.GetInt(); + switch (primitiveMode) { + case PrimitiveMode_POINTS: { + nFaces = acc->count; + faces = new aiFace[nFaces]; + for (unsigned i = 0; i < acc->count; ++i) { + setFace(faces[i], acc->GetValue(i)); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = acc->count / 2; + faces = new aiFace[nFaces]; + for (unsigned i = 0; i < acc->count; i += 2) { + setFace(faces[i / 2], acc->GetValue(i), acc->GetValue(i + 1)); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = acc->count - ((primitiveMode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + setFace(faces[0], acc->GetValue(0), acc->GetValue(1)); + for (unsigned i = 2; i < acc->count; ++i) { + setFace(faces[i - 1], faces[i - 2].mIndices[1], acc->GetValue(i)); + } + if (primitiveMode == PrimitiveMode_LINE_LOOP) { // close the loop + setFace(faces[acc->count - 1], faces[acc->count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = acc->count / 3; + faces = new aiFace[nFaces]; + for (unsigned i = 0; i < acc->count; i += 3) { + setFace(faces[i / 3], acc->GetValue(i), acc->GetValue(i + 1), acc->GetValue(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = acc->count - 2; + faces = new aiFace[nFaces]; + setFace(faces[0], acc->GetValue(0), acc->GetValue(1), acc->GetValue(2)); + for (unsigned i = 3; i < acc->count; ++i) { + setFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], acc->GetValue(i)); + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = acc->count - 2; + faces = new aiFace[nFaces]; + setFace(faces[0], acc->GetValue(0), acc->GetValue(1), acc->GetValue(2)); + for (unsigned i = 3; i < acc->count; ++i) { + setFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], acc->GetValue(i)); + } + break; + } + + if (faces) { + aimesh->mFaces = faces; + aimesh->mNumFaces = nFaces; + } + } + } + + + MemIt material = primitive.FindMember("material"); + if (material != primitive.MemberEnd() && material->value.IsString()) { + aimesh->mMaterialIndex = mMaterials.Get(material->value.GetString()); + } + } + } + + return range; +} + + +struct Image +{ + aiString uri; +}; + +struct Texture +{ + Ptr source; +}; + +Ptr glTFReader::LoadImage(const char* id, Value& obj) +{ + + Image* img = new Image(); + + std::size_t embeddedDataLen = 0; + uint8_t* embeddedData = 0; + const char* mimeType = 0; + + // Check for extensions first (to detect binary embedded data) + MemIt extensions = obj.FindMember("extensions"); + if (extensions != obj.MemberEnd()) { + Value& exts = extensions->value; + + MemIt KHR_binary_glTF = exts.FindMember("KHR_binary_glTF"); + if (KHR_binary_glTF != exts.MemberEnd() && KHR_binary_glTF->value.IsObject()) { + + int width = TryReadMember(KHR_binary_glTF->value, "width", 0); + int height = TryReadMember(KHR_binary_glTF->value, "height", 0); + + ReadMember(KHR_binary_glTF->value, "mimeType", mimeType); + + const char* bufferViewId; + if (ReadMember(KHR_binary_glTF->value, "bufferView", bufferViewId)) { + Ptr bv = mBufferViews.Get(bufferViewId); + if (bv) { + embeddedDataLen = bv->byteLength; + embeddedData = new uint8_t[embeddedDataLen]; + memcpy(embeddedData, bv->buffer->GetPointer() + bv->byteOffset, embeddedDataLen); + } + } + } + } + + if (!embeddedDataLen) { + const char* uri; + if (ReadMember(obj, "uri", uri)) { + if (IsDataURI(uri)) { + const char* comma = strchr(uri, ','); + *const_cast(comma) = '\0'; + + bool isBase64 = (strstr(uri, "base64") != 0); + if (isBase64) { + embeddedDataLen = DecodeBase64(comma + 1, embeddedData); + } + + const char* sc = strchr(uri, ';'); + if (sc != 0) { + *const_cast(sc) = '\0'; + mimeType = uri; + } + } + else { + img->uri = uri; + } + } + } + + // Add the embedded texture + if (embeddedDataLen > 0) { + aiTexture* tex = new aiTexture(); + mImpTextures.push_back(tex); + + tex->mWidth = static_cast(embeddedDataLen); + tex->mHeight = 0; + tex->pcData = reinterpret_cast(embeddedData); + + if (mimeType) { + const char* ext = strchr(mimeType, '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + + std::size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + img->uri.data[0] = '*'; + img->uri.length = 1 + ASSIMP_itoa10(img->uri.data + 1, MAXLEN - 1, mImpTextures.size() - 1); + } + + return Ptr(img); +} + +Ptr glTFReader::LoadTexture(const char* id, Value& obj) +{ + Texture* tex = new Texture(); + + const char* source; + if (ReadMember(obj, "source", source)) { + tex->source = mImages.Get(source); + } + + return Ptr(tex); +} + +void glTFReader::SetMaterialColorProperty(aiMaterial* mat, Value& vals, const char* propName, aiTextureType texType, const char* pKey, unsigned int type, unsigned int idx) +{ + MemIt prop = vals.FindMember(propName); + if (prop != vals.MemberEnd()) { + aiColor3D col; + if (Read(prop->value, col)) { + mat->AddProperty(&col, 1, pKey, type, idx); + } + else if (prop->value.IsString()) { + Ptr tex = mTextures.Get(prop->value.GetString()); + if (tex && tex->source) { + mat->AddProperty(&tex->source->uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); + } + } + } +} + +unsigned int glTFReader::LoadMaterial(const char* id, Value& material) +{ + aiMaterial* mat = new aiMaterial(); + mImpMaterials.push_back(mat); + + const char* name; + if (ReadMember(material, "name", name)) { + aiString str(name); + mat->AddProperty(&str, AI_MATKEY_NAME); + } + + MemIt values = material.FindMember("values"); + if (values != material.MemberEnd() && values->value.IsObject()) { + Value& vals = values->value; + + SetMaterialColorProperty(mat, vals, "diffuse", aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(mat, vals, "specular", aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR); + SetMaterialColorProperty(mat, vals, "ambient", aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT); + + float shininess; + if (ReadMember(vals, "shininess", shininess)) { + mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS); + } + } + + MemIt extensions = material.FindMember("values"); + if (extensions != material.MemberEnd() && extensions->value.IsObject()) { + Value& exts = extensions->value; + + MemIt KHR_materials_common = exts.FindMember("KHR_materials_common"); + if (KHR_materials_common != exts.MemberEnd() && KHR_materials_common->value.IsObject()) { + // TODO: support KHR_materials_common (https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common) + } + } + + return static_cast(mImpMaterials.size() - 1); +} + +aiNode* glTFReader::LoadNode(const char* id, Value& node) +{ + aiNode* ainode = new aiNode(id); + + //MemIt name = node.FindMember("name"); + //if (name != node.MemberEnd() && name->value.IsString()) { + // strcpy(ainode->mName.data, name->value.GetString()); + //} + + MemIt children = node.FindMember("children"); + if (children != node.MemberEnd() && children->value.IsArray()) { + + ainode->mChildren = new aiNode*[children->value.Size()]; + //ainode->mNumChildren = 0; + + for (unsigned int i = 0; i < children->value.Size(); ++i) { + Value& child = children->value[i]; + if (child.IsString()) { + // get/create the child node + aiNode* aichild = mNodes.Get(child.GetString()); + aichild->mParent = ainode; + ainode->mChildren[ainode->mNumChildren++] = aichild; + } + } + } + + aiMatrix4x4& transf = ainode->mTransformation; + + MemIt matrix = node.FindMember("matrix"); + if (matrix != node.MemberEnd()) { + Read(matrix->value, transf); + } + else { + MemIt translation = node.FindMember("translation"); + if (translation != node.MemberEnd()) { + aiVector3D trans; + if (Read(matrix->value, trans)) { + aiMatrix4x4 m; + aiMatrix4x4::Translation(trans, m); + transf = m * transf; + } + } + + MemIt scale = node.FindMember("scale"); + if (scale != node.MemberEnd()) { + aiVector3D scal(1.f); + if (Read(matrix->value, scal)) { + aiMatrix4x4 m; + aiMatrix4x4::Scaling(scal, m); + transf = m * transf; + } + } + + MemIt rotation = node.FindMember("rotation"); + if (rotation != node.MemberEnd()) { + aiQuaternion rot; + if (Read(matrix->value, rot)) { + transf = aiMatrix4x4(rot.GetMatrix()) * transf; + } + } + } + + MemIt meshes = node.FindMember("meshes"); + if (meshes != node.MemberEnd() && meshes->value.IsArray()) { + std::size_t numMeshes = (std::size_t)meshes->value.Size(); + + std::vector meshList; + + for (std::size_t i = 0; i < numMeshes; ++i) { + if (meshes->value[i].IsString()) { + Range range = mMeshes.Get(meshes->value[i].GetString()); + for (unsigned int m = range.first; m < range.second; ++m) { + meshList.push_back(m); + } + } + } + + if (meshList.size()) { + ainode->mNumMeshes = meshList.size(); + ainode->mMeshes = new unsigned int[meshList.size()]; + std::swap_ranges(meshList.begin(), meshList.end(), ainode->mMeshes); + } + } + + // TODO load "skeletons", "skin", "jointName", "camera" + + return ainode; +} + + + + + +// +// glTFImporter methods +// + +template<> const std::string LogFunctions::log_prefix = "glTF: "; + +static const aiImporterDesc desc = { + "glTF Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour + | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" +}; glTFImporter::glTFImporter() : BaseImporter() -, m_scene( NULL ) -, m_buffer() { +{ } @@ -76,6 +1090,10 @@ glTFImporter::~glTFImporter() { } bool glTFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig ) const { + const std::string& extension = GetExtension(pFile); + if (extension == "gltf" || extension == "glb") { + return true; + } return false; } @@ -83,18 +1101,88 @@ const aiImporterDesc* glTFImporter::GetInfo() const { return &desc; } -void glTFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) { - m_scene = pScene; - boost::shared_ptr stream( pIOHandler->Open( pFile, "rb" ) ); - if (!stream.get()) { - throw DeadlyImportError( "Failed to open file " + pFile + "." ); +void glTFImporter::ReadBinaryHeader(IOStream& stream) +{ + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + ThrowException("Unable to read the file header"); } - // Get the file-size and validate it, throwing an exception when fails - size_t fileSize = stream->FileSize(); + if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + ThrowException("Invalid binary glTF file"); + } - // Allocate buffer and read file into it - TextFileToBuffer( stream.get(), m_buffer ); + AI_SWAP4(header.version); + if (header.version != 1) { + ThrowException("Unsupported binary glTF version"); + } + + AI_SWAP4(header.sceneFormat); + if (header.sceneFormat != SceneFormat_JSON) { + ThrowException("Unsupported binary glTF scene format"); + } + + AI_SWAP4(header.length); + AI_SWAP4(header.sceneLength); + + mSceneLength = static_cast(header.sceneLength); + + mBodyOffset = sizeof(header) + mSceneLength; + mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 + + mBodyLength = header.length - mBodyOffset; } +void glTFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) { + scoped_ptr stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + ThrowException("Could not open file for reading"); + } + + // is binary? then read the header + if (GetExtension(pFile) == "glb") { + ReadBinaryHeader(*stream); + } + else { + mSceneLength = stream->FileSize(); + mBodyLength = 0; + } + + + // read the scene data + + scoped_ptr sceneData = new char[mSceneLength + 1]; + sceneData[mSceneLength] = '\0'; + + if (stream->Read(sceneData, 1, mSceneLength) != mSceneLength) { + ThrowException("Could not read the file contents"); + } + + + // parse the JSON document + + Document doc; + doc.ParseInsitu(sceneData); + + if (doc.HasParseError()) { + char buffer[32]; + ASSIMP_itoa10(buffer, doc.GetErrorOffset()); + ThrowException(std::string("JSON parse error, offset ") + buffer + ": " + + GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + ThrowException("gltf file must be a JSON object!"); + } + + + // Buffer instance for the current file embedded contents + shared_ptr bodyBuffer; + if (mBodyLength > 0) { + bodyBuffer.reset(Buffer::FromStream(*stream, mBodyLength, mBodyOffset)); + } + + // import the data + glTFReader reader(pScene, doc, *pIOHandler, bodyBuffer); + reader.Load(); } diff --git a/code/glTFImporter.h b/code/glTFImporter.h index dc113c003..9254040c1 100644 --- a/code/glTFImporter.h +++ b/code/glTFImporter.h @@ -41,10 +41,34 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_GLTFIMPORTER_H_INC #include "BaseImporter.h" +#include "LogAux.h" +#include "DefaultIOSystem.h" + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# else +# define gltf_unordered_map map +# define gltf_unordered_multimap multimap +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include +# if _MSC_VER > 1600 +# define gltf_unordered_map unordered_map +# define gltf_unordered_multimap unordered_multimap +# else +# define gltf_unordered_map tr1::unordered_map +# define gltf_unordered_multimap tr1::unordered_multimap +# endif +#endif namespace Assimp { -class glTFImporter : public BaseImporter { +/** + * Load the glTF format. + * https://github.com/KhronosGroup/glTF/tree/master/specification + */ +class glTFImporter : public BaseImporter, public LogFunctions { public: glTFImporter(); virtual ~glTFImporter(); @@ -55,8 +79,10 @@ protected: virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ); private: - aiScene *m_scene; - std::vector m_buffer; + void ReadBinaryHeader(IOStream& stream); + + std::size_t mSceneLength; + std::size_t mBodyOffset, mBodyLength; }; } // Namespace assimp diff --git a/code/glTFUtil.cpp b/code/glTFUtil.cpp new file mode 100644 index 000000000..46ef32a42 --- /dev/null +++ b/code/glTFUtil.cpp @@ -0,0 +1,164 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#include "glTFUtil.h" + + +using namespace Assimp; +using namespace Assimp::glTF; + + +bool Assimp::glTF::IsDataURI(const char* uri) +{ + return strncmp(uri, "data:", 5) == 0; +} + + +static const uint8_t tableDecodeBase64[128] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static inline char EncodeCharBase64(uint8_t b) +{ + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[b]; +} + +static inline uint8_t DecodeCharBase64(char c) +{ + return tableDecodeBase64[c]; // TODO faster with lookup table or ifs? + /*if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + return c == '+' ? 62 : 63;*/ +} + +std::size_t Assimp::glTF::DecodeBase64( + const char* in, uint8_t*& out) +{ + return DecodeBase64(in, strlen(in), out); +} + +std::size_t Assimp::glTF::DecodeBase64( + const char* in, std::size_t inLength, uint8_t*& out) +{ + ai_assert(dataLen % 4 == 0); + + if (inLength < 4) { + out = 0; + return 0; + } + + int nEquals = int(in[inLength - 1] == '=') + + int(in[inLength - 2] == '='); + + std::size_t outLength = (inLength * 3) / 4 - nEquals; + out = new uint8_t[outLength]; + memset(out, 0, outLength); + + std::size_t j = 0; + + for (std::size_t i = 0; i < inLength; i += 4) { + uint8_t b0 = DecodeCharBase64(in[i]); + uint8_t b1 = DecodeCharBase64(in[i + 1]); + uint8_t b2 = DecodeCharBase64(in[i + 2]); + uint8_t b3 = DecodeCharBase64(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + out[j++] = (uint8_t)((b2 << 6) | b3); + } + + return outLength; +} + + + +void Assimp::glTF::EncodeBase64( + const uint8_t* in, std::size_t inLength, + std::string& out) +{ + std::size_t outLength = ((inLength + 2) / 3) * 4; + + out.resize(outLength); + + std::size_t j = 0; + for (std::size_t i = 0; i < inLength; i += 3) { + uint8_t b = (in[i] & 0xFC) >> 2; + out[j++] = EncodeCharBase64(b); + + b = (in[i] & 0x03) << 4; + if (i + 1 < inLength) { + b |= (in[i + 1] & 0xF0) >> 4; + out[j++] = EncodeCharBase64(b); + + b = (in[i + 1] & 0x0F) << 2; + if (i + 2 < inLength) { + b |= (in[i + 2] & 0xC0) >> 6; + out[j++] = EncodeCharBase64(b); + + b = in[i + 2] & 0x3F; + out[j++] = EncodeCharBase64(b); + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + } + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + out[j++] = '='; + } + } +} diff --git a/code/glTFUtil.h b/code/glTFUtil.h new file mode 100644 index 000000000..010d50ee3 --- /dev/null +++ b/code/glTFUtil.h @@ -0,0 +1,67 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLTFUTIL_H_INC +#define AI_GLTFUTIL_H_INC + +//#include "StreamReader.h" +//#include "MemoryIOWrapper.h" +#include "StringComparison.h" + +namespace Assimp { +namespace glTF { + + // + // Misc + // + + std::size_t DecodeBase64(const char* in, uint8_t*& out); + std::size_t DecodeBase64(const char* in, std::size_t inLength, uint8_t*& out); + + void EncodeBase64(const uint8_t* in, std::size_t inLength, std::string& out); + + + bool IsDataURI(const char* uri); + +} +} + + +#endif // AI_GLTFUTIL_H_INC +