1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2021, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14copyright notice, this list of conditions and the
15following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18copyright notice, this list of conditions and the
19following disclaimer in the documentation and/or other
20materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23contributors may be used to endorse or promote products
24derived from this software without specific prior
25written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42#include <assimp/MemoryIOWrapper.h>
43#include <assimp/StringUtils.h>
44#include <iomanip>
45
46// Header files, Assimp
47#include <assimp/DefaultLogger.hpp>
48
49#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
50// Header files, Open3DGC.
51#include <Open3DGC/o3dgcSC3DMCDecoder.h>
52#endif
53
54using namespace Assimp;
55using namespace glTFCommon;
56
57namespace glTF {
58
59#if _MSC_VER
60#pragma warning(push)
61#pragma warning(disable : 4706)
62#endif // _MSC_VER
63
64//
65// LazyDict methods
66//
67
68template <class T>
69inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) :
70        mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) {
71    asset.mDicts.push_back(this); // register to the list of dictionaries
72}
73
74template <class T>
75inline LazyDict<T>::~LazyDict() {
76    for (size_t i = 0; i < mObjs.size(); ++i) {
77        delete mObjs[i];
78    }
79}
80
81template <class T>
82inline void LazyDict<T>::AttachToDocument(Document &doc) {
83    Value *container = 0;
84
85    if (mExtId) {
86        if (Value *exts = FindObject(doc, "extensions")) {
87            container = FindObject(*exts, mExtId);
88        }
89    } else {
90        container = &doc;
91    }
92
93    if (container) {
94        mDict = FindObject(*container, mDictId);
95    }
96}
97
98template <class T>
99inline void LazyDict<T>::DetachFromDocument() {
100    mDict = 0;
101}
102
103template <class T>
104Ref<T> LazyDict<T>::Get(unsigned int i) {
105    return Ref<T>(mObjs, i);
106}
107
108template <class T>
109Ref<T> LazyDict<T>::Get(const char *id) {
110    id = T::TranslateId(mAsset, id);
111
112    typename Dict::iterator it = mObjsById.find(id);
113    if (it != mObjsById.end()) { // already created?
114        return Ref<T>(mObjs, it->second);
115    }
116
117    // read it from the JSON object
118    if (!mDict) {
119        throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\"");
120    }
121
122    Value::MemberIterator obj = mDict->FindMember(id);
123    if (obj == mDict->MemberEnd()) {
124        throw DeadlyImportError("GLTF: Missing object with id \"", id, "\" in \"", mDictId, "\"");
125    }
126    if (!obj->value.IsObject()) {
127        throw DeadlyImportError("GLTF: Object with id \"", id, "\" is not a JSON object");
128    }
129
130    // create an instance of the given type
131    T *inst = new T();
132    inst->id = id;
133    ReadMember(obj->value, "name", inst->name);
134    inst->Read(obj->value, mAsset);
135    return Add(inst);
136}
137
138template <class T>
139Ref<T> LazyDict<T>::Add(T *obj) {
140    unsigned int idx = unsigned(mObjs.size());
141    mObjs.push_back(obj);
142    mObjsById[obj->id] = idx;
143    mAsset.mUsedIds[obj->id] = true;
144    return Ref<T>(mObjs, idx);
145}
146
147template <class T>
148Ref<T> LazyDict<T>::Create(const char *id) {
149    Asset::IdMap::iterator it = mAsset.mUsedIds.find(id);
150    if (it != mAsset.mUsedIds.end()) {
151        throw DeadlyImportError("GLTF: two objects with the same ID exist");
152    }
153    T *inst = new T();
154    inst->id = id;
155    return Add(inst);
156}
157
158//
159// glTF dictionary objects methods
160//
161
162inline Buffer::Buffer() :
163        byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) {}
164
165inline Buffer::~Buffer() {
166    for (SEncodedRegion *reg : EncodedRegion_List)
167        delete reg;
168}
169
170inline const char *Buffer::TranslateId(Asset &r, const char *id) {
171    // Compatibility with old spec
172    if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) {
173        return "binary_glTF";
174    }
175
176    return id;
177}
178
179inline void Buffer::Read(Value &obj, Asset &r) {
180    size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0);
181    byteLength = statedLength;
182
183    Value *it = FindString(obj, "uri");
184    if (!it) {
185        if (statedLength > 0) {
186            throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute");
187        }
188        return;
189    }
190
191    const char *uri = it->GetString();
192
193    glTFCommon::Util::DataURI dataURI;
194    if (ParseDataURI(uri, it->GetStringLength(), dataURI)) {
195        if (dataURI.base64) {
196            uint8_t *data = 0;
197            this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data);
198            this->mData.reset(data, std::default_delete<uint8_t[]>());
199
200            if (statedLength > 0 && this->byteLength != statedLength) {
201                throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength),
202                        " bytes, but found ", ai_to_string(dataURI.dataLength));
203            }
204        } else { // assume raw data
205            if (statedLength != dataURI.dataLength) {
206                throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength),
207                        " bytes, but found ", ai_to_string(dataURI.dataLength));
208            }
209
210            this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete<uint8_t[]>());
211            memcpy(this->mData.get(), dataURI.data, dataURI.dataLength);
212        }
213    } else { // Local file
214        if (byteLength > 0) {
215            std::string dir = !r.mCurrentAssetDir.empty() ? (
216                                                                    r.mCurrentAssetDir.back() == '/' ?
217                                                                            r.mCurrentAssetDir :
218                                                                            r.mCurrentAssetDir + '/') :
219                                                            "";
220
221            IOStream *file = r.OpenFile(dir + uri, "rb");
222            if (file) {
223                bool ok = LoadFromStream(*file, byteLength);
224                delete file;
225
226                if (!ok)
227                    throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\"");
228            } else {
229                throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\"");
230            }
231        }
232    }
233}
234
235inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) {
236    byteLength = length ? length : stream.FileSize();
237
238    if (baseOffset) {
239        stream.Seek(baseOffset, aiOrigin_SET);
240    }
241
242    mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>());
243
244    if (stream.Read(mData.get(), byteLength, 1) != 1) {
245        return false;
246    }
247    return true;
248}
249
250inline 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) {
251    // Check pointer to data
252    if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided.");
253
254    // Check offset
255    if (pOffset > byteLength) {
256        const uint8_t val_size = 32;
257
258        char val[val_size];
259
260        ai_snprintf(val, val_size, AI_SIZEFMT, pOffset);
261        throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region.");
262    }
263
264    // Check length
265    if ((pOffset + pEncodedData_Length) > byteLength) {
266        const uint8_t val_size = 64;
267
268        char val[val_size];
269
270        ai_snprintf(val, val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length);
271        throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range.");
272    }
273
274    // Add new region
275    EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID));
276    // And set new value for "byteLength"
277    byteLength += (pDecodedData_Length - pEncodedData_Length);
278}
279
280inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) {
281    if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return;
282
283    for (SEncodedRegion *reg : EncodedRegion_List) {
284        if (reg->ID == pID) {
285            EncodedRegion_Current = reg;
286
287            return;
288        }
289    }
290
291    throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found.");
292}
293
294inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) {
295    const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count;
296
297    uint8_t *new_data;
298
299    if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false;
300
301    new_data = new uint8_t[new_data_size];
302    // Copy data which place before replacing part.
303    memcpy(new_data, mData.get(), pBufferData_Offset);
304    // Copy new data.
305    memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count);
306    // Copy data which place after replacing part.
307    memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset);
308    // Apply new data
309    mData.reset(new_data, std::default_delete<uint8_t[]>());
310    byteLength = new_data_size;
311
312    return true;
313}
314
315inline size_t Buffer::AppendData(uint8_t *data, size_t length) {
316    size_t offset = this->byteLength;
317    Grow(length);
318    memcpy(mData.get() + offset, data, length);
319    return offset;
320}
321
322inline void Buffer::Grow(size_t amount) {
323    if (amount <= 0) return;
324    if (capacity >= byteLength + amount) {
325        byteLength += amount;
326        return;
327    }
328
329    // 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,
330    // originally it would look like: static_cast<size_t>(capacity * 1.5f)
331    capacity = std::max(capacity + (capacity >> 1), byteLength + amount);
332
333    uint8_t *b = new uint8_t[capacity];
334    if (mData) memcpy(b, mData.get(), byteLength);
335    mData.reset(b, std::default_delete<uint8_t[]>());
336    byteLength += amount;
337}
338
339//
340// struct BufferView
341//
342
343inline void BufferView::Read(Value &obj, Asset &r) {
344    const char *bufferId = MemberOrDefault<const char *>(obj, "buffer", 0);
345    if (bufferId) {
346        buffer = r.buffers.Get(bufferId);
347    }
348
349    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
350    byteLength = MemberOrDefault(obj, "byteLength", 0u);
351}
352
353//
354// struct Accessor
355//
356
357inline void Accessor::Read(Value &obj, Asset &r) {
358    const char *bufferViewId = MemberOrDefault<const char *>(obj, "bufferView", 0);
359    if (bufferViewId) {
360        bufferView = r.bufferViews.Get(bufferViewId);
361    }
362
363    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
364    byteStride = MemberOrDefault(obj, "byteStride", 0u);
365    componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE);
366    count = MemberOrDefault(obj, "count", 0u);
367
368    const char *typestr;
369    type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR;
370}
371
372inline unsigned int Accessor::GetNumComponents() {
373    return AttribType::GetNumComponents(type);
374}
375
376inline unsigned int Accessor::GetBytesPerComponent() {
377    return int(ComponentTypeSize(componentType));
378}
379
380inline unsigned int Accessor::GetElementSize() {
381    return GetNumComponents() * GetBytesPerComponent();
382}
383
384inline uint8_t *Accessor::GetPointer() {
385    if (!bufferView || !bufferView->buffer) return 0;
386    uint8_t *basePtr = bufferView->buffer->GetPointer();
387    if (!basePtr) return 0;
388
389    size_t offset = byteOffset + bufferView->byteOffset;
390
391    // Check if region is encoded.
392    if (bufferView->buffer->EncodedRegion_Current != nullptr) {
393        const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset;
394        const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length;
395
396        if ((offset >= begin) && (offset < end))
397            return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin];
398    }
399
400    return basePtr + offset;
401}
402
403namespace {
404inline void CopyData(size_t count,
405        const uint8_t *src, size_t src_stride,
406        uint8_t *dst, size_t dst_stride) {
407    if (src_stride == dst_stride) {
408        memcpy(dst, src, count * src_stride);
409    } else {
410        size_t sz = std::min(src_stride, dst_stride);
411        for (size_t i = 0; i < count; ++i) {
412            memcpy(dst, src, sz);
413            if (sz < dst_stride) {
414                memset(dst + sz, 0, dst_stride - sz);
415            }
416            src += src_stride;
417            dst += dst_stride;
418        }
419    }
420}
421} // namespace
422
423template <class T>
424bool Accessor::ExtractData(T *&outData) {
425    uint8_t *data = GetPointer();
426    if (!data) return false;
427
428    const size_t elemSize = GetElementSize();
429    const size_t totalSize = elemSize * count;
430
431    const size_t stride = byteStride ? byteStride : elemSize;
432
433    const size_t targetElemSize = sizeof(T);
434    ai_assert(elemSize <= targetElemSize);
435
436    ai_assert(count * stride <= bufferView->byteLength);
437
438    outData = new T[count];
439    if (stride == elemSize && targetElemSize == elemSize) {
440        memcpy(outData, data, totalSize);
441    } else {
442        for (size_t i = 0; i < count; ++i) {
443            memcpy(outData + i, data + i * stride, elemSize);
444        }
445    }
446
447    return true;
448}
449
450inline void Accessor::WriteData(size_t cnt, const void *src_buffer, size_t src_stride) {
451    uint8_t *buffer_ptr = bufferView->buffer->GetPointer();
452    size_t offset = byteOffset + bufferView->byteOffset;
453
454    size_t dst_stride = GetNumComponents() * GetBytesPerComponent();
455
456    const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer);
457    uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset);
458
459    ai_assert(dst + count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength);
460    CopyData(cnt, src, src_stride, dst, dst_stride);
461}
462
463inline Accessor::Indexer::Indexer(Accessor &acc) :
464        accessor(acc), data(acc.GetPointer()), elemSize(acc.GetElementSize()), stride(acc.byteStride ? acc.byteStride : elemSize) {
465}
466
467//! Accesses the i-th value as defined by the accessor
468template <class T>
469T Accessor::Indexer::GetValue(int i) {
470    ai_assert(data);
471    ai_assert(i * stride < accessor.bufferView->byteLength);
472    T value = T();
473    memcpy(&value, data + i * stride, elemSize);
474    //value >>= 8 * (sizeof(T) - elemSize);
475    return value;
476}
477
478inline Image::Image() :
479        width(0), height(0), mDataLength(0) {
480}
481
482inline void Image::Read(Value &obj, Asset &r) {
483    // Check for extensions first (to detect binary embedded data)
484    if (Value *extensions = FindObject(obj, "extensions")) {
485        if (r.extensionsUsed.KHR_binary_glTF) {
486            if (Value *ext = FindObject(*extensions, "KHR_binary_glTF")) {
487
488                width = MemberOrDefault(*ext, "width", 0);
489                height = MemberOrDefault(*ext, "height", 0);
490
491                ReadMember(*ext, "mimeType", mimeType);
492
493                const char *bufferViewId;
494                if (ReadMember(*ext, "bufferView", bufferViewId)) {
495                    Ref<BufferView> bv = r.bufferViews.Get(bufferViewId);
496                    if (bv) {
497                        mDataLength = bv->byteLength;
498                        mData.reset(new uint8_t[mDataLength]);
499                        memcpy(mData.get(), bv->buffer->GetPointer() + bv->byteOffset, mDataLength);
500                    }
501                }
502            }
503        }
504    }
505
506    if (!mDataLength) {
507        Value *curUri = FindString(obj, "uri");
508        if (nullptr != curUri) {
509            const char *uristr = curUri->GetString();
510
511            glTFCommon::Util::DataURI dataURI;
512            if (ParseDataURI(uristr, curUri->GetStringLength(), dataURI)) {
513                mimeType = dataURI.mediaType;
514                if (dataURI.base64) {
515                    uint8_t *ptr = nullptr;
516                    mDataLength = glTFCommon::Util::DecodeBase64(dataURI.data, dataURI.dataLength, ptr);
517                    mData.reset(ptr);
518                }
519            } else {
520                this->uri = uristr;
521            }
522        }
523    }
524}
525
526inline uint8_t *Image::StealData() {
527    mDataLength = 0;
528    return mData.release();
529}
530
531inline void Image::SetData(uint8_t *data, size_t length, Asset &r) {
532    Ref<Buffer> b = r.GetBodyBuffer();
533    if (b) { // binary file: append to body
534        std::string bvId = r.FindUniqueID(this->id, "imgdata");
535        bufferView = r.bufferViews.Create(bvId);
536
537        bufferView->buffer = b;
538        bufferView->byteLength = length;
539        bufferView->byteOffset = b->AppendData(data, length);
540    } else { // text file: will be stored as a data uri
541        uint8_t *temp = new uint8_t[length];
542        memcpy(temp, data, length);
543        this->mData.reset(temp);
544        this->mDataLength = length;
545    }
546}
547
548inline void Sampler::Read(Value &obj, Asset & /*r*/) {
549    SetDefaults();
550
551    ReadMember(obj, "magFilter", magFilter);
552    ReadMember(obj, "minFilter", minFilter);
553    ReadMember(obj, "wrapS", wrapS);
554    ReadMember(obj, "wrapT", wrapT);
555}
556
557inline void Sampler::SetDefaults() {
558    magFilter = SamplerMagFilter_Linear;
559    minFilter = SamplerMinFilter_Linear;
560    wrapS = SamplerWrap_Repeat;
561    wrapT = SamplerWrap_Repeat;
562}
563
564inline void Texture::Read(Value &obj, Asset &r) {
565    const char *sourcestr;
566    if (ReadMember(obj, "source", sourcestr)) {
567        source = r.images.Get(sourcestr);
568    }
569
570    const char *samplerstr;
571    if (ReadMember(obj, "sampler", samplerstr)) {
572        sampler = r.samplers.Get(samplerstr);
573    }
574}
575
576namespace {
577inline void ReadMaterialProperty(Asset &r, Value &vals, const char *propName, TexProperty &out) {
578    if (Value *prop = FindMember(vals, propName)) {
579        if (prop->IsString()) {
580            out.texture = r.textures.Get(prop->GetString());
581        } else {
582            ReadValue(*prop, out.color);
583        }
584    }
585}
586} // namespace
587
588inline void Material::Read(Value &material, Asset &r) {
589    SetDefaults();
590
591    if (Value *values = FindObject(material, "values")) {
592        ReadMaterialProperty(r, *values, "ambient", this->ambient);
593        ReadMaterialProperty(r, *values, "diffuse", this->diffuse);
594        ReadMaterialProperty(r, *values, "specular", this->specular);
595
596        ReadMember(*values, "transparency", transparency);
597        ReadMember(*values, "shininess", shininess);
598    }
599
600    if (Value *extensions = FindObject(material, "extensions")) {
601        if (r.extensionsUsed.KHR_materials_common) {
602            if (Value *ext = FindObject(*extensions, "KHR_materials_common")) {
603                if (Value *tnq = FindString(*ext, "technique")) {
604                    const char *t = tnq->GetString();
605                    if (strcmp(t, "BLINN") == 0)
606                        technique = Technique_BLINN;
607                    else if (strcmp(t, "PHONG") == 0)
608                        technique = Technique_PHONG;
609                    else if (strcmp(t, "LAMBERT") == 0)
610                        technique = Technique_LAMBERT;
611                    else if (strcmp(t, "CONSTANT") == 0)
612                        technique = Technique_CONSTANT;
613                }
614
615                if (Value *values = FindObject(*ext, "values")) {
616                    ReadMaterialProperty(r, *values, "ambient", this->ambient);
617                    ReadMaterialProperty(r, *values, "diffuse", this->diffuse);
618                    ReadMaterialProperty(r, *values, "specular", this->specular);
619
620                    ReadMember(*values, "doubleSided", doubleSided);
621                    ReadMember(*values, "transparent", transparent);
622                    ReadMember(*values, "transparency", transparency);
623                    ReadMember(*values, "shininess", shininess);
624                }
625            }
626        }
627    }
628}
629
630namespace {
631void SetVector(vec4 &v, float x, float y, float z, float w) {
632    v[0] = x;
633    v[1] = y;
634    v[2] = z;
635    v[3] = w;
636}
637} // namespace
638
639inline void Material::SetDefaults() {
640    SetVector(ambient.color, 0, 0, 0, 1);
641    SetVector(diffuse.color, 0, 0, 0, 1);
642    SetVector(specular.color, 0, 0, 0, 1);
643    SetVector(emission.color, 0, 0, 0, 1);
644
645    doubleSided = false;
646    transparent = false;
647    transparency = 1.0;
648    shininess = 0.0;
649
650    technique = Technique_undefined;
651}
652
653namespace {
654
655template <int N>
656inline int Compare(const char *attr, const char (&str)[N]) {
657    return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0;
658}
659
660inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) {
661    if ((pos = Compare(attr, "POSITION"))) {
662        v = &(p.attributes.position);
663    } else if ((pos = Compare(attr, "NORMAL"))) {
664        v = &(p.attributes.normal);
665    } else if ((pos = Compare(attr, "TEXCOORD"))) {
666        v = &(p.attributes.texcoord);
667    } else if ((pos = Compare(attr, "COLOR"))) {
668        v = &(p.attributes.color);
669    } else if ((pos = Compare(attr, "JOINT"))) {
670        v = &(p.attributes.joint);
671    } else if ((pos = Compare(attr, "JOINTMATRIX"))) {
672        v = &(p.attributes.jointmatrix);
673    } else if ((pos = Compare(attr, "WEIGHT"))) {
674        v = &(p.attributes.weight);
675    } else
676        return false;
677    return true;
678}
679} // namespace
680
681inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
682    /****************** Mesh primitives ******************/
683    Value *curPrimitives = FindArray(pJSON_Object, "primitives");
684    if (nullptr != curPrimitives) {
685        this->primitives.resize(curPrimitives->Size());
686        for (unsigned int i = 0; i < curPrimitives->Size(); ++i) {
687            Value &primitive = (*curPrimitives)[i];
688
689            Primitive &prim = this->primitives[i];
690            prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES);
691
692            if (Value *attrs = FindObject(primitive, "attributes")) {
693                for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) {
694                    if (!it->value.IsString()) continue;
695                    const char *attr = it->name.GetString();
696                    // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX,
697                    // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc.
698
699                    int undPos = 0;
700                    Mesh::AccessorList *vec = 0;
701                    if (GetAttribVector(prim, attr, vec, undPos)) {
702                        size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0;
703                        if ((*vec).size() <= idx) (*vec).resize(idx + 1);
704                        (*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString());
705                    }
706                }
707            }
708
709            if (Value *indices = FindString(primitive, "indices")) {
710                prim.indices = pAsset_Root.accessors.Get(indices->GetString());
711            }
712
713            if (Value *material = FindString(primitive, "material")) {
714                prim.material = pAsset_Root.materials.Get(material->GetString());
715            }
716        }
717    }
718
719    /****************** Mesh extensions ******************/
720    Value *json_extensions = FindObject(pJSON_Object, "extensions");
721
722    if (json_extensions == nullptr) goto mr_skip_extensions;
723
724#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
725    for (Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) {
726        if (it_memb->name.GetString() == std::string("Open3DGC-compression")) {
727            // Search for compressed data.
728            // Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and
729            // new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure.
730            Value *comp_data = FindObject(it_memb->value, "compressedData");
731
732            if (comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\".");
733
734            ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data.");
735
736/************** Read data from JSON-document **************/
737#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut)                                   \
738    if (!ReadMember(*comp_data, pFieldName, pOut)) {                                        \
739        throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \
740    }
741
742            const char *mode_str;
743            const char *type_str;
744            ComponentType component_type;
745            SCompression_Open3DGC *ext_o3dgc = new SCompression_Open3DGC;
746
747            MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer);
748            MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset);
749            MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type);
750            MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str);
751            MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count);
752            MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str);
753            MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount);
754            MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount);
755
756#undef MESH_READ_COMPRESSEDDATA_MEMBER
757
758            // Check some values
759            if (strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data.");
760            if (component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data.");
761
762            // Set read/write data mode.
763            if (strcmp(mode_str, "binary") == 0)
764                ext_o3dgc->Binary = true;
765            else if (strcmp(mode_str, "ascii") == 0)
766                ext_o3dgc->Binary = false;
767            else
768                throw DeadlyImportError("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"", mode_str, "\".");
769
770            /************************ Decoding ************************/
771            Decode_O3DGC(*ext_o3dgc, pAsset_Root);
772            Extension.push_back(ext_o3dgc); // store info in mesh extensions list.
773        } // if(it_memb->name.GetString() == "Open3DGC-compression")
774        else {
775            throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\".");
776        }
777    } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++)
778#endif
779
780mr_skip_extensions:
781
782    return; // After label some operators must be present.
783}
784
785#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
786inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root) {
787    typedef unsigned short IndicesType; ///< \sa glTFExporter::ExportMeshes.
788
789    o3dgc::SC3DMCDecoder<IndicesType> decoder;
790    o3dgc::IndexedFaceSet<IndicesType> ifs;
791    o3dgc::BinaryStream bstream;
792    uint8_t *decoded_data;
793    size_t decoded_data_size = 0;
794    Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer);
795
796    // Read data from buffer and place it in BinaryStream for decoder.
797    // Just "Count" because always is used type equivalent to uint8_t.
798    bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast<unsigned long>(pCompression_Open3DGC.Count));
799
800    // After decoding header we can get size of primitives.
801    if (decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header.");
802
803    /****************** Get sizes of arrays and check sizes ******************/
804    // Note. See "Limitations for meshes when using Open3DGC-compression".
805
806    // Indices
807    size_t size_coordindex = ifs.GetNCoordIndex() * 3; // See float attributes note.
808
809    if (primitives[0].indices->count != size_coordindex)
810        throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (", ai_to_string(size_coordindex),
811                ") not equal to uncompressed (", ai_to_string(primitives[0].indices->count), ").");
812
813    size_coordindex *= sizeof(IndicesType);
814    // Coordinates
815    size_t size_coord = ifs.GetNCoord(); // See float attributes note.
816
817    if (primitives[0].attributes.position[0]->count != size_coord)
818        throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (", ai_to_string(size_coord),
819                ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.position[0]->count), ").");
820
821    size_coord *= 3 * sizeof(float);
822    // Normals
823    size_t size_normal = ifs.GetNNormal(); // See float attributes note.
824
825    if (primitives[0].attributes.normal[0]->count != size_normal)
826        throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (", ai_to_string(size_normal),
827                ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.normal[0]->count), ").");
828
829    size_normal *= 3 * sizeof(float);
830    // Additional attributes.
831    std::vector<size_t> size_floatattr;
832    std::vector<size_t> size_intattr;
833
834    size_floatattr.resize(ifs.GetNumFloatAttributes());
835    size_intattr.resize(ifs.GetNumIntAttributes());
836
837    decoded_data_size = size_coordindex + size_coord + size_normal;
838    for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) {
839        // size = number_of_elements * components_per_element * size_of_component.
840        // Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects...
841        size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx));
842
843        switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) {
844        case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
845            // Check situation when encoded data contain texture coordinates but primitive not.
846            if (idx_texcoord < primitives[0].attributes.texcoord.size()) {
847                if (primitives[0].attributes.texcoord[idx]->count != tval)
848                    throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval),
849                            ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ").");
850
851                idx_texcoord++;
852            } else {
853                ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul); // Disable decoding this attribute.
854            }
855
856            break;
857        default:
858            throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
859        }
860
861        tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array.
862        size_floatattr[idx] = tval;
863        decoded_data_size += tval;
864    }
865
866    for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) {
867        // size = number_of_elements * components_per_element * size_of_component. See float attributes note.
868        size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx));
869        switch (ifs.GetIntAttributeType(static_cast<unsigned long>(idx))) {
870        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
871        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
872        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
873        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
874            break;
875
876        default:
877            throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
878        }
879
880        tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long); // See float attributes note.
881        size_intattr[idx] = tval;
882        decoded_data_size += tval;
883    }
884
885    // Create array for decoded data.
886    decoded_data = new uint8_t[decoded_data_size];
887
888    /****************** Set right array regions for decoder ******************/
889
890    auto get_buf_offset = [](Ref<Accessor> &pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; };
891
892    // Indices
893    ifs.SetCoordIndex((IndicesType *const)(decoded_data + get_buf_offset(primitives[0].indices)));
894    // Coordinates
895    ifs.SetCoord((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0])));
896    // Normals
897    if (size_normal) {
898        ifs.SetNormal((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0])));
899    }
900
901    for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) {
902        switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) {
903        case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
904            if (idx_texcoord < primitives[0].attributes.texcoord.size()) {
905                // See above about absent attributes.
906                ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx])));
907                idx_texcoord++;
908            }
909
910            break;
911        default:
912            throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
913        }
914    }
915
916    for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) {
917        switch (ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) {
918        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
919        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
920        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
921        case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
922            break;
923
924        // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint)));
925        default:
926            throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
927        }
928    }
929
930    //
931    // Decode data
932    //
933    if (decoder.DecodePayload(ifs, bstream) != o3dgc::O3DGC_OK) {
934        throw DeadlyImportError("GLTF: can not decode Open3DGC data.");
935    }
936
937    // Set encoded region for "buffer".
938    buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id);
939    // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data".
940    // "delete [] output_data;"
941}
942#endif
943
944inline void Camera::Read(Value &obj, Asset & /*r*/) {
945    type = MemberOrDefault(obj, "type", Camera::Perspective);
946
947    const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective";
948
949    Value *it = FindObject(obj, subobjId);
950    if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters");
951
952    if (type == Camera::Perspective) {
953        perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f);
954        perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f / 2.f);
955        perspective.zfar = MemberOrDefault(*it, "zfar", 100.f);
956        perspective.znear = MemberOrDefault(*it, "znear", 0.01f);
957    } else {
958        ortographic.xmag = MemberOrDefault(*it, "xmag", 1.f);
959        ortographic.ymag = MemberOrDefault(*it, "ymag", 1.f);
960        ortographic.zfar = MemberOrDefault(*it, "zfar", 100.f);
961        ortographic.znear = MemberOrDefault(*it, "znear", 0.01f);
962    }
963}
964
965inline void Light::Read(Value &obj, Asset & /*r*/) {
966    SetDefaults();
967
968    Value *curType = FindString(obj, "type");
969    if (nullptr != curType) {
970        const char *t = curType->GetString();
971        if (strcmp(t, "ambient") == 0)
972            this->type = Type_ambient;
973        else if (strcmp(t, "directional") == 0)
974            this->type = Type_directional;
975        else if (strcmp(t, "point") == 0)
976            this->type = Type_point;
977        else if (strcmp(t, "spot") == 0)
978            this->type = Type_spot;
979
980        if (this->type != Type_undefined) {
981            if (Value *vals = FindString(obj, t)) {
982                ReadMember(*vals, "color", color);
983
984                ReadMember(*vals, "constantAttenuation", constantAttenuation);
985                ReadMember(*vals, "linearAttenuation", linearAttenuation);
986                ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation);
987                ReadMember(*vals, "distance", distance);
988
989                ReadMember(*vals, "falloffAngle", falloffAngle);
990                ReadMember(*vals, "falloffExponent", falloffExponent);
991            }
992        }
993    }
994}
995
996inline void Light::SetDefaults() {
997#ifndef M_PI
998    const float M_PI = 3.14159265358979323846f;
999#endif
1000
1001    type = Type_undefined;
1002
1003    SetVector(color, 0.f, 0.f, 0.f, 1.f);
1004
1005    constantAttenuation = 0.f;
1006    linearAttenuation = 1.f;
1007    quadraticAttenuation = 1.f;
1008    distance = 0.f;
1009
1010    falloffAngle = static_cast<float>(M_PI / 2.f);
1011    falloffExponent = 0.f;
1012}
1013
1014inline void Node::Read(Value &obj, Asset &r) {
1015    if (name.empty()) {
1016        name = id;
1017    }
1018
1019    Value *curChildren = FindArray(obj, "children");
1020    if (nullptr != curChildren) {
1021        this->children.reserve(curChildren->Size());
1022        for (unsigned int i = 0; i < curChildren->Size(); ++i) {
1023            Value &child = (*curChildren)[i];
1024            if (child.IsString()) {
1025                // get/create the child node
1026                Ref<Node> chn = r.nodes.Get(child.GetString());
1027                if (chn) this->children.push_back(chn);
1028            }
1029        }
1030    }
1031
1032    Value *curMatrix = FindArray(obj, "matrix");
1033    if (nullptr != curMatrix) {
1034        ReadValue(*curMatrix, this->matrix);
1035    } else {
1036        ReadMember(obj, "translation", translation);
1037        ReadMember(obj, "scale", scale);
1038        ReadMember(obj, "rotation", rotation);
1039    }
1040
1041    Value *curMeshes = FindArray(obj, "meshes");
1042    if (nullptr != curMeshes) {
1043        unsigned int numMeshes = (unsigned int)curMeshes->Size();
1044
1045        std::vector<unsigned int> meshList;
1046
1047        this->meshes.reserve(numMeshes);
1048        for (unsigned i = 0; i < numMeshes; ++i) {
1049            if ((*curMeshes)[i].IsString()) {
1050                Ref<Mesh> mesh = r.meshes.Get((*curMeshes)[i].GetString());
1051                if (mesh) {
1052                    this->meshes.push_back(mesh);
1053                }
1054            }
1055        }
1056    }
1057
1058    Value *curCamera = FindString(obj, "camera");
1059    if (nullptr != curCamera) {
1060        this->camera = r.cameras.Get(curCamera->GetString());
1061        if (this->camera) {
1062            this->camera->id = this->id;
1063        }
1064    }
1065
1066    // TODO load "skeletons", "skin", "jointName"
1067
1068    if (Value *extensions = FindObject(obj, "extensions")) {
1069        if (r.extensionsUsed.KHR_materials_common) {
1070
1071            if (Value *ext = FindObject(*extensions, "KHR_materials_common")) {
1072                Value *curLight = FindString(*ext, "light");
1073                if (nullptr != curLight) {
1074                    this->light = r.lights.Get(curLight->GetString());
1075                }
1076            }
1077        }
1078    }
1079}
1080
1081inline void Scene::Read(Value &obj, Asset &r) {
1082    if (Value *array = FindArray(obj, "nodes")) {
1083        for (unsigned int i = 0; i < array->Size(); ++i) {
1084            if (!(*array)[i].IsString()) continue;
1085            Ref<Node> node = r.nodes.Get((*array)[i].GetString());
1086            if (node)
1087                this->nodes.push_back(node);
1088        }
1089    }
1090}
1091
1092inline void AssetMetadata::Read(Document &doc) {
1093    // read the version, etc.
1094    if (Value *obj = FindObject(doc, "asset")) {
1095        ReadMember(*obj, "copyright", copyright);
1096        ReadMember(*obj, "generator", generator);
1097
1098        premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false);
1099
1100        if (Value *versionString = FindString(*obj, "version")) {
1101            version = versionString->GetString();
1102        } else if (Value *versionNumber = FindNumber(*obj, "version")) {
1103            char buf[4];
1104
1105            ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble());
1106
1107            version = buf;
1108        }
1109
1110        Value *curProfile = FindObject(*obj, "profile");
1111        if (nullptr != curProfile) {
1112            ReadMember(*curProfile, "api", this->profile.api);
1113            ReadMember(*curProfile, "version", this->profile.version);
1114        }
1115    }
1116
1117    if (version.empty() || version[0] != '1') {
1118        throw DeadlyImportError("GLTF: Unsupported glTF version: ", version);
1119    }
1120}
1121
1122//
1123// Asset methods implementation
1124//
1125
1126inline void Asset::ReadBinaryHeader(IOStream &stream) {
1127    GLB_Header header;
1128    if (stream.Read(&header, sizeof(header), 1) != 1) {
1129        throw DeadlyImportError("GLTF: Unable to read the file header");
1130    }
1131
1132    if (strncmp((char *)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) {
1133        throw DeadlyImportError("GLTF: Invalid binary glTF file");
1134    }
1135
1136    AI_SWAP4(header.version);
1137    asset.version = ai_to_string(header.version);
1138    if (header.version != 1) {
1139        throw DeadlyImportError("GLTF: Unsupported binary glTF version");
1140    }
1141
1142    AI_SWAP4(header.sceneFormat);
1143    if (header.sceneFormat != SceneFormat_JSON) {
1144        throw DeadlyImportError("GLTF: Unsupported binary glTF scene format");
1145    }
1146
1147    AI_SWAP4(header.length);
1148    AI_SWAP4(header.sceneLength);
1149
1150    static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits");
1151    mSceneLength = static_cast<size_t>(header.sceneLength); // Can't be larger than 4GB (max. uint32_t)
1152
1153    mBodyOffset = sizeof(header) + mSceneLength;
1154    mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4
1155
1156    mBodyLength = header.length - mBodyOffset;
1157}
1158
1159inline void Asset::Load(const std::string &pFile, bool isBinary) {
1160    mCurrentAssetDir.clear();
1161
1162    /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\')));
1163    if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);*/
1164    if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) {
1165        mCurrentAssetDir = getCurrentAssetDir(pFile);
1166    }
1167
1168    shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true));
1169    if (!stream) {
1170        throw DeadlyImportError("GLTF: Could not open file for reading");
1171    }
1172
1173    // is binary? then read the header
1174    if (isBinary) {
1175        SetAsBinary(); // also creates the body buffer
1176        ReadBinaryHeader(*stream);
1177    } else {
1178        mSceneLength = stream->FileSize();
1179        mBodyLength = 0;
1180    }
1181
1182    // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later
1183    if (mSceneLength < 2) {
1184        throw DeadlyImportError("GLTF: No JSON file contents");
1185    }
1186
1187    // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation
1188    if (mSceneLength >= std::numeric_limits<uint32_t>::max()) {
1189        throw DeadlyImportError("GLTF: JSON size greater than 4GB");
1190    }
1191
1192    // read the scene data, ensure null termination
1193    std::vector<char> sceneData(mSceneLength + 1);
1194    sceneData[mSceneLength] = '\0';
1195
1196    if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) {
1197        throw DeadlyImportError("GLTF: Could not read the file contents");
1198    }
1199
1200    // parse the JSON document
1201
1202    Document doc;
1203    doc.ParseInsitu(&sceneData[0]);
1204
1205    if (doc.HasParseError()) {
1206        char buffer[32];
1207        ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset()));
1208        throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(doc.GetParseError()));
1209    }
1210
1211    if (!doc.IsObject()) {
1212        throw DeadlyImportError("GLTF: JSON document root must be a JSON object");
1213    }
1214
1215    // Fill the buffer instance for the current file embedded contents
1216    if (mBodyLength > 0) {
1217        if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) {
1218            throw DeadlyImportError("GLTF: Unable to read gltf file");
1219        }
1220    }
1221
1222    // Load the metadata
1223    asset.Read(doc);
1224    ReadExtensionsUsed(doc);
1225
1226    // Prepare the dictionaries
1227    for (size_t i = 0; i < mDicts.size(); ++i) {
1228        mDicts[i]->AttachToDocument(doc);
1229    }
1230
1231    // Read the "scene" property, which specifies which scene to load
1232    // and recursively load everything referenced by it
1233    Value *curScene = FindString(doc, "scene");
1234    if (nullptr != curScene) {
1235        this->scene = scenes.Get(curScene->GetString());
1236    }
1237
1238    // Clean up
1239    for (size_t i = 0; i < mDicts.size(); ++i) {
1240        mDicts[i]->DetachFromDocument();
1241    }
1242}
1243
1244inline void Asset::SetAsBinary() {
1245    if (!extensionsUsed.KHR_binary_glTF) {
1246        extensionsUsed.KHR_binary_glTF = true;
1247        mBodyBuffer = buffers.Create("binary_glTF");
1248        mBodyBuffer->MarkAsSpecial();
1249    }
1250}
1251
1252inline void Asset::ReadExtensionsUsed(Document &doc) {
1253    Value *extsUsed = FindArray(doc, "extensionsUsed");
1254    if (!extsUsed) return;
1255
1256    std::gltf_unordered_map<std::string, bool> exts;
1257
1258    for (unsigned int i = 0; i < extsUsed->Size(); ++i) {
1259        if ((*extsUsed)[i].IsString()) {
1260            exts[(*extsUsed)[i].GetString()] = true;
1261        }
1262    }
1263
1264    CHECK_EXT(KHR_binary_glTF);
1265    CHECK_EXT(KHR_materials_common);
1266
1267#undef CHECK_EXT
1268}
1269
1270inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) {
1271#ifdef ASSIMP_API
1272    (void)absolute;
1273    return mIOSystem->Open(path, mode);
1274#else
1275    if (path.size() < 2) return 0;
1276    if (!absolute && path[1] != ':' && path[0] != '/') { // relative?
1277        path = mCurrentAssetDir + path;
1278    }
1279    FILE *f = fopen(path.c_str(), mode);
1280    return f ? new IOStream(f) : 0;
1281#endif
1282}
1283
1284inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) {
1285    std::string id = str;
1286
1287    if (!id.empty()) {
1288        if (mUsedIds.find(id) == mUsedIds.end())
1289            return id;
1290
1291        id += "_";
1292    }
1293
1294    id += suffix;
1295
1296    Asset::IdMap::iterator it = mUsedIds.find(id);
1297    if (it == mUsedIds.end())
1298        return id;
1299
1300    char buffer[1024];
1301    int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str());
1302    for (int i = 0; it != mUsedIds.end(); ++i) {
1303        ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i);
1304        id = buffer;
1305        it = mUsedIds.find(id);
1306    }
1307
1308    return id;
1309}
1310
1311#if _MSC_VER
1312#pragma warning(pop)
1313#endif // _MSC_VER
1314
1315} // namespace glTF
1316