1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2024, 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 "AssetLib/glTF/glTFCommon.h"
43
44#include <assimp/MemoryIOWrapper.h>
45#include <assimp/StringUtils.h>
46#include <assimp/DefaultLogger.hpp>
47#include <assimp/Base64.hpp>
48#include <rapidjson/document.h>
49#include <rapidjson/schema.h>
50#include <rapidjson/stringbuffer.h>
51
52// clang-format off
53#ifdef ASSIMP_ENABLE_DRACO
54
55// Google draco library headers spew many warnings. Bad Google, no cookie
56# if _MSC_VER
57# pragma warning(push)
58# pragma warning(disable : 4018) // Signed/unsigned mismatch
59# pragma warning(disable : 4804) // Unsafe use of type 'bool'
60# elif defined(__clang__)
61# pragma clang diagnostic push
62# pragma clang diagnostic ignored "-Wsign-compare"
63# elif defined(__GNUC__)
64# pragma GCC diagnostic push
65# if (__GNUC__ > 4)
66# pragma GCC diagnostic ignored "-Wbool-compare"
67# endif
68# pragma GCC diagnostic ignored "-Wsign-compare"
69#endif
70
71#include "draco/compression/decode.h"
72#include "draco/core/decoder_buffer.h"
73
74#if _MSC_VER
75# pragma warning(pop)
76#elif defined(__clang__)
77# pragma clang diagnostic pop
78#elif defined(__GNUC__)
79# pragma GCC diagnostic pop
80#endif
81#ifndef DRACO_MESH_COMPRESSION_SUPPORTED
82# error glTF: KHR_draco_mesh_compression: draco library must have DRACO_MESH_COMPRESSION_SUPPORTED
83#endif
84#endif
85// clang-format on
86
87using namespace Assimp;
88
89namespace glTF2 {
90using glTFCommon::FindStringInContext;
91using glTFCommon::FindNumberInContext;
92using glTFCommon::FindUIntInContext;
93using glTFCommon::FindArrayInContext;
94using glTFCommon::FindObjectInContext;
95using glTFCommon::FindExtensionInContext;
96using glTFCommon::MemberOrDefault;
97using glTFCommon::ReadMember;
98using glTFCommon::FindMember;
99using glTFCommon::FindObject;
100using glTFCommon::FindUInt;
101using glTFCommon::FindArray;
102using glTFCommon::FindArray;
103
104namespace {
105
106//
107// JSON Value reading helpers
108//
109inline CustomExtension ReadExtensions(const char *name, Value &obj) {
110 CustomExtension ret;
111 ret.name = name;
112 if (obj.IsObject()) {
113 ret.mValues.isPresent = true;
114 for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) {
115 auto &val = it->value;
116 ret.mValues.value.push_back(x: ReadExtensions(name: it->name.GetString(), obj&: val));
117 }
118 } else if (obj.IsArray()) {
119 ret.mValues.value.reserve(n: obj.Size());
120 ret.mValues.isPresent = true;
121 for (unsigned int i = 0; i < obj.Size(); ++i) {
122 ret.mValues.value.push_back(x: ReadExtensions(name, obj&: obj[i]));
123 }
124 } else if (obj.IsNumber()) {
125 if (obj.IsUint64()) {
126 ret.mUint64Value.value = obj.GetUint64();
127 ret.mUint64Value.isPresent = true;
128 } else if (obj.IsInt64()) {
129 ret.mInt64Value.value = obj.GetInt64();
130 ret.mInt64Value.isPresent = true;
131 } else if (obj.IsDouble()) {
132 ret.mDoubleValue.value = obj.GetDouble();
133 ret.mDoubleValue.isPresent = true;
134 }
135 } else if (obj.IsString()) {
136 ReadValue(val&: obj, out&: ret.mStringValue);
137 ret.mStringValue.isPresent = true;
138 } else if (obj.IsBool()) {
139 ret.mBoolValue.value = obj.GetBool();
140 ret.mBoolValue.isPresent = true;
141 }
142 return ret;
143}
144
145inline Extras ReadExtras(Value &obj) {
146 Extras ret;
147
148 ret.mValues.reserve(n: obj.MemberCount());
149 for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) {
150 auto &val = it->value;
151 ret.mValues.emplace_back(args: ReadExtensions(name: it->name.GetString(), obj&: val));
152 }
153
154 return ret;
155}
156
157inline void CopyData(size_t count, const uint8_t *src, size_t src_stride,
158 uint8_t *dst, size_t dst_stride) {
159 if (src_stride == dst_stride) {
160 memcpy(dest: dst, src: src, n: count * src_stride);
161 return;
162 }
163
164 size_t sz = std::min(a: src_stride, b: dst_stride);
165 for (size_t i = 0; i < count; ++i) {
166 memcpy(dest: dst, src: src, n: sz);
167 if (sz < dst_stride) {
168 memset(s: dst + sz, c: 0, n: dst_stride - sz);
169 }
170 src += src_stride;
171 dst += dst_stride;
172 }
173}
174
175void SetVector(vec4 &v, const float (&in)[4]) {
176 v[0] = in[0];
177 v[1] = in[1];
178 v[2] = in[2];
179 v[3] = in[3];
180}
181
182void SetVector(vec3 &v, const float (&in)[3]) {
183 v[0] = in[0];
184 v[1] = in[1];
185 v[2] = in[2];
186}
187
188template <int N>
189inline int Compare(const char *attr, const char (&str)[N]) {
190 return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0;
191}
192
193#if _MSC_VER
194#pragma warning(push)
195#pragma warning(disable : 4706)
196#endif // _MSC_VER
197
198inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) {
199 if ((pos = Compare(attr, str: "POSITION"))) {
200 v = &(p.attributes.position);
201 } else if ((pos = Compare(attr, str: "NORMAL"))) {
202 v = &(p.attributes.normal);
203 } else if ((pos = Compare(attr, str: "TANGENT"))) {
204 v = &(p.attributes.tangent);
205 } else if ((pos = Compare(attr, str: "TEXCOORD"))) {
206 v = &(p.attributes.texcoord);
207 } else if ((pos = Compare(attr, str: "COLOR"))) {
208 v = &(p.attributes.color);
209 } else if ((pos = Compare(attr, str: "JOINTS"))) {
210 v = &(p.attributes.joint);
211 } else if ((pos = Compare(attr, str: "JOINTMATRIX"))) {
212 v = &(p.attributes.jointmatrix);
213 } else if ((pos = Compare(attr, str: "WEIGHTS"))) {
214 v = &(p.attributes.weight);
215 } else
216 return false;
217 return true;
218}
219
220inline bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, const char *attr, Mesh::AccessorList *&v, int &pos) {
221 if ((pos = Compare(attr, str: "POSITION"))) {
222 v = &(p.targets[targetIndex].position);
223 } else if ((pos = Compare(attr, str: "NORMAL"))) {
224 v = &(p.targets[targetIndex].normal);
225 } else if ((pos = Compare(attr, str: "TANGENT"))) {
226 v = &(p.targets[targetIndex].tangent);
227 } else
228 return false;
229 return true;
230}
231
232} // namespace
233
234inline Value *Object::FindString(Value &val, const char *memberId) {
235 return FindStringInContext(val, memberId, context: id.c_str(), extraContext: name.c_str());
236}
237
238inline Value *Object::FindNumber(Value &val, const char *memberId) {
239 return FindNumberInContext(val, memberId, context: id.c_str(), extraContext: name.c_str());
240}
241
242inline Value *Object::FindUInt(Value &val, const char *memberId) {
243 return FindUIntInContext(val, memberId, context: id.c_str(), extraContext: name.c_str());
244}
245
246inline Value *Object::FindArray(Value &val, const char *memberId) {
247 return FindArrayInContext(val, memberId, context: id.c_str(), extraContext: name.c_str());
248}
249
250inline Value *Object::FindObject(Value &val, const char *memberId) {
251 return FindObjectInContext(val, memberId, context: id.c_str(), extraContext: name.c_str());
252}
253
254inline Value *Object::FindExtension(Value &val, const char *extensionId) {
255 return FindExtensionInContext(val, extensionId, context: id.c_str(), extraContext: name.c_str());
256}
257
258inline void Object::ReadExtensions(Value &val) {
259 if (Value *curExtensions = FindObject(val, memberId: "extensions")) {
260 this->customExtensions = glTF2::ReadExtensions(name: "extensions", obj&: *curExtensions);
261 }
262}
263
264inline void Object::ReadExtras(Value &val) {
265 if (Value *curExtras = FindObject(val, memberId: "extras")) {
266 this->extras = glTF2::ReadExtras(obj&: *curExtras);
267 }
268}
269
270#ifdef ASSIMP_ENABLE_DRACO
271
272template <typename T>
273inline void CopyFaceIndex_Draco(Buffer &decodedIndexBuffer, const draco::Mesh &draco_mesh) {
274 const size_t faceStride = sizeof(T) * 3;
275 for (draco::FaceIndex f(0); f < draco_mesh.num_faces(); ++f) {
276 const draco::Mesh::Face &face = draco_mesh.face(f);
277 T indices[3] = { static_cast<T>(face[0].value()), static_cast<T>(face[1].value()), static_cast<T>(face[2].value()) };
278 memcpy(decodedIndexBuffer.GetPointer() + (f.value() * faceStride), &indices[0], faceStride);
279 }
280}
281
282inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Primitive &prim) {
283 if (!prim.indices || dracoMesh.num_faces() == 0)
284 return;
285
286 // Create a decoded Index buffer (if there is one)
287 size_t componentBytes = prim.indices->GetBytesPerComponent();
288
289 std::unique_ptr<Buffer> decodedIndexBuffer(new Buffer());
290 decodedIndexBuffer->Grow(dracoMesh.num_faces() * 3 * componentBytes);
291
292 // If accessor uses the same size as draco implementation, copy the draco buffer directly
293
294 // Usually uint32_t but shouldn't assume
295 if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) {
296 memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength);
297 return;
298 }
299
300 // Not same size, convert
301 switch (componentBytes) {
302 case sizeof(uint32_t):
303 CopyFaceIndex_Draco<uint32_t>(*decodedIndexBuffer, dracoMesh);
304 break;
305 case sizeof(uint16_t):
306 CopyFaceIndex_Draco<uint16_t>(*decodedIndexBuffer, dracoMesh);
307 break;
308 case sizeof(uint8_t):
309 CopyFaceIndex_Draco<uint8_t>(*decodedIndexBuffer, dracoMesh);
310 break;
311 default:
312 ai_assert(false);
313 break;
314 }
315
316 // Assign this alternate data buffer to the accessor
317 prim.indices->decodedBuffer.swap(decodedIndexBuffer);
318}
319
320template <typename T>
321static bool GetAttributeForAllPoints_Draco(const draco::Mesh &dracoMesh,
322 const draco::PointAttribute &dracoAttribute,
323 Buffer &outBuffer) {
324 size_t byteOffset = 0;
325 T values[4] = { 0, 0, 0, 0 };
326 for (draco::PointIndex i(0); i < dracoMesh.num_points(); ++i) {
327 const draco::AttributeValueIndex val_index = dracoAttribute.mapped_index(i);
328 if (!dracoAttribute.ConvertValue<T>(val_index, dracoAttribute.num_components(), values)) {
329 return false;
330 }
331
332 memcpy(outBuffer.GetPointer() + byteOffset, &values[0], sizeof(T) * dracoAttribute.num_components());
333 byteOffset += sizeof(T) * dracoAttribute.num_components();
334 }
335
336 return true;
337}
338
339inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32_t dracoAttribId, Accessor &accessor) {
340 // Create decoded buffer
341 const draco::PointAttribute *pDracoAttribute = dracoMesh.GetAttributeByUniqueId(dracoAttribId);
342 if (pDracoAttribute == nullptr) {
343 throw DeadlyImportError("GLTF: Invalid draco attribute id: ", dracoAttribId);
344 }
345
346 size_t componentBytes = accessor.GetBytesPerComponent();
347
348 std::unique_ptr<Buffer> decodedAttribBuffer(new Buffer());
349 decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes);
350
351 switch (accessor.componentType) {
352 case ComponentType_BYTE:
353 GetAttributeForAllPoints_Draco<int8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
354 break;
355 case ComponentType_UNSIGNED_BYTE:
356 GetAttributeForAllPoints_Draco<uint8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
357 break;
358 case ComponentType_SHORT:
359 GetAttributeForAllPoints_Draco<int16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
360 break;
361 case ComponentType_UNSIGNED_SHORT:
362 GetAttributeForAllPoints_Draco<uint16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
363 break;
364 case ComponentType_UNSIGNED_INT:
365 GetAttributeForAllPoints_Draco<uint32_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
366 break;
367 case ComponentType_FLOAT:
368 GetAttributeForAllPoints_Draco<float>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
369 break;
370 default:
371 ai_assert(false);
372 break;
373 }
374
375 // Assign this alternate data buffer to the accessor
376 accessor.decodedBuffer.swap(decodedAttribBuffer);
377}
378
379#endif // ASSIMP_ENABLE_DRACO
380
381//
382// LazyDict methods
383//
384
385template <class T>
386inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) :
387 mDictId(dictId),
388 mExtId(extId),
389 mDict(nullptr),
390 mAsset(asset) {
391 asset.mDicts.push_back(this); // register to the list of dictionaries
392}
393
394template <class T>
395inline LazyDict<T>::~LazyDict() {
396 for (size_t i = 0; i < mObjs.size(); ++i) {
397 delete mObjs[i];
398 }
399}
400
401template <class T>
402inline void LazyDict<T>::AttachToDocument(Document &doc) {
403 Value *container = nullptr;
404 const char *context = nullptr;
405
406 if (mExtId) {
407 if (Value *exts = FindObject(doc, memberId: "extensions")) {
408 container = FindObjectInContext(val&: *exts, memberId: mExtId, context: "extensions");
409 context = mExtId;
410 }
411 } else {
412 container = &doc;
413 context = "the document";
414 }
415
416 if (container) {
417 mDict = FindArrayInContext(val&: *container, memberId: mDictId, context);
418 }
419}
420
421template <class T>
422inline void LazyDict<T>::DetachFromDocument() {
423 mDict = nullptr;
424}
425
426template <class T>
427unsigned int LazyDict<T>::Remove(const char *id) {
428 id = T::TranslateId(mAsset, id);
429
430 typename IdDict::iterator objIt = mObjsById.find(x: id);
431
432 if (objIt == mObjsById.end()) {
433 throw DeadlyExportError("GLTF: Object with id \"" + std::string(id) + "\" is not found");
434 }
435
436 const unsigned int index = objIt->second;
437
438 mAsset.mUsedIds[id] = false;
439 mObjsById.erase(x: id);
440 mObjsByOIndex.erase(x: index);
441 delete mObjs[index];
442 mObjs.erase(mObjs.begin() + index);
443
444 //update index of object in mObjs;
445 for (unsigned int i = index; i < mObjs.size(); ++i) {
446 T *obj = mObjs[i];
447
448 obj->index = i;
449 }
450
451 for (IdDict::iterator it = mObjsById.begin(); it != mObjsById.end(); ++it) {
452 if (it->second <= index) {
453 continue;
454 }
455
456 mObjsById[it->first] = it->second - 1;
457 }
458
459 for (Dict::iterator it = mObjsByOIndex.begin(); it != mObjsByOIndex.end(); ++it) {
460 if (it->second <= index) {
461 continue;
462 }
463
464 mObjsByOIndex[it->first] = it->second - 1;
465 }
466
467 return index;
468}
469
470template <class T>
471Ref<T> LazyDict<T>::Retrieve(unsigned int i) {
472
473 typename Dict::iterator it = mObjsByOIndex.find(x: i);
474 if (it != mObjsByOIndex.end()) { // already created?
475 return Ref<T>(mObjs, it->second);
476 }
477
478 // read it from the JSON object
479 if (!mDict) {
480 throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\"");
481 }
482
483 if (!mDict->IsArray()) {
484 throw DeadlyImportError("GLTF: Field \"", mDictId, "\" is not an array");
485 }
486
487 if (i >= mDict->Size()) {
488 throw DeadlyImportError("GLTF: Array index ", i, " is out of bounds (", mDict->Size(), ") for \"", mDictId, "\"");
489 }
490
491 Value &obj = (*mDict)[i];
492
493 if (!obj.IsObject()) {
494 throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" is not a JSON object");
495 }
496
497 if (mRecursiveReferenceCheck.find(x: i) != mRecursiveReferenceCheck.end()) {
498 throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" has recursive reference to itself");
499 }
500 mRecursiveReferenceCheck.insert(x: i);
501
502 // Unique ptr prevents memory leak in case of Read throws an exception
503 auto inst = std::unique_ptr<T>(new T());
504 // Try to make this human readable so it can be used in error messages.
505 inst->id = std::string(mDictId) + "[" + ai_to_string(value: i) + "]";
506 inst->oIndex = i;
507 ReadMember(obj, "name", inst->name);
508 inst->Read(obj, mAsset);
509 inst->ReadExtensions(obj);
510 inst->ReadExtras(obj);
511
512 Ref<T> result = Add(obj: inst.release());
513 mRecursiveReferenceCheck.erase(x: i);
514 return result;
515}
516
517template <class T>
518Ref<T> LazyDict<T>::Get(unsigned int i) {
519 return Ref<T>(mObjs, i);
520}
521
522template <class T>
523Ref<T> LazyDict<T>::Get(const char *id) {
524 id = T::TranslateId(mAsset, id);
525
526 typename IdDict::iterator it = mObjsById.find(x: id);
527 if (it != mObjsById.end()) { // already created?
528 return Ref<T>(mObjs, it->second);
529 }
530
531 return Ref<T>();
532}
533
534template <class T>
535Ref<T> LazyDict<T>::Add(T *obj) {
536 unsigned int idx = unsigned(mObjs.size());
537 mObjs.push_back(obj);
538 mObjsByOIndex[obj->oIndex] = idx;
539 mObjsById[obj->id] = idx;
540 mAsset.mUsedIds[obj->id] = true;
541 return Ref<T>(mObjs, idx);
542}
543
544template <class T>
545Ref<T> LazyDict<T>::Create(const char *id) {
546 Asset::IdMap::iterator it = mAsset.mUsedIds.find(x: id);
547 if (it != mAsset.mUsedIds.end()) {
548 throw DeadlyImportError("GLTF: two objects with the same ID exist");
549 }
550 T *inst = new T();
551 unsigned int idx = unsigned(mObjs.size());
552 inst->id = id;
553 inst->index = idx;
554 inst->oIndex = idx;
555 return Add(obj: inst);
556}
557
558//
559// glTF dictionary objects methods
560//
561inline Buffer::Buffer() :
562 byteLength(0),
563 type(Type_arraybuffer),
564 EncodedRegion_Current(nullptr),
565 mIsSpecial(false) {}
566
567inline Buffer::~Buffer() {
568 for (SEncodedRegion *reg : EncodedRegion_List)
569 delete reg;
570}
571
572inline const char *Buffer::TranslateId(Asset & /*r*/, const char *id) {
573 return id;
574}
575
576inline void Buffer::Read(Value &obj, Asset &r) {
577 size_t statedLength = MemberOrDefault<size_t>(obj, id: "byteLength", defaultValue: 0);
578 byteLength = statedLength;
579
580 Value *it = FindString(val&: obj, memberId: "uri");
581 if (!it) {
582 if (statedLength > 0) {
583 throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute");
584 }
585 return;
586 }
587
588 const char *uri = it->GetString();
589
590 glTFCommon::Util::DataURI dataURI;
591 if (ParseDataURI(const_uri: uri, uriLen: it->GetStringLength(), out&: dataURI)) {
592 if (dataURI.base64) {
593 uint8_t *data = nullptr;
594 this->byteLength = Base64::Decode(in: dataURI.data, inLength: dataURI.dataLength, out&: data);
595 this->mData.reset(p: data, d: std::default_delete<uint8_t[]>());
596
597 if (statedLength > 0 && this->byteLength != statedLength) {
598 throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(value: statedLength),
599 " bytes, but found ", ai_to_string(value: dataURI.dataLength));
600 }
601 } else { // assume raw data
602 if (statedLength != dataURI.dataLength) {
603 throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(value: statedLength),
604 " bytes, but found ", ai_to_string(value: dataURI.dataLength));
605 }
606
607 this->mData.reset(p: new uint8_t[dataURI.dataLength], d: std::default_delete<uint8_t[]>());
608 memcpy(dest: this->mData.get(), src: dataURI.data, n: dataURI.dataLength);
609 }
610 } else { // Local file
611 if (byteLength > 0) {
612 std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir.back() == '/' ? r.mCurrentAssetDir : r.mCurrentAssetDir + '/') : "";
613
614 IOStream *file = r.OpenFile(path: dir + uri, mode: "rb");
615 if (file) {
616 bool ok = LoadFromStream(stream&: *file, length: byteLength);
617 delete file;
618
619 if (!ok)
620 throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\"");
621 } else {
622 throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\"");
623 }
624 }
625 }
626}
627
628inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) {
629 byteLength = length ? length : stream.FileSize();
630
631 if (byteLength > stream.FileSize()) {
632 throw DeadlyImportError("GLTF: Invalid byteLength exceeds size of actual data.");
633 }
634
635 if (baseOffset) {
636 stream.Seek(pOffset: baseOffset, pOrigin: aiOrigin_SET);
637 }
638
639 mData.reset(p: new uint8_t[byteLength], d: std::default_delete<uint8_t[]>());
640
641 if (stream.Read(pvBuffer: mData.get(), pSize: byteLength, pCount: 1) != 1) {
642 return false;
643 }
644 return true;
645}
646
647inline 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) {
648 // Check pointer to data
649 if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided.");
650
651 // Check offset
652 if (pOffset > byteLength) {
653 const uint8_t val_size = 32;
654
655 char val[val_size];
656
657 ai_snprintf(s: val, maxlen: val_size, AI_SIZEFMT, pOffset);
658 throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region.");
659 }
660
661 // Check length
662 if ((pOffset + pEncodedData_Length) > byteLength) {
663 const uint8_t val_size = 64;
664
665 char val[val_size];
666
667 ai_snprintf(s: val, maxlen: val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length);
668 throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range.");
669 }
670
671 // Add new region
672 EncodedRegion_List.push_back(x: new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID));
673 // And set new value for "byteLength"
674 byteLength += (pDecodedData_Length - pEncodedData_Length);
675}
676
677inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) {
678 if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) {
679 return;
680 }
681
682 for (SEncodedRegion *reg : EncodedRegion_List) {
683 if (reg->ID == pID) {
684 EncodedRegion_Current = reg;
685 return;
686 }
687 }
688
689 throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found.");
690}
691
692inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) {
693
694 if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) {
695 return false;
696 }
697
698 const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count;
699 uint8_t *new_data = new uint8_t[new_data_size];
700 // Copy data which place before replacing part.
701 ::memcpy(dest: new_data, src: mData.get(), n: pBufferData_Offset);
702 // Copy new data.
703 ::memcpy(dest: &new_data[pBufferData_Offset], src: pReplace_Data, n: pReplace_Count);
704 // Copy data which place after replacing part.
705 ::memcpy(dest: &new_data[pBufferData_Offset + pReplace_Count], src: &mData.get()[pBufferData_Offset + pBufferData_Count], n: pBufferData_Offset);
706 // Apply new data
707 mData.reset(p: new_data, d: std::default_delete<uint8_t[]>());
708 byteLength = new_data_size;
709
710 return true;
711}
712
713inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) {
714 if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) {
715 return false;
716 }
717
718 const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count;
719 uint8_t *new_data = new uint8_t[new_data_size];
720 // Copy data which place before replacing part.
721 memcpy(dest: new_data, src: mData.get(), n: pBufferData_Offset);
722 // Copy new data.
723 memcpy(dest: &new_data[pBufferData_Offset], src: pReplace_Data, n: pReplace_Count);
724 // Copy data which place after replacing part.
725 memcpy(dest: &new_data[pBufferData_Offset + pReplace_Count], src: &mData.get()[pBufferData_Offset + pBufferData_Count], n: new_data_size - (pBufferData_Offset + pReplace_Count));
726 // Apply new data
727 mData.reset(p: new_data, d: std::default_delete<uint8_t[]>());
728 byteLength = new_data_size;
729
730 return true;
731}
732
733inline size_t Buffer::AppendData(uint8_t *data, size_t length) {
734 const size_t offset = this->byteLength;
735
736 // Force alignment to 4 bits
737 const size_t paddedLength = (length + 3) & ~3;
738 Grow(amount: paddedLength);
739 memcpy(dest: mData.get() + offset, src: data, n: length);
740 memset(s: mData.get() + offset + length, c: 0, n: paddedLength - length);
741 return offset;
742}
743
744inline void Buffer::Grow(size_t amount) {
745 if (amount <= 0) {
746 return;
747 }
748
749 // Capacity is big enough
750 if (capacity >= byteLength + amount) {
751 byteLength += amount;
752 return;
753 }
754
755 // Just allocate data which we need
756 capacity = byteLength + amount;
757
758 uint8_t *b = new uint8_t[capacity];
759 if (nullptr != mData) {
760 memcpy(dest: b, src: mData.get(), n: byteLength);
761 }
762 mData.reset(p: b, d: std::default_delete<uint8_t[]>());
763 byteLength += amount;
764}
765
766//
767// struct BufferView
768//
769inline void BufferView::Read(Value &obj, Asset &r) {
770 if (Value *bufferVal = FindUInt(val&: obj, memberId: "buffer")) {
771 buffer = r.buffers.Retrieve(i: bufferVal->GetUint());
772 }
773
774 if (!buffer) {
775 throw DeadlyImportError("GLTF: Buffer view without valid buffer.");
776 }
777
778 byteOffset = MemberOrDefault(obj, id: "byteOffset", defaultValue: size_t(0));
779 byteLength = MemberOrDefault(obj, id: "byteLength", defaultValue: size_t(0));
780 byteStride = MemberOrDefault(obj, id: "byteStride", defaultValue: 0u);
781
782 // Check length
783 if ((byteOffset + byteLength) > buffer->byteLength) {
784 throw DeadlyImportError("GLTF: Buffer view with offset/length (", byteOffset, "/", byteLength, ") is out of range.");
785 }
786}
787
788inline uint8_t *BufferView::GetPointerAndTailSize(size_t accOffset, size_t& outTailSize) {
789 if (!buffer) {
790 outTailSize = 0;
791 return nullptr;
792 }
793 uint8_t * const basePtr = buffer->GetPointer();
794 if (!basePtr) {
795 outTailSize = 0;
796 return nullptr;
797 }
798
799 size_t offset = accOffset + byteOffset;
800 if (buffer->EncodedRegion_Current != nullptr) {
801 const size_t begin = buffer->EncodedRegion_Current->Offset;
802 const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length;
803 if ((offset >= begin) && (offset < end)) {
804 outTailSize = end - offset;
805 return &buffer->EncodedRegion_Current->DecodedData[offset - begin];
806 }
807 }
808
809 if (offset >= buffer->byteLength)
810 {
811 outTailSize = 0;
812 return nullptr;
813 }
814
815 outTailSize = buffer->byteLength - offset;
816 return basePtr + offset;
817}
818
819//
820// struct Accessor
821//
822inline void Accessor::Sparse::PopulateData(size_t numBytes, const uint8_t *bytes) {
823 if (bytes) {
824 data.assign(first: bytes, last: bytes + numBytes);
825 } else {
826 data.resize(new_size: numBytes, x: 0x00);
827 }
828}
829
830inline void Accessor::Sparse::PatchData(unsigned int elementSize) {
831 size_t indicesTailDataSize;
832 uint8_t *pIndices = indices->GetPointerAndTailSize(accOffset: indicesByteOffset, outTailSize&: indicesTailDataSize);
833 const unsigned int indexSize = int(ComponentTypeSize(t: indicesType));
834 uint8_t *indicesEnd = pIndices + count * indexSize;
835
836 if ((uint64_t)indicesEnd > (uint64_t)pIndices + indicesTailDataSize) {
837 throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory.");
838 }
839
840 size_t valuesTailDataSize;
841 uint8_t* pValues = values->GetPointerAndTailSize(accOffset: valuesByteOffset, outTailSize&: valuesTailDataSize);
842
843 if (elementSize * count > valuesTailDataSize) {
844 throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory.");
845 }
846 while (pIndices != indicesEnd) {
847 size_t offset;
848 switch (indicesType) {
849 case ComponentType_UNSIGNED_BYTE:
850 offset = *pIndices;
851 break;
852 case ComponentType_UNSIGNED_SHORT:
853 offset = *reinterpret_cast<uint16_t *>(pIndices);
854 break;
855 case ComponentType_UNSIGNED_INT:
856 offset = *reinterpret_cast<uint32_t *>(pIndices);
857 break;
858 default:
859 // have fun with float and negative values from signed types as indices.
860 throw DeadlyImportError("Unsupported component type in index.");
861 }
862
863 offset *= elementSize;
864
865 if (offset + elementSize > data.size()) {
866 throw DeadlyImportError("Invalid sparse accessor. Byte offset for patching points outside allocated memory.");
867 }
868
869 std::memcpy(dest: data.data() + offset, src: pValues, n: elementSize);
870
871 pValues += elementSize;
872 pIndices += indexSize;
873 }
874}
875
876inline void Accessor::Read(Value &obj, Asset &r) {
877 if (Value *bufferViewVal = FindUInt(val&: obj, memberId: "bufferView")) {
878 bufferView = r.bufferViews.Retrieve(i: bufferViewVal->GetUint());
879 }
880
881 byteOffset = MemberOrDefault(obj, id: "byteOffset", defaultValue: size_t(0));
882 componentType = MemberOrDefault(obj, id: "componentType", defaultValue: ComponentType_BYTE);
883 {
884 const Value *countValue = FindUInt(val&: obj, memberId: "count");
885 if (!countValue) {
886 throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")");
887 }
888 count = countValue->GetUint();
889 }
890
891 const char *typestr;
892 type = ReadMember(obj, id: "type", out&: typestr) ? AttribType::FromString(str: typestr) : AttribType::SCALAR;
893
894 if (bufferView) {
895 // Check length
896 unsigned long long byteLength = (unsigned long long)GetBytesPerComponent() * (unsigned long long)count;
897
898 // handle integer overflow
899 if (byteLength < count) {
900 throw DeadlyImportError("GLTF: Accessor with offset/count (", byteOffset, "/", count, ") is out of range.");
901 }
902
903 if ((byteOffset + byteLength) > bufferView->byteLength || (bufferView->byteOffset + byteOffset + byteLength) > bufferView->buffer->byteLength) {
904 throw DeadlyImportError("GLTF: Accessor with offset/length (", byteOffset, "/", byteLength, ") is out of range.");
905 }
906 }
907
908 if (Value *sparseValue = FindObject(val&: obj, memberId: "sparse")) {
909 sparse.reset(p: new Sparse);
910 // count
911 ReadMember(obj&: *sparseValue, id: "count", out&: sparse->count);
912
913 // indices
914 if (Value *indicesValue = FindObject(val&: *sparseValue, memberId: "indices")) {
915 //indices bufferView
916 Value *indiceViewID = FindUInt(val&: *indicesValue, memberId: "bufferView");
917 if (!indiceViewID) {
918 throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")");
919 }
920 sparse->indices = r.bufferViews.Retrieve(i: indiceViewID->GetUint());
921 //indices byteOffset
922 sparse->indicesByteOffset = MemberOrDefault(obj&: *indicesValue, id: "byteOffset", defaultValue: size_t(0));
923 //indices componentType
924 sparse->indicesType = MemberOrDefault(obj&: *indicesValue, id: "componentType", defaultValue: ComponentType_BYTE);
925 //sparse->indices->Read(*indicesValue, r);
926 } else {
927 // indicesType
928 sparse->indicesType = MemberOrDefault(obj&: *sparseValue, id: "componentType", defaultValue: ComponentType_UNSIGNED_SHORT);
929 }
930
931 // value
932 if (Value *valuesValue = FindObject(val&: *sparseValue, memberId: "values")) {
933 //value bufferView
934 Value *valueViewID = FindUInt(val&: *valuesValue, memberId: "bufferView");
935 if (!valueViewID) {
936 throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")");
937 }
938 sparse->values = r.bufferViews.Retrieve(i: valueViewID->GetUint());
939 //value byteOffset
940 sparse->valuesByteOffset = MemberOrDefault(obj&: *valuesValue, id: "byteOffset", defaultValue: size_t(0));
941 //sparse->values->Read(*valuesValue, r);
942 }
943
944
945 const unsigned int elementSize = GetElementSize();
946 const size_t dataSize = count * elementSize;
947 if (bufferView) {
948 size_t bufferViewTailSize;
949 const uint8_t* bufferViewPointer = bufferView->GetPointerAndTailSize(accOffset: byteOffset, outTailSize&: bufferViewTailSize);
950 if (dataSize > bufferViewTailSize) {
951 throw DeadlyImportError("Invalid buffer when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")");
952 }
953 sparse->PopulateData(numBytes: dataSize, bytes: bufferViewPointer);
954 }
955 else {
956 sparse->PopulateData(numBytes: dataSize, bytes: nullptr);
957 }
958 sparse->PatchData(elementSize);
959 }
960}
961
962inline unsigned int Accessor::GetNumComponents() {
963 return AttribType::GetNumComponents(type);
964}
965
966inline unsigned int Accessor::GetBytesPerComponent() {
967 return int(ComponentTypeSize(t: componentType));
968}
969
970inline unsigned int Accessor::GetElementSize() {
971 return GetNumComponents() * GetBytesPerComponent();
972}
973
974inline uint8_t *Accessor::GetPointer() {
975 if (decodedBuffer)
976 return decodedBuffer->GetPointer();
977
978 if (sparse)
979 return sparse->data.data();
980
981 if (!bufferView || !bufferView->buffer) return nullptr;
982 uint8_t *basePtr = bufferView->buffer->GetPointer();
983 if (!basePtr) return nullptr;
984
985 size_t offset = byteOffset + bufferView->byteOffset;
986
987 // Check if region is encoded.
988 if (bufferView->buffer->EncodedRegion_Current != nullptr) {
989 const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset;
990 const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length;
991
992 if ((offset >= begin) && (offset < end))
993 return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin];
994 }
995
996 return basePtr + offset;
997}
998
999inline size_t Accessor::GetStride() {
1000 // Decoded buffer is always packed
1001 if (decodedBuffer)
1002 return GetElementSize();
1003
1004 // Sparse and normal bufferView
1005 return (bufferView && bufferView->byteStride ? bufferView->byteStride : GetElementSize());
1006}
1007
1008inline size_t Accessor::GetMaxByteSize() {
1009 if (decodedBuffer)
1010 return decodedBuffer->byteLength;
1011
1012 return (bufferView ? bufferView->byteLength : sparse->data.size());
1013}
1014
1015template <class T>
1016size_t Accessor::ExtractData(T *&outData, const std::vector<unsigned int> *remappingIndices) {
1017 uint8_t *data = GetPointer();
1018 if (!data) {
1019 throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name));
1020 }
1021
1022 const size_t usedCount = (remappingIndices != nullptr) ? remappingIndices->size() : count;
1023 const size_t elemSize = GetElementSize();
1024 const size_t totalSize = elemSize * usedCount;
1025
1026 const size_t stride = GetStride();
1027
1028 const size_t targetElemSize = sizeof(T);
1029
1030 if (elemSize > targetElemSize) {
1031 throw DeadlyImportError("GLTF: elemSize ", elemSize, " > targetElemSize ", targetElemSize, " in ", getContextForErrorMessages(id, name));
1032 }
1033
1034 const size_t maxSize = GetMaxByteSize();
1035
1036 outData = new T[usedCount];
1037
1038 if (remappingIndices != nullptr) {
1039 const unsigned int maxIndexCount = static_cast<unsigned int>(maxSize / stride);
1040 for (size_t i = 0; i < usedCount; ++i) {
1041 size_t srcIdx = (*remappingIndices)[i];
1042 if (srcIdx >= maxIndexCount) {
1043 throw DeadlyImportError("GLTF: index*stride ", (srcIdx * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name));
1044 }
1045 memcpy(outData + i, data + srcIdx * stride, elemSize);
1046 }
1047 } else { // non-indexed cases
1048 if (usedCount * stride > maxSize) {
1049 throw DeadlyImportError("GLTF: count*stride ", (usedCount * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name));
1050 }
1051 if (stride == elemSize && targetElemSize == elemSize) {
1052 memcpy(outData, data, totalSize);
1053 } else {
1054 for (size_t i = 0; i < usedCount; ++i) {
1055 memcpy(outData + i, data + i * stride, elemSize);
1056 }
1057 }
1058 }
1059 return usedCount;
1060}
1061
1062inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) {
1063 uint8_t *buffer_ptr = bufferView->buffer->GetPointer();
1064 size_t offset = byteOffset + bufferView->byteOffset;
1065
1066 size_t dst_stride = GetNumComponents() * GetBytesPerComponent();
1067
1068 const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer);
1069 uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset);
1070
1071 ai_assert(dst + _count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength);
1072 CopyData(count: _count, src, src_stride, dst, dst_stride);
1073}
1074
1075inline void Accessor::WriteSparseValues(size_t _count, const void *src_data, size_t src_dataStride) {
1076 if (!sparse)
1077 return;
1078
1079 // values
1080 uint8_t *value_buffer_ptr = sparse->values->buffer->GetPointer();
1081 size_t value_offset = sparse->valuesByteOffset + sparse->values->byteOffset;
1082 size_t value_dst_stride = GetNumComponents() * GetBytesPerComponent();
1083 const uint8_t *value_src = reinterpret_cast<const uint8_t *>(src_data);
1084 uint8_t *value_dst = reinterpret_cast<uint8_t *>(value_buffer_ptr + value_offset);
1085 ai_assert(value_dst + _count * value_dst_stride <= value_buffer_ptr + sparse->values->buffer->byteLength);
1086 CopyData(count: _count, src: value_src, src_stride: src_dataStride, dst: value_dst, dst_stride: value_dst_stride);
1087}
1088
1089inline void Accessor::WriteSparseIndices(size_t _count, const void *src_idx, size_t src_idxStride) {
1090 if (!sparse)
1091 return;
1092
1093 // indices
1094 uint8_t *indices_buffer_ptr = sparse->indices->buffer->GetPointer();
1095 size_t indices_offset = sparse->indicesByteOffset + sparse->indices->byteOffset;
1096 size_t indices_dst_stride = 1 * sizeof(unsigned short);
1097 const uint8_t *indices_src = reinterpret_cast<const uint8_t *>(src_idx);
1098 uint8_t *indices_dst = reinterpret_cast<uint8_t *>(indices_buffer_ptr + indices_offset);
1099 ai_assert(indices_dst + _count * indices_dst_stride <= indices_buffer_ptr + sparse->indices->buffer->byteLength);
1100 CopyData(count: _count, src: indices_src, src_stride: src_idxStride, dst: indices_dst, dst_stride: indices_dst_stride);
1101}
1102
1103inline Accessor::Indexer::Indexer(Accessor &acc) :
1104 accessor(acc),
1105 data(acc.GetPointer()),
1106 elemSize(acc.GetElementSize()),
1107 stride(acc.GetStride()) {
1108}
1109
1110//! Accesses the i-th value as defined by the accessor
1111template <class T>
1112T Accessor::Indexer::GetValue(int i) {
1113 ai_assert(data);
1114 if (i * stride >= accessor.GetMaxByteSize()) {
1115 throw DeadlyImportError("GLTF: Invalid index ", i, ", count out of range for buffer with stride ", stride, " and size ", accessor.GetMaxByteSize(), ".");
1116 }
1117 // Ensure that the memcpy doesn't overwrite the local.
1118 const size_t sizeToCopy = std::min(a: elemSize, b: sizeof(T));
1119 T value = T();
1120 // Assume platform endianness matches GLTF binary data (which is little-endian).
1121 memcpy(&value, data + i * stride, sizeToCopy);
1122 return value;
1123}
1124
1125inline Image::Image() :
1126 width(0),
1127 height(0),
1128 mDataLength(0) {
1129}
1130
1131inline void Image::Read(Value &obj, Asset &r) {
1132 //basisu: no need to handle .ktx2, .basis, load as is
1133 if (!mDataLength) {
1134 Value *curUri = FindString(val&: obj, memberId: "uri");
1135 if (nullptr != curUri) {
1136 const char *uristr = curUri->GetString();
1137
1138 glTFCommon::Util::DataURI dataURI;
1139 if (ParseDataURI(const_uri: uristr, uriLen: curUri->GetStringLength(), out&: dataURI)) {
1140 mimeType = dataURI.mediaType;
1141 if (dataURI.base64) {
1142 uint8_t *ptr = nullptr;
1143 mDataLength = Base64::Decode(in: dataURI.data, inLength: dataURI.dataLength, out&: ptr);
1144 mData.reset(p: ptr);
1145 }
1146 } else {
1147 this->uri = uristr;
1148 }
1149 } else if (Value *bufferViewVal = FindUInt(val&: obj, memberId: "bufferView")) {
1150 this->bufferView = r.bufferViews.Retrieve(i: bufferViewVal->GetUint());
1151 if (Value *mtype = FindString(val&: obj, memberId: "mimeType")) {
1152 this->mimeType = mtype->GetString();
1153 }
1154 if (!this->bufferView || this->mimeType.empty()) {
1155 throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " does not have a URI, so it must have a valid bufferView and mimetype");
1156 }
1157
1158 Ref<Buffer> buffer = this->bufferView->buffer;
1159
1160 this->mDataLength = this->bufferView->byteLength;
1161 // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction.
1162
1163 this->mData.reset(p: new uint8_t[this->mDataLength]);
1164 memcpy(dest: this->mData.get(), src: buffer->GetPointer() + this->bufferView->byteOffset, n: this->mDataLength);
1165 } else {
1166 throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " should have either a URI of a bufferView and mimetype");
1167 }
1168 }
1169}
1170
1171inline uint8_t *Image::StealData() {
1172 mDataLength = 0;
1173 return mData.release();
1174}
1175
1176// Never take over the ownership of data whenever binary or not
1177inline void Image::SetData(uint8_t *data, size_t length, Asset &r) {
1178 Ref<Buffer> b = r.GetBodyBuffer();
1179 if (b) { // binary file: append to body
1180 std::string bvId = r.FindUniqueID(str: this->id, suffix: "imgdata");
1181 bufferView = r.bufferViews.Create(id: bvId);
1182
1183 bufferView->buffer = b;
1184 bufferView->byteLength = length;
1185 bufferView->byteOffset = b->AppendData(data, length);
1186 } else { // text file: will be stored as a data uri
1187 uint8_t *temp = new uint8_t[length];
1188 memcpy(dest: temp, src: data, n: length);
1189 this->mData.reset(p: temp);
1190 this->mDataLength = length;
1191 }
1192}
1193
1194inline void Sampler::Read(Value &obj, Asset & /*r*/) {
1195 SetDefaults();
1196
1197 ReadMember(obj, id: "name", out&: name);
1198 ReadMember(obj, id: "magFilter", out&: magFilter);
1199 ReadMember(obj, id: "minFilter", out&: minFilter);
1200 ReadMember(obj, id: "wrapS", out&: wrapS);
1201 ReadMember(obj, id: "wrapT", out&: wrapT);
1202}
1203
1204inline void Sampler::SetDefaults() {
1205 //only wrapping modes have defaults
1206 wrapS = SamplerWrap::Repeat;
1207 wrapT = SamplerWrap::Repeat;
1208 magFilter = SamplerMagFilter::UNSET;
1209 minFilter = SamplerMinFilter::UNSET;
1210}
1211
1212inline void Texture::Read(Value &obj, Asset &r) {
1213 if (Value *sourceVal = FindUInt(val&: obj, memberId: "source")) {
1214 source = r.images.Retrieve(i: sourceVal->GetUint());
1215 }
1216
1217 if (Value *samplerVal = FindUInt(val&: obj, memberId: "sampler")) {
1218 sampler = r.samplers.Retrieve(i: samplerVal->GetUint());
1219 }
1220}
1221
1222void Material::SetTextureProperties(Asset &r, Value *prop, TextureInfo &out) {
1223 if (r.extensionsUsed.KHR_texture_transform) {
1224 if (Value *pKHR_texture_transform = FindExtension(val&: *prop, extensionId: "KHR_texture_transform")) {
1225 out.textureTransformSupported = true;
1226 if (Value *array = FindArray(val&: *pKHR_texture_transform, memberId: "offset")) {
1227 out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat();
1228 out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat();
1229 } else {
1230 out.TextureTransformExt_t.offset[0] = 0;
1231 out.TextureTransformExt_t.offset[1] = 0;
1232 }
1233
1234 if (!ReadMember(obj&: *pKHR_texture_transform, id: "rotation", out&: out.TextureTransformExt_t.rotation)) {
1235 out.TextureTransformExt_t.rotation = 0;
1236 }
1237
1238 if (Value *array = FindArray(val&: *pKHR_texture_transform, memberId: "scale")) {
1239 out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat();
1240 out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat();
1241 } else {
1242 out.TextureTransformExt_t.scale[0] = 1;
1243 out.TextureTransformExt_t.scale[1] = 1;
1244 }
1245 }
1246 }
1247
1248 if (Value *indexProp = FindUInt(val&: *prop, memberId: "index")) {
1249 out.texture = r.textures.Retrieve(i: indexProp->GetUint());
1250 }
1251
1252 if (Value *texcoord = FindUInt(val&: *prop, memberId: "texCoord")) {
1253 out.texCoord = texcoord->GetUint();
1254 }
1255}
1256
1257inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, TextureInfo &out) {
1258 if (Value *prop = FindMember(val&: vals, id: propName)) {
1259 SetTextureProperties(r, prop, out);
1260 }
1261}
1262
1263inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, NormalTextureInfo &out) {
1264 if (Value *prop = FindMember(val&: vals, id: propName)) {
1265 SetTextureProperties(r, prop, out);
1266
1267 if (Value *scale = FindNumber(val&: *prop, memberId: "scale")) {
1268 out.scale = static_cast<float>(scale->GetDouble());
1269 }
1270 }
1271}
1272
1273inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, OcclusionTextureInfo &out) {
1274 if (Value *prop = FindMember(val&: vals, id: propName)) {
1275 SetTextureProperties(r, prop, out);
1276
1277 if (Value *strength = FindNumber(val&: *prop, memberId: "strength")) {
1278 out.strength = static_cast<float>(strength->GetDouble());
1279 }
1280 }
1281}
1282
1283inline void Material::Read(Value &material, Asset &r) {
1284 SetDefaults();
1285
1286 if (Value *curPbrMetallicRoughness = FindObject(val&: material, memberId: "pbrMetallicRoughness")) {
1287 ReadMember(obj&: *curPbrMetallicRoughness, id: "baseColorFactor", out&: this->pbrMetallicRoughness.baseColorFactor);
1288 ReadTextureProperty(r, vals&: *curPbrMetallicRoughness, propName: "baseColorTexture", out&: this->pbrMetallicRoughness.baseColorTexture);
1289 ReadTextureProperty(r, vals&: *curPbrMetallicRoughness, propName: "metallicRoughnessTexture", out&: this->pbrMetallicRoughness.metallicRoughnessTexture);
1290 ReadMember(obj&: *curPbrMetallicRoughness, id: "metallicFactor", out&: this->pbrMetallicRoughness.metallicFactor);
1291 ReadMember(obj&: *curPbrMetallicRoughness, id: "roughnessFactor", out&: this->pbrMetallicRoughness.roughnessFactor);
1292 }
1293
1294 ReadTextureProperty(r, vals&: material, propName: "normalTexture", out&: this->normalTexture);
1295 ReadTextureProperty(r, vals&: material, propName: "occlusionTexture", out&: this->occlusionTexture);
1296 ReadTextureProperty(r, vals&: material, propName: "emissiveTexture", out&: this->emissiveTexture);
1297 ReadMember(obj&: material, id: "emissiveFactor", out&: this->emissiveFactor);
1298
1299 ReadMember(obj&: material, id: "doubleSided", out&: this->doubleSided);
1300 ReadMember(obj&: material, id: "alphaMode", out&: this->alphaMode);
1301 ReadMember(obj&: material, id: "alphaCutoff", out&: this->alphaCutoff);
1302
1303 if (Value *extensions = FindObject(val&: material, memberId: "extensions")) {
1304 if (r.extensionsUsed.KHR_materials_pbrSpecularGlossiness) {
1305 if (Value *curPbrSpecularGlossiness = FindObject(val&: *extensions, memberId: "KHR_materials_pbrSpecularGlossiness")) {
1306 PbrSpecularGlossiness pbrSG;
1307
1308 ReadMember(obj&: *curPbrSpecularGlossiness, id: "diffuseFactor", out&: pbrSG.diffuseFactor);
1309 ReadTextureProperty(r, vals&: *curPbrSpecularGlossiness, propName: "diffuseTexture", out&: pbrSG.diffuseTexture);
1310 ReadTextureProperty(r, vals&: *curPbrSpecularGlossiness, propName: "specularGlossinessTexture", out&: pbrSG.specularGlossinessTexture);
1311 ReadMember(obj&: *curPbrSpecularGlossiness, id: "specularFactor", out&: pbrSG.specularFactor);
1312 ReadMember(obj&: *curPbrSpecularGlossiness, id: "glossinessFactor", out&: pbrSG.glossinessFactor);
1313
1314 this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG);
1315 }
1316 }
1317
1318 if (r.extensionsUsed.KHR_materials_specular) {
1319 if (Value *curMatSpecular = FindObject(val&: *extensions, memberId: "KHR_materials_specular")) {
1320 MaterialSpecular specular;
1321
1322 ReadMember(obj&: *curMatSpecular, id: "specularFactor", out&: specular.specularFactor);
1323 ReadTextureProperty(r, vals&: *curMatSpecular, propName: "specularTexture", out&: specular.specularTexture);
1324 ReadMember(obj&: *curMatSpecular, id: "specularColorFactor", out&: specular.specularColorFactor);
1325 ReadTextureProperty(r, vals&: *curMatSpecular, propName: "specularColorTexture", out&: specular.specularColorTexture);
1326
1327 this->materialSpecular = Nullable<MaterialSpecular>(specular);
1328 }
1329 }
1330
1331 // Extension KHR_texture_transform is handled in ReadTextureProperty
1332
1333 if (r.extensionsUsed.KHR_materials_sheen) {
1334 if (Value *curMaterialSheen = FindObject(val&: *extensions, memberId: "KHR_materials_sheen")) {
1335 MaterialSheen sheen;
1336
1337 ReadMember(obj&: *curMaterialSheen, id: "sheenColorFactor", out&: sheen.sheenColorFactor);
1338 ReadTextureProperty(r, vals&: *curMaterialSheen, propName: "sheenColorTexture", out&: sheen.sheenColorTexture);
1339 ReadMember(obj&: *curMaterialSheen, id: "sheenRoughnessFactor", out&: sheen.sheenRoughnessFactor);
1340 ReadTextureProperty(r, vals&: *curMaterialSheen, propName: "sheenRoughnessTexture", out&: sheen.sheenRoughnessTexture);
1341
1342 this->materialSheen = Nullable<MaterialSheen>(sheen);
1343 }
1344 }
1345
1346 if (r.extensionsUsed.KHR_materials_clearcoat) {
1347 if (Value *curMaterialClearcoat = FindObject(val&: *extensions, memberId: "KHR_materials_clearcoat")) {
1348 MaterialClearcoat clearcoat;
1349
1350 ReadMember(obj&: *curMaterialClearcoat, id: "clearcoatFactor", out&: clearcoat.clearcoatFactor);
1351 ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatTexture", out&: clearcoat.clearcoatTexture);
1352 ReadMember(obj&: *curMaterialClearcoat, id: "clearcoatRoughnessFactor", out&: clearcoat.clearcoatRoughnessFactor);
1353 ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatRoughnessTexture", out&: clearcoat.clearcoatRoughnessTexture);
1354 ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatNormalTexture", out&: clearcoat.clearcoatNormalTexture);
1355
1356 this->materialClearcoat = Nullable<MaterialClearcoat>(clearcoat);
1357 }
1358 }
1359
1360 if (r.extensionsUsed.KHR_materials_transmission) {
1361 if (Value *curMaterialTransmission = FindObject(val&: *extensions, memberId: "KHR_materials_transmission")) {
1362 MaterialTransmission transmission;
1363
1364 ReadMember(obj&: *curMaterialTransmission, id: "transmissionFactor", out&: transmission.transmissionFactor);
1365 ReadTextureProperty(r, vals&: *curMaterialTransmission, propName: "transmissionTexture", out&: transmission.transmissionTexture);
1366
1367 this->materialTransmission = Nullable<MaterialTransmission>(transmission);
1368 }
1369 }
1370
1371 if (r.extensionsUsed.KHR_materials_volume) {
1372 if (Value *curMaterialVolume = FindObject(val&: *extensions, memberId: "KHR_materials_volume")) {
1373 MaterialVolume volume;
1374
1375 ReadMember(obj&: *curMaterialVolume, id: "thicknessFactor", out&: volume.thicknessFactor);
1376 ReadTextureProperty(r, vals&: *curMaterialVolume, propName: "thicknessTexture", out&: volume.thicknessTexture);
1377 ReadMember(obj&: *curMaterialVolume, id: "attenuationDistance", out&: volume.attenuationDistance);
1378 ReadMember(obj&: *curMaterialVolume, id: "attenuationColor", out&: volume.attenuationColor);
1379
1380 this->materialVolume = Nullable<MaterialVolume>(volume);
1381 }
1382 }
1383
1384 if (r.extensionsUsed.KHR_materials_ior) {
1385 if (Value *curMaterialIOR = FindObject(val&: *extensions, memberId: "KHR_materials_ior")) {
1386 MaterialIOR ior;
1387
1388 ReadMember(obj&: *curMaterialIOR, id: "ior", out&: ior.ior);
1389
1390 this->materialIOR = Nullable<MaterialIOR>(ior);
1391 }
1392 }
1393
1394 if (r.extensionsUsed.KHR_materials_emissive_strength) {
1395 if (Value *curMaterialEmissiveStrength = FindObject(val&: *extensions, memberId: "KHR_materials_emissive_strength")) {
1396 MaterialEmissiveStrength emissiveStrength;
1397
1398 ReadMember(obj&: *curMaterialEmissiveStrength, id: "emissiveStrength", out&: emissiveStrength.emissiveStrength);
1399
1400 this->materialEmissiveStrength = Nullable<MaterialEmissiveStrength>(emissiveStrength);
1401 }
1402 }
1403
1404 unlit = nullptr != FindObject(val&: *extensions, memberId: "KHR_materials_unlit");
1405 }
1406}
1407
1408inline void Material::SetDefaults() {
1409 //pbr materials
1410 SetVector(v&: pbrMetallicRoughness.baseColorFactor, in: defaultBaseColor);
1411 pbrMetallicRoughness.metallicFactor = 1.0f;
1412 pbrMetallicRoughness.roughnessFactor = 1.0f;
1413
1414 SetVector(v&: emissiveFactor, in: defaultEmissiveFactor);
1415 alphaMode = "OPAQUE";
1416 alphaCutoff = 0.5f;
1417 doubleSided = false;
1418 unlit = false;
1419}
1420
1421inline void PbrSpecularGlossiness::SetDefaults() {
1422 //pbrSpecularGlossiness properties
1423 SetVector(v&: diffuseFactor, in: defaultDiffuseFactor);
1424 SetVector(v&: specularFactor, in: defaultSpecularFactor);
1425 glossinessFactor = 1.0f;
1426}
1427
1428inline void MaterialSpecular::SetDefaults() {
1429 //KHR_materials_specular properties
1430 SetVector(v&: specularColorFactor, in: defaultSpecularColorFactor);
1431 specularFactor = 1.f;
1432}
1433
1434inline void MaterialSheen::SetDefaults() {
1435 //KHR_materials_sheen properties
1436 SetVector(v&: sheenColorFactor, in: defaultSheenFactor);
1437 sheenRoughnessFactor = 0.f;
1438}
1439
1440inline void MaterialVolume::SetDefaults() {
1441 //KHR_materials_volume properties
1442 thicknessFactor = 0.f;
1443 attenuationDistance = std::numeric_limits<float>::infinity();
1444 SetVector(v&: attenuationColor, in: defaultAttenuationColor);
1445}
1446
1447inline void MaterialIOR::SetDefaults() {
1448 //KHR_materials_ior properties
1449 ior = 1.5f;
1450}
1451
1452inline void MaterialEmissiveStrength::SetDefaults() {
1453 //KHR_materials_emissive_strength properties
1454 emissiveStrength = 0.f;
1455}
1456
1457inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
1458 Value *curName = FindMember(val&: pJSON_Object, id: "name");
1459 if (nullptr != curName && curName->IsString()) {
1460 name = curName->GetString();
1461 }
1462
1463 /****************** Mesh primitives ******************/
1464 Value *curPrimitives = FindArray(val&: pJSON_Object, memberId: "primitives");
1465 if (nullptr != curPrimitives) {
1466 this->primitives.resize(new_size: curPrimitives->Size());
1467 for (unsigned int i = 0; i < curPrimitives->Size(); ++i) {
1468 Value &primitive = (*curPrimitives)[i];
1469
1470 Primitive &prim = this->primitives[i];
1471 prim.mode = MemberOrDefault(obj&: primitive, id: "mode", defaultValue: PrimitiveMode_TRIANGLES);
1472
1473 if (Value *indices = FindUInt(val&: primitive, memberId: "indices")) {
1474 prim.indices = pAsset_Root.accessors.Retrieve(i: indices->GetUint());
1475 }
1476
1477 if (Value *material = FindUInt(val&: primitive, memberId: "material")) {
1478 prim.material = pAsset_Root.materials.Retrieve(i: material->GetUint());
1479 }
1480
1481 if (Value *attrs = FindObject(val&: primitive, memberId: "attributes")) {
1482 for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) {
1483 if (!it->value.IsUint()) continue;
1484 const char *attr = it->name.GetString();
1485 // Valid attribute semantics include POSITION, NORMAL, TANGENT, TEXCOORD, COLOR, JOINT, JOINTMATRIX,
1486 // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc.
1487
1488 int undPos = 0;
1489 Mesh::AccessorList *vec = nullptr;
1490 if (GetAttribVector(p&: prim, attr, v&: vec, pos&: undPos)) {
1491 size_t idx = (attr[undPos] == '_') ? atoi(nptr: attr + undPos + 1) : 0;
1492 if ((*vec).size() != idx) {
1493 throw DeadlyImportError("GLTF: Invalid attribute in mesh: ", name, " primitive: ", i, "attrib: ", attr,
1494 ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc.");
1495 }
1496 (*vec).resize(new_size: idx + 1);
1497 (*vec)[idx] = pAsset_Root.accessors.Retrieve(i: it->value.GetUint());
1498 }
1499 }
1500 }
1501
1502#ifdef ASSIMP_ENABLE_DRACO
1503 // KHR_draco_mesh_compression spec: Draco can only be used for glTF Triangles or Triangle Strips
1504 if (pAsset_Root.extensionsUsed.KHR_draco_mesh_compression && (prim.mode == PrimitiveMode_TRIANGLES || prim.mode == PrimitiveMode_TRIANGLE_STRIP)) {
1505 // Look for draco mesh compression extension and bufferView
1506 // Skip if any missing
1507 if (Value *dracoExt = FindExtension(primitive, "KHR_draco_mesh_compression")) {
1508 if (Value *bufView = FindUInt(*dracoExt, "bufferView")) {
1509 // Attempt to load indices and attributes using draco compression
1510 auto bufferView = pAsset_Root.bufferViews.Retrieve(bufView->GetUint());
1511 // Attempt to perform the draco decode on the buffer data
1512 const char *bufferViewData = reinterpret_cast<const char *>(bufferView->buffer->GetPointer() + bufferView->byteOffset);
1513 draco::DecoderBuffer decoderBuffer;
1514 decoderBuffer.Init(bufferViewData, bufferView->byteLength);
1515 draco::Decoder decoder;
1516 auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer);
1517 if (!decodeResult.ok()) {
1518 // A corrupt Draco isn't actually fatal if the primitive data is also provided in a standard buffer, but does anyone do that?
1519 throw DeadlyImportError("GLTF: Invalid Draco mesh compression in mesh: ", name, " primitive: ", i, ": ", decodeResult.status().error_msg_string());
1520 }
1521
1522 // Now we have a draco mesh
1523 const std::unique_ptr<draco::Mesh> &pDracoMesh = decodeResult.value();
1524
1525 // Redirect the accessors to the decoded data
1526
1527 // Indices
1528 SetDecodedIndexBuffer_Draco(*pDracoMesh, prim);
1529
1530 // Vertex attributes
1531 if (Value *attrs = FindObject(*dracoExt, "attributes")) {
1532 for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) {
1533 if (!it->value.IsUint()) continue;
1534 const char *attr = it->name.GetString();
1535
1536 int undPos = 0;
1537 Mesh::AccessorList *vec = nullptr;
1538 if (GetAttribVector(prim, attr, vec, undPos)) {
1539 size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0;
1540 if (idx >= (*vec).size()) {
1541 throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr,
1542 ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc.");
1543 }
1544
1545 if (!(*vec)[idx]) {
1546 throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr,
1547 ". All draco-encoded attributes must also define an accessor.");
1548 }
1549
1550 Accessor &attribAccessor = *(*vec)[idx];
1551 if (attribAccessor.count == 0)
1552 throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr);
1553
1554 // Redirect this accessor to the appropriate Draco vertex attribute data
1555 const uint32_t dracoAttribId = it->value.GetUint();
1556 SetDecodedAttributeBuffer_Draco(*pDracoMesh, dracoAttribId, attribAccessor);
1557 }
1558 }
1559 }
1560 }
1561 }
1562 }
1563#endif
1564
1565 Value *targetsArray = FindArray(val&: primitive, memberId: "targets");
1566 if (nullptr != targetsArray) {
1567 prim.targets.resize(new_size: targetsArray->Size());
1568 for (unsigned int j = 0; j < targetsArray->Size(); ++j) {
1569 Value &target = (*targetsArray)[j];
1570 if (!target.IsObject()) {
1571 continue;
1572 }
1573 for (Value::MemberIterator it = target.MemberBegin(); it != target.MemberEnd(); ++it) {
1574 if (!it->value.IsUint()) {
1575 continue;
1576 }
1577 const char *attr = it->name.GetString();
1578 // Valid attribute semantics include POSITION, NORMAL, TANGENT
1579 int undPos = 0;
1580 Mesh::AccessorList *vec = nullptr;
1581 if (GetAttribTargetVector(p&: prim, targetIndex: j, attr, v&: vec, pos&: undPos)) {
1582 size_t idx = (attr[undPos] == '_') ? atoi(nptr: attr + undPos + 1) : 0;
1583 if ((*vec).size() <= idx) {
1584 (*vec).resize(new_size: idx + 1);
1585 }
1586 (*vec)[idx] = pAsset_Root.accessors.Retrieve(i: it->value.GetUint());
1587 }
1588 }
1589 }
1590 }
1591
1592 if(this->targetNames.empty())
1593 {
1594 Value *curExtras = FindObject(val&: primitive, memberId: "extras");
1595 if (nullptr != curExtras) {
1596 if (Value *curTargetNames = FindArray(val&: *curExtras, memberId: "targetNames")) {
1597 this->targetNames.resize(new_size: curTargetNames->Size());
1598 for (unsigned int j = 0; j < curTargetNames->Size(); ++j) {
1599 Value &targetNameValue = (*curTargetNames)[j];
1600 if (targetNameValue.IsString()) {
1601 this->targetNames[j] = targetNameValue.GetString();
1602 }
1603 }
1604 }
1605 }
1606 }
1607 }
1608 }
1609
1610 Value *curWeights = FindArray(val&: pJSON_Object, memberId: "weights");
1611 if (nullptr != curWeights) {
1612 this->weights.resize(new_size: curWeights->Size());
1613 for (unsigned int i = 0; i < curWeights->Size(); ++i) {
1614 Value &weightValue = (*curWeights)[i];
1615 if (weightValue.IsNumber()) {
1616 this->weights[i] = weightValue.GetFloat();
1617 }
1618 }
1619 }
1620
1621 Value *curExtras = FindObject(val&: pJSON_Object, memberId: "extras");
1622 if (nullptr != curExtras) {
1623 if (Value *curTargetNames = FindArray(val&: *curExtras, memberId: "targetNames")) {
1624 this->targetNames.resize(new_size: curTargetNames->Size());
1625 for (unsigned int i = 0; i < curTargetNames->Size(); ++i) {
1626 Value &targetNameValue = (*curTargetNames)[i];
1627 if (targetNameValue.IsString()) {
1628 this->targetNames[i] = targetNameValue.GetString();
1629 }
1630 }
1631 }
1632 }
1633}
1634
1635inline void Camera::Read(Value &obj, Asset & /*r*/) {
1636 std::string type_string = std::string(MemberOrDefault(obj, id: "type", defaultValue: "perspective"));
1637 if (type_string == "orthographic") {
1638 type = Camera::Orthographic;
1639 } else {
1640 type = Camera::Perspective;
1641 }
1642
1643 const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective";
1644
1645 Value *it = FindObject(val&: obj, memberId: subobjId);
1646 if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters");
1647
1648 if (type == Camera::Perspective) {
1649 cameraProperties.perspective.aspectRatio = MemberOrDefault(obj&: *it, id: "aspectRatio", defaultValue: 0.f);
1650 cameraProperties.perspective.yfov = MemberOrDefault(obj&: *it, id: "yfov", defaultValue: 3.1415f / 2.f);
1651 cameraProperties.perspective.zfar = MemberOrDefault(obj&: *it, id: "zfar", defaultValue: 100.f);
1652 cameraProperties.perspective.znear = MemberOrDefault(obj&: *it, id: "znear", defaultValue: 0.01f);
1653 } else {
1654 cameraProperties.ortographic.xmag = MemberOrDefault(obj&: *it, id: "xmag", defaultValue: 1.f);
1655 cameraProperties.ortographic.ymag = MemberOrDefault(obj&: *it, id: "ymag", defaultValue: 1.f);
1656 cameraProperties.ortographic.zfar = MemberOrDefault(obj&: *it, id: "zfar", defaultValue: 100.f);
1657 cameraProperties.ortographic.znear = MemberOrDefault(obj&: *it, id: "znear", defaultValue: 0.01f);
1658 }
1659}
1660
1661inline void Light::Read(Value &obj, Asset & /*r*/) {
1662#ifndef M_PI
1663 const float M_PI = 3.14159265358979323846f;
1664#endif
1665
1666 std::string type_string;
1667 ReadMember(obj, id: "type", out&: type_string);
1668 if (type_string == "directional")
1669 type = Light::Directional;
1670 else if (type_string == "point")
1671 type = Light::Point;
1672 else
1673 type = Light::Spot;
1674
1675 name = MemberOrDefault(obj, id: "name", defaultValue: "");
1676
1677 SetVector(v&: color, in: vec3{ 1.0f, 1.0f, 1.0f });
1678 ReadMember(obj, id: "color", out&: color);
1679
1680 intensity = MemberOrDefault(obj, id: "intensity", defaultValue: 1.0f);
1681
1682 ReadMember(obj, id: "range", out&: range);
1683
1684 if (type == Light::Spot) {
1685 Value *spot = FindObject(val&: obj, memberId: "spot");
1686 if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters");
1687 innerConeAngle = MemberOrDefault(obj&: *spot, id: "innerConeAngle", defaultValue: 0.0f);
1688 outerConeAngle = MemberOrDefault(obj&: *spot, id: "outerConeAngle", defaultValue: static_cast<float>(M_PI / 4.0f));
1689 }
1690}
1691
1692inline void Node::Read(Value &obj, Asset &r) {
1693 if (name.empty()) {
1694 name = id;
1695 }
1696
1697 Value *curChildren = FindArray(val&: obj, memberId: "children");
1698 if (nullptr != curChildren) {
1699 this->children.reserve(n: curChildren->Size());
1700 for (unsigned int i = 0; i < curChildren->Size(); ++i) {
1701 Value &child = (*curChildren)[i];
1702 if (child.IsUint()) {
1703 // get/create the child node
1704 Ref<Node> chn = r.nodes.Retrieve(i: child.GetUint());
1705 if (chn) {
1706 this->children.push_back(x: chn);
1707 }
1708 }
1709 }
1710 }
1711
1712 Value *curMatrix = FindArray(val&: obj, memberId: "matrix");
1713 if (nullptr != curMatrix) {
1714 ReadValue(val&: *curMatrix, out&: this->matrix);
1715 } else {
1716 ReadMember(obj, id: "translation", out&: translation);
1717 ReadMember(obj, id: "scale", out&: scale);
1718 ReadMember(obj, id: "rotation", out&: rotation);
1719 }
1720
1721 Value *curMesh = FindUInt(val&: obj, memberId: "mesh");
1722 if (nullptr != curMesh) {
1723 unsigned int numMeshes = 1;
1724 this->meshes.reserve(n: numMeshes);
1725 Ref<Mesh> meshRef = r.meshes.Retrieve(i: (*curMesh).GetUint());
1726 if (meshRef) {
1727 this->meshes.push_back(x: meshRef);
1728 }
1729 }
1730
1731 // Do not retrieve a skin here, just take a reference, to avoid infinite recursion
1732 // Skins will be properly loaded later
1733 Value *curSkin = FindUInt(val&: obj, memberId: "skin");
1734 if (nullptr != curSkin) {
1735 this->skin = r.skins.Get(i: curSkin->GetUint());
1736 }
1737
1738 Value *curCamera = FindUInt(val&: obj, memberId: "camera");
1739 if (nullptr != curCamera) {
1740 this->camera = r.cameras.Retrieve(i: curCamera->GetUint());
1741 if (this->camera) {
1742 this->camera->id = this->id;
1743 }
1744 }
1745
1746 Value *curExtensions = FindObject(val&: obj, memberId: "extensions");
1747 if (nullptr != curExtensions) {
1748 if (r.extensionsUsed.KHR_lights_punctual) {
1749 if (Value *ext = FindObject(val&: *curExtensions, memberId: "KHR_lights_punctual")) {
1750 Value *curLight = FindUInt(val&: *ext, memberId: "light");
1751 if (nullptr != curLight) {
1752 this->light = r.lights.Retrieve(i: curLight->GetUint());
1753 if (this->light) {
1754 this->light->id = this->id;
1755 }
1756 }
1757 }
1758 }
1759 }
1760}
1761
1762inline void Scene::Read(Value &obj, Asset &r) {
1763 if (Value *scene_name = FindString(val&: obj, memberId: "name")) {
1764 if (scene_name->IsString()) {
1765 this->name = scene_name->GetString();
1766 }
1767 }
1768 if (Value *array = FindArray(val&: obj, memberId: "nodes")) {
1769 for (unsigned int i = 0; i < array->Size(); ++i) {
1770 if (!(*array)[i].IsUint()) continue;
1771 Ref<Node> node = r.nodes.Retrieve(i: (*array)[i].GetUint());
1772 if (node)
1773 this->nodes.push_back(x: node);
1774 }
1775 }
1776}
1777
1778inline void Skin::Read(Value &obj, Asset &r) {
1779 if (Value *matrices = FindUInt(val&: obj, memberId: "inverseBindMatrices")) {
1780 inverseBindMatrices = r.accessors.Retrieve(i: matrices->GetUint());
1781 }
1782
1783 if (Value *joints = FindArray(val&: obj, memberId: "joints")) {
1784 for (unsigned i = 0; i < joints->Size(); ++i) {
1785 if (!(*joints)[i].IsUint()) continue;
1786 Ref<Node> node = r.nodes.Retrieve(i: (*joints)[i].GetUint());
1787 if (node) {
1788 this->jointNames.push_back(x: node);
1789 }
1790 }
1791 }
1792}
1793
1794inline void Animation::Read(Value &obj, Asset &r) {
1795 Value *curSamplers = FindArray(val&: obj, memberId: "samplers");
1796 if (nullptr != curSamplers) {
1797 for (unsigned i = 0; i < curSamplers->Size(); ++i) {
1798 Value &sampler = (*curSamplers)[i];
1799
1800 Sampler s;
1801 if (Value *input = FindUInt(val&: sampler, memberId: "input")) {
1802 s.input = r.accessors.Retrieve(i: input->GetUint());
1803 }
1804 if (Value *output = FindUInt(val&: sampler, memberId: "output")) {
1805 s.output = r.accessors.Retrieve(i: output->GetUint());
1806 }
1807 s.interpolation = Interpolation_LINEAR;
1808 if (Value *interpolation = FindString(val&: sampler, memberId: "interpolation")) {
1809 const std::string interp = interpolation->GetString();
1810 if (interp == "LINEAR") {
1811 s.interpolation = Interpolation_LINEAR;
1812 } else if (interp == "STEP") {
1813 s.interpolation = Interpolation_STEP;
1814 } else if (interp == "CUBICSPLINE") {
1815 s.interpolation = Interpolation_CUBICSPLINE;
1816 }
1817 }
1818 this->samplers.push_back(x: s);
1819 }
1820 }
1821
1822 Value *curChannels = FindArray(val&: obj, memberId: "channels");
1823 if (nullptr != curChannels) {
1824 for (unsigned i = 0; i < curChannels->Size(); ++i) {
1825 Value &channel = (*curChannels)[i];
1826
1827 Channel c;
1828 Value *curSampler = FindUInt(val&: channel, memberId: "sampler");
1829 if (nullptr != curSampler) {
1830 c.sampler = curSampler->GetUint();
1831 }
1832
1833 if (Value *target = FindObject(val&: channel, memberId: "target")) {
1834 if (Value *node = FindUInt(val&: *target, memberId: "node")) {
1835 c.target.node = r.nodes.Retrieve(i: node->GetUint());
1836 }
1837 if (Value *path = FindString(val&: *target, memberId: "path")) {
1838 const std::string p = path->GetString();
1839 if (p == "translation") {
1840 c.target.path = AnimationPath_TRANSLATION;
1841 } else if (p == "rotation") {
1842 c.target.path = AnimationPath_ROTATION;
1843 } else if (p == "scale") {
1844 c.target.path = AnimationPath_SCALE;
1845 } else if (p == "weights") {
1846 c.target.path = AnimationPath_WEIGHTS;
1847 }
1848 }
1849 }
1850 this->channels.push_back(x: c);
1851 }
1852 }
1853}
1854
1855inline void AssetMetadata::Read(Document &doc) {
1856 if (Value *obj = FindObject(doc, memberId: "asset")) {
1857 ReadMember(obj&: *obj, id: "copyright", out&: copyright);
1858 ReadMember(obj&: *obj, id: "generator", out&: generator);
1859
1860 if (Value *versionString = FindStringInContext(val&: *obj, memberId: "version", context: "\"asset\"")) {
1861 version = versionString->GetString();
1862 }
1863 Value *curProfile = FindObjectInContext(val&: *obj, memberId: "profile", context: "\"asset\"");
1864 if (nullptr != curProfile) {
1865 ReadMember(obj&: *curProfile, id: "api", out&: this->profile.api);
1866 ReadMember(obj&: *curProfile, id: "version", out&: this->profile.version);
1867 }
1868 }
1869
1870 if (version.empty() || version[0] != '2') {
1871 throw DeadlyImportError("GLTF: Unsupported glTF version: ", version);
1872 }
1873}
1874
1875//
1876// Asset methods implementation
1877//
1878
1879inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector<char> &sceneData) {
1880 ASSIMP_LOG_DEBUG("Reading GLTF2 binary");
1881 GLB_Header header;
1882 if (stream.Read(pvBuffer: &header, pSize: sizeof(header), pCount: 1) != 1) {
1883 throw DeadlyImportError("GLTF: Unable to read the file header");
1884 }
1885
1886 if (strncmp(s1: (char *)header.magic, AI_GLB_MAGIC_NUMBER, n: sizeof(header.magic)) != 0) {
1887 throw DeadlyImportError("GLTF: Invalid binary glTF file");
1888 }
1889
1890 AI_SWAP4(header.version);
1891 asset.version = ai_to_string(value: header.version);
1892 if (header.version != 2) {
1893 throw DeadlyImportError("GLTF: Unsupported binary glTF version");
1894 }
1895
1896 GLB_Chunk chunk;
1897 if (stream.Read(pvBuffer: &chunk, pSize: sizeof(chunk), pCount: 1) != 1) {
1898 throw DeadlyImportError("GLTF: Unable to read JSON chunk");
1899 }
1900
1901 AI_SWAP4(chunk.chunkLength);
1902 AI_SWAP4(chunk.chunkType);
1903
1904 if (chunk.chunkType != ChunkType_JSON) {
1905 throw DeadlyImportError("GLTF: JSON chunk missing");
1906 }
1907
1908 // read the scene data, ensure null termination
1909 static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits");
1910 mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t)
1911 sceneData.resize(new_size: mSceneLength + 1);
1912 sceneData[mSceneLength] = '\0';
1913
1914 if (stream.Read(pvBuffer: &sceneData[0], pSize: 1, pCount: mSceneLength) != mSceneLength) {
1915 throw DeadlyImportError("GLTF: Could not read the file contents");
1916 }
1917
1918 uint32_t padding = ((chunk.chunkLength + 3) & ~3) - chunk.chunkLength;
1919 if (padding > 0) {
1920 stream.Seek(pOffset: padding, pOrigin: aiOrigin_CUR);
1921 }
1922
1923 AI_SWAP4(header.length);
1924 mBodyOffset = 12 + 8 + chunk.chunkLength + padding + 8;
1925 if (header.length >= mBodyOffset) {
1926 if (stream.Read(pvBuffer: &chunk, pSize: sizeof(chunk), pCount: 1) != 1) {
1927 throw DeadlyImportError("GLTF: Unable to read BIN chunk");
1928 }
1929
1930 AI_SWAP4(chunk.chunkLength);
1931 AI_SWAP4(chunk.chunkType);
1932
1933 if (chunk.chunkType != ChunkType_BIN) {
1934 throw DeadlyImportError("GLTF: BIN chunk missing");
1935 }
1936
1937 mBodyLength = chunk.chunkLength;
1938 } else {
1939 mBodyOffset = mBodyLength = 0;
1940 }
1941}
1942
1943inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector<char> &sceneData) {
1944 ASSIMP_LOG_DEBUG("Loading GLTF2 asset");
1945
1946 // is binary? then read the header
1947 if (isBinary) {
1948 SetAsBinary(); // also creates the body buffer
1949 ReadBinaryHeader(stream, sceneData);
1950 } else {
1951 mSceneLength = stream.FileSize();
1952 mBodyLength = 0;
1953
1954 // Binary format only supports up to 4GB of JSON, use that as a maximum
1955 if (mSceneLength >= std::numeric_limits<uint32_t>::max()) {
1956 throw DeadlyImportError("GLTF: JSON size greater than 4GB");
1957 }
1958
1959 // read the scene data, ensure null termination
1960 sceneData.resize(new_size: mSceneLength + 1);
1961 sceneData[mSceneLength] = '\0';
1962
1963 if (stream.Read(pvBuffer: &sceneData[0], pSize: 1, pCount: mSceneLength) != mSceneLength) {
1964 throw DeadlyImportError("GLTF: Could not read the file contents");
1965 }
1966 }
1967
1968 // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later
1969 if (mSceneLength < 2) {
1970 throw DeadlyImportError("GLTF: No JSON file contents");
1971 }
1972
1973 // parse the JSON document
1974 ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON");
1975 Document doc;
1976 doc.ParseInsitu(str: &sceneData[0]);
1977
1978 if (doc.HasParseError()) {
1979 char buffer[32];
1980 ai_snprintf(s: buffer, maxlen: 32, format: "%d", static_cast<int>(doc.GetErrorOffset()));
1981 throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(parseErrorCode: doc.GetParseError()));
1982 }
1983
1984 if (!doc.IsObject()) {
1985 throw DeadlyImportError("GLTF: JSON document root must be a JSON object");
1986 }
1987
1988 return doc;
1989}
1990
1991inline void Asset::Load(const std::string &pFile, bool isBinary)
1992{
1993 mCurrentAssetDir.clear();
1994 if (0 != strncmp(s1: pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) {
1995 mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile);
1996 }
1997
1998 shared_ptr<IOStream> stream(OpenFile(path: pFile.c_str(), mode: "rb", absolute: true));
1999 if (!stream) {
2000 throw DeadlyImportError("GLTF: Could not open file for reading");
2001 }
2002
2003 std::vector<char> sceneData;
2004 rapidjson::Document doc = ReadDocument(stream&: *stream, isBinary, sceneData);
2005
2006 // If a schemaDocumentProvider is available, see if the glTF schema is present.
2007 // If so, use it to validate the document.
2008 if (mSchemaDocumentProvider) {
2009 if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument(uri: "glTF.schema.json", length: 16)) {
2010 // The schemas are found here: https://github.com/KhronosGroup/glTF/tree/main/specification/2.0/schema
2011 rapidjson::SchemaValidator validator(*gltfSchema);
2012 if (!doc.Accept(handler&: validator)) {
2013 rapidjson::StringBuffer pathBuffer;
2014 validator.GetInvalidSchemaPointer().StringifyUriFragment(os&: pathBuffer);
2015 rapidjson::StringBuffer argumentBuffer;
2016 validator.GetInvalidDocumentPointer().StringifyUriFragment(os&: argumentBuffer);
2017 throw DeadlyImportError("GLTF: The JSON document did not satisfy the glTF2 schema. Schema keyword: ", validator.GetInvalidSchemaKeyword(), ", document path: ", pathBuffer.GetString(), ", argument: ", argumentBuffer.GetString());
2018 }
2019 }
2020 }
2021
2022 // Fill the buffer instance for the current file embedded contents
2023 if (mBodyLength > 0) {
2024 if (!mBodyBuffer->LoadFromStream(stream&: *stream, length: mBodyLength, baseOffset: mBodyOffset)) {
2025 throw DeadlyImportError("GLTF: Unable to read gltf file");
2026 }
2027 }
2028
2029 // Load the metadata
2030 asset.Read(doc);
2031 ReadExtensionsUsed(doc);
2032 ReadExtensionsRequired(doc);
2033
2034#ifndef ASSIMP_ENABLE_DRACO
2035 // Is Draco required?
2036 if (extensionsRequired.KHR_draco_mesh_compression) {
2037 throw DeadlyImportError("GLTF: Draco mesh compression not supported.");
2038 }
2039#endif
2040
2041 // Prepare the dictionaries
2042 for (size_t i = 0; i < mDicts.size(); ++i) {
2043 mDicts[i]->AttachToDocument(doc);
2044 }
2045
2046 // Read the "scene" property, which specifies which scene to load
2047 // and recursively load everything referenced by it
2048 unsigned int sceneIndex = 0;
2049 Value *curScene = FindUInt(doc, memberId: "scene");
2050 if (nullptr != curScene) {
2051 sceneIndex = curScene->GetUint();
2052 }
2053
2054 if (Value *scenesArray = FindArray(val&: doc, memberId: "scenes")) {
2055 if (sceneIndex < scenesArray->Size()) {
2056 this->scene = scenes.Retrieve(i: sceneIndex);
2057 }
2058 }
2059
2060 if (Value *skinsArray = FindArray(val&: doc, memberId: "skins")) {
2061 for (unsigned int i = 0; i < skinsArray->Size(); ++i) {
2062 skins.Retrieve(i);
2063 }
2064 }
2065
2066 if (Value *animsArray = FindArray(val&: doc, memberId: "animations")) {
2067 for (unsigned int i = 0; i < animsArray->Size(); ++i) {
2068 animations.Retrieve(i);
2069 }
2070 }
2071
2072 // Clean up
2073 for (size_t i = 0; i < mDicts.size(); ++i) {
2074 mDicts[i]->DetachFromDocument();
2075 }
2076}
2077
2078inline bool Asset::CanRead(const std::string &pFile, bool isBinary) {
2079 try {
2080 shared_ptr<IOStream> stream(OpenFile(path: pFile.c_str(), mode: "rb", absolute: true));
2081 if (!stream) {
2082 return false;
2083 }
2084 std::vector<char> sceneData;
2085 rapidjson::Document doc = ReadDocument(stream&: *stream, isBinary, sceneData);
2086 asset.Read(doc);
2087 } catch (...) {
2088 return false;
2089 }
2090 return true;
2091}
2092
2093inline void Asset::SetAsBinary() {
2094 if (!mBodyBuffer) {
2095 mBodyBuffer = buffers.Create(id: "binary_glTF");
2096 mBodyBuffer->MarkAsSpecial();
2097 }
2098}
2099
2100// As required extensions are only a concept in glTF 2.0, this is here
2101// instead of glTFCommon.h
2102#define CHECK_REQUIRED_EXT(EXT) \
2103 if (exts.find(#EXT) != exts.end()) extensionsRequired.EXT = true;
2104
2105inline void Asset::ReadExtensionsRequired(Document &doc) {
2106 Value *extsRequired = FindArray(val&: doc, memberId: "extensionsRequired");
2107 if (nullptr == extsRequired) {
2108 return;
2109 }
2110
2111 std::gltf_unordered_map<std::string, bool> exts;
2112 for (unsigned int i = 0; i < extsRequired->Size(); ++i) {
2113 if ((*extsRequired)[i].IsString()) {
2114 exts[(*extsRequired)[i].GetString()] = true;
2115 }
2116 }
2117
2118 CHECK_REQUIRED_EXT(KHR_draco_mesh_compression);
2119
2120#undef CHECK_REQUIRED_EXT
2121}
2122
2123inline void Asset::ReadExtensionsUsed(Document &doc) {
2124 Value *extsUsed = FindArray(val&: doc, memberId: "extensionsUsed");
2125 if (!extsUsed) return;
2126
2127 std::gltf_unordered_map<std::string, bool> exts;
2128
2129 for (unsigned int i = 0; i < extsUsed->Size(); ++i) {
2130 if ((*extsUsed)[i].IsString()) {
2131 exts[(*extsUsed)[i].GetString()] = true;
2132 }
2133 }
2134
2135 CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
2136 CHECK_EXT(KHR_materials_specular);
2137 CHECK_EXT(KHR_materials_unlit);
2138 CHECK_EXT(KHR_lights_punctual);
2139 CHECK_EXT(KHR_texture_transform);
2140 CHECK_EXT(KHR_materials_sheen);
2141 CHECK_EXT(KHR_materials_clearcoat);
2142 CHECK_EXT(KHR_materials_transmission);
2143 CHECK_EXT(KHR_materials_volume);
2144 CHECK_EXT(KHR_materials_ior);
2145 CHECK_EXT(KHR_materials_emissive_strength);
2146 CHECK_EXT(KHR_draco_mesh_compression);
2147 CHECK_EXT(KHR_texture_basisu);
2148
2149#undef CHECK_EXT
2150}
2151
2152inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool /*absolute*/) {
2153#ifdef ASSIMP_API
2154 return mIOSystem->Open(pFile: path, pMode: mode);
2155#else
2156 if (path.size() < 2) return nullptr;
2157 if (!absolute && path[1] != ':' && path[0] != '/') { // relative?
2158 path = mCurrentAssetDir + path;
2159 }
2160 FILE *f = fopen(path.c_str(), mode);
2161 return f ? new IOStream(f) : nullptr;
2162#endif
2163}
2164
2165inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) {
2166 std::string id = str;
2167
2168 if (!id.empty()) {
2169 if (mUsedIds.find(x: id) == mUsedIds.end())
2170 return id;
2171
2172 id += "_";
2173 }
2174
2175 id += suffix;
2176
2177 Asset::IdMap::iterator it = mUsedIds.find(x: id);
2178 if (it == mUsedIds.end()) {
2179 return id;
2180 }
2181
2182 std::vector<char> buffer;
2183 buffer.resize(new_size: id.size() + 16);
2184 int offset = ai_snprintf(s: buffer.data(), maxlen: buffer.size(), format: "%s_", id.c_str());
2185 for (int i = 0; it != mUsedIds.end(); ++i) {
2186 ai_snprintf(s: buffer.data() + offset, maxlen: buffer.size() - offset, format: "%d", i);
2187 id = buffer.data();
2188 it = mUsedIds.find(x: id);
2189 }
2190
2191 return id;
2192}
2193
2194#if _MSC_VER
2195# pragma warning(pop)
2196#endif // _MSC_VER
2197
2198} // namespace glTF2
2199

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtquick3d/src/3rdparty/assimp/src/code/AssetLib/glTF2/glTF2Asset.inl