• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 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_loader.h"
17 
18 #include <algorithm>
19 #include <cctype>
20 #include <charconv>
21 #include <cstdint>
22 #include <optional>
23 
24 #include <base/containers/fixed_string.h>
25 #include <base/containers/string.h>
26 #include <base/containers/string_view.h>
27 #include <base/containers/unique_ptr.h>
28 #include <base/containers/vector.h>
29 #include <core/io/intf_file_manager.h>
30 #include <core/log.h>
31 #include <core/namespace.h>
32 #include <core/perf/cpu_perf_scope.h>
33 
34 #include "gltf/data.h"
35 #include "gltf/gltf2_util.h"
36 #include "util/json_util.h"
37 #include "util/log.h"
38 
39 namespace {
40 #include <3d/shaders/common/3d_dm_structures_common.h>
41 }
42 
SetError(CORE3D_NS::GLTF2::LoadResult & loadResult,const BASE_NS::string_view message)43 inline void SetError(CORE3D_NS::GLTF2::LoadResult& loadResult, const BASE_NS::string_view message)
44 {
45     loadResult.error += message + '\n';
46     loadResult.success = false;
47 }
48 
49 #define RETURN_WITH_ERROR(loadResult, message) \
50     {                                          \
51         SetError((loadResult), (message));     \
52         return false;                          \
53     }
54 
55 CORE3D_BEGIN_NAMESPACE()
56 using namespace BASE_NS;
57 using namespace CORE_NS;
58 
59 namespace GLTF2 {
60 namespace {
61 const string_view SUPPORTED_EXTENSIONS[] = {
62 #if defined(GLTF2_EXTENSION_IGFX_COMPRESSED)
63     "IGFX_compressed",
64 #endif
65 #if defined(GLTF2_EXTENSION_KHR_LIGHTS)
66     "KHR_lights_punctual",
67 #endif
68 #if defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
69     "KHR_lights_pbr",
70 #endif
71 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT)
72     "KHR_materials_clearcoat",
73 #endif
74 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_EMISSIVE_STRENGTH)
75     "KHR_materials_emissive_strength",
76 #endif
77 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
78     "KHR_materials_ior",
79 #endif
80 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
81     "KHR_materials_pbrSpecularGlossiness",
82 #endif
83 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
84     "KHR_materials_sheen",
85 #endif
86 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
87     "KHR_materials_specular",
88 #endif
89 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
90     "KHR_materials_transmission",
91 #endif
92 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_UNLIT)
93     "KHR_materials_unlit",
94 #endif
95 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
96     "KHR_mesh_quantization",
97 #endif
98 #if defined(GLTF2_EXTENSION_KHR_TEXTURE_BASISU)
99     "KHR_texture_basisu",
100 #endif
101 #if defined(GLTF2_EXTENSION_KHR_TEXTURE_TRANSFORM)
102     "KHR_texture_transform",
103 #endif
104 #if defined(GLTF2_EXTENSION_HW_XR_EXT)
105     "HW_XR_EXT",
106 #endif
107 #if defined(GLTF2_EXTENSION_EXT_LIGHTS_IMAGE_BASED)
108     "EXT_lights_image_based",
109 #endif
110     "MSFT_texture_dds",
111     // legacy stuff found potentially in animoji models
112     "IGFX_lights",
113     "IGFX_environment",
114 };
115 
116 // Replace percent-encoded characters from the URI.
117 // NOTE: glTF spec says "Reserved characters must be percent-encoded, per RFC 3986, Section 2.2.". The RFC says that
118 // for consistency, percent encoded unreserved characters (alphanum, '-', '.', '_', '~') should be also decoded. The RFC
119 // doesn't list space as reserved, but it' s still somehow expected.
120 // -> Not limiting to reserved and unreserved characters, but converts everything in the range of [%00, %FF].
DecodeUri(string & uri)121 void DecodeUri(string& uri)
122 {
123     string::size_type pos = 0;
124     while ((pos = uri.find('%', pos)) != string::npos) {
125         // there should be at least two characters after '%'
126         if ((pos + 2) < uri.size()) {
127             // start converting after '%'
128             const auto begin = uri.data() + (pos + 1);
129             // convert up to two characters
130             const auto end = begin + 2;
131             uint32_t val;
132             if (const auto result = std::from_chars(begin, end, val, 16);
133                 (result.ec == std::errc()) && result.ptr == end) {
134                 // replace '%' with the hex value converted to char
135                 *(begin - 1) = static_cast<char>(val);
136                 // remove the encoding
137                 uri.erase(pos + 1, 2);
138             }
139         }
140         pos++;
141     }
142 }
143 
144 template<typename EnumType, typename InputType>
RangedEnumCast(LoadResult & loadResult,EnumType & out,InputType input)145 bool RangedEnumCast(LoadResult& loadResult, EnumType& out, InputType input)
146 {
147     if (input >= static_cast<int>(EnumType::BEGIN) && input < static_cast<int>(EnumType::COUNT)) {
148         out = static_cast<EnumType>(input);
149         return true;
150     }
151 
152     RETURN_WITH_ERROR(loadResult, "Invalid enum cast");
153 }
154 
155 template<typename Parser>
ParseObject(LoadResult & loadResult,const json::value & jsonObject,Parser parser)156 bool ParseObject(LoadResult& loadResult, const json::value& jsonObject, Parser parser)
157 {
158     if (!jsonObject.is_object()) {
159         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected JSON object");
160     }
161 
162     return parser(loadResult, jsonObject);
163 }
164 
165 template<typename Parser>
ParseObject(LoadResult & loadResult,const json::value & jsonObject,const string_view name,Parser parser)166 bool ParseObject(LoadResult& loadResult, const json::value& jsonObject, const string_view name, Parser parser)
167 {
168     if (auto it = jsonObject.find(name); it) {
169         return ParseObject(loadResult, *it, parser);
170     }
171 
172     return true;
173 }
174 
175 template<typename Parser>
ForEachInArray(LoadResult & loadResult,const json::value & jsonArray,Parser parser)176 bool ForEachInArray(LoadResult& loadResult, const json::value& jsonArray, Parser parser)
177 {
178     if (!jsonArray.is_array()) {
179         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected JSON array");
180     }
181 
182     for (const auto& item : jsonArray.array_) {
183         if (!parser(loadResult, item)) {
184             return false;
185         }
186     }
187 
188     return true;
189 }
190 
191 template<typename Parser>
ForEachInArray(LoadResult & loadResult,const json::value & jsonObject,const string_view name,Parser parser)192 bool ForEachInArray(LoadResult& loadResult, const json::value& jsonObject, const string_view name, Parser parser)
193 {
194     if (auto it = jsonObject.find(name); it) {
195         return ForEachInArray(loadResult, *it, parser);
196     }
197 
198     return true;
199 }
200 
201 template<typename Parser>
ForEachObjectInArray(LoadResult & loadResult,const json::value & jsonObject,Parser parser)202 bool ForEachObjectInArray(LoadResult& loadResult, const json::value& jsonObject, Parser parser)
203 {
204     return ForEachInArray(loadResult, jsonObject, [parser](LoadResult& loadResult, const json::value& item) -> bool {
205         return ParseObject(loadResult, item,
206             [parser](LoadResult& loadResult, const json::value& item) -> bool { return parser(loadResult, item); });
207     });
208 }
209 
210 template<typename Parser>
ForEachObjectInArray(LoadResult & loadResult,const json::value & jsonObject,const string_view name,Parser parser)211 bool ForEachObjectInArray(LoadResult& loadResult, const json::value& jsonObject, const string_view name, Parser parser)
212 {
213     return ForEachInArray(
214         loadResult, jsonObject, name, [parser](LoadResult& loadResult, const json::value& item) -> bool {
215             return ParseObject(loadResult, item,
216                 [parser](LoadResult& loadResult, const json::value& item) -> bool { return parser(loadResult, item); });
217         });
218 }
219 
ParseOptionalString(LoadResult & loadResult,string & out,const json::value & jsonObject,const string_view name,const string_view defaultValue)220 bool ParseOptionalString(LoadResult& loadResult, string& out, const json::value& jsonObject, const string_view name,
221     const string_view defaultValue)
222 {
223     if (const auto value = jsonObject.find(name); value) {
224         if (!value->is_string()) {
225             RETURN_WITH_ERROR(loadResult, "expected string");
226         }
227         if (value->string_.find('\\') != string::npos) {
228             out = json::unescape(value->string_);
229         } else {
230             out = value->string_;
231         }
232 
233         return true;
234     }
235 
236     out = defaultValue;
237     return true;
238 }
239 
240 template<typename Number>
ConvertStringToValue(const string_view str,Number & value)241 void ConvertStringToValue(const string_view str, Number& value)
242 {
243 #if defined(__OHOS_PLATFORM__) || defined(__linux__) || defined(__APPLE__)
244     if constexpr (std::is_integral_v<Number>) {
245         std::from_chars(str.data(), str.data() + str.size(), value);
246     } else {
247         value = strtof(str.data(), nullptr);
248     }
249 #else
250     std::from_chars(str.data(), str.data() + str.size(), value);
251 #endif
252 }
253 
254 template<typename T>
ParseOptionalNumber(LoadResult & loadResult,T & out,const json::value & jsonObject,const string_view name,T defaultValue)255 bool ParseOptionalNumber(
256     LoadResult& loadResult, T& out, const json::value& jsonObject, const string_view name, T defaultValue)
257 {
258     if (const auto it = jsonObject.find(name); it) {
259         if (it->is_number()) {
260             out = it->as_number<T>();
261             return true;
262         } else if (it->is_string()) {
263             // Some glTF files have strings in place of numbers, so we try to perform auto conversion here.
264             ConvertStringToValue(it->string_, out);
265             CORE_LOG_W("Expected number when parsing but found string (performing auto conversion to number type).");
266             return true;
267         } else {
268             out = defaultValue;
269             RETURN_WITH_ERROR(loadResult, "expected number");
270         }
271     }
272     out = defaultValue;
273     return true;
274 }
275 
ParseOptionalBoolean(LoadResult & loadResult,bool & out,const json::value & jsonObject,const string_view name,bool defaultValue)276 bool ParseOptionalBoolean(
277     LoadResult& loadResult, bool& out, const json::value& jsonObject, const string_view name, bool defaultValue)
278 {
279     if (const auto value = jsonObject.find(name); value) {
280         if (value->is_boolean()) {
281             out = value->boolean_;
282             return true;
283         } else {
284             RETURN_WITH_ERROR(loadResult, "expected boolean");
285         }
286     }
287 
288     out = defaultValue;
289     return true;
290 }
291 
292 template<typename T>
ParseOptionalNumberArray(LoadResult & loadResult,vector<T> & out,const json::value & jsonObject,const string_view name,vector<T> defaultValue,uint32_t minSize=0,uint32_t maxSize=std::numeric_limits<int>::max ())293 bool ParseOptionalNumberArray(LoadResult& loadResult, vector<T>& out, const json::value& jsonObject,
294     const string_view name, vector<T> defaultValue, uint32_t minSize = 0,
295     uint32_t maxSize = std::numeric_limits<int>::max())
296 {
297     const auto* it = jsonObject.find(name);
298     if (!it) {
299         out = defaultValue;
300         return true;
301     }
302     if (!it->is_array()) {
303         RETURN_WITH_ERROR(loadResult, "expected array");
304     }
305     const auto view =
306         array_view(it->array_.data(), BASE_NS::Math::min(static_cast<uint32_t>(it->array_.size()), maxSize));
307     out.reserve(view.size());
308     for (const auto& item : view) {
309         if (!item.is_number()) {
310             RETURN_WITH_ERROR(loadResult, "expected array of numbers");
311         }
312         out.push_back(item.as_number<T>());
313     }
314 
315     if (out.size() >= minSize) {
316         return true;
317     }
318 
319     out = defaultValue;
320     return true;
321 }
322 
323 /** Tries to parse a Core::Math object (e.g. Vec4, Quat, and Mat4X4) from json. */
324 template<typename T>
ParseOptionalMath(LoadResult & loadResult,T & out,const json::value & jsonObject,const string_view name,T defaultValue)325 bool ParseOptionalMath(
326     LoadResult& loadResult, T& out, const json::value& jsonObject, const string_view name, T defaultValue)
327 {
328     if (auto it = jsonObject.find(name); it) {
329         if (it->is_array() && (it->array_.size() == countof(out.data))) {
330             auto& array = it->array_;
331             std::transform(array.begin(), array.end(), out.data, [](const json::value& item) {
332                 if (item.is_number()) {
333                     return item.as_number<float>();
334                 } else if (item.is_string()) {
335                     float value;
336                     ConvertStringToValue(item.string_, value);
337                     return value;
338                 } else {
339                     return 0.f;
340                 }
341             });
342             return true;
343         } else {
344             RETURN_WITH_ERROR(loadResult, "expected array of size when parsing");
345         }
346     } else {
347         out = defaultValue;
348         return true;
349     }
350 }
351 
ParseVersion(string_view version)352 std::optional<std::pair<uint32_t, uint32_t>> ParseVersion(string_view version)
353 {
354     uint32_t min = 0;
355     uint32_t maj = 0;
356     if (const auto delim = version.find('.'); delim == string::npos) {
357         return {};
358     } else if (auto result = std::from_chars(version.data(), version.data() + delim, maj, 10);
359                result.ec != std::errc() || *(result.ptr) != '.') {
360         return {};
361     } else if (auto result2 = std::from_chars(result.ptr + 1, version.data() + version.size(), min, 10);
362                result2.ec != std::errc() || *(result2.ptr) != '\0') {
363         return {};
364     }
365     return std::make_pair(maj, min);
366 }
367 
ParseBuffer(LoadResult & loadResult,const json::value & jsonData)368 bool ParseBuffer(LoadResult& loadResult, const json::value& jsonData)
369 {
370     bool result = true;
371 
372     int32_t length = 0;
373     if (!SafeGetJsonValue(jsonData, "byteLength", loadResult.error, length)) {
374         SetError(loadResult, "Failed to read buffer.byteLength");
375         result = false;
376     }
377 
378     if (length < 1) {
379         SetError(loadResult, "buffer.byteLength was smaller than 1 byte");
380         result = false;
381     }
382 
383     string uri;
384     if (!ParseOptionalString(loadResult, uri, jsonData, "uri", string())) {
385         result = false;
386     }
387 
388     auto buffer = make_unique<Buffer>();
389     if (result) {
390         DecodeUri(uri);
391         buffer->uri = move(uri);
392         buffer->byteLength = size_t(length);
393     }
394 
395     loadResult.data->buffers.push_back(move(buffer));
396 
397     return result;
398 }
399 
BufferViewBuffer(LoadResult & loadResult,const json::value & jsonData)400 std::optional<Buffer*> BufferViewBuffer(LoadResult& loadResult, const json::value& jsonData)
401 {
402     int index = 0;
403     if (!SafeGetJsonValue(jsonData, "buffer", loadResult.error, index)) {
404         SetError(loadResult, "Failed to read bufferView.buffer");
405         return std::nullopt;
406     } else if (index < 0 || size_t(index) >= loadResult.data->buffers.size()) {
407         SetError(loadResult, "bufferView.buffer isn't valid index");
408         return std::nullopt;
409     } else {
410         return loadResult.data->buffers[(unsigned int)index].get();
411     }
412 }
413 
BufferViewByteLength(LoadResult & loadResult,const json::value & jsonData)414 std::optional<int> BufferViewByteLength(LoadResult& loadResult, const json::value& jsonData)
415 {
416     int length = 0;
417     if (!SafeGetJsonValue(jsonData, "byteLength", loadResult.error, length)) {
418         SetError(loadResult, "Failed to read bufferView.byteLength");
419         return std::nullopt;
420     } else if (length < 1) {
421         SetError(loadResult, "bufferView.byteLength was smaller than 1 byte");
422         return std::nullopt;
423     }
424     return length;
425 }
426 
BufferViewByteOffset(LoadResult & loadResult,const json::value & jsonData,std::optional<Buffer * > buffer,std::optional<int> byteLength)427 std::optional<int> BufferViewByteOffset(
428     LoadResult& loadResult, const json::value& jsonData, std::optional<Buffer*> buffer, std::optional<int> byteLength)
429 {
430     int offset;
431     if (!ParseOptionalNumber<int>(loadResult, offset, jsonData, "byteOffset", 0)) { // "default": 0
432         return std::nullopt;
433     } else if (offset < 0) {
434         SetError(loadResult, "bufferView.byteOffset isn't valid offset");
435         return std::nullopt;
436     } else if (!buffer || !(*buffer) || !byteLength || ((*buffer)->byteLength < (size_t(offset) + *byteLength))) {
437         SetError(loadResult, "bufferView.byteLength is larger than buffer.byteLength");
438         return std::nullopt;
439     }
440     return offset;
441 }
442 
BufferViewByteStride(LoadResult & loadResult,const json::value & jsonData)443 std::optional<int> BufferViewByteStride(LoadResult& loadResult, const json::value& jsonData)
444 {
445     // "default": 0 "minimum": 4, "maximum": 252, "multipleOf":
446     int stride;
447     if (!ParseOptionalNumber<int>(loadResult, stride, jsonData, "byteStride", 0)) {
448         return std::nullopt;
449     } else if ((stride < 4 && stride != 0) || stride > 252 || (stride % 4)) {
450         SetError(loadResult, "bufferView.byteStride isn't valid stride");
451         return std::nullopt;
452     }
453     return stride;
454 }
455 
BufferViewTarget(LoadResult & loadResult,const json::value & jsonData)456 std::optional<BufferTarget> BufferViewTarget(LoadResult& loadResult, const json::value& jsonData)
457 {
458     // "default": NOT_DEFINED if set then ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER
459     int target;
460     if (!ParseOptionalNumber<int>(
461             loadResult, target, jsonData, "target", static_cast<int>(BufferTarget::NOT_DEFINED))) {
462         return std::nullopt;
463     } else if (target != static_cast<int>(BufferTarget::NOT_DEFINED) &&
464                target != static_cast<int>(BufferTarget::ARRAY_BUFFER) &&
465                target != static_cast<int>(BufferTarget::ELEMENT_ARRAY_BUFFER)) {
466         SetError(loadResult, "bufferView.target isn't valid target");
467         return std::nullopt;
468     }
469     return static_cast<BufferTarget>(target);
470 }
471 
ParseBufferView(LoadResult & loadResult,const json::value & jsonData)472 bool ParseBufferView(LoadResult& loadResult, const json::value& jsonData)
473 {
474     const auto buffer = BufferViewBuffer(loadResult, jsonData);
475     const auto byteLength = BufferViewByteLength(loadResult, jsonData);
476     const auto offset = BufferViewByteOffset(loadResult, jsonData, buffer, byteLength);
477     const auto stride = BufferViewByteStride(loadResult, jsonData);
478     const auto target = BufferViewTarget(loadResult, jsonData);
479 
480     const auto result = byteLength && buffer && offset && stride && target;
481     if (result) {
482         auto view = make_unique<BufferView>();
483         view->buffer = *buffer;
484         view->byteLength = size_t(*byteLength);
485         view->byteOffset = size_t(*offset);
486         view->byteStride = size_t(*stride);
487         view->target = *target;
488         loadResult.data->bufferViews.push_back(move(view));
489     } else {
490         loadResult.data->bufferViews.emplace_back();
491     }
492 
493     return result;
494 }
495 
AccessorComponentType(LoadResult & loadResult,const json::value & jsonData)496 std::optional<ComponentType> AccessorComponentType(LoadResult& loadResult, const json::value& jsonData)
497 {
498     int componentType = 0;
499     if (!SafeGetJsonValue(jsonData, "componentType", loadResult.error, componentType)) {
500         SetError(loadResult, "Failed to read accessor.componentType");
501         return std::nullopt;
502     }
503     return static_cast<ComponentType>(componentType);
504 }
505 
AccessorCount(LoadResult & loadResult,const json::value & jsonData)506 std::optional<uint32_t> AccessorCount(LoadResult& loadResult, const json::value& jsonData)
507 {
508     int count = 0;
509     if (!SafeGetJsonValue(jsonData, "count", loadResult.error, count)) {
510         SetError(loadResult, "Failed to read accessor.count");
511         return std::nullopt;
512     } else if (count < 1) {
513         SetError(loadResult, "Accessor.count is invalid");
514         return std::nullopt;
515     }
516     return count;
517 }
518 
AccessorType(LoadResult & loadResult,const json::value & jsonData)519 std::optional<DataType> AccessorType(LoadResult& loadResult, const json::value& jsonData)
520 {
521     string_view type;
522     if (!SafeGetJsonValue(jsonData, "type", loadResult.error, type)) {
523         SetError(loadResult, "Failed to read accessor.type");
524         return std::nullopt;
525     }
526 
527     DataType datatype;
528     if (!GetDataType(type, datatype)) {
529         SetError(loadResult, "Invalid or unsupported data type");
530         return std::nullopt;
531     }
532     return datatype;
533 }
534 
AccessorBufferView(LoadResult & loadResult,const json::value & jsonData)535 std::optional<BufferView*> AccessorBufferView(LoadResult& loadResult, const json::value& jsonData)
536 {
537     BufferView* bufferView = nullptr;
538 
539     size_t bufferIndex;
540     if (!ParseOptionalNumber<size_t>(loadResult, bufferIndex, jsonData, "bufferView", GLTF_INVALID_INDEX)) {
541         return std::nullopt;
542     } else if (bufferIndex != GLTF_INVALID_INDEX) {
543         if (bufferIndex < loadResult.data->bufferViews.size()) {
544             bufferView = loadResult.data->bufferViews[bufferIndex].get();
545         } else {
546             SetError(loadResult, "Accessor.bufferView is invalid");
547             return std::nullopt;
548         }
549     }
550     return bufferView;
551 }
552 
AccessorByteOffset(LoadResult & loadResult,const json::value & jsonData,std::optional<BufferView * > bufferView)553 std::optional<uint32_t> AccessorByteOffset(
554     LoadResult& loadResult, const json::value& jsonData, std::optional<BufferView*> bufferView)
555 {
556     uint32_t byteOffset;
557     if (!ParseOptionalNumber<uint32_t>(loadResult, byteOffset, jsonData, "byteOffset", 0)) {
558         return false;
559     } else if (bufferView && (*bufferView) && ((*bufferView)->byteLength <= byteOffset)) {
560         SetError(loadResult, "Accessor.byteOffset isn't valid offset");
561         return std::nullopt;
562     }
563     return byteOffset;
564 }
565 
AccessorNormalized(LoadResult & loadResult,const json::value & jsonData)566 std::optional<bool> AccessorNormalized(LoadResult& loadResult, const json::value& jsonData)
567 {
568     bool normalized = false;
569     if (!ParseOptionalBoolean(loadResult, normalized, jsonData, "normalized", false)) {
570         return std::nullopt;
571     }
572     return normalized;
573 }
574 
AccessorMin(LoadResult & loadResult,const json::value & jsonData)575 std::optional<vector<float>> AccessorMin(LoadResult& loadResult, const json::value& jsonData)
576 {
577     vector<float> min;
578     if (!ParseOptionalNumberArray(loadResult, min, jsonData, "min", vector<float>(), 1U, 16U)) {
579         return std::nullopt;
580     }
581     return move(min);
582 }
583 
AccessorMax(LoadResult & loadResult,const json::value & jsonData)584 std::optional<vector<float>> AccessorMax(LoadResult& loadResult, const json::value& jsonData)
585 {
586     vector<float> max;
587     if (!ParseOptionalNumberArray(loadResult, max, jsonData, "max", vector<float>(), 1U, 16U)) {
588         return std::nullopt;
589     }
590     return move(max);
591 }
592 
AccessorSparseIndices(LoadResult & loadResult,const json::value & jsonData)593 std::optional<SparseIndices> AccessorSparseIndices(LoadResult& loadResult, const json::value& jsonData)
594 {
595     SparseIndices indices;
596     size_t bufferViewIndex;
597     if (!SafeGetJsonValue(jsonData, "bufferView", loadResult.error, bufferViewIndex)) {
598         SetError(loadResult, "Failed to read Sparse.indices.bufferView");
599         return std::nullopt;
600     }
601 
602     if (bufferViewIndex < loadResult.data->bufferViews.size()) {
603         indices.bufferView = loadResult.data->bufferViews[bufferViewIndex].get();
604     } else {
605         SetError(loadResult, "Sparse.indices.bufferView is invalid");
606         return std::nullopt;
607     }
608 
609     int32_t componentType;
610     if (!SafeGetJsonValue(jsonData, "componentType", loadResult.error, componentType)) {
611         SetError(loadResult, "Failed to read Sparse.indices.componentType");
612         return std::nullopt;
613     }
614 
615     indices.componentType = static_cast<ComponentType>(componentType);
616 
617     if (!ParseOptionalNumber<uint32_t>(loadResult, indices.byteOffset, jsonData, "bufferOffset", 0)) {
618         return std::nullopt;
619     }
620 
621     return indices;
622 }
623 
AccessorSparseValues(LoadResult & loadResult,const json::value & jsonData)624 std::optional<SparseValues> AccessorSparseValues(LoadResult& loadResult, const json::value& jsonData)
625 {
626     SparseValues values;
627     size_t bufferViewIndex;
628     if (!SafeGetJsonValue(jsonData, "bufferView", loadResult.error, bufferViewIndex)) {
629         SetError(loadResult, "Failed to read Sparse.values.bufferView");
630         return std::nullopt;
631     } else if (bufferViewIndex < loadResult.data->bufferViews.size()) {
632         values.bufferView = loadResult.data->bufferViews[bufferViewIndex].get();
633     } else {
634         SetError(loadResult, "Sparse.values.bufferView is invalid");
635         return std::nullopt;
636     }
637 
638     if (!ParseOptionalNumber<uint32_t>(loadResult, values.byteOffset, jsonData, "bufferOffset", 0)) {
639         return std::nullopt;
640     }
641 
642     return values;
643 }
644 
AccessorSparse(LoadResult & loadResult,const json::value & jsonData)645 std::optional<Sparse> AccessorSparse(LoadResult& loadResult, const json::value& jsonData)
646 {
647     Sparse sparse;
648     if (auto sparseJson = jsonData.find("sparse"); sparseJson) {
649         if (!SafeGetJsonValue(*sparseJson, "count", loadResult.error, sparse.count)) {
650             SetError(loadResult, "Failed to read sparse.count");
651             return std::nullopt;
652         }
653 
654         if (auto it = sparseJson->find("indices"); it) {
655             if (auto ret = AccessorSparseIndices(loadResult, *it); ret) {
656                 sparse.indices = *ret;
657             } else {
658                 return std::nullopt;
659             }
660         } else {
661             SetError(loadResult, "Failed to read sparse.indices");
662             return std::nullopt;
663         }
664 
665         if (auto it = sparseJson->find("values"); it) {
666             if (auto ret = AccessorSparseValues(loadResult, *it); ret) {
667                 sparse.values = *ret;
668             } else {
669                 return std::nullopt;
670             }
671         } else {
672             SetError(loadResult, "Failed to read sparse.values");
673             return std::nullopt;
674         }
675     }
676     return sparse;
677 }
678 
ValidateAccessor(LoadResult & loadResult,ComponentType componentType,uint32_t count,DataType dataType,const BufferView * bufferView,uint32_t byteOffset,const array_view<const float> min,const array_view<const float> max)679 bool ValidateAccessor(LoadResult& loadResult, ComponentType componentType, uint32_t count, DataType dataType,
680     const BufferView* bufferView, uint32_t byteOffset, const array_view<const float> min,
681     const array_view<const float> max)
682 {
683     if (const size_t elementSize = GetComponentsCount(dataType) * GetComponentByteSize(componentType); elementSize) {
684         if (bufferView) {
685             // check count against buffer size
686             if (byteOffset > bufferView->byteLength) {
687                 SetError(loadResult, "Accessor.byteOffset is invalid");
688                 return false;
689             }
690             const auto bufferSize = bufferView->byteLength - byteOffset;
691             const auto elementCount = bufferSize / elementSize;
692 
693             if (count > elementCount) {
694                 SetError(loadResult, "Accessor.count is invalid");
695                 return false;
696             }
697         }
698     } else {
699         SetError(loadResult, "Accessor.type or componentType is invalid");
700         return false;
701     }
702     const auto minVecSize = min.size();
703     const auto maxVecSize = max.size();
704     if (minVecSize == maxVecSize) {
705         if (minVecSize && minVecSize != GetComponentsCount(dataType)) {
706             SetError(loadResult, "Accessor.min and max vector size doesn't match component count");
707             return false;
708         }
709     } else {
710         SetError(loadResult, "Accessor.min and max vectors have different size");
711         return false;
712     }
713     return true;
714 }
715 
716 template<typename T>
GetView(const std::optional<vector<T>> & potential)717 array_view<const T> GetView(const std::optional<vector<T>>& potential)
718 {
719     if (potential.has_value()) {
720         return array_view<const float>(potential.value());
721     }
722     return {};
723 }
724 
ParseAccessor(LoadResult & loadResult,const json::value & jsonData)725 bool ParseAccessor(LoadResult& loadResult, const json::value& jsonData)
726 {
727     const auto componentType = AccessorComponentType(loadResult, jsonData);
728     const auto count = AccessorCount(loadResult, jsonData);
729     const auto datatype = AccessorType(loadResult, jsonData);
730 
731     const auto bufferView = AccessorBufferView(loadResult, jsonData);
732     const auto byteOffset = AccessorByteOffset(loadResult, jsonData, bufferView);
733     const auto normalized = AccessorNormalized(loadResult, jsonData);
734     auto max = AccessorMax(loadResult, jsonData);
735     auto min = AccessorMin(loadResult, jsonData);
736     auto sparse = AccessorSparse(loadResult, jsonData);
737 
738     const auto minView = GetView(min);
739     const auto maxView = GetView(max);
740 
741     bool result = true;
742     if (!ValidateAccessor(loadResult, componentType.value_or(ComponentType::INVALID), count.value_or(0U),
743             datatype.value_or(DataType::INVALID), bufferView.value_or(nullptr), byteOffset.value_or(0U), minView,
744             maxView)) {
745         result = false;
746     }
747     if (count && sparse) {
748         const auto& sparseRef = *sparse;
749         if (sparseRef.count > *count) {
750             SetError(loadResult, "Accessor.sparse.count is invalid");
751             return false;
752         }
753         if (!ValidateAccessor(loadResult, sparseRef.indices.componentType, sparseRef.count, DataType::SCALAR,
754                 sparseRef.indices.bufferView, sparseRef.indices.byteOffset, array_view<const float> {},
755                 array_view<const float> {})) {
756             SetError(loadResult, "Accessor.sparse.indices is invalid");
757             return false;
758         }
759         if (!ValidateAccessor(loadResult, componentType.value_or(ComponentType::INVALID), sparseRef.count,
760                 datatype.value_or(DataType::INVALID), sparseRef.values.bufferView, sparseRef.values.byteOffset, minView,
761                 maxView)) {
762             SetError(loadResult, "Accessor.sparse.values is invalid");
763             return false;
764         }
765     }
766 
767     auto accessor = make_unique<Accessor>();
768     result =
769         result && componentType && count && datatype && bufferView && byteOffset && normalized && max && min && sparse;
770     if (result) {
771         accessor->componentType = static_cast<ComponentType>(*componentType);
772         accessor->count = *count;
773         accessor->type = *datatype;
774         accessor->bufferView = *bufferView;
775         accessor->byteOffset = *byteOffset;
776         accessor->max = move(*max);
777         accessor->min = move(*min);
778         accessor->normalized = *normalized;
779         accessor->sparse = move(*sparse);
780     }
781     loadResult.data->accessors.push_back(move(accessor));
782 
783     return result;
784 }
785 
ParseTextureInfo(LoadResult & loadResult,TextureInfo & info,const json::value & jsonData)786 bool ParseTextureInfo(LoadResult& loadResult, TextureInfo& info, const json::value& jsonData)
787 {
788     if (!ParseOptionalNumber<uint32_t>(loadResult, info.index, jsonData, "index", GLTF_INVALID_INDEX)) {
789         return false;
790     }
791 
792     if (info.index != GLTF_INVALID_INDEX && info.index < loadResult.data->textures.size()) {
793         info.texture = loadResult.data->textures[info.index].get();
794     }
795 
796     if (!ParseOptionalNumber<uint32_t>(loadResult, info.texCoordIndex, jsonData, "texCoord", GLTF_INVALID_INDEX)) {
797         return false;
798     }
799 #if defined(GLTF2_EXTENSION_KHR_TEXTURE_TRANSFORM)
800     const auto textureTransformParser = [&info](LoadResult& loadResult, const json::value& extensions) -> bool {
801         return ParseObject(loadResult, extensions, "KHR_texture_transform",
802             [&info](LoadResult& loadResult, const json::value& textureTransform) {
803                 if (!ParseOptionalMath(loadResult, info.transform.offset, textureTransform, "offset", { 0.0f, 0.0f })) {
804                     return false;
805                 }
806 
807                 if (!ParseOptionalNumber(loadResult, info.transform.rotation, textureTransform, "rotation", 0.0f)) {
808                     return false;
809                 }
810 
811                 if (!ParseOptionalMath(loadResult, info.transform.scale, textureTransform, "scale", { 1.0f, 1.0f })) {
812                     return false;
813                 }
814 
815                 if (!ParseOptionalNumber(
816                         loadResult, info.transform.texCoordIndex, textureTransform, "texCoord", GLTF_INVALID_INDEX)) {
817                     return false;
818                 }
819                 return true;
820             });
821     };
822     if (!ParseObject(loadResult, jsonData, "extensions", textureTransformParser)) {
823         return false;
824     }
825 #endif
826     return true;
827 }
828 
ParseMetallicRoughness(LoadResult & loadResult,const json::value & jsonData,MetallicRoughness & metallicRoughness)829 bool ParseMetallicRoughness(LoadResult& loadResult, const json::value& jsonData, MetallicRoughness& metallicRoughness)
830 {
831     if (auto roughnessJson = jsonData.find("pbrMetallicRoughness"); roughnessJson) {
832         if (!ParseOptionalMath(loadResult, metallicRoughness.baseColorFactor, *roughnessJson, "baseColorFactor",
833                 metallicRoughness.baseColorFactor)) {
834             return false;
835         }
836 
837         if (!ParseObject(loadResult, *roughnessJson, "baseColorTexture",
838                 [&metallicRoughness](LoadResult& loadResult, const json::value& baseJson) -> bool {
839                     return ParseTextureInfo(loadResult, metallicRoughness.baseColorTexture, baseJson);
840                 })) {
841             return false;
842         }
843 
844         if (!ParseObject(loadResult, *roughnessJson, "metallicRoughnessTexture",
845                 [&metallicRoughness](LoadResult& loadResult, const json::value& baseJson) -> bool {
846                     return ParseTextureInfo(loadResult, metallicRoughness.metallicRoughnessTexture, baseJson);
847                 })) {
848             return false;
849         }
850 
851         if (!ParseOptionalNumber<float>(loadResult, metallicRoughness.metallicFactor, *roughnessJson, "metallicFactor",
852                 metallicRoughness.metallicFactor)) {
853             return false;
854         }
855 
856         if (!ParseOptionalNumber<float>(loadResult, metallicRoughness.roughnessFactor, *roughnessJson,
857                 "roughnessFactor", metallicRoughness.roughnessFactor)) {
858             return false;
859         }
860     }
861     return true;
862 }
863 
TextureSampler(LoadResult & loadResult,const json::value & jsonData)864 std::optional<Sampler*> TextureSampler(LoadResult& loadResult, const json::value& jsonData)
865 {
866     size_t samplerIndex;
867     if (!ParseOptionalNumber<size_t>(loadResult, samplerIndex, jsonData, "sampler", GLTF_INVALID_INDEX)) {
868         return std::nullopt;
869     }
870 
871     Sampler* sampler = loadResult.data->defaultSampler.get();
872     if (samplerIndex != GLTF_INVALID_INDEX) {
873         if (samplerIndex < loadResult.data->samplers.size()) {
874             sampler = loadResult.data->samplers[samplerIndex].get();
875         } else {
876             SetError(loadResult, "Invalid sampler index");
877             return std::nullopt;
878         }
879     }
880     return sampler;
881 }
882 
TextureSource(LoadResult & loadResult,const json::value & jsonData)883 std::optional<Image*> TextureSource(LoadResult& loadResult, const json::value& jsonData)
884 {
885     size_t imageIndex;
886     if (!ParseOptionalNumber<size_t>(loadResult, imageIndex, jsonData, "source", GLTF_INVALID_INDEX)) {
887         return std::nullopt;
888     }
889 
890     Image* image = nullptr;
891     if (imageIndex != GLTF_INVALID_INDEX) {
892         if (imageIndex < loadResult.data->images.size()) {
893             image = loadResult.data->images[imageIndex].get();
894         } else {
895             SetError(loadResult, "Invalid image index");
896             return std::nullopt;
897         }
898     }
899     return image;
900 }
901 
ParseTexture(LoadResult & loadResult,const json::value & jsonData)902 bool ParseTexture(LoadResult& loadResult, const json::value& jsonData)
903 {
904     auto sampler = TextureSampler(loadResult, jsonData);
905     auto image = TextureSource(loadResult, jsonData);
906     if (auto jsonExtensions = jsonData.find("extensions"); jsonExtensions) {
907         if (auto jsonDds = jsonExtensions->find("MSFT_texture_dds"); jsonDds) {
908             image = TextureSource(loadResult, *jsonDds);
909         }
910 #if defined(GLTF2_EXTENSION_KHR_TEXTURE_BASISU)
911         if (auto jsonBasisu = jsonExtensions->find("KHR_texture_basisu"); jsonBasisu) {
912             image = TextureSource(loadResult, *jsonBasisu);
913         }
914 #endif
915     }
916     const auto result = (sampler && image);
917     auto texture = make_unique<Texture>();
918     if (result) {
919         texture->sampler = *sampler;
920         texture->image = *image;
921     }
922     loadResult.data->textures.push_back(move(texture));
923 
924     return result;
925 }
926 
ParseImage(LoadResult & loadResult,const json::value & jsonData)927 bool ParseImage(LoadResult& loadResult, const json::value& jsonData)
928 {
929     auto image = make_unique<Image>();
930     if (!ParseOptionalString(loadResult, image->uri, jsonData, "uri", string())) {
931         return false;
932     }
933 
934     DecodeUri(image->uri);
935 
936     size_t bufferIndex;
937     if (!ParseOptionalNumber<size_t>(loadResult, bufferIndex, jsonData, "bufferView", GLTF_INVALID_INDEX)) {
938         return false;
939     }
940 
941     if (bufferIndex != GLTF_INVALID_INDEX && bufferIndex < loadResult.data->bufferViews.size()) {
942         image->bufferView = loadResult.data->bufferViews[bufferIndex].get();
943 
944         string imageType;
945         if (!ParseOptionalString(loadResult, imageType, jsonData, "mimeType", "")) {
946             return false;
947         }
948 
949         if (!GetMimeType(imageType, image->type)) {
950             RETURN_WITH_ERROR(loadResult, "Invalid mime type.");
951         }
952     }
953 
954     loadResult.data->images.push_back(move(image));
955 
956     return true;
957 }
958 
ParseFilterMode(LoadResult & loadResult,FilterMode & out,const json::value & jsonData,const string & filterName)959 bool ParseFilterMode(LoadResult& loadResult, FilterMode& out, const json::value& jsonData, const string& filterName)
960 {
961     int value = 0;
962     if (!ParseOptionalNumber<int>(loadResult, value, jsonData, filterName, static_cast<int>(FilterMode::LINEAR))) {
963         return false;
964     }
965 
966     out = (FilterMode)value;
967 
968     switch (out) {
969         case FilterMode::NEAREST:
970         case FilterMode::LINEAR:
971         case FilterMode::NEAREST_MIPMAP_NEAREST:
972         case FilterMode::LINEAR_MIPMAP_NEAREST:
973         case FilterMode::NEAREST_MIPMAP_LINEAR:
974         case FilterMode::LINEAR_MIPMAP_LINEAR:
975             return true;
976         default:
977             break;
978     }
979 
980     RETURN_WITH_ERROR(loadResult, "Invalid filter mode");
981 }
982 
ParseWrapMode(LoadResult & loadResult,WrapMode & out,const json::value & jsonData,const string & wrapModeName)983 bool ParseWrapMode(LoadResult& loadResult, WrapMode& out, const json::value& jsonData, const string& wrapModeName)
984 {
985     int value = 0;
986     if (!ParseOptionalNumber(loadResult, value, jsonData, wrapModeName, static_cast<int>(WrapMode::REPEAT))) {
987         return false;
988     }
989 
990     out = (WrapMode)value;
991 
992     switch (out) {
993         case WrapMode::CLAMP_TO_EDGE:
994         case WrapMode::MIRRORED_REPEAT:
995         case WrapMode::REPEAT:
996             return true;
997         default:
998             break;
999     }
1000 
1001     RETURN_WITH_ERROR(loadResult, "Invalid wrap mode");
1002 }
1003 
ParseSampler(LoadResult & loadResult,const json::value & jsonData)1004 bool ParseSampler(LoadResult& loadResult, const json::value& jsonData)
1005 {
1006     bool result = true;
1007 
1008     FilterMode magFilter;
1009     if (!ParseFilterMode(loadResult, magFilter, jsonData, "magFilter")) {
1010         result = false;
1011     }
1012 
1013     FilterMode minFilter;
1014     if (!ParseFilterMode(loadResult, minFilter, jsonData, "minFilter")) {
1015         result = false;
1016     }
1017 
1018     WrapMode wrapS;
1019     if (!ParseWrapMode(loadResult, wrapS, jsonData, "wrapS")) {
1020         result = false;
1021     }
1022 
1023     WrapMode wrapT;
1024     if (!ParseWrapMode(loadResult, wrapT, jsonData, "wrapT")) {
1025         result = false;
1026     }
1027 
1028     auto sampler = make_unique<Sampler>();
1029     if (result) {
1030         sampler->magFilter = magFilter;
1031         sampler->minFilter = minFilter;
1032         sampler->wrapS = wrapS;
1033         sampler->wrapT = wrapT;
1034     }
1035 
1036     loadResult.data->samplers.push_back(move(sampler));
1037 
1038     return result;
1039 }
1040 
ParseNormalTexture(LoadResult & loadResult,const json::value & jsonData,NormalTexture & normalTexture)1041 bool ParseNormalTexture(LoadResult& loadResult, const json::value& jsonData, NormalTexture& normalTexture)
1042 {
1043     if (auto normalJson = jsonData.find("normalTexture"); normalJson) {
1044         if (!ParseTextureInfo(loadResult, normalTexture.textureInfo, *normalJson)) {
1045             return false;
1046         }
1047 
1048         if (!ParseOptionalNumber<float>(loadResult, normalTexture.scale, *normalJson, "scale", normalTexture.scale)) {
1049             return false;
1050         }
1051     }
1052 
1053     return true;
1054 }
1055 
ParseOcclusionTexture(LoadResult & loadResult,const json::value & jsonData,OcclusionTexture & occlusionTexture)1056 bool ParseOcclusionTexture(LoadResult& loadResult, const json::value& jsonData, OcclusionTexture& occlusionTexture)
1057 {
1058     if (auto occlusionJson = jsonData.find("occlusionTexture"); occlusionJson) {
1059         if (!ParseTextureInfo(loadResult, occlusionTexture.textureInfo, *occlusionJson)) {
1060             return false;
1061         }
1062 
1063         if (!ParseOptionalNumber<float>(
1064                 loadResult, occlusionTexture.strength, *occlusionJson, "strength", occlusionTexture.strength)) {
1065             return false;
1066         }
1067     }
1068 
1069     return true;
1070 }
1071 
ParseEmissiveTexture(LoadResult & loadResult,const json::value & jsonData,TextureInfo & emissiveTexture)1072 bool ParseEmissiveTexture(LoadResult& loadResult, const json::value& jsonData, TextureInfo& emissiveTexture)
1073 {
1074     if (auto emissiveJson = jsonData.find("emissiveTexture"); emissiveJson) {
1075         if (!ParseTextureInfo(loadResult, emissiveTexture, *emissiveJson)) {
1076             return false;
1077         }
1078     }
1079 
1080     return true;
1081 }
1082 
ParseMaterialExtras(LoadResult & loadResult,const json::value & jsonData,Material & material)1083 bool ParseMaterialExtras(LoadResult& loadResult, const json::value& jsonData, Material& material)
1084 {
1085     if (auto extrasJson = jsonData.find("extras"); extrasJson) {
1086 #if defined(GLTF2_EXTRAS_CLEAR_COAT_MATERIAL)
1087         const auto parseClearCoat = [&material](LoadResult& loadResult, const json::value& materialJson) -> bool {
1088             if (!ParseOptionalNumber<float>(
1089                     loadResult, material.clearcoat.factor, materialJson, "factor", material.clearcoat.factor)) {
1090                 return false;
1091             }
1092 
1093             if (!ParseOptionalNumber<float>(loadResult, material.clearcoat.roughness, materialJson, "roughness",
1094                     material.clearcoat.roughness)) {
1095                 return false;
1096             }
1097 
1098             return true;
1099         };
1100         if (!ParseObject(loadResult, *extrasJson, "clearCoat", parseClearCoat)) {
1101             return false;
1102         }
1103 #endif
1104     }
1105     return true;
1106 }
1107 
1108 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT)
ParseKhrMaterialsClearcoat(LoadResult & loadResult,const json::value & jsonData,Material::Clearcoat & clearcoat)1109 bool ParseKhrMaterialsClearcoat(LoadResult& loadResult, const json::value& jsonData, Material::Clearcoat& clearcoat)
1110 {
1111     if (auto clearcoatJson = jsonData.find("KHR_materials_clearcoat"); clearcoatJson) {
1112         // clearcoatFactor
1113         if (!ParseOptionalNumber(loadResult, clearcoat.factor, *clearcoatJson, "clearcoatFactor", 0.f)) {
1114             return false;
1115         }
1116 
1117         // clearcoatTexture
1118         const auto parseClearcoatTexture = [&textureInfo = clearcoat.texture](
1119                                                LoadResult& loadResult, const json::value& clearcoat) -> bool {
1120             return ParseTextureInfo(loadResult, textureInfo, clearcoat);
1121         };
1122         if (!ParseObject(loadResult, *clearcoatJson, "clearcoatTexture", parseClearcoatTexture)) {
1123             return false;
1124         }
1125 
1126         // clearcoaRoughnessFactor
1127         if (!ParseOptionalNumber(loadResult, clearcoat.roughness, *clearcoatJson, "clearcoatRoughnessFactor", 0.f)) {
1128             return false;
1129         }
1130 
1131         // clearcoatRougnessTexture
1132         const auto parseClearcoatRoughnessTexture = [&textureInfo = clearcoat.roughnessTexture](
1133                                                         LoadResult& loadResult, const json::value& clearcoat) -> bool {
1134             return ParseTextureInfo(loadResult, textureInfo, clearcoat);
1135         };
1136         if (!ParseObject(loadResult, *clearcoatJson, "clearcoatRoughnessTexture", parseClearcoatRoughnessTexture)) {
1137             return false;
1138         }
1139 
1140         // clearcoatNormalTexture
1141         const auto parseClearcoatNormalTexture = [&normalTexture = clearcoat.normalTexture](
1142                                                      LoadResult& loadResult, const json::value& clearcoat) -> bool {
1143             if (!ParseTextureInfo(loadResult, normalTexture.textureInfo, clearcoat)) {
1144                 return false;
1145             }
1146 
1147             if (!ParseOptionalNumber(loadResult, normalTexture.scale, clearcoat, "scale", normalTexture.scale)) {
1148                 return false;
1149             }
1150             return true;
1151         };
1152         if (!ParseObject(loadResult, *clearcoatJson, "clearcoatNormalTexture", parseClearcoatNormalTexture)) {
1153             return false;
1154         }
1155         return true;
1156     }
1157     return true;
1158 }
1159 #endif
1160 
1161 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_EMISSIVE_STRENGTH)
ParseKhrMaterialsEmissiveStrength(LoadResult & loadResult,const json::value & jsonData,Material & material)1162 bool ParseKhrMaterialsEmissiveStrength(LoadResult& loadResult, const json::value& jsonData, Material& material)
1163 {
1164     if (auto emissiveStrengthJson = jsonData.find("KHR_materials_emissive_strength"); emissiveStrengthJson) {
1165         return ParseOptionalNumber(
1166             loadResult, material.emissiveFactor.w, *emissiveStrengthJson, "emissiveStrength", 1.f);
1167     }
1168     return true;
1169 }
1170 #endif
1171 
1172 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
ParseKhrMaterialsIor(LoadResult & loadResult,const json::value & jsonData,Material::Ior & ior)1173 bool ParseKhrMaterialsIor(LoadResult& loadResult, const json::value& jsonData, Material::Ior& ior)
1174 {
1175     if (auto iorJson = jsonData.find("KHR_materials_ior"); iorJson) {
1176         return ParseOptionalNumber(loadResult, ior.ior, *iorJson, "ior", ior.ior);
1177     }
1178     return true;
1179 }
1180 #endif
1181 
1182 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
ParseKhrMaterialsPbrSpecularGlossiness(LoadResult & loadResult,const json::value & jsonData,Material & material)1183 bool ParseKhrMaterialsPbrSpecularGlossiness(LoadResult& loadResult, const json::value& jsonData, Material& material)
1184 {
1185     if (auto specGlossJson = jsonData.find("KHR_materials_pbrSpecularGlossiness"); specGlossJson) {
1186         material.type = Material::Type::SpecularGlossiness;
1187 
1188         if (!ParseOptionalMath(loadResult, material.specularGlossiness.diffuseFactor, *specGlossJson, "diffuseFactor",
1189                 material.specularGlossiness.diffuseFactor)) {
1190             return false;
1191         }
1192 
1193         const auto parseDiffuseTexture = [&material](LoadResult& loadResult, const json::value& jsonData) -> bool {
1194             return ParseTextureInfo(loadResult, material.specularGlossiness.diffuseTexture, jsonData);
1195         };
1196         if (!ParseObject(loadResult, *specGlossJson, "diffuseTexture", parseDiffuseTexture)) {
1197             return false;
1198         }
1199 
1200         if (!ParseOptionalMath(loadResult, material.specularGlossiness.specularFactor, *specGlossJson, "specularFactor",
1201                 material.specularGlossiness.specularFactor)) {
1202             return false;
1203         }
1204 
1205         if (!ParseOptionalNumber<float>(
1206                 loadResult, material.specularGlossiness.glossinessFactor, *specGlossJson, "glossinessFactor", 1.0f)) {
1207             return false;
1208         }
1209 
1210         const auto parseSpecularGlossinessTexture = [&material](
1211                                                         LoadResult& loadResult, const json::value& jsonData) -> bool {
1212             return ParseTextureInfo(loadResult, material.specularGlossiness.specularGlossinessTexture, jsonData);
1213         };
1214         if (!ParseObject(loadResult, *specGlossJson, "specularGlossinessTexture", parseSpecularGlossinessTexture)) {
1215             return false;
1216         }
1217     }
1218     return true;
1219 }
1220 #endif
1221 
1222 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
ParseKhrMaterialsSheen(LoadResult & loadResult,const json::value & jsonData,Material::Sheen & sheen)1223 bool ParseKhrMaterialsSheen(LoadResult& loadResult, const json::value& jsonData, Material::Sheen& sheen)
1224 {
1225     if (auto sheenJson = jsonData.find("KHR_materials_sheen"); sheenJson) {
1226         // sheenColorFactor
1227         if (!ParseOptionalMath(loadResult, sheen.factor, *sheenJson, "sheenColorFactor", {})) {
1228             return false;
1229         }
1230 
1231         // sheenColorTexture
1232         const auto parseSheenTexture = [&textureInfo = sheen.texture](
1233                                            LoadResult& loadResult, const json::value& sheen) -> bool {
1234             return ParseTextureInfo(loadResult, textureInfo, sheen);
1235         };
1236         if (!ParseObject(loadResult, *sheenJson, "sheenColorTexture", parseSheenTexture)) {
1237             return false;
1238         }
1239 
1240         // sheenRoughnessFactor
1241         if (!ParseOptionalNumber(loadResult, sheen.roughness, *sheenJson, "sheenRoughnessFactor", 0.f)) {
1242             return false;
1243         }
1244 
1245         // sheenRougnessTexture
1246         const auto parseSheenRoughnessTexture = [&textureInfo = sheen.roughnessTexture](
1247                                                     LoadResult& loadResult, const json::value& sheen) -> bool {
1248             return ParseTextureInfo(loadResult, textureInfo, sheen);
1249         };
1250         if (!ParseObject(loadResult, *sheenJson, "sheenRoughnessTexture", parseSheenRoughnessTexture)) {
1251             return false;
1252         }
1253     }
1254     return true;
1255 }
1256 #endif
1257 
1258 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
ParseKhrMaterialsSpecular(LoadResult & loadResult,const json::value & jsonData,Material::Specular & specular)1259 bool ParseKhrMaterialsSpecular(LoadResult& loadResult, const json::value& jsonData, Material::Specular& specular)
1260 {
1261     if (auto specularJson = jsonData.find("KHR_materials_specular"); specularJson) {
1262         // specularFactor
1263         if (!ParseOptionalNumber(loadResult, specular.factor, *specularJson, "specularFactor", 1.f)) {
1264             return false;
1265         }
1266 
1267         // specularTexture
1268         const auto parseSpecularTexture = [&textureInfo = specular.texture](
1269                                               LoadResult& loadResult, const json::value& specular) -> bool {
1270             return ParseTextureInfo(loadResult, textureInfo, specular);
1271         };
1272         if (!ParseObject(loadResult, *specularJson, "specularTexture", parseSpecularTexture)) {
1273             return false;
1274         }
1275 
1276         // specularColorFactor
1277         if (!ParseOptionalMath(loadResult, specular.color, *specularJson, "specularColorFactor", specular.color)) {
1278             return false;
1279         }
1280 
1281         // specularColorTexture
1282         const auto parseSpecularColorTexture = [&textureInfo = specular.colorTexture](
1283                                                    LoadResult& loadResult, const json::value& specular) -> bool {
1284             return ParseTextureInfo(loadResult, textureInfo, specular);
1285         };
1286         if (!ParseObject(loadResult, *specularJson, "specularColorTexture", parseSpecularColorTexture)) {
1287             return false;
1288         }
1289     }
1290     return true;
1291 }
1292 #endif
1293 
1294 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
ParseKhrMaterialsTransmission(LoadResult & loadResult,const json::value & jsonData,Material::Transmission & transmission)1295 bool ParseKhrMaterialsTransmission(
1296     LoadResult& loadResult, const json::value& jsonData, Material::Transmission& transmission)
1297 {
1298     if (auto transmissionJson = jsonData.find("KHR_materials_transmission"); transmissionJson) {
1299         // transmissionFactor
1300         if (!ParseOptionalNumber(loadResult, transmission.factor, *transmissionJson, "transmissionFactor", 0.f)) {
1301             return false;
1302         }
1303 
1304         // transmissionTexture
1305         const auto parseTransmissionTexture = [&textureInfo = transmission.texture](
1306                                                   LoadResult& loadResult, const json::value& transmission) -> bool {
1307             return ParseTextureInfo(loadResult, textureInfo, transmission);
1308         };
1309         if (!ParseObject(loadResult, *transmissionJson, "transmissionTexture", parseTransmissionTexture)) {
1310             return false;
1311         }
1312     }
1313     return true;
1314 }
1315 #endif
1316 
ParseMaterialExtensions(LoadResult & loadResult,const json::value & jsonData,Material & material)1317 bool ParseMaterialExtensions(LoadResult& loadResult, const json::value& jsonData, Material& material)
1318 {
1319     if (auto extensionsJson = jsonData.find("extensions"); extensionsJson) {
1320 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT)
1321         if (!ParseKhrMaterialsClearcoat(loadResult, *extensionsJson, material.clearcoat)) {
1322             return false;
1323         }
1324 #endif
1325 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_EMISSIVE_STRENGTH)
1326         if (!ParseKhrMaterialsEmissiveStrength(loadResult, *extensionsJson, material)) {
1327             return false;
1328         }
1329 #endif
1330 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
1331         if (!ParseKhrMaterialsIor(loadResult, *extensionsJson, material.ior)) {
1332             return false;
1333         }
1334 #endif
1335 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
1336         if (!ParseKhrMaterialsPbrSpecularGlossiness(loadResult, *extensionsJson, material)) {
1337             return false;
1338         }
1339 #endif
1340 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
1341         if (!ParseKhrMaterialsSheen(loadResult, *extensionsJson, material.sheen)) {
1342             return false;
1343         }
1344 #endif
1345 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
1346         if (!ParseKhrMaterialsSpecular(loadResult, *extensionsJson, material.specular)) {
1347             return false;
1348         }
1349 #endif
1350 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
1351         if (!ParseKhrMaterialsTransmission(loadResult, *extensionsJson, material.transmission)) {
1352             return false;
1353         }
1354 #endif
1355 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_UNLIT)
1356         const auto parseUnlitExtension = [&material](LoadResult& loadResult, const json::value& materialJson) -> bool {
1357             material.type = Material::Type::Unlit;
1358             return true;
1359         };
1360 
1361         if (!ParseObject(loadResult, *extensionsJson, "KHR_materials_unlit", parseUnlitExtension)) {
1362             // Parsing of materials_unlit failed.
1363             return false;
1364         }
1365 #endif
1366         return true;
1367     }
1368     return true;
1369 }
1370 
ParseMaterial(LoadResult & loadResult,const json::value & jsonData)1371 bool ParseMaterial(LoadResult& loadResult, const json::value& jsonData)
1372 {
1373     bool result = true;
1374 
1375     auto material = make_unique<Material>();
1376     if (!ParseMetallicRoughness(loadResult, jsonData, material->metallicRoughness)) {
1377         result = false;
1378     }
1379 
1380     if (!ParseNormalTexture(loadResult, jsonData, material->normalTexture)) {
1381         result = false;
1382     }
1383 
1384     if (!ParseOcclusionTexture(loadResult, jsonData, material->occlusionTexture)) {
1385         result = false;
1386     }
1387 
1388     if (!ParseEmissiveTexture(loadResult, jsonData, material->emissiveTexture)) {
1389         result = false;
1390     }
1391 
1392     if (!ParseOptionalString(
1393             loadResult, material->name, jsonData, "name", "material_" + to_string(loadResult.data->materials.size()))) {
1394         result = false;
1395     }
1396 
1397     if (Math::Vec3 emissive; !ParseOptionalMath(loadResult, emissive, jsonData, "emissiveFactor", emissive)) {
1398         result = false;
1399     } else {
1400         material->emissiveFactor.x = emissive.x;
1401         material->emissiveFactor.y = emissive.y;
1402         material->emissiveFactor.z = emissive.z;
1403     }
1404 
1405     string alphaMode;
1406     if (!ParseOptionalString(loadResult, alphaMode, jsonData, "alphaMode", "OPAQUE")) {
1407         result = false;
1408     }
1409 
1410     if (!GetAlphaMode(alphaMode, material->alphaMode)) {
1411         SetError(loadResult, "Invalid alpha mode.");
1412         result = false;
1413     }
1414 
1415     if (!ParseOptionalNumber<float>(
1416             loadResult, material->alphaCutoff, jsonData, "alphaCutoff", material->alphaCutoff)) {
1417         result = false;
1418     }
1419 
1420     if (!ParseOptionalBoolean(loadResult, material->doubleSided, jsonData, "doubleSided", false)) {
1421         result = false;
1422     }
1423 
1424     if (!ParseMaterialExtras(loadResult, jsonData, *material)) {
1425         result = false;
1426     }
1427 
1428     if (!ParseMaterialExtensions(loadResult, jsonData, *material)) {
1429         result = false;
1430     }
1431 
1432     loadResult.data->materials.push_back(move(material));
1433 
1434     return result;
1435 }
1436 
PrimitiveAttributes(LoadResult & loadResult,const json::value & jsonData,MeshPrimitive & meshPrimitive)1437 bool PrimitiveAttributes(LoadResult& loadResult, const json::value& jsonData, MeshPrimitive& meshPrimitive)
1438 {
1439     if (const auto* attirbutesJson = jsonData.find("attributes"); attirbutesJson && attirbutesJson->is_object()) {
1440         for (const auto& it : attirbutesJson->object_) {
1441             if (it.value.is_number()) {
1442                 Attribute attribute;
1443 
1444                 if (!GetAttributeType(it.key, attribute.attribute)) {
1445                     RETURN_WITH_ERROR(loadResult, "Invalid attribute type.");
1446                 }
1447 
1448                 const uint32_t accessor = it.value.as_number<uint32_t>();
1449                 if (accessor < loadResult.data->accessors.size()) {
1450                     attribute.accessor = loadResult.data->accessors[accessor].get();
1451 
1452                     auto const validationResult = ValidatePrimitiveAttribute(
1453                         attribute.attribute.type, attribute.accessor->type, attribute.accessor->componentType);
1454                     if (!validationResult.empty()) {
1455 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
1456                         if (loadResult.data->quantization) {
1457                             auto const extendedValidationResult = ValidatePrimitiveAttributeQuatization(
1458                                 attribute.attribute.type, attribute.accessor->type, attribute.accessor->componentType);
1459                             if (!extendedValidationResult.empty()) {
1460                                 RETURN_WITH_ERROR(loadResult, extendedValidationResult);
1461                             }
1462                         } else {
1463 #else
1464                         {
1465 #endif
1466                             RETURN_WITH_ERROR(loadResult, validationResult);
1467                         }
1468                     }
1469 
1470                     meshPrimitive.attributes.push_back(attribute);
1471                 }
1472             }
1473         }
1474         if (std::none_of(meshPrimitive.attributes.begin(), meshPrimitive.attributes.end(),
1475                 [](const Attribute& attr) { return attr.attribute.type == AttributeType::POSITION; })) {
1476             RETURN_WITH_ERROR(loadResult, "Primitive must have POSITION attribute.");
1477         }
1478     } else {
1479         RETURN_WITH_ERROR(loadResult, "Missing primitive.attributes.");
1480     }
1481     return true;
1482 }
1483 
1484 bool PrimitiveTargets(
1485     LoadResult& loadResult, const json::value& jsonData, MeshPrimitive& meshPrimitive, bool compressed)
1486 {
1487     return ForEachInArray(loadResult, jsonData, "targets",
1488         [&meshPrimitive, compressed](LoadResult& loadResult, const json::value& target) -> bool {
1489             MorphTarget mTarget;
1490 #ifdef GLTF2_EXTENSION_IGFX_COMPRESSED
1491             mTarget.iGfxCompressed = compressed;
1492 #endif
1493             for (const auto& it : target.object_) {
1494                 if (it.value.is_number()) {
1495                     Attribute attribute;
1496 
1497                     if (!GetAttributeType(it.key, attribute.attribute)) {
1498                         RETURN_WITH_ERROR(loadResult, "Invalid attribute type.");
1499                     }
1500 
1501                     const uint32_t accessor = it.value.as_number<uint32_t>();
1502 
1503                     if (accessor < loadResult.data->accessors.size()) {
1504                         attribute.accessor = loadResult.data->accessors[accessor].get();
1505 
1506                         auto const validationResult = ValidateMorphTargetAttribute(
1507                             attribute.attribute.type, attribute.accessor->type, attribute.accessor->componentType);
1508                         if (!validationResult.empty()) {
1509 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
1510                             if (loadResult.data->quantization) {
1511                                 auto const extendedValidationResult =
1512                                     ValidateMorphTargetAttributeQuantization(attribute.attribute.type,
1513                                         attribute.accessor->type, attribute.accessor->componentType);
1514                                 if (!extendedValidationResult.empty()) {
1515                                     RETURN_WITH_ERROR(loadResult, extendedValidationResult);
1516                                 }
1517                             } else {
1518 #else
1519                                 {
1520 #endif
1521                                 RETURN_WITH_ERROR(loadResult, validationResult);
1522                             }
1523                         }
1524 
1525                         mTarget.target.push_back(attribute);
1526                     }
1527                 }
1528             }
1529 
1530             meshPrimitive.targets.push_back(move(mTarget));
1531 
1532             return true;
1533         });
1534 }
1535 
1536 bool ParsePrimitive(LoadResult& loadResult, vector<MeshPrimitive>& primitives, const json::value& jsonData)
1537 {
1538     MeshPrimitive meshPrimitive;
1539 
1540     if (!PrimitiveAttributes(loadResult, jsonData, meshPrimitive)) {
1541         return false;
1542     }
1543 
1544     size_t indices;
1545     if (!ParseOptionalNumber<size_t>(loadResult, indices, jsonData, "indices", GLTF_INVALID_INDEX)) {
1546         return false;
1547     }
1548     if (indices != GLTF_INVALID_INDEX && indices < loadResult.data->accessors.size()) {
1549         meshPrimitive.indices = loadResult.data->accessors[indices].get();
1550     }
1551 
1552     if (!ParseOptionalNumber<uint32_t>(
1553             loadResult, meshPrimitive.materialIndex, jsonData, "material", GLTF_INVALID_INDEX)) {
1554         return false;
1555     }
1556     if (meshPrimitive.materialIndex != GLTF_INVALID_INDEX &&
1557         meshPrimitive.materialIndex < loadResult.data->materials.size()) {
1558         meshPrimitive.material = loadResult.data->materials[meshPrimitive.materialIndex].get();
1559     } else {
1560         meshPrimitive.material = loadResult.data->defaultMaterial.get();
1561     }
1562 
1563     int mode;
1564     if (!ParseOptionalNumber<int>(loadResult, mode, jsonData, "mode", static_cast<int>(RenderMode::TRIANGLES))) {
1565         return false;
1566     }
1567 
1568     if (!RangedEnumCast<RenderMode>(loadResult, meshPrimitive.mode, mode)) {
1569         return false;
1570     }
1571 
1572     bool compressed = false;
1573 
1574     if (const auto& extensionsJson = jsonData.find("extensions"); extensionsJson) {
1575 #ifdef GLTF2_EXTENSION_IGFX_COMPRESSED
1576         if (const auto& compressedJson = extensionsJson->find("IGFX_compressed"); compressedJson) {
1577             compressed = true;
1578             if (!PrimitiveTargets(loadResult, *compressedJson, meshPrimitive, compressed)) {
1579                 return false;
1580             }
1581         }
1582 #endif
1583     }
1584 
1585     if (!compressed) {
1586         if (!PrimitiveTargets(loadResult, jsonData, meshPrimitive, compressed)) {
1587             return false;
1588         }
1589     }
1590 
1591     primitives.push_back(move(meshPrimitive));
1592 
1593     return true;
1594 }
1595 
1596 bool MeshExtras(LoadResult& loadResult, const json::value& jsonData, array_view<MeshPrimitive> primitives)
1597 {
1598     size_t index = 0;
1599     return ForEachInArray(loadResult, jsonData, "targetNames",
1600         [&primitives, &index](LoadResult& loadResult, const json::value& targetName) -> bool {
1601             if (!targetName.is_string()) {
1602                 RETURN_WITH_ERROR(loadResult, "mesh.extras.targetNames should be an array of strings");
1603             }
1604 
1605             auto name = targetName.string_;
1606 
1607             for (auto& primitive : primitives) {
1608                 if (index < primitive.targets.size()) {
1609                     primitive.targets[index].name = name;
1610                 }
1611             }
1612 
1613             index++;
1614 
1615             return true;
1616         });
1617 }
1618 
1619 bool ParseMesh(LoadResult& loadResult, const json::value& jsonData)
1620 {
1621     bool result = true;
1622 
1623     string name;
1624     if (!ParseOptionalString(loadResult, name, jsonData, "name", "")) {
1625         return false;
1626     }
1627 
1628     vector<MeshPrimitive> primitives;
1629     if (auto const primitivesJson = jsonData.find("primitives"); primitivesJson) {
1630         if (!ForEachInArray(
1631                 loadResult, *primitivesJson, [&primitives](LoadResult& loadResult, const json::value& item) -> bool {
1632                     return ParsePrimitive(loadResult, primitives, item);
1633                 })) {
1634             return false;
1635         }
1636     }
1637 
1638     vector<float> weights;
1639     const auto parseWeights = [&weights](LoadResult& loadResult, const json::value& weight) -> bool {
1640         if (weight.is_number()) {
1641             weights.push_back(weight.as_number<float>());
1642         }
1643         return true;
1644     };
1645 
1646     if (!ForEachInArray(loadResult, jsonData, "weights", parseWeights)) {
1647         return false;
1648     }
1649 
1650     // validate morph target counts
1651     for (size_t i = 1; i < primitives.size(); i++) {
1652         if (primitives[i].targets.size() != primitives[0].targets.size()) {
1653             SetError(loadResult,
1654                 "Morph target count mismatch: each primitive of a mesh should have same amount of morph targets");
1655             result = false;
1656         }
1657     }
1658 
1659     const auto parseExtras = [&primitives](LoadResult& loadResult, const json::value& extras) -> bool {
1660         return MeshExtras(loadResult, extras, primitives);
1661     };
1662 
1663     if (!ParseObject(loadResult, jsonData, "extras", parseExtras)) {
1664         return false;
1665     }
1666 
1667     auto mesh = make_unique<Mesh>();
1668     if (result) {
1669         mesh->name = move(name);
1670         mesh->weights = move(weights);
1671         mesh->primitives = move(primitives);
1672     }
1673 
1674     loadResult.data->meshes.push_back(move(mesh));
1675 
1676     return true;
1677 }
1678 
1679 bool CameraPerspective(
1680     LoadResult& loadResult, const json::value& jsonData, Camera::Attributes::Perspective& perspective)
1681 {
1682     if (!ParseOptionalNumber<float>(loadResult, perspective.aspect, jsonData, "aspectRatio", -1.f)) {
1683         return false;
1684     }
1685     if (!ParseOptionalNumber<float>(loadResult, perspective.yfov, jsonData, "yfov", -1.f)) { // required
1686         return false;
1687     }
1688     if (!ParseOptionalNumber<float>(loadResult, perspective.zfar, jsonData, "zfar", -1.f)) {
1689         return false;
1690     }
1691     if (!ParseOptionalNumber<float>(loadResult, perspective.znear, jsonData, "znear", -1.f)) { // required
1692         return false;
1693     }
1694     if (perspective.yfov < 0 || perspective.znear < 0) {
1695         RETURN_WITH_ERROR(loadResult, "Invalid camera properties (perspective)");
1696     }
1697 
1698     return true;
1699 }
1700 
1701 bool CameraOrthographic(LoadResult& loadResult, const json::value& jsonData, Camera::Attributes::Ortho& ortho)
1702 {
1703     if (!ParseOptionalNumber<float>(loadResult, ortho.xmag, jsonData, "xmag", 0)) { // required
1704         return false;
1705     }
1706     if (!ParseOptionalNumber<float>(loadResult, ortho.ymag, jsonData, "ymag", 0)) { // required
1707         return false;
1708     }
1709     if (!ParseOptionalNumber<float>(loadResult, ortho.zfar, jsonData, "zfar", -1.f)) { // required
1710         return false;
1711     }
1712     if (!ParseOptionalNumber<float>(loadResult, ortho.znear, jsonData, "znear", -1.f)) { // required
1713         return false;
1714     }
1715     if (ortho.zfar < 0 || ortho.znear < 0 || ortho.xmag == 0 || ortho.ymag == 0) {
1716         RETURN_WITH_ERROR(loadResult, "Invalid camera properties (ortho)");
1717     }
1718     return true;
1719 }
1720 
1721 bool ParseCamera(LoadResult& loadResult, const json::value& jsonData)
1722 {
1723     bool result = true;
1724 
1725     auto camera = make_unique<Camera>();
1726     if (!ParseOptionalString(loadResult, camera->name, jsonData, "name", "")) {
1727         result = false;
1728     }
1729     string cameraType;
1730     if (!ParseOptionalString(loadResult, cameraType, jsonData, "type", "")) {
1731         result = false;
1732     }
1733     if (!GetCameraType(cameraType, camera->type)) {
1734         SetError(loadResult, "Invalid camera type");
1735         result = false;
1736     }
1737 
1738     switch (camera->type) {
1739         case CameraType::PERSPECTIVE: {
1740             const auto parser = [&camera](LoadResult& loadResult, const json::value& perspective) -> bool {
1741                 return CameraPerspective(loadResult, perspective, camera->attributes.perspective);
1742             };
1743 
1744             if (!ParseObject(loadResult, jsonData, "perspective", parser)) {
1745                 result = false;
1746             }
1747             break;
1748         }
1749 
1750         case CameraType::ORTHOGRAPHIC: {
1751             const auto parser = [&camera](LoadResult& loadResult, const json::value& orthographic) -> bool {
1752                 return CameraOrthographic(loadResult, orthographic, camera->attributes.ortho);
1753             };
1754 
1755             if (!ParseObject(loadResult, jsonData, "orthographic", parser)) {
1756                 result = false;
1757             }
1758             break;
1759         }
1760 
1761         default:
1762         case CameraType::INVALID: {
1763             SetError(loadResult, "Invalid camera type");
1764             result = false;
1765         }
1766     }
1767 
1768     loadResult.data->cameras.push_back(move(camera));
1769     return result;
1770 }
1771 
1772 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
1773 bool LightSpot(LoadResult& loadResult, const json::value& jsonData, decltype(KHRLight::positional.spot)& spot)
1774 {
1775     return ParseObject(
1776         loadResult, jsonData, "spot", [&spot](LoadResult& loadResult, const json::value& jsonData) -> bool {
1777             if (!ParseOptionalNumber<float>(loadResult, spot.innerAngle, jsonData, "innerConeAngle", 0.f)) {
1778                 return false;
1779             }
1780 
1781             if (!ParseOptionalNumber<float>(
1782                     loadResult, spot.outerAngle, jsonData, "outerConeAngle", 0.785398163397448f)) {
1783                 return false;
1784             }
1785 
1786             return true;
1787         });
1788 }
1789 
1790 bool LightShadow(LoadResult& loadResult, const json::value& jsonData, decltype(KHRLight::shadow)& shadow)
1791 {
1792     return ParseObject(
1793         loadResult, jsonData, "shadow", [&shadow](LoadResult& loadResult, const json::value& jsonData) -> bool {
1794             if (!ParseOptionalBoolean(loadResult, shadow.shadowCaster, jsonData, "caster", false)) {
1795                 return false;
1796             }
1797 
1798             if (!ParseOptionalNumber<float>(
1799                     loadResult, shadow.nearClipDistance, jsonData, "znear", shadow.nearClipDistance)) {
1800                 return false;
1801             }
1802 
1803             if (!ParseOptionalNumber<float>(
1804                     loadResult, shadow.farClipDistance, jsonData, "zfar", shadow.farClipDistance)) {
1805                 return false;
1806             }
1807             return true;
1808         });
1809 }
1810 
1811 bool ParseKHRLight(LoadResult& loadResult, const json::value& jsonData)
1812 {
1813     bool result = true;
1814 
1815     auto light = make_unique<KHRLight>();
1816 
1817     if (!ParseOptionalString(loadResult, light->name, jsonData, "name", "")) {
1818         result = false;
1819     }
1820 
1821     string lightType;
1822     if (!ParseOptionalString(loadResult, lightType, jsonData, "type", "")) {
1823         result = false;
1824     }
1825 
1826     if (!GetLightType(lightType, light->type)) {
1827         SetError(loadResult, "Invalid light type.");
1828         result = false;
1829     }
1830 
1831     const auto parsePositionalInfo = [&light](LoadResult& loadResult, const json::value& positional) -> bool {
1832         return ParseOptionalNumber<float>(loadResult, light->positional.range, positional, "range", 0.0f);
1833     };
1834 
1835     if (!ParseObject(loadResult, jsonData, "positional", parsePositionalInfo)) {
1836         result = false;
1837     }
1838 
1839     if (!ParseOptionalNumber<float>(loadResult, light->positional.range, jsonData, "range", 0.0f)) {
1840         return false;
1841     }
1842 
1843     if (!LightSpot(loadResult, jsonData, light->positional.spot)) {
1844         result = false;
1845     }
1846 
1847     if (!ParseOptionalMath(loadResult, light->color, jsonData, "color", light->color)) {
1848         result = false;
1849     }
1850 
1851     // blender uses strength
1852     if (!ParseOptionalNumber<float>(loadResult, light->intensity, jsonData, "strength", 1.0f)) {
1853         result = false;
1854     }
1855 
1856     // khronos uses intensity
1857     if (!ParseOptionalNumber<float>(loadResult, light->intensity, jsonData, "intensity", light->intensity)) {
1858         result = false;
1859     }
1860     if (!LightShadow(loadResult, jsonData, light->shadow)) {
1861         result = false;
1862     }
1863 
1864     loadResult.data->lights.push_back(move(light));
1865 
1866     return result;
1867 }
1868 #endif
1869 
1870 #if defined(GLTF2_EXTENSION_EXT_LIGHTS_IMAGE_BASED)
1871 bool ImageBasedLightIrradianceCoefficients(
1872     LoadResult& loadResult, const json::value& jsonData, vector<ImageBasedLight::LightingCoeff>& irradianceCoefficients)
1873 {
1874     const auto parseIrradianceCoefficients = [&irradianceCoefficients](
1875                                                  LoadResult& loadResult, const json::value& mipLevelJson) -> bool {
1876         ImageBasedLight::LightingCoeff coeff;
1877         if (mipLevelJson.is_array()) {
1878             coeff.reserve(mipLevelJson.array_.size());
1879             std::transform(mipLevelJson.array_.begin(), mipLevelJson.array_.end(), std::back_inserter(coeff),
1880                 [](const json::value& item) { return item.is_number() ? item.as_number<float>() : 0.f; });
1881         }
1882 
1883         if (coeff.size() != 3) {
1884             return false;
1885         }
1886 
1887         irradianceCoefficients.push_back(move(coeff));
1888         return true;
1889     };
1890 
1891     return ForEachInArray(loadResult, jsonData, "irradianceCoefficients", parseIrradianceCoefficients);
1892 }
1893 
1894 bool ImageBasedLightSpecularImages(
1895     LoadResult& loadResult, const json::value& jsonData, vector<ImageBasedLight::CubemapMipLevel>& specularImages)
1896 {
1897     const auto parseCubeMipLevel = [&specularImages](LoadResult& loadResult, const json::value& mipLevelJson) -> bool {
1898         ImageBasedLight::CubemapMipLevel mipLevel;
1899         static constexpr size_t requiredLevels = 6U;
1900         if (mipLevelJson.is_array() && (mipLevelJson.array_.size() == requiredLevels)) {
1901             mipLevel.reserve(mipLevelJson.array_.size());
1902             std::transform(mipLevelJson.array_.begin(), mipLevelJson.array_.end(), std::back_inserter(mipLevel),
1903                 [](const json::value& item) {
1904                     return item.is_number() ? item.as_number<size_t>() : GLTF_INVALID_INDEX;
1905                 });
1906         }
1907         if (mipLevel.size() != requiredLevels) {
1908             return false;
1909         }
1910         if (std::any_of(mipLevel.cbegin(), mipLevel.cend(),
1911                 [images = loadResult.data->images.size()](const size_t& index) { return index >= images; })) {
1912             return false;
1913         }
1914         specularImages.push_back(move(mipLevel));
1915         return true;
1916     };
1917 
1918     return ForEachInArray(loadResult, jsonData, "specularImages", parseCubeMipLevel);
1919 }
1920 
1921 bool ParseImageBasedLight(LoadResult& loadResult, const json::value& jsonData)
1922 {
1923     bool result = true;
1924 
1925     auto light = make_unique<ImageBasedLight>();
1926 
1927     if (!ParseOptionalString(loadResult, light->name, jsonData, "name", "")) {
1928         result = false;
1929     }
1930 
1931     if (!ParseOptionalMath(loadResult, light->rotation, jsonData, "rotation", light->rotation)) {
1932         result = false;
1933     }
1934 
1935     if (!ParseOptionalNumber<float>(loadResult, light->intensity, jsonData, "intensity", light->intensity)) {
1936         result = false;
1937     }
1938 
1939     if (!ImageBasedLightIrradianceCoefficients(loadResult, jsonData, light->irradianceCoefficients)) {
1940         result = false;
1941     }
1942 
1943     if (!ImageBasedLightSpecularImages(loadResult, jsonData, light->specularImages)) {
1944         result = false;
1945     }
1946 
1947     if (!ParseOptionalNumber<uint32_t>(
1948             loadResult, light->specularImageSize, jsonData, "specularImageSize", light->specularImageSize)) {
1949         result = false;
1950     }
1951 
1952     const auto parseExtras = [&light](LoadResult& loadResult, const json::value& e) -> bool {
1953         if (!ParseOptionalNumber(loadResult, light->skymapImage, e, "skymapImage", light->skymapImage)) {
1954             return false;
1955         }
1956 
1957         if (!ParseOptionalNumber(
1958                 loadResult, light->skymapImageLodLevel, e, "skymapImageLodLevel", light->skymapImageLodLevel)) {
1959             return false;
1960         }
1961 
1962         if (!ParseOptionalNumber(
1963                 loadResult, light->specularCubeImage, e, "specularCubeImage", light->specularCubeImage)) {
1964             return false;
1965         }
1966 
1967         return true;
1968     };
1969 
1970     if (!ParseObject(loadResult, jsonData, "extras", parseExtras)) {
1971         result = false;
1972     }
1973 
1974     loadResult.data->imageBasedLights.push_back(move(light));
1975 
1976     return result;
1977 }
1978 #endif
1979 
1980 bool NodeName(LoadResult& loadResult, const json::value& jsonData, string& name)
1981 {
1982     if (!ParseOptionalString(loadResult, name, jsonData, "name", "")) {
1983         return false;
1984     }
1985 
1986     if (name.empty()) {
1987         name = "node_" + to_string(loadResult.data->nodes.size());
1988     }
1989     return true;
1990 }
1991 
1992 bool NodeMesh(LoadResult& loadResult, const json::value& jsonData, Mesh*& mesh)
1993 {
1994     size_t meshIndex;
1995     if (!ParseOptionalNumber<size_t>(loadResult, meshIndex, jsonData, "mesh", GLTF_INVALID_INDEX)) {
1996         return false;
1997     }
1998 
1999     if (meshIndex != GLTF_INVALID_INDEX) {
2000         if (meshIndex < loadResult.data->meshes.size()) {
2001             mesh = loadResult.data->meshes[meshIndex].get();
2002         } else {
2003             SetError(loadResult, "Node refers to invalid mesh index");
2004             return false;
2005         }
2006     }
2007     return true;
2008 }
2009 
2010 bool NodeCamera(LoadResult& loadResult, const json::value& jsonData, Camera*& camera)
2011 {
2012     size_t cameraIndex;
2013     if (!ParseOptionalNumber<size_t>(loadResult, cameraIndex, jsonData, "camera", GLTF_INVALID_INDEX)) {
2014         return false;
2015     }
2016 
2017     if (cameraIndex != GLTF_INVALID_INDEX) {
2018         if (size_t(cameraIndex) < loadResult.data->cameras.size()) {
2019             camera = loadResult.data->cameras[cameraIndex].get();
2020         } else {
2021             SetError(loadResult, "Node refers to invalid camera index");
2022             return false;
2023         }
2024     }
2025     return true;
2026 }
2027 
2028 struct ExtensionData {
2029     size_t lightIndex;
2030     bool compressed;
2031 };
2032 
2033 std::optional<ExtensionData> NodeExtensions(LoadResult& loadResult, const json::value& jsonData, Node& node)
2034 {
2035     ExtensionData data { GLTF_INVALID_INDEX, false };
2036 
2037     const auto parseExtensions = [&data, &node](LoadResult& loadResult, const json::value& extensions) -> bool {
2038 #if defined(GLTF2_EXTENSION_KHR_LIGHTS)
2039         const auto parseLight = [&data](LoadResult& loadResult, const json::value& light) -> bool {
2040             if (!ParseOptionalNumber<size_t>(loadResult, data.lightIndex, light, "light", GLTF_INVALID_INDEX)) {
2041                 return false;
2042             }
2043 
2044             return true;
2045         };
2046 
2047         if (!ParseObject(loadResult, extensions, "KHR_lights_punctual", parseLight)) {
2048             return false;
2049         }
2050 #endif
2051 
2052 #if defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2053         if (data.lightIndex == GLTF_INVALID_INDEX) {
2054             const auto parseLightPbr = [&data](LoadResult& loadResult, const json::value& light) -> bool {
2055                 if (!ParseOptionalNumber<size_t>(loadResult, data.lightIndex, light, "light", GLTF_INVALID_INDEX)) {
2056                     return false;
2057                 }
2058 
2059                 if (data.lightIndex != GLTF_INVALID_INDEX) {
2060                     data.lightIndex += loadResult.data->pbrLightOffset;
2061                 }
2062 
2063                 return true;
2064             };
2065 
2066             if (!ParseObject(loadResult, extensions, "KHR_lights_pbr", parseLightPbr)) {
2067                 return false;
2068             }
2069         }
2070 #endif
2071 
2072 #ifdef GLTF2_EXTENSION_IGFX_COMPRESSED
2073         const auto parseCompressed = [&data, &weights = node.weights](
2074                                          LoadResult& loadResult, const json::value& compressedJson) {
2075             data.compressed = true;
2076             return ParseOptionalNumberArray(loadResult, weights, compressedJson, "weights", vector<float>());
2077         };
2078         if (!ParseObject(loadResult, extensions, "IGFX_compressed", parseCompressed)) {
2079             return false;
2080         }
2081 #endif
2082         return true;
2083     };
2084 
2085     if (!ParseObject(loadResult, jsonData, "extensions", parseExtensions)) {
2086         return std::nullopt;
2087     }
2088     return data;
2089 }
2090 
2091 bool NodeChildren(LoadResult& loadResult, const json::value& jsonData, Node& node)
2092 {
2093     return ForEachInArray(
2094         loadResult, jsonData, "children", [&node](LoadResult& loadResult, const json::value& item) -> bool {
2095             if (item.is_number()) {
2096                 // indices will be later resolved to pointers when all nodes have been parsed
2097                 // this is required since children may come later than parents
2098                 node.tmpChildren.push_back(item.as_number<size_t>());
2099                 return true;
2100             } else {
2101                 node.tmpChildren.push_back(GLTF_INVALID_INDEX);
2102                 SetError(loadResult, "Node children index was expected to be number");
2103                 return false;
2104             }
2105         });
2106 }
2107 
2108 bool NodeTransform(LoadResult& loadResult, const json::value& jsonData, Node& node)
2109 {
2110     bool result = true;
2111     if (auto const pos = jsonData.find("matrix"); pos) {
2112         if (ParseOptionalMath(loadResult, node.matrix, jsonData, "matrix", node.matrix)) {
2113             node.usesTRS = false;
2114         }
2115     } else {
2116         if (!ParseOptionalMath(loadResult, node.translation, jsonData, "translation", node.translation)) {
2117             result = false;
2118         }
2119 
2120         // order is x,y,z,w as defined in gltf
2121         if (!ParseOptionalMath(loadResult, node.rotation, jsonData, "rotation", node.rotation)) {
2122             result = false;
2123         }
2124 
2125         if (!ParseOptionalMath(loadResult, node.scale, jsonData, "scale", node.scale)) {
2126             result = false;
2127         }
2128     }
2129     return result;
2130 }
2131 
2132 bool NodeExtras(LoadResult& loadResult, const json::value& jsonData, Node& node)
2133 {
2134 #if defined(GLTF2_EXTRAS_RSDZ)
2135     const auto parseExtras = [&node](LoadResult& loadResult, const json::value& extras) -> bool {
2136         ParseOptionalString(loadResult, node.modelIdRSDZ, extras, "modelId", "");
2137         return true;
2138     };
2139     if (!ParseObject(loadResult, jsonData, "extras", parseExtras)) {
2140         return false;
2141     }
2142 #endif
2143     return true;
2144 }
2145 
2146 bool ParseNode(LoadResult& loadResult, const json::value& jsonData)
2147 {
2148     auto node = make_unique<Node>();
2149 
2150     bool result = NodeName(loadResult, jsonData, node->name);
2151 
2152     if (!NodeMesh(loadResult, jsonData, node->mesh)) {
2153         result = false;
2154     }
2155 
2156     if (!NodeCamera(loadResult, jsonData, node->camera)) {
2157         result = false;
2158     }
2159 
2160     if (auto extensionData = NodeExtensions(loadResult, jsonData, *node); extensionData) {
2161         if (!extensionData->compressed) {
2162             if (!ParseOptionalNumberArray(loadResult, node->weights, jsonData, "weights", vector<float>())) {
2163                 result = false;
2164             }
2165         }
2166         if (!node->mesh && node->weights.size() > 0) {
2167             SetError(loadResult, "No mesh defined for node using morph target weights");
2168             result = false;
2169         }
2170 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2171         if (extensionData->lightIndex != GLTF_INVALID_INDEX) {
2172             if (extensionData->lightIndex < loadResult.data->lights.size()) {
2173                 node->light = loadResult.data->lights[extensionData->lightIndex].get();
2174             } else {
2175                 SetError(loadResult, "Node refers to invalid light index");
2176                 result = false;
2177             }
2178         }
2179 #endif
2180     } else {
2181         result = false;
2182     }
2183 
2184     if (!ParseOptionalNumber(loadResult, node->tmpSkin, jsonData, "skin", GLTF_INVALID_INDEX)) {
2185         result = false;
2186     }
2187     if (!NodeChildren(loadResult, jsonData, *node)) {
2188         result = false;
2189     }
2190 
2191     if (!NodeTransform(loadResult, jsonData, *node)) {
2192         result = false;
2193     }
2194 
2195     if (!NodeExtras(loadResult, jsonData, *node)) {
2196         result = false;
2197     }
2198 
2199     loadResult.data->nodes.push_back(move(node));
2200     return result;
2201 }
2202 
2203 bool FinalizeNodes(LoadResult& loadResult)
2204 {
2205     bool result = true;
2206     // resolve indices to direct pointers
2207     auto& nodes = loadResult.data->nodes;
2208 
2209     for (const auto& node : nodes) {
2210         for (auto index : node->tmpChildren) {
2211             if (index >= nodes.size()) {
2212                 SetError(loadResult, "Invalid node index");
2213                 result = false;
2214                 continue;
2215             }
2216             auto childNode = nodes[index].get();
2217             if (childNode->parent) {
2218                 SetError(loadResult, "Node has multiple parents");
2219                 result = false;
2220                 continue;
2221             }
2222             // since parent owns childs, and we don't want ref-loops, pass a raw pointer instead
2223             childNode->parent = node.get();
2224             node->children.push_back(childNode);
2225         }
2226 
2227         if (node->tmpSkin != GLTF_INVALID_INDEX && node->tmpSkin < loadResult.data->skins.size()) {
2228             node->skin = loadResult.data->skins[node->tmpSkin].get();
2229         }
2230     }
2231 
2232     return result;
2233 }
2234 
2235 bool SceneExtensions(LoadResult& loadResult, const json::value& jsonData, Scene& scene)
2236 {
2237     const auto parseExtensions = [&scene](LoadResult& loadResult, const json::value& extensions) -> bool {
2238         size_t lightIndex = GLTF_INVALID_INDEX;
2239 #if defined(GLTF2_EXTENSION_KHR_LIGHTS)
2240         if (!ParseObject(loadResult, extensions, "KHR_lights",
2241                 [&lightIndex](LoadResult& loadResult, const json::value& light) -> bool {
2242                     return ParseOptionalNumber<size_t>(loadResult, lightIndex, light, "light", GLTF_INVALID_INDEX);
2243                 })) {
2244             return false;
2245         }
2246 #endif
2247 #if defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2248         if (lightIndex == GLTF_INVALID_INDEX) {
2249             if (!ParseObject(loadResult, extensions, "KHR_lights_pbr",
2250                     [&lightIndex](LoadResult& loadResult, const json::value& light) -> bool {
2251                         if (!ParseOptionalNumber<size_t>(loadResult, lightIndex, light, "light", GLTF_INVALID_INDEX)) {
2252                             return false;
2253                         } else if (lightIndex != GLTF_INVALID_INDEX) {
2254                             lightIndex += loadResult.data->pbrLightOffset;
2255                         }
2256                         return true;
2257                     })) {
2258                 return false;
2259             }
2260         }
2261 #endif
2262 
2263 #if defined(GLTF2_EXTENSION_EXT_LIGHTS_IMAGE_BASED)
2264         if (!ParseObject(loadResult, extensions, "EXT_lights_image_based",
2265                 [&scene](LoadResult& loadResult, const json::value& light) -> bool {
2266                     return ParseOptionalNumber<size_t>(
2267                         loadResult, scene.imageBasedLightIndex, light, "light", GLTF_INVALID_INDEX);
2268                 })) {
2269             return false;
2270         }
2271 #endif
2272 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2273         if (lightIndex != GLTF_INVALID_INDEX) {
2274             if (lightIndex < loadResult.data->lights.size()) {
2275                 // Only Ambient light could be set for scene
2276                 scene.light = loadResult.data->lights[lightIndex].get();
2277             } else {
2278                 SetError(loadResult, "Scene refers to invalid light index");
2279                 return false;
2280             }
2281         }
2282 #endif
2283         return true;
2284     };
2285 
2286     return ParseObject(loadResult, jsonData, "extensions", parseExtensions);
2287 }
2288 
2289 bool ParseScene(LoadResult& loadResult, const json::value& jsonData)
2290 {
2291     bool result = true;
2292 
2293     auto scene = make_unique<Scene>();
2294     if (!ParseOptionalString(loadResult, scene->name, jsonData, "name", "")) {
2295         return false;
2296     }
2297 
2298     const auto parseNodes = [&scene](LoadResult& loadResult, const json::value& nodeIndex) -> bool {
2299         if (!nodeIndex.is_number()) {
2300             RETURN_WITH_ERROR(loadResult, "Invalid node index (not a number)");
2301         }
2302 
2303         const size_t index = nodeIndex.as_number<size_t>();
2304         if (index >= loadResult.data->nodes.size()) {
2305             RETURN_WITH_ERROR(loadResult, "Invalid node index");
2306         }
2307 
2308         auto const equalsNodeToAdd = [nodeToAdd = loadResult.data->nodes[index].get()](
2309                                          auto const& node) { return node == nodeToAdd; };
2310 
2311         if (std::any_of(scene->nodes.begin(), scene->nodes.end(), equalsNodeToAdd)) {
2312             RETURN_WITH_ERROR(loadResult, "Non-unique node index");
2313         }
2314 
2315         scene->nodes.push_back(loadResult.data->nodes[index].get());
2316 
2317         return true;
2318     };
2319 
2320     if (!ForEachInArray(loadResult, jsonData, "nodes", parseNodes)) {
2321         result = false;
2322     }
2323 
2324     if (!SceneExtensions(loadResult, jsonData, *scene)) {
2325         result = false;
2326     }
2327 
2328     loadResult.data->scenes.push_back(move(scene));
2329 
2330     return result;
2331 }
2332 
2333 bool SceneContainsNode(Scene const& scene, Node const& node)
2334 {
2335     return std::any_of(
2336         scene.nodes.begin(), scene.nodes.end(), [nodePtr = &node](auto const node) { return nodePtr == node; });
2337 }
2338 
2339 Scene* SceneContainingNode(vector<unique_ptr<Scene>> const& scenes, Node const& node)
2340 {
2341     auto const pos = std::find_if(scenes.begin(), scenes.end(),
2342         [nodePtr = &node](auto const& scene) { return SceneContainsNode(*scene, *nodePtr); });
2343     if (pos != scenes.end()) {
2344         return pos->get();
2345     }
2346     return nullptr;
2347 }
2348 
2349 bool JointsInSameScene(Skin const& skin, LoadResult& loadResult)
2350 {
2351     Scene const* scene = nullptr;
2352     return std::all_of(skin.joints.begin(), skin.joints.end(), [&loadResult, &scene](auto const joint) {
2353         Node const* hierarchyRoot = joint;
2354         while (hierarchyRoot->parent) {
2355             hierarchyRoot = hierarchyRoot->parent;
2356         }
2357 
2358         if (!scene) {
2359             scene = SceneContainingNode(loadResult.data->scenes, *hierarchyRoot);
2360             if (!scene) {
2361                 RETURN_WITH_ERROR(loadResult, "Joint must belong to a scene");
2362             }
2363         } else if (!SceneContainsNode(*scene, *hierarchyRoot)) {
2364             RETURN_WITH_ERROR(loadResult, "Skin joints must belong to the same scene");
2365         }
2366         return true;
2367     });
2368 }
2369 
2370 bool ParseSkin(LoadResult& loadResult, const json::value& jsonData)
2371 {
2372     auto skin = make_unique<Skin>();
2373 
2374     size_t matrices;
2375     if (!ParseOptionalNumber<size_t>(loadResult, matrices, jsonData, "inverseBindMatrices", GLTF_INVALID_INDEX)) {
2376         return false;
2377     }
2378 
2379     if (matrices != GLTF_INVALID_INDEX && matrices < loadResult.data->accessors.size()) {
2380         skin->inverseBindMatrices = loadResult.data->accessors[matrices].get();
2381     }
2382 
2383     size_t skeleton;
2384     if (!ParseOptionalNumber<size_t>(loadResult, skeleton, jsonData, "skeleton", GLTF_INVALID_INDEX)) {
2385         return false;
2386     }
2387 
2388     if (skeleton != GLTF_INVALID_INDEX && skeleton < loadResult.data->nodes.size()) {
2389         skin->skeleton = loadResult.data->nodes[skeleton].get();
2390     }
2391 
2392     vector<size_t> joints;
2393     if (!ParseOptionalNumberArray(loadResult, joints, jsonData, "joints", vector<size_t>())) {
2394         return false;
2395     }
2396 
2397     if (joints.size() > CORE_DEFAULT_MATERIAL_MAX_JOINT_COUNT) {
2398         CORE_LOG_W("Number of joints (%zu) more than current limit (%u)", joints.size(),
2399             CORE_DEFAULT_MATERIAL_MAX_JOINT_COUNT);
2400     }
2401 
2402     skin->joints.resize(joints.size());
2403 
2404     for (size_t i = 0; i < joints.size(); i++) {
2405         if (joints[i] >= loadResult.data->nodes.size()) {
2406             RETURN_WITH_ERROR(loadResult, "Invalid node index");
2407         }
2408         auto joint = loadResult.data->nodes[joints[i]].get();
2409         joint->isJoint = true;
2410         skin->joints[i] = joint;
2411     }
2412 
2413     loadResult.data->skins.push_back(move(skin));
2414 
2415     return true;
2416 }
2417 
2418 void FinalizeGltfContent(LoadResult& loadResult)
2419 {
2420     using ImageContainer = vector<unique_ptr<Image>>;
2421     using TextureContainer = vector<unique_ptr<Texture>>;
2422 
2423     // See if there are duplicate images with the same uri.
2424     for (size_t imageIndex = 0; imageIndex < loadResult.data->images.size(); ++imageIndex) {
2425         if (loadResult.data->images[imageIndex]->uri.empty()) {
2426             continue;
2427         }
2428 
2429         bool hasDuplicate = false;
2430         for (size_t lookupImageIndex = imageIndex + 1; lookupImageIndex < loadResult.data->images.size();) {
2431             // Two images are the same?
2432             if (loadResult.data->images[imageIndex]->uri == loadResult.data->images[lookupImageIndex]->uri) {
2433                 hasDuplicate = true;
2434 
2435                 // Fix all textures to reference the first image.
2436                 for (TextureContainer::iterator textureIt = loadResult.data->textures.begin();
2437                      textureIt != loadResult.data->textures.end(); ++textureIt) {
2438                     if ((*textureIt)->image == loadResult.data->images[lookupImageIndex].get()) {
2439                         (*textureIt)->image = loadResult.data->images[imageIndex].get();
2440                     }
2441                 }
2442 
2443                 // Two images are the same and the other one can be removed.
2444                 const auto indexOffset = static_cast<typename ImageContainer::difference_type>(lookupImageIndex);
2445                 loadResult.data->images.erase(loadResult.data->images.begin() + indexOffset);
2446             } else {
2447                 ++lookupImageIndex;
2448             }
2449         }
2450 
2451         if (hasDuplicate) {
2452             CORE_LOG_D("Optimizing out duplicate image from glTF: %s/images/%zu",
2453                 loadResult.data->defaultResources.c_str(), imageIndex);
2454         }
2455     }
2456 }
2457 
2458 bool AnimationSamplers(LoadResult& loadResult, const json::value& jsonData, Animation& animation)
2459 {
2460     const auto parseSamplers = [&animation](LoadResult& loadResult, const json::value& samplerJson) -> bool {
2461         auto sampler = make_unique<AnimationSampler>();
2462 
2463         // parse sampler
2464         size_t accessor;
2465         if (!ParseOptionalNumber<size_t>(loadResult, accessor, samplerJson, "input", GLTF_INVALID_INDEX)) {
2466             return false;
2467         }
2468 
2469         if (accessor != GLTF_INVALID_INDEX && accessor < loadResult.data->accessors.size()) {
2470             sampler->input = loadResult.data->accessors[accessor].get();
2471         }
2472 
2473         if (!ParseOptionalNumber<size_t>(loadResult, accessor, samplerJson, "output", GLTF_INVALID_INDEX)) {
2474             return false;
2475         }
2476 
2477         if (accessor != GLTF_INVALID_INDEX && accessor < loadResult.data->accessors.size()) {
2478             sampler->output = loadResult.data->accessors[accessor].get();
2479         }
2480 
2481         string interpolation;
2482         if (!ParseOptionalString(loadResult, interpolation, samplerJson, "interpolation", string())) {
2483             return false;
2484         }
2485 
2486         // This attribute is not required, defaults to linear.
2487         GetAnimationInterpolation(interpolation, sampler->interpolation);
2488 
2489         animation.samplers.push_back(move(sampler));
2490 
2491         return true;
2492     };
2493 
2494     if (!ForEachInArray(loadResult, jsonData, "samplers", parseSamplers)) {
2495         return false;
2496     }
2497     return true;
2498 }
2499 
2500 bool AnimationChannels(LoadResult& loadResult, const json::value& jsonData, Animation& animation)
2501 {
2502     const auto channelsParser = [&animation](LoadResult& loadResult, const json::value& channelJson) -> bool {
2503         AnimationTrack animationTrack;
2504 
2505         // parse sampler
2506         size_t sampler;
2507         if (!ParseOptionalNumber<size_t>(loadResult, sampler, channelJson, "sampler", GLTF_INVALID_INDEX)) {
2508             return false;
2509         }
2510 
2511         if (sampler != GLTF_INVALID_INDEX && sampler < animation.samplers.size()) {
2512             animationTrack.sampler = animation.samplers[sampler].get();
2513         }
2514 
2515         const auto targetParser = [&animationTrack](LoadResult& loadResult, const json::value& targetJson) -> bool {
2516             {
2517                 string path;
2518                 if (!ParseOptionalString(loadResult, path, targetJson, "path", string())) {
2519                     return false;
2520                 }
2521 
2522                 if (path.empty()) {
2523                     RETURN_WITH_ERROR(loadResult, "Path is required");
2524                 }
2525 
2526                 if (!GetAnimationPath(path, animationTrack.channel.path)) {
2527                     CORE_LOG_W("Skipping unsupported animation path: %s", path.c_str());
2528                     return false;
2529                 }
2530             }
2531 
2532             size_t node;
2533             if (!ParseOptionalNumber<size_t>(loadResult, node, targetJson, "node", GLTF_INVALID_INDEX)) {
2534                 return false;
2535             }
2536 
2537             if (node != GLTF_INVALID_INDEX && node < loadResult.data->nodes.size()) {
2538                 animationTrack.channel.node = loadResult.data->nodes[node].get();
2539             } else {
2540                 // this channel will be ignored
2541             }
2542 
2543             return true;
2544         };
2545 
2546         if (!ParseObject(loadResult, channelJson, "target", targetParser)) {
2547             return false;
2548         }
2549 
2550         animation.tracks.push_back(move(animationTrack));
2551         return true;
2552     };
2553 
2554     if (!ForEachInArray(loadResult, jsonData, "channels", channelsParser)) {
2555         return false;
2556     }
2557     return true;
2558 }
2559 
2560 bool ParseAnimation(LoadResult& loadResult, const json::value& jsonData)
2561 {
2562     auto animation = make_unique<Animation>();
2563     if (!ParseOptionalString(loadResult, animation->name, jsonData, "name",
2564             "animation_" + to_string(loadResult.data->animations.size()))) {
2565         return false;
2566     }
2567 
2568     if (!AnimationSamplers(loadResult, jsonData, *animation)) {
2569         return false;
2570     }
2571 
2572     if (!AnimationChannels(loadResult, jsonData, *animation)) {
2573         return false;
2574     }
2575 
2576     if (!animation->tracks.empty() && !animation->samplers.empty()) {
2577         loadResult.data->animations.push_back(move(animation));
2578     } else {
2579         // RsdzExporter produces empty animations so just adding an error message.
2580         loadResult.error += "Skipped empty animation. Animation should have at least one channel and sampler.\n";
2581     }
2582     return true;
2583 }
2584 
2585 bool GltfAsset(LoadResult& loadResult, const json::value& jsonData)
2586 {
2587     if (auto const& assetJson = jsonData.find("asset"); assetJson) {
2588         // Client implementations should first check whether a minVersion property is specified and ensure both
2589         // major and minor versions can be supported.
2590         string version;
2591         ParseOptionalString(loadResult, version, *assetJson, "minVersion", "");
2592         if (!version.empty()) {
2593             if (const auto minVersion = ParseVersion(version); minVersion) {
2594                 if ((minVersion->first > 2u) || (minVersion->second > 0u)) {
2595                     RETURN_WITH_ERROR(loadResult, "Required glTF minVersion not supported");
2596                 }
2597             } else {
2598                 RETURN_WITH_ERROR(loadResult, "Invalid minVersion");
2599             }
2600         } else {
2601             // If no minVersion is specified, then clients should check the version property and ensure the major
2602             // version is supported.
2603             ParseOptionalString(loadResult, version, *assetJson, "version", "");
2604             if (const auto minVersion = ParseVersion(version); minVersion) {
2605                 if ((minVersion->first > 2u)) {
2606                     RETURN_WITH_ERROR(loadResult, "Required glTF version not supported");
2607                 }
2608             } else {
2609                 RETURN_WITH_ERROR(loadResult, "Invalid version");
2610             }
2611         }
2612         return true;
2613     } else {
2614         RETURN_WITH_ERROR(loadResult, "Missing asset metadata");
2615     }
2616 }
2617 
2618 bool GltfRequiredExtension(LoadResult& loadResult, const json::value& jsonData)
2619 {
2620     const auto parseRequiredExtensions = [](LoadResult& loadResult, const json::value& extension) {
2621         if (extension.is_string()) {
2622             const auto& val = extension.string_;
2623             if (std::find(std::begin(SUPPORTED_EXTENSIONS), std::end(SUPPORTED_EXTENSIONS), val) ==
2624                 std::end(SUPPORTED_EXTENSIONS)) {
2625                 SetError(loadResult, "glTF requires unsupported extension: " + val);
2626                 return false;
2627             }
2628 #if defined(GLTF2_EXTENSION_KHR_MESH_QUANTIZATION)
2629             if (val == "KHR_mesh_quantization") {
2630                 loadResult.data->quantization = true;
2631             }
2632 #endif
2633         }
2634 
2635         return true;
2636     };
2637     return ForEachInArray(loadResult, jsonData, "extensionsRequired", parseRequiredExtensions);
2638 }
2639 
2640 bool GltfUsedExtension(LoadResult& loadResult, const json::value& jsonData)
2641 {
2642     const auto parseUsedExtensions = [](LoadResult& loadResult, const json::value& extension) {
2643         if (extension.is_string()) {
2644             const auto& val = extension.string_;
2645             if (std::find(std::begin(SUPPORTED_EXTENSIONS), std::end(SUPPORTED_EXTENSIONS), val) ==
2646                 std::end(SUPPORTED_EXTENSIONS)) {
2647                 CORE_LOG_W("glTF uses unsupported extension: %s", string(val).c_str());
2648             }
2649         }
2650 
2651         return true;
2652     };
2653     return ForEachInArray(loadResult, jsonData, "extensionsUsed", parseUsedExtensions);
2654 }
2655 
2656 bool GltfExtension(LoadResult& loadResult, const json::value& jsonData)
2657 {
2658     return ParseObject(loadResult, jsonData, "extensions", [](LoadResult& loadResult, const json::value& extensions) {
2659         bool result = true;
2660 
2661 #if defined(GLTF2_EXTENSION_KHR_LIGHTS)
2662         if (!ParseObject(loadResult, extensions, "KHR_lights_punctual",
2663                 [](LoadResult& loadResult, const json::value& khrLights) {
2664                     return ForEachObjectInArray(loadResult, khrLights, "lights", ParseKHRLight);
2665                 })) {
2666             result = false;
2667         }
2668 #endif
2669 #if defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2670         loadResult.data->pbrLightOffset = static_cast<uint32_t>(loadResult.data->lights.size());
2671 
2672         if (!ParseObject(loadResult, extensions, "KHR_lights_pbr",
2673                 [](LoadResult& loadResult, const json::value& khrLights) -> bool {
2674                     return ForEachObjectInArray(loadResult, khrLights, "lights", ParseKHRLight);
2675                 })) {
2676             result = false;
2677         }
2678 #endif
2679 
2680 #if defined(GLTF2_EXTENSION_HW_XR_EXT)
2681         if (!ParseObject(
2682                 loadResult, extensions, "HW_XR_EXT", [](LoadResult& loadResult, const json::value& jsonData) -> bool {
2683                     string thumbnailUri;
2684                     if (ParseOptionalString(loadResult, thumbnailUri, jsonData, "sceneThumbnail", "")) {
2685                         DecodeUri(thumbnailUri);
2686                         loadResult.data->thumbnails.push_back(Assets::Thumbnail { move(thumbnailUri), {}, {} });
2687                     } else {
2688                         return false;
2689                     }
2690                     return true;
2691                 })) {
2692             result = false;
2693         }
2694 #endif
2695 
2696 #if defined(GLTF2_EXTENSION_EXT_LIGHTS_IMAGE_BASED)
2697         if (!ParseObject(loadResult, extensions, "EXT_lights_image_based",
2698                 [](LoadResult& loadResult, const json::value& imageBasedLights) -> bool {
2699                     return ForEachObjectInArray(loadResult, imageBasedLights, "lights", ParseImageBasedLight);
2700                 })) {
2701             result = false;
2702         }
2703 #endif
2704         return result;
2705     });
2706 }
2707 
2708 bool GltfExtras(LoadResult& loadResult, const json::value& jsonData)
2709 {
2710 #if defined(GLTF2_EXTRAS_RSDZ)
2711     const auto parseExtras = [](LoadResult& loadResult, const json::value& extras) -> bool {
2712         return ForEachObjectInArray(loadResult, extras, "rsdzAnimations", ParseAnimation);
2713     };
2714     return ParseObject(loadResult, jsonData, "extras", parseExtras);
2715 #else
2716     return true;
2717 #endif
2718 }
2719 
2720 bool ParseGLTF(LoadResult& loadResult, const json::value& jsonData)
2721 {
2722     if (!GltfAsset(loadResult, jsonData) || !GltfRequiredExtension(loadResult, jsonData)) {
2723         return false;
2724     }
2725     // parse through all types of objects regardles of errors. result will be false is any of the steps failed.
2726     bool result = GltfUsedExtension(loadResult, jsonData);
2727     result = ForEachObjectInArray(loadResult, jsonData, "buffers", ParseBuffer) && result;
2728     result = ForEachObjectInArray(loadResult, jsonData, "bufferViews", ParseBufferView) && result;
2729     result = ForEachObjectInArray(loadResult, jsonData, "accessors", ParseAccessor) && result;
2730     result = ForEachObjectInArray(loadResult, jsonData, "images", ParseImage) && result;
2731     result = ForEachObjectInArray(loadResult, jsonData, "samplers", ParseSampler) && result;
2732     result = ForEachObjectInArray(loadResult, jsonData, "textures", ParseTexture) && result;
2733     result = ForEachObjectInArray(loadResult, jsonData, "materials", ParseMaterial) && result;
2734     result = ForEachObjectInArray(loadResult, jsonData, "meshes", ParseMesh) && result;
2735     result = ForEachObjectInArray(loadResult, jsonData, "cameras", ParseCamera) && result;
2736     result = GltfExtension(loadResult, jsonData) && result;
2737     result = ForEachObjectInArray(loadResult, jsonData, "nodes", ParseNode) && result;
2738     result = ForEachObjectInArray(loadResult, jsonData, "skins", ParseSkin) && result;
2739     result = ForEachObjectInArray(loadResult, jsonData, "animations", ParseAnimation) && result;
2740     result = GltfExtras(loadResult, jsonData) && result;
2741     result = FinalizeNodes(loadResult) && result;
2742     result = ForEachObjectInArray(loadResult, jsonData, "scenes", ParseScene) && result;
2743 
2744     if (!std::all_of(loadResult.data->skins.begin(), loadResult.data->skins.end(),
2745             [&loadResult](auto const& skin) { return JointsInSameScene(*skin, loadResult); })) {
2746         return false;
2747     }
2748 
2749     int defaultSceneIndex;
2750     if (!ParseOptionalNumber<int>(loadResult, defaultSceneIndex, jsonData, "scene", -1)) {
2751         result = false;
2752     } else if (defaultSceneIndex != -1) {
2753         if (defaultSceneIndex < 0 || size_t(defaultSceneIndex) >= loadResult.data->scenes.size()) {
2754             loadResult.error += "Invalid default scene index\n";
2755             loadResult.success = false;
2756         } else {
2757             loadResult.data->defaultScene = loadResult.data->scenes[static_cast<size_t>(defaultSceneIndex)].get();
2758         }
2759     }
2760 
2761     FinalizeGltfContent(loadResult);
2762 
2763     return result;
2764 }
2765 
2766 void LoadGLTF(LoadResult& loadResult, IFile& file)
2767 {
2768     const uint64_t byteLength = file.GetLength();
2769 
2770     string raw;
2771     raw.resize(static_cast<size_t>(byteLength));
2772 
2773     if (file.Read(raw.data(), byteLength) != byteLength) {
2774         return;
2775     }
2776     CORE_CPU_PERF_BEGIN(jkson, "CORE3D", "LoadGLTF", "json::parse", CORE3D_PROFILER_DEFAULT_COLOR);
2777     json::value jsonObject = json::parse(raw.data());
2778     CORE_CPU_PERF_END(jkson);
2779     if (!jsonObject) {
2780         SetError(loadResult, "Parsing GLTF failed: invalid JSON");
2781         return;
2782     }
2783 
2784     ParseGLTF(loadResult, jsonObject);
2785 }
2786 
2787 bool LoadGLB(LoadResult& loadResult, IFile& file)
2788 {
2789     GLBHeader header;
2790     uint64_t bytes = file.Read(&header, sizeof(GLBHeader));
2791 
2792     if (bytes < sizeof(GLBHeader)) {
2793         // cannot read header
2794         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected GLB object");
2795     }
2796 
2797     if (header.magic != GLTF_MAGIC) {
2798         // 0x46546C67 >> "glTF"
2799         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected GLB header");
2800     }
2801 
2802     if (header.length > loadResult.data->size) {
2803         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: GLB header definition for size is larger than file size");
2804     } else {
2805         loadResult.data->size = header.length;
2806     }
2807 
2808     if (header.version != 2) {
2809         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected GLB version 2");
2810     }
2811 
2812     GLBChunk chunkJson;
2813     bytes = file.Read(&chunkJson, sizeof(GLBChunk));
2814 
2815     if (bytes < sizeof(GLBChunk)) {
2816         // cannot read chunk data
2817         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected GLB chunk");
2818     }
2819 
2820     if (chunkJson.chunkType != static_cast<uint32_t>(ChunkType::JSON) || chunkJson.chunkLength == 0 ||
2821         (chunkJson.chunkLength % 4) || chunkJson.chunkLength > (header.length - sizeof(header) - sizeof(chunkJson))) {
2822         // first chunk have to be JSON
2823         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: expected JSON chunk");
2824     }
2825 
2826     const size_t dataOffset = chunkJson.chunkLength + sizeof(GLBHeader) + 2 * sizeof(GLBChunk);
2827 
2828     if (dataOffset > loadResult.data->size) {
2829         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: data part offset is out of file");
2830     }
2831 
2832     loadResult.data->defaultResourcesOffset = static_cast<int32_t>(dataOffset);
2833 
2834     string jsonString;
2835     jsonString.resize(chunkJson.chunkLength);
2836 
2837     if (jsonString.size() != chunkJson.chunkLength) {
2838         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: allocation for JSON data failed");
2839     }
2840 
2841     bytes = file.Read(reinterpret_cast<void*>(jsonString.data()), chunkJson.chunkLength);
2842 
2843     if (bytes < chunkJson.chunkLength) {
2844         // cannot read chunk data
2845         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: JSON chunk size not match");
2846     }
2847 
2848     json::value o = json::parse(jsonString.data());
2849     if (!o) {
2850         RETURN_WITH_ERROR(loadResult, "Parsing GLTF failed: invalid JSON");
2851     }
2852 
2853     return ParseGLTF(loadResult, o);
2854 }
2855 } // namespace
2856 
2857 // Internal loading function.
2858 LoadResult LoadGLTF(IFileManager& fileManager, const string_view uri)
2859 {
2860     LoadResult result;
2861 
2862     CORE_CPU_PERF_SCOPE("CORE3D", "LoadGLTF()", uri, CORE3D_PROFILER_DEFAULT_COLOR);
2863 
2864     IFile::Ptr file = fileManager.OpenFile(uri);
2865     if (!file) {
2866         CORE_LOG_D("Error loading '%s'", string(uri).data());
2867         return LoadResult("Failed to open file.");
2868     }
2869 
2870     const uint64_t fileLength = file->GetLength();
2871     if (fileLength > SIZE_MAX) {
2872         CORE_LOG_D("Error loading '%s'", string(uri).data());
2873         return LoadResult("Failed to open file, file size larger than SIZE_MAX");
2874     }
2875 
2876     string_view baseName;
2877     string_view path;
2878     SplitFilename(uri, baseName, path);
2879 
2880     result.data = make_unique<Data>(fileManager);
2881     result.data->filepath = path;
2882     result.data->defaultResources = baseName;
2883     result.data->size = static_cast<size_t>(fileLength);
2884 
2885     string_view baseNameNoExt;
2886     string_view extensionView;
2887     SplitBaseFilename(baseName, baseNameNoExt, extensionView);
2888 
2889     string extension(extensionView.size(), '\0');
2890     std::transform(extensionView.begin(), extensionView.end(), extension.begin(),
2891         [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
2892 
2893     if (extension == "gltf" || extension == "glt") {
2894         LoadGLTF(result, *file);
2895     } else if (extension == "glb") {
2896         LoadGLB(result, *file);
2897     } else {
2898         LoadGLB(result, *file);
2899     }
2900     return result;
2901 }
2902 
2903 LoadResult LoadGLTF(IFileManager& fileManager, array_view<uint8_t const> data)
2904 {
2905     LoadResult result;
2906 
2907     // if the buffer starts with a GLB header assume GLB, otherwise glTF with embedded data.
2908     char const* ext = ".gltf";
2909     if (data.size() >= (sizeof(GLBHeader) + sizeof(GLBChunk))) {
2910         GLBHeader const& header = *reinterpret_cast<GLBHeader const*>(data.data());
2911         if (header.magic == GLTF_MAGIC) {
2912             ext = ".glb";
2913         }
2914     }
2915 
2916     // wrap the buffer in a temporary file
2917     auto const tmpFileName = "memory://" + to_string((uintptr_t)data.data()) + ext;
2918     {
2919         auto tmpFile = fileManager.CreateFile(tmpFileName);
2920         if (!tmpFile) {
2921             result.success = false;
2922             return result;
2923         }
2924         // NOTE: not ideal as this actually copies the data
2925         // alternative would be to cast to MemoryFile and give the array_view to the file
2926         tmpFile->Write(data.data(), data.size());
2927 
2928         result = GLTF2::LoadGLTF(fileManager, tmpFileName);
2929         if (result.success) {
2930             // File is stored here so it can be deleted from the file manager. loadBuffers is the only thing at
2931             // the moment which will need the file after parsing and that will check whether to use the
2932             // mMemoryFile or the URI related to the buffer.
2933             (*result.data).memoryFile_ = move(tmpFile);
2934         }
2935     }
2936     fileManager.DeleteFile(tmpFileName);
2937     return result;
2938 }
2939 } // namespace GLTF2
2940 CORE3D_END_NAMESPACE()
2941