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
44// Header files, Assimp
45#include <assimp/DefaultLogger.hpp>
46
47using namespace Assimp;
48
49namespace glTF2 {
50
51namespace {
52
53    //
54    // JSON Value reading helpers
55    //
56
57    template<class T>
58    struct ReadHelper { static bool Read(Value& val, T& out) {
59        return val.IsInt() ? out = static_cast<T>(val.GetInt()), true : false;
60    }};
61
62    template<> struct ReadHelper<bool> { static bool Read(Value& val, bool& out) {
63        return val.IsBool() ? out = val.GetBool(), true : false;
64    }};
65
66    template<> struct ReadHelper<float> { static bool Read(Value& val, float& out) {
67        return val.IsNumber() ? out = static_cast<float>(val.GetDouble()), true : false;
68    }};
69
70    template<unsigned int N> struct ReadHelper<float[N]> { static bool Read(Value& val, float (&out)[N]) {
71        if (!val.IsArray() || val.Size() != N) return false;
72        for (unsigned int i = 0; i < N; ++i) {
73            if (val[i].IsNumber())
74                out[i] = static_cast<float>(val[i].GetDouble());
75        }
76        return true;
77    }};
78
79    template<> struct ReadHelper<const char*> { static bool Read(Value& val, const char*& out) {
80        return val.IsString() ? (out = val.GetString(), true) : false;
81    }};
82
83    template<> struct ReadHelper<std::string> { static bool Read(Value& val, std::string& out) {
84        return val.IsString() ? (out = std::string(val.GetString(), val.GetStringLength()), true) : false;
85    }};
86
87    template<class T> struct ReadHelper< Nullable<T> > { static bool Read(Value& val, Nullable<T>& out) {
88        return out.isPresent = ReadHelper<T>::Read(val, out.value);
89    }};
90
91    template<class T>
92    inline static bool ReadValue(Value& val, T& out)
93    {
94        return ReadHelper<T>::Read(val, out);
95    }
96
97    template<class T>
98    inline static bool ReadMember(Value& obj, const char* id, T& out)
99    {
100        Value::MemberIterator it = obj.FindMember(id);
101        if (it != obj.MemberEnd()) {
102            return ReadHelper<T>::Read(it->value, out);
103        }
104        return false;
105    }
106
107    template<class T>
108    inline static T MemberOrDefault(Value& obj, const char* id, T defaultValue)
109    {
110        T out;
111        return ReadMember(obj, id, out) ? out : defaultValue;
112    }
113
114    inline Value* FindMember(Value& val, const char* id)
115    {
116        Value::MemberIterator it = val.FindMember(id);
117        return (it != val.MemberEnd()) ? &it->value : 0;
118    }
119
120    inline Value* FindString(Value& val, const char* id)
121    {
122        Value::MemberIterator it = val.FindMember(id);
123        return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0;
124    }
125
126    inline Value* FindNumber(Value& val, const char* id)
127    {
128        Value::MemberIterator it = val.FindMember(id);
129        return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0;
130    }
131
132    inline Value* FindUInt(Value& val, const char* id)
133    {
134        Value::MemberIterator it = val.FindMember(id);
135        return (it != val.MemberEnd() && it->value.IsUint()) ? &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 = FindArray(*container, mDictId);
187    }
188}
189
190template<class T>
191inline void LazyDict<T>::DetachFromDocument()
192{
193    mDict = 0;
194}
195
196template<class T>
197unsigned int LazyDict<T>::Remove(const char* id)
198{
199    id = T::TranslateId(mAsset, id);
200
201    typename IdDict::iterator it = mObjsById.find(id);
202
203    if (it == mObjsById.end()) {
204        throw DeadlyExportError("GLTF: Object with id \"" + std::string(id) + "\" is not found");
205    }
206
207    const unsigned int index = it->second;
208
209    mAsset.mUsedIds[id] = false;
210    mObjsById.erase(id);
211    mObjsByOIndex.erase(index);
212    mObjs.erase(mObjs.begin() + index);
213
214    //update index of object in mObjs;
215    for (unsigned int i = index; i < mObjs.size(); ++i) {
216        T *obj = mObjs[i];
217
218        obj->index = i;
219    }
220
221    for (IdDict::iterator it = mObjsById.begin(); it != mObjsById.end(); ++it) {
222        if (it->second <= index) {
223            continue;
224        }
225
226        mObjsById[it->first] = it->second - 1;
227    }
228
229    for (Dict::iterator it = mObjsByOIndex.begin(); it != mObjsByOIndex.end(); ++it) {
230        if (it->second <= index) {
231            continue;
232        }
233
234        mObjsByOIndex[it->first] = it->second - 1;
235    }
236
237    return index;
238}
239
240template<class T>
241Ref<T> LazyDict<T>::Retrieve(unsigned int i)
242{
243
244    typename Dict::iterator it = mObjsByOIndex.find(i);
245    if (it != mObjsByOIndex.end()) {// already created?
246        return Ref<T>(mObjs, it->second);
247    }
248
249    // read it from the JSON object
250    if (!mDict) {
251        throw DeadlyImportError("GLTF: Missing section \"" + std::string(mDictId) + "\"");
252    }
253
254    if (!mDict->IsArray()) {
255        throw DeadlyImportError("GLTF: Field is not an array \"" + std::string(mDictId) + "\"");
256    }
257
258    Value &obj = (*mDict)[i];
259
260    if (!obj.IsObject()) {
261        throw DeadlyImportError("GLTF: Object at index \"" + to_string(i) + "\" is not a JSON object");
262    }
263
264    T* inst = new T();
265    inst->id = std::string(mDictId) + "_" + to_string(i);
266    inst->oIndex = i;
267    ReadMember(obj, "name", inst->name);
268    inst->Read(obj, mAsset);
269
270    return Add(inst);
271}
272
273template<class T>
274Ref<T> LazyDict<T>::Get(unsigned int i)
275{
276
277    return Ref<T>(mObjs, i);
278
279}
280
281template<class T>
282Ref<T> LazyDict<T>::Get(const char* id)
283{
284    id = T::TranslateId(mAsset, id);
285
286    typename IdDict::iterator it = mObjsById.find(id);
287    if (it != mObjsById.end()) { // already created?
288        return Ref<T>(mObjs, it->second);
289    }
290
291    return Ref<T>();
292}
293
294template<class T>
295Ref<T> LazyDict<T>::Add(T* obj)
296{
297    unsigned int idx = unsigned(mObjs.size());
298    mObjs.push_back(obj);
299    mObjsByOIndex[obj->oIndex] = idx;
300    mObjsById[obj->id] = idx;
301    mAsset.mUsedIds[obj->id] = true;
302    return Ref<T>(mObjs, idx);
303}
304
305template<class T>
306Ref<T> LazyDict<T>::Create(const char* id)
307{
308    Asset::IdMap::iterator it = mAsset.mUsedIds.find(id);
309    if (it != mAsset.mUsedIds.end()) {
310        throw DeadlyImportError("GLTF: two objects with the same ID exist");
311    }
312    T* inst = new T();
313    unsigned int idx = unsigned(mObjs.size());
314    inst->id = id;
315    inst->index = idx;
316    inst->oIndex = idx;
317    return Add(inst);
318}
319
320
321//
322// glTF dictionary objects methods
323//
324
325
326inline Buffer::Buffer()
327	: byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false)
328{ }
329
330inline Buffer::~Buffer()
331{
332	for(SEncodedRegion* reg : EncodedRegion_List) delete reg;
333}
334
335inline const char* Buffer::TranslateId(Asset& /*r*/, const char* id)
336{
337    return id;
338}
339
340inline void Buffer::Read(Value& obj, Asset& r)
341{
342    size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0);
343    byteLength = statedLength;
344
345    Value* it = FindString(obj, "uri");
346    if (!it) {
347        if (statedLength > 0) {
348            throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute");
349        }
350        return;
351    }
352
353    const char* uri = it->GetString();
354
355    Util::DataURI dataURI;
356    if (ParseDataURI(uri, it->GetStringLength(), dataURI)) {
357        if (dataURI.base64) {
358            uint8_t* data = 0;
359            this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data);
360            this->mData.reset(data, std::default_delete<uint8_t[]>());
361
362            if (statedLength > 0 && this->byteLength != statedLength) {
363                throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) +
364                    " bytes, but found " + to_string(dataURI.dataLength));
365            }
366        }
367        else { // assume raw data
368            if (statedLength != dataURI.dataLength) {
369                throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) +
370                                        " bytes, but found " + to_string(dataURI.dataLength));
371            }
372
373            this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete<uint8_t[]>());
374            memcpy( this->mData.get(), dataURI.data, dataURI.dataLength );
375        }
376    }
377    else { // Local file
378        if (byteLength > 0) {
379            std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir + "/") : "";
380
381            IOStream* file = r.OpenFile(dir + uri, "rb");
382            if (file) {
383                bool ok = LoadFromStream(*file, byteLength);
384                delete file;
385
386                if (!ok)
387                    throw DeadlyImportError("GLTF: error while reading referenced file \"" + std::string(uri) + "\"" );
388            }
389            else {
390                throw DeadlyImportError("GLTF: could not open referenced file \"" + std::string(uri) + "\"");
391            }
392        }
393    }
394}
395
396inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
397{
398    byteLength = length ? length : stream.FileSize();
399
400    if (baseOffset) {
401        stream.Seek(baseOffset, aiOrigin_SET);
402    }
403
404    mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>());
405
406    if (stream.Read(mData.get(), byteLength, 1) != 1) {
407        return false;
408    }
409    return true;
410}
411
412inline 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)
413{
414	// Check pointer to data
415	if(pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided.");
416
417	// Check offset
418	if(pOffset > byteLength)
419	{
420		const uint8_t val_size = 32;
421
422		char val[val_size];
423
424		ai_snprintf(val, val_size, "%llu", (long long)pOffset);
425		throw DeadlyImportError(std::string("GLTF: incorrect offset value (") + val + ") for marking encoded region.");
426	}
427
428	// Check length
429	if((pOffset + pEncodedData_Length) > byteLength)
430	{
431		const uint8_t val_size = 64;
432
433		char val[val_size];
434
435		ai_snprintf(val, val_size, "%llu, %llu", (long long)pOffset, (long long)pEncodedData_Length);
436		throw DeadlyImportError(std::string("GLTF: encoded region with offset/length (") + val + ") is out of range.");
437	}
438
439	// Add new region
440	EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID));
441	// And set new value for "byteLength"
442	byteLength += (pDecodedData_Length - pEncodedData_Length);
443}
444
445inline void Buffer::EncodedRegion_SetCurrent(const std::string& pID)
446{
447	if((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return;
448
449	for(SEncodedRegion* reg : EncodedRegion_List)
450	{
451		if(reg->ID == pID)
452		{
453			EncodedRegion_Current = reg;
454
455			return;
456		}
457
458	}
459
460	throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found.");
461}
462
463inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count)
464{
465const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count;
466
467uint8_t* new_data;
468
469	if((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false;
470
471	new_data = new uint8_t[new_data_size];
472	// Copy data which place before replacing part.
473	memcpy(new_data, mData.get(), pBufferData_Offset);
474	// Copy new data.
475	memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count);
476	// Copy data which place after replacing part.
477	memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset);
478	// Apply new data
479	mData.reset(new_data, std::default_delete<uint8_t[]>());
480	byteLength = new_data_size;
481
482	return true;
483}
484
485inline size_t Buffer::AppendData(uint8_t* data, size_t length)
486{
487    size_t offset = this->byteLength;
488    Grow(length);
489    memcpy(mData.get() + offset, data, length);
490    return offset;
491}
492
493inline void Buffer::Grow(size_t amount)
494{
495    if (amount <= 0) return;
496    uint8_t* b = new uint8_t[byteLength + amount];
497    if (mData) memcpy(b, mData.get(), byteLength);
498    mData.reset(b, std::default_delete<uint8_t[]>());
499    byteLength += amount;
500}
501
502//
503// struct BufferView
504//
505
506inline void BufferView::Read(Value& obj, Asset& r)
507{
508
509    if (Value* bufferVal = FindUInt(obj, "buffer")) {
510        buffer = r.buffers.Retrieve(bufferVal->GetUint());
511    }
512
513    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
514    byteLength = MemberOrDefault(obj, "byteLength", 0u);
515    byteStride = MemberOrDefault(obj, "byteStride", 0u);
516}
517
518//
519// struct Accessor
520//
521
522inline void Accessor::Read(Value& obj, Asset& r)
523{
524
525    if (Value* bufferViewVal = FindUInt(obj, "bufferView")) {
526        bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint());
527    }
528
529    byteOffset = MemberOrDefault(obj, "byteOffset", 0u);
530    componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE);
531    count = MemberOrDefault(obj, "count", 0u);
532
533    const char* typestr;
534    type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR;
535}
536
537inline unsigned int Accessor::GetNumComponents()
538{
539    return AttribType::GetNumComponents(type);
540}
541
542inline unsigned int Accessor::GetBytesPerComponent()
543{
544    return int(ComponentTypeSize(componentType));
545}
546
547inline unsigned int Accessor::GetElementSize()
548{
549    return GetNumComponents() * GetBytesPerComponent();
550}
551
552inline uint8_t* Accessor::GetPointer()
553{
554    if (!bufferView || !bufferView->buffer) return 0;
555    uint8_t* basePtr = bufferView->buffer->GetPointer();
556    if (!basePtr) return 0;
557
558    size_t offset = byteOffset + bufferView->byteOffset;
559
560	// Check if region is encoded.
561	if(bufferView->buffer->EncodedRegion_Current != nullptr)
562	{
563		const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset;
564		const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length;
565
566		if((offset >= begin) && (offset < end))
567			return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin];
568	}
569
570	return basePtr + offset;
571}
572
573namespace {
574    inline void CopyData(size_t count,
575            const uint8_t* src, size_t src_stride,
576                  uint8_t* dst, size_t dst_stride)
577    {
578        if (src_stride == dst_stride) {
579            memcpy(dst, src, count * src_stride);
580        }
581        else {
582            size_t sz = std::min(src_stride, dst_stride);
583            for (size_t i = 0; i < count; ++i) {
584                memcpy(dst, src, sz);
585                if (sz < dst_stride) {
586                    memset(dst + sz, 0, dst_stride - sz);
587                }
588                src += src_stride;
589                dst += dst_stride;
590            }
591        }
592    }
593}
594
595template<class T>
596bool Accessor::ExtractData(T*& outData)
597{
598    uint8_t* data = GetPointer();
599    if (!data) return false;
600
601    const size_t elemSize = GetElementSize();
602    const size_t totalSize = elemSize * count;
603
604    const size_t stride = bufferView && bufferView->byteStride ? bufferView->byteStride : elemSize;
605
606    const size_t targetElemSize = sizeof(T);
607    ai_assert(elemSize <= targetElemSize);
608
609    ai_assert(count*stride <= bufferView->byteLength);
610
611    outData = new T[count];
612    if (stride == elemSize && targetElemSize == elemSize) {
613        memcpy(outData, data, totalSize);
614    }
615    else {
616        for (size_t i = 0; i < count; ++i) {
617            memcpy(outData + i, data + i*stride, elemSize);
618        }
619    }
620
621    return true;
622}
623
624inline void Accessor::WriteData(size_t count, const void* src_buffer, size_t src_stride)
625{
626    uint8_t* buffer_ptr = bufferView->buffer->GetPointer();
627    size_t offset = byteOffset + bufferView->byteOffset;
628
629    size_t dst_stride = GetNumComponents() * GetBytesPerComponent();
630
631    const uint8_t* src = reinterpret_cast<const uint8_t*>(src_buffer);
632    uint8_t*       dst = reinterpret_cast<      uint8_t*>(buffer_ptr + offset);
633
634    ai_assert(dst + count*dst_stride <= buffer_ptr + bufferView->buffer->byteLength);
635    CopyData(count, src, src_stride, dst, dst_stride);
636}
637
638
639
640inline Accessor::Indexer::Indexer(Accessor& acc)
641    : accessor(acc)
642    , data(acc.GetPointer())
643    , elemSize(acc.GetElementSize())
644    , stride(acc.bufferView && acc.bufferView->byteStride ? acc.bufferView->byteStride : elemSize)
645{
646
647}
648
649//! Accesses the i-th value as defined by the accessor
650template<class T>
651T Accessor::Indexer::GetValue(int i)
652{
653    ai_assert(data);
654    ai_assert(i*stride < accessor.bufferView->byteLength);
655    T value = T();
656    memcpy(&value, data + i*stride, elemSize);
657    //value >>= 8 * (sizeof(T) - elemSize);
658    return value;
659}
660
661inline Image::Image()
662    : width(0)
663    , height(0)
664    , mData(0)
665    , mDataLength(0)
666{
667
668}
669
670inline void Image::Read(Value& obj, Asset& /*r*/)
671{
672    if (!mDataLength) {
673        if (Value* uri = FindString(obj, "uri")) {
674            const char* uristr = uri->GetString();
675
676            Util::DataURI dataURI;
677            if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) {
678                mimeType = dataURI.mediaType;
679                if (dataURI.base64) {
680                    mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, mData);
681                }
682            }
683            else {
684                this->uri = uristr;
685            }
686        }
687    }
688}
689
690inline uint8_t* Image::StealData()
691{
692    uint8_t* data = mData;
693    mDataLength = 0;
694    mData = 0;
695    return data;
696}
697
698inline void Image::SetData(uint8_t* data, size_t length, Asset& r)
699{
700    Ref<Buffer> b = r.GetBodyBuffer();
701    if (b) { // binary file: append to body
702        std::string bvId = r.FindUniqueID(this->id, "imgdata");
703        bufferView = r.bufferViews.Create(bvId);
704
705        bufferView->buffer = b;
706        bufferView->byteLength = length;
707        bufferView->byteOffset = b->AppendData(data, length);
708    }
709    else { // text file: will be stored as a data uri
710        this->mData = data;
711        this->mDataLength = length;
712    }
713}
714
715inline void Sampler::Read(Value& obj, Asset& /*r*/)
716{
717    SetDefaults();
718
719    ReadMember(obj, "name", name);
720    ReadMember(obj, "magFilter", magFilter);
721    ReadMember(obj, "minFilter", minFilter);
722    ReadMember(obj, "wrapS", wrapS);
723    ReadMember(obj, "wrapT", wrapT);
724}
725
726inline void Sampler::SetDefaults()
727{
728    //only wrapping modes have defaults
729    wrapS = SamplerWrap::Repeat;
730    wrapT = SamplerWrap::Repeat;
731    magFilter = SamplerMagFilter::UNSET;
732    minFilter = SamplerMinFilter::UNSET;
733}
734
735inline void Texture::Read(Value& obj, Asset& r)
736{
737    if (Value* sourceVal = FindUInt(obj, "source")) {
738        source = r.images.Retrieve(sourceVal->GetUint());
739    }
740
741    if (Value* samplerVal = FindUInt(obj, "sampler")) {
742        sampler = r.samplers.Retrieve(samplerVal->GetUint());
743    }
744}
745
746namespace {
747    inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out)
748    {
749        if (Value* index = FindUInt(*prop, "index")) {
750            out.texture = r.textures.Retrieve(index->GetUint());
751        }
752
753        if (Value* texcoord = FindUInt(*prop, "texCoord")) {
754            out.texCoord = texcoord->GetUint();
755        }
756    }
757
758    inline void ReadTextureProperty(Asset& r, Value& vals, const char* propName, TextureInfo& out)
759    {
760        if (Value* prop = FindMember(vals, propName)) {
761            SetTextureProperties(r, prop, out);
762        }
763    }
764
765    inline void ReadTextureProperty(Asset& r, Value& vals, const char* propName, NormalTextureInfo& out)
766    {
767        if (Value* prop = FindMember(vals, propName)) {
768            SetTextureProperties(r, prop, out);
769
770            if (Value* scale = FindNumber(*prop, "scale")) {
771                out.scale = static_cast<float>(scale->GetDouble());
772            }
773        }
774    }
775
776    inline void ReadTextureProperty(Asset& r, Value& vals, const char* propName, OcclusionTextureInfo& out)
777    {
778        if (Value* prop = FindMember(vals, propName)) {
779            SetTextureProperties(r, prop, out);
780
781            if (Value* strength = FindNumber(*prop, "strength")) {
782                out.strength = static_cast<float>(strength->GetDouble());
783            }
784        }
785    }
786}
787
788inline void Material::Read(Value& material, Asset& r)
789{
790    SetDefaults();
791
792    if (Value* pbrMetallicRoughness = FindObject(material, "pbrMetallicRoughness")) {
793        ReadMember(*pbrMetallicRoughness, "baseColorFactor", this->pbrMetallicRoughness.baseColorFactor);
794        ReadTextureProperty(r, *pbrMetallicRoughness, "baseColorTexture", this->pbrMetallicRoughness.baseColorTexture);
795        ReadTextureProperty(r, *pbrMetallicRoughness, "metallicRoughnessTexture", this->pbrMetallicRoughness.metallicRoughnessTexture);
796        ReadMember(*pbrMetallicRoughness, "metallicFactor", this->pbrMetallicRoughness.metallicFactor);
797        ReadMember(*pbrMetallicRoughness, "roughnessFactor", this->pbrMetallicRoughness.roughnessFactor);
798    }
799
800    ReadTextureProperty(r, material, "normalTexture", this->normalTexture);
801    ReadTextureProperty(r, material, "occlusionTexture", this->occlusionTexture);
802    ReadTextureProperty(r, material, "emissiveTexture", this->emissiveTexture);
803    ReadMember(material, "emissiveFactor", this->emissiveFactor);
804
805    ReadMember(material, "doubleSided", this->doubleSided);
806    ReadMember(material, "alphaMode", this->alphaMode);
807    ReadMember(material, "alphaCutoff", this->alphaCutoff);
808
809    if (Value* extensions = FindObject(material, "extensions")) {
810        if (r.extensionsUsed.KHR_materials_pbrSpecularGlossiness) {
811            if (Value* pbrSpecularGlossiness = FindObject(*extensions, "KHR_materials_pbrSpecularGlossiness")) {
812                PbrSpecularGlossiness pbrSG;
813
814                ReadMember(*pbrSpecularGlossiness, "diffuseFactor", pbrSG.diffuseFactor);
815                ReadTextureProperty(r, *pbrSpecularGlossiness, "diffuseTexture", pbrSG.diffuseTexture);
816                ReadTextureProperty(r, *pbrSpecularGlossiness, "specularGlossinessTexture", pbrSG.specularGlossinessTexture);
817                ReadMember(*pbrSpecularGlossiness, "specularFactor", pbrSG.specularFactor);
818                ReadMember(*pbrSpecularGlossiness, "glossinessFactor", pbrSG.glossinessFactor);
819
820                this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG);
821            }
822        }
823    }
824}
825
826namespace {
827    void SetVector(vec4& v, const float(&in)[4])
828        { v[0] = in[0]; v[1] = in[1]; v[2] = in[2]; v[3] = in[3]; }
829
830    void SetVector(vec3& v, const float(&in)[3])
831        { v[0] = in[0]; v[1] = in[1]; v[2] = in[2]; }
832}
833
834inline void Material::SetDefaults()
835{
836    //pbr materials
837    SetVector(pbrMetallicRoughness.baseColorFactor, defaultBaseColor);
838    pbrMetallicRoughness.metallicFactor = 1.0;
839    pbrMetallicRoughness.roughnessFactor = 1.0;
840
841    SetVector(emissiveFactor, defaultEmissiveFactor);
842    alphaMode = "OPAQUE";
843    alphaCutoff = 0.5;
844    doubleSided = false;
845}
846
847inline void PbrSpecularGlossiness::SetDefaults()
848{
849    //pbrSpecularGlossiness properties
850    SetVector(diffuseFactor, defaultDiffuseFactor);
851    SetVector(specularFactor, defaultSpecularFactor);
852    glossinessFactor = 1.0;
853}
854
855namespace {
856
857    template<int N>
858    inline int Compare(const char* attr, const char (&str)[N]) {
859        return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0;
860    }
861
862    inline bool GetAttribVector(Mesh::Primitive& p, const char* attr, Mesh::AccessorList*& v, int& pos)
863    {
864        if ((pos = Compare(attr, "POSITION"))) {
865            v = &(p.attributes.position);
866        }
867        else if ((pos = Compare(attr, "NORMAL"))) {
868            v = &(p.attributes.normal);
869        }
870        else if ((pos = Compare(attr, "TANGENT"))) {
871            v = &(p.attributes.tangent);
872        }
873        else if ((pos = Compare(attr, "TEXCOORD"))) {
874            v = &(p.attributes.texcoord);
875        }
876        else if ((pos = Compare(attr, "COLOR"))) {
877            v = &(p.attributes.color);
878        }
879        else if ((pos = Compare(attr, "JOINT"))) {
880            v = &(p.attributes.joint);
881        }
882        else if ((pos = Compare(attr, "JOINTMATRIX"))) {
883            v = &(p.attributes.jointmatrix);
884        }
885        else if ((pos = Compare(attr, "WEIGHT"))) {
886            v = &(p.attributes.weight);
887        }
888        else return false;
889        return true;
890    }
891}
892
893inline void Mesh::Read(Value& pJSON_Object, Asset& pAsset_Root)
894{
895    if (Value* name = FindMember(pJSON_Object, "name")) {
896        this->name = name->GetString();
897    }
898
899	/****************** Mesh primitives ******************/
900	if (Value* primitives = FindArray(pJSON_Object, "primitives")) {
901        this->primitives.resize(primitives->Size());
902        for (unsigned int i = 0; i < primitives->Size(); ++i) {
903            Value& primitive = (*primitives)[i];
904
905            Primitive& prim = this->primitives[i];
906            prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES);
907
908            if (Value* attrs = FindObject(primitive, "attributes")) {
909                for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) {
910                    if (!it->value.IsUint()) continue;
911                    const char* attr = it->name.GetString();
912                    // Valid attribute semantics include POSITION, NORMAL, TANGENT, TEXCOORD, COLOR, JOINT, JOINTMATRIX,
913                    // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc.
914
915                    int undPos = 0;
916                    Mesh::AccessorList* vec = 0;
917                    if (GetAttribVector(prim, attr, vec, undPos)) {
918                        size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0;
919                        if ((*vec).size() <= idx) (*vec).resize(idx + 1);
920						(*vec)[idx] = pAsset_Root.accessors.Retrieve(it->value.GetUint());
921                    }
922                }
923            }
924
925            if (Value* indices = FindUInt(primitive, "indices")) {
926				prim.indices = pAsset_Root.accessors.Retrieve(indices->GetUint());
927            }
928
929            if (Value* material = FindUInt(primitive, "material")) {
930				prim.material = pAsset_Root.materials.Retrieve(material->GetUint());
931            }
932        }
933    }
934}
935
936inline void Camera::Read(Value& obj, Asset& /*r*/)
937{
938    type = MemberOrDefault(obj, "type", Camera::Perspective);
939
940    const char* subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective";
941
942    Value* it = FindObject(obj, subobjId);
943    if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters");
944
945    if (type == Camera::Perspective) {
946        cameraProperties.perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f);
947        cameraProperties.perspective.yfov        = MemberOrDefault(*it, "yfov", 3.1415f/2.f);
948        cameraProperties.perspective.zfar        = MemberOrDefault(*it, "zfar", 100.f);
949        cameraProperties.perspective.znear       = MemberOrDefault(*it, "znear", 0.01f);
950    }
951    else {
952        cameraProperties.ortographic.xmag  = MemberOrDefault(obj, "xmag", 1.f);
953        cameraProperties.ortographic.ymag  = MemberOrDefault(obj, "ymag", 1.f);
954        cameraProperties.ortographic.zfar  = MemberOrDefault(obj, "zfar", 100.f);
955        cameraProperties.ortographic.znear = MemberOrDefault(obj, "znear", 0.01f);
956    }
957}
958
959inline void Node::Read(Value& obj, Asset& r)
960{
961
962    if (Value* children = FindArray(obj, "children")) {
963        this->children.reserve(children->Size());
964        for (unsigned int i = 0; i < children->Size(); ++i) {
965            Value& child = (*children)[i];
966            if (child.IsUint()) {
967                // get/create the child node
968                Ref<Node> chn = r.nodes.Retrieve(child.GetUint());
969                if (chn) this->children.push_back(chn);
970            }
971        }
972    }
973
974    if (Value* matrix = FindArray(obj, "matrix")) {
975        ReadValue(*matrix, this->matrix);
976    }
977    else {
978        ReadMember(obj, "translation", translation);
979        ReadMember(obj, "scale", scale);
980        ReadMember(obj, "rotation", rotation);
981    }
982
983    if (Value* mesh = FindUInt(obj, "mesh")) {
984        unsigned numMeshes = 1;
985
986        this->meshes.reserve(numMeshes);
987
988        Ref<Mesh> meshRef = r.meshes.Retrieve((*mesh).GetUint());
989
990        if (meshRef) this->meshes.push_back(meshRef);
991    }
992
993    if (Value* camera = FindUInt(obj, "camera")) {
994        this->camera = r.cameras.Retrieve(camera->GetUint());
995        if (this->camera)
996            this->camera->id = this->id;
997    }
998}
999
1000inline void Scene::Read(Value& obj, Asset& r)
1001{
1002    if (Value* array = FindArray(obj, "nodes")) {
1003        for (unsigned int i = 0; i < array->Size(); ++i) {
1004            if (!(*array)[i].IsUint()) continue;
1005            Ref<Node> node = r.nodes.Retrieve((*array)[i].GetUint());
1006            if (node)
1007                this->nodes.push_back(node);
1008        }
1009    }
1010}
1011
1012inline void AssetMetadata::Read(Document& doc)
1013{
1014    if (Value* obj = FindObject(doc, "asset")) {
1015        ReadMember(*obj, "copyright", copyright);
1016        ReadMember(*obj, "generator", generator);
1017
1018        if (Value* versionString = FindString(*obj, "version")) {
1019            version = versionString->GetString();
1020        } else if (Value* versionNumber = FindNumber (*obj, "version")) {
1021            char buf[4];
1022
1023            ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble());
1024
1025            version = buf;
1026        }
1027
1028        if (Value* profile = FindObject(*obj, "profile")) {
1029            ReadMember(*profile, "api",     this->profile.api);
1030            ReadMember(*profile, "version", this->profile.version);
1031        }
1032    }
1033
1034    if (version.empty() || version[0] != '2') {
1035        throw DeadlyImportError("GLTF: Unsupported glTF version: " + version);
1036    }
1037}
1038
1039//
1040// Asset methods implementation
1041//
1042
1043inline void Asset::ReadBinaryHeader(IOStream& stream, std::vector<char>& sceneData)
1044{
1045    GLB_Header header;
1046    if (stream.Read(&header, sizeof(header), 1) != 1) {
1047        throw DeadlyImportError("GLTF: Unable to read the file header");
1048    }
1049
1050    if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) {
1051        throw DeadlyImportError("GLTF: Invalid binary glTF file");
1052    }
1053
1054    AI_SWAP4(header.version);
1055    asset.version = to_string(header.version);
1056    if (header.version != 2) {
1057        throw DeadlyImportError("GLTF: Unsupported binary glTF version");
1058    }
1059
1060    GLB_Chunk chunk;
1061    if (stream.Read(&chunk, sizeof(chunk), 1) != 1) {
1062        throw DeadlyImportError("GLTF: Unable to read JSON chunk");
1063    }
1064
1065    AI_SWAP4(chunk.chunkLength);
1066    AI_SWAP4(chunk.chunkType);
1067
1068    if (chunk.chunkType != ChunkType_JSON) {
1069        throw DeadlyImportError("GLTF: JSON chunk missing");
1070    }
1071
1072    // read the scene data
1073
1074    mSceneLength = chunk.chunkLength;
1075    sceneData.resize(mSceneLength + 1);
1076    sceneData[mSceneLength] = '\0';
1077
1078    if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) {
1079        throw DeadlyImportError("GLTF: Could not read the file contents");
1080    }
1081
1082    uint32_t padding = ((chunk.chunkLength + 3) & ~3) - chunk.chunkLength;
1083    if (padding > 0) {
1084        stream.Seek(padding, aiOrigin_CUR);
1085    }
1086
1087    AI_SWAP4(header.length);
1088    mBodyOffset = 12 + 8 + chunk.chunkLength + padding + 8;
1089    if (header.length >= mBodyOffset) {
1090        if (stream.Read(&chunk, sizeof(chunk), 1) != 1) {
1091            throw DeadlyImportError("GLTF: Unable to read BIN chunk");
1092        }
1093
1094        AI_SWAP4(chunk.chunkLength);
1095        AI_SWAP4(chunk.chunkType);
1096
1097        if (chunk.chunkType != ChunkType_BIN) {
1098            throw DeadlyImportError("GLTF: BIN chunk missing");
1099        }
1100
1101        mBodyLength = chunk.chunkLength;
1102    }
1103    else {
1104        mBodyOffset = mBodyLength = 0;
1105    }
1106}
1107
1108inline void Asset::Load(const std::string& pFile, bool isBinary)
1109{
1110    mCurrentAssetDir.clear();
1111    int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\')));
1112    if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);
1113
1114    shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true));
1115    if (!stream) {
1116        throw DeadlyImportError("GLTF: Could not open file for reading");
1117    }
1118
1119    // is binary? then read the header
1120    std::vector<char> sceneData;
1121    if (isBinary) {
1122        SetAsBinary(); // also creates the body buffer
1123        ReadBinaryHeader(*stream, sceneData);
1124    }
1125    else {
1126        mSceneLength = stream->FileSize();
1127        mBodyLength = 0;
1128
1129
1130        // read the scene data
1131
1132        sceneData.resize(mSceneLength + 1);
1133        sceneData[mSceneLength] = '\0';
1134
1135        if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) {
1136            throw DeadlyImportError("GLTF: Could not read the file contents");
1137        }
1138    }
1139
1140
1141    // parse the JSON document
1142
1143    Document doc;
1144    doc.ParseInsitu(&sceneData[0]);
1145
1146    if (doc.HasParseError()) {
1147        char buffer[32];
1148        ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset()));
1149        throw DeadlyImportError(std::string("GLTF: JSON parse error, offset ") + buffer + ": "
1150            + GetParseError_En(doc.GetParseError()));
1151    }
1152
1153    if (!doc.IsObject()) {
1154        throw DeadlyImportError("GLTF: JSON document root must be a JSON object");
1155    }
1156
1157    // Fill the buffer instance for the current file embedded contents
1158    if (mBodyLength > 0) {
1159        if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) {
1160            throw DeadlyImportError("GLTF: Unable to read gltf file");
1161        }
1162    }
1163
1164
1165    // Load the metadata
1166    asset.Read(doc);
1167    ReadExtensionsUsed(doc);
1168
1169    // Prepare the dictionaries
1170    for (size_t i = 0; i < mDicts.size(); ++i) {
1171        mDicts[i]->AttachToDocument(doc);
1172    }
1173
1174    // Read the "scene" property, which specifies which scene to load
1175    // and recursively load everything referenced by it
1176    if (Value* scene = FindUInt(doc, "scene")) {
1177        unsigned int sceneIndex = scene->GetUint();
1178
1179        Ref<Scene> s = scenes.Retrieve(sceneIndex);
1180
1181        this->scene = s;
1182    }
1183
1184    // Clean up
1185    for (size_t i = 0; i < mDicts.size(); ++i) {
1186        mDicts[i]->DetachFromDocument();
1187    }
1188}
1189
1190inline void Asset::SetAsBinary()
1191{
1192    if (!mBodyBuffer) {
1193        mBodyBuffer = buffers.Create("binary_glTF");
1194        mBodyBuffer->MarkAsSpecial();
1195    }
1196}
1197
1198
1199inline void Asset::ReadExtensionsUsed(Document& doc)
1200{
1201    Value* extsUsed = FindArray(doc, "extensionsUsed");
1202    if (!extsUsed) return;
1203
1204    std::gltf_unordered_map<std::string, bool> exts;
1205
1206    for (unsigned int i = 0; i < extsUsed->Size(); ++i) {
1207        if ((*extsUsed)[i].IsString()) {
1208            exts[(*extsUsed)[i].GetString()] = true;
1209        }
1210    }
1211
1212    #define CHECK_EXT(EXT) \
1213        if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true;
1214
1215    CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
1216
1217    #undef CHECK_EXT
1218}
1219
1220inline IOStream* Asset::OpenFile(std::string path, const char* mode, bool /*absolute*/)
1221{
1222    #ifdef ASSIMP_API
1223        return mIOSystem->Open(path, mode);
1224    #else
1225        if (path.size() < 2) return 0;
1226        if (!absolute && path[1] != ':' && path[0] != '/') { // relative?
1227            path = mCurrentAssetDir + path;
1228        }
1229        FILE* f = fopen(path.c_str(), mode);
1230        return f ? new IOStream(f) : 0;
1231    #endif
1232}
1233
1234inline std::string Asset::FindUniqueID(const std::string& str, const char* suffix)
1235{
1236    std::string id = str;
1237
1238    if (!id.empty()) {
1239        if (mUsedIds.find(id) == mUsedIds.end())
1240            return id;
1241
1242        id += "_";
1243    }
1244
1245    id += suffix;
1246
1247    Asset::IdMap::iterator it = mUsedIds.find(id);
1248    if (it == mUsedIds.end())
1249        return id;
1250
1251    std::vector<char> buffer;
1252    buffer.resize(id.size() + 16);
1253    int offset = ai_snprintf(buffer.data(), buffer.size(), "%s_", id.c_str());
1254    for (int i = 0; it != mUsedIds.end(); ++i) {
1255        ai_snprintf(buffer.data() + offset, buffer.size() - offset, "%d", i);
1256        id = buffer.data();
1257        it = mUsedIds.find(id);
1258    }
1259
1260    return id;
1261}
1262
1263namespace Util {
1264
1265    inline
1266    bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) {
1267        if ( NULL == const_uri ) {
1268            return false;
1269        }
1270
1271        if (const_uri[0] != 0x10) { // we already parsed this uri?
1272            if (strncmp(const_uri, "data:", 5) != 0) // not a data uri?
1273                return false;
1274        }
1275
1276        // set defaults
1277        out.mediaType = "text/plain";
1278        out.charset = "US-ASCII";
1279        out.base64 = false;
1280
1281        char* uri = const_cast<char*>(const_uri);
1282        if (uri[0] != 0x10) {
1283            uri[0] = 0x10;
1284            uri[1] = uri[2] = uri[3] = uri[4] = 0;
1285
1286            size_t i = 5, j;
1287            if (uri[i] != ';' && uri[i] != ',') { // has media type?
1288                uri[1] = char(i);
1289                for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
1290                    // nothing to do!
1291                }
1292            }
1293            while (uri[i] == ';' && i < uriLen) {
1294                uri[i++] = '\0';
1295                for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
1296                    // nothing to do!
1297                }
1298
1299                if ( strncmp( uri + j, "charset=", 8 ) == 0 ) {
1300                    uri[2] = char(j + 8);
1301                } else if ( strncmp( uri + j, "base64", 6 ) == 0 ) {
1302                    uri[3] = char(j);
1303                }
1304            }
1305            if (i < uriLen) {
1306                uri[i++] = '\0';
1307                uri[4] = char(i);
1308            } else {
1309                uri[1] = uri[2] = uri[3] = 0;
1310                uri[4] = 5;
1311            }
1312        }
1313
1314        if ( uri[ 1 ] != 0 ) {
1315            out.mediaType = uri + uri[ 1 ];
1316        }
1317        if ( uri[ 2 ] != 0 ) {
1318            out.charset = uri + uri[ 2 ];
1319        }
1320        if ( uri[ 3 ] != 0 ) {
1321            out.base64 = true;
1322        }
1323        out.data = uri + uri[4];
1324        out.dataLength = (uri + uriLen) - out.data;
1325
1326        return true;
1327    }
1328
1329    template<bool B>
1330    struct DATA
1331    {
1332        static const uint8_t tableDecodeBase64[128];
1333    };
1334
1335    template<bool B>
1336    const uint8_t DATA<B>::tableDecodeBase64[128] = {
1337         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1338         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1339         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62,  0,  0,  0, 63,
1340        52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0, 64,  0,  0,
1341         0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
1342        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0,  0,
1343         0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
1344        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  0,  0,  0,  0,  0
1345    };
1346
1347    inline char EncodeCharBase64(uint8_t b)
1348    {
1349        return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)];
1350    }
1351
1352    inline uint8_t DecodeCharBase64(char c)
1353    {
1354        return DATA<true>::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs?
1355        /*if (c >= 'A' && c <= 'Z') return c - 'A';
1356        if (c >= 'a' && c <= 'z') return c - 'a' + 26;
1357        if (c >= '0' && c <= '9') return c - '0' + 52;
1358        if (c == '+') return 62;
1359        if (c == '/') return 63;
1360        return 64; // '-' */
1361    }
1362
1363    inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out)
1364    {
1365        ai_assert(inLength % 4 == 0);
1366
1367        if (inLength < 4) {
1368            out = 0;
1369            return 0;
1370        }
1371
1372        int nEquals = int(in[inLength - 1] == '=') +
1373                      int(in[inLength - 2] == '=');
1374
1375        size_t outLength = (inLength * 3) / 4 - nEquals;
1376        out = new uint8_t[outLength];
1377        memset(out, 0, outLength);
1378
1379        size_t i, j = 0;
1380
1381        for (i = 0; i + 4 < inLength; i += 4) {
1382            uint8_t b0 = DecodeCharBase64(in[i]);
1383            uint8_t b1 = DecodeCharBase64(in[i + 1]);
1384            uint8_t b2 = DecodeCharBase64(in[i + 2]);
1385            uint8_t b3 = DecodeCharBase64(in[i + 3]);
1386
1387            out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4));
1388            out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2));
1389            out[j++] = (uint8_t)((b2 << 6) | b3);
1390        }
1391
1392        {
1393            uint8_t b0 = DecodeCharBase64(in[i]);
1394            uint8_t b1 = DecodeCharBase64(in[i + 1]);
1395            uint8_t b2 = DecodeCharBase64(in[i + 2]);
1396            uint8_t b3 = DecodeCharBase64(in[i + 3]);
1397
1398            out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4));
1399            if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2));
1400            if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3);
1401        }
1402
1403        return outLength;
1404    }
1405
1406
1407
1408    inline void EncodeBase64(
1409        const uint8_t* in, size_t inLength,
1410        std::string& out)
1411    {
1412        size_t outLength = ((inLength + 2) / 3) * 4;
1413
1414        size_t j = out.size();
1415        out.resize(j + outLength);
1416
1417        for (size_t i = 0; i <  inLength; i += 3) {
1418            uint8_t b = (in[i] & 0xFC) >> 2;
1419            out[j++] = EncodeCharBase64(b);
1420
1421            b = (in[i] & 0x03) << 4;
1422            if (i + 1 < inLength) {
1423                b |= (in[i + 1] & 0xF0) >> 4;
1424                out[j++] = EncodeCharBase64(b);
1425
1426                b = (in[i + 1] & 0x0F) << 2;
1427                if (i + 2 < inLength) {
1428                    b |= (in[i + 2] & 0xC0) >> 6;
1429                    out[j++] = EncodeCharBase64(b);
1430
1431                    b = in[i + 2] & 0x3F;
1432                    out[j++] = EncodeCharBase64(b);
1433                }
1434                else {
1435                    out[j++] = EncodeCharBase64(b);
1436                    out[j++] = '=';
1437                }
1438            }
1439            else {
1440                out[j++] = EncodeCharBase64(b);
1441                out[j++] = '=';
1442                out[j++] = '=';
1443            }
1444        }
1445    }
1446
1447}
1448
1449} // ns glTF
1450