cgv
Loading...
Searching...
No Matches
gltf.h
1// ------------------------------------------------------------
2// Copyright(c) 2019 Jesse Yurkovich
3// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
4// See the LICENSE file in the repo root for full license information.
5// ------------------------------------------------------------
6#pragma once
7
8#include <array>
9#include <cstring>
10#include <fstream>
11#include <istream>
12#include <ostream>
13#include <stdexcept>
14#include <string>
15#include <system_error>
16#include <unordered_map>
17#include <vector>
18
19#include <nlohmann/json.hpp>
20
21#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L) && (_MSC_VER >= 1911))
22#define FX_GLTF_HAS_CPP_17
23#include <string_view>
24#endif
25
26namespace fx
27{
28namespace base64
29{
30 namespace detail
31 {
32 // clang-format off
33 constexpr std::array<char, 64> EncodeMap =
34 {
35 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
36 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
37 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
38 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
39 };
40
41 constexpr std::array<char, 256> DecodeMap =
42 {
43 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
44 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
45 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
46 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
47 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
48 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
49 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
50 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
51 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
52 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
53 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
54 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
55 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
56 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
57 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
58 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
59 };
60 // clang-format on
61 } // namespace detail
62
63 inline std::string Encode(std::vector<uint8_t> const & bytes)
64 {
65 const std::size_t length = bytes.size();
66 if (length == 0)
67 {
68 return {};
69 }
70
71 std::string out{};
72 out.reserve(((length * 4 / 3) + 3) & (~3u)); // round up to nearest 4
73
74 uint32_t value = 0;
75 int32_t bitCount = -6;
76 for (const uint8_t c : bytes)
77 {
78 value = (value << 8u) + c;
79 bitCount += 8;
80 while (bitCount >= 0)
81 {
82 const uint32_t shiftOperand = bitCount;
83 out.push_back(detail::EncodeMap.at((value >> shiftOperand) & 0x3fu));
84 bitCount -= 6;
85 }
86 }
87
88 if (bitCount > -6)
89 {
90 const uint32_t shiftOperand = bitCount + 8;
91 out.push_back(detail::EncodeMap.at(((value << 8u) >> shiftOperand) & 0x3fu));
92 }
93
94 while (out.size() % 4 != 0)
95 {
96 out.push_back('=');
97 }
98
99 return out;
100 }
101
102#if defined(FX_GLTF_HAS_CPP_17)
103 inline bool TryDecode(std::string_view in, std::vector<uint8_t> & out)
104#else
105 inline bool TryDecode(std::string const & in, std::vector<uint8_t> & out)
106#endif
107 {
108 out.clear();
109
110 const std::size_t length = in.length();
111 if (length == 0)
112 {
113 return true;
114 }
115
116 if (length % 4 != 0)
117 {
118 return false;
119 }
120
121 out.reserve((length / 4) * 3);
122
123 bool invalid = false;
124 uint32_t value = 0;
125 int32_t bitCount = -8;
126 for (std::size_t i = 0; i < length; i++)
127 {
128 const uint8_t c = static_cast<uint8_t>(in[i]);
129 const char map = detail::DecodeMap.at(c);
130 if (map == -1)
131 {
132 if (c != '=') // Non base64 character
133 {
134 invalid = true;
135 }
136 else
137 {
138 // Padding characters not where they should be
139 const std::size_t remaining = length - i - 1;
140 if (remaining > 1 || (remaining == 1 ? in[i + 1] != '=' : false))
141 {
142 invalid = true;
143 }
144 }
145
146 break;
147 }
148
149 value = (value << 6u) + map;
150 bitCount += 6;
151 if (bitCount >= 0)
152 {
153 const uint32_t shiftOperand = bitCount;
154 out.push_back(static_cast<uint8_t>(value >> shiftOperand));
155 bitCount -= 8;
156 }
157 }
158
159 if (invalid)
160 {
161 out.clear();
162 }
163
164 return !invalid;
165 }
166} // namespace base64
167
168namespace gltf
169{
170 class invalid_gltf_document : public std::runtime_error
171 {
172 public:
173 explicit invalid_gltf_document(char const * message)
174 : std::runtime_error(message)
175 {
176 }
177
178 invalid_gltf_document(char const * message, std::string const & extra)
179 : std::runtime_error(CreateMessage(message, extra).c_str())
180 {
181 }
182
183 private:
184 std::string CreateMessage(char const * message, std::string const & extra)
185 {
186 return std::string(message).append(" : ").append(extra);
187 }
188 };
189
190 namespace detail
191 {
192#if defined(FX_GLTF_HAS_CPP_17)
193 template <typename TTarget>
194 inline void ReadRequiredField(std::string_view key, nlohmann::json const & json, TTarget & target)
195#else
196 template <typename TKey, typename TTarget>
197 inline void ReadRequiredField(TKey && key, nlohmann::json const & json, TTarget & target)
198#endif
199 {
200 const nlohmann::json::const_iterator iter = json.find(key);
201 if (iter == json.end())
202 {
203 throw invalid_gltf_document("Required field not found", std::string(key));
204 }
205
206 target = iter->get<TTarget>();
207 }
208
209#if defined(FX_GLTF_HAS_CPP_17)
210 template <typename TTarget>
211 inline void ReadOptionalField(std::string_view key, nlohmann::json const & json, TTarget & target)
212#else
213 template <typename TKey, typename TTarget>
214 inline void ReadOptionalField(TKey && key, nlohmann::json const & json, TTarget & target)
215#endif
216 {
217 const nlohmann::json::const_iterator iter = json.find(key);
218 if (iter != json.end())
219 {
220 target = iter->get<TTarget>();
221 }
222 }
223
224 inline void ReadExtensionsAndExtras(nlohmann::json const & json, nlohmann::json & extensionsAndExtras)
225 {
226 const nlohmann::json::const_iterator iterExtensions = json.find("extensions");
227 const nlohmann::json::const_iterator iterExtras = json.find("extras");
228 if (iterExtensions != json.end())
229 {
230 extensionsAndExtras["extensions"] = *iterExtensions;
231 }
232
233 if (iterExtras != json.end())
234 {
235 extensionsAndExtras["extras"] = *iterExtras;
236 }
237 }
238
239 template <typename TValue>
240 inline void WriteField(std::string const & key, nlohmann::json & json, TValue const & value)
241 {
242 if (!value.empty())
243 {
244 json[key] = value;
245 }
246 }
247
248 template <typename TValue>
249 inline void WriteField(std::string const & key, nlohmann::json & json, TValue const & value, TValue const & defaultValue)
250 {
251 if (value != defaultValue)
252 {
253 json[key] = value;
254 }
255 }
256
257 inline void WriteExtensions(nlohmann::json & json, nlohmann::json const & extensionsAndExtras)
258 {
259 if (!extensionsAndExtras.empty())
260 {
261 for (nlohmann::json::const_iterator it = extensionsAndExtras.begin(); it != extensionsAndExtras.end(); ++it)
262 {
263 json[it.key()] = it.value();
264 }
265 }
266 }
267
268 inline std::string GetDocumentRootPath(std::string const & documentFilePath)
269 {
270 const std::size_t pos = documentFilePath.find_last_of("/\\");
271 if (pos != std::string::npos)
272 {
273 return documentFilePath.substr(0, pos);
274 }
275
276 return {};
277 }
278
279 inline std::string CreateBufferUriPath(std::string const & documentRootPath, std::string const & bufferUri)
280 {
281 // Prevent simple forms of path traversal from malicious uri references...
282 if (bufferUri.empty() || bufferUri.find("..") != std::string::npos || bufferUri.front() == '/' || bufferUri.front() == '\\')
283 {
284 throw invalid_gltf_document("Invalid buffer.uri value", bufferUri);
285 }
286
287 std::string documentRoot = documentRootPath;
288 if (documentRoot.length() > 0)
289 {
290 if (documentRoot.back() != '/')
291 {
292 documentRoot.push_back('/');
293 }
294 }
295
296 return documentRoot + bufferUri;
297 }
298
299 struct ChunkHeader
300 {
301 uint32_t chunkLength{};
302 uint32_t chunkType{};
303 };
304
305 struct GLBHeader
306 {
307 uint32_t magic{};
308 uint32_t version{};
309 uint32_t length{};
310
311 ChunkHeader jsonHeader{};
312 };
313
314 constexpr uint32_t DefaultMaxBufferCount = 8;
315 constexpr uint32_t DefaultMaxMemoryAllocation = 32 * 1024 * 1024;
316 constexpr std::size_t HeaderSize{ sizeof(GLBHeader) };
317 constexpr std::size_t ChunkHeaderSize{ sizeof(ChunkHeader) };
318 constexpr uint32_t GLBHeaderMagic = 0x46546c67u;
319 constexpr uint32_t GLBChunkJSON = 0x4e4f534au;
320 constexpr uint32_t GLBChunkBIN = 0x004e4942u;
321
322 constexpr char const * const MimetypeApplicationOctet = "data:application/octet-stream;base64";
323 constexpr char const * const MimetypeImagePNG = "data:image/png;base64";
324 constexpr char const * const MimetypeImageJPG = "data:image/jpeg;base64";
325 } // namespace detail
326
327 namespace defaults
328 {
329 constexpr std::array<float, 16> IdentityMatrix{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
330 constexpr std::array<float, 4> IdentityRotation{ 0, 0, 0, 1 };
331 constexpr std::array<float, 4> IdentityVec4{ 1, 1, 1, 1 };
332 constexpr std::array<float, 3> IdentityVec3{ 1, 1, 1 };
333 constexpr std::array<float, 3> NullVec3{ 0, 0, 0 };
334 constexpr float IdentityScalar = 1;
335 constexpr float FloatSentinel = 10000;
336
337 constexpr bool AccessorNormalized = false;
338
339 constexpr float MaterialAlphaCutoff = 0.5f;
340 constexpr bool MaterialDoubleSided = false;
341 } // namespace defaults
342
343 using Attributes = std::unordered_map<std::string, uint32_t>;
344
346 {
347 bool empty() const noexcept
348 {
349 return false;
350 }
351 };
352
353 struct Accessor
354 {
355 enum class ComponentType : uint16_t
356 {
357 None = 0,
358 Byte = 5120,
359 UnsignedByte = 5121,
360 Short = 5122,
361 UnsignedShort = 5123,
362 UnsignedInt = 5125,
363 Float = 5126
364 };
365
366 enum class Type : uint8_t
367 {
368 None,
369 Scalar,
370 Vec2,
371 Vec3,
372 Vec4,
373 Mat2,
374 Mat3,
375 Mat4
376 };
377
378 struct Sparse
379 {
381 {
382 uint32_t bufferView{};
383 uint32_t byteOffset{};
384 ComponentType componentType{ ComponentType::None };
385
386 nlohmann::json extensionsAndExtras{};
387 };
388
390 {
391 uint32_t bufferView{};
392 uint32_t byteOffset{};
393
394 nlohmann::json extensionsAndExtras{};
395 };
396
397 int32_t count{};
398 Indices indices{};
399 Values values{};
400
401 nlohmann::json extensionsAndExtras{};
402
403 bool empty() const noexcept
404 {
405 return count == 0;
406 }
407 };
408
409 int32_t bufferView{ -1 };
410 uint32_t byteOffset{};
411 uint32_t count{};
412 bool normalized{ defaults::AccessorNormalized };
413
414 ComponentType componentType{ ComponentType::None };
415 Type type{ Type::None };
416 Sparse sparse{};
417
418 std::string name;
419 std::vector<float> max{};
420 std::vector<float> min{};
421
422 nlohmann::json extensionsAndExtras{};
423 };
424
426 {
427 struct Channel
428 {
430 {
431 int32_t node{ -1 };
432 std::string path{};
433
434 nlohmann::json extensionsAndExtras{};
435 };
436
437 int32_t sampler{ -1 };
438 Target target{};
439
440 nlohmann::json extensionsAndExtras{};
441 };
442
443 struct Sampler
444 {
445 enum class Type
446 {
447 Linear,
448 Step,
449 CubicSpline
450 };
451
452 int32_t input{ -1 };
453 int32_t output{ -1 };
454
455 Type interpolation{ Sampler::Type::Linear };
456
457 nlohmann::json extensionsAndExtras{};
458 };
459
460 std::string name{};
461 std::vector<Channel> channels{};
462 std::vector<Sampler> samplers{};
463
464 nlohmann::json extensionsAndExtras{};
465 };
466
468 {
469 std::string copyright{};
470 std::string generator{};
471 std::string minVersion{};
472 std::string version{ "2.0" };
473
474 nlohmann::json extensionsAndExtras{};
475 };
476
477 struct Buffer
478 {
479 uint32_t byteLength{};
480
481 std::string name;
482 std::string uri;
483
484 nlohmann::json extensionsAndExtras{};
485
486 std::vector<uint8_t> data{};
487
488 bool IsEmbeddedResource() const noexcept
489 {
490 return uri.find(detail::MimetypeApplicationOctet) == 0;
491 }
492
493 void SetEmbeddedResource()
494 {
495 uri = std::string(detail::MimetypeApplicationOctet).append(",").append(base64::Encode(data));
496 }
497 };
498
500 {
501 enum class TargetType : uint16_t
502 {
503 None = 0,
504 ArrayBuffer = 34962,
505 ElementArrayBuffer = 34963
506 };
507
508 std::string name;
509
510 int32_t buffer{ -1 };
511 uint32_t byteOffset{};
512 uint32_t byteLength{};
513 uint32_t byteStride{};
514
515 TargetType target{ TargetType::None };
516
517 nlohmann::json extensionsAndExtras{};
518 };
519
520 struct Camera
521 {
522 enum class Type
523 {
524 None,
527 };
528
530 {
531 float xmag{ defaults::FloatSentinel };
532 float ymag{ defaults::FloatSentinel };
533 float zfar{ -defaults::FloatSentinel };
534 float znear{ -defaults::FloatSentinel };
535
536 nlohmann::json extensionsAndExtras{};
537 };
538
540 {
541 float aspectRatio{};
542 float yfov{};
543 float zfar{};
544 float znear{};
545
546 nlohmann::json extensionsAndExtras{};
547 };
548
549 std::string name{};
550 Type type{ Type::None };
551
552 Orthographic orthographic;
553 Perspective perspective;
554
555 nlohmann::json extensionsAndExtras{};
556 };
557
558 struct Image
559 {
560 int32_t bufferView{};
561
562 std::string name;
563 std::string uri;
564 std::string mimeType;
565
566 nlohmann::json extensionsAndExtras{};
567
568 bool IsEmbeddedResource() const noexcept
569 {
570 return uri.find(detail::MimetypeImagePNG) == 0 || uri.find(detail::MimetypeImageJPG) == 0;
571 }
572
573 void MaterializeData(std::vector<uint8_t> & data) const
574 {
575 char const * const mimetype = uri.find(detail::MimetypeImagePNG) == 0 ? detail::MimetypeImagePNG : detail::MimetypeImageJPG;
576 const std::size_t startPos = std::char_traits<char>::length(mimetype) + 1;
577
578#if defined(FX_GLTF_HAS_CPP_17)
579 const std::size_t base64Length = uri.length() - startPos;
580 const bool success = base64::TryDecode({ &uri[startPos], base64Length }, data);
581#else
582 const bool success = base64::TryDecode(uri.substr(startPos), data);
583#endif
584 if (!success)
585 {
586 throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
587 }
588 }
589 };
590
591 struct Material
592 {
593 enum class AlphaMode : uint8_t
594 {
595 Opaque,
596 Mask,
597 Blend
598 };
599
600 struct Texture
601 {
602 int32_t index{ -1 };
603 int32_t texCoord{};
604
605 nlohmann::json extensionsAndExtras{};
606
607 bool empty() const noexcept
608 {
609 return index == -1;
610 }
611 };
612
614 {
615 float scale{ defaults::IdentityScalar };
616 };
617
619 {
620 float strength{ defaults::IdentityScalar };
621 };
622
624 {
625 std::array<float, 4> baseColorFactor = { defaults::IdentityVec4 };
626 Texture baseColorTexture;
627
628 float roughnessFactor{ defaults::IdentityScalar };
629 float metallicFactor{ defaults::IdentityScalar };
630 Texture metallicRoughnessTexture;
631
632 nlohmann::json extensionsAndExtras{};
633
634 bool empty() const
635 {
636 return baseColorTexture.empty() && metallicRoughnessTexture.empty() && metallicFactor == 1.0f && roughnessFactor == 1.0f && baseColorFactor == defaults::IdentityVec4;
637 }
638 };
639
640 float alphaCutoff{ defaults::MaterialAlphaCutoff };
641 AlphaMode alphaMode{ AlphaMode::Opaque };
642
643 bool doubleSided{ defaults::MaterialDoubleSided };
644
645 NormalTexture normalTexture;
646 OcclusionTexture occlusionTexture;
647 PBRMetallicRoughness pbrMetallicRoughness;
648
649 Texture emissiveTexture;
650 std::array<float, 3> emissiveFactor = { defaults::NullVec3 };
651
652 std::string name;
653 nlohmann::json extensionsAndExtras{};
654 };
655
657 {
658 enum class Mode : uint8_t
659 {
660 Points = 0,
661 Lines = 1,
662 LineLoop = 2,
663 LineStrip = 3,
664 Triangles = 4,
665 TriangleStrip = 5,
666 TriangleFan = 6
667 };
668
669 int32_t indices{ -1 };
670 int32_t material{ -1 };
671
672 Mode mode{ Mode::Triangles };
673
674 Attributes attributes{};
675 std::vector<Attributes> targets{};
676
677 nlohmann::json extensionsAndExtras{};
678 };
679
680 struct Mesh
681 {
682 std::string name;
683
684 std::vector<float> weights{};
685 std::vector<Primitive> primitives{};
686
687 nlohmann::json extensionsAndExtras{};
688 };
689
690 struct Node
691 {
692 std::string name;
693
694 int32_t camera{ -1 };
695 int32_t mesh{ -1 };
696 int32_t skin{ -1 };
697
698 std::array<float, 16> matrix{ defaults::IdentityMatrix };
699 std::array<float, 4> rotation{ defaults::IdentityRotation };
700 std::array<float, 3> scale{ defaults::IdentityVec3 };
701 std::array<float, 3> translation{ defaults::NullVec3 };
702
703 std::vector<int32_t> children{};
704 std::vector<float> weights{};
705
706 nlohmann::json extensionsAndExtras{};
707 };
708
709 struct Sampler
710 {
711 enum class MagFilter : uint16_t
712 {
713 None,
714 Nearest = 9728,
715 Linear = 9729
716 };
717
718 enum class MinFilter : uint16_t
719 {
720 None,
721 Nearest = 9728,
722 Linear = 9729,
723 NearestMipMapNearest = 9984,
724 LinearMipMapNearest = 9985,
725 NearestMipMapLinear = 9986,
726 LinearMipMapLinear = 9987
727 };
728
729 enum class WrappingMode : uint16_t
730 {
731 ClampToEdge = 33071,
732 MirroredRepeat = 33648,
733 Repeat = 10497
734 };
735
736 std::string name;
737
738 MagFilter magFilter{ MagFilter::None };
739 MinFilter minFilter{ MinFilter::None };
740
741 WrappingMode wrapS{ WrappingMode::Repeat };
742 WrappingMode wrapT{ WrappingMode::Repeat };
743
744 nlohmann::json extensionsAndExtras{};
745
746 bool empty() const noexcept
747 {
748 return name.empty() && magFilter == MagFilter::None && minFilter == MinFilter::None && wrapS == WrappingMode::Repeat && wrapT == WrappingMode::Repeat && extensionsAndExtras.empty();
749 }
750 };
751
752 struct Scene
753 {
754 std::string name;
755
756 std::vector<uint32_t> nodes{};
757
758 nlohmann::json extensionsAndExtras{};
759 };
760
761 struct Skin
762 {
763 int32_t inverseBindMatrices{ -1 };
764 int32_t skeleton{ -1 };
765
766 std::string name;
767 std::vector<uint32_t> joints{};
768
769 nlohmann::json extensionsAndExtras{};
770 };
771
772 struct Texture
773 {
774 std::string name;
775
776 int32_t sampler{ -1 };
777 int32_t source{ -1 };
778
779 nlohmann::json extensionsAndExtras{};
780 };
781
782 struct Document
783 {
784 Asset asset;
785
786 std::vector<Accessor> accessors{};
787 std::vector<Animation> animations{};
788 std::vector<Buffer> buffers{};
789 std::vector<BufferView> bufferViews{};
790 std::vector<Camera> cameras{};
791 std::vector<Image> images{};
792 std::vector<Material> materials{};
793 std::vector<Mesh> meshes{};
794 std::vector<Node> nodes{};
795 std::vector<Sampler> samplers{};
796 std::vector<Scene> scenes{};
797 std::vector<Skin> skins{};
798 std::vector<Texture> textures{};
799
800 int32_t scene{ -1 };
801 std::vector<std::string> extensionsUsed{};
802 std::vector<std::string> extensionsRequired{};
803
804 nlohmann::json extensionsAndExtras{};
805 };
806
808 {
809 uint32_t MaxBufferCount{ detail::DefaultMaxBufferCount };
810 uint32_t MaxFileSize{ detail::DefaultMaxMemoryAllocation };
811 uint32_t MaxBufferByteLength{ detail::DefaultMaxMemoryAllocation };
812 };
813
814 inline void from_json(nlohmann::json const & json, Accessor::Type & accessorType)
815 {
816 std::string type = json.get<std::string>();
817 if (type == "SCALAR")
818 {
819 accessorType = Accessor::Type::Scalar;
820 }
821 else if (type == "VEC2")
822 {
823 accessorType = Accessor::Type::Vec2;
824 }
825 else if (type == "VEC3")
826 {
827 accessorType = Accessor::Type::Vec3;
828 }
829 else if (type == "VEC4")
830 {
831 accessorType = Accessor::Type::Vec4;
832 }
833 else if (type == "MAT2")
834 {
835 accessorType = Accessor::Type::Mat2;
836 }
837 else if (type == "MAT3")
838 {
839 accessorType = Accessor::Type::Mat3;
840 }
841 else if (type == "MAT4")
842 {
843 accessorType = Accessor::Type::Mat4;
844 }
845 else
846 {
847 throw invalid_gltf_document("Unknown accessor.type value", type);
848 }
849 }
850
851 inline void from_json(nlohmann::json const & json, Accessor::Sparse::Values & values)
852 {
853 detail::ReadRequiredField("bufferView", json, values.bufferView);
854
855 detail::ReadOptionalField("byteOffset", json, values.byteOffset);
856
857 detail::ReadExtensionsAndExtras(json, values.extensionsAndExtras);
858 }
859
860 inline void from_json(nlohmann::json const & json, Accessor::Sparse::Indices & indices)
861 {
862 detail::ReadRequiredField("bufferView", json, indices.bufferView);
863 detail::ReadRequiredField("componentType", json, indices.componentType);
864
865 detail::ReadOptionalField("byteOffset", json, indices.byteOffset);
866
867 detail::ReadExtensionsAndExtras(json, indices.extensionsAndExtras);
868 }
869
870 inline void from_json(nlohmann::json const & json, Accessor::Sparse & sparse)
871 {
872 detail::ReadRequiredField("count", json, sparse.count);
873 detail::ReadRequiredField("indices", json, sparse.indices);
874 detail::ReadRequiredField("values", json, sparse.values);
875
876 detail::ReadExtensionsAndExtras(json, sparse.extensionsAndExtras);
877 }
878
879 inline void from_json(nlohmann::json const & json, Accessor & accessor)
880 {
881 detail::ReadRequiredField("componentType", json, accessor.componentType);
882 detail::ReadRequiredField("count", json, accessor.count);
883 detail::ReadRequiredField("type", json, accessor.type);
884
885 detail::ReadOptionalField("bufferView", json, accessor.bufferView);
886 detail::ReadOptionalField("byteOffset", json, accessor.byteOffset);
887 detail::ReadOptionalField("max", json, accessor.max);
888 detail::ReadOptionalField("min", json, accessor.min);
889 detail::ReadOptionalField("name", json, accessor.name);
890 detail::ReadOptionalField("normalized", json, accessor.normalized);
891 detail::ReadOptionalField("sparse", json, accessor.sparse);
892
893 detail::ReadExtensionsAndExtras(json, accessor.extensionsAndExtras);
894 }
895
896 inline void from_json(nlohmann::json const & json, Animation::Channel::Target & animationChannelTarget)
897 {
898 detail::ReadRequiredField("path", json, animationChannelTarget.path);
899
900 detail::ReadOptionalField("node", json, animationChannelTarget.node);
901
902 detail::ReadExtensionsAndExtras(json, animationChannelTarget.extensionsAndExtras);
903 }
904
905 inline void from_json(nlohmann::json const & json, Animation::Channel & animationChannel)
906 {
907 detail::ReadRequiredField("sampler", json, animationChannel.sampler);
908 detail::ReadRequiredField("target", json, animationChannel.target);
909
910 detail::ReadExtensionsAndExtras(json, animationChannel.extensionsAndExtras);
911 }
912
913 inline void from_json(nlohmann::json const & json, Animation::Sampler::Type & animationSamplerType)
914 {
915 std::string type = json.get<std::string>();
916 if (type == "LINEAR")
917 {
918 animationSamplerType = Animation::Sampler::Type::Linear;
919 }
920 else if (type == "STEP")
921 {
922 animationSamplerType = Animation::Sampler::Type::Step;
923 }
924 else if (type == "CUBICSPLINE")
925 {
926 animationSamplerType = Animation::Sampler::Type::CubicSpline;
927 }
928 else
929 {
930 throw invalid_gltf_document("Unknown animation.sampler.interpolation value", type);
931 }
932 }
933
934 inline void from_json(nlohmann::json const & json, Animation::Sampler & animationSampler)
935 {
936 detail::ReadRequiredField("input", json, animationSampler.input);
937 detail::ReadRequiredField("output", json, animationSampler.output);
938
939 detail::ReadOptionalField("interpolation", json, animationSampler.interpolation);
940
941 detail::ReadExtensionsAndExtras(json, animationSampler.extensionsAndExtras);
942 }
943
944 inline void from_json(nlohmann::json const & json, Animation & animation)
945 {
946 detail::ReadRequiredField("channels", json, animation.channels);
947 detail::ReadRequiredField("samplers", json, animation.samplers);
948
949 detail::ReadOptionalField("name", json, animation.name);
950
951 detail::ReadExtensionsAndExtras(json, animation.extensionsAndExtras);
952 }
953
954 inline void from_json(nlohmann::json const & json, Asset & asset)
955 {
956 detail::ReadRequiredField("version", json, asset.version);
957 detail::ReadOptionalField("copyright", json, asset.copyright);
958 detail::ReadOptionalField("generator", json, asset.generator);
959 detail::ReadOptionalField("minVersion", json, asset.minVersion);
960
961 detail::ReadExtensionsAndExtras(json, asset.extensionsAndExtras);
962 }
963
964 inline void from_json(nlohmann::json const & json, Buffer & buffer)
965 {
966 detail::ReadRequiredField("byteLength", json, buffer.byteLength);
967
968 detail::ReadOptionalField("name", json, buffer.name);
969 detail::ReadOptionalField("uri", json, buffer.uri);
970
971 detail::ReadExtensionsAndExtras(json, buffer.extensionsAndExtras);
972 }
973
974 inline void from_json(nlohmann::json const & json, BufferView & bufferView)
975 {
976 detail::ReadRequiredField("buffer", json, bufferView.buffer);
977 detail::ReadRequiredField("byteLength", json, bufferView.byteLength);
978
979 detail::ReadOptionalField("byteOffset", json, bufferView.byteOffset);
980 detail::ReadOptionalField("byteStride", json, bufferView.byteStride);
981 detail::ReadOptionalField("name", json, bufferView.name);
982 detail::ReadOptionalField("target", json, bufferView.target);
983
984 detail::ReadExtensionsAndExtras(json, bufferView.extensionsAndExtras);
985 }
986
987 inline void from_json(nlohmann::json const & json, Camera::Type & cameraType)
988 {
989 std::string type = json.get<std::string>();
990 if (type == "orthographic")
991 {
992 cameraType = Camera::Type::Orthographic;
993 }
994 else if (type == "perspective")
995 {
996 cameraType = Camera::Type::Perspective;
997 }
998 else
999 {
1000 throw invalid_gltf_document("Unknown camera.type value", type);
1001 }
1002 }
1003
1004 inline void from_json(nlohmann::json const & json, Camera::Orthographic & camera)
1005 {
1006 detail::ReadRequiredField("xmag", json, camera.xmag);
1007 detail::ReadRequiredField("ymag", json, camera.ymag);
1008 detail::ReadRequiredField("zfar", json, camera.zfar);
1009 detail::ReadRequiredField("znear", json, camera.znear);
1010
1011 detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
1012 }
1013
1014 inline void from_json(nlohmann::json const & json, Camera::Perspective & camera)
1015 {
1016 detail::ReadRequiredField("yfov", json, camera.yfov);
1017 detail::ReadRequiredField("znear", json, camera.znear);
1018
1019 detail::ReadOptionalField("aspectRatio", json, camera.aspectRatio);
1020 detail::ReadOptionalField("zfar", json, camera.zfar);
1021
1022 detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
1023 }
1024
1025 inline void from_json(nlohmann::json const & json, Camera & camera)
1026 {
1027 detail::ReadRequiredField("type", json, camera.type);
1028
1029 detail::ReadOptionalField("name", json, camera.name);
1030
1031 detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
1032
1033 if (camera.type == Camera::Type::Perspective)
1034 {
1035 detail::ReadRequiredField("perspective", json, camera.perspective);
1036 }
1037 else if (camera.type == Camera::Type::Orthographic)
1038 {
1039 detail::ReadRequiredField("orthographic", json, camera.orthographic);
1040 }
1041 }
1042
1043 inline void from_json(nlohmann::json const & json, Image & image)
1044 {
1045 detail::ReadOptionalField("bufferView", json, image.bufferView);
1046 detail::ReadOptionalField("mimeType", json, image.mimeType);
1047 detail::ReadOptionalField("name", json, image.name);
1048 detail::ReadOptionalField("uri", json, image.uri);
1049
1050 detail::ReadExtensionsAndExtras(json, image.extensionsAndExtras);
1051 }
1052
1053 inline void from_json(nlohmann::json const & json, Material::AlphaMode & materialAlphaMode)
1054 {
1055 std::string alphaMode = json.get<std::string>();
1056 if (alphaMode == "OPAQUE")
1057 {
1058 materialAlphaMode = Material::AlphaMode::Opaque;
1059 }
1060 else if (alphaMode == "MASK")
1061 {
1062 materialAlphaMode = Material::AlphaMode::Mask;
1063 }
1064 else if (alphaMode == "BLEND")
1065 {
1066 materialAlphaMode = Material::AlphaMode::Blend;
1067 }
1068 else
1069 {
1070 throw invalid_gltf_document("Unknown material.alphaMode value", alphaMode);
1071 }
1072 }
1073
1074 inline void from_json(nlohmann::json const & json, Material::Texture & materialTexture)
1075 {
1076 detail::ReadRequiredField("index", json, materialTexture.index);
1077 detail::ReadOptionalField("texCoord", json, materialTexture.texCoord);
1078
1079 detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
1080 }
1081
1082 inline void from_json(nlohmann::json const & json, Material::NormalTexture & materialTexture)
1083 {
1084 from_json(json, static_cast<Material::Texture &>(materialTexture));
1085 detail::ReadOptionalField("scale", json, materialTexture.scale);
1086
1087 detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
1088 }
1089
1090 inline void from_json(nlohmann::json const & json, Material::OcclusionTexture & materialTexture)
1091 {
1092 from_json(json, static_cast<Material::Texture &>(materialTexture));
1093 detail::ReadOptionalField("strength", json, materialTexture.strength);
1094
1095 detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
1096 }
1097
1098 inline void from_json(nlohmann::json const & json, Material::PBRMetallicRoughness & pbrMetallicRoughness)
1099 {
1100 detail::ReadOptionalField("baseColorFactor", json, pbrMetallicRoughness.baseColorFactor);
1101 detail::ReadOptionalField("baseColorTexture", json, pbrMetallicRoughness.baseColorTexture);
1102 detail::ReadOptionalField("metallicFactor", json, pbrMetallicRoughness.metallicFactor);
1103 detail::ReadOptionalField("metallicRoughnessTexture", json, pbrMetallicRoughness.metallicRoughnessTexture);
1104 detail::ReadOptionalField("roughnessFactor", json, pbrMetallicRoughness.roughnessFactor);
1105
1106 detail::ReadExtensionsAndExtras(json, pbrMetallicRoughness.extensionsAndExtras);
1107 }
1108
1109 inline void from_json(nlohmann::json const & json, Material & material)
1110 {
1111 detail::ReadOptionalField("alphaMode", json, material.alphaMode);
1112 detail::ReadOptionalField("alphaCutoff", json, material.alphaCutoff);
1113 detail::ReadOptionalField("doubleSided", json, material.doubleSided);
1114 detail::ReadOptionalField("emissiveFactor", json, material.emissiveFactor);
1115 detail::ReadOptionalField("emissiveTexture", json, material.emissiveTexture);
1116 detail::ReadOptionalField("name", json, material.name);
1117 detail::ReadOptionalField("normalTexture", json, material.normalTexture);
1118 detail::ReadOptionalField("occlusionTexture", json, material.occlusionTexture);
1119 detail::ReadOptionalField("pbrMetallicRoughness", json, material.pbrMetallicRoughness);
1120
1121 detail::ReadExtensionsAndExtras(json, material.extensionsAndExtras);
1122 }
1123
1124 inline void from_json(nlohmann::json const & json, Mesh & mesh)
1125 {
1126 detail::ReadRequiredField("primitives", json, mesh.primitives);
1127
1128 detail::ReadOptionalField("name", json, mesh.name);
1129 detail::ReadOptionalField("weights", json, mesh.weights);
1130
1131 detail::ReadExtensionsAndExtras(json, mesh.extensionsAndExtras);
1132 }
1133
1134 inline void from_json(nlohmann::json const & json, Node & node)
1135 {
1136 detail::ReadOptionalField("camera", json, node.camera);
1137 detail::ReadOptionalField("children", json, node.children);
1138 detail::ReadOptionalField("matrix", json, node.matrix);
1139 detail::ReadOptionalField("mesh", json, node.mesh);
1140 detail::ReadOptionalField("name", json, node.name);
1141 detail::ReadOptionalField("rotation", json, node.rotation);
1142 detail::ReadOptionalField("scale", json, node.scale);
1143 detail::ReadOptionalField("skin", json, node.skin);
1144 detail::ReadOptionalField("translation", json, node.translation);
1145
1146 detail::ReadExtensionsAndExtras(json, node.extensionsAndExtras);
1147 }
1148
1149 inline void from_json(nlohmann::json const & json, Primitive & primitive)
1150 {
1151 detail::ReadRequiredField("attributes", json, primitive.attributes);
1152
1153 detail::ReadOptionalField("indices", json, primitive.indices);
1154 detail::ReadOptionalField("material", json, primitive.material);
1155 detail::ReadOptionalField("mode", json, primitive.mode);
1156 detail::ReadOptionalField("targets", json, primitive.targets);
1157
1158 detail::ReadExtensionsAndExtras(json, primitive.extensionsAndExtras);
1159 }
1160
1161 inline void from_json(nlohmann::json const & json, Sampler & sampler)
1162 {
1163 detail::ReadOptionalField("magFilter", json, sampler.magFilter);
1164 detail::ReadOptionalField("minFilter", json, sampler.minFilter);
1165 detail::ReadOptionalField("name", json, sampler.name);
1166 detail::ReadOptionalField("wrapS", json, sampler.wrapS);
1167 detail::ReadOptionalField("wrapT", json, sampler.wrapT);
1168
1169 detail::ReadExtensionsAndExtras(json, sampler.extensionsAndExtras);
1170 }
1171
1172 inline void from_json(nlohmann::json const & json, Scene & scene)
1173 {
1174 detail::ReadOptionalField("name", json, scene.name);
1175 detail::ReadOptionalField("nodes", json, scene.nodes);
1176
1177 detail::ReadExtensionsAndExtras(json, scene.extensionsAndExtras);
1178 }
1179
1180 inline void from_json(nlohmann::json const & json, Skin & skin)
1181 {
1182 detail::ReadRequiredField("joints", json, skin.joints);
1183
1184 detail::ReadOptionalField("inverseBindMatrices", json, skin.inverseBindMatrices);
1185 detail::ReadOptionalField("name", json, skin.name);
1186 detail::ReadOptionalField("skeleton", json, skin.skeleton);
1187
1188 detail::ReadExtensionsAndExtras(json, skin.extensionsAndExtras);
1189 }
1190
1191 inline void from_json(nlohmann::json const & json, Texture & texture)
1192 {
1193 detail::ReadOptionalField("name", json, texture.name);
1194 detail::ReadOptionalField("sampler", json, texture.sampler);
1195 detail::ReadOptionalField("source", json, texture.source);
1196
1197 detail::ReadExtensionsAndExtras(json, texture.extensionsAndExtras);
1198 }
1199
1200 inline void from_json(nlohmann::json const & json, Document & document)
1201 {
1202 detail::ReadRequiredField("asset", json, document.asset);
1203
1204 detail::ReadOptionalField("accessors", json, document.accessors);
1205 detail::ReadOptionalField("animations", json, document.animations);
1206 detail::ReadOptionalField("buffers", json, document.buffers);
1207 detail::ReadOptionalField("bufferViews", json, document.bufferViews);
1208 detail::ReadOptionalField("cameras", json, document.cameras);
1209 detail::ReadOptionalField("materials", json, document.materials);
1210 detail::ReadOptionalField("meshes", json, document.meshes);
1211 detail::ReadOptionalField("nodes", json, document.nodes);
1212 detail::ReadOptionalField("images", json, document.images);
1213 detail::ReadOptionalField("samplers", json, document.samplers);
1214 detail::ReadOptionalField("scene", json, document.scene);
1215 detail::ReadOptionalField("scenes", json, document.scenes);
1216 detail::ReadOptionalField("skins", json, document.skins);
1217 detail::ReadOptionalField("textures", json, document.textures);
1218
1219 detail::ReadOptionalField("extensionsUsed", json, document.extensionsUsed);
1220 detail::ReadOptionalField("extensionsRequired", json, document.extensionsRequired);
1221 detail::ReadExtensionsAndExtras(json, document.extensionsAndExtras);
1222 }
1223
1224 inline void to_json(nlohmann::json & json, Accessor::ComponentType const & accessorComponentType)
1225 {
1226 if (accessorComponentType == Accessor::ComponentType::None)
1227 {
1228 throw invalid_gltf_document("Unknown accessor.componentType value");
1229 }
1230
1231 json = static_cast<uint16_t>(accessorComponentType);
1232 }
1233
1234 inline void to_json(nlohmann::json & json, Accessor::Type const & accessorType)
1235 {
1236 switch (accessorType)
1237 {
1238 case Accessor::Type::Scalar:
1239 json = "SCALAR";
1240 break;
1241 case Accessor::Type::Vec2:
1242 json = "VEC2";
1243 break;
1244 case Accessor::Type::Vec3:
1245 json = "VEC3";
1246 break;
1247 case Accessor::Type::Vec4:
1248 json = "VEC4";
1249 break;
1250 case Accessor::Type::Mat2:
1251 json = "MAT2";
1252 break;
1253 case Accessor::Type::Mat3:
1254 json = "MAT3";
1255 break;
1256 case Accessor::Type::Mat4:
1257 json = "MAT4";
1258 break;
1259 default:
1260 throw invalid_gltf_document("Unknown accessor.type value");
1261 }
1262 }
1263
1264 inline void to_json(nlohmann::json & json, Accessor::Sparse::Values const & values)
1265 {
1266 detail::WriteField("bufferView", json, values.bufferView, static_cast<uint32_t>(-1));
1267 detail::WriteField("byteOffset", json, values.byteOffset, {});
1268 detail::WriteExtensions(json, values.extensionsAndExtras);
1269 }
1270
1271 inline void to_json(nlohmann::json & json, Accessor::Sparse::Indices const & indices)
1272 {
1273 detail::WriteField("componentType", json, indices.componentType, Accessor::ComponentType::None);
1274 detail::WriteField("bufferView", json, indices.bufferView, static_cast<uint32_t>(-1));
1275 detail::WriteField("byteOffset", json, indices.byteOffset, {});
1276 detail::WriteExtensions(json, indices.extensionsAndExtras);
1277 }
1278
1279 inline void to_json(nlohmann::json & json, Accessor::Sparse const & sparse)
1280 {
1281 detail::WriteField("count", json, sparse.count, -1);
1282 detail::WriteField("indices", json, sparse.indices);
1283 detail::WriteField("values", json, sparse.values);
1284 detail::WriteExtensions(json, sparse.extensionsAndExtras);
1285 }
1286
1287 inline void to_json(nlohmann::json & json, Accessor const & accessor)
1288 {
1289 detail::WriteField("bufferView", json, accessor.bufferView, -1);
1290 detail::WriteField("byteOffset", json, accessor.byteOffset, {});
1291 detail::WriteField("componentType", json, accessor.componentType, Accessor::ComponentType::None);
1292 detail::WriteField("count", json, accessor.count, {});
1293 detail::WriteField("max", json, accessor.max);
1294 detail::WriteField("min", json, accessor.min);
1295 detail::WriteField("name", json, accessor.name);
1296 detail::WriteField("normalized", json, accessor.normalized, false);
1297 detail::WriteField("sparse", json, accessor.sparse);
1298 detail::WriteField("type", json, accessor.type, Accessor::Type::None);
1299 detail::WriteExtensions(json, accessor.extensionsAndExtras);
1300 }
1301
1302 inline void to_json(nlohmann::json & json, Animation::Channel::Target const & animationChannelTarget)
1303 {
1304 detail::WriteField("node", json, animationChannelTarget.node, -1);
1305 detail::WriteField("path", json, animationChannelTarget.path);
1306 detail::WriteExtensions(json, animationChannelTarget.extensionsAndExtras);
1307 }
1308
1309 inline void to_json(nlohmann::json & json, Animation::Channel const & animationChannel)
1310 {
1311 detail::WriteField("sampler", json, animationChannel.sampler, -1);
1312 detail::WriteField("target", json, animationChannel.target);
1313 detail::WriteExtensions(json, animationChannel.extensionsAndExtras);
1314 }
1315
1316 inline void to_json(nlohmann::json & json, Animation::Sampler::Type const & animationSamplerType)
1317 {
1318 switch (animationSamplerType)
1319 {
1320 case Animation::Sampler::Type::Linear:
1321 json = "LINEAR";
1322 break;
1323 case Animation::Sampler::Type::Step:
1324 json = "STEP";
1325 break;
1326 case Animation::Sampler::Type::CubicSpline:
1327 json = "CUBICSPLINE";
1328 break;
1329 }
1330 }
1331
1332 inline void to_json(nlohmann::json & json, Animation::Sampler const & animationSampler)
1333 {
1334 detail::WriteField("input", json, animationSampler.input, -1);
1335 detail::WriteField("interpolation", json, animationSampler.interpolation, Animation::Sampler::Type::Linear);
1336 detail::WriteField("output", json, animationSampler.output, -1);
1337 detail::WriteExtensions(json, animationSampler.extensionsAndExtras);
1338 }
1339
1340 inline void to_json(nlohmann::json & json, Animation const & animation)
1341 {
1342 detail::WriteField("channels", json, animation.channels);
1343 detail::WriteField("name", json, animation.name);
1344 detail::WriteField("samplers", json, animation.samplers);
1345 detail::WriteExtensions(json, animation.extensionsAndExtras);
1346 }
1347
1348 inline void to_json(nlohmann::json & json, Asset const & asset)
1349 {
1350 detail::WriteField("copyright", json, asset.copyright);
1351 detail::WriteField("generator", json, asset.generator);
1352 detail::WriteField("minVersion", json, asset.minVersion);
1353 detail::WriteField("version", json, asset.version);
1354 detail::WriteExtensions(json, asset.extensionsAndExtras);
1355 }
1356
1357 inline void to_json(nlohmann::json & json, Buffer const & buffer)
1358 {
1359 detail::WriteField("byteLength", json, buffer.byteLength, {});
1360 detail::WriteField("name", json, buffer.name);
1361 detail::WriteField("uri", json, buffer.uri);
1362 detail::WriteExtensions(json, buffer.extensionsAndExtras);
1363 }
1364
1365 inline void to_json(nlohmann::json & json, BufferView const & bufferView)
1366 {
1367 detail::WriteField("buffer", json, bufferView.buffer, -1);
1368 detail::WriteField("byteLength", json, bufferView.byteLength, {});
1369 detail::WriteField("byteOffset", json, bufferView.byteOffset, {});
1370 detail::WriteField("byteStride", json, bufferView.byteStride, {});
1371 detail::WriteField("name", json, bufferView.name);
1372 detail::WriteField("target", json, bufferView.target, BufferView::TargetType::None);
1373 detail::WriteExtensions(json, bufferView.extensionsAndExtras);
1374 }
1375
1376 inline void to_json(nlohmann::json & json, Camera::Type const & cameraType)
1377 {
1378 switch (cameraType)
1379 {
1380 case Camera::Type::Orthographic:
1381 json = "orthographic";
1382 break;
1383 case Camera::Type::Perspective:
1384 json = "perspective";
1385 break;
1386 default:
1387 throw invalid_gltf_document("Unknown camera.type value");
1388 }
1389 }
1390
1391 inline void to_json(nlohmann::json & json, Camera::Orthographic const & camera)
1392 {
1393 detail::WriteField("xmag", json, camera.xmag, defaults::FloatSentinel);
1394 detail::WriteField("ymag", json, camera.ymag, defaults::FloatSentinel);
1395 detail::WriteField("zfar", json, camera.zfar, -defaults::FloatSentinel);
1396 detail::WriteField("znear", json, camera.znear, -defaults::FloatSentinel);
1397 detail::WriteExtensions(json, camera.extensionsAndExtras);
1398 }
1399
1400 inline void to_json(nlohmann::json & json, Camera::Perspective const & camera)
1401 {
1402 detail::WriteField("aspectRatio", json, camera.aspectRatio, {});
1403 detail::WriteField("yfov", json, camera.yfov, {});
1404 detail::WriteField("zfar", json, camera.zfar, {});
1405 detail::WriteField("znear", json, camera.znear, {});
1406 detail::WriteExtensions(json, camera.extensionsAndExtras);
1407 }
1408
1409 inline void to_json(nlohmann::json & json, Camera const & camera)
1410 {
1411 detail::WriteField("name", json, camera.name);
1412 detail::WriteField("type", json, camera.type, Camera::Type::None);
1413 detail::WriteExtensions(json, camera.extensionsAndExtras);
1414
1415 if (camera.type == Camera::Type::Perspective)
1416 {
1417 detail::WriteField("perspective", json, camera.perspective);
1418 }
1419 else if (camera.type == Camera::Type::Orthographic)
1420 {
1421 detail::WriteField("orthographic", json, camera.orthographic);
1422 }
1423 }
1424
1425 inline void to_json(nlohmann::json & json, Image const & image)
1426 {
1427 detail::WriteField("bufferView", json, image.bufferView, image.uri.empty() ? -1 : 0); // bufferView or uri need to be written; even if default 0
1428 detail::WriteField("mimeType", json, image.mimeType);
1429 detail::WriteField("name", json, image.name);
1430 detail::WriteField("uri", json, image.uri);
1431 detail::WriteExtensions(json, image.extensionsAndExtras);
1432 }
1433
1434 inline void to_json(nlohmann::json & json, Material::AlphaMode const & materialAlphaMode)
1435 {
1436 switch (materialAlphaMode)
1437 {
1438 case Material::AlphaMode::Opaque:
1439 json = "OPAQUE";
1440 break;
1441 case Material::AlphaMode::Mask:
1442 json = "MASK";
1443 break;
1444 case Material::AlphaMode::Blend:
1445 json = "BLEND";
1446 break;
1447 }
1448 }
1449
1450 inline void to_json(nlohmann::json & json, Material::Texture const & materialTexture)
1451 {
1452 detail::WriteField("index", json, materialTexture.index, -1);
1453 detail::WriteField("texCoord", json, materialTexture.texCoord, 0);
1454 detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
1455 }
1456
1457 inline void to_json(nlohmann::json & json, Material::NormalTexture const & materialTexture)
1458 {
1459 to_json(json, static_cast<Material::Texture const &>(materialTexture));
1460 detail::WriteField("scale", json, materialTexture.scale, defaults::IdentityScalar);
1461 detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
1462 }
1463
1464 inline void to_json(nlohmann::json & json, Material::OcclusionTexture const & materialTexture)
1465 {
1466 to_json(json, static_cast<Material::Texture const &>(materialTexture));
1467 detail::WriteField("strength", json, materialTexture.strength, defaults::IdentityScalar);
1468 detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
1469 }
1470
1471 inline void to_json(nlohmann::json & json, Material::PBRMetallicRoughness const & pbrMetallicRoughness)
1472 {
1473 detail::WriteField("baseColorFactor", json, pbrMetallicRoughness.baseColorFactor, defaults::IdentityVec4);
1474 detail::WriteField("baseColorTexture", json, pbrMetallicRoughness.baseColorTexture);
1475 detail::WriteField("metallicFactor", json, pbrMetallicRoughness.metallicFactor, defaults::IdentityScalar);
1476 detail::WriteField("metallicRoughnessTexture", json, pbrMetallicRoughness.metallicRoughnessTexture);
1477 detail::WriteField("roughnessFactor", json, pbrMetallicRoughness.roughnessFactor, defaults::IdentityScalar);
1478 detail::WriteExtensions(json, pbrMetallicRoughness.extensionsAndExtras);
1479 }
1480
1481 inline void to_json(nlohmann::json & json, Material const & material)
1482 {
1483 detail::WriteField("alphaCutoff", json, material.alphaCutoff, defaults::MaterialAlphaCutoff);
1484 detail::WriteField("alphaMode", json, material.alphaMode, Material::AlphaMode::Opaque);
1485 detail::WriteField("doubleSided", json, material.doubleSided, defaults::MaterialDoubleSided);
1486 detail::WriteField("emissiveTexture", json, material.emissiveTexture);
1487 detail::WriteField("emissiveFactor", json, material.emissiveFactor, defaults::NullVec3);
1488 detail::WriteField("name", json, material.name);
1489 detail::WriteField("normalTexture", json, material.normalTexture);
1490 detail::WriteField("occlusionTexture", json, material.occlusionTexture);
1491 detail::WriteField("pbrMetallicRoughness", json, material.pbrMetallicRoughness);
1492
1493 detail::WriteExtensions(json, material.extensionsAndExtras);
1494 }
1495
1496 inline void to_json(nlohmann::json & json, Mesh const & mesh)
1497 {
1498 detail::WriteField("name", json, mesh.name);
1499 detail::WriteField("primitives", json, mesh.primitives);
1500 detail::WriteField("weights", json, mesh.weights);
1501 detail::WriteExtensions(json, mesh.extensionsAndExtras);
1502 }
1503
1504 inline void to_json(nlohmann::json & json, Node const & node)
1505 {
1506 detail::WriteField("camera", json, node.camera, -1);
1507 detail::WriteField("children", json, node.children);
1508 detail::WriteField("matrix", json, node.matrix, defaults::IdentityMatrix);
1509 detail::WriteField("mesh", json, node.mesh, -1);
1510 detail::WriteField("name", json, node.name);
1511 detail::WriteField("rotation", json, node.rotation, defaults::IdentityRotation);
1512 detail::WriteField("scale", json, node.scale, defaults::IdentityVec3);
1513 detail::WriteField("skin", json, node.skin, -1);
1514 detail::WriteField("translation", json, node.translation, defaults::NullVec3);
1515 detail::WriteField("weights", json, node.weights);
1516 detail::WriteExtensions(json, node.extensionsAndExtras);
1517 }
1518
1519 inline void to_json(nlohmann::json & json, Primitive const & primitive)
1520 {
1521 detail::WriteField("attributes", json, primitive.attributes);
1522 detail::WriteField("indices", json, primitive.indices, -1);
1523 detail::WriteField("material", json, primitive.material, -1);
1524 detail::WriteField("mode", json, primitive.mode, Primitive::Mode::Triangles);
1525 detail::WriteField("targets", json, primitive.targets);
1526 detail::WriteExtensions(json, primitive.extensionsAndExtras);
1527 }
1528
1529 inline void to_json(nlohmann::json & json, Sampler const & sampler)
1530 {
1531 if (!sampler.empty())
1532 {
1533 detail::WriteField("name", json, sampler.name);
1534 detail::WriteField("magFilter", json, sampler.magFilter, Sampler::MagFilter::None);
1535 detail::WriteField("minFilter", json, sampler.minFilter, Sampler::MinFilter::None);
1536 detail::WriteField("wrapS", json, sampler.wrapS, Sampler::WrappingMode::Repeat);
1537 detail::WriteField("wrapT", json, sampler.wrapT, Sampler::WrappingMode::Repeat);
1538 detail::WriteExtensions(json, sampler.extensionsAndExtras);
1539 }
1540 else
1541 {
1542 // If a sampler is completely empty we still need to write out an empty object for the encompassing array...
1543 json = nlohmann::json::object();
1544 }
1545 }
1546
1547 inline void to_json(nlohmann::json & json, Scene const & scene)
1548 {
1549 detail::WriteField("name", json, scene.name);
1550 detail::WriteField("nodes", json, scene.nodes);
1551 detail::WriteExtensions(json, scene.extensionsAndExtras);
1552 }
1553
1554 inline void to_json(nlohmann::json & json, Skin const & skin)
1555 {
1556 detail::WriteField("inverseBindMatrices", json, skin.inverseBindMatrices, -1);
1557 detail::WriteField("name", json, skin.name);
1558 detail::WriteField("skeleton", json, skin.skeleton, -1);
1559 detail::WriteField("joints", json, skin.joints);
1560 detail::WriteExtensions(json, skin.extensionsAndExtras);
1561 }
1562
1563 inline void to_json(nlohmann::json & json, Texture const & texture)
1564 {
1565 detail::WriteField("name", json, texture.name);
1566 detail::WriteField("sampler", json, texture.sampler, -1);
1567 detail::WriteField("source", json, texture.source, -1);
1568 detail::WriteExtensions(json, texture.extensionsAndExtras);
1569 }
1570
1571 inline void to_json(nlohmann::json & json, Document const & document)
1572 {
1573 detail::WriteField("accessors", json, document.accessors);
1574 detail::WriteField("animations", json, document.animations);
1575 detail::WriteField("asset", json, document.asset);
1576 detail::WriteField("buffers", json, document.buffers);
1577 detail::WriteField("bufferViews", json, document.bufferViews);
1578 detail::WriteField("cameras", json, document.cameras);
1579 detail::WriteField("images", json, document.images);
1580 detail::WriteField("materials", json, document.materials);
1581 detail::WriteField("meshes", json, document.meshes);
1582 detail::WriteField("nodes", json, document.nodes);
1583 detail::WriteField("samplers", json, document.samplers);
1584 detail::WriteField("scene", json, document.scene, -1);
1585 detail::WriteField("scenes", json, document.scenes);
1586 detail::WriteField("skins", json, document.skins);
1587 detail::WriteField("textures", json, document.textures);
1588
1589 detail::WriteField("extensionsUsed", json, document.extensionsUsed);
1590 detail::WriteField("extensionsRequired", json, document.extensionsRequired);
1591 detail::WriteExtensions(json, document.extensionsAndExtras);
1592 }
1593
1594 namespace detail
1595 {
1597 {
1598 std::string bufferRootPath{};
1599 ReadQuotas readQuotas;
1600
1601 std::vector<uint8_t> * binaryData{};
1602 };
1603
1604 inline void ThrowIfBad(std::ios const & io)
1605 {
1606 if (!io.good())
1607 {
1608 throw std::system_error(std::make_error_code(std::errc::io_error));
1609 }
1610 }
1611
1612 inline void MaterializeData(Buffer & buffer)
1613 {
1614 const std::size_t startPos = std::char_traits<char>::length(detail::MimetypeApplicationOctet) + 1;
1615 const std::size_t base64Length = buffer.uri.length() - startPos;
1616 const std::size_t decodedEstimate = base64Length / 4 * 3;
1617 if ((decodedEstimate - 2) > buffer.byteLength) // we need to give room for padding...
1618 {
1619 throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
1620 }
1621
1622#if defined(FX_GLTF_HAS_CPP_17)
1623 const bool success = base64::TryDecode({ &buffer.uri[startPos], base64Length }, buffer.data);
1624#else
1625 const bool success = base64::TryDecode(buffer.uri.substr(startPos), buffer.data);
1626#endif
1627 if (!success)
1628 {
1629 throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
1630 }
1631 }
1632
1633 inline Document Create(nlohmann::json const & json, DataContext const & dataContext)
1634 {
1635 Document document = json;
1636
1637 if (document.buffers.size() > dataContext.readQuotas.MaxBufferCount)
1638 {
1639 throw invalid_gltf_document("Quota exceeded : number of buffers > MaxBufferCount");
1640 }
1641
1642 for (auto & buffer : document.buffers)
1643 {
1644 if (buffer.byteLength == 0)
1645 {
1646 throw invalid_gltf_document("Invalid buffer.byteLength value : 0");
1647 }
1648
1649 if (buffer.byteLength > dataContext.readQuotas.MaxBufferByteLength)
1650 {
1651 throw invalid_gltf_document("Quota exceeded : buffer.byteLength > MaxBufferByteLength");
1652 }
1653
1654 if (!buffer.uri.empty())
1655 {
1656 if (buffer.IsEmbeddedResource())
1657 {
1658 detail::MaterializeData(buffer);
1659 }
1660 else
1661 {
1662 std::ifstream fileData(detail::CreateBufferUriPath(dataContext.bufferRootPath, buffer.uri), std::ios::binary);
1663 if (!fileData.good())
1664 {
1665 throw invalid_gltf_document("Invalid buffer.uri value", buffer.uri);
1666 }
1667
1668 buffer.data.resize(buffer.byteLength);
1669 fileData.read(reinterpret_cast<char *>(&buffer.data[0]), buffer.byteLength);
1670 }
1671 }
1672 else if (dataContext.binaryData != nullptr)
1673 {
1674 std::vector<uint8_t> & binary = *dataContext.binaryData;
1675 if (binary.size() < buffer.byteLength)
1676 {
1677 throw invalid_gltf_document("Invalid GLB buffer data");
1678 }
1679
1680 buffer.data.resize(buffer.byteLength);
1681 std::memcpy(&buffer.data[0], &binary[0], buffer.byteLength);
1682 }
1683 }
1684
1685 return document;
1686 }
1687
1688 inline void ValidateBuffers(Document const & document, bool useBinaryFormat)
1689 {
1690 if (document.buffers.empty())
1691 {
1692 throw invalid_gltf_document("Invalid glTF document. A document must have at least 1 buffer.");
1693 }
1694
1695 bool foundBinaryBuffer = false;
1696 for (std::size_t bufferIndex = 0; bufferIndex < document.buffers.size(); bufferIndex++)
1697 {
1698 Buffer const & buffer = document.buffers[bufferIndex];
1699 if (buffer.byteLength == 0)
1700 {
1701 throw invalid_gltf_document("Invalid buffer.byteLength value : 0");
1702 }
1703
1704 if (buffer.byteLength != buffer.data.size())
1705 {
1706 throw invalid_gltf_document("Invalid buffer.byteLength value : does not match buffer.data size");
1707 }
1708
1709 if (buffer.uri.empty())
1710 {
1711 foundBinaryBuffer = true;
1712 if (bufferIndex != 0)
1713 {
1714 throw invalid_gltf_document("Invalid glTF document. Only 1 buffer, the very first, is allowed to have an empty buffer.uri field.");
1715 }
1716 }
1717 }
1718
1719 if (useBinaryFormat && !foundBinaryBuffer)
1720 {
1721 throw invalid_gltf_document("Invalid glTF document. No buffer found which can meet the criteria for saving to a .glb file.");
1722 }
1723 }
1724
1725 inline void Save(Document const & document, std::ostream & output, std::string const & documentRootPath, bool useBinaryFormat)
1726 {
1727 // There is no way to check if an ostream has been opened in binary mode or not. Just checking
1728 // if it's "good" is the best we can do from here...
1729 detail::ThrowIfBad(output);
1730
1731 nlohmann::json json = document;
1732
1733 std::size_t externalBufferIndex = 0;
1734 if (useBinaryFormat)
1735 {
1736 detail::GLBHeader header{ detail::GLBHeaderMagic, 2, 0, { 0, detail::GLBChunkJSON } };
1737 detail::ChunkHeader binHeader{ 0, detail::GLBChunkBIN };
1738
1739 std::string jsonText = json.dump();
1740
1741 Buffer const & binBuffer = document.buffers.front();
1742 const uint32_t binPaddedLength = ((binBuffer.byteLength + 3) & (~3u));
1743 const uint32_t binPadding = binPaddedLength - binBuffer.byteLength;
1744 binHeader.chunkLength = binPaddedLength;
1745
1746 header.jsonHeader.chunkLength = ((jsonText.length() + 3) & (~3u));
1747 const uint32_t headerPadding = static_cast<uint32_t>(header.jsonHeader.chunkLength - jsonText.length());
1748 header.length = detail::HeaderSize + header.jsonHeader.chunkLength + detail::ChunkHeaderSize + binHeader.chunkLength;
1749
1750 const char spaces[3] = { ' ', ' ', ' ' };
1751 const char nulls[3] = { 0, 0, 0 };
1752
1753 output.write(reinterpret_cast<char *>(&header), detail::HeaderSize);
1754 output.write(jsonText.c_str(), jsonText.length());
1755 output.write(&spaces[0], headerPadding);
1756 output.write(reinterpret_cast<char *>(&binHeader), detail::ChunkHeaderSize);
1757 output.write(reinterpret_cast<char const *>(&binBuffer.data[0]), binBuffer.byteLength);
1758 output.write(&nulls[0], binPadding);
1759
1760 externalBufferIndex = 1;
1761 }
1762 else
1763 {
1764 output << json.dump(2);
1765 }
1766
1767 // The glTF 2.0 spec allows a document to have more than 1 buffer. However, only the first one will be included in the .glb
1768 // All others must be considered as External/Embedded resources. Process them if necessary...
1769 for (; externalBufferIndex < document.buffers.size(); externalBufferIndex++)
1770 {
1771 Buffer const & buffer = document.buffers[externalBufferIndex];
1772 if (!buffer.IsEmbeddedResource())
1773 {
1774 std::ofstream fileData(detail::CreateBufferUriPath(documentRootPath, buffer.uri), std::ios::binary);
1775 if (!fileData.good())
1776 {
1777 throw invalid_gltf_document("Invalid buffer.uri value", buffer.uri);
1778 }
1779
1780 fileData.write(reinterpret_cast<char const *>(&buffer.data[0]), buffer.byteLength);
1781 }
1782 }
1783 }
1784 } // namespace detail
1785
1786 inline Document LoadFromText(std::istream & input, std::string const & documentRootPath, ReadQuotas const & readQuotas = {})
1787 {
1788 try
1789 {
1790 detail::ThrowIfBad(input);
1791
1792 nlohmann::json json;
1793 input >> json;
1794
1795 return detail::Create(json, { documentRootPath, readQuotas });
1796 }
1797 catch (invalid_gltf_document &)
1798 {
1799 throw;
1800 }
1801 catch (std::system_error &)
1802 {
1803 throw;
1804 }
1805 catch (...)
1806 {
1807 std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
1808 }
1809 }
1810
1811 inline Document LoadFromText(std::string const & documentFilePath, ReadQuotas const & readQuotas = {})
1812 {
1813 std::ifstream input(documentFilePath);
1814 if (!input.is_open())
1815 {
1816 throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory));
1817 }
1818
1819 return LoadFromText(input, detail::GetDocumentRootPath(documentFilePath), readQuotas);
1820 }
1821
1822 inline Document LoadFromBinary(std::istream & input, std::string const & documentRootPath, ReadQuotas const & readQuotas = {})
1823 {
1824 try
1825 {
1826 detail::GLBHeader header{};
1827 detail::ThrowIfBad(input.read(reinterpret_cast<char *>(&header), detail::HeaderSize));
1828 if (header.magic != detail::GLBHeaderMagic ||
1829 header.jsonHeader.chunkType != detail::GLBChunkJSON ||
1830 header.jsonHeader.chunkLength + detail::HeaderSize > header.length)
1831 {
1832 throw invalid_gltf_document("Invalid GLB header");
1833 }
1834
1835 std::vector<uint8_t> json{};
1836 json.resize(header.jsonHeader.chunkLength);
1837 detail::ThrowIfBad(input.read(reinterpret_cast<char *>(&json[0]), header.jsonHeader.chunkLength));
1838
1839 std::size_t totalSize = detail::HeaderSize + header.jsonHeader.chunkLength;
1840 if (totalSize > readQuotas.MaxFileSize)
1841 {
1842 throw invalid_gltf_document("Quota exceeded : file size > MaxFileSize");
1843 }
1844
1845 detail::ChunkHeader binHeader{};
1846 detail::ThrowIfBad(input.read(reinterpret_cast<char *>(&binHeader), detail::ChunkHeaderSize));
1847 if (binHeader.chunkType != detail::GLBChunkBIN)
1848 {
1849 throw invalid_gltf_document("Invalid GLB header");
1850 }
1851
1852 totalSize += detail::ChunkHeaderSize + binHeader.chunkLength;
1853 if (totalSize > readQuotas.MaxFileSize)
1854 {
1855 throw invalid_gltf_document("Quota exceeded : file size > MaxFileSize");
1856 }
1857
1858 std::vector<uint8_t> binary{};
1859 binary.resize(binHeader.chunkLength);
1860 detail::ThrowIfBad(input.read(reinterpret_cast<char *>(&binary[0]), binHeader.chunkLength));
1861
1862 return detail::Create(
1863 nlohmann::json::parse({ &json[0], header.jsonHeader.chunkLength }),
1864 { documentRootPath, readQuotas, &binary });
1865 }
1866 catch (invalid_gltf_document &)
1867 {
1868 throw;
1869 }
1870 catch (std::system_error &)
1871 {
1872 throw;
1873 }
1874 catch (...)
1875 {
1876 std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
1877 }
1878 }
1879
1880 inline Document LoadFromBinary(std::string const & documentFilePath, ReadQuotas const & readQuotas = {})
1881 {
1882 std::ifstream input(documentFilePath, std::ios::binary);
1883 if (!input.is_open())
1884 {
1885 throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory));
1886 }
1887
1888 return LoadFromBinary(input, detail::GetDocumentRootPath(documentFilePath), readQuotas);
1889 }
1890
1891 inline void Save(Document const & document, std::ostream & output, std::string const & documentRootPath, bool useBinaryFormat)
1892 {
1893 try
1894 {
1895 detail::ValidateBuffers(document, useBinaryFormat);
1896
1897 detail::Save(document, output, documentRootPath, useBinaryFormat);
1898 }
1899 catch (invalid_gltf_document &)
1900 {
1901 throw;
1902 }
1903 catch (std::system_error &)
1904 {
1905 throw;
1906 }
1907 catch (...)
1908 {
1909 std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
1910 }
1911 }
1912
1913 inline void Save(Document const & document, std::string const & documentFilePath, bool useBinaryFormat)
1914 {
1915 std::ofstream output(documentFilePath, useBinaryFormat ? std::ios::binary : std::ios::out);
1916 Save(document, output, detail::GetDocumentRootPath(documentFilePath), useBinaryFormat);
1917 }
1918} // namespace gltf
1919
1920// A general-purpose utility to format an exception hierarchy into a string for output
1921inline void FormatException(std::string & output, std::exception const & ex, int level = 0)
1922{
1923 output.append(std::string(level, ' ')).append(ex.what());
1924 try
1925 {
1926 std::rethrow_if_nested(ex);
1927 }
1928 catch (std::exception const & e)
1929 {
1930 FormatException(output.append("\n"), e, level + 2);
1931 }
1932}
1933
1934} // namespace fx
1935
1936#undef FX_GLTF_HAS_CPP_17