diff --git a/.gitmodules b/.gitmodules
index 7b16fae7d72502a1be0920e0a8f52c1390cc6af9..524f3992e46963458c29ea7ecb067c8258029e69 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "external/DMath"]
 	path = external/DMath
 	url = git@github.com:Didgy74/DMath.git
+[submodule "external/fx-gltf"]
+	path = external/fx-gltf
+	url = https://github.com/jessey-git/fx-gltf.git
diff --git a/external/fx-gltf b/external/fx-gltf
new file mode 160000
index 0000000000000000000000000000000000000000..85ac827969428df599f7b7866eef9af143217ec9
--- /dev/null
+++ b/external/fx-gltf
@@ -0,0 +1 @@
+Subproject commit 85ac827969428df599f7b7866eef9af143217ec9
diff --git a/include/fx-gltf/gltf.h b/include/fx-gltf/gltf.h
deleted file mode 100644
index 01ad051d3de227353bb55177ee6656089620a183..0000000000000000000000000000000000000000
--- a/include/fx-gltf/gltf.h
+++ /dev/null
@@ -1,1928 +0,0 @@
-// ------------------------------------------------------------
-// Copyright(c) 2018 Jesse Yurkovich
-// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-// See the LICENSE file in the repo root for full license information.
-// ------------------------------------------------------------
-#pragma once
-
-#include <array>
-#include <cstring>
-#include <fstream>
-#include <stdexcept>
-#include <string>
-#include <system_error>
-#include <unordered_map>
-#include <vector>
-
-#include <nlohmann/json.hpp>
-
-#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1)
-#define FX_GLTF_HAS_CPP_17
-#include <string_view>
-#endif
-
-namespace fx
-{
-namespace base64
-{
-    namespace detail
-    {
-        // clang-format off
-        constexpr std::array<char, 64> EncodeMap =
-        {
-            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
-            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
-            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
-            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
-        };
-
-        constexpr std::array<char, 256> DecodeMap =
-        {
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
-            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
-            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        };
-        // clang-format on
-    } // namespace detail
-
-    inline std::string Encode(std::vector<uint8_t> const & bytes)
-    {
-        const std::size_t length = bytes.size();
-        if (length == 0)
-        {
-            return {};
-        }
-
-        std::string out{};
-        out.reserve(((length * 4 / 3) + 3) & (~3u)); // round up to nearest 4
-
-        uint32_t value = 0;
-        int32_t bitCount = -6;
-        for (const uint8_t c : bytes)
-        {
-            value = (value << 8u) + c;
-            bitCount += 8;
-            while (bitCount >= 0)
-            {
-                const uint32_t shiftOperand = bitCount;
-                out.push_back(detail::EncodeMap.at((value >> shiftOperand) & 0x3fu));
-                bitCount -= 6;
-            }
-        }
-
-        if (bitCount > -6)
-        {
-            const uint32_t shiftOperand = bitCount + 8;
-            out.push_back(detail::EncodeMap.at(((value << 8u) >> shiftOperand) & 0x3fu));
-        }
-
-        while (out.size() % 4 != 0)
-        {
-            out.push_back('=');
-        }
-
-        return out;
-    }
-
-#if defined(FX_GLTF_HAS_CPP_17)
-    inline bool TryDecode(std::string_view in, std::vector<uint8_t> & out)
-#else
-    inline bool TryDecode(std::string const & in, std::vector<uint8_t> & out)
-#endif
-    {
-        out.clear();
-
-        const std::size_t length = in.length();
-        if (length == 0)
-        {
-            return true;
-        }
-
-        if (length % 4 != 0)
-        {
-            return false;
-        }
-
-        out.reserve((length / 4) * 3);
-
-        bool invalid = false;
-        uint32_t value = 0;
-        int32_t bitCount = -8;
-        for (std::size_t i = 0; i < length; i++)
-        {
-            const uint8_t c = static_cast<uint8_t>(in[i]);
-            const char map = detail::DecodeMap.at(c);
-            if (map == -1)
-            {
-                if (c != '=') // Non base64 character
-                {
-                    invalid = true;
-                }
-                else
-                {
-                    // Padding characters not where they should be
-                    const std::size_t remaining = length - i - 1;
-                    if (remaining > 1 || (remaining == 1 ? in[i + 1] != '=' : false))
-                    {
-                        invalid = true;
-                    }
-                }
-
-                break;
-            }
-
-            value = (value << 6u) + map;
-            bitCount += 6;
-            if (bitCount >= 0)
-            {
-                const uint32_t shiftOperand = bitCount;
-                out.push_back(static_cast<uint8_t>(value >> shiftOperand));
-                bitCount -= 8;
-            }
-        }
-
-        if (invalid)
-        {
-            out.clear();
-        }
-
-        return !invalid;
-    }
-} // namespace base64
-
-namespace gltf
-{
-    class invalid_gltf_document : public std::runtime_error
-    {
-    public:
-        explicit invalid_gltf_document(char const * message)
-            : std::runtime_error(message)
-        {
-        }
-
-        invalid_gltf_document(char const * message, std::string const & extra)
-            : std::runtime_error(CreateMessage(message, extra).c_str())
-        {
-        }
-
-    private:
-        std::string CreateMessage(char const * message, std::string const & extra)
-        {
-            return std::string(message).append(" : ").append(extra);
-        }
-    };
-
-    namespace detail
-    {
-#if defined(FX_GLTF_HAS_CPP_17)
-        template <typename TTarget>
-        inline void ReadRequiredField(std::string_view key, nlohmann::json const & json, TTarget & target)
-#else
-        template <typename TKey, typename TTarget>
-        inline void ReadRequiredField(TKey && key, nlohmann::json const & json, TTarget & target)
-#endif
-        {
-            const nlohmann::json::const_iterator iter = json.find(key);
-            if (iter == json.end())
-            {
-                throw invalid_gltf_document("Required field not found", std::string(key));
-            }
-
-            target = iter->get<TTarget>();
-        }
-
-#if defined(FX_GLTF_HAS_CPP_17)
-        template <typename TTarget>
-        inline void ReadOptionalField(std::string_view key, nlohmann::json const & json, TTarget & target)
-#else
-        template <typename TKey, typename TTarget>
-        inline void ReadOptionalField(TKey && key, nlohmann::json const & json, TTarget & target)
-#endif
-        {
-            const nlohmann::json::const_iterator iter = json.find(key);
-            if (iter != json.end())
-            {
-                target = iter->get<TTarget>();
-            }
-        }
-
-        inline void ReadExtensionsAndExtras(nlohmann::json const & json, nlohmann::json & extensionsAndExtras)
-        {
-            const nlohmann::json::const_iterator iterExtensions = json.find("extensions");
-            const nlohmann::json::const_iterator iterExtras = json.find("extras");
-            if (iterExtensions != json.end())
-            {
-                extensionsAndExtras["extensions"] = *iterExtensions;
-            }
-
-            if (iterExtras != json.end())
-            {
-                extensionsAndExtras["extras"] = *iterExtras;
-            }
-        }
-
-        template <typename TValue>
-        inline void WriteField(std::string const & key, nlohmann::json & json, TValue const & value)
-        {
-            if (!value.empty())
-            {
-                json[key] = value;
-            }
-        }
-
-        template <typename TValue>
-        inline void WriteField(std::string const & key, nlohmann::json & json, TValue const & value, TValue const & defaultValue)
-        {
-            if (value != defaultValue)
-            {
-                json[key] = value;
-            }
-        }
-
-        inline void WriteExtensions(nlohmann::json & json, nlohmann::json const & extensionsAndExtras)
-        {
-            if (!extensionsAndExtras.empty())
-            {
-                for (nlohmann::json::const_iterator it = extensionsAndExtras.begin(); it != extensionsAndExtras.end(); ++it)
-                {
-                    json[it.key()] = it.value();
-                }
-            }
-        }
-
-        inline std::string GetDocumentRootPath(std::string const & documentFilePath)
-        {
-            const std::size_t pos = documentFilePath.find_last_of("/\\");
-            if (pos != std::string::npos)
-            {
-                return documentFilePath.substr(0, pos);
-            }
-
-            return {};
-        }
-
-        inline std::string CreateBufferUriPath(std::string const & documentRootPath, std::string const & bufferUri)
-        {
-            // Prevent simple forms of path traversal from malicious uri references...
-            if (bufferUri.empty() || bufferUri.find("..") != std::string::npos || bufferUri.front() == '/' || bufferUri.front() == '\\')
-            {
-                throw invalid_gltf_document("Invalid buffer.uri value", bufferUri);
-            }
-
-            std::string documentRoot = documentRootPath;
-            if (documentRoot.length() > 0)
-            {
-                if (documentRoot.back() != '/')
-                {
-                    documentRoot.push_back('/');
-                }
-            }
-
-            return documentRoot + bufferUri;
-        }
-
-        struct ChunkHeader
-        {
-            uint32_t chunkLength{};
-            uint32_t chunkType{};
-        };
-
-        struct GLBHeader
-        {
-            uint32_t magic{};
-            uint32_t version{};
-            uint32_t length{};
-
-            ChunkHeader jsonHeader{};
-        };
-
-        constexpr uint32_t DefaultMaxBufferCount = 8;
-        constexpr uint32_t DefaultMaxMemoryAllocation = 32 * 1024 * 1024;
-        constexpr std::size_t HeaderSize{ sizeof(GLBHeader) };
-        constexpr std::size_t ChunkHeaderSize{ sizeof(ChunkHeader) };
-        constexpr uint32_t GLBHeaderMagic = 0x46546c67u;
-        constexpr uint32_t GLBChunkJSON = 0x4e4f534au;
-        constexpr uint32_t GLBChunkBIN = 0x004e4942u;
-
-        constexpr char const * const MimetypeApplicationOctet = "data:application/octet-stream;base64";
-        constexpr char const * const MimetypeImagePNG = "data:image/png;base64";
-        constexpr char const * const MimetypeImageJPG = "data:image/jpeg;base64";
-    } // namespace detail
-
-    namespace defaults
-    {
-        constexpr std::array<float, 16> IdentityMatrix{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
-        constexpr std::array<float, 4> IdentityRotation{ 0, 0, 0, 1 };
-        constexpr std::array<float, 4> IdentityVec4{ 1, 1, 1, 1 };
-        constexpr std::array<float, 3> IdentityVec3{ 1, 1, 1 };
-        constexpr std::array<float, 3> NullVec3{ 0, 0, 0 };
-        constexpr float IdentityScalar = 1;
-        constexpr float FloatSentinel = 10000;
-
-        constexpr bool AccessorNormalized = false;
-
-        constexpr float MaterialAlphaCutoff = 0.5f;
-        constexpr bool MaterialDoubleSided = false;
-    } // namespace defaults
-
-    using Attributes = std::unordered_map<std::string, uint32_t>;
-
-    struct NeverEmpty
-    {
-        bool empty() const noexcept
-        {
-            return false;
-        }
-    };
-
-    struct Accessor
-    {
-        enum class ComponentType : uint16_t
-        {
-            None = 0,
-            Byte = 5120,
-            UnsignedByte = 5121,
-            Short = 5122,
-            UnsignedShort = 5123,
-            UnsignedInt = 5125,
-            Float = 5126
-        };
-
-        enum class Type : uint8_t
-        {
-            None,
-            Scalar,
-            Vec2,
-            Vec3,
-            Vec4,
-            Mat2,
-            Mat3,
-            Mat4
-        };
-
-        struct Sparse
-        {
-            struct Indices : NeverEmpty
-            {
-                uint32_t bufferView{};
-                uint32_t byteOffset{};
-                ComponentType componentType{ ComponentType::None };
-
-                nlohmann::json extensionsAndExtras{};
-            };
-
-            struct Values : NeverEmpty
-            {
-                uint32_t bufferView{};
-                uint32_t byteOffset{};
-
-                nlohmann::json extensionsAndExtras{};
-            };
-
-            int32_t count{};
-            Indices indices{};
-            Values values{};
-
-            nlohmann::json extensionsAndExtras{};
-
-            bool empty() const noexcept
-            {
-                return count == 0;
-            }
-        };
-
-        int32_t bufferView{ -1 };
-        uint32_t byteOffset{};
-        uint32_t count{};
-        bool normalized{ defaults::AccessorNormalized };
-
-        ComponentType componentType{ ComponentType::None };
-        Type type{ Type::None };
-        Sparse sparse{};
-
-        std::string name;
-        std::vector<float> max{};
-        std::vector<float> min{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Animation
-    {
-        struct Channel
-        {
-            struct Target : NeverEmpty
-            {
-                int32_t node{ -1 };
-                std::string path{};
-
-                nlohmann::json extensionsAndExtras{};
-            };
-
-            int32_t sampler{ -1 };
-            Target target{};
-
-            nlohmann::json extensionsAndExtras{};
-        };
-
-        struct Sampler
-        {
-            enum class Type
-            {
-                Linear,
-                Step,
-                CubicSpline
-            };
-
-            int32_t input{ -1 };
-            int32_t output{ -1 };
-
-            Type interpolation{ Sampler::Type::Linear };
-
-            nlohmann::json extensionsAndExtras{};
-        };
-
-        std::string name{};
-        std::vector<Channel> channels{};
-        std::vector<Sampler> samplers{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Asset : NeverEmpty
-    {
-        std::string copyright{};
-        std::string generator{};
-        std::string minVersion{};
-        std::string version{ "2.0" };
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Buffer
-    {
-        uint32_t byteLength{};
-
-        std::string name;
-        std::string uri;
-
-        nlohmann::json extensionsAndExtras{};
-
-        std::vector<uint8_t> data{};
-
-        bool IsEmbeddedResource() const noexcept
-        {
-            return uri.find(detail::MimetypeApplicationOctet) == 0;
-        }
-
-        void SetEmbeddedResource()
-        {
-            uri = std::string(detail::MimetypeApplicationOctet).append(",").append(base64::Encode(data));
-        }
-    };
-
-    struct BufferView
-    {
-        enum class TargetType : uint16_t
-        {
-            None = 0,
-            ArrayBuffer = 34962,
-            ElementArrayBuffer = 34963
-        };
-
-        std::string name;
-
-        int32_t buffer{ -1 };
-        uint32_t byteOffset{};
-        uint32_t byteLength{};
-        uint32_t byteStride{};
-
-        TargetType target{ TargetType::None };
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Camera
-    {
-        enum class Type
-        {
-            None,
-            Orthographic,
-            Perspective
-        };
-
-        struct Orthographic : NeverEmpty
-        {
-            float xmag{ defaults::FloatSentinel };
-            float ymag{ defaults::FloatSentinel };
-            float zfar{ -defaults::FloatSentinel };
-            float znear{ -defaults::FloatSentinel };
-
-            nlohmann::json extensionsAndExtras{};
-        };
-
-        struct Perspective : NeverEmpty
-        {
-            float aspectRatio{};
-            float yfov{};
-            float zfar{};
-            float znear{};
-
-            nlohmann::json extensionsAndExtras{};
-        };
-
-        std::string name{};
-        Type type{ Type::None };
-
-        Orthographic orthographic;
-        Perspective perspective;
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Image
-    {
-        int32_t bufferView{};
-
-        std::string name;
-        std::string uri;
-        std::string mimeType;
-
-        nlohmann::json extensionsAndExtras{};
-
-        bool IsEmbeddedResource() const noexcept
-        {
-            return uri.find(detail::MimetypeImagePNG) == 0 || uri.find(detail::MimetypeImageJPG) == 0;
-        }
-
-        void MaterializeData(std::vector<uint8_t> & data) const
-        {
-            char const * const mimetype = uri.find(detail::MimetypeImagePNG) == 0 ? detail::MimetypeImagePNG : detail::MimetypeImageJPG;
-            const std::size_t startPos = std::char_traits<char>::length(mimetype) + 1;
-            const std::size_t base64Length = uri.length() - startPos;
-
-#if defined(FX_GLTF_HAS_CPP_17)
-            const bool success = base64::TryDecode({ &uri[startPos], base64Length }, data);
-#else
-            const bool success = base64::TryDecode(uri.substr(startPos), data);
-#endif
-            if (!success)
-            {
-                throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
-            }
-        }
-    };
-
-    struct Material
-    {
-        enum class AlphaMode : uint8_t
-        {
-            Opaque,
-            Mask,
-            Blend
-        };
-
-        struct Texture
-        {
-            int32_t index{ -1 };
-            int32_t texCoord{};
-
-            nlohmann::json extensionsAndExtras{};
-
-            bool empty() const noexcept
-            {
-                return index == -1;
-            }
-        };
-
-        struct NormalTexture : Texture
-        {
-            float scale{ defaults::IdentityScalar };
-        };
-
-        struct OcclusionTexture : Texture
-        {
-            float strength{ defaults::IdentityScalar };
-        };
-
-        struct PBRMetallicRoughness
-        {
-            std::array<float, 4> baseColorFactor = { defaults::IdentityVec4 };
-            Texture baseColorTexture;
-
-            float roughnessFactor{ defaults::IdentityScalar };
-            float metallicFactor{ defaults::IdentityScalar };
-            Texture metallicRoughnessTexture;
-
-            nlohmann::json extensionsAndExtras{};
-
-            bool empty() const
-            {
-                return baseColorTexture.empty() && metallicRoughnessTexture.empty() && metallicFactor == 1.0f && roughnessFactor == 1.0f && baseColorFactor == defaults::IdentityVec4;
-            }
-        };
-
-        float alphaCutoff{ defaults::MaterialAlphaCutoff };
-        AlphaMode alphaMode{ AlphaMode::Opaque };
-
-        bool doubleSided{ defaults::MaterialDoubleSided };
-
-        NormalTexture normalTexture;
-        OcclusionTexture occlusionTexture;
-        PBRMetallicRoughness pbrMetallicRoughness;
-
-        Texture emissiveTexture;
-        std::array<float, 3> emissiveFactor = { defaults::NullVec3 };
-
-        std::string name;
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Primitive
-    {
-        enum class Mode : uint8_t
-        {
-            Points = 0,
-            Lines = 1,
-            LineLoop = 2,
-            LineStrip = 3,
-            Triangles = 4,
-            TriangleStrip = 5,
-            TriangleFan = 6
-        };
-
-        int32_t indices{ -1 };
-        int32_t material{ -1 };
-
-        Mode mode{ Mode::Triangles };
-
-        Attributes attributes{};
-        std::vector<Attributes> targets{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Mesh
-    {
-        std::string name;
-
-        std::vector<float> weights{};
-        std::vector<Primitive> primitives{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Node
-    {
-        std::string name;
-
-        int32_t camera{ -1 };
-        int32_t mesh{ -1 };
-        int32_t skin{ -1 };
-
-        std::array<float, 16> matrix{ defaults::IdentityMatrix };
-        std::array<float, 4> rotation{ defaults::IdentityRotation };
-        std::array<float, 3> scale{ defaults::IdentityVec3 };
-        std::array<float, 3> translation{ defaults::NullVec3 };
-
-        std::vector<int32_t> children{};
-        std::vector<float> weights{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Sampler
-    {
-        enum class MagFilter : uint16_t
-        {
-            None,
-            Nearest = 9728,
-            Linear = 9729
-        };
-
-        enum class MinFilter : uint16_t
-        {
-            None,
-            Nearest = 9728,
-            Linear = 9729,
-            NearestMipMapNearest = 9984,
-            LinearMipMapNearest = 9985,
-            NearestMipMapLinear = 9986,
-            LinearMipMapLinear = 9987
-        };
-
-        enum class WrappingMode : uint16_t
-        {
-            ClampToEdge = 33071,
-            MirroredRepeat = 33648,
-            Repeat = 10497
-        };
-
-        std::string name;
-
-        MagFilter magFilter{ MagFilter::None };
-        MinFilter minFilter{ MinFilter::None };
-
-        WrappingMode wrapS{ WrappingMode::Repeat };
-        WrappingMode wrapT{ WrappingMode::Repeat };
-
-        nlohmann::json extensionsAndExtras{};
-
-        bool empty() const noexcept
-        {
-            return name.empty() && magFilter == MagFilter::None && minFilter == MinFilter::None && wrapS == WrappingMode::Repeat && wrapT == WrappingMode::Repeat && extensionsAndExtras.empty();
-        }
-    };
-
-    struct Scene
-    {
-        std::string name;
-
-        std::vector<uint32_t> nodes{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Skin
-    {
-        int32_t inverseBindMatrices{ -1 };
-        int32_t skeleton{ -1 };
-
-        std::string name;
-        std::vector<uint32_t> joints{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Texture
-    {
-        std::string name;
-
-        int32_t sampler{ -1 };
-        int32_t source{ -1 };
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct Document
-    {
-        Asset asset;
-
-        std::vector<Accessor> accessors{};
-        std::vector<Animation> animations{};
-        std::vector<Buffer> buffers{};
-        std::vector<BufferView> bufferViews{};
-        std::vector<Camera> cameras{};
-        std::vector<Image> images{};
-        std::vector<Material> materials{};
-        std::vector<Mesh> meshes{};
-        std::vector<Node> nodes{};
-        std::vector<Sampler> samplers{};
-        std::vector<Scene> scenes{};
-        std::vector<Skin> skins{};
-        std::vector<Texture> textures{};
-
-        int32_t scene{ -1 };
-        std::vector<std::string> extensionsUsed{};
-        std::vector<std::string> extensionsRequired{};
-
-        nlohmann::json extensionsAndExtras{};
-    };
-
-    struct ReadQuotas
-    {
-        uint32_t MaxBufferCount{ detail::DefaultMaxBufferCount };
-        uint32_t MaxFileSize{ detail::DefaultMaxMemoryAllocation };
-        uint32_t MaxBufferByteLength{ detail::DefaultMaxMemoryAllocation };
-    };
-
-    namespace detail
-    {
-        struct DataContext
-        {
-            std::string bufferRootPath{};
-            ReadQuotas readQuotas;
-
-            std::vector<uint8_t> * binaryData{};
-            std::size_t binaryOffset{};
-        };
-
-        inline std::size_t GetFileSize(std::ifstream & file)
-        {
-            file.seekg(0, file.end);
-            const std::streampos fileSize = file.tellg();
-            file.seekg(0, file.beg);
-
-            if (fileSize < 0)
-            {
-                throw std::system_error(std::make_error_code(std::errc::io_error));
-            }
-
-            return static_cast<std::size_t>(fileSize);
-        }
-
-        inline void MaterializeData(Buffer & buffer)
-        {
-            const std::size_t startPos = std::char_traits<char>::length(detail::MimetypeApplicationOctet) + 1;
-            const std::size_t base64Length = buffer.uri.length() - startPos;
-            const std::size_t decodedEstimate = base64Length / 4 * 3;
-            if ((decodedEstimate - 2) > buffer.byteLength) // we need to give room for padding...
-            {
-                throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
-            }
-
-#if defined(FX_GLTF_HAS_CPP_17)
-            const bool success = base64::TryDecode({ &buffer.uri[startPos], base64Length }, buffer.data);
-#else
-            const bool success = base64::TryDecode(buffer.uri.substr(startPos), buffer.data);
-#endif
-            if (!success)
-            {
-                throw invalid_gltf_document("Invalid buffer.uri value", "malformed base64");
-            }
-        }
-
-        inline Document Create(nlohmann::json const & json, DataContext const & dataContext)
-        {
-            Document document = json;
-
-            if (document.buffers.size() > dataContext.readQuotas.MaxBufferCount)
-            {
-                throw invalid_gltf_document("Quota exceeded : number of buffers > MaxBufferCount");
-            }
-
-            for (auto & buffer : document.buffers)
-            {
-                if (buffer.byteLength == 0)
-                {
-                    throw invalid_gltf_document("Invalid buffer.byteLength value : 0");
-                }
-
-                if (buffer.byteLength > dataContext.readQuotas.MaxBufferByteLength)
-                {
-                    throw invalid_gltf_document("Quota exceeded : buffer.byteLength > MaxBufferByteLength");
-                }
-
-                if (!buffer.uri.empty())
-                {
-                    if (buffer.IsEmbeddedResource())
-                    {
-                        detail::MaterializeData(buffer);
-                    }
-                    else
-                    {
-                        std::ifstream fileData(detail::CreateBufferUriPath(dataContext.bufferRootPath, buffer.uri), std::ios::binary);
-                        if (!fileData.good())
-                        {
-                            throw invalid_gltf_document("Invalid buffer.uri value", buffer.uri);
-                        }
-
-                        buffer.data.resize(buffer.byteLength);
-                        fileData.read(reinterpret_cast<char *>(&buffer.data[0]), buffer.byteLength);
-                    }
-                }
-                else if (dataContext.binaryData != nullptr)
-                {
-                    detail::ChunkHeader header;
-
-                    std::vector<uint8_t> & binary = *dataContext.binaryData;
-                    std::memcpy(&header, &binary[dataContext.binaryOffset], detail::ChunkHeaderSize);
-
-                    if (header.chunkType != detail::GLBChunkBIN || header.chunkLength < buffer.byteLength)
-                    {
-                        throw invalid_gltf_document("Invalid GLB buffer data");
-                    }
-
-                    buffer.data.resize(buffer.byteLength);
-                    std::memcpy(&buffer.data[0], &binary[dataContext.binaryOffset + detail::ChunkHeaderSize], buffer.byteLength);
-                }
-            }
-
-            return document;
-        }
-
-        inline void ValidateBuffers(Document const & document, bool useBinaryFormat)
-        {
-            if (document.buffers.empty())
-            {
-                throw invalid_gltf_document("Invalid glTF document. A document must have at least 1 buffer.");
-            }
-
-            bool foundBinaryBuffer = false;
-            for (std::size_t bufferIndex = 0; bufferIndex < document.buffers.size(); bufferIndex++)
-            {
-                Buffer const & buffer = document.buffers[bufferIndex];
-                if (buffer.byteLength == 0)
-                {
-                    throw invalid_gltf_document("Invalid buffer.byteLength value : 0");
-                }
-
-                if (buffer.byteLength != buffer.data.size())
-                {
-                    throw invalid_gltf_document("Invalid buffer.byteLength value : does not match buffer.data size");
-                }
-
-                if (buffer.uri.empty())
-                {
-                    foundBinaryBuffer = true;
-                    if (bufferIndex != 0)
-                    {
-                        throw invalid_gltf_document("Invalid glTF document. Only 1 buffer, the very first, is allowed to have an empty buffer.uri field.");
-                    }
-                }
-            }
-
-            if (useBinaryFormat && !foundBinaryBuffer)
-            {
-                throw invalid_gltf_document("Invalid glTF document. No buffer found which can meet the criteria for saving to a .glb file.");
-            }
-        }
-
-        inline void Save(Document const & document, std::string documentFilePath, bool useBinaryFormat)
-        {
-            nlohmann::json json = documentFilePath;
-
-            std::size_t externalBufferIndex = 0;
-            if (useBinaryFormat)
-            {
-                detail::GLBHeader header{ detail::GLBHeaderMagic, 2, 0, { 0, detail::GLBChunkJSON } };
-                detail::ChunkHeader binHeader{ 0, detail::GLBChunkBIN };
-
-                std::string jsonText = json.dump();
-
-                Buffer const & binBuffer = document.buffers.front();
-                const uint32_t binPaddedLength = ((binBuffer.byteLength + 3) & (~3u));
-                const uint32_t binPadding = binPaddedLength - binBuffer.byteLength;
-                binHeader.chunkLength = binPaddedLength;
-
-                header.jsonHeader.chunkLength = ((jsonText.length() + 3) & (~3u));
-                const uint32_t headerPadding = static_cast<uint32_t>(header.jsonHeader.chunkLength - jsonText.length());
-                header.length = detail::HeaderSize + header.jsonHeader.chunkLength + detail::ChunkHeaderSize + binHeader.chunkLength;
-
-                std::ofstream fileData(documentFilePath, std::ios::binary);
-                if (!fileData.good())
-                {
-                    throw std::system_error(std::make_error_code(std::errc::io_error));
-                }
-
-                const char spaces[3] = { ' ', ' ', ' ' };
-                const char nulls[3] = { 0, 0, 0 };
-
-                fileData.write(reinterpret_cast<char *>(&header), detail::HeaderSize);
-                fileData.write(jsonText.c_str(), jsonText.length());
-                fileData.write(&spaces[0], headerPadding);
-                fileData.write(reinterpret_cast<char *>(&binHeader), detail::ChunkHeaderSize);
-                fileData.write(reinterpret_cast<char const *>(&binBuffer.data[0]), binBuffer.byteLength);
-                fileData.write(&nulls[0], binPadding);
-
-                externalBufferIndex = 1;
-            }
-            else
-            {
-                std::ofstream file(documentFilePath);
-                if (!file.is_open())
-                {
-                    throw std::system_error(std::make_error_code(std::errc::io_error));
-                }
-
-                file << json.dump(2);
-            }
-
-            // 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
-            // All others must be considered as External/Embedded resources. Process them if necessary...
-            std::string documentRootPath = detail::GetDocumentRootPath(documentFilePath);
-            for (; externalBufferIndex < document.buffers.size(); externalBufferIndex++)
-            {
-                Buffer const & buffer = document.buffers[externalBufferIndex];
-                if (!buffer.IsEmbeddedResource())
-                {
-                    std::ofstream fileData(detail::CreateBufferUriPath(documentRootPath, buffer.uri), std::ios::binary);
-                    if (!fileData.good())
-                    {
-                        throw invalid_gltf_document("Invalid buffer.uri value", buffer.uri);
-                    }
-
-                    fileData.write(reinterpret_cast<char const *>(&buffer.data[0]), buffer.byteLength);
-                }
-            }
-        }
-    } // namespace detail
-
-    inline void from_json(nlohmann::json const & json, Accessor::Type & accessorType)
-    {
-        std::string type = json.get<std::string>();
-        if (type == "SCALAR")
-        {
-            accessorType = Accessor::Type::Scalar;
-        }
-        else if (type == "VEC2")
-        {
-            accessorType = Accessor::Type::Vec2;
-        }
-        else if (type == "VEC3")
-        {
-            accessorType = Accessor::Type::Vec3;
-        }
-        else if (type == "VEC4")
-        {
-            accessorType = Accessor::Type::Vec4;
-        }
-        else if (type == "MAT2")
-        {
-            accessorType = Accessor::Type::Mat2;
-        }
-        else if (type == "MAT3")
-        {
-            accessorType = Accessor::Type::Mat3;
-        }
-        else if (type == "MAT4")
-        {
-            accessorType = Accessor::Type::Mat4;
-        }
-        else
-        {
-            throw invalid_gltf_document("Unknown accessor.type value", type);
-        }
-    }
-
-    inline void from_json(nlohmann::json const & json, Accessor::Sparse::Values & values)
-    {
-        detail::ReadRequiredField("bufferView", json, values.bufferView);
-
-        detail::ReadOptionalField("byteOffset", json, values.byteOffset);
-
-        detail::ReadExtensionsAndExtras(json, values.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Accessor::Sparse::Indices & indices)
-    {
-        detail::ReadRequiredField("bufferView", json, indices.bufferView);
-        detail::ReadRequiredField("componentType", json, indices.componentType);
-
-        detail::ReadOptionalField("byteOffset", json, indices.byteOffset);
-
-        detail::ReadExtensionsAndExtras(json, indices.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Accessor::Sparse & sparse)
-    {
-        detail::ReadRequiredField("count", json, sparse.count);
-        detail::ReadRequiredField("indices", json, sparse.indices);
-        detail::ReadRequiredField("values", json, sparse.values);
-
-        detail::ReadExtensionsAndExtras(json, sparse.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Accessor & accessor)
-    {
-        detail::ReadRequiredField("componentType", json, accessor.componentType);
-        detail::ReadRequiredField("count", json, accessor.count);
-        detail::ReadRequiredField("type", json, accessor.type);
-
-        detail::ReadOptionalField("bufferView", json, accessor.bufferView);
-        detail::ReadOptionalField("byteOffset", json, accessor.byteOffset);
-        detail::ReadOptionalField("max", json, accessor.max);
-        detail::ReadOptionalField("min", json, accessor.min);
-        detail::ReadOptionalField("name", json, accessor.name);
-        detail::ReadOptionalField("normalized", json, accessor.normalized);
-        detail::ReadOptionalField("sparse", json, accessor.sparse);
-
-        detail::ReadExtensionsAndExtras(json, accessor.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Animation::Channel::Target & animationChannelTarget)
-    {
-        detail::ReadRequiredField("path", json, animationChannelTarget.path);
-
-        detail::ReadOptionalField("node", json, animationChannelTarget.node);
-
-        detail::ReadExtensionsAndExtras(json, animationChannelTarget.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Animation::Channel & animationChannel)
-    {
-        detail::ReadRequiredField("sampler", json, animationChannel.sampler);
-        detail::ReadRequiredField("target", json, animationChannel.target);
-
-        detail::ReadExtensionsAndExtras(json, animationChannel.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Animation::Sampler::Type & animationSamplerType)
-    {
-        std::string type = json.get<std::string>();
-        if (type == "LINEAR")
-        {
-            animationSamplerType = Animation::Sampler::Type::Linear;
-        }
-        else if (type == "STEP")
-        {
-            animationSamplerType = Animation::Sampler::Type::Step;
-        }
-        else if (type == "CUBICSPLINE")
-        {
-            animationSamplerType = Animation::Sampler::Type::CubicSpline;
-        }
-        else
-        {
-            throw invalid_gltf_document("Unknown animation.sampler.interpolation value", type);
-        }
-    }
-
-    inline void from_json(nlohmann::json const & json, Animation::Sampler & animationSampler)
-    {
-        detail::ReadRequiredField("input", json, animationSampler.input);
-        detail::ReadRequiredField("output", json, animationSampler.output);
-
-        detail::ReadOptionalField("interpolation", json, animationSampler.interpolation);
-
-        detail::ReadExtensionsAndExtras(json, animationSampler.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Animation & animation)
-    {
-        detail::ReadRequiredField("channels", json, animation.channels);
-        detail::ReadRequiredField("samplers", json, animation.samplers);
-
-        detail::ReadOptionalField("name", json, animation.name);
-
-        detail::ReadExtensionsAndExtras(json, animation.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Asset & asset)
-    {
-        detail::ReadRequiredField("version", json, asset.version);
-        detail::ReadOptionalField("copyright", json, asset.copyright);
-        detail::ReadOptionalField("generator", json, asset.generator);
-        detail::ReadOptionalField("minVersion", json, asset.minVersion);
-
-        detail::ReadExtensionsAndExtras(json, asset.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Buffer & buffer)
-    {
-        detail::ReadRequiredField("byteLength", json, buffer.byteLength);
-
-        detail::ReadOptionalField("name", json, buffer.name);
-        detail::ReadOptionalField("uri", json, buffer.uri);
-
-        detail::ReadExtensionsAndExtras(json, buffer.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, BufferView & bufferView)
-    {
-        detail::ReadRequiredField("buffer", json, bufferView.buffer);
-        detail::ReadRequiredField("byteLength", json, bufferView.byteLength);
-
-        detail::ReadOptionalField("byteOffset", json, bufferView.byteOffset);
-        detail::ReadOptionalField("byteStride", json, bufferView.byteStride);
-        detail::ReadOptionalField("name", json, bufferView.name);
-        detail::ReadOptionalField("target", json, bufferView.target);
-
-        detail::ReadExtensionsAndExtras(json, bufferView.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Camera::Type & cameraType)
-    {
-        std::string type = json.get<std::string>();
-        if (type == "orthographic")
-        {
-            cameraType = Camera::Type::Orthographic;
-        }
-        else if (type == "perspective")
-        {
-            cameraType = Camera::Type::Perspective;
-        }
-        else
-        {
-            throw invalid_gltf_document("Unknown camera.type value", type);
-        }
-    }
-
-    inline void from_json(nlohmann::json const & json, Camera::Orthographic & camera)
-    {
-        detail::ReadRequiredField("xmag", json, camera.xmag);
-        detail::ReadRequiredField("ymag", json, camera.ymag);
-        detail::ReadRequiredField("zfar", json, camera.zfar);
-        detail::ReadRequiredField("znear", json, camera.znear);
-
-        detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Camera::Perspective & camera)
-    {
-        detail::ReadRequiredField("yfov", json, camera.yfov);
-        detail::ReadRequiredField("znear", json, camera.znear);
-
-        detail::ReadOptionalField("aspectRatio", json, camera.aspectRatio);
-        detail::ReadOptionalField("zfar", json, camera.zfar);
-
-        detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Camera & camera)
-    {
-        detail::ReadRequiredField("type", json, camera.type);
-
-        detail::ReadOptionalField("name", json, camera.name);
-
-        detail::ReadExtensionsAndExtras(json, camera.extensionsAndExtras);
-
-        if (camera.type == Camera::Type::Perspective)
-        {
-            detail::ReadRequiredField("perspective", json, camera.perspective);
-        }
-        else if (camera.type == Camera::Type::Orthographic)
-        {
-            detail::ReadRequiredField("orthographic", json, camera.orthographic);
-        }
-    }
-
-    inline void from_json(nlohmann::json const & json, Image & image)
-    {
-        detail::ReadOptionalField("bufferView", json, image.bufferView);
-        detail::ReadOptionalField("mimeType", json, image.mimeType);
-        detail::ReadOptionalField("name", json, image.name);
-        detail::ReadOptionalField("uri", json, image.uri);
-
-        detail::ReadExtensionsAndExtras(json, image.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Material::AlphaMode & materialAlphaMode)
-    {
-        std::string alphaMode = json.get<std::string>();
-        if (alphaMode == "OPAQUE")
-        {
-            materialAlphaMode = Material::AlphaMode::Opaque;
-        }
-        else if (alphaMode == "MASK")
-        {
-            materialAlphaMode = Material::AlphaMode::Mask;
-        }
-        else if (alphaMode == "BLEND")
-        {
-            materialAlphaMode = Material::AlphaMode::Blend;
-        }
-        else
-        {
-            throw invalid_gltf_document("Unknown material.alphaMode value", alphaMode);
-        }
-    }
-
-    inline void from_json(nlohmann::json const & json, Material::Texture & materialTexture)
-    {
-        detail::ReadRequiredField("index", json, materialTexture.index);
-        detail::ReadOptionalField("texCoord", json, materialTexture.texCoord);
-
-        detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Material::NormalTexture & materialTexture)
-    {
-        from_json(json, static_cast<Material::Texture &>(materialTexture));
-        detail::ReadOptionalField("scale", json, materialTexture.scale);
-
-        detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Material::OcclusionTexture & materialTexture)
-    {
-        from_json(json, static_cast<Material::Texture &>(materialTexture));
-        detail::ReadOptionalField("strength", json, materialTexture.strength);
-
-        detail::ReadExtensionsAndExtras(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Material::PBRMetallicRoughness & pbrMetallicRoughness)
-    {
-        detail::ReadOptionalField("baseColorFactor", json, pbrMetallicRoughness.baseColorFactor);
-        detail::ReadOptionalField("baseColorTexture", json, pbrMetallicRoughness.baseColorTexture);
-        detail::ReadOptionalField("metallicFactor", json, pbrMetallicRoughness.metallicFactor);
-        detail::ReadOptionalField("metallicRoughnessTexture", json, pbrMetallicRoughness.metallicRoughnessTexture);
-        detail::ReadOptionalField("roughnessFactor", json, pbrMetallicRoughness.roughnessFactor);
-
-        detail::ReadExtensionsAndExtras(json, pbrMetallicRoughness.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Material & material)
-    {
-        detail::ReadOptionalField("alphaMode", json, material.alphaMode);
-        detail::ReadOptionalField("alphaCutoff", json, material.alphaCutoff);
-        detail::ReadOptionalField("doubleSided", json, material.doubleSided);
-        detail::ReadOptionalField("emissiveFactor", json, material.emissiveFactor);
-        detail::ReadOptionalField("emissiveTexture", json, material.emissiveTexture);
-        detail::ReadOptionalField("name", json, material.name);
-        detail::ReadOptionalField("normalTexture", json, material.normalTexture);
-        detail::ReadOptionalField("occlusionTexture", json, material.occlusionTexture);
-        detail::ReadOptionalField("pbrMetallicRoughness", json, material.pbrMetallicRoughness);
-
-        detail::ReadExtensionsAndExtras(json, material.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Mesh & mesh)
-    {
-        detail::ReadRequiredField("primitives", json, mesh.primitives);
-
-        detail::ReadOptionalField("name", json, mesh.name);
-        detail::ReadOptionalField("weights", json, mesh.weights);
-
-        detail::ReadExtensionsAndExtras(json, mesh.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Node & node)
-    {
-        detail::ReadOptionalField("camera", json, node.camera);
-        detail::ReadOptionalField("children", json, node.children);
-        detail::ReadOptionalField("matrix", json, node.matrix);
-        detail::ReadOptionalField("mesh", json, node.mesh);
-        detail::ReadOptionalField("name", json, node.name);
-        detail::ReadOptionalField("rotation", json, node.rotation);
-        detail::ReadOptionalField("scale", json, node.scale);
-        detail::ReadOptionalField("skin", json, node.skin);
-        detail::ReadOptionalField("translation", json, node.translation);
-
-        detail::ReadExtensionsAndExtras(json, node.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Primitive & primitive)
-    {
-        detail::ReadRequiredField("attributes", json, primitive.attributes);
-
-        detail::ReadOptionalField("indices", json, primitive.indices);
-        detail::ReadOptionalField("material", json, primitive.material);
-        detail::ReadOptionalField("mode", json, primitive.mode);
-        detail::ReadOptionalField("targets", json, primitive.targets);
-
-        detail::ReadExtensionsAndExtras(json, primitive.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Sampler & sampler)
-    {
-        detail::ReadOptionalField("magFilter", json, sampler.magFilter);
-        detail::ReadOptionalField("minFilter", json, sampler.minFilter);
-        detail::ReadOptionalField("name", json, sampler.name);
-        detail::ReadOptionalField("wrapS", json, sampler.wrapS);
-        detail::ReadOptionalField("wrapT", json, sampler.wrapT);
-
-        detail::ReadExtensionsAndExtras(json, sampler.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Scene & scene)
-    {
-        detail::ReadOptionalField("name", json, scene.name);
-        detail::ReadOptionalField("nodes", json, scene.nodes);
-
-        detail::ReadExtensionsAndExtras(json, scene.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Skin & skin)
-    {
-        detail::ReadRequiredField("joints", json, skin.joints);
-
-        detail::ReadOptionalField("inverseBindMatrices", json, skin.inverseBindMatrices);
-        detail::ReadOptionalField("name", json, skin.name);
-        detail::ReadOptionalField("skeleton", json, skin.skeleton);
-
-        detail::ReadExtensionsAndExtras(json, skin.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Texture & texture)
-    {
-        detail::ReadOptionalField("name", json, texture.name);
-        detail::ReadOptionalField("sampler", json, texture.sampler);
-        detail::ReadOptionalField("source", json, texture.source);
-
-        detail::ReadExtensionsAndExtras(json, texture.extensionsAndExtras);
-    }
-
-    inline void from_json(nlohmann::json const & json, Document & document)
-    {
-        detail::ReadRequiredField("asset", json, document.asset);
-
-        detail::ReadOptionalField("accessors", json, document.accessors);
-        detail::ReadOptionalField("animations", json, document.animations);
-        detail::ReadOptionalField("buffers", json, document.buffers);
-        detail::ReadOptionalField("bufferViews", json, document.bufferViews);
-        detail::ReadOptionalField("cameras", json, document.cameras);
-        detail::ReadOptionalField("materials", json, document.materials);
-        detail::ReadOptionalField("meshes", json, document.meshes);
-        detail::ReadOptionalField("nodes", json, document.nodes);
-        detail::ReadOptionalField("images", json, document.images);
-        detail::ReadOptionalField("samplers", json, document.samplers);
-        detail::ReadOptionalField("scene", json, document.scene);
-        detail::ReadOptionalField("scenes", json, document.scenes);
-        detail::ReadOptionalField("skins", json, document.skins);
-        detail::ReadOptionalField("textures", json, document.textures);
-
-        detail::ReadOptionalField("extensionsUsed", json, document.extensionsUsed);
-        detail::ReadOptionalField("extensionsRequired", json, document.extensionsRequired);
-        detail::ReadExtensionsAndExtras(json, document.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor::ComponentType const & accessorComponentType)
-    {
-        if (accessorComponentType == Accessor::ComponentType::None)
-        {
-            throw invalid_gltf_document("Unknown accessor.componentType value");
-        }
-
-        json = static_cast<uint16_t>(accessorComponentType);
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor::Type const & accessorType)
-    {
-        switch (accessorType)
-        {
-        case Accessor::Type::Scalar:
-            json = "SCALAR";
-            break;
-        case Accessor::Type::Vec2:
-            json = "VEC2";
-            break;
-        case Accessor::Type::Vec3:
-            json = "VEC3";
-            break;
-        case Accessor::Type::Vec4:
-            json = "VEC4";
-            break;
-        case Accessor::Type::Mat2:
-            json = "MAT2";
-            break;
-        case Accessor::Type::Mat3:
-            json = "MAT3";
-            break;
-        case Accessor::Type::Mat4:
-            json = "MAT4";
-            break;
-        default:
-            throw invalid_gltf_document("Unknown accessor.type value");
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor::Sparse::Values const & values)
-    {
-        detail::WriteField("bufferView", json, values.bufferView, static_cast<uint32_t>(-1));
-        detail::WriteField("byteOffset", json, values.byteOffset, {});
-        detail::WriteExtensions(json, values.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor::Sparse::Indices const & indices)
-    {
-        detail::WriteField("componentType", json, indices.componentType, Accessor::ComponentType::None);
-        detail::WriteField("bufferView", json, indices.bufferView, static_cast<uint32_t>(-1));
-        detail::WriteField("byteOffset", json, indices.byteOffset, {});
-        detail::WriteExtensions(json, indices.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor::Sparse const & sparse)
-    {
-        detail::WriteField("count", json, sparse.count, -1);
-        detail::WriteField("indices", json, sparse.indices);
-        detail::WriteField("values", json, sparse.values);
-        detail::WriteExtensions(json, sparse.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Accessor const & accessor)
-    {
-        detail::WriteField("bufferView", json, accessor.bufferView, -1);
-        detail::WriteField("byteOffset", json, accessor.byteOffset, {});
-        detail::WriteField("componentType", json, accessor.componentType, Accessor::ComponentType::None);
-        detail::WriteField("count", json, accessor.count, {});
-        detail::WriteField("max", json, accessor.max);
-        detail::WriteField("min", json, accessor.min);
-        detail::WriteField("name", json, accessor.name);
-        detail::WriteField("normalized", json, accessor.normalized, false);
-        detail::WriteField("sparse", json, accessor.sparse);
-        detail::WriteField("type", json, accessor.type, Accessor::Type::None);
-        detail::WriteExtensions(json, accessor.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Animation::Channel::Target const & animationChannelTarget)
-    {
-        detail::WriteField("node", json, animationChannelTarget.node, -1);
-        detail::WriteField("path", json, animationChannelTarget.path);
-        detail::WriteExtensions(json, animationChannelTarget.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Animation::Channel const & animationChannel)
-    {
-        detail::WriteField("sampler", json, animationChannel.sampler, -1);
-        detail::WriteField("target", json, animationChannel.target);
-        detail::WriteExtensions(json, animationChannel.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Animation::Sampler::Type const & animationSamplerType)
-    {
-        switch (animationSamplerType)
-        {
-        case Animation::Sampler::Type::Linear:
-            json = "LINEAR";
-            break;
-        case Animation::Sampler::Type::Step:
-            json = "STEP";
-            break;
-        case Animation::Sampler::Type::CubicSpline:
-            json = "CUBICSPLINE";
-            break;
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Animation::Sampler const & animationSampler)
-    {
-        detail::WriteField("input", json, animationSampler.input, -1);
-        detail::WriteField("interpolation", json, animationSampler.interpolation, Animation::Sampler::Type::Linear);
-        detail::WriteField("output", json, animationSampler.output, -1);
-        detail::WriteExtensions(json, animationSampler.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Animation const & animation)
-    {
-        detail::WriteField("channels", json, animation.channels);
-        detail::WriteField("name", json, animation.name);
-        detail::WriteField("samplers", json, animation.samplers);
-        detail::WriteExtensions(json, animation.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Asset const & asset)
-    {
-        detail::WriteField("copyright", json, asset.copyright);
-        detail::WriteField("generator", json, asset.generator);
-        detail::WriteField("minVersion", json, asset.minVersion);
-        detail::WriteField("version", json, asset.version);
-        detail::WriteExtensions(json, asset.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Buffer const & buffer)
-    {
-        detail::WriteField("byteLength", json, buffer.byteLength, {});
-        detail::WriteField("name", json, buffer.name);
-        detail::WriteField("uri", json, buffer.uri);
-        detail::WriteExtensions(json, buffer.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, BufferView const & bufferView)
-    {
-        detail::WriteField("buffer", json, bufferView.buffer, -1);
-        detail::WriteField("byteLength", json, bufferView.byteLength, {});
-        detail::WriteField("byteOffset", json, bufferView.byteOffset, {});
-        detail::WriteField("byteStride", json, bufferView.byteStride, {});
-        detail::WriteField("name", json, bufferView.name);
-        detail::WriteField("target", json, bufferView.target, BufferView::TargetType::None);
-        detail::WriteExtensions(json, bufferView.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Camera::Type const & cameraType)
-    {
-        switch (cameraType)
-        {
-        case Camera::Type::Orthographic:
-            json = "orthographic";
-            break;
-        case Camera::Type::Perspective:
-            json = "perspective";
-            break;
-        default:
-            throw invalid_gltf_document("Unknown camera.type value");
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Camera::Orthographic const & camera)
-    {
-        detail::WriteField("xmag", json, camera.xmag, defaults::FloatSentinel);
-        detail::WriteField("ymag", json, camera.ymag, defaults::FloatSentinel);
-        detail::WriteField("zfar", json, camera.zfar, -defaults::FloatSentinel);
-        detail::WriteField("znear", json, camera.znear, -defaults::FloatSentinel);
-        detail::WriteExtensions(json, camera.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Camera::Perspective const & camera)
-    {
-        detail::WriteField("aspectRatio", json, camera.aspectRatio, {});
-        detail::WriteField("yfov", json, camera.yfov, {});
-        detail::WriteField("zfar", json, camera.zfar, {});
-        detail::WriteField("znear", json, camera.znear, {});
-        detail::WriteExtensions(json, camera.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Camera const & camera)
-    {
-        detail::WriteField("name", json, camera.name);
-        detail::WriteField("type", json, camera.type, Camera::Type::None);
-        detail::WriteExtensions(json, camera.extensionsAndExtras);
-
-        if (camera.type == Camera::Type::Perspective)
-        {
-            detail::WriteField("perspective", json, camera.perspective);
-        }
-        else if (camera.type == Camera::Type::Orthographic)
-        {
-            detail::WriteField("orthographic", json, camera.orthographic);
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Image const & image)
-    {
-        detail::WriteField("bufferView", json, image.bufferView, image.uri.empty() ? -1 : 0); // bufferView or uri need to be written; even if default 0
-        detail::WriteField("mimeType", json, image.mimeType);
-        detail::WriteField("name", json, image.name);
-        detail::WriteField("uri", json, image.uri);
-        detail::WriteExtensions(json, image.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Material::AlphaMode const & materialAlphaMode)
-    {
-        switch (materialAlphaMode)
-        {
-        case Material::AlphaMode::Opaque:
-            json = "OPAQUE";
-            break;
-        case Material::AlphaMode::Mask:
-            json = "MASK";
-            break;
-        case Material::AlphaMode::Blend:
-            json = "BLEND";
-            break;
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Material::Texture const & materialTexture)
-    {
-        detail::WriteField("index", json, materialTexture.index, -1);
-        detail::WriteField("texCoord", json, materialTexture.texCoord, 0);
-        detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Material::NormalTexture const & materialTexture)
-    {
-        to_json(json, static_cast<Material::Texture const &>(materialTexture));
-        detail::WriteField("scale", json, materialTexture.scale, defaults::IdentityScalar);
-        detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Material::OcclusionTexture const & materialTexture)
-    {
-        to_json(json, static_cast<Material::Texture const &>(materialTexture));
-        detail::WriteField("strength", json, materialTexture.strength, defaults::IdentityScalar);
-        detail::WriteExtensions(json, materialTexture.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Material::PBRMetallicRoughness const & pbrMetallicRoughness)
-    {
-        detail::WriteField("baseColorFactor", json, pbrMetallicRoughness.baseColorFactor, defaults::IdentityVec4);
-        detail::WriteField("baseColorTexture", json, pbrMetallicRoughness.baseColorTexture);
-        detail::WriteField("metallicFactor", json, pbrMetallicRoughness.metallicFactor, defaults::IdentityScalar);
-        detail::WriteField("metallicRoughnessTexture", json, pbrMetallicRoughness.metallicRoughnessTexture);
-        detail::WriteField("roughnessFactor", json, pbrMetallicRoughness.roughnessFactor, defaults::IdentityScalar);
-        detail::WriteExtensions(json, pbrMetallicRoughness.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Material const & material)
-    {
-        detail::WriteField("alphaCutoff", json, material.alphaCutoff, defaults::MaterialAlphaCutoff);
-        detail::WriteField("alphaMode", json, material.alphaMode, Material::AlphaMode::Opaque);
-        detail::WriteField("doubleSided", json, material.doubleSided, defaults::MaterialDoubleSided);
-        detail::WriteField("emissiveTexture", json, material.emissiveTexture);
-        detail::WriteField("emissiveFactor", json, material.emissiveFactor, defaults::NullVec3);
-        detail::WriteField("name", json, material.name);
-        detail::WriteField("normalTexture", json, material.normalTexture);
-        detail::WriteField("occlusionTexture", json, material.occlusionTexture);
-        detail::WriteField("pbrMetallicRoughness", json, material.pbrMetallicRoughness);
-
-        detail::WriteExtensions(json, material.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Mesh const & mesh)
-    {
-        detail::WriteField("name", json, mesh.name);
-        detail::WriteField("primitives", json, mesh.primitives);
-        detail::WriteField("weights", json, mesh.weights);
-        detail::WriteExtensions(json, mesh.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Node const & node)
-    {
-        detail::WriteField("camera", json, node.camera, -1);
-        detail::WriteField("children", json, node.children);
-        detail::WriteField("matrix", json, node.matrix, defaults::IdentityMatrix);
-        detail::WriteField("mesh", json, node.mesh, -1);
-        detail::WriteField("name", json, node.name);
-        detail::WriteField("rotation", json, node.rotation, defaults::IdentityRotation);
-        detail::WriteField("scale", json, node.scale, defaults::IdentityVec3);
-        detail::WriteField("skin", json, node.skin, -1);
-        detail::WriteField("translation", json, node.translation, defaults::NullVec3);
-        detail::WriteField("weights", json, node.weights);
-        detail::WriteExtensions(json, node.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Primitive const & primitive)
-    {
-        detail::WriteField("attributes", json, primitive.attributes);
-        detail::WriteField("indices", json, primitive.indices, -1);
-        detail::WriteField("material", json, primitive.material, -1);
-        detail::WriteField("mode", json, primitive.mode, Primitive::Mode::Triangles);
-        detail::WriteField("targets", json, primitive.targets);
-        detail::WriteExtensions(json, primitive.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Sampler const & sampler)
-    {
-        if (!sampler.empty())
-        {
-            detail::WriteField("name", json, sampler.name);
-            detail::WriteField("magFilter", json, sampler.magFilter, Sampler::MagFilter::None);
-            detail::WriteField("minFilter", json, sampler.minFilter, Sampler::MinFilter::None);
-            detail::WriteField("wrapS", json, sampler.wrapS, Sampler::WrappingMode::Repeat);
-            detail::WriteField("wrapT", json, sampler.wrapT, Sampler::WrappingMode::Repeat);
-            detail::WriteExtensions(json, sampler.extensionsAndExtras);
-        }
-        else
-        {
-            // If a sampler is completely empty we still need to write out an empty object for the encompassing array...
-            json = nlohmann::json::object();
-        }
-    }
-
-    inline void to_json(nlohmann::json & json, Scene const & scene)
-    {
-        detail::WriteField("name", json, scene.name);
-        detail::WriteField("nodes", json, scene.nodes);
-        detail::WriteExtensions(json, scene.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Skin const & skin)
-    {
-        detail::WriteField("inverseBindMatrices", json, skin.inverseBindMatrices, -1);
-        detail::WriteField("name", json, skin.name);
-        detail::WriteField("skeleton", json, skin.skeleton, -1);
-        detail::WriteField("joints", json, skin.joints);
-        detail::WriteExtensions(json, skin.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Texture const & texture)
-    {
-        detail::WriteField("name", json, texture.name);
-        detail::WriteField("sampler", json, texture.sampler, -1);
-        detail::WriteField("source", json, texture.source, -1);
-        detail::WriteExtensions(json, texture.extensionsAndExtras);
-    }
-
-    inline void to_json(nlohmann::json & json, Document const & document)
-    {
-        detail::WriteField("accessors", json, document.accessors);
-        detail::WriteField("animations", json, document.animations);
-        detail::WriteField("asset", json, document.asset);
-        detail::WriteField("buffers", json, document.buffers);
-        detail::WriteField("bufferViews", json, document.bufferViews);
-        detail::WriteField("cameras", json, document.cameras);
-        detail::WriteField("images", json, document.images);
-        detail::WriteField("materials", json, document.materials);
-        detail::WriteField("meshes", json, document.meshes);
-        detail::WriteField("nodes", json, document.nodes);
-        detail::WriteField("samplers", json, document.samplers);
-        detail::WriteField("scene", json, document.scene, -1);
-        detail::WriteField("scenes", json, document.scenes);
-        detail::WriteField("skins", json, document.skins);
-        detail::WriteField("textures", json, document.textures);
-
-        detail::WriteField("extensionsUsed", json, document.extensionsUsed);
-        detail::WriteField("extensionsRequired", json, document.extensionsRequired);
-        detail::WriteExtensions(json, document.extensionsAndExtras);
-    }
-
-    inline Document LoadFromText(std::string const & documentFilePath, ReadQuotas const & readQuotas = {})
-    {
-        try
-        {
-            nlohmann::json json;
-            {
-                std::ifstream file(documentFilePath);
-                if (!file.is_open())
-                {
-                    throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory));
-                }
-
-                file >> json;
-            }
-
-            return detail::Create(json, { detail::GetDocumentRootPath(documentFilePath), readQuotas });
-        }
-        catch (invalid_gltf_document &)
-        {
-            throw;
-        }
-        catch (std::system_error &)
-        {
-            throw;
-        }
-        catch (...)
-        {
-            std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
-        }
-    }
-
-    inline Document LoadFromBinary(std::string const & documentFilePath, ReadQuotas const & readQuotas = {})
-    {
-        try
-        {
-            std::vector<uint8_t> binary{};
-            {
-                std::ifstream file(documentFilePath, std::ios::binary);
-                if (!file.is_open())
-                {
-                    throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory));
-                }
-
-                const std::size_t fileSize = detail::GetFileSize(file);
-                if (fileSize < detail::HeaderSize)
-                {
-                    throw invalid_gltf_document("Invalid GLB file");
-                }
-
-                if (fileSize > readQuotas.MaxFileSize)
-                {
-                    throw invalid_gltf_document("Quota exceeded : file size > MaxFileSize");
-                }
-
-                binary.resize(fileSize);
-                file.read(reinterpret_cast<char *>(&binary[0]), fileSize);
-            }
-
-            detail::GLBHeader header;
-            std::memcpy(&header, &binary[0], detail::HeaderSize);
-            if (header.magic != detail::GLBHeaderMagic ||
-                header.jsonHeader.chunkType != detail::GLBChunkJSON ||
-                header.jsonHeader.chunkLength + detail::HeaderSize > header.length)
-            {
-                throw invalid_gltf_document("Invalid GLB header");
-            }
-
-            return detail::Create(
-                nlohmann::json::parse({ &binary[detail::HeaderSize], header.jsonHeader.chunkLength }),
-                { detail::GetDocumentRootPath(documentFilePath), readQuotas, &binary, header.jsonHeader.chunkLength + detail::HeaderSize });
-        }
-        catch (invalid_gltf_document &)
-        {
-            throw;
-        }
-        catch (std::system_error &)
-        {
-            throw;
-        }
-        catch (...)
-        {
-            std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
-        }
-    }
-
-    inline void Save(Document const & document, std::string documentFilePath, bool useBinaryFormat)
-    {
-        try
-        {
-            detail::ValidateBuffers(document, useBinaryFormat);
-
-            detail::Save(document, documentFilePath, useBinaryFormat);
-        }
-        catch (invalid_gltf_document &)
-        {
-            throw;
-        }
-        catch (std::system_error &)
-        {
-            throw;
-        }
-        catch (...)
-        {
-            std::throw_with_nested(invalid_gltf_document("Invalid glTF document. See nested exception for details."));
-        }
-    }
-} // namespace gltf
-
-// A general-purpose utility to format an exception hierarchy into a string for output
-inline void FormatException(std::string & output, std::exception const & ex, int level = 0)
-{
-    output.append(std::string(level, ' ')).append(ex.what());
-    try
-    {
-        std::rethrow_if_nested(ex);
-    }
-    catch (std::exception const & e)
-    {
-        FormatException(output.append("\n"), e, level + 2);
-    }
-}
-
-} // namespace fx
-
-#undef FX_GLTF_HAS_CPP_17