/* 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 ASSIMP_BUILD_NO_GLTF_IMPORTER #include "glTFImporter.h" #include "StreamReader.h" #include "DefaultIOSystem.h" #include #include #include #include #include #include "glTFFileData.h" #include "glTFUtil.h" #define RAPIDJSON_HAS_STDSTRING 1 #include #include #include 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 // #define GETF(VAL, OUT) { if ((VAL).IsNumber()) (OUT) = static_cast((VAL).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 // //! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID template< class T, class INST, T(INST::*FACTORY_FN)(const char*, Value&)> class LazyDict { typedef typename std::gltf_unordered_map Map; Value* mDict; //! JSON dictionary object const char* mDictId; //! ID of the dictionary object INST& mInstance; //! The reader object instance Map mReadObjs; //! The read objects public: LazyDict(INST& 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 : 0; } T Get(const char* id) { if (!mDict) return T(); // section was missing typename 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, &T::LoadAccessor> mAccessors; //LazyDict mAnimations; //LazyDict mAssets; LazyDict, T, &T::LoadBuffer> mBuffers; LazyDict, T, &T::LoadBufferView> mBufferViews; //LazyDict mCameras; LazyDict, T, &T::LoadImage> mImages; LazyDict mMaterials; LazyDict mMeshes; LazyDict mNodes; //LazyDict, T, &T::LoadProgram> mPrograms; //LazyDict, T, &T::LoadSampler> mSamplers; //LazyDict, T, &T::LoadShader> mShaders; //LazyDict, T, &T::LoadSkin> mSkins; //LazyDict,T, &T::LoadTechnique> mTechniques; LazyDict, T, &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; } } //if (!mScene->mRootNode) { //mScene->mRootNode = new aiNode("EMPTY"); //} } 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); shared_ptr dataptr(data); b = new Buffer(dataptr, 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) { ai_assert(data); const std::size_t totalSize = elemSize * count; const std::size_t targetElemSize = sizeof(T); ai_assert(elemSize <= targetElemSize); ai_assert(count*byteStride <= bufferView->byteLength); 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; } //! Gets the i-th value as defined by the accessor template T GetValue(int i) { ai_assert(data); ai_assert(i*byteStride < bufferView->byteLength); T value = T(); memcpy(&value, data + i*byteStride, elemSize); //value >>= 8 * (sizeof(T) - elemSize); return value; } //! Gets the i-th value as defined by the accessor unsigned int GetUInt(int i) { return GetValue(i); } }; 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'; if (idx >= 0 && idx <= AI_MAX_NUMBER_OF_TEXTURECOORDS) { 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 int i = 0; i < acc->count; ++i) { setFace(faces[i], acc->GetUInt(i)); } break; } case PrimitiveMode_LINES: { nFaces = acc->count / 2; faces = new aiFace[nFaces]; for (unsigned int i = 0; i < acc->count; i += 2) { setFace(faces[i / 2], acc->GetUInt(i), acc->GetUInt(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->GetUInt(0), acc->GetUInt(1)); for (unsigned int i = 2; i < acc->count; ++i) { setFace(faces[i - 1], faces[i - 2].mIndices[1], acc->GetUInt(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 int i = 0; i < acc->count; i += 3) { setFace(faces[i / 3], acc->GetUInt(i), acc->GetUInt(i + 1), acc->GetUInt(i + 2)); } break; } case PrimitiveMode_TRIANGLE_STRIP: { nFaces = acc->count - 2; faces = new aiFace[nFaces]; setFace(faces[0], acc->GetUInt(0), acc->GetUInt(1), acc->GetUInt(2)); for (unsigned int i = 3; i < acc->count; ++i) { setFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], acc->GetUInt(i)); } break; } case PrimitiveMode_TRIANGLE_FAN: nFaces = acc->count - 2; faces = new aiFace[nFaces]; setFace(faces[0], acc->GetUInt(0), acc->GetUInt(1), acc->GetUInt(2)); for (unsigned int i = 3; i < acc->count; ++i) { setFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], acc->GetUInt(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()); if (aichild) { 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() { } 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; } const aiImporterDesc* glTFImporter::GetInfo() const { return &desc; } void glTFImporter::ReadBinaryHeader(IOStream& stream) { GLB_Header header; if (stream.Read(&header, sizeof(header), 1) != 1) { ThrowException("Unable to read the file header"); } if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { ThrowException("Invalid binary glTF file"); } 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(); if (pScene->mNumMeshes == 0) { pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; } } #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER