1 /*
2 * Copyright (C) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "gltf/gltf2_util.h"
17
18 #include <algorithm>
19 #include <cinttypes>
20 #include <cstring>
21
22 #include <base/containers/fixed_string.h>
23 #include <core/io/intf_file_manager.h>
24 #include <core/log.h>
25 #include <core/namespace.h>
26
27 CORE3D_BEGIN_NAMESPACE()
28 using namespace BASE_NS;
29 using namespace CORE_NS;
30
31 namespace GLTF2 {
32 namespace {
33 using ByteBuffer = vector<uint8_t>;
34
Base64(uint8_t c)35 inline uint8_t Base64(uint8_t c)
36 {
37 /*
38 '+' 62
39 '/' 63
40 '=' 0
41 '0' '9' 52 61
42 'A' 'Z' 0 25
43 'a' 'z' 26 51
44 */
45 if (c <= 'z' && c >= 'a') {
46 return c - 'a' + 26u;
47 } else if (c <= 'Z' && c >= 'A') {
48 return c - 'A' + 0u;
49 } else if (c <= '9' && c >= '0') {
50 return c - '0' + 52u;
51 } else if (c == '=') {
52 return 0u;
53 } else if (c == '/') {
54 return 63u;
55 } else if (c == '+') {
56 return 62u;
57 } else {
58 CORE_LOG_W("Base64: invalid character");
59 return 0u;
60 }
61 }
62
Base64Decode(string_view const & encodedString,size_t aOffset,vector<uint8_t> & decodedBinary)63 void Base64Decode(string_view const& encodedString, size_t aOffset, vector<uint8_t>& decodedBinary)
64 {
65 // a base64 encoded string should be padded to the next four bytes with '=' characters.
66 const size_t encodedDataSize = encodedString.size() - aOffset;
67 if (encodedDataSize % 4u) {
68 CORE_LOG_W("base64: encoded string not multiple of 4");
69 return;
70 }
71
72 // the length of the decoded binary data will be 3/4 of the encoded string.
73 decodedBinary.resize(encodedDataSize * 3u / 4u);
74
75 auto out = decodedBinary.data();
76
77 uint32_t bits = 0u;
78 for (auto it = encodedString.data() + aOffset, end = encodedString.data() + encodedString.size(); it != end;
79 bits = 0u) {
80 // map each character into a 6 bit value and pack the values tightly in 24 bits.
81 bits |= Base64((uint8_t)(*it++)) << (3u * 6u);
82 bits |= Base64((uint8_t)(*it++)) << (2u * 6u);
83 bits |= Base64((uint8_t)(*it++)) << (1u * 6u);
84 bits |= Base64((uint8_t)(*it++)) << (0u * 6u);
85
86 // store the resulting decoded 8 bit decoded values
87 *out++ = uint8_t((bits >> 16u) & 0xff);
88 *out++ = uint8_t((bits >> 8u) & 0xff);
89 *out++ = uint8_t((bits >> 0u) & 0xff);
90 }
91
92 // pop as many decoded bytes as there's padding in the encoded string.
93 for (auto it = encodedString.data() + encodedDataSize; *it-- == '=';) {
94 decodedBinary.pop_back();
95 }
96 }
97
Read(const uint8_t * src,uint32_t componentByteSize,uint32_t componentCount,uint32_t elementSize,size_t byteStride,uint32_t count)98 vector<uint8_t> Read(const uint8_t* src, uint32_t componentByteSize, uint32_t componentCount, uint32_t elementSize,
99 size_t byteStride, uint32_t count)
100 {
101 vector<uint8_t> result;
102
103 if (elementSize > 0u) {
104 const size_t sourceSize = componentCount * componentByteSize * count;
105 result.reserve(sourceSize);
106
107 if (elementSize == byteStride || byteStride == 0u) {
108 result.insert(result.end(), src, src + sourceSize);
109 } else {
110 const uint8_t* source = src;
111
112 for (size_t i = 0u; i < count; ++i) {
113 result.insert(result.end(), source, source + elementSize);
114 source += byteStride;
115 }
116 }
117 }
118
119 return result;
120 }
121
Read(Accessor const & accessor)122 vector<uint8_t> Read(Accessor const& accessor)
123 {
124 if (!accessor.bufferView->data) {
125 return {};
126 }
127
128 const size_t startOffset = accessor.bufferView->byteOffset + accessor.byteOffset;
129 const size_t bufferLength = accessor.bufferView->buffer->byteLength;
130 const size_t bufferRemaining = bufferLength - startOffset;
131
132 const uint8_t* src = accessor.bufferView->data + accessor.byteOffset;
133
134 const uint32_t componentByteSize = GetComponentByteSize(accessor.componentType);
135 const uint32_t componentCount = GetComponentsCount(accessor.type);
136 const uint32_t elementSize = componentCount * componentByteSize;
137 const size_t byteStride = accessor.bufferView->byteStride;
138 const uint32_t count = accessor.count;
139
140 size_t readBytes = 0u;
141
142 if (elementSize == byteStride || byteStride == 0u) {
143 readBytes = accessor.count * elementSize;
144 } else {
145 readBytes = accessor.count * byteStride;
146 }
147
148 if (bufferRemaining < readBytes) {
149 // Avoid buffer overflow.
150 return vector<uint8_t>();
151 }
152
153 return Read(src, componentByteSize, componentCount, elementSize, byteStride, count);
154 }
155
156 template<class T>
CopySparseElements(ByteBuffer & destination,ByteBuffer & source,ByteBuffer & indices,uint32_t elementSize,size_t count)157 void CopySparseElements(
158 ByteBuffer& destination, ByteBuffer& source, ByteBuffer& indices, uint32_t elementSize, size_t count)
159 {
160 T* indicesPtr = reinterpret_cast<T*>(indices.data());
161 auto const end = ptrdiff_t(destination.data() + destination.size());
162 for (size_t i = 0u; i < count; ++i) {
163 const uint8_t* sourcePtr = source.data() + (i * elementSize);
164 uint8_t* destinationPtr = destination.data() + (indicesPtr[i] * elementSize);
165 auto const left = end - ptrdiff_t(destinationPtr);
166 if (left > 0) {
167 if (!CloneData(destinationPtr, size_t(left), sourcePtr, elementSize)) {
168 CORE_LOG_E("Copying of sparseElements failed.");
169 }
170 }
171 }
172 }
173
LoadBuffer(Data const & data,Buffer & buffer,IFileManager & fileManager)174 BufferLoadResult LoadBuffer(Data const& data, Buffer& buffer, IFileManager& fileManager)
175 {
176 if (IsDataURI(buffer.uri)) {
177 string_view type;
178 if (!DecodeDataURI(buffer.data, buffer.uri, 0u, false, type)) {
179 return BufferLoadResult { false, "Failed to decode data uri: " + buffer.uri + '\n' };
180 }
181 } else {
182 uint32_t offset;
183 string_view uri;
184 if (buffer.uri.size()) {
185 uri = buffer.uri;
186 offset = 0u;
187 } else if (data.defaultResourcesOffset >= 0u) {
188 uri = data.defaultResources;
189 offset = static_cast<uint32_t>(data.defaultResourcesOffset);
190 } else {
191 return BufferLoadResult { false, "Failed to open buffer: " + buffer.uri + '\n' };
192 }
193
194 IFile::Ptr file;
195 IFile* filePtr = nullptr;
196 if (data.memoryFile_) {
197 filePtr = data.memoryFile_.get();
198 } else {
199 string fileName;
200 fileName.reserve(data.filepath.size() + 1u + uri.size());
201 fileName.append(data.filepath);
202 fileName.append("/");
203 fileName.append(uri);
204
205 file = fileManager.OpenFile(fileName);
206 filePtr = file.get();
207 }
208 if (!filePtr) {
209 return BufferLoadResult { false, "Failed open uri: " + buffer.uri + '\n' };
210 }
211
212 filePtr->Seek(offset);
213
214 // Check that buffer does not overflow over the file boundaries.
215 const auto length = filePtr->GetLength();
216 const auto remaining = length - filePtr->GetPosition();
217 if (remaining < buffer.byteLength) {
218 // Clamp buffer to file boundary.
219 CORE_LOG_W("Buffer size %zu larger than file size (%" PRIu64 " remaining).", buffer.byteLength, remaining);
220 buffer.byteLength = static_cast<size_t>(remaining);
221 }
222
223 buffer.data.resize(buffer.byteLength);
224
225 if (filePtr->Read(buffer.data.data(), buffer.byteLength) != buffer.byteLength) {
226 return BufferLoadResult { false, "Failed to read buffer: " + buffer.uri + '\n' };
227 }
228 }
229
230 return BufferLoadResult {};
231 }
232
LoadSparseAccessor(Accessor const & accessor,GLTFLoadDataResult & result)233 void LoadSparseAccessor(Accessor const& accessor, GLTFLoadDataResult& result)
234 {
235 auto const& sparseIndicesBufferView = accessor.sparse.indices.bufferView;
236 vector<uint8_t> sparseIndicesData;
237 if (sparseIndicesBufferView->buffer) {
238 const uint8_t* src = sparseIndicesBufferView->data + accessor.sparse.indices.byteOffset;
239
240 auto const componentCount = 1u;
241 auto const componentByteSize = GetComponentByteSize(accessor.sparse.indices.componentType);
242 auto const elementSize = componentCount * componentByteSize;
243 auto const byteStride = accessor.bufferView->byteStride;
244 auto const count = accessor.sparse.count;
245
246 sparseIndicesData = Read(src, componentByteSize, componentCount, elementSize, byteStride, count);
247 }
248
249 auto const& sparseValuesBufferView = accessor.sparse.values.bufferView;
250 if (sparseValuesBufferView->buffer) {
251 vector<uint8_t> sourceData;
252
253 const uint8_t* src = sparseValuesBufferView->data + accessor.sparse.indices.byteOffset;
254 auto const componentCount = GetComponentsCount(accessor.type);
255 auto const componentByteSize = GetComponentByteSize(accessor.componentType);
256 auto const elementSize = componentCount * componentByteSize;
257 auto const byteStride = accessor.bufferView->byteStride;
258 auto const count = accessor.sparse.count;
259
260 sourceData = Read(src, componentByteSize, componentCount, elementSize, byteStride, count);
261
262 switch (accessor.sparse.indices.componentType) {
263 case ComponentType::UNSIGNED_BYTE: {
264 CopySparseElements<uint8_t>(
265 result.data, sourceData, sparseIndicesData, elementSize, accessor.sparse.count);
266 break;
267 }
268
269 case ComponentType::UNSIGNED_SHORT: {
270 CopySparseElements<uint16_t>(
271 result.data, sourceData, sparseIndicesData, elementSize, accessor.sparse.count);
272 break;
273 }
274
275 case ComponentType::UNSIGNED_INT: {
276 CopySparseElements<uint32_t>(
277 result.data, sourceData, sparseIndicesData, elementSize, accessor.sparse.count);
278 break;
279 }
280
281 case ComponentType::BYTE:
282 case ComponentType::SHORT:
283 case ComponentType::INT:
284 case ComponentType::FLOAT:
285 default:
286 result.error += "invalid accessor.sparse.indices.componentType\n";
287 result.success = false;
288 break;
289 }
290 }
291 }
292
ReadUriToVector(const string_view filePath,IFileManager & fileManager,string_view const & uri,vector<uint8_t> & out)293 bool ReadUriToVector(
294 const string_view filePath, IFileManager& fileManager, string_view const& uri, vector<uint8_t>& out)
295 {
296 string filepath;
297 filepath.reserve(filePath.size() + 1u + uri.size());
298 filepath += filePath;
299 filepath += '/';
300 filepath += uri;
301 auto file = fileManager.OpenFile(filepath);
302 if (file) {
303 const size_t count = static_cast<size_t>(file->GetLength());
304 out.resize(count);
305 return file->Read(out.data(), count) == count;
306 }
307 return false;
308 }
309 } // namespace
310
311 // Helper functions to access GLTF2 data
GetAttributeType(const string_view dataType,AttributeBase & out)312 bool GetAttributeType(const string_view dataType, AttributeBase& out)
313 {
314 const char* data = dataType.data();
315 const unsigned int size = static_cast<unsigned int>(dataType.size());
316
317 AttributeBase attribute;
318 attribute.type = AttributeType::INVALID;
319 attribute.index = 0u;
320 if (data == nullptr) {
321 return false;
322 }
323
324 /*
325 POSITION, NORMAL, TANGENT, TEXCOORD_0, TEXCOORD_1, COLOR_0, JOINTS_0, and WEIGHTS_0
326 */
327 if (dataType == "POSITION") {
328 attribute.type = AttributeType::POSITION;
329 } else if (dataType == "NORMAL") {
330 attribute.type = AttributeType::NORMAL;
331 } else if (dataType == "TANGENT") {
332 attribute.type = AttributeType::TANGENT;
333 } else if (dataType.find("TEXCOORD_") == 0u) {
334 attribute.type = AttributeType::TEXCOORD;
335 attribute.index = 0u;
336
337 if (size > 9u) {
338 attribute.index = (unsigned int)(data[9u] - '0'); // NOTE: check if size is over 10 => index more than 9
339 }
340 } else if (dataType.find("COLOR_") == 0u) {
341 attribute.type = AttributeType::COLOR;
342 attribute.index = 0u;
343
344 if (size > 6u) {
345 attribute.index = (unsigned int)(data[6u] - '0'); // NOTE: check if size is over 7 => index more than 9
346 }
347 } else if (dataType.find("JOINTS_") == 0u) {
348 attribute.type = AttributeType::JOINTS;
349 attribute.index = 0u;
350
351 if (size > 7u) {
352 attribute.index = (unsigned int)(data[7u] - '0'); // NOTE: check if size is over 8 => index more than 9
353 }
354 } else if (dataType.find("WEIGHTS_") == 0u) {
355 attribute.type = AttributeType::WEIGHTS;
356 attribute.index = 0u;
357
358 if (size > 8u) {
359 attribute.index = (unsigned int)(data[8u] - '0'); // NOTE: check if size is over 9 => index more than 9
360 }
361 } else {
362 return false;
363 }
364
365 if (attribute.index > 9u) {
366 return false;
367 }
368
369 out = attribute;
370 return true;
371 }
372
GetMimeType(const string_view type,MimeType & out)373 bool GetMimeType(const string_view type, MimeType& out)
374 {
375 out = MimeType::INVALID;
376
377 if (type == "image/jpeg") {
378 out = MimeType::JPEG;
379 } else if (type == "image/png") {
380 out = MimeType::PNG;
381 } else if (type == "image/ktx") {
382 out = MimeType::KTX;
383 } else if (type == "image/ktx2") {
384 out = MimeType::KTX2;
385 } else if (type == "image/vnd-ms.dds") {
386 out = MimeType::DDS;
387 } else if (type == "application/octet-stream") {
388 out = MimeType::KTX; // Probably it's a KTX file, bundled by using an external application.
389 }
390 return out != MimeType::INVALID;
391 }
392
GetDataType(const string_view dataType,DataType & out)393 bool GetDataType(const string_view dataType, DataType& out)
394 {
395 out = DataType::INVALID;
396
397 if (dataType == "SCALAR") {
398 out = DataType::SCALAR;
399 } else if (dataType == "VEC2") {
400 out = DataType::VEC2;
401 } else if (dataType == "VEC3") {
402 out = DataType::VEC3;
403 } else if (dataType == "VEC4") {
404 out = DataType::VEC4;
405 } else if (dataType == "MAT2") {
406 out = DataType::MAT2;
407 } else if (dataType == "MAT3") {
408 out = DataType::MAT3;
409 } else if (dataType == "MAT4") {
410 out = DataType::MAT4;
411 }
412 return out != DataType::INVALID;
413 }
414
GetCameraType(const string_view type,CameraType & out)415 bool GetCameraType(const string_view type, CameraType& out)
416 {
417 out = CameraType::INVALID;
418
419 if (type == "perspective") {
420 out = CameraType::PERSPECTIVE;
421 } else if (type == "orthographic") {
422 out = CameraType::ORTHOGRAPHIC;
423 }
424 return out != CameraType::INVALID;
425 }
426
GetAlphaMode(const string_view dataType,AlphaMode & out)427 bool GetAlphaMode(const string_view dataType, AlphaMode& out)
428 {
429 out = AlphaMode::OPAQUE;
430
431 bool result = true;
432
433 if (dataType == "BLEND") {
434 out = AlphaMode::BLEND;
435 } else if (dataType == "MASK") {
436 out = AlphaMode::MASK;
437 } else if (dataType == "OPAQUE") {
438 out = AlphaMode::OPAQUE;
439 } else {
440 result = false;
441 }
442 return result;
443 }
444
GetBlendMode(const string_view dataType,BlendMode & out)445 bool GetBlendMode(const string_view dataType, BlendMode& out)
446 {
447 out = BlendMode::REPLACE;
448
449 bool result = true;
450
451 if (dataType == "transparentColor") {
452 out = BlendMode::TRANSPARENT_COLOR;
453 } else if (dataType == "transparentAlpha") {
454 out = BlendMode::TRANSPARENT_ALPHA;
455 } else if (dataType == "add") {
456 out = BlendMode::ADD;
457 } else if (dataType == "modulate") {
458 out = BlendMode::MODULATE;
459 } else if (dataType == "replace") {
460 out = BlendMode::REPLACE;
461 } else if (dataType == "none") {
462 out = BlendMode::NONE;
463 } else {
464 result = false;
465 }
466 return result;
467 }
468
GetAnimationInterpolation(string_view interpolation,AnimationInterpolation & out)469 bool GetAnimationInterpolation(string_view interpolation, AnimationInterpolation& out)
470 {
471 // Default type is linear, this is not required attribute.
472 out = AnimationInterpolation::LINEAR;
473
474 if (interpolation == "LINEAR") {
475 out = AnimationInterpolation::LINEAR;
476 } else if (interpolation == "STEP") {
477 out = AnimationInterpolation::STEP;
478 } else if (interpolation == "CUBICSPLINE") {
479 out = AnimationInterpolation::SPLINE;
480 }
481 return true;
482 }
483
GetAnimationPath(string_view path,AnimationPath & out)484 bool GetAnimationPath(string_view path, AnimationPath& out)
485 {
486 bool result = true;
487
488 if (path == "translation") {
489 out = AnimationPath::TRANSLATION;
490 } else if (path == "rotation") {
491 out = AnimationPath::ROTATION;
492 } else if (path == "scale") {
493 out = AnimationPath::SCALE;
494 } else if (path == "weights") {
495 out = AnimationPath::WEIGHTS;
496 } else if (path == "visible") {
497 out = AnimationPath::VISIBLE;
498 } else if (path == "material.opacity") {
499 out = AnimationPath::OPACITY;
500 } else {
501 result = false;
502 }
503 return result;
504 }
505
506 namespace {
507 constexpr const char* ATTRIBUTE_TYPES[] = { "NORMAL", "POSITION", "TANGENT" };
508 constexpr const char* TEXCOORD_ATTRIBUTES[] = { "TEXCOORD_0", "TEXCOORD_1", "TEXCOORD_2", "TEXCOORD_3", "TEXCOORD_4" };
509 constexpr const char* COLOR_ATTRIBUTES[] = { "COLOR_0", "COLOR_1", "COLOR_2", "COLOR_3", "COLOR_4" };
510 constexpr const char* JOINTS_ATTRIBUTES[] = { "JOINTS_0", "JOINTS_1", "JOINTS_2", "JOINTS_3", "JOINTS_4" };
511 constexpr const char* WEIGHTS_ATTRIBUTES[] = { "WEIGHTS_0", "WEIGHTS_1", "WEIGHTS_2", "WEIGHTS_3", "WEIGHTS_4" };
512 constexpr const char* ATTRIBUTE_INVALID = "INVALID";
513 } // namespace
514
GetAttributeType(AttributeBase dataType)515 string_view GetAttributeType(AttributeBase dataType)
516 {
517 if ((dataType.type < AttributeType::NORMAL)) {
518 } else if (dataType.type <= AttributeType::TANGENT) {
519 return ATTRIBUTE_TYPES[static_cast<int>(dataType.type)];
520 } else if (dataType.type == AttributeType::TEXCOORD) {
521 if (dataType.index < countof(TEXCOORD_ATTRIBUTES)) {
522 return TEXCOORD_ATTRIBUTES[dataType.index];
523 }
524 } else if (dataType.type == AttributeType::COLOR) {
525 if (dataType.index < countof(COLOR_ATTRIBUTES)) {
526 return COLOR_ATTRIBUTES[dataType.index];
527 }
528 } else if (dataType.type == AttributeType::JOINTS) {
529 if (dataType.index < countof(JOINTS_ATTRIBUTES)) {
530 return JOINTS_ATTRIBUTES[dataType.index];
531 }
532 } else if (dataType.type == AttributeType::WEIGHTS) {
533 if (dataType.index < countof(WEIGHTS_ATTRIBUTES)) {
534 return WEIGHTS_ATTRIBUTES[dataType.index];
535 }
536 }
537 return ATTRIBUTE_INVALID;
538 }
539
GetMimeType(MimeType type)540 string_view GetMimeType(MimeType type)
541 {
542 switch (type) {
543 case MimeType::JPEG:
544 return "image/jpeg";
545 case MimeType::PNG:
546 return "image/png";
547 case MimeType::KTX:
548 return "image/ktx";
549 case MimeType::DDS:
550 return "image/vnd-ms.dds";
551 case MimeType::KTX2:
552 return "image/ktx2";
553
554 case MimeType::INVALID:
555 default:
556 break;
557 }
558 return "INVALID";
559 }
560
GetDataType(DataType dataType)561 string_view GetDataType(DataType dataType)
562 {
563 switch (dataType) {
564 case DataType::SCALAR:
565 return "SCALAR";
566 case DataType::VEC2:
567 return "VEC2";
568 case DataType::VEC3:
569 return "VEC3";
570 case DataType::VEC4:
571 return "VEC4";
572 case DataType::MAT2:
573 return "MAT2";
574 case DataType::MAT3:
575 return "MAT3";
576 case DataType::MAT4:
577 return "MAT4";
578 case DataType::INVALID:
579 default:
580 break;
581 }
582 return "INVALID";
583 }
584
GetCameraType(CameraType type)585 string_view GetCameraType(CameraType type)
586 {
587 switch (type) {
588 case CameraType::PERSPECTIVE:
589 return "perspective";
590 case CameraType::ORTHOGRAPHIC:
591 return "orthographic";
592 case CameraType::INVALID:
593 default:
594 break;
595 }
596
597 return "INVALID";
598 }
599
600 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
GetLightType(const string_view type,LightType & out)601 bool GetLightType(const string_view type, LightType& out)
602 {
603 out = LightType::INVALID;
604
605 if (type == "directional") {
606 out = LightType::DIRECTIONAL;
607 } else if (type == "point") {
608 out = LightType::POINT;
609 } else if (type == "spot") {
610 out = LightType::SPOT;
611 } else if (type == "ambient") {
612 out = LightType::AMBIENT;
613 }
614 return out != LightType::INVALID;
615 }
616
GetLightType(LightType type)617 string_view GetLightType(LightType type)
618 {
619 switch (type) {
620 case LightType::DIRECTIONAL:
621 return "directional";
622 case LightType::POINT:
623 return "point";
624 case LightType::SPOT:
625 return "spot";
626 case LightType::AMBIENT:
627 return "ambient";
628 case LightType::INVALID:
629 default:
630 break;
631 }
632
633 return "INVALID";
634 }
635 #endif
636
GetAlphaMode(AlphaMode aMode)637 string_view GetAlphaMode(AlphaMode aMode)
638 {
639 switch (aMode) {
640 case AlphaMode::BLEND:
641 return "BLEND";
642 case AlphaMode::MASK:
643 return "MASK";
644 case AlphaMode::OPAQUE:
645 default:
646 return "OPAQUE";
647 }
648 }
649
GetBlendMode(BlendMode data)650 string_view GetBlendMode(BlendMode data)
651 {
652 switch (data) {
653 case BlendMode::TRANSPARENT_ALPHA:
654 return "transparentAlpha";
655 case BlendMode::TRANSPARENT_COLOR:
656 return "transparentColor";
657 case BlendMode::ADD:
658 return "add";
659 case BlendMode::MODULATE:
660 return "modulate";
661
662 case BlendMode::NONE:
663 case BlendMode::REPLACE:
664 default:
665 return "replace";
666 }
667 }
668
GetAnimationInterpolation(AnimationInterpolation interpolation)669 string_view GetAnimationInterpolation(AnimationInterpolation interpolation)
670 {
671 switch (interpolation) {
672 case AnimationInterpolation::STEP:
673 return "STEP";
674 default:
675 [[fallthrough]];
676 case AnimationInterpolation::INVALID:
677 [[fallthrough]];
678 case AnimationInterpolation::LINEAR:
679 return "LINEAR";
680 case AnimationInterpolation::SPLINE:
681 return "CUBICSPLINE";
682 }
683 }
684
GetAnimationPath(AnimationPath path)685 string_view GetAnimationPath(AnimationPath path)
686 {
687 switch (path) {
688 default:
689 [[fallthrough]];
690 case AnimationPath::INVALID:
691 CORE_LOG_W("invalid animation path %d", path);
692 return "translation";
693 case AnimationPath::TRANSLATION:
694 return "translation";
695 case AnimationPath::ROTATION:
696 return "rotation";
697 case AnimationPath::SCALE:
698 return "scale";
699 case AnimationPath::WEIGHTS:
700 return "weights";
701 }
702 }
703
GetComponentByteSize(ComponentType component)704 uint32_t GetComponentByteSize(ComponentType component)
705 {
706 switch (component) {
707 case ComponentType::BYTE:
708 case ComponentType::UNSIGNED_BYTE:
709 return 1u;
710
711 case ComponentType::SHORT:
712 case ComponentType::UNSIGNED_SHORT:
713 return 2u;
714
715 case ComponentType::UNSIGNED_INT:
716 case ComponentType::INT:
717 case ComponentType::FLOAT:
718 return 4u;
719
720 default:
721 return 0u;
722 }
723 }
724
GetComponentsCount(DataType type)725 uint32_t GetComponentsCount(DataType type)
726 {
727 switch (type) {
728 case DataType::SCALAR:
729 return 1u;
730 case DataType::VEC2:
731 return 2u;
732 case DataType::VEC3:
733 return 3u;
734 case DataType::VEC4:
735 return 4u;
736 case DataType::MAT2:
737 return 4u;
738 case DataType::MAT3:
739 return 9u;
740 case DataType::MAT4:
741 return 16u;
742
743 case DataType::INVALID:
744 default:
745 return 0u;
746 }
747 }
748
GetAlternativeType(ComponentType component,size_t aNewByteCount)749 ComponentType GetAlternativeType(ComponentType component, size_t aNewByteCount)
750 {
751 switch (component) {
752 case ComponentType::UNSIGNED_SHORT:
753 case ComponentType::UNSIGNED_INT:
754 case ComponentType::UNSIGNED_BYTE: {
755 switch (aNewByteCount) {
756 case 1u:
757 return ComponentType::UNSIGNED_BYTE;
758 case 2u:
759 return ComponentType::UNSIGNED_SHORT;
760 case 4u:
761 return ComponentType::UNSIGNED_INT;
762 default:
763 break;
764 }
765 break;
766 }
767
768 case ComponentType::BYTE:
769 case ComponentType::SHORT:
770 case ComponentType::INT: {
771 switch (aNewByteCount) {
772 case 1u:
773 return ComponentType::BYTE;
774 case 2u:
775 return ComponentType::SHORT;
776 case 4u:
777 return ComponentType::INT;
778 default:
779 break;
780 }
781 break;
782 }
783
784 case ComponentType::FLOAT:
785 default:
786 // do nothing
787 break;
788 }
789
790 return component;
791 }
792
793 namespace {
794 struct AttributeValidation {
795 array_view<const DataType> dataTypes;
796 array_view<const ComponentType> componentTypes;
797 };
798 struct AttributeValidationErrors {
799 string_view dataTypeError;
800 string_view componentTypeError;
801 };
802
803 constexpr const DataType NORMAL_DATA_TYPES[] = { DataType::VEC3 };
804 constexpr const ComponentType NORMAL_COMPONENT_TYPES[] = { ComponentType::FLOAT };
805 constexpr const DataType POSITION_DATA_TYPES[] = { DataType::VEC3 };
806 constexpr const ComponentType POSITION_COMPONENT_TYPES[] = { ComponentType::FLOAT };
807 constexpr const DataType TANGENT_DATA_TYPES[] = { DataType::VEC4 };
808 constexpr const ComponentType TANGENT_COMPONENT_TYPES[] = { ComponentType::FLOAT };
809 constexpr const DataType TEXCOORD_DATA_TYPES[] = { DataType::VEC2 };
810 constexpr const ComponentType TEXCOORD_COMPONENT_TYPES[] = { ComponentType::FLOAT, ComponentType::UNSIGNED_BYTE,
811 ComponentType::UNSIGNED_SHORT };
812 constexpr const DataType COLOR_DATA_TYPES[] = { DataType::VEC3, DataType::VEC4 };
813 constexpr const ComponentType COLOR_COMPONENT_TYPES[] = { ComponentType::FLOAT, ComponentType::UNSIGNED_BYTE,
814 ComponentType::UNSIGNED_SHORT };
815 constexpr const DataType JOINTS_DATA_TYPES[] = { DataType::VEC4 };
816 constexpr const ComponentType JOINTS_COMPONENT_TYPES[] = { ComponentType::UNSIGNED_BYTE,
817 ComponentType::UNSIGNED_SHORT };
818 constexpr const DataType WEIGHTS_DATA_TYPES[] = { DataType::VEC4 };
819 constexpr const ComponentType WEIGHTS_COMPONENT_TYPES[] = { ComponentType::FLOAT, ComponentType::UNSIGNED_BYTE,
820 ComponentType::UNSIGNED_SHORT };
821
822 constexpr const AttributeValidation ATTRIBUTE_VALIDATION[] = {
823 { NORMAL_DATA_TYPES, NORMAL_COMPONENT_TYPES },
824 { POSITION_DATA_TYPES, POSITION_COMPONENT_TYPES },
825 { TANGENT_DATA_TYPES, TANGENT_COMPONENT_TYPES },
826 { TEXCOORD_DATA_TYPES, TEXCOORD_COMPONENT_TYPES },
827 { COLOR_DATA_TYPES, COLOR_COMPONENT_TYPES },
828 { JOINTS_DATA_TYPES, JOINTS_COMPONENT_TYPES },
829 { WEIGHTS_DATA_TYPES, WEIGHTS_COMPONENT_TYPES },
830 };
831
832 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
833 constexpr const ComponentType NORMAL_COMPONENT_TYPES_Q[] = { ComponentType::BYTE, ComponentType::SHORT,
834 ComponentType::FLOAT };
835 constexpr const ComponentType POSITION_COMPONENT_TYPES_Q[] = { ComponentType::BYTE, ComponentType::UNSIGNED_BYTE,
836 ComponentType::SHORT, ComponentType::UNSIGNED_SHORT, ComponentType::FLOAT };
837 constexpr const ComponentType TANGENT_COMPONENT_TYPES_Q[] = { ComponentType::BYTE, ComponentType::SHORT,
838 ComponentType::FLOAT };
839 constexpr const ComponentType TEXCOORD_COMPONENT_TYPES_Q[] = { ComponentType::BYTE, ComponentType::UNSIGNED_BYTE,
840 ComponentType::SHORT, ComponentType::UNSIGNED_SHORT, ComponentType::FLOAT };
841
842 constexpr const AttributeValidation ATTRIBUTE_VALIDATION_Q[] = {
843 { NORMAL_DATA_TYPES, NORMAL_COMPONENT_TYPES_Q },
844 { POSITION_DATA_TYPES, POSITION_COMPONENT_TYPES_Q },
845 { TANGENT_DATA_TYPES, TANGENT_COMPONENT_TYPES_Q },
846 { TEXCOORD_DATA_TYPES, TEXCOORD_COMPONENT_TYPES_Q },
847 { COLOR_DATA_TYPES, COLOR_COMPONENT_TYPES },
848 { JOINTS_DATA_TYPES, JOINTS_COMPONENT_TYPES },
849 { WEIGHTS_DATA_TYPES, WEIGHTS_COMPONENT_TYPES },
850 };
851 #endif
852
853 constexpr const AttributeValidationErrors ATTRIBUTE_VALIDATION_ERRORS[] = {
854 { "NORMAL accessor must use VEC3.", "NORMAL accessor must use FLOAT." },
855 { "POSITION accessor must use VEC3.", "POSITION accessor must use FLOAT." },
856 { "TANGENT accessor must use VEC4.", "TANGENT accessor must use FLOAT." },
857 { "TEXCOORD accessor must use VEC2.", "TEXCOORD accessor must use FLOAT, UNSIGNED_BYTE, or UNSIGNED_SHORT." },
858 { "COLOR accessor must use VEC3 or VEC4.", "COLOR accessor must use FLOAT, UNSIGNED_BYTE, or UNSIGNED_SHORT." },
859 { "JOINTS accessor must use VEC4.", "JOINTS accessor must use UNSIGNED_BYTE or UNSIGNED_SHORT." },
860 { "WEIGHTS accessor must use VEC4.", "WEIGHTS accessor must use FLOAT, UNSIGNED_BYTE, or UNSIGNED_SHORT." },
861 };
862 } // namespace
863
ValidatePrimitiveAttribute(AttributeType attribute,DataType accessorType,ComponentType accessorComponentType)864 string_view ValidatePrimitiveAttribute(
865 AttributeType attribute, DataType accessorType, ComponentType accessorComponentType)
866 {
867 const auto attributeIndex = static_cast<size_t>(attribute);
868 if (attributeIndex <= countof(ATTRIBUTE_VALIDATION)) {
869 auto& validation = ATTRIBUTE_VALIDATION[attributeIndex];
870 if (std::none_of(validation.dataTypes.begin(), validation.dataTypes.end(),
871 [accessorType](const DataType& validType) { return validType == accessorType; })) {
872 return ATTRIBUTE_VALIDATION_ERRORS[attributeIndex].dataTypeError;
873 } else if (std::none_of(validation.componentTypes.begin(), validation.componentTypes.end(),
874 [accessorComponentType](
875 const ComponentType& validType) { return validType == accessorComponentType; })) {
876 return ATTRIBUTE_VALIDATION_ERRORS[attributeIndex].componentTypeError;
877 }
878 } else {
879 return "invalid attribute";
880 }
881 return string_view();
882 }
883
ValidateMorphTargetAttribute(AttributeType attribute,DataType accessorType,ComponentType accessorComponentType)884 string_view ValidateMorphTargetAttribute(
885 AttributeType attribute, DataType accessorType, ComponentType accessorComponentType)
886 {
887 switch (attribute) {
888 case AttributeType::NORMAL:
889 if (accessorType != DataType::VEC3) {
890 return "ValidateMorphTargetAttribute: NORMAL accessor must use VEC3.";
891 }
892 if (accessorComponentType != ComponentType::FLOAT) {
893 return "ValidateMorphTargetAttribute: NORMAL accessor must use FLOAT.";
894 }
895 break;
896
897 case AttributeType::POSITION:
898 if (accessorType != DataType::VEC3) {
899 return "ValidateMorphTargetAttribute: POSITION accessor must use VEC3.";
900 }
901 if (accessorComponentType != ComponentType::FLOAT && accessorComponentType != ComponentType::SHORT) {
902 return "ValidateMorphTargetAttribute: POSITION accessor must use FLOAT or SHORT.";
903 }
904 break;
905
906 case AttributeType::TANGENT:
907 if (accessorType != DataType::VEC3) {
908 return "ValidateMorphTargetAttribute: TANGENT accessor must use VEC3.";
909 }
910 if (accessorComponentType != ComponentType::FLOAT) {
911 return "ValidateMorphTargetAttribute: TANGENT accessor must use FLOAT.";
912 }
913 break;
914
915 case AttributeType::INVALID:
916 case AttributeType::TEXCOORD:
917 case AttributeType::COLOR:
918 case AttributeType::JOINTS:
919 case AttributeType::WEIGHTS:
920 default:
921 return "ValidateMorphTargetAttribute: invalid attribute";
922 }
923
924 return string_view();
925 }
926
927 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
ValidatePrimitiveAttributeQuatization(AttributeType attribute,DataType accessorType,ComponentType accessorComponentType)928 string_view ValidatePrimitiveAttributeQuatization(
929 AttributeType attribute, DataType accessorType, ComponentType accessorComponentType)
930 {
931 const auto attributeIndex = static_cast<size_t>(attribute);
932 if (attributeIndex <= countof(ATTRIBUTE_VALIDATION_Q)) {
933 auto& validation = ATTRIBUTE_VALIDATION_Q[attributeIndex];
934 if (std::none_of(validation.dataTypes.begin(), validation.dataTypes.end(),
935 [accessorType](const DataType& validType) { return validType == accessorType; })) {
936 return ATTRIBUTE_VALIDATION_ERRORS[attributeIndex].dataTypeError;
937 } else if (std::none_of(validation.componentTypes.begin(), validation.componentTypes.end(),
938 [accessorComponentType](
939 const ComponentType& validType) { return validType == accessorComponentType; })) {
940 return ATTRIBUTE_VALIDATION_ERRORS[attributeIndex].componentTypeError;
941 }
942 } else {
943 return "invalid attribute";
944 }
945 return string_view();
946 }
947
ValidateMorphTargetAttributeQuantization(AttributeType attribute,DataType accessorType,ComponentType accessorComponentType)948 string_view ValidateMorphTargetAttributeQuantization(
949 AttributeType attribute, DataType accessorType, ComponentType accessorComponentType)
950 {
951 switch (attribute) {
952 case AttributeType::NORMAL:
953 if (accessorType != DataType::VEC3) {
954 return "ValidateMorphTargetAttribute: NORMAL accessor must use VEC3.";
955 }
956 if (accessorComponentType != ComponentType::FLOAT && accessorComponentType != ComponentType::BYTE &&
957 accessorComponentType != ComponentType::SHORT) {
958 return "ValidateMorphTargetAttribute: NORMAL accessor must use FLOAT, BYTE or SHORT.";
959 }
960 break;
961
962 case AttributeType::POSITION:
963 if (accessorType != DataType::VEC3) {
964 return "ValidateMorphTargetAttribute: POSITION accessor must use VEC3.";
965 }
966 if (accessorComponentType != ComponentType::FLOAT && accessorComponentType != ComponentType::BYTE &&
967 accessorComponentType != ComponentType::SHORT) {
968 return "ValidateMorphTargetAttribute: POSITION accessor must use FLOAT, BYTE or SHORT.";
969 }
970 break;
971
972 case AttributeType::TANGENT:
973 if (accessorType != DataType::VEC3) {
974 return "ValidateMorphTargetAttribute: TANGENT accessor must use VEC3.";
975 }
976 if (accessorComponentType != ComponentType::FLOAT && accessorComponentType != ComponentType::BYTE &&
977 accessorComponentType != ComponentType::SHORT) {
978 return "ValidateMorphTargetAttribute: TANGENT accessor must use FLOAT, BYTE or SHORT.";
979 }
980 break;
981
982 case AttributeType::INVALID:
983 case AttributeType::TEXCOORD:
984 case AttributeType::COLOR:
985 case AttributeType::JOINTS:
986 case AttributeType::WEIGHTS:
987 default:
988 return "ValidateMorphTargetAttribute: invalid attribute";
989 }
990
991 return string_view();
992 }
993 #endif
994
SplitFilename(const string_view source,string_view & base,string_view & path)995 void SplitFilename(const string_view source, string_view& base, string_view& path)
996 {
997 size_t slash = source.rfind('\\');
998 if (slash == string_view::npos) {
999 slash = source.rfind('/');
1000 }
1001 if (slash == string_view::npos) {
1002 base = source;
1003 path = {};
1004 return;
1005 }
1006 path = source.substr(0u, slash);
1007 base = source.substr(slash + 1u);
1008 }
1009
SplitBaseFilename(const string_view source,string_view & name,string_view & extension)1010 void SplitBaseFilename(const string_view source, string_view& name, string_view& extension)
1011 {
1012 const size_t slash = source.rfind('.');
1013 if (slash == string_view::npos) {
1014 extension = {};
1015 name = source;
1016 return;
1017 }
1018 name = source.substr(0u, slash);
1019 extension = source.substr(slash + 1u);
1020 }
1021
ParseDataUri(const string_view in,size_t & offsetToData)1022 string_view ParseDataUri(const string_view in, size_t& offsetToData)
1023 {
1024 offsetToData = 0u;
1025 // see: https://en.wikipedia.org/wiki/Data_URI_scheme
1026 auto const scheme = in.substr(0u, 5u);
1027 if (scheme != "data:") {
1028 return {}; // scheme is not data:
1029 }
1030 auto pos = in.find(",");
1031 if (pos == string_view::npos) {
1032 return {}; // no start of data.
1033 }
1034 auto const mediaType = in.substr(5u, pos - 5u);
1035 offsetToData = pos + 1u;
1036 pos = mediaType.find_last_of(
1037 ';'); // technically media-type could contain parameters. but the last parameter should be base64 here
1038 if (pos == string_view::npos) {
1039 return {}; // no encoding defined.
1040 }
1041 auto const encoding = mediaType.substr(pos + 1u);
1042 if (encoding != "base64") {
1043 return {};
1044 }
1045
1046 // NOTE: 0 to pos would return media-type without the base64 encoding option. (which leaves all the optional
1047 // parameters to be handled by user)
1048 pos = mediaType.find_first_of(';');
1049 return mediaType.substr(0u, pos); // NOTE: return media-type without any of the parameters.
1050 }
1051
DecodeDataURI(vector<uint8_t> & out,const string_view in,size_t reqBytes,bool checkSize,string_view & outMimeType)1052 bool DecodeDataURI(
1053 vector<uint8_t>& out, const string_view in, size_t reqBytes, bool checkSize, string_view& outMimeType)
1054 {
1055 size_t offsetToData = 0u;
1056 out.clear();
1057 if (auto const mimeType = ParseDataUri(in, offsetToData); mimeType.empty()) {
1058 return false;
1059 } else {
1060 outMimeType = mimeType;
1061 }
1062
1063 Base64Decode(in, offsetToData, out);
1064
1065 if (out.empty()) {
1066 return false;
1067 }
1068
1069 if (checkSize) {
1070 if (out.size() != reqBytes) {
1071 return false;
1072 }
1073 }
1074 return true;
1075 }
1076
IsDataURI(const string_view in)1077 bool IsDataURI(const string_view in)
1078 {
1079 size_t offsetToData;
1080 if (auto const mimeType = ParseDataUri(in, offsetToData); !mimeType.empty()) {
1081 if (mimeType == "application/octet-stream") {
1082 return true;
1083 }
1084 if (mimeType == "image/jpeg") {
1085 return true;
1086 }
1087 if (mimeType == "image/png") {
1088 return true;
1089 }
1090 if (mimeType == "image/bmp") {
1091 return true;
1092 }
1093 if (mimeType == "image/gif") {
1094 return true;
1095 }
1096 if (mimeType == "text/plain") {
1097 return true;
1098 }
1099 if (mimeType == "application/gltf-buffer") {
1100 return true;
1101 }
1102 }
1103 return false;
1104 }
1105
1106 // Buffer / data helpers
GLTFLoadDataResult(GLTFLoadDataResult && other)1107 GLTFLoadDataResult::GLTFLoadDataResult(GLTFLoadDataResult&& other) noexcept
1108 : success(other.success), normalized(other.normalized), error(move(other.error)),
1109 componentType(other.componentType), componentByteSize(other.componentByteSize),
1110 componentCount(other.componentCount), elementSize(other.elementSize), elementCount(other.elementCount),
1111 min(move(other.min)), max(move(other.max)), data(move(other.data))
1112 {}
1113
operator =(GLTFLoadDataResult && other)1114 GLTFLoadDataResult& GLTFLoadDataResult::operator=(GLTFLoadDataResult&& other) noexcept
1115 {
1116 success = other.success;
1117 normalized = other.normalized;
1118 error = std::move(other.error);
1119 componentType = other.componentType;
1120 componentByteSize = other.componentByteSize;
1121 componentCount = other.componentCount;
1122 elementSize = other.elementSize;
1123 elementCount = other.elementCount;
1124 min = std::move(other.min);
1125 max = std::move(other.max);
1126 data = std::move(other.data);
1127
1128 return *this;
1129 }
1130
1131 // Populate GLTF buffers with data.
LoadBuffers(const Data & data,IFileManager & fileManager)1132 BufferLoadResult LoadBuffers(const Data& data, IFileManager& fileManager)
1133 {
1134 BufferLoadResult result;
1135 // Load data to all buffers.
1136 for (size_t i = 0u; i < data.buffers.size(); ++i) {
1137 Buffer* buffer = data.buffers[i].get();
1138 if (buffer->data.empty()) {
1139 result = LoadBuffer(data, *buffer, fileManager);
1140 if (!result.success) {
1141 return result;
1142 }
1143 }
1144 }
1145
1146 // Set up bufferview data pointers.
1147 for (size_t i = 0u; i < data.bufferViews.size(); ++i) {
1148 BufferView* view = data.bufferViews[i].get();
1149 view->data = &(view->buffer->data[view->byteOffset]);
1150 }
1151
1152 return result;
1153 }
1154
LoadUri(const string_view uri,const string_view expectedMimeType,const string_view filePath,IFileManager & fileManager,string_view & outExtension,vector<uint8_t> & outData)1155 UriLoadResult LoadUri(const string_view uri, const string_view expectedMimeType, const string_view filePath,
1156 IFileManager& fileManager, string_view& outExtension, vector<uint8_t>& outData)
1157 {
1158 size_t offsetToData;
1159 if (auto const mimeType = ParseDataUri(uri, offsetToData); !mimeType.empty()) {
1160 bool isValidMimeType = true;
1161 const auto pos = mimeType.find_first_of('/');
1162 if (pos != string_view::npos) {
1163 auto const type = mimeType.substr(0u, pos);
1164
1165 if (!expectedMimeType.empty()) {
1166 if (type != expectedMimeType) {
1167 isValidMimeType = false;
1168 }
1169 }
1170 outExtension = mimeType.substr(pos + 1u);
1171 }
1172 if (isValidMimeType) {
1173 string_view outMimeType;
1174 DecodeDataURI(outData, uri, 0u, false, outMimeType);
1175 if (outData.empty()) {
1176 return URI_LOAD_FAILED_TO_DECODE_BASE64;
1177 }
1178 } else {
1179 return URI_LOAD_FAILED_INVALID_MIME_TYPE;
1180 }
1181 } else {
1182 string_view baseName, extension;
1183 SplitBaseFilename(uri, baseName, extension);
1184 outExtension = extension;
1185 if (!ReadUriToVector(filePath, fileManager, uri, outData)) {
1186 return URI_LOAD_FAILED_TO_READ_FILE;
1187 }
1188 }
1189
1190 return URI_LOAD_SUCCESS;
1191 }
1192
1193 // Load accessor data.
LoadData(Accessor const & accessor)1194 GLTFLoadDataResult LoadData(Accessor const& accessor)
1195 {
1196 GLTFLoadDataResult result;
1197 result.success = true;
1198
1199 const auto* bufferView = accessor.bufferView;
1200 const auto dataType = accessor.type;
1201 const auto component = accessor.componentType;
1202
1203 result.normalized = accessor.normalized;
1204 result.componentType = accessor.componentType;
1205 result.componentByteSize = GetComponentByteSize(component);
1206 result.componentCount = GetComponentsCount(dataType);
1207 result.elementSize = result.componentCount * result.componentByteSize;
1208 result.elementCount = accessor.count;
1209 result.min = accessor.min;
1210 result.max = accessor.max;
1211
1212 if (bufferView && bufferView->buffer) {
1213 vector<uint8_t> fileData = Read(accessor);
1214 if (fileData.empty()) {
1215 result.error = "Failed to load attribute data.\n";
1216 result.success = false;
1217 }
1218
1219 result.data.swap(fileData);
1220 }
1221
1222 if (accessor.sparse.count) {
1223 LoadSparseAccessor(accessor, result);
1224 }
1225
1226 return result;
1227 }
1228
1229 // class Data from GLTFData.h
Data(IFileManager & fileManager)1230 Data::Data(IFileManager& fileManager) : fileManager_(fileManager) {}
1231
LoadBuffers()1232 bool Data::LoadBuffers()
1233 {
1234 BufferLoadResult result = GLTF2::LoadBuffers(*this, fileManager_);
1235 return result.success;
1236 }
1237
ReleaseBuffers()1238 void Data::ReleaseBuffers()
1239 {
1240 // Reset bufferview pointers.
1241 for (size_t i = 0u; i < bufferViews.size(); ++i) {
1242 BufferView* view = bufferViews[i].get();
1243 view->data = nullptr;
1244 }
1245
1246 // Release buffer data.
1247 for (size_t i = 0u; i < buffers.size(); ++i) {
1248 Buffer* buffer = buffers[i].get();
1249 buffer->data = vector<uint8_t>();
1250 }
1251 }
1252
GetExternalFileUris()1253 vector<string> Data::GetExternalFileUris()
1254 {
1255 vector<string> result;
1256
1257 // All images in this glTF.
1258 for (auto const& image : images) {
1259 if (!IsDataURI(image->uri)) {
1260 result.emplace_back(image->uri);
1261 }
1262 }
1263
1264 // All buffers in this glTF (in case there are several .bin files).
1265 for (auto const& buffer : buffers) {
1266 if (!IsDataURI(buffer->uri)) {
1267 result.emplace_back(buffer->uri);
1268 }
1269 }
1270
1271 #if defined(GLTF2_EXTENSION_HW_XR_EXT)
1272 // All thumbnails glTF (in case there are non-embedded thumbnail uris).
1273 for (auto const& thumbnail : thumbnails) {
1274 if (!IsDataURI(thumbnail.uri)) {
1275 result.emplace_back(thumbnail.uri);
1276 }
1277 }
1278 #endif
1279
1280 return result;
1281 }
1282
GetDefaultSceneIndex() const1283 size_t Data::GetDefaultSceneIndex() const
1284 {
1285 if (defaultScene != nullptr) {
1286 for (size_t i = 0u; i < scenes.size(); ++i) {
1287 if (scenes[i].get() == defaultScene) {
1288 return i;
1289 }
1290 }
1291 }
1292
1293 return CORE_GLTF_INVALID_INDEX;
1294 }
1295
GetSceneCount() const1296 size_t Data::GetSceneCount() const
1297 {
1298 return scenes.size();
1299 }
1300
GetThumbnailImageCount() const1301 size_t Data::GetThumbnailImageCount() const
1302 {
1303 #if defined(GLTF2_EXTENSION_HW_XR_EXT)
1304 return thumbnails.size();
1305 #else
1306 return 0;
1307 #endif
1308 }
1309
GetThumbnailImage(size_t thumbnailIndex)1310 IGLTFData::ThumbnailImage Data::GetThumbnailImage(size_t thumbnailIndex)
1311 {
1312 IGLTFData::ThumbnailImage result;
1313
1314 #if defined(GLTF2_EXTENSION_HW_XR_EXT)
1315 if (thumbnailIndex >= thumbnails.size()) {
1316 return result;
1317 }
1318
1319 auto& thumbnail = thumbnails[thumbnailIndex];
1320 if (thumbnail.data.empty()) {
1321 // Load thumbnail data.
1322 string_view extension;
1323 if (LoadUri(thumbnail.uri, "image", filepath, fileManager_, extension, thumbnail.data) != URI_LOAD_SUCCESS) {
1324 thumbnails[thumbnailIndex].data.clear();
1325 thumbnails[thumbnailIndex].extension = "";
1326 } else {
1327 thumbnail.extension = extension;
1328 }
1329 }
1330
1331 result.data = thumbnail.data;
1332 result.extension = thumbnail.extension;
1333 #endif
1334
1335 return result;
1336 }
1337
Destroy()1338 void Data::Destroy()
1339 {
1340 delete this;
1341 }
1342 } // namespace GLTF2
1343 CORE3D_END_NAMESPACE()
1344