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