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