diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/glTF/glTFAsset.inl')
-rw-r--r-- | libs/assimp/code/AssetLib/glTF/glTFAsset.inl | 1316 |
1 files changed, 1316 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/glTF/glTFAsset.inl b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl new file mode 100644 index 0000000..2b76a30 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl @@ -0,0 +1,1316 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 <assimp/MemoryIOWrapper.h> +#include <assimp/StringUtils.h> +#include <iomanip> + +// Header files, Assimp +#include <assimp/DefaultLogger.hpp> +#include <assimp/Base64.hpp> + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +// Header files, Open3DGC. +#include <Open3DGC/o3dgcSC3DMCDecoder.h> +#endif + +using namespace Assimp; +using namespace glTFCommon; + +namespace glTF { + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +// +// LazyDict methods +// + +template <class T> +inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) : + mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) { + asset.mDicts.push_back(this); // register to the list of dictionaries +} + +template <class T> +inline LazyDict<T>::~LazyDict() { + for (size_t i = 0; i < mObjs.size(); ++i) { + delete mObjs[i]; + } +} + +template <class T> +inline void LazyDict<T>::AttachToDocument(Document &doc) { + Value *container = 0; + + if (mExtId) { + if (Value *exts = FindObject(doc, "extensions")) { + container = FindObject(*exts, mExtId); + } + } else { + container = &doc; + } + + if (container) { + mDict = FindObject(*container, mDictId); + } +} + +template <class T> +inline void LazyDict<T>::DetachFromDocument() { + mDict = 0; +} + +template <class T> +Ref<T> LazyDict<T>::Get(unsigned int i) { + return Ref<T>(mObjs, i); +} + +template <class T> +Ref<T> LazyDict<T>::Get(const char *id) { + id = T::TranslateId(mAsset, id); + + typename Dict::iterator it = mObjsById.find(id); + if (it != mObjsById.end()) { // already created? + return Ref<T>(mObjs, it->second); + } + + // read it from the JSON object + if (!mDict) { + throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\""); + } + + Value::MemberIterator obj = mDict->FindMember(id); + if (obj == mDict->MemberEnd()) { + throw DeadlyImportError("GLTF: Missing object with id \"", id, "\" in \"", mDictId, "\""); + } + if (!obj->value.IsObject()) { + throw DeadlyImportError("GLTF: Object with id \"", id, "\" is not a JSON object"); + } + + // create an instance of the given type + T *inst = new T(); + inst->id = id; + ReadMember(obj->value, "name", inst->name); + inst->Read(obj->value, mAsset); + return Add(inst); +} + +template <class T> +Ref<T> LazyDict<T>::Add(T *obj) { + unsigned int idx = unsigned(mObjs.size()); + mObjs.push_back(obj); + mObjsById[obj->id] = idx; + mAsset.mUsedIds[obj->id] = true; + return Ref<T>(mObjs, idx); +} + +template <class T> +Ref<T> LazyDict<T>::Create(const char *id) { + Asset::IdMap::iterator it = mAsset.mUsedIds.find(id); + if (it != mAsset.mUsedIds.end()) { + throw DeadlyImportError("GLTF: two objects with the same ID exist"); + } + T *inst = new T(); + inst->id = id; + return Add(inst); +} + +// +// glTF dictionary objects methods +// + +inline Buffer::Buffer() : + byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) {} + +inline Buffer::~Buffer() { + for (SEncodedRegion *reg : EncodedRegion_List) + delete reg; +} + +inline const char *Buffer::TranslateId(Asset &r, const char *id) { + // Compatibility with old spec + if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { + return "binary_glTF"; + } + + return id; +} + +inline void Buffer::Read(Value &obj, Asset &r) { + size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0); + byteLength = statedLength; + + Value *it = FindString(obj, "uri"); + if (!it) { + if (statedLength > 0) { + throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); + } + return; + } + + const char *uri = it->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { + if (dataURI.base64) { + uint8_t *data = 0; + this->byteLength = Base64::Decode(dataURI.data, dataURI.dataLength, data); + this->mData.reset(data, std::default_delete<uint8_t[]>()); + + if (statedLength > 0 && this->byteLength != statedLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + } else { // assume raw data + if (statedLength != dataURI.dataLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + + this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete<uint8_t[]>()); + memcpy(this->mData.get(), dataURI.data, dataURI.dataLength); + } + } else { // Local file + if (byteLength > 0) { + std::string dir = !r.mCurrentAssetDir.empty() ? ( + r.mCurrentAssetDir.back() == '/' ? + r.mCurrentAssetDir : + r.mCurrentAssetDir + '/') : + ""; + + IOStream *file = r.OpenFile(dir + uri, "rb"); + if (file) { + bool ok = LoadFromStream(*file, byteLength); + delete file; + + if (!ok) + throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\""); + } else { + throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\""); + } + } + } +} + +inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) { + byteLength = length ? length : stream.FileSize(); + + if (baseOffset) { + stream.Seek(baseOffset, aiOrigin_SET); + } + + mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>()); + + if (stream.Read(mData.get(), byteLength, 1) != 1) { + return false; + } + return true; +} + +inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) { + // Check pointer to data + if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); + + // Check offset + if (pOffset > byteLength) { + const uint8_t val_size = 32; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT, pOffset); + throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region."); + } + + // Check length + if ((pOffset + pEncodedData_Length) > byteLength) { + const uint8_t val_size = 64; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length); + throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range."); + } + + // Add new region + EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); + // And set new value for "byteLength" + byteLength += (pDecodedData_Length - pEncodedData_Length); +} + +inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + + for (SEncodedRegion *reg : EncodedRegion_List) { + if (reg->ID == pID) { + EncodedRegion_Current = reg; + + return; + } + } + + throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found."); +} + +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { + const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; + + uint8_t *new_data; + + if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false; + + new_data = new uint8_t[new_data_size]; + // Copy data which place before replacing part. + memcpy(new_data, mData.get(), pBufferData_Offset); + // Copy new data. + memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); + // Copy data which place after replacing part. + memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset); + // Apply new data + mData.reset(new_data, std::default_delete<uint8_t[]>()); + byteLength = new_data_size; + + return true; +} + +inline size_t Buffer::AppendData(uint8_t *data, size_t length) { + size_t offset = this->byteLength; + Grow(length); + memcpy(mData.get() + offset, data, length); + return offset; +} + +inline void Buffer::Grow(size_t amount) { + if (amount <= 0) return; + if (capacity >= byteLength + amount) { + byteLength += amount; + return; + } + + // Shift operation is standard way to divide integer by 2, it doesn't cast it to float back and forth, also works for odd numbers, + // originally it would look like: static_cast<size_t>(capacity * 1.5f) + capacity = std::max(capacity + (capacity >> 1), byteLength + amount); + + uint8_t *b = new uint8_t[capacity]; + if (mData) memcpy(b, mData.get(), byteLength); + mData.reset(b, std::default_delete<uint8_t[]>()); + byteLength += amount; +} + +// +// struct BufferView +// + +inline void BufferView::Read(Value &obj, Asset &r) { + const char *bufferId = MemberOrDefault<const char *>(obj, "buffer", 0); + if (bufferId) { + buffer = r.buffers.Get(bufferId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteLength = MemberOrDefault(obj, "byteLength", 0u); +} + +// +// struct Accessor +// + +inline void Accessor::Read(Value &obj, Asset &r) { + const char *bufferViewId = MemberOrDefault<const char *>(obj, "bufferView", 0); + if (bufferViewId) { + bufferView = r.bufferViews.Get(bufferViewId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteStride = MemberOrDefault(obj, "byteStride", 0u); + componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); + count = MemberOrDefault(obj, "count", 0u); + + const char *typestr; + type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR; +} + +inline unsigned int Accessor::GetNumComponents() { + return AttribType::GetNumComponents(type); +} + +inline unsigned int Accessor::GetBytesPerComponent() { + return int(ComponentTypeSize(componentType)); +} + +inline unsigned int Accessor::GetElementSize() { + return GetNumComponents() * GetBytesPerComponent(); +} + +inline uint8_t *Accessor::GetPointer() { + if (!bufferView || !bufferView->buffer) return 0; + uint8_t *basePtr = bufferView->buffer->GetPointer(); + if (!basePtr) return 0; + + size_t offset = byteOffset + bufferView->byteOffset; + + // Check if region is encoded. + if (bufferView->buffer->EncodedRegion_Current != nullptr) { + const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; + const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; + + if ((offset >= begin) && (offset < end)) + return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } + + return basePtr + offset; +} + +namespace { +inline void CopyData(size_t count, + const uint8_t *src, size_t src_stride, + uint8_t *dst, size_t dst_stride) { + if (src_stride == dst_stride) { + memcpy(dst, src, count * src_stride); + } else { + size_t sz = std::min(src_stride, dst_stride); + for (size_t i = 0; i < count; ++i) { + memcpy(dst, src, sz); + if (sz < dst_stride) { + memset(dst + sz, 0, dst_stride - sz); + } + src += src_stride; + dst += dst_stride; + } + } +} +} // namespace + +template <class T> +bool Accessor::ExtractData(T *&outData) { + uint8_t *data = GetPointer(); + if (!data) return false; + + const size_t elemSize = GetElementSize(); + const size_t totalSize = elemSize * count; + + const size_t stride = byteStride ? byteStride : elemSize; + + const size_t targetElemSize = sizeof(T); + ai_assert(elemSize <= targetElemSize); + + ai_assert(count * stride <= bufferView->byteLength); + + outData = new T[count]; + if (stride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } else { + for (size_t i = 0; i < count; ++i) { + memcpy(outData + i, data + i * stride, elemSize); + } + } + + return true; +} + +inline void Accessor::WriteData(size_t cnt, const void *src_buffer, size_t src_stride) { + uint8_t *buffer_ptr = bufferView->buffer->GetPointer(); + size_t offset = byteOffset + bufferView->byteOffset; + + size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); + + const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer); + uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset); + + ai_assert(dst + count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength); + CopyData(cnt, src, src_stride, dst, dst_stride); +} + +inline Accessor::Indexer::Indexer(Accessor &acc) : + accessor(acc), data(acc.GetPointer()), elemSize(acc.GetElementSize()), stride(acc.byteStride ? acc.byteStride : elemSize) { +} + +//! Accesses the i-th value as defined by the accessor +template <class T> +T Accessor::Indexer::GetValue(int i) { + ai_assert(data); + ai_assert(i * stride < accessor.bufferView->byteLength); + T value = T(); + memcpy(&value, data + i * stride, elemSize); + //value >>= 8 * (sizeof(T) - elemSize); + return value; +} + +inline Image::Image() : + width(0), height(0), mDataLength(0) { +} + +inline void Image::Read(Value &obj, Asset &r) { + // Check for extensions first (to detect binary embedded data) + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_binary_glTF) { + if (Value *ext = FindObject(*extensions, "KHR_binary_glTF")) { + + width = MemberOrDefault(*ext, "width", 0); + height = MemberOrDefault(*ext, "height", 0); + + ReadMember(*ext, "mimeType", mimeType); + + const char *bufferViewId; + if (ReadMember(*ext, "bufferView", bufferViewId)) { + Ref<BufferView> bv = r.bufferViews.Get(bufferViewId); + if (bv) { + mDataLength = bv->byteLength; + mData.reset(new uint8_t[mDataLength]); + memcpy(mData.get(), bv->buffer->GetPointer() + bv->byteOffset, mDataLength); + } + } + } + } + } + + if (!mDataLength) { + Value *curUri = FindString(obj, "uri"); + if (nullptr != curUri) { + const char *uristr = curUri->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uristr, curUri->GetStringLength(), dataURI)) { + mimeType = dataURI.mediaType; + if (dataURI.base64) { + uint8_t *ptr = nullptr; + mDataLength = Base64::Decode(dataURI.data, dataURI.dataLength, ptr); + mData.reset(ptr); + } + } else { + this->uri = uristr; + } + } + } +} + +inline uint8_t *Image::StealData() { + mDataLength = 0; + return mData.release(); +} + +inline void Image::SetData(uint8_t *data, size_t length, Asset &r) { + Ref<Buffer> b = r.GetBodyBuffer(); + if (b) { // binary file: append to body + std::string bvId = r.FindUniqueID(this->id, "imgdata"); + bufferView = r.bufferViews.Create(bvId); + + bufferView->buffer = b; + bufferView->byteLength = length; + bufferView->byteOffset = b->AppendData(data, length); + } else { // text file: will be stored as a data uri + uint8_t *temp = new uint8_t[length]; + memcpy(temp, data, length); + this->mData.reset(temp); + this->mDataLength = length; + } +} + +inline void Sampler::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + ReadMember(obj, "magFilter", magFilter); + ReadMember(obj, "minFilter", minFilter); + ReadMember(obj, "wrapS", wrapS); + ReadMember(obj, "wrapT", wrapT); +} + +inline void Sampler::SetDefaults() { + magFilter = SamplerMagFilter_Linear; + minFilter = SamplerMinFilter_Linear; + wrapS = SamplerWrap_Repeat; + wrapT = SamplerWrap_Repeat; +} + +inline void Texture::Read(Value &obj, Asset &r) { + const char *sourcestr; + if (ReadMember(obj, "source", sourcestr)) { + source = r.images.Get(sourcestr); + } + + const char *samplerstr; + if (ReadMember(obj, "sampler", samplerstr)) { + sampler = r.samplers.Get(samplerstr); + } +} + +namespace { +inline void ReadMaterialProperty(Asset &r, Value &vals, const char *propName, TexProperty &out) { + if (Value *prop = FindMember(vals, propName)) { + if (prop->IsString()) { + out.texture = r.textures.Get(prop->GetString()); + } else { + ReadValue(*prop, out.color); + } + } +} +} // namespace + +inline void Material::Read(Value &material, Asset &r) { + SetDefaults(); + + if (Value *values = FindObject(material, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + + if (Value *extensions = FindObject(material, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + if (Value *tnq = FindString(*ext, "technique")) { + const char *t = tnq->GetString(); + if (strcmp(t, "BLINN") == 0) + technique = Technique_BLINN; + else if (strcmp(t, "PHONG") == 0) + technique = Technique_PHONG; + else if (strcmp(t, "LAMBERT") == 0) + technique = Technique_LAMBERT; + else if (strcmp(t, "CONSTANT") == 0) + technique = Technique_CONSTANT; + } + + if (Value *values = FindObject(*ext, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "doubleSided", doubleSided); + ReadMember(*values, "transparent", transparent); + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + } + } + } +} + +namespace { +void SetVector(vec4 &v, float x, float y, float z, float w) { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; +} +} // namespace + +inline void Material::SetDefaults() { + SetVector(ambient.color, 0, 0, 0, 1); + SetVector(diffuse.color, 0, 0, 0, 1); + SetVector(specular.color, 0, 0, 0, 1); + SetVector(emission.color, 0, 0, 0, 1); + + doubleSided = false; + transparent = false; + transparency = 1.0; + shininess = 0.0; + + technique = Technique_undefined; +} + +namespace { + +template <int N> +inline int Compare(const char *attr, const char (&str)[N]) { + return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; +} + +inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.attributes.position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.attributes.normal); + } else if ((pos = Compare(attr, "TEXCOORD"))) { + v = &(p.attributes.texcoord); + } else if ((pos = Compare(attr, "COLOR"))) { + v = &(p.attributes.color); + } else if ((pos = Compare(attr, "JOINT"))) { + v = &(p.attributes.joint); + } else if ((pos = Compare(attr, "JOINTMATRIX"))) { + v = &(p.attributes.jointmatrix); + } else if ((pos = Compare(attr, "WEIGHT"))) { + v = &(p.attributes.weight); + } else + return false; + return true; +} +} // namespace + +inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { + /****************** Mesh primitives ******************/ + Value *curPrimitives = FindArray(pJSON_Object, "primitives"); + if (nullptr != curPrimitives) { + this->primitives.resize(curPrimitives->Size()); + for (unsigned int i = 0; i < curPrimitives->Size(); ++i) { + Value &primitive = (*curPrimitives)[i]; + + Primitive &prim = this->primitives[i]; + prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES); + + if (Value *attrs = FindObject(primitive, "attributes")) { + for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { + if (!it->value.IsString()) continue; + const char *attr = it->name.GetString(); + // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX, + // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. + + int undPos = 0; + Mesh::AccessorList *vec = 0; + if (GetAttribVector(prim, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if ((*vec).size() <= idx) (*vec).resize(idx + 1); + (*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString()); + } + } + } + + if (Value *indices = FindString(primitive, "indices")) { + prim.indices = pAsset_Root.accessors.Get(indices->GetString()); + } + + if (Value *material = FindString(primitive, "material")) { + prim.material = pAsset_Root.materials.Get(material->GetString()); + } + } + } + + /****************** Mesh extensions ******************/ + Value *json_extensions = FindObject(pJSON_Object, "extensions"); + + if (json_extensions == nullptr) goto mr_skip_extensions; + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for (Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) { + if (it_memb->name.GetString() == std::string("Open3DGC-compression")) { + // Search for compressed data. + // Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and + // new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure. + Value *comp_data = FindObject(it_memb->value, "compressedData"); + + if (comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); + + ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data."); + +/************** Read data from JSON-document **************/ +#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ + if (!ReadMember(*comp_data, pFieldName, pOut)) { \ + throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \ + } + + const char *mode_str; + const char *type_str; + ComponentType component_type; + SCompression_Open3DGC *ext_o3dgc = new SCompression_Open3DGC; + + MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer); + MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset); + MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type); + MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str); + MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count); + MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str); + MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount); + MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount); + +#undef MESH_READ_COMPRESSEDDATA_MEMBER + + // Check some values + if (strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data."); + if (component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data."); + + // Set read/write data mode. + if (strcmp(mode_str, "binary") == 0) + ext_o3dgc->Binary = true; + else if (strcmp(mode_str, "ascii") == 0) + ext_o3dgc->Binary = false; + else + throw DeadlyImportError("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"", mode_str, "\"."); + + /************************ Decoding ************************/ + Decode_O3DGC(*ext_o3dgc, pAsset_Root); + Extension.push_back(ext_o3dgc); // store info in mesh extensions list. + } // if(it_memb->name.GetString() == "Open3DGC-compression") + else { + throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\"."); + } + } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) +#endif + +mr_skip_extensions: + + return; // After label some operators must be present. +} + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root) { + typedef unsigned short IndicesType; ///< \sa glTFExporter::ExportMeshes. + + o3dgc::SC3DMCDecoder<IndicesType> decoder; + o3dgc::IndexedFaceSet<IndicesType> ifs; + o3dgc::BinaryStream bstream; + uint8_t *decoded_data; + size_t decoded_data_size = 0; + Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer); + + // Read data from buffer and place it in BinaryStream for decoder. + // Just "Count" because always is used type equivalent to uint8_t. + bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast<unsigned long>(pCompression_Open3DGC.Count)); + + // After decoding header we can get size of primitives. + if (decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header."); + + /****************** Get sizes of arrays and check sizes ******************/ + // Note. See "Limitations for meshes when using Open3DGC-compression". + + // Indices + size_t size_coordindex = ifs.GetNCoordIndex() * 3; // See float attributes note. + + if (primitives[0].indices->count != size_coordindex) + throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (", ai_to_string(size_coordindex), + ") not equal to uncompressed (", ai_to_string(primitives[0].indices->count), ")."); + + size_coordindex *= sizeof(IndicesType); + // Coordinates + size_t size_coord = ifs.GetNCoord(); // See float attributes note. + + if (primitives[0].attributes.position[0]->count != size_coord) + throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (", ai_to_string(size_coord), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.position[0]->count), ")."); + + size_coord *= 3 * sizeof(float); + // Normals + size_t size_normal = ifs.GetNNormal(); // See float attributes note. + + if (primitives[0].attributes.normal[0]->count != size_normal) + throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (", ai_to_string(size_normal), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.normal[0]->count), ")."); + + size_normal *= 3 * sizeof(float); + // Additional attributes. + std::vector<size_t> size_floatattr; + std::vector<size_t> size_intattr; + + size_floatattr.resize(ifs.GetNumFloatAttributes()); + size_intattr.resize(ifs.GetNumIntAttributes()); + + decoded_data_size = size_coordindex + size_coord + size_normal; + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. + // Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects... + size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx)); + + switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + // Check situation when encoded data contain texture coordinates but primitive not. + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + if (primitives[0].attributes.texcoord[idx]->count != tval) + throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); + + idx_texcoord++; + } else { + ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul); // Disable decoding this attribute. + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); + } + + tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array. + size_floatattr[idx] = tval; + decoded_data_size += tval; + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. See float attributes note. + size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx)); + switch (ifs.GetIntAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); + } + + tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long); // See float attributes note. + size_intattr[idx] = tval; + decoded_data_size += tval; + } + + // Create array for decoded data. + decoded_data = new uint8_t[decoded_data_size]; + + /****************** Set right array regions for decoder ******************/ + + auto get_buf_offset = [](Ref<Accessor> &pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; }; + + // Indices + ifs.SetCoordIndex((IndicesType *const)(decoded_data + get_buf_offset(primitives[0].indices))); + // Coordinates + ifs.SetCoord((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0]))); + // Normals + if (size_normal) { + ifs.SetNormal((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0]))); + } + + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + // See above about absent attributes. + ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); + idx_texcoord++; + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); + } + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + switch (ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); + } + } + + // + // Decode data + // + if (decoder.DecodePayload(ifs, bstream) != o3dgc::O3DGC_OK) { + throw DeadlyImportError("GLTF: can not decode Open3DGC data."); + } + + // Set encoded region for "buffer". + buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id); + // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data". + // "delete [] output_data;" +} +#endif + +inline void Camera::Read(Value &obj, Asset & /*r*/) { + type = MemberOrDefault(obj, "type", Camera::Perspective); + + const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective"; + + Value *it = FindObject(obj, subobjId); + if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); + + if (type == Camera::Perspective) { + perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f); + perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f / 2.f); + perspective.zfar = MemberOrDefault(*it, "zfar", 100.f); + perspective.znear = MemberOrDefault(*it, "znear", 0.01f); + } else { + ortographic.xmag = MemberOrDefault(*it, "xmag", 1.f); + ortographic.ymag = MemberOrDefault(*it, "ymag", 1.f); + ortographic.zfar = MemberOrDefault(*it, "zfar", 100.f); + ortographic.znear = MemberOrDefault(*it, "znear", 0.01f); + } +} + +inline void Light::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + Value *curType = FindString(obj, "type"); + if (nullptr != curType) { + const char *t = curType->GetString(); + if (strcmp(t, "ambient") == 0) + this->type = Type_ambient; + else if (strcmp(t, "directional") == 0) + this->type = Type_directional; + else if (strcmp(t, "point") == 0) + this->type = Type_point; + else if (strcmp(t, "spot") == 0) + this->type = Type_spot; + + if (this->type != Type_undefined) { + if (Value *vals = FindString(obj, t)) { + ReadMember(*vals, "color", color); + + ReadMember(*vals, "constantAttenuation", constantAttenuation); + ReadMember(*vals, "linearAttenuation", linearAttenuation); + ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation); + ReadMember(*vals, "distance", distance); + + ReadMember(*vals, "falloffAngle", falloffAngle); + ReadMember(*vals, "falloffExponent", falloffExponent); + } + } + } +} + +inline void Light::SetDefaults() { +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + type = Type_undefined; + + SetVector(color, 0.f, 0.f, 0.f, 1.f); + + constantAttenuation = 0.f; + linearAttenuation = 1.f; + quadraticAttenuation = 1.f; + distance = 0.f; + + falloffAngle = static_cast<float>(M_PI / 2.f); + falloffExponent = 0.f; +} + +inline void Node::Read(Value &obj, Asset &r) { + if (name.empty()) { + name = id; + } + + Value *curChildren = FindArray(obj, "children"); + if (nullptr != curChildren) { + this->children.reserve(curChildren->Size()); + for (unsigned int i = 0; i < curChildren->Size(); ++i) { + Value &child = (*curChildren)[i]; + if (child.IsString()) { + // get/create the child node + Ref<Node> chn = r.nodes.Get(child.GetString()); + if (chn) this->children.push_back(chn); + } + } + } + + Value *curMatrix = FindArray(obj, "matrix"); + if (nullptr != curMatrix) { + ReadValue(*curMatrix, this->matrix); + } else { + ReadMember(obj, "translation", translation); + ReadMember(obj, "scale", scale); + ReadMember(obj, "rotation", rotation); + } + + Value *curMeshes = FindArray(obj, "meshes"); + if (nullptr != curMeshes) { + unsigned int numMeshes = (unsigned int)curMeshes->Size(); + + std::vector<unsigned int> meshList; + + this->meshes.reserve(numMeshes); + for (unsigned i = 0; i < numMeshes; ++i) { + if ((*curMeshes)[i].IsString()) { + Ref<Mesh> mesh = r.meshes.Get((*curMeshes)[i].GetString()); + if (mesh) { + this->meshes.push_back(mesh); + } + } + } + } + + Value *curCamera = FindString(obj, "camera"); + if (nullptr != curCamera) { + this->camera = r.cameras.Get(curCamera->GetString()); + if (this->camera) { + this->camera->id = this->id; + } + } + + // TODO load "skeletons", "skin", "jointName" + + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + Value *curLight = FindString(*ext, "light"); + if (nullptr != curLight) { + this->light = r.lights.Get(curLight->GetString()); + } + } + } + } +} + +inline void Scene::Read(Value &obj, Asset &r) { + if (Value *array = FindArray(obj, "nodes")) { + for (unsigned int i = 0; i < array->Size(); ++i) { + if (!(*array)[i].IsString()) continue; + Ref<Node> node = r.nodes.Get((*array)[i].GetString()); + if (node) + this->nodes.push_back(node); + } + } +} + +inline void AssetMetadata::Read(Document &doc) { + // read the version, etc. + if (Value *obj = FindObject(doc, "asset")) { + ReadMember(*obj, "copyright", copyright); + ReadMember(*obj, "generator", generator); + + premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false); + + if (Value *versionString = FindString(*obj, "version")) { + version = versionString->GetString(); + } else if (Value *versionNumber = FindNumber(*obj, "version")) { + char buf[4]; + + ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble()); + + version = buf; + } + + Value *curProfile = FindObject(*obj, "profile"); + if (nullptr != curProfile) { + ReadMember(*curProfile, "api", this->profile.api); + ReadMember(*curProfile, "version", this->profile.version); + } + } + + if (version.empty() || version[0] != '1') { + throw DeadlyImportError("GLTF: Unsupported glTF version: ", version); + } +} + +// +// Asset methods implementation +// + +inline void Asset::ReadBinaryHeader(IOStream &stream) { + 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 = ai_to_string(header.version); + if (header.version != 1) { + throw DeadlyImportError("GLTF: Unsupported binary glTF version"); + } + + AI_SWAP4(header.sceneFormat); + if (header.sceneFormat != SceneFormat_JSON) { + throw DeadlyImportError("GLTF: Unsupported binary glTF scene format"); + } + + AI_SWAP4(header.length); + AI_SWAP4(header.sceneLength); + + static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits"); + mSceneLength = static_cast<size_t>(header.sceneLength); // Can't be larger than 4GB (max. uint32_t) + + mBodyOffset = sizeof(header) + mSceneLength; + mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 + + mBodyLength = header.length - mBodyOffset; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) { + mCurrentAssetDir.clear(); + + /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);*/ + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = getCurrentAssetDir(pFile); + } + + shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + // is binary? then read the header + if (isBinary) { + SetAsBinary(); // also creates the body buffer + ReadBinaryHeader(*stream); + } else { + mSceneLength = stream->FileSize(); + mBodyLength = 0; + } + + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + + // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation + if (mSceneLength >= std::numeric_limits<uint32_t>::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } + + // read the scene data, ensure null termination + std::vector<char> sceneData(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + + // parse the JSON document + + Document doc; + doc.ParseInsitu(&sceneData[0]); + + if (doc.HasParseError()) { + char buffer[32]; + ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset())); + throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); + } + + // Fill the buffer instance for the current file embedded contents + if (mBodyLength > 0) { + if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { + throw DeadlyImportError("GLTF: Unable to read gltf file"); + } + } + + // Load the metadata + asset.Read(doc); + ReadExtensionsUsed(doc); + + // Prepare the dictionaries + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->AttachToDocument(doc); + } + + // Read the "scene" property, which specifies which scene to load + // and recursively load everything referenced by it + Value *curScene = FindString(doc, "scene"); + if (nullptr != curScene) { + this->scene = scenes.Get(curScene->GetString()); + } + + // Clean up + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->DetachFromDocument(); + } +} + +inline void Asset::SetAsBinary() { + if (!extensionsUsed.KHR_binary_glTF) { + extensionsUsed.KHR_binary_glTF = true; + mBodyBuffer = buffers.Create("binary_glTF"); + mBodyBuffer->MarkAsSpecial(); + } +} + +inline void Asset::ReadExtensionsUsed(Document &doc) { + Value *extsUsed = FindArray(doc, "extensionsUsed"); + if (!extsUsed) return; + + std::gltf_unordered_map<std::string, bool> exts; + + for (unsigned int i = 0; i < extsUsed->Size(); ++i) { + if ((*extsUsed)[i].IsString()) { + exts[(*extsUsed)[i].GetString()] = true; + } + } + + CHECK_EXT(KHR_binary_glTF); + CHECK_EXT(KHR_materials_common); + +#undef CHECK_EXT +} + +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) { +#ifdef ASSIMP_API + (void)absolute; + return mIOSystem->Open(path, mode); +#else + if (path.size() < 2) return 0; + if (!absolute && path[1] != ':' && path[0] != '/') { // relative? + path = mCurrentAssetDir + path; + } + FILE *f = fopen(path.c_str(), mode); + return f ? new IOStream(f) : 0; +#endif +} + +inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) { + std::string id = str; + + if (!id.empty()) { + if (mUsedIds.find(id) == mUsedIds.end()) + return id; + + id += "_"; + } + + id += suffix; + + Asset::IdMap::iterator it = mUsedIds.find(id); + if (it == mUsedIds.end()) + return id; + + char buffer[1024]; + int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str()); + for (int i = 0; it != mUsedIds.end(); ++i) { + ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i); + id = buffer; + it = mUsedIds.find(id); + } + + return id; +} + +#if _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +} // namespace glTF |