1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, 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 "StringUtils.h"
43#include <iomanip>
44
45// Header files, Assimp
46#include <assimp/DefaultLogger.hpp>
47
48#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
49	// Header files, Open3DGC.
50#	include <Open3DGC/o3dgcSC3DMCDecoder.h>
51#endif
52
53using namespace Assimp;
54
55namespace glTF {
56
57namespace {
58
59    //
60    // JSON Value reading helpers
61    //
62
63    template<class T>
64    struct ReadHelper { static bool Read(Value& val, T& out) {
65        return val.IsInt() ? out = static_cast<T>(val.GetInt()), true : false;
66    }};
67
68    template<> struct ReadHelper<bool> { static bool Read(Value& val, bool& out) {
69        return val.IsBool() ? out = val.GetBool(), true : false;
70    }};
71
72    template<> struct ReadHelper<float> { static bool Read(Value& val, float& out) {
73        return val.IsNumber() ? out = static_cast<float>(val.GetDouble()), true : false;
74    }};
75
76    template<unsigned int N> struct ReadHelper<float[N]> { static bool Read(Value& val, float (&out)[N]) {
77        if (!val.IsArray() || val.Size() != N) return false;
78        for (unsigned int i = 0; i < N; ++i) {
79            if (val[i].IsNumber())
80                out[i] = static_cast<float>(val[i].GetDouble());
81        }
82        return true;
83    }};
84
85    template<> struct ReadHelper<const char*> { static bool Read(Value& val, const char*& out) {
86        return val.IsString() ? (out = val.GetString(), true) : false;
87    }};
88
89    template<> struct ReadHelper<std::string> { static bool Read(Value& val, std::string& out) {
90        return val.IsString() ? (out = std::string(val.GetString(), val.GetStringLength()), true) : false;
91    }};
92
93    template<class T> struct ReadHelper< Nullable<T> > { static bool Read(Value& val, Nullable<T>& out) {
94        return out.isPresent = ReadHelper<T>::Read(val, out.value);
95    }};
96
97    template<class T>
98    inline static bool ReadValue(Value& val, T& out)
99    {
100        return ReadHelper<T>::Read(val, out);
101    }
102
103    template<class T>
104    inline static bool ReadMember(Value& obj, const char* id, T& out)
105    {
106        Value::MemberIterator it = obj.FindMember(id);
107        if (it != obj.MemberEnd()) {
108            return ReadHelper<T>::Read(it->value, out);
109        }
110        return false;
111    }
112
113    template<class T>
114    inline static T MemberOrDefault(Value& obj, const char* id, T defaultValue)
115    {
116        T out;
117        return ReadMember(obj, id, out) ? out : defaultValue;
118    }
119
120    inline Value* FindMember(Value& val, const char* id)
121    {
122        Value::MemberIterator it = val.FindMember(id);
123        return (it != val.MemberEnd()) ? &it->value : 0;
124    }
125
126    inline Value* FindString(Value& val, const char* id)
127    {
128        Value::MemberIterator it = val.FindMember(id);
129        return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0;
130    }
131
132    inline Value* FindNumber(Value& val, const char* id)
133    {
134        Value::MemberIterator it = val.FindMember(id);
135        return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0;
136    }
137
138    inline Value* FindArray(Value& val, const char* id)
139    {
140        Value::MemberIterator it = val.FindMember(id);
141        return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : 0;
142    }
143
144    inline Value* FindObject(Value& val, const char* id)
145    {
146        Value::MemberIterator it = val.FindMember(id);
147        return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : 0;
148    }
149}
150
151//
152// LazyDict methods
153//
154
155template<class T>
156inline LazyDict<T>::LazyDict(Asset& asset, const char* dictId, const char* extId)
157    : mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset)
158{
159    asset.mDicts.push_back(this); // register to the list of dictionaries
160}
161
162template<class T>
163inline LazyDict<T>::~LazyDict()
164{
165    for (size_t i = 0; i < mObjs.size(); ++i) {
166        delete mObjs[i];
167    }
168}
169
170
171template<class T>
172inline void LazyDict<T>::AttachToDocument(Document& doc)
173{
174    Value* container = 0;
175
176    if (mExtId) {
177        if (Value* exts = FindObject(doc, "extensions")) {
178            container = FindObject(*exts, mExtId);
179        }
180    }
181    else {
182        container = &doc;
183    }
184
185    if (container) {
186        mDict = FindObject(*container, mDictId);
187    }
188}
189
190template<class T>
191inline void LazyDict<T>::DetachFromDocument()
192{
193    mDict = 0;
194}
195
196template<class T>
197Ref<T> LazyDict<T>::Get(unsigned int i)
198{
199    return Ref<T>(mObjs, i);
200}
201
202template<class T>
203Ref<T> LazyDict<T>::Get(const char* id)
204{
205    id = T::TranslateId(mAsset, id);
206
207    typename Dict::iterator it = mObjsById.find(id);
208    if (it != mObjsById.end()) { // already created?
209        return Ref<T>(mObjs, it->second);
210    }
211
212    // read it from the JSON object
213    if (!mDict) {
214        throw DeadlyImportError("GLTF: Missing section \"" + std::string(mDictId) + "\"");
215    }
216
217    Value::MemberIterator obj = mDict->FindMember(id);
218    if (obj == mDict->MemberEnd()) {
219        throw DeadlyImportError("GLTF: Missing object with id \"" + std::string(id) + "\" in \"" + mDictId + "\"");
220    }
221    if (!obj->value.IsObject()) {
222        throw DeadlyImportError("GLTF: Object with id \"" + std::string(id) + "\" is not a JSON object");
223    }
224
225    // create an instance of the given type
226    T* inst = new T();
227    inst->id = id;
228    ReadMember(obj->value, "name", inst->name);
229    inst->Read(obj->value, mAsset);
230    return Add(inst);
231}
232
233template<class T>
234Ref<T> LazyDict<T>::Add(T* obj)
235{
236    unsigned int idx = unsigned(mObjs.size());
237    mObjs.push_back(obj);
238    mObjsById[obj->id] = idx;
239    mAsset.mUsedIds[obj->id] = true;
240    return Ref<T>(mObjs, idx);
241}
242
243template<class T>
244Ref<T> LazyDict<T>::Create(const char* id)
245{
246    Asset::IdMap::iterator it = mAsset.mUsedIds.find(id);
247    if (it != mAsset.mUsedIds.end()) {
248        throw DeadlyImportError("GLTF: two objects with the same ID exist");
249    }
250    T* inst = new T();
251    inst->id = id;
252    return Add(inst);
253}
254
255
256//
257// glTF dictionary objects methods
258//
259
260
261inline Buffer::Buffer()
262	: byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false)
263{ }
264
265inline Buffer::~Buffer()
266{
267	for(SEncodedRegion* reg : EncodedRegion_List) delete reg;
268}
269
270inline const char* Buffer::TranslateId(Asset& r, const char* id)
271{
272    // Compatibility with old spec
273    if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) {
274        return "binary_glTF";
275    }
276
277    return id;
278}
279
280inline void Buffer::Read(Value& obj, Asset& r)
281{
282    size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0);
283    byteLength = statedLength;
284
285    Value* it = FindString(obj, "uri");
286    if (!it) {
287        if (statedLength > 0) {
288            throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute");
289        }
290        return;
291    }
292
293    const char* uri = it->GetString();
294
295    Util::DataURI dataURI;
296    if (ParseDataURI(uri, it->GetStringLength(), dataURI)) {
297        if (dataURI.base64) {
298            uint8_t* data = 0;
299            this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data);
300            this->mData.reset(data);
301
302            if (statedLength > 0 && this->byteLength != statedLength) {
303                throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) +
304                    " bytes, but found " + to_string(dataURI.dataLength));
305            }
306        }
307        else { // assume raw data
308            if (statedLength != dataURI.dataLength) {
309                throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) +
310                                        " bytes, but found " + to_string(dataURI.dataLength));
311            }
312
313            this->mData.reset(new uint8_t[dataURI.dataLength]);
314            memcpy( this->mData.get(), dataURI.data, dataURI.dataLength );
315        }
316    }
317    else { // Local file
318        if (byteLength > 0) {
319            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir + "/") : "";
320
321            IOStream* file = r.OpenFile(dir + uri, "rb");
322            if (file) {
323                bool ok = LoadFromStream(*file, byteLength);
324                delete file;
325
326                if (!ok)
327                    throw DeadlyImportError("GLTF: error while reading referenced file \"" + std::string(uri) + "\"" );
328            }
329            else {
330                throw DeadlyImportError("GLTF: could not open referenced file \"" + std::string(uri) + "\"");
331            }
332        }
333    }
334}
335
336inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
337{
338    byteLength = length ? length : stream.FileSize();
339
340    if (baseOffset) {
341        stream.Seek(baseOffset, aiOrigin_SET);
342    }
343
344    mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>());
345
346    if (stream.Read(mData.get(), byteLength, 1) != 1) {
347        return false;
348    }
349    return true;
350}
351
352inline 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)
353{
354	// Check pointer to data
355	if(pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided.");
356
357	// Check offset
358	if(pOffset > byteLength)
359	{
360		const uint8_t val_size = 32;
361
362		char val[val_size];
363
364		ai_snprintf(val, val_size, "%llu", (long long)pOffset);
365		throw DeadlyImportError(std::string("GLTF: incorrect offset value (") + val + ") for marking encoded region.");
366	}
367
368	// Check length
369	if((pOffset + pEncodedData_Length) > byteLength)
370	{
371		const uint8_t val_size = 64;
372
373		char val[val_size];
374
375		ai_snprintf(val, val_size, "%llu, %llu", (long long)pOffset, (long long)pEncodedData_Length);
376		throw DeadlyImportError(std::string("GLTF: encoded region with offset/length (") + val + ") is out of range.");
377	}
378
379	// Add new region
380	EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID));
381	// And set new value for "byteLength"
382	byteLength += (pDecodedData_Length - pEncodedData_Length);
383}
384
385inline void Buffer::EncodedRegion_SetCurrent(const std::string& pID)
386{
387	if((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return;
388
389	for(SEncodedRegion* reg : EncodedRegion_List)
390	{
391		if(reg->ID == pID)
392		{
393			EncodedRegion_Current = reg;
394
395			return;
396		}
397
398	}
399
400	throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found.");
401}
402
403inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count)
404{
405const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count;
406
407uint8_t* new_data;
408
409	if((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false;
410
411	new_data = new uint8_t[new_data_size];
412	// Copy data which place before replacing part.
413	memcpy(new_data, mData.get(), pBufferData_Offset);
414	// Copy new data.
415	memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count);
416	// Copy data which place after replacing part.
417	memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset);
418	// Apply new data
419	mData.reset(new_data);
420	byteLength = new_data_size;
421
422	return true;
423}
424
425inline size_t Buffer::AppendData(uint8_t* data, size_t length)
426{
427    size_t offset = this->byteLength;
428    Grow(length);
429    memcpy(mData.get() + offset, data, length);
430    return offset;
431}
432
433inline void Buffer::Grow(size_t amount)
434{
435    if (amount <= 0) return;
436    uint8_t* b = new uint8_t[byteLength + amount];
437    if (mData) memcpy(b, mData.get(), byteLength);
438    mData.reset(b);
439    byteLength += amount;
440}
441
442//
443// struct BufferView
444//
445
446inline void BufferView::Read(Value& obj, Asset& r)
447{
448    const char* bufferId = MemberOrDefault<const char*>(obj, "buffer", 0);
449    if (bufferId) {
450        buffer = r.buffers.Get(bufferId);
451    }
452
453    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
454    byteLength = MemberOrDefault(obj, "byteLength", 0u);
455}
456
457//
458// struct Accessor
459//
460
461inline void Accessor::Read(Value& obj, Asset& r)
462{
463    const char* bufferViewId = MemberOrDefault<const char*>(obj, "bufferView", 0);
464    if (bufferViewId) {
465        bufferView = r.bufferViews.Get(bufferViewId);
466    }
467
468    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
469    byteStride = MemberOrDefault(obj, "byteStride", 0u);
470    componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE);
471    count = MemberOrDefault(obj, "count", 0u);
472
473    const char* typestr;
474    type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR;
475}
476
477inline unsigned int Accessor::GetNumComponents()
478{
479    return AttribType::GetNumComponents(type);
480}
481
482inline unsigned int Accessor::GetBytesPerComponent()
483{
484    return int(ComponentTypeSize(componentType));
485}
486
487inline unsigned int Accessor::GetElementSize()
488{
489    return GetNumComponents() * GetBytesPerComponent();
490}
491
492inline uint8_t* Accessor::GetPointer()
493{
494    if (!bufferView || !bufferView->buffer) return 0;
495    uint8_t* basePtr = bufferView->buffer->GetPointer();
496    if (!basePtr) return 0;
497
498    size_t offset = byteOffset + bufferView->byteOffset;
499
500	// Check if region is encoded.
501	if(bufferView->buffer->EncodedRegion_Current != nullptr)
502	{
503		const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset;
504		const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length;
505
506		if((offset >= begin) && (offset < end))
507			return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin];
508	}
509
510	return basePtr + offset;
511}
512
513namespace {
514    inline void CopyData(size_t count,
515            const uint8_t* src, size_t src_stride,
516                  uint8_t* dst, size_t dst_stride)
517    {
518        if (src_stride == dst_stride) {
519            memcpy(dst, src, count * src_stride);
520        }
521        else {
522            size_t sz = std::min(src_stride, dst_stride);
523            for (size_t i = 0; i < count; ++i) {
524                memcpy(dst, src, sz);
525                if (sz < dst_stride) {
526                    memset(dst + sz, 0, dst_stride - sz);
527                }
528                src += src_stride;
529                dst += dst_stride;
530            }
531        }
532    }
533}
534
535template<class T>
536bool Accessor::ExtractData(T*& outData)
537{
538    uint8_t* data = GetPointer();
539    if (!data) return false;
540
541    const size_t elemSize = GetElementSize();
542    const size_t totalSize = elemSize * count;
543
544    const size_t stride = byteStride ? byteStride : elemSize;
545
546    const size_t targetElemSize = sizeof(T);
547    ai_assert(elemSize <= targetElemSize);
548
549    ai_assert(count*stride <= bufferView->byteLength);
550
551    outData = new T[count];
552    if (stride == elemSize && targetElemSize == elemSize) {
553        memcpy(outData, data, totalSize);
554    }
555    else {
556        for (size_t i = 0; i < count; ++i) {
557            memcpy(outData + i, data + i*stride, elemSize);
558        }
559    }
560
561    return true;
562}
563
564inline void Accessor::WriteData(size_t count, const void* src_buffer, size_t src_stride)
565{
566    uint8_t* buffer_ptr = bufferView->buffer->GetPointer();
567    size_t offset = byteOffset + bufferView->byteOffset;
568
569    size_t dst_stride = GetNumComponents() * GetBytesPerComponent();
570
571    const uint8_t* src = reinterpret_cast<const uint8_t*>(src_buffer);
572    uint8_t*       dst = reinterpret_cast<      uint8_t*>(buffer_ptr + offset);
573
574    ai_assert(dst + count*dst_stride <= buffer_ptr + bufferView->buffer->byteLength);
575    CopyData(count, src, src_stride, dst, dst_stride);
576}
577
578
579
580inline Accessor::Indexer::Indexer(Accessor& acc)
581    : accessor(acc)
582    , data(acc.GetPointer())
583    , elemSize(acc.GetElementSize())
584    , stride(acc.byteStride ? acc.byteStride : elemSize)
585{
586
587}
588
589//! Accesses the i-th value as defined by the accessor
590template<class T>
591T Accessor::Indexer::GetValue(int i)
592{
593    ai_assert(data);
594    ai_assert(i*stride < accessor.bufferView->byteLength);
595    T value = T();
596    memcpy(&value, data + i*stride, elemSize);
597    //value >>= 8 * (sizeof(T) - elemSize);
598    return value;
599}
600
601inline Image::Image()
602    : width(0)
603    , height(0)
604    , mData(0)
605    , mDataLength(0)
606{
607
608}
609
610inline void Image::Read(Value& obj, Asset& r)
611{
612    // Check for extensions first (to detect binary embedded data)
613    if (Value* extensions = FindObject(obj, "extensions")) {
614        if (r.extensionsUsed.KHR_binary_glTF) {
615            if (Value* ext = FindObject(*extensions, "KHR_binary_glTF")) {
616
617                width  = MemberOrDefault(*ext, "width", 0);
618                height = MemberOrDefault(*ext, "height", 0);
619
620                ReadMember(*ext, "mimeType", mimeType);
621
622                const char* bufferViewId;
623                if (ReadMember(*ext, "bufferView", bufferViewId)) {
624                    Ref<BufferView> bv = r.bufferViews.Get(bufferViewId);
625                    if (bv) {
626                        mDataLength = bv->byteLength;
627                        mData = new uint8_t[mDataLength];
628                        memcpy(mData, bv->buffer->GetPointer() + bv->byteOffset, mDataLength);
629                    }
630                }
631            }
632        }
633    }
634
635    if (!mDataLength) {
636        if (Value* uri = FindString(obj, "uri")) {
637            const char* uristr = uri->GetString();
638
639            Util::DataURI dataURI;
640            if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) {
641                mimeType = dataURI.mediaType;
642                if (dataURI.base64) {
643                    mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, mData);
644                }
645            }
646            else {
647                this->uri = uristr;
648            }
649        }
650    }
651}
652
653inline uint8_t* Image::StealData()
654{
655    uint8_t* data = mData;
656    mDataLength = 0;
657    mData = 0;
658    return data;
659}
660
661inline void Image::SetData(uint8_t* data, size_t length, Asset& r)
662{
663    Ref<Buffer> b = r.GetBodyBuffer();
664    if (b) { // binary file: append to body
665        std::string bvId = r.FindUniqueID(this->id, "imgdata");
666        bufferView = r.bufferViews.Create(bvId);
667
668        bufferView->buffer = b;
669        bufferView->byteLength = length;
670        bufferView->byteOffset = b->AppendData(data, length);
671    }
672    else { // text file: will be stored as a data uri
673        this->mData = data;
674        this->mDataLength = length;
675    }
676}
677
678inline void Sampler::Read(Value& obj, Asset& /*r*/)
679{
680    SetDefaults();
681
682    ReadMember(obj, "magFilter", magFilter);
683    ReadMember(obj, "minFilter", minFilter);
684    ReadMember(obj, "wrapS", wrapS);
685    ReadMember(obj, "wrapT", wrapT);
686}
687
688inline void Sampler::SetDefaults()
689{
690    magFilter = SamplerMagFilter_Linear;
691    minFilter = SamplerMinFilter_Linear;
692    wrapS = SamplerWrap_Repeat;
693    wrapT = SamplerWrap_Repeat;
694}
695
696inline void Texture::Read(Value& obj, Asset& r)
697{
698    const char* sourcestr;
699    if (ReadMember(obj, "source", sourcestr)) {
700        source = r.images.Get(sourcestr);
701    }
702
703    const char* samplerstr;
704    if (ReadMember(obj, "sampler", samplerstr)) {
705        sampler = r.samplers.Get(samplerstr);
706    }
707}
708
709namespace {
710    inline void ReadMaterialProperty(Asset& r, Value& vals, const char* propName, TexProperty& out)
711    {
712        if (Value* prop = FindMember(vals, propName)) {
713            if (prop->IsString()) {
714                out.texture = r.textures.Get(prop->GetString());
715            }
716            else {
717                ReadValue(*prop, out.color);
718            }
719        }
720    }
721}
722
723inline void Material::Read(Value& material, Asset& r)
724{
725    SetDefaults();
726
727    if (Value* values = FindObject(material, "values")) {
728        ReadMaterialProperty(r, *values, "ambient", this->ambient);
729        ReadMaterialProperty(r, *values, "diffuse", this->diffuse);
730        ReadMaterialProperty(r, *values, "specular", this->specular);
731
732        ReadMember(*values, "transparency", transparency);
733        ReadMember(*values, "shininess", shininess);
734    }
735
736    if (Value* extensions = FindObject(material, "extensions")) {
737        if (r.extensionsUsed.KHR_materials_common) {
738            if (Value* ext = FindObject(*extensions, "KHR_materials_common")) {
739                if (Value* tnq = FindString(*ext, "technique")) {
740                    const char* t = tnq->GetString();
741                    if      (strcmp(t, "BLINN") == 0)    technique = Technique_BLINN;
742                    else if (strcmp(t, "PHONG") == 0)    technique = Technique_PHONG;
743                    else if (strcmp(t, "LAMBERT") == 0)  technique = Technique_LAMBERT;
744                    else if (strcmp(t, "CONSTANT") == 0) technique = Technique_CONSTANT;
745                }
746
747                if (Value* values = FindObject(*ext, "values")) {
748                    ReadMaterialProperty(r, *values, "ambient", this->ambient);
749                    ReadMaterialProperty(r, *values, "diffuse", this->diffuse);
750                    ReadMaterialProperty(r, *values, "specular", this->specular);
751
752                    ReadMember(*values, "doubleSided", doubleSided);
753                    ReadMember(*values, "transparent", transparent);
754                    ReadMember(*values, "transparency", transparency);
755                    ReadMember(*values, "shininess", shininess);
756                }
757            }
758        }
759    }
760}
761
762namespace {
763    void SetVector(vec4& v, float x, float y, float z, float w)
764        { v[0] = x; v[1] = y; v[2] = z; v[3] = w; }
765}
766
767inline void Material::SetDefaults()
768{
769    SetVector(ambient.color, 0, 0, 0, 1);
770    SetVector(diffuse.color, 0, 0, 0, 1);
771    SetVector(specular.color, 0, 0, 0, 1);
772    SetVector(emission.color, 0, 0, 0, 1);
773
774    doubleSided = false;
775    transparent = false;
776    transparency = 1.0;
777    shininess = 0.0;
778
779    technique = Technique_undefined;
780}
781
782namespace {
783
784    template<int N>
785    inline int Compare(const char* attr, const char (&str)[N]) {
786        return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0;
787    }
788
789    inline bool GetAttribVector(Mesh::Primitive& p, const char* attr, Mesh::AccessorList*& v, int& pos)
790    {
791        if ((pos = Compare(attr, "POSITION"))) {
792            v = &(p.attributes.position);
793        }
794        else if ((pos = Compare(attr, "NORMAL"))) {
795            v = &(p.attributes.normal);
796        }
797        else if ((pos = Compare(attr, "TEXCOORD"))) {
798            v = &(p.attributes.texcoord);
799        }
800        else if ((pos = Compare(attr, "COLOR"))) {
801            v = &(p.attributes.color);
802        }
803        else if ((pos = Compare(attr, "JOINT"))) {
804            v = &(p.attributes.joint);
805        }
806        else if ((pos = Compare(attr, "JOINTMATRIX"))) {
807            v = &(p.attributes.jointmatrix);
808        }
809        else if ((pos = Compare(attr, "WEIGHT"))) {
810            v = &(p.attributes.weight);
811        }
812        else return false;
813        return true;
814    }
815}
816
817inline void Mesh::Read(Value& pJSON_Object, Asset& pAsset_Root)
818{
819	/****************** Mesh primitives ******************/
820	if (Value* primitives = FindArray(pJSON_Object, "primitives")) {
821        this->primitives.resize(primitives->Size());
822        for (unsigned int i = 0; i < primitives->Size(); ++i) {
823            Value& primitive = (*primitives)[i];
824
825            Primitive& prim = this->primitives[i];
826            prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES);
827
828            if (Value* attrs = FindObject(primitive, "attributes")) {
829                for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) {
830                    if (!it->value.IsString()) continue;
831                    const char* attr = it->name.GetString();
832                    // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX,
833                    // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc.
834
835                    int undPos = 0;
836                    Mesh::AccessorList* vec = 0;
837                    if (GetAttribVector(prim, attr, vec, undPos)) {
838                        size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0;
839                        if ((*vec).size() <= idx) (*vec).resize(idx + 1);
840						(*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString());
841                    }
842                }
843            }
844
845            if (Value* indices = FindString(primitive, "indices")) {
846				prim.indices = pAsset_Root.accessors.Get(indices->GetString());
847            }
848
849            if (Value* material = FindString(primitive, "material")) {
850				prim.material = pAsset_Root.materials.Get(material->GetString());
851            }
852        }
853    }
854
855	/****************** Mesh extensions ******************/
856	Value* json_extensions = FindObject(pJSON_Object, "extensions");
857
858	if(json_extensions == nullptr) goto mr_skip_extensions;
859
860	for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++)
861	{
862#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
863        if(it_memb->name.GetString() == std::string("Open3DGC-compression"))
864		{
865			// Search for compressed data.
866			// Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and
867			// new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure.
868			Value* comp_data = FindObject(it_memb->value, "compressedData");
869
870			if(comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\".");
871
872			DefaultLogger::get()->info("GLTF: Decompressing Open3DGC data.");
873
874			/************** Read data from JSON-document **************/
875			#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \
876				if(!ReadMember(*comp_data, pFieldName, pOut)) \
877				{ \
878					throw DeadlyImportError(std::string("GLTF: \"compressedData\" must has \"") + pFieldName + "\"."); \
879				}
880
881			const char* mode_str;
882			const char* type_str;
883			ComponentType component_type;
884			SCompression_Open3DGC* ext_o3dgc = new SCompression_Open3DGC;
885
886			MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer);
887			MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset);
888			MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type);
889			MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str);
890			MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count);
891			MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str);
892			MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount);
893			MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount);
894
895			#undef MESH_READ_COMPRESSEDDATA_MEMBER
896
897			// Check some values
898			if(strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data.");
899			if(component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data.");
900
901			// Set read/write data mode.
902			if(strcmp(mode_str, "binary") == 0)
903				ext_o3dgc->Binary = true;
904			else if(strcmp(mode_str, "ascii") == 0)
905				ext_o3dgc->Binary = false;
906			else
907				throw DeadlyImportError(std::string("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"") + mode_str + "\".");
908
909			/************************ Decoding ************************/
910			Decode_O3DGC(*ext_o3dgc, pAsset_Root);
911			Extension.push_back(ext_o3dgc);// store info in mesh extensions list.
912		}// if(it_memb->name.GetString() == "Open3DGC-compression")
913		else
914#endif
915		{
916			throw DeadlyImportError(std::string("GLTF: Unknown mesh extension: \"") + it_memb->name.GetString() + "\".");
917		}
918	}// for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++)
919
920mr_skip_extensions:
921
922	return;// After label some operators must be present.
923}
924
925#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
926inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC& pCompression_Open3DGC, Asset& pAsset_Root)
927{
928typedef unsigned short IndicesType;///< \sa glTFExporter::ExportMeshes.
929
930o3dgc::SC3DMCDecoder<IndicesType> decoder;
931o3dgc::IndexedFaceSet<IndicesType> ifs;
932o3dgc::BinaryStream bstream;
933uint8_t* decoded_data;
934size_t decoded_data_size = 0;
935Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer);
936
937	// Read data from buffer and place it in BinaryStream for decoder.
938	// Just "Count" because always is used type equivalent to uint8_t.
939	bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast<unsigned long>(pCompression_Open3DGC.Count));
940
941	// After decoding header we can get size of primitives.
942	if(decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header.");
943
944	/****************** Get sizes of arrays and check sizes ******************/
945	// Note. See "Limitations for meshes when using Open3DGC-compression".
946
947	// Indices
948	size_t size_coordindex = ifs.GetNCoordIndex() * 3;// See float attributes note.
949
950	if(primitives[0].indices->count != size_coordindex)
951		throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (" + std::to_string(size_coordindex) +
952								") not equal to uncompressed (" + std::to_string(primitives[0].indices->count) + ").");
953
954	size_coordindex *= sizeof(IndicesType);
955	// Coordinates
956	size_t size_coord = ifs.GetNCoord();// See float attributes note.
957
958	if(primitives[0].attributes.position[0]->count != size_coord)
959		throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (" + std::to_string(size_coord) +
960								") not equal to uncompressed (" + std::to_string(primitives[0].attributes.position[0]->count) + ").");
961
962	size_coord *= 3 * sizeof(float);
963	// Normals
964	size_t size_normal = ifs.GetNNormal();// See float attributes note.
965
966	if(primitives[0].attributes.normal[0]->count != size_normal)
967		throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (" + std::to_string(size_normal) +
968								") not equal to uncompressed (" + std::to_string(primitives[0].attributes.normal[0]->count) + ").");
969
970	size_normal *= 3 * sizeof(float);
971	// Additional attributes.
972	std::vector<size_t> size_floatattr;
973	std::vector<size_t> size_intattr;
974
975	size_floatattr.resize(ifs.GetNumFloatAttributes());
976	size_intattr.resize(ifs.GetNumIntAttributes());
977
978	decoded_data_size = size_coordindex + size_coord + size_normal;
979	for(size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++)
980	{
981		// size = number_of_elements * components_per_element * size_of_component.
982		// Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects...
983		size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx));
984
985		switch(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))
986		{
987			case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
988				// Check situation when encoded data contain texture coordinates but primitive not.
989				if(idx_texcoord < primitives[0].attributes.texcoord.size())
990				{
991					if(primitives[0].attributes.texcoord[idx]->count != tval)
992						throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (" + std::to_string(tval) +
993												") not equal to uncompressed (" + std::to_string(primitives[0].attributes.texcoord[idx]->count) + ").");
994
995					idx_texcoord++;
996				}
997				else
998				{
999					ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul);// Disable decoding this attribute.
1000				}
1001
1002				break;
1003			default:
1004				throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
1005		}
1006
1007		tval *=  ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real);// After checking count of objects we can get size of array.
1008		size_floatattr[idx] = tval;
1009		decoded_data_size += tval;
1010	}
1011
1012	for(size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++)
1013	{
1014		// size = number_of_elements * components_per_element * size_of_component. See float attributes note.
1015		size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx));
1016		switch( ifs.GetIntAttributeType(static_cast<unsigned long>(idx) ) )
1017		{
1018            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
1019            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
1020            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
1021            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
1022                break;
1023
1024			default:
1025				throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
1026		}
1027
1028		tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long);// See float attributes note.
1029		size_intattr[idx] = tval;
1030		decoded_data_size += tval;
1031	}
1032
1033	// Create array for decoded data.
1034	decoded_data = new uint8_t[decoded_data_size];
1035
1036	/****************** Set right array regions for decoder ******************/
1037
1038	auto get_buf_offset = [](Ref<Accessor>& pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; };
1039
1040	// Indices
1041	ifs.SetCoordIndex((IndicesType* const)(decoded_data + get_buf_offset(primitives[0].indices)));
1042	// Coordinates
1043	ifs.SetCoord((o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0])));
1044	// Normals
1045	if(size_normal)
1046	{
1047		ifs.SetNormal((o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0])));
1048	}
1049
1050	for(size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++)
1051	{
1052		switch(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))
1053		{
1054			case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
1055				if(idx_texcoord < primitives[0].attributes.texcoord.size())
1056				{
1057					// See above about absent attributes.
1058					ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx])));
1059					idx_texcoord++;
1060				}
1061
1062				break;
1063			default:
1064				throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
1065		}
1066	}
1067
1068	for(size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) {
1069		switch(ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) {
1070            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
1071            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
1072            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
1073            case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
1074                break;
1075
1076			// ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint)));
1077			default:
1078				throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
1079		}
1080	}
1081
1082	//
1083	// Decode data
1084	//
1085    if ( decoder.DecodePayload( ifs, bstream ) != o3dgc::O3DGC_OK ) {
1086        throw DeadlyImportError( "GLTF: can not decode Open3DGC data." );
1087    }
1088
1089	// Set encoded region for "buffer".
1090	buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id);
1091	// No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data".
1092	// "delete [] output_data;"
1093}
1094#endif
1095
1096inline void Camera::Read(Value& obj, Asset& /*r*/)
1097{
1098    type = MemberOrDefault(obj, "type", Camera::Perspective);
1099
1100    const char* subobjId = (type == Camera::Orthographic) ? "ortographic" : "perspective";
1101
1102    Value* it = FindObject(obj, subobjId);
1103    if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters");
1104
1105    if (type == Camera::Perspective) {
1106        perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f);
1107        perspective.yfov        = MemberOrDefault(*it, "yfov", 3.1415f/2.f);
1108        perspective.zfar        = MemberOrDefault(*it, "zfar", 100.f);
1109        perspective.znear       = MemberOrDefault(*it, "znear", 0.01f);
1110    }
1111    else {
1112        ortographic.xmag  = MemberOrDefault(obj, "xmag", 1.f);
1113        ortographic.ymag  = MemberOrDefault(obj, "ymag", 1.f);
1114        ortographic.zfar  = MemberOrDefault(obj, "zfar", 100.f);
1115        ortographic.znear = MemberOrDefault(obj, "znear", 0.01f);
1116    }
1117}
1118
1119inline void Light::Read(Value& obj, Asset& /*r*/)
1120{
1121    SetDefaults();
1122
1123    if (Value* type = FindString(obj, "type")) {
1124        const char* t = type->GetString();
1125        if      (strcmp(t, "ambient") == 0)     this->type = Type_ambient;
1126        else if (strcmp(t, "directional") == 0) this->type = Type_directional;
1127        else if (strcmp(t, "point") == 0)       this->type = Type_point;
1128        else if (strcmp(t, "spot") == 0)        this->type = Type_spot;
1129
1130        if (this->type != Type_undefined) {
1131            if (Value* vals = FindString(obj, t)) {
1132                ReadMember(*vals, "color", color);
1133
1134                ReadMember(*vals, "constantAttenuation", constantAttenuation);
1135                ReadMember(*vals, "linearAttenuation", linearAttenuation);
1136                ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation);
1137                ReadMember(*vals, "distance", distance);
1138
1139                ReadMember(*vals, "falloffAngle", falloffAngle);
1140                ReadMember(*vals, "falloffExponent", falloffExponent);
1141            }
1142        }
1143    }
1144}
1145
1146inline void Light::SetDefaults()
1147{
1148    #ifndef M_PI
1149        const float M_PI = 3.14159265358979323846f;
1150    #endif
1151
1152    type = Type_undefined;
1153
1154    SetVector(color, 0.f, 0.f, 0.f, 1.f);
1155
1156    constantAttenuation = 0.f;
1157    linearAttenuation = 1.f;
1158    quadraticAttenuation = 1.f;
1159    distance = 0.f;
1160
1161    falloffAngle = static_cast<float>(M_PI / 2.f);
1162    falloffExponent = 0.f;
1163}
1164
1165inline void Node::Read(Value& obj, Asset& r)
1166{
1167    if (Value* children = FindArray(obj, "children")) {
1168        this->children.reserve(children->Size());
1169        for (unsigned int i = 0; i < children->Size(); ++i) {
1170            Value& child = (*children)[i];
1171            if (child.IsString()) {
1172                // get/create the child node
1173                Ref<Node> chn = r.nodes.Get(child.GetString());
1174                if (chn) this->children.push_back(chn);
1175            }
1176        }
1177    }
1178
1179
1180    if (Value* matrix = FindArray(obj, "matrix")) {
1181        ReadValue(*matrix, this->matrix);
1182    }
1183    else {
1184        ReadMember(obj, "translation", translation);
1185        ReadMember(obj, "scale", scale);
1186        ReadMember(obj, "rotation", rotation);
1187    }
1188
1189    if (Value* meshes = FindArray(obj, "meshes")) {
1190        unsigned numMeshes = (unsigned)meshes->Size();
1191
1192        std::vector<unsigned int> meshList;
1193
1194        this->meshes.reserve(numMeshes);
1195        for (unsigned i = 0; i < numMeshes; ++i) {
1196            if ((*meshes)[i].IsString()) {
1197                Ref<Mesh> mesh = r.meshes.Get((*meshes)[i].GetString());
1198                if (mesh) this->meshes.push_back(mesh);
1199            }
1200        }
1201    }
1202
1203    if (Value* camera = FindString(obj, "camera")) {
1204        this->camera = r.cameras.Get(camera->GetString());
1205        if (this->camera)
1206            this->camera->id = this->id;
1207    }
1208
1209    // TODO load "skeletons", "skin", "jointName"
1210
1211    if (Value* extensions = FindObject(obj, "extensions")) {
1212        if (r.extensionsUsed.KHR_materials_common) {
1213
1214            if (Value* ext = FindObject(*extensions, "KHR_materials_common")) {
1215                if (Value* light = FindString(*ext, "light")) {
1216                    this->light = r.lights.Get(light->GetString());
1217                }
1218            }
1219
1220        }
1221    }
1222}
1223
1224inline void Scene::Read(Value& obj, Asset& r)
1225{
1226    if (Value* array = FindArray(obj, "nodes")) {
1227        for (unsigned int i = 0; i < array->Size(); ++i) {
1228            if (!(*array)[i].IsString()) continue;
1229            Ref<Node> node = r.nodes.Get((*array)[i].GetString());
1230            if (node)
1231                this->nodes.push_back(node);
1232        }
1233    }
1234}
1235
1236
1237inline void AssetMetadata::Read(Document& doc)
1238{
1239    // read the version, etc.
1240    if (Value* obj = FindObject(doc, "asset")) {
1241        ReadMember(*obj, "copyright", copyright);
1242        ReadMember(*obj, "generator", generator);
1243
1244        premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false);
1245
1246        if (Value* versionString = FindString(*obj, "version")) {
1247            version = versionString->GetString();
1248        } else if (Value* versionNumber = FindNumber (*obj, "version")) {
1249            char buf[4];
1250
1251            ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble());
1252
1253            version = buf;
1254        }
1255
1256        if (Value* profile = FindObject(*obj, "profile")) {
1257            ReadMember(*profile, "api",     this->profile.api);
1258            ReadMember(*profile, "version", this->profile.version);
1259        }
1260    }
1261
1262    if (version.empty() || version[0] != '1') {
1263        throw DeadlyImportError("GLTF: Unsupported glTF version: " + version);
1264    }
1265}
1266
1267
1268
1269//
1270// Asset methods implementation
1271//
1272
1273inline void Asset::ReadBinaryHeader(IOStream& stream)
1274{
1275    GLB_Header header;
1276    if (stream.Read(&header, sizeof(header), 1) != 1) {
1277        throw DeadlyImportError("GLTF: Unable to read the file header");
1278    }
1279
1280    if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) {
1281        throw DeadlyImportError("GLTF: Invalid binary glTF file");
1282    }
1283
1284    AI_SWAP4(header.version);
1285    asset.version = to_string(header.version);
1286    if (header.version != 1) {
1287        throw DeadlyImportError("GLTF: Unsupported binary glTF version");
1288    }
1289
1290    AI_SWAP4(header.sceneFormat);
1291    if (header.sceneFormat != SceneFormat_JSON) {
1292        throw DeadlyImportError("GLTF: Unsupported binary glTF scene format");
1293    }
1294
1295    AI_SWAP4(header.length);
1296    AI_SWAP4(header.sceneLength);
1297
1298    mSceneLength = static_cast<size_t>(header.sceneLength);
1299
1300    mBodyOffset = sizeof(header)+mSceneLength;
1301    mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4
1302
1303    mBodyLength = header.length - mBodyOffset;
1304}
1305
1306inline void Asset::Load(const std::string& pFile, bool isBinary)
1307{
1308    mCurrentAssetDir.clear();
1309    int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\')));
1310    if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);
1311
1312    shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true));
1313    if (!stream) {
1314        throw DeadlyImportError("GLTF: Could not open file for reading");
1315    }
1316
1317    // is binary? then read the header
1318    if (isBinary) {
1319        SetAsBinary(); // also creates the body buffer
1320        ReadBinaryHeader(*stream);
1321    }
1322    else {
1323        mSceneLength = stream->FileSize();
1324        mBodyLength = 0;
1325    }
1326
1327
1328    // read the scene data
1329
1330    std::vector<char> sceneData(mSceneLength + 1);
1331    sceneData[mSceneLength] = '\0';
1332
1333    if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) {
1334        throw DeadlyImportError("GLTF: Could not read the file contents");
1335    }
1336
1337
1338    // parse the JSON document
1339
1340    Document doc;
1341    doc.ParseInsitu(&sceneData[0]);
1342
1343    if (doc.HasParseError()) {
1344        char buffer[32];
1345        ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset()));
1346        throw DeadlyImportError(std::string("GLTF: JSON parse error, offset ") + buffer + ": "
1347            + GetParseError_En(doc.GetParseError()));
1348    }
1349
1350    if (!doc.IsObject()) {
1351        throw DeadlyImportError("GLTF: JSON document root must be a JSON object");
1352    }
1353
1354    // Fill the buffer instance for the current file embedded contents
1355    if (mBodyLength > 0) {
1356        if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) {
1357            throw DeadlyImportError("GLTF: Unable to read gltf file");
1358        }
1359    }
1360
1361
1362    // Load the metadata
1363    asset.Read(doc);
1364    ReadExtensionsUsed(doc);
1365
1366    // Prepare the dictionaries
1367    for (size_t i = 0; i < mDicts.size(); ++i) {
1368        mDicts[i]->AttachToDocument(doc);
1369    }
1370
1371
1372
1373    // Read the "scene" property, which specifies which scene to load
1374    // and recursively load everything referenced by it
1375    if (Value* scene = FindString(doc, "scene")) {
1376        this->scene = scenes.Get(scene->GetString());
1377    }
1378
1379    // Clean up
1380    for (size_t i = 0; i < mDicts.size(); ++i) {
1381        mDicts[i]->DetachFromDocument();
1382    }
1383}
1384
1385inline void Asset::SetAsBinary()
1386{
1387    if (!extensionsUsed.KHR_binary_glTF) {
1388        extensionsUsed.KHR_binary_glTF = true;
1389        mBodyBuffer = buffers.Create("binary_glTF");
1390        mBodyBuffer->MarkAsSpecial();
1391    }
1392}
1393
1394
1395inline void Asset::ReadExtensionsUsed(Document& doc)
1396{
1397    Value* extsUsed = FindArray(doc, "extensionsUsed");
1398    if (!extsUsed) return;
1399
1400    std::gltf_unordered_map<std::string, bool> exts;
1401
1402    for (unsigned int i = 0; i < extsUsed->Size(); ++i) {
1403        if ((*extsUsed)[i].IsString()) {
1404            exts[(*extsUsed)[i].GetString()] = true;
1405        }
1406    }
1407
1408    #define CHECK_EXT(EXT) \
1409        if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true;
1410
1411    CHECK_EXT(KHR_binary_glTF);
1412    CHECK_EXT(KHR_materials_common);
1413
1414    #undef CHECK_EXT
1415}
1416
1417inline IOStream* Asset::OpenFile(std::string path, const char* mode, bool /*absolute*/)
1418{
1419    #ifdef ASSIMP_API
1420        return mIOSystem->Open(path, mode);
1421    #else
1422        if (path.size() < 2) return 0;
1423        if (!absolute && path[1] != ':' && path[0] != '/') { // relative?
1424            path = mCurrentAssetDir + path;
1425        }
1426        FILE* f = fopen(path.c_str(), mode);
1427        return f ? new IOStream(f) : 0;
1428    #endif
1429}
1430
1431inline std::string Asset::FindUniqueID(const std::string& str, const char* suffix)
1432{
1433    std::string id = str;
1434
1435    if (!id.empty()) {
1436        if (mUsedIds.find(id) == mUsedIds.end())
1437            return id;
1438
1439        id += "_";
1440    }
1441
1442    id += suffix;
1443
1444    Asset::IdMap::iterator it = mUsedIds.find(id);
1445    if (it == mUsedIds.end())
1446        return id;
1447
1448    char buffer[256];
1449    int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str());
1450    for (int i = 0; it != mUsedIds.end(); ++i) {
1451        ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i);
1452        id = buffer;
1453        it = mUsedIds.find(id);
1454    }
1455
1456    return id;
1457}
1458
1459namespace Util {
1460
1461    inline
1462    bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) {
1463        if ( NULL == const_uri ) {
1464            return false;
1465        }
1466
1467        if (const_uri[0] != 0x10) { // we already parsed this uri?
1468            if (strncmp(const_uri, "data:", 5) != 0) // not a data uri?
1469                return false;
1470        }
1471
1472        // set defaults
1473        out.mediaType = "text/plain";
1474        out.charset = "US-ASCII";
1475        out.base64 = false;
1476
1477        char* uri = const_cast<char*>(const_uri);
1478        if (uri[0] != 0x10) {
1479            uri[0] = 0x10;
1480            uri[1] = uri[2] = uri[3] = uri[4] = 0;
1481
1482            size_t i = 5, j;
1483            if (uri[i] != ';' && uri[i] != ',') { // has media type?
1484                uri[1] = char(i);
1485                for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
1486                    // nothing to do!
1487                }
1488            }
1489            while (uri[i] == ';' && i < uriLen) {
1490                uri[i++] = '\0';
1491                for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
1492                    // nothing to do!
1493                }
1494
1495                if ( strncmp( uri + j, "charset=", 8 ) == 0 ) {
1496                    uri[2] = char(j + 8);
1497                } else if ( strncmp( uri + j, "base64", 6 ) == 0 ) {
1498                    uri[3] = char(j);
1499                }
1500            }
1501            if (i < uriLen) {
1502                uri[i++] = '\0';
1503                uri[4] = char(i);
1504            } else {
1505                uri[1] = uri[2] = uri[3] = 0;
1506                uri[4] = 5;
1507            }
1508        }
1509
1510        if ( uri[ 1 ] != 0 ) {
1511            out.mediaType = uri + uri[ 1 ];
1512        }
1513        if ( uri[ 2 ] != 0 ) {
1514            out.charset = uri + uri[ 2 ];
1515        }
1516        if ( uri[ 3 ] != 0 ) {
1517            out.base64 = true;
1518        }
1519        out.data = uri + uri[4];
1520        out.dataLength = (uri + uriLen) - out.data;
1521
1522        return true;
1523    }
1524
1525    template<bool B>
1526    struct DATA
1527    {
1528        static const uint8_t tableDecodeBase64[128];
1529    };
1530
1531    template<bool B>
1532    const uint8_t DATA<B>::tableDecodeBase64[128] = {
1533         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1534         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1535         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62,  0,  0,  0, 63,
1536        52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0, 64,  0,  0,
1537         0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
1538        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0,  0,
1539         0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
1540        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  0,  0,  0,  0,  0
1541    };
1542
1543    inline char EncodeCharBase64(uint8_t b)
1544    {
1545        return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)];
1546    }
1547
1548    inline uint8_t DecodeCharBase64(char c)
1549    {
1550        return DATA<true>::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs?
1551        /*if (c >= 'A' && c <= 'Z') return c - 'A';
1552        if (c >= 'a' && c <= 'z') return c - 'a' + 26;
1553        if (c >= '0' && c <= '9') return c - '0' + 52;
1554        if (c == '+') return 62;
1555        if (c == '/') return 63;
1556        return 64; // '-' */
1557    }
1558
1559    inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out)
1560    {
1561        ai_assert(inLength % 4 == 0);
1562
1563        if (inLength < 4) {
1564            out = 0;
1565            return 0;
1566        }
1567
1568        int nEquals = int(in[inLength - 1] == '=') +
1569                      int(in[inLength - 2] == '=');
1570
1571        size_t outLength = (inLength * 3) / 4 - nEquals;
1572        out = new uint8_t[outLength];
1573        memset(out, 0, outLength);
1574
1575        size_t i, j = 0;
1576
1577        for (i = 0; i + 4 < inLength; i += 4) {
1578            uint8_t b0 = DecodeCharBase64(in[i]);
1579            uint8_t b1 = DecodeCharBase64(in[i + 1]);
1580            uint8_t b2 = DecodeCharBase64(in[i + 2]);
1581            uint8_t b3 = DecodeCharBase64(in[i + 3]);
1582
1583            out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4));
1584            out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2));
1585            out[j++] = (uint8_t)((b2 << 6) | b3);
1586        }
1587
1588        {
1589            uint8_t b0 = DecodeCharBase64(in[i]);
1590            uint8_t b1 = DecodeCharBase64(in[i + 1]);
1591            uint8_t b2 = DecodeCharBase64(in[i + 2]);
1592            uint8_t b3 = DecodeCharBase64(in[i + 3]);
1593
1594            out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4));
1595            if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2));
1596            if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3);
1597        }
1598
1599        return outLength;
1600    }
1601
1602
1603
1604    inline void EncodeBase64(
1605        const uint8_t* in, size_t inLength,
1606        std::string& out)
1607    {
1608        size_t outLength = ((inLength + 2) / 3) * 4;
1609
1610        size_t j = out.size();
1611        out.resize(j + outLength);
1612
1613        for (size_t i = 0; i <  inLength; i += 3) {
1614            uint8_t b = (in[i] & 0xFC) >> 2;
1615            out[j++] = EncodeCharBase64(b);
1616
1617            b = (in[i] & 0x03) << 4;
1618            if (i + 1 < inLength) {
1619                b |= (in[i + 1] & 0xF0) >> 4;
1620                out[j++] = EncodeCharBase64(b);
1621
1622                b = (in[i + 1] & 0x0F) << 2;
1623                if (i + 2 < inLength) {
1624                    b |= (in[i + 2] & 0xC0) >> 6;
1625                    out[j++] = EncodeCharBase64(b);
1626
1627                    b = in[i + 2] & 0x3F;
1628                    out[j++] = EncodeCharBase64(b);
1629                }
1630                else {
1631                    out[j++] = EncodeCharBase64(b);
1632                    out[j++] = '=';
1633                }
1634            }
1635            else {
1636                out[j++] = EncodeCharBase64(b);
1637                out[j++] = '=';
1638                out[j++] = '=';
1639            }
1640        }
1641    }
1642
1643}
1644
1645} // ns glTF
1646