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