diff --git a/CMakeLists.txt b/CMakeLists.txt index 438114a02..e22f67a36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,9 @@ if (FASTGLTF_DOWNLOAD_SIMDJSON) target_include_directories(fastgltf_simdjson PUBLIC $ $) compiler_flags(fastgltf_simdjson) enable_debug_inlining(fastgltf_simdjson) + if (SIMDJSON_VERBOSE_LOGGING) + target_compile_definitions(fastgltf_simdjson PUBLIC SIMDJSON_VERBOSE_LOGGING=1) + endif() install( FILES deps/simdjson/simdjson.h diff --git a/examples/gl_viewer/gl_viewer.cpp b/examples/gl_viewer/gl_viewer.cpp index ad710600d..531b0010c 100644 --- a/examples/gl_viewer/gl_viewer.cpp +++ b/examples/gl_viewer/gl_viewer.cpp @@ -286,7 +286,8 @@ bool loadGltf(Viewer* viewer, std::string_view cPath) { constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers; fastgltf::GltfDataBuffer data; - data.loadFromFile(path); + if (!data.loadFromFile(path)) + return false; auto type = fastgltf::determineGltfFileType(&data); if (type == fastgltf::GltfType::glTF) { diff --git a/src/fastgltf.cpp b/src/fastgltf.cpp index 590e595a5..cbbbc2129 100644 --- a/src/fastgltf.cpp +++ b/src/fastgltf.cpp @@ -64,13 +64,17 @@ namespace fastgltf { constexpr std::string_view mimeTypeOctetStream = "application/octet-stream"; struct ParserData { - simdjson::dom::document doc; - simdjson::dom::object root; + simdjson::ondemand::document doc; + simdjson::ondemand::object root; ParserInternalConfig config; + BufferMapCallback* mapCallback = nullptr; + BufferUnmapCallback* unmapCallback = nullptr; + void* userPointer = nullptr; + explicit ParserData() = default; - explicit ParserData(const ParserData& other) = delete; - ParserData& operator=(const ParserData& other) = delete; + ParserData(const ParserData&) = delete; + ParserData& operator=(ParserData&) = delete; }; constexpr uint32_t binaryGltfHeaderMagic = 0x46546C67; // ASCII for "glTF". @@ -89,16 +93,16 @@ namespace fastgltf { uint32_t chunkType; }; - [[nodiscard, gnu::always_inline]] inline std::tuple getImageIndexForExtension(simdjson::dom::object& object, std::string_view extension); - [[nodiscard, gnu::always_inline]] inline bool parseTextureExtensions(Texture& texture, simdjson::dom::object& extensions, Extensions extensionFlags); + [[nodiscard, gnu::always_inline]] inline std::tuple getImageIndexForExtension(simdjson::ondemand::object& object, std::string_view extension); + [[nodiscard, gnu::always_inline]] inline std::optional parseTextureExtensions(simdjson::ondemand::object& extensions, Extensions extensionFlags); - [[nodiscard, gnu::always_inline]] inline Error getJsonArray(simdjson::dom::object& parent, std::string_view arrayName, simdjson::dom::array* array) noexcept; + [[nodiscard, gnu::always_inline]] inline Error getJsonArray(simdjson::ondemand::object& parent, std::string_view arrayName, simdjson::ondemand::array* array) noexcept; } -std::tuple fg::getImageIndexForExtension(simdjson::dom::object& object, std::string_view extension) { +std::tuple fg::getImageIndexForExtension(simdjson::ondemand::object& object, std::string_view extension) { using namespace simdjson; - dom::object sourceExtensionObject; + ondemand::object sourceExtensionObject; if (object[extension].get_object().get(sourceExtensionObject) != SUCCESS) { return std::make_tuple(false, true, 0U); } @@ -111,7 +115,7 @@ std::tuple fg::getImageIndexForExtension(simdjson::dom::obje return std::make_tuple(false, false, imageIndex); } -fg::Error fg::getJsonArray(simdjson::dom::object& parent, std::string_view arrayName, simdjson::dom::array* array) noexcept { +fg::Error fg::getJsonArray(simdjson::ondemand::object& parent, std::string_view arrayName, simdjson::ondemand::array* array) noexcept { using namespace simdjson; auto error = parent[arrayName].get_array().get(*array); @@ -120,48 +124,45 @@ fg::Error fg::getJsonArray(simdjson::dom::object& parent, std::string_view array } else if (error == SUCCESS) { return Error::None; } else { - return Error::InvalidJson; + return Error::InvalidJson; // Also for TAPE_ERROR } } -bool fg::parseTextureExtensions(Texture& texture, simdjson::dom::object& extensions, Extensions extensionFlags) { +std::optional fg::parseTextureExtensions(simdjson::ondemand::object& extensions, Extensions extensionFlags) { if (hasBit(extensionFlags, Extensions::KHR_texture_basisu)) { auto [invalidGltf, extensionNotPresent, imageIndex] = getImageIndexForExtension(extensions, extensions::KHR_texture_basisu); if (invalidGltf) { - return false; + return std::nullopt; } if (!extensionNotPresent) { - texture.imageIndex = imageIndex; - return true; + return imageIndex; } } if (hasBit(extensionFlags, Extensions::MSFT_texture_dds)) { auto [invalidGltf, extensionNotPresent, imageIndex] = getImageIndexForExtension(extensions, extensions::MSFT_texture_dds); if (invalidGltf) { - return false; + return std::nullopt; } if (!extensionNotPresent) { - texture.imageIndex = imageIndex; - return true; + return imageIndex; } } if (hasBit(extensionFlags, Extensions::EXT_texture_webp)) { auto [invalidGltf, extensionNotPresent, imageIndex] = getImageIndexForExtension(extensions, extensions::EXT_texture_webp); if (invalidGltf) { - return false; + return std::nullopt; } if (!extensionNotPresent) { - texture.imageIndex = imageIndex; - return true; + return imageIndex; } } - return false; + return std::nullopt; } #pragma region glTF @@ -604,99 +605,83 @@ fg::Error fg::glTF::validate() { fg::Error fg::glTF::parse(Category categories) { using namespace simdjson; + // We always try and parse Asset + if (!hasBit(options, Options::DontRequireValidAssetMember)) + categories |= Category::Asset; fillCategories(categories); - if (!hasBit(options, Options::DontRequireValidAssetMember)) { - dom::object asset; - AssetInfo info = {}; - auto error = data->root["asset"].get_object().get(asset); - if (error == NO_SUCH_FIELD) { - SET_ERROR_RETURN_ERROR(Error::InvalidOrMissingAssetField) - } else if (error != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidJson) - } - - std::string_view version; - if (asset["version"].get_string().get(version) != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidOrMissingAssetField) - } else { - uint32_t major = static_cast(version.substr(0, 1)[0] - '0'); - // uint32_t minor = version.substr(2, 3)[0] - '0'; - if (major != 2) { - SET_ERROR_RETURN_ERROR(Error::UnsupportedVersion) - } - } - info.gltfVersion = std::string { version }; - - std::string_view copyright; - if (asset["copyright"].get_string().get(copyright) == SUCCESS) { - info.copyright = std::string { copyright }; + Category readCategories = Category::None; + for (auto object : data->root) { + // We've read everything the user asked for, we can safely exit the loop. + if (readCategories == categories) { + break; } - std::string_view generator; - if (asset["generator"].get_string().get(generator) == SUCCESS) { - info.generator = std::string { generator }; + std::string_view key; + if (object.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidJson) } + auto hashedKey = crc32(key); - parsedAsset->assetInfo = std::move(info); - } + switch (hashedKey) { + case force_consteval: { + uint64_t defaultScene; + if (object.value().get_uint64().get(defaultScene) != SUCCESS) { + errorCode = Error::InvalidGltf; + } + parsedAsset->defaultScene = static_cast(defaultScene); + continue; + } + case force_consteval: { + ondemand::object extensionsObject; + if (object.value().get_object().get(extensionsObject) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - dom::array extensionsRequired; - if (data->root["extensionsRequired"].get_array().get(extensionsRequired) == SUCCESS) { - for (auto extension : extensionsRequired) { - std::string_view string; - if (extension.get_string().get(string) != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + parseExtensions(extensionsObject); + continue; } + case force_consteval: { + ondemand::object assetObject; + if (object.value().get_object().get(assetObject) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidJson) + } - bool known = false; - for (const auto& [extensionString, extensionEnum] : extensionStrings) { - if (extensionString == string) { - known = true; - if (!hasBit(data->config.extensions, extensionEnum)) { - // The extension is required, but not enabled by the user. - SET_ERROR_RETURN_ERROR(Error::MissingExtensions) + AssetInfo info = {}; + std::string_view version; + if (assetObject["version"].get_string().get(version) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidOrMissingAssetField) + } else { + uint32_t major = static_cast(version.substr(0, 1)[0] - '0'); + // uint32_t minor = version.substr(2, 3)[0] - '0'; + if (major != 2) { + SET_ERROR_RETURN_ERROR(Error::UnsupportedVersion) } - break; } - } - if (!known) { - SET_ERROR_RETURN_ERROR(Error::UnknownRequiredExtension) - } - } - } + info.gltfVersion = std::string { version }; - Category readCategories = Category::None; - for (const auto& object : data->root) { - // We've read everything the user asked for, we can safely exit the loop. - if (readCategories == categories) { - break; - } + std::string_view copyright; + if (assetObject["copyright"].get_string().get(copyright) == SUCCESS) { + info.copyright = std::string { copyright }; + } - auto hashedKey = crc32(object.key); + std::string_view generator; + if (assetObject["generator"].get_string().get(generator) == SUCCESS) { + info.generator = std::string { generator }; + } - if (hashedKey == force_consteval) { - uint64_t defaultScene; - if (object.value.get_uint64().get(defaultScene) != SUCCESS) { - errorCode = Error::InvalidGltf; - } - parsedAsset->defaultScene = static_cast(defaultScene); - continue; - } else if (hashedKey == force_consteval) { - dom::object extensionsObject; - if (object.value.get_object().get(extensionsObject) != SUCCESS) { - errorCode = Error::InvalidGltf; - return errorCode; + parsedAsset->assetInfo = std::move(info); + readCategories |= Category::Asset; + continue; } - - parseExtensions(extensionsObject); - continue; - } else if (hashedKey == force_consteval || hashedKey == force_consteval) { - continue; + case force_consteval: + continue; + default: + break; } - dom::array array; - if (object.value.get_array().get(array) != SUCCESS) { + ondemand::array array; + if (object.value().get_array().get(array) != SUCCESS) { errorCode = Error::InvalidGltf; return errorCode; } @@ -721,6 +706,30 @@ fg::Error fg::glTF::parse(Category categories) { KEY_SWITCH_CASE(Scenes, scenes) KEY_SWITCH_CASE(Skins, skins) KEY_SWITCH_CASE(Textures, textures) + case force_consteval: { + for (auto extension : array) { + std::string_view string; + if (extension.get_string().get(string) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + + bool known = false; + for (const auto& [extensionString, extensionEnum] : extensionStrings) { + if (extensionString == string) { + known = true; + if (!hasBit(data->config.extensions, extensionEnum)) { + // The extension is required, but not enabled by the user. + SET_ERROR_RETURN_ERROR(Error::MissingExtensions) + } + break; + } + } + if (!known) { + SET_ERROR_RETURN_ERROR(Error::UnknownRequiredExtension) + } + } + break; + } default: break; } @@ -728,234 +737,276 @@ fg::Error fg::glTF::parse(Category categories) { #undef KEY_SWITCH_CASE } + if (!hasBit(readCategories, Category::Asset)) { + SET_ERROR_RETURN_ERROR(Error::InvalidOrMissingAssetField) + } + parsedAsset->availableCategories = readCategories; return errorCode; } -void fg::glTF::parseAccessors(simdjson::dom::array& accessors) { +void fg::glTF::parseAccessors(simdjson::ondemand::array accessors) { using namespace simdjson; + // parsedAsset->accessors.reserve(accessors.count_elements()); - parsedAsset->accessors.reserve(accessors.size()); for (auto accessorValue : accessors) { - // Required fields: "componentType", "count" - Accessor accessor = {}; - dom::object accessorObject; + ondemand::object accessorObject; if (accessorValue.get_object().get(accessorObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - uint64_t componentType; - if (accessorObject["componentType"].get_uint64().get(componentType) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else { - accessor.componentType = getComponentType(static_cast>(componentType)); - if (accessor.componentType == ComponentType::Double && !hasBit(options, Options::AllowDouble)) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - } - - std::string_view accessorType; - if (accessorObject["type"].get_string().get(accessorType) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else { - accessor.type = getAccessorType(accessorType); - } - - uint64_t accessorCount; - if (accessorObject["count"].get_uint64().get(accessorCount) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else { - accessor.count = static_cast(accessorCount); - } - - uint64_t bufferView; - if (accessorObject["bufferView"].get_uint64().get(bufferView) == SUCCESS) { - accessor.bufferViewIndex = static_cast(bufferView); - } - - // byteOffset is optional, but defaults to 0 - uint64_t byteOffset; - if (accessorObject["byteOffset"].get_uint64().get(byteOffset) != SUCCESS) { - accessor.byteOffset = 0U; - } else { - accessor.byteOffset = static_cast(byteOffset); - } + Accessor accessor = {}; + accessor.byteOffset = 0UL; + accessor.normalized = false; + for (auto field : accessorObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } + + switch (crc32(key)) { + case force_consteval: + uint64_t bufferView; + if (field.value().get_uint64().get(bufferView) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + accessor.bufferViewIndex = static_cast(bufferView); + break; + case force_consteval: + uint64_t byteOffset; + if (field.value().get_uint64().get(byteOffset) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } else { + accessor.byteOffset = static_cast(byteOffset); + } + break; + case force_consteval: + uint64_t componentType; + if (field.value().get_uint64().get(componentType) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + accessor.componentType = getComponentType( + static_cast>(componentType)); + if (accessor.componentType == ComponentType::Double && !hasBit(options, Options::AllowDouble)) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + case force_consteval: + if (field.value().get_bool().get(accessor.normalized) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + case force_consteval: + uint64_t accessorCount; + if (field.value().get_uint64().get(accessorCount) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + accessor.count = static_cast(accessorCount); + break; + case force_consteval: { + std::string_view accessorType; + if (field.value().get_string().get(accessorType) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + accessor.type = getAccessorType(accessorType); + break; + } + case force_consteval: { + ondemand::object sparseAccessorObject; + if (field.value().get_object().get(sparseAccessorObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (accessorObject["normalized"].get_bool().get(accessor.normalized) != SUCCESS) { - accessor.normalized = false; - } + SparseAccessor sparse = {}; + uint64_t value; + ondemand::object child; + if (sparseAccessorObject["count"].get_uint64().get(value) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sparse.count = static_cast(value); - dom::object sparseAccessorObject; - if (accessorObject["sparse"].get_object().get(sparseAccessorObject) == SUCCESS) { - SparseAccessor sparse = {}; - uint64_t value; - dom::object child; - if (sparseAccessorObject["count"].get_uint64().get(value) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sparse.count = static_cast(value); + // Accessor Sparce Indices + if (sparseAccessorObject["indices"].get_object().get(child) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - // Accessor Sparce Indices - if (sparseAccessorObject["indices"].get_object().get(child) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (child["bufferView"].get_uint64().get(value) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sparse.bufferViewIndices = static_cast(value); - if (child["bufferView"].get_uint64().get(value) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sparse.bufferViewIndices = static_cast(value); + if (child["byteOffset"].get_uint64().get(value) != SUCCESS) { + sparse.byteOffsetIndices = 0; + } else { + sparse.byteOffsetIndices = static_cast(value); + } - if (child["byteOffset"].get_uint64().get(value) != SUCCESS) { - sparse.byteOffsetIndices = 0; - } else { - sparse.byteOffsetIndices = static_cast(value); - } + if (child["componentType"].get_uint64().get(value) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sparse.indexComponentType = getComponentType(static_cast>(value)); - if (child["componentType"].get_uint64().get(value) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sparse.indexComponentType = getComponentType(static_cast>(value)); + // Accessor Sparse Values + if (sparseAccessorObject["values"].get_object().get(child) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - // Accessor Sparse Values - if (sparseAccessorObject["values"].get_object().get(child) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (child["bufferView"].get_uint64().get(value) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sparse.bufferViewValues = static_cast(value); - if (child["bufferView"].get_uint64().get(value) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sparse.bufferViewValues = static_cast(value); + if (child["byteOffset"].get_uint64().get(value) != SUCCESS) { + sparse.byteOffsetValues = 0; + } else { + sparse.byteOffsetValues = static_cast(value); + } - if (child["byteOffset"].get_uint64().get(value) != SUCCESS) { - sparse.byteOffsetValues = 0; - } else { - sparse.byteOffsetValues = static_cast(value); + accessor.sparse = sparse; + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + accessor.name = std::string { name }; + break; + } + default: continue; } - - accessor.sparse = sparse; - } - - // name is optional. - std::string_view name; - if (accessorObject["name"].get_string().get(name) == SUCCESS) { - accessor.name = std::string { name }; } parsedAsset->accessors.emplace_back(std::move(accessor)); } } -void fg::glTF::parseAnimations(simdjson::dom::array& animations) { +void fg::glTF::parseAnimations(simdjson::ondemand::array animations) { using namespace simdjson; + // parsedAsset->animations.reserve(animations.count_elements()); - parsedAsset->animations.reserve(animations.size()); for (auto animationValue : animations) { - dom::object animationObject; Animation animation = {}; + ondemand::object animationObject; if (animationValue.get_object().get(animationObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - dom::array channels; - auto channelError = getJsonArray(animationObject, "channels", &channels); - if (channelError != Error::None) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - - animation.channels.reserve(channels.size()); - for (auto channelValue : channels) { - dom::object channelObject; - AnimationChannel channel = {}; - if (channelValue.get_object().get(channelObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - - uint64_t sampler; - if (channelObject["sampler"].get_uint64().get(sampler) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - channel.samplerIndex = static_cast(sampler); - - dom::object targetObject; - if (channelObject["target"].get_object().get(targetObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else { - uint64_t node; - if (targetObject["node"].get_uint64().get(node) != SUCCESS) { - // We don't support any extensions for animations, so it is required. - SET_ERROR_RETURN(Error::InvalidGltf) - } - channel.nodeIndex = static_cast(node); - - std::string_view path; - if (targetObject["path"].get_string().get(path) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - - if (path == "translation") { - channel.path = AnimationPath::Translation; - } else if (path == "rotation") { - channel.path = AnimationPath::Rotation; - } else if (path == "scale") { - channel.path = AnimationPath::Scale; - } else if (path == "weights") { - channel.path = AnimationPath::Weights; + for (auto field : animationObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } + + switch (crc32(key)) { + case force_consteval: { + ondemand::array channels; + auto result = field.value().get_array().get(channels); + if (result == SUCCESS) { + // animation.channels.reserve(channels.count_elements()); + for (auto channelValue : channels) { + ondemand::object channelObject; + AnimationChannel channel = {}; + if (channelValue.get_object().get(channelObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + uint64_t sampler; + if (channelObject["sampler"].get_uint64().get(sampler) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + channel.samplerIndex = static_cast(sampler); + + ondemand::object targetObject; + if (channelObject["target"].get_object().get(targetObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } else { + uint64_t node; + if (targetObject["node"].get_uint64().get(node) != SUCCESS) { + // We don't support any extensions for animations, so it is required. + SET_ERROR_RETURN(Error::InvalidGltf) + } + channel.nodeIndex = static_cast(node); + + std::string_view path; + if (targetObject["path"].get_string().get(path) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + if (path == "translation") { + channel.path = AnimationPath::Translation; + } else if (path == "rotation") { + channel.path = AnimationPath::Rotation; + } else if (path == "scale") { + channel.path = AnimationPath::Scale; + } else if (path == "weights") { + channel.path = AnimationPath::Weights; + } + } + + animation.channels.emplace_back(channel); + } + } else { + SET_ERROR_RETURN(Error::InvalidJson) + } + break; } - } - - animation.channels.emplace_back(channel); - } + case force_consteval: { + ondemand::array samplers; + auto result = field.value().get_array().get(samplers); + if (result != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } - dom::array samplers; - auto samplerError = getJsonArray(animationObject, "samplers", &samplers); - if (samplerError != Error::None) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + // animation.samplers.reserve(samplers.count_elements()); + for (auto samplerValue : samplers) { + ondemand::object samplerObject; + AnimationSampler sampler = {}; + if (samplerValue.get_object().get(samplerObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - animation.samplers.reserve(samplers.size()); - for (auto samplerValue : samplers) { - dom::object samplerObject; - AnimationSampler sampler = {}; - if (samplerValue.get_object().get(samplerObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + uint64_t input; + if (samplerObject["input"].get_uint64().get(input) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.inputAccessor = static_cast(input); - uint64_t input; - if (samplerObject["input"].get_uint64().get(input) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sampler.inputAccessor = static_cast(input); + uint64_t output; + if (samplerObject["output"].get_uint64().get(output) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.outputAccessor = static_cast(output); - uint64_t output; - if (samplerObject["output"].get_uint64().get(output) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - sampler.outputAccessor = static_cast(output); + std::string_view interpolation; + if (samplerObject["interpolation"].get_string().get(interpolation) != SUCCESS) { + sampler.interpolation = AnimationInterpolation::Linear; + } else { + if (interpolation == "LINEAR") { + sampler.interpolation = AnimationInterpolation::Linear; + } else if (interpolation == "STEP") { + sampler.interpolation = AnimationInterpolation::Step; + } else if (interpolation == "CUBICSPLINE") { + sampler.interpolation = AnimationInterpolation::CubicSpline; + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + } - std::string_view interpolation; - if (samplerObject["interpolation"].get_string().get(interpolation) != SUCCESS) { - sampler.interpolation = AnimationInterpolation::Linear; - } else { - if (interpolation == "LINEAR") { - sampler.interpolation = AnimationInterpolation::Linear; - } else if (interpolation == "STEP") { - sampler.interpolation = AnimationInterpolation::Step; - } else if (interpolation == "CUBICSPLINE") { - sampler.interpolation = AnimationInterpolation::CubicSpline; - } else { - SET_ERROR_RETURN(Error::InvalidGltf) + animation.samplers.emplace_back(sampler); + } + break; } - } - - animation.samplers.emplace_back(sampler); - } - - // name is optional. - { - std::string_view name; - if (animationObject["name"].get_string().get(name) == SUCCESS) { - animation.name = std::string { name }; + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + animation.name = std::string { name }; + break; + } + default: continue; } } @@ -963,40 +1014,68 @@ void fg::glTF::parseAnimations(simdjson::dom::array& animations) { } } -void fg::glTF::parseBuffers(simdjson::dom::array& buffers) { +void fg::glTF::parseBuffers(simdjson::ondemand::array buffers) { using namespace simdjson; + // parsedAsset->buffers.reserve(buffers.count_elements()); - parsedAsset->buffers.reserve(buffers.size()); size_t bufferIndex = 0; for (auto bufferValue : buffers) { - // Required fields: "byteLength" Buffer buffer = {}; - dom::object bufferObject; + ondemand::object bufferObject; if (bufferValue.get_object().get(bufferObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - uint64_t byteLength; - if (bufferObject["byteLength"].get_uint64().get(byteLength) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else { - buffer.byteLength = static_cast(byteLength); + std::string uri; + for (auto field : bufferObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } + + switch (crc32(key)) { + case force_consteval: { + // When parsing GLB, there's a buffer object that will point to the BUF chunk in the + // file. Otherwise, data must be specified in the "uri" field. + std::string_view _uri; + auto error = field.value().get_string().get(_uri); + if (error != SUCCESS || _uri.empty()) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + uri = std::string { _uri }; + break; + } + case force_consteval: { + uint64_t byteLength; + if (field.value().get_uint64().get(byteLength) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } else { + buffer.byteLength = static_cast(byteLength); + } + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + buffer.name = std::string { name }; + break; + } + default: continue; + } } - // When parsing GLB, there's a buffer object that will point to the BUF chunk in the - // file. Otherwise, data must be specified in the "uri" field. - std::string_view uri; - if (bufferObject["uri"].get_string().get(uri) == SUCCESS) { - auto [error, source] = decodeUri(uri); - if (error != Error::None) { - SET_ERROR_RETURN(error) + if (!uri.empty()) { + auto [decodeError, source] = decodeUri(uri); + if (decodeError != Error::None) { + SET_ERROR_RETURN(decodeError) } buffer.data = std::move(source); } else if (bufferIndex == 0 && !std::holds_alternative(glbBuffer)) { buffer.data = std::move(glbBuffer); } else { - // All other buffers have to contain an uri field. SET_ERROR_RETURN(Error::InvalidGltf) } @@ -1004,238 +1083,301 @@ void fg::glTF::parseBuffers(simdjson::dom::array& buffers) { SET_ERROR_RETURN(Error::InvalidGltf) } - // name is optional. - std::string_view name; - if (bufferObject["name"].get_string().get(name) == SUCCESS) { - buffer.name = std::string { name }; - } - ++bufferIndex; parsedAsset->buffers.emplace_back(std::move(buffer)); } } -void fg::glTF::parseBufferViews(simdjson::dom::array& bufferViews) { +fg::Error fg::glTF::parseBufferViewObject(void* viewObject, BufferView* view, Extensions extension) noexcept { using namespace simdjson; + auto& object = *static_cast(viewObject); - parsedAsset->bufferViews.reserve(bufferViews.size()); - for (auto bufferViewValue : bufferViews) { - // Required fields: "bufferIndex", "byteLength" - dom::object bufferViewObject; - if (bufferViewValue.get_object().get(bufferViewObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + view->byteOffset = 0UL; + if (extension == Extensions::EXT_meshopt_compression) { + view->filter = MeshoptCompressionFilter::None; + } + for (auto field : object) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidJson) } - auto parseBufferViewObject = [&views = parsedAsset->bufferViews, this](dom::object& object, bool fromMeshoptCompression) -> fg::Error { - BufferView view = {}; - - uint64_t number; - if (object["buffer"].get_uint64().get(number) != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) - } else { - view.bufferIndex = static_cast(number); - } - - if (object["byteLength"].get_uint64().get(number) != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) - } else { - view.byteLength = static_cast(number); + switch (crc32(key)) { + case force_consteval: { + uint64_t buffer; + if (field.value().get_uint64().get(buffer) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->bufferIndex = static_cast(buffer); + break; } - - // byteOffset is optional, but defaults to 0 - if (object["byteOffset"].get_uint64().get(number) != SUCCESS) { - view.byteOffset = 0; - } else { - view.byteOffset = static_cast(number); + case force_consteval: { + uint64_t byteOffset; + if (field.value().get_uint64().get(byteOffset) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->byteOffset = static_cast(byteOffset); + break; } - - if (object["byteStride"].get_uint64().get(number) == SUCCESS) { - view.byteStride = static_cast(number); - } else if (fromMeshoptCompression) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + case force_consteval: { + uint64_t byteLength; + if (field.value().get_uint64().get(byteLength) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->byteLength = static_cast(byteLength); + break; } - - if (object["count"].get_uint64().get(number) == SUCCESS) { - view.count = number; - } else if (fromMeshoptCompression) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + case force_consteval: { + uint64_t byteStride; + if (field.value().get_uint64().get(byteStride) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->byteStride = static_cast(byteStride); + break; } + case force_consteval: { + if (extension == Extensions::EXT_meshopt_compression) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - // target is optional - if (!fromMeshoptCompression && object["target"].get_uint64().get(number) == SUCCESS) { - view.target = static_cast(number); + uint64_t target; + if (field.value().get_uint64().get(target) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->target = static_cast(target); + break; } + case force_consteval: { + if (extension != Extensions::EXT_meshopt_compression) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - // name is optional. With EXT_meshopt_compression it seems to be entirely omitted, but - // the spec is not fully clear on this. - std::string_view string; - if (object["name"].get_string().get(string) == SUCCESS) { - view.name = std::string { string }; - } + std::string_view mode; + if (field.value().get_string().get(mode) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - if (object["mode"].get_string().get(string) == SUCCESS) { - const auto key = crc32(string); - switch (key) { + const auto modeKey = crc32(mode); + switch (modeKey) { case force_consteval: { - view.mode = MeshoptCompressionMode::Attributes; + view->mode = MeshoptCompressionMode::Attributes; break; } case force_consteval: { - view.mode = MeshoptCompressionMode::Triangles; + view->mode = MeshoptCompressionMode::Triangles; break; } case force_consteval: { - view.mode = MeshoptCompressionMode::Indices; + view->mode = MeshoptCompressionMode::Indices; break; } default: { SET_ERROR_RETURN_ERROR(Error::InvalidGltf) } } - } else if (fromMeshoptCompression) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + break; } + case force_consteval: { + if (extension != Extensions::EXT_meshopt_compression) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + + std::string_view filter; + if (field.value().get_string().get(filter) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - if (object["filter"].get_string().get(string) == SUCCESS) { - const auto key = crc32(string); - switch (key) { + const auto filterKey = crc32(filter); + switch (filterKey) { case force_consteval: { - view.filter = MeshoptCompressionFilter::None; + view->filter = MeshoptCompressionFilter::None; break; } case force_consteval: { - view.filter = MeshoptCompressionFilter::Octahedral; + view->filter = MeshoptCompressionFilter::Octahedral; break; } case force_consteval: { - view.filter = MeshoptCompressionFilter::Quaternion; + view->filter = MeshoptCompressionFilter::Quaternion; break; } case force_consteval: { - view.filter = MeshoptCompressionFilter::Exponential; + view->filter = MeshoptCompressionFilter::Exponential; break; } default: { SET_ERROR_RETURN_ERROR(Error::InvalidGltf) } } - } else if (fromMeshoptCompression) { - view.filter = MeshoptCompressionFilter::None; + break; } + case force_consteval: { + if (extension != Extensions::EXT_meshopt_compression) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - views.emplace_back(std::move(view)); - return errorCode; - }; - - dom::object extensionObject; - if (bufferViewObject["extensions"].get_object().get(extensionObject) == SUCCESS) { - dom::object meshoptCompression; - if (hasBit(data->config.extensions, Extensions::EXT_meshopt_compression) && bufferViewObject[extensions::EXT_meshopt_compression].get_object().get(meshoptCompression) == SUCCESS) { - parseBufferViewObject(meshoptCompression, true); - continue; + uint64_t count; + if (field.value().get_uint64().get(count) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + break; } - } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + view->name = std::string{name}; + break; + } + case force_consteval: { + ondemand::object extensionObject; + if (field.value().get_object().get(extensionObject) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } - parseBufferViewObject(bufferViewObject, false); + ondemand::object meshoptCompression; + if (hasBit(data->config.extensions, Extensions::EXT_meshopt_compression) && extensionObject[extensions::EXT_meshopt_compression].get_object().get(meshoptCompression) == SUCCESS) { + if (auto error = parseBufferViewObject(&meshoptCompression, view, Extensions::EXT_meshopt_compression); error != Error::None) { + return error; + } + continue; + } + } + } } + + return errorCode; } -void fg::glTF::parseCameras(simdjson::dom::array& cameras) { +void fg::glTF::parseBufferViews(simdjson::ondemand::array bufferViews) { using namespace simdjson; + // parsedAsset->bufferViews.reserve(bufferViews.count_elements()); - parsedAsset->cameras.reserve(cameras.size()); - for (auto cameraValue : cameras) { - Camera camera = {}; - dom::object cameraObject; - if (cameraValue.get_object().get(cameraObject) != SUCCESS) { + for (auto bufferViewValue : bufferViews) { + ondemand::object bufferViewObject; + if (bufferViewValue.get_object().get(bufferViewObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - std::string_view name; - if (cameraObject["name"].get_string().get(name) == SUCCESS) { - camera.name = std::string { name }; + auto& view = parsedAsset->bufferViews.emplace_back(); + if (auto error = parseBufferViewObject(&bufferViewObject, &view, Extensions::None); error != Error::None) { + return; } + } +} - std::string_view type; - if (cameraObject["type"].get_string().get(type) != SUCCESS) { +void fg::glTF::parseCameras(simdjson::ondemand::array cameras) { + using namespace simdjson; + // parsedAsset->cameras.reserve(cameras.count_elements()); + + for (auto cameraValue : cameras) { + Camera camera = {}; + ondemand::object cameraObject; + if (cameraValue.get_object().get(cameraObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - if (type == "perspective") { - dom::object perspectiveCamera; - if (cameraObject["perspective"].get_object().get(perspectiveCamera) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + for (auto field : cameraObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) } - Camera::Perspective perspective = {}; - double value; - if (perspectiveCamera["aspectRatio"].get_double().get(value) == SUCCESS) { - perspective.aspectRatio = static_cast(value); - } - if (perspectiveCamera["zfar"].get_double().get(value) == SUCCESS) { - perspective.zfar = static_cast(value); - } + switch (crc32(key)) { + case force_consteval: { + ondemand::object orthographicCamera; + if (cameraObject["orthographic"].get_object().get(orthographicCamera) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (perspectiveCamera["yfov"].get_double().get(value) == SUCCESS) { - perspective.yfov = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + Camera::Orthographic orthographic = {}; + double value; + if (orthographicCamera["xmag"].get_double().get(value) == SUCCESS) { + orthographic.xmag = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (perspectiveCamera["znear"].get_double().get(value) == SUCCESS) { - perspective.znear = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (orthographicCamera["ymag"].get_double().get(value) == SUCCESS) { + orthographic.ymag = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } - camera.camera = perspective; - } else if (type == "orthographic") { - dom::object orthographicCamera; - if (cameraObject["orthographic"].get_object().get(orthographicCamera) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (orthographicCamera["zfar"].get_double().get(value) == SUCCESS) { + orthographic.zfar = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } - Camera::Orthographic orthographic = {}; - double value; - if (orthographicCamera["xmag"].get_double().get(value) == SUCCESS) { - orthographic.xmag = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (orthographicCamera["znear"].get_double().get(value) == SUCCESS) { + orthographic.znear = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (orthographicCamera["ymag"].get_double().get(value) == SUCCESS) { - orthographic.ymag = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + camera.camera = orthographic; + break; + } + case force_consteval: { + ondemand::object perspectiveCamera; + if (cameraObject["perspective"].get_object().get(perspectiveCamera) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (orthographicCamera["zfar"].get_double().get(value) == SUCCESS) { - orthographic.zfar = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + Camera::Perspective perspective = {}; + double value; + if (perspectiveCamera["aspectRatio"].get_double().get(value) == SUCCESS) { + perspective.aspectRatio = static_cast(value); + } + if (perspectiveCamera["zfar"].get_double().get(value) == SUCCESS) { + perspective.zfar = static_cast(value); + } - if (orthographicCamera["znear"].get_double().get(value) == SUCCESS) { - orthographic.znear = static_cast(value); - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } + if (perspectiveCamera["yfov"].get_double().get(value) == SUCCESS) { + perspective.yfov = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } - camera.camera = orthographic; - } else { - SET_ERROR_RETURN(Error::InvalidGltf) + if (perspectiveCamera["znear"].get_double().get(value) == SUCCESS) { + perspective.znear = static_cast(value); + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + camera.camera = perspective; + break; + } + /*case force_consteval: { + if (field.value().get_string().get(cameraType) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf); + } + break; + }*/ + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + camera.name = std::string { name }; + break; + } + default: continue; + } } parsedAsset->cameras.emplace_back(std::move(camera)); } } -void fg::glTF::parseExtensions(simdjson::dom::object& extensionsObject) { +void fg::glTF::parseExtensions(simdjson::ondemand::object& extensionsObject) { using namespace simdjson; for (auto extensionValue : extensionsObject) { - dom::object extensionObject; - if (auto error = extensionValue.value.get_object().get(extensionObject); error != SUCCESS) { + ondemand::object extensionObject; + if (auto error = extensionValue.value().get_object().get(extensionObject); error != SUCCESS) { if (error == INCORRECT_TYPE) { continue; // We want to ignore } else { @@ -1243,13 +1385,13 @@ void fg::glTF::parseExtensions(simdjson::dom::object& extensionsObject) { } } - auto hash = crc32(extensionValue.key); + auto hash = crc32(extensionValue.unescaped_key()); switch (hash) { case force_consteval: { if (!hasBit(data->config.extensions, Extensions::KHR_lights_punctual)) break; - dom::array lightsArray; + ondemand::array lightsArray; if (auto error = extensionObject["lights"].get_array().get(lightsArray); error == SUCCESS) { parseLights(lightsArray); } else if (error != NO_SUCH_FIELD) { @@ -1261,23 +1403,69 @@ void fg::glTF::parseExtensions(simdjson::dom::object& extensionsObject) { } } -void fg::glTF::parseImages(simdjson::dom::array& images) { +void fg::glTF::parseImages(simdjson::ondemand::array images) { using namespace simdjson; + // parsedAsset->images.reserve(images.count_elements()); - parsedAsset->images.reserve(images.size()); for (auto imageValue : images) { Image image = {}; - dom::object imageObject; + ondemand::object imageObject; if (imageValue.get_object().get(imageObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - std::string_view uri; - if (imageObject["uri"].get_string().get(uri) == SUCCESS) { - if (imageObject["bufferView"].error() == SUCCESS) { - // If uri is declared, bufferView cannot be declared. - SET_ERROR_RETURN(Error::InvalidGltf) + std::string uri; + auto mimeType = MimeType::None; + std::optional bufferViewIndex; + for (auto field : imageObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } + + switch (crc32(key)) { + case force_consteval: { + std::string_view _uri; + if (field.value().get_string().get(_uri) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + uri = _uri; + break; + } + case force_consteval: { + std::string_view _mimeType; + if (field.value().get_string().get(_mimeType) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + mimeType = getMimeTypeFromString(_mimeType); + break; + } + case force_consteval: { + uint64_t _bufferView; + if (field.value().get_uint64().get(_bufferView) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + bufferViewIndex = _bufferView; + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + image.name = std::string { name }; + break; + } } + } + + // There has to be either a URI provided, or a pair of bufferView and mimeType values. But not a + // value for bufferView when a uri is provided. + if ((uri.empty() && !bufferViewIndex.has_value()) || (bufferViewIndex.has_value() && mimeType != MimeType::None) || (!uri.empty() && bufferViewIndex.has_value())) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + if (!uri.empty()) { auto [error, source] = decodeUri(uri); if (error != Error::None) { SET_ERROR_RETURN(error) @@ -1285,53 +1473,38 @@ void fg::glTF::parseImages(simdjson::dom::array& images) { image.data = std::move(source); - std::string_view mimeType; - if (imageObject["mimeType"].get_string().get(mimeType) == SUCCESS) { + if (mimeType != MimeType::None) { std::visit([&](auto& arg) { using T = std::decay_t; // This is kinda cursed if constexpr (is_any()) { - arg.mimeType = getMimeTypeFromString(mimeType); + arg.mimeType = mimeType; } }, image.data); } - } - - uint64_t bufferViewIndex; - if (imageObject["bufferView"].get_uint64().get(bufferViewIndex) == SUCCESS) { - std::string_view mimeType; - if (imageObject["mimeType"].get_string().get(mimeType) != SUCCESS) { - // If bufferView is defined, mimeType needs to also be defined. - SET_ERROR_RETURN(Error::InvalidGltf) - } - + } else { image.data = sources::BufferView { - static_cast(bufferViewIndex), - getMimeTypeFromString(mimeType), + bufferViewIndex.value(), + mimeType, }; } + // Last check to ensure data availability if (std::holds_alternative(image.data)) { SET_ERROR_RETURN(Error::InvalidGltf) } - // name is optional. - std::string_view name; - if (imageObject["name"].get_string().get(name) == SUCCESS) { - image.name = std::string { name }; - } - parsedAsset->images.emplace_back(std::move(image)); } } -void fg::glTF::parseLights(simdjson::dom::array& lights) { +void fg::glTF::parseLights(simdjson::ondemand::array& lights) { using namespace simdjson; - parsedAsset->lights.reserve(lights.size()); + // parsedAsset->lights.reserve(lights.size()); for (auto lightValue : lights) { - dom::object lightObject; + ondemand::object lightObject; if (lightValue.get_object().get(lightObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } @@ -1361,7 +1534,7 @@ void fg::glTF::parseLights(simdjson::dom::array& lights) { } if (light.type == LightType::Spot) { - dom::object spotObject; + ondemand::object spotObject; if (lightObject["spot"].get_object().get(spotObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } @@ -1379,19 +1552,20 @@ void fg::glTF::parseLights(simdjson::dom::array& lights) { light.outerConeAngle = static_cast(outerConeAngle); } - dom::array colorArray; + ondemand::array colorArray; if (lightObject["color"].get_array().get(colorArray) == SUCCESS) { - if (colorArray.size() != 3U) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - for (size_t i = 0U; i < colorArray.size(); ++i) { + size_t i = 0U; + for (auto arrayValue : colorArray) { double color; - if (colorArray.at(i).get_double().get(color) == SUCCESS) { - light.color[i] = static_cast(color); + if (arrayValue.get_double().get(color) == SUCCESS) { + light.color[i++] = static_cast(color); } else { SET_ERROR_RETURN(Error::InvalidGltf) } } + if (i != 3U) { + SET_ERROR_RETURN(Error::InvalidGltf) + } } double intensity; @@ -1415,522 +1589,790 @@ void fg::glTF::parseLights(simdjson::dom::array& lights) { } } -void fg::glTF::parseMaterials(simdjson::dom::array& materials) { +void fg::glTF::parseMaterials(simdjson::ondemand::array materials) { using namespace simdjson; + // parsedAsset->materials.reserve(materials.count_elements()); - parsedAsset->materials.reserve(materials.size()); for (auto materialValue : materials) { - dom::object materialObject; + ondemand::object materialObject; if (materialValue.get_object().get(materialObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } Material material = {}; - dom::array emissiveFactor; - if (materialObject["emissiveFactor"].get_array().get(emissiveFactor) == SUCCESS) { - if (emissiveFactor.size() != 3) { + material.emissiveFactor = {{ 0, 0, 0 }}; + material.alphaMode = AlphaMode::Opaque; + material.alphaCutoff = 0.5F; + material.doubleSided = false; + for (auto field : materialObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - for (auto i = 0U; i < 3; ++i) { - double val; - if (emissiveFactor.at(i).get_double().get(val) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - material.emissiveFactor[i] = static_cast(val); - } - } else { - material.emissiveFactor = {{ 0, 0, 0 }}; - } - - TextureInfo textureObject = {}; - auto error = parseTextureObject(&materialObject, "normalTexture", &textureObject); - if (error == Error::None) { - material.normalTexture = textureObject; - } else if (error != Error::MissingField) { - SET_ERROR_RETURN(error) - } - error = parseTextureObject(&materialObject, "occlusionTexture", &textureObject); - if (error == Error::None) { - material.occlusionTexture = textureObject; - } else if (error != Error::MissingField) { - SET_ERROR_RETURN(error) - } - error = parseTextureObject(&materialObject, "emissiveTexture", &textureObject); - if (error == Error::None) { - material.emissiveTexture = textureObject; - } else if (error != Error::MissingField) { - SET_ERROR_RETURN(error) - } - dom::object pbrMetallicRoughness; - if (materialObject["pbrMetallicRoughness"].get_object().get(pbrMetallicRoughness) == SUCCESS) { - PBRData pbr = {}; - - dom::array baseColorFactor; - if (pbrMetallicRoughness["baseColorFactor"].get_array().get(baseColorFactor) == SUCCESS) { - for (auto i = 0U; i < 4; ++i) { - double val; - if (baseColorFactor.at(i).get_double().get(val) != SUCCESS) { + switch (crc32(key)) { + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - pbr.baseColorFactor[i] = static_cast(val); + material.name = std::string { name }; + break; } - } else { - pbr.baseColorFactor = {{ 1, 1, 1, 1 }}; + case force_consteval: { + ondemand::object pbrObject; + if (field.value().get_object().get(pbrObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + PBRData pbr = {}; + if (auto error = parsePBRObject(&pbrObject, &pbr); error != Error::None) { + return; + } + material.pbrData = pbr; + break; + } + case force_consteval: { + TextureInfo textureObject = {}; + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + auto error = parseTextureObject(&object, &textureObject); + material.normalTexture = textureObject; + break; + } + case force_consteval: { + TextureInfo textureObject = {}; + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + auto error = parseTextureObject(&object, &textureObject); + material.occlusionTexture = textureObject; + break; + } + case force_consteval: { + TextureInfo textureObject = {}; + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + auto error = parseTextureObject(&object, &textureObject); + material.emissiveTexture = textureObject; + break; + } + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + size_t i = 0; + for (auto emissiveValue : array) { + double val; + if (emissiveValue.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + material.emissiveFactor[i++] = static_cast(val); + if (i >= material.emissiveFactor.size()) + break; + } + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + } + case force_consteval: { + std::string_view alphaMode; + if (field.value().get_string().get(alphaMode) == SUCCESS) { + if (alphaMode == "OPAQUE") { + material.alphaMode = AlphaMode::Opaque; + } else if (alphaMode == "MASK") { + material.alphaMode = AlphaMode::Mask; + } else if (alphaMode == "BLEND") { + material.alphaMode = AlphaMode::Blend; + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + } + case force_consteval: { + double value; + if (field.value().get_double().get(value) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + material.alphaCutoff = static_cast(value); + break; + } + case force_consteval: { + bool doubleSided = false; + if (materialObject["doubleSided"].get_bool().get(doubleSided) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + material.doubleSided = doubleSided; + break; + } + default: continue; } + } - double factor; - if (pbrMetallicRoughness["metallicFactor"].get_double().get(factor) == SUCCESS) { - pbr.metallicFactor = static_cast(factor); - } else { - pbr.metallicFactor = 1.0f; - } - if (pbrMetallicRoughness["roughnessFactor"].get_double().get(factor) == SUCCESS) { - pbr.roughnessFactor = static_cast(factor); - } else { - pbr.roughnessFactor = 1.0f; - } + parsedAsset->materials.emplace_back(std::move(material)); + } +} - error = parseTextureObject(&pbrMetallicRoughness, "baseColorTexture", &textureObject); - if (error == Error::None) { - pbr.baseColorTexture = textureObject; - } else if (error != Error::MissingField) { - SET_ERROR_RETURN(error) - } - error = parseTextureObject(&pbrMetallicRoughness, "metallicRoughnessTexture", &textureObject); - if (error == Error::None) { - pbr.metallicRoughnessTexture = textureObject; - } else if (error != Error::MissingField) { - SET_ERROR_RETURN(error) - } +void fg::glTF::parseMeshes(simdjson::ondemand::array meshes) { + using namespace simdjson; + // parsedAsset->meshes.reserve(meshes.count_elements()); - material.pbrData = pbr; + for (auto meshValue : meshes) { + // Required fields: "primitives" + ondemand::object meshObject; + if (meshValue.get_object().get(meshObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) } + Mesh mesh = {}; - std::string_view alphaMode; - if (materialObject["alphaMode"].get_string().get(alphaMode) == SUCCESS) { - if (alphaMode == "OPAQUE") { - material.alphaMode = AlphaMode::Opaque; - } else if (alphaMode == "MASK") { - material.alphaMode = AlphaMode::Mask; - } else if (alphaMode == "BLEND") { - material.alphaMode = AlphaMode::Blend; - } else { - SET_ERROR_RETURN(Error::InvalidGltf) + auto parseAttributes = [](ondemand::object& object, std::unordered_map& map) -> auto { + // We iterate through the JSON object and write each key/pair value into the + // attributes map. The keys are only validated in the validate() method. + for (auto field : object) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + return Error::InvalidGltf; + } + + uint64_t attributeIndex; + if (field.value().get_uint64().get(attributeIndex) != SUCCESS) { + return Error::InvalidGltf; + } else { + map[std::string { key }] = static_cast(attributeIndex); + } } - } else { - material.alphaMode = AlphaMode::Opaque; - } + return Error::None; + }; - double alphaCutoff = 0.5; - if (materialObject["alphaCutoff"].get_double().get(alphaCutoff) == SUCCESS) { - material.alphaCutoff = static_cast(alphaCutoff); - } else { - material.alphaCutoff = 0.5f; - } + for (auto field : meshObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } - bool doubleSided = false; - if (materialObject["doubleSided"].get_bool().get(doubleSided) == SUCCESS) { - material.doubleSided = doubleSided; - } else { - material.doubleSided = false; + switch (crc32(key)) { + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + if (auto error = parseMeshPrimitives(&array, &mesh); error != Error::None) { + SET_ERROR_RETURN(error) + } + break; + } + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + // mesh.weights.reserve(array.count_elements()); + for (auto weightValue : array) { + double val; + if (weightValue.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + mesh.weights.emplace_back(static_cast(val)); + } + break; + } + case force_consteval: { + std::string_view name; + if (meshObject["name"].get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + mesh.name = std::string { name }; + break; + } + } } - // name is optional. - std::string_view name; - if (materialObject["name"].get_string().get(name) == SUCCESS) { - material.name = std::string { name }; + if (mesh.primitives.empty()) { + SET_ERROR_RETURN(Error::InvalidGltf) } - parsedAsset->materials.emplace_back(std::move(material)); + parsedAsset->meshes.emplace_back(std::move(mesh)); } } -void fg::glTF::parseMeshes(simdjson::dom::array& meshes) { +fg::Error fg::glTF::parseMeshPrimitives(void* pPrimitives, Mesh* mesh) noexcept { using namespace simdjson; + auto& primitives = *static_cast(pPrimitives); + // mesh->primitives.reserve(primitives.count_elements()); - parsedAsset->meshes.reserve(meshes.size()); - for (auto meshValue : meshes) { - // Required fields: "primitives" - dom::object meshObject; - if (meshValue.get_object().get(meshObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + auto parseAttributes = [](ondemand::object& object, std::unordered_map& map) -> auto { + // We iterate through the JSON object and write each key/pair value into the + // attributes map. This is not filtered for actual values. TODO? + for (auto field : object) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + return Error::InvalidJson; + } + + uint64_t attributeIndex; + if (field.value().get_uint64().get(attributeIndex) != SUCCESS) { + return Error::InvalidGltf; + } else { + map[std::string { key }] = static_cast(attributeIndex); + } } - Mesh mesh = {}; + return Error::None; + }; - dom::array array; - auto meshError = getJsonArray(meshObject, "primitives", &array); - if (meshError == Error::MissingField) { - SET_ERROR_RETURN(Error::InvalidGltf) - } else if (meshError != Error::None) { - SET_ERROR_RETURN(meshError) - } else { - mesh.primitives.reserve(array.size()); - for (auto primitiveValue : array) { - // Required fields: "attributes" - Primitive primitive = {}; - dom::object primitiveObject; - if (primitiveValue.get_object().get(primitiveObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } + for (auto primitiveValue : primitives) { + ondemand::object object; + if (primitiveValue.get_object().get(object) != SUCCESS) { + return Error::InvalidGltf; + } - auto parseAttributes = [](dom::object& object, std::unordered_map& map) -> auto { - // We iterate through the JSON object and write each key/pair value into the - // attributes map. The keys are only validated in the validate() method. - for (const auto& field : object) { - std::string_view key = field.key; + Primitive primitive = {}; + primitive.type = PrimitiveType::Triangles; + for (auto field : object) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + return Error::InvalidJson; + } - uint64_t attributeIndex; - if (field.value.get_uint64().get(attributeIndex) != SUCCESS) { - return Error::InvalidGltf; - } else { - map[std::string { key }] = static_cast(attributeIndex); - } + switch (crc32(key)) { + case force_consteval: { + ondemand::object attributes; + if (field.value().get_object().get(attributes) != SUCCESS) { + return Error::InvalidGltf; + } + if (auto error = parseAttributes(attributes, primitive.attributes); error != Error::None) { + return error; + } + break; + } + case force_consteval: { + uint64_t indices; + if (field.value().get_uint64().get(indices) != SUCCESS) { + return Error::InvalidGltf; + } + primitive.indicesAccessor = static_cast(indices); + break; + } + case force_consteval: { + uint64_t material; + if (field.value().get_uint64().get(material) != SUCCESS) { + return Error::InvalidGltf; + } + primitive.materialIndex = static_cast(material); + break; + } + case force_consteval: { + uint64_t mode; + if (field.value().get_uint64().get(mode) != SUCCESS) { + return Error::InvalidGltf; } - return Error::None; - }; - - dom::object attributesObject; - if (primitiveObject["attributes"].get_object().get(attributesObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + primitive.type = static_cast(mode); + break; } - parseAttributes(attributesObject, primitive.attributes); + case force_consteval: { + ondemand::array targets; + if (field.value().get_array().get(targets) != SUCCESS) { + return Error::InvalidGltf; + } - dom::array targets; - if (primitiveObject["targets"].get_array().get(targets) == SUCCESS) { + ondemand::object attributesObject; for (auto targetValue : targets) { if (targetValue.get_object().get(attributesObject) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + return Error::InvalidGltf; } auto& map = primitive.targets.emplace_back(); - parseAttributes(attributesObject, map); + if (auto error = parseAttributes(attributesObject, map); error != Error::None) { + return error; + } } + break; } - - // Mode shall default to 4. - uint64_t value; - if (primitiveObject["mode"].get_uint64().get(value) != SUCCESS) { - primitive.type = PrimitiveType::Triangles; - } else { - primitive.type = static_cast(value); - } - - if (primitiveObject["indices"].get_uint64().get(value) == SUCCESS) { - primitive.indicesAccessor = static_cast(value); - } - - if (primitiveObject["material"].get_uint64().get(value) == SUCCESS) { - primitive.materialIndex = static_cast(value); - } - - mesh.primitives.emplace_back(std::move(primitive)); - } - } - - if (meshError = getJsonArray(meshObject, "weights", &array); meshError == Error::None) { - mesh.weights.reserve(array.size()); - for (auto weightValue : array) { - double val; - if (weightValue.get_double().get(val) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - mesh.weights.emplace_back(static_cast(val)); } - } else if (meshError != Error::MissingField && meshError != Error::None) { - SET_ERROR_RETURN(Error::InvalidGltf) } - // name is optional. - std::string_view name; - if (meshObject["name"].get_string().get(name) == SUCCESS) { - mesh.name = std::string { name }; + if (primitive.attributes.empty()) { + return Error::InvalidGltf; } - parsedAsset->meshes.emplace_back(std::move(mesh)); + mesh->primitives.emplace_back(std::move(primitive)); } + + return Error::None; } -void fg::glTF::parseNodes(simdjson::dom::array& nodes) { +void fg::glTF::parseNodes(simdjson::ondemand::array nodes) { using namespace simdjson; + // parsedAsset->nodes.reserve(nodes.count_elements()); - parsedAsset->nodes.reserve(nodes.size()); for (auto nodeValue : nodes) { Node node = {}; - dom::object nodeObject; + ondemand::object nodeObject; if (nodeValue.get_object().get(nodeObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - uint64_t index; - if (nodeObject["mesh"].get_uint64().get(index) == SUCCESS) { - node.meshIndex = static_cast(index); - } - if (nodeObject["skin"].get_uint64().get(index) == SUCCESS) { - node.skinIndex = static_cast(index); - } - if (nodeObject["camera"].get_uint64().get(index) == SUCCESS) { - node.cameraIndex = static_cast(index); - } - - dom::array array; - auto childError = getJsonArray(nodeObject, "children", &array); - if (childError == Error::None) { - node.children.reserve(array.size()); - for (auto childValue : array) { - if (childValue.get_uint64().get(index) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - - node.children.emplace_back(static_cast(index)); + Node::TRS trs = { + {{ 0, 0, 0 }}, // translation + {{ 0, 0, 0, 1 }}, // rotation + {{ 1, 1, 1 }}, // scale + }; + for (auto field : nodeObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) } - } else if (childError != Error::MissingField) { - SET_ERROR_RETURN(childError) - } - auto weightsError = getJsonArray(nodeObject, "weights", &array); - if (weightsError != Error::MissingField) { - if (weightsError != Error::None) { - node.weights.reserve(array.size()); - for (auto weightValue : array) { - double val; - if (weightValue.get_double().get(val) != SUCCESS) { + switch (crc32(key)) { + case force_consteval: { + uint64_t index; + if (field.value().get_uint64().get(index) == SUCCESS) { + node.cameraIndex = static_cast(index); + } + break; + } + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + // node.children.reserve(array.count_elements()); + for (auto childValue : array) { + uint64_t index; + if (childValue.get_uint64().get(index) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + node.children.emplace_back(static_cast(index)); + } + } else { SET_ERROR_RETURN(Error::InvalidGltf) } - node.weights.emplace_back(static_cast(val)); + break; } - } else { - SET_ERROR_RETURN(Error::InvalidGltf) - } - } - - auto error = nodeObject["matrix"].get_array().get(array); - if (error == SUCCESS) { - Node::TransformMatrix transformMatrix = {}; - auto i = 0U; - for (auto num : array) { - double val; - if (num.get_double().get(val) != SUCCESS) { + case force_consteval: { + uint64_t index; + if (field.value().get_uint64().get(index) == SUCCESS) { + node.skinIndex = static_cast(index); + } break; } - transformMatrix[i] = static_cast(val); - ++i; - } - - if (hasBit(options, Options::DecomposeNodeMatrices)) { - Node::TRS trs = {}; - decomposeTransformMatrix(transformMatrix, trs.scale, trs.rotation, trs.translation); - node.transform = trs; - } else { - node.transform = transformMatrix; - } - } else if (error == NO_SUCH_FIELD) { - Node::TRS trs = {}; + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + Node::TransformMatrix transformMatrix = {}; + auto i = 0U; + for (auto num : array) { + double val; + if (num.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + transformMatrix[i++] = static_cast(val); + if (i > transformMatrix.size()) + break; + } - // There's no matrix, let's see if there's scale, rotation, or rotation fields. - if (nodeObject["scale"].get_array().get(array) == SUCCESS) { - auto i = 0U; - for (auto num : array) { - double val; - if (num.get_double().get(val) != SUCCESS) { + if (hasBit(options, Options::DecomposeNodeMatrices)) { + decomposeTransformMatrix(transformMatrix, trs.scale, trs.rotation, trs.translation); + } else { + node.transform = transformMatrix; + } + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + } + case force_consteval: { + uint64_t index; + if (field.value().get_uint64().get(index) == SUCCESS) { + node.meshIndex = static_cast(index); + } + break; + } + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + auto i = 0U; + for (auto num : array) { + double val; + if (num.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + trs.rotation[i++] = static_cast(val); + if (i > trs.rotation.size()) + break; + } + } else { SET_ERROR_RETURN(Error::InvalidGltf) } - trs.scale[i] = static_cast(val); - ++i; + break; } - } else { - trs.scale = {{ 1.0f, 1.0f, 1.0f }}; - } - - if (nodeObject["translation"].get_array().get(array) == SUCCESS) { - auto i = 0U; - for (auto num : array) { - double val; - if (num.get_double().get(val) != SUCCESS) { + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + auto i = 0U; + for (auto num : array) { + double val; + if (num.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + trs.scale[i++] = static_cast(val); + if (i > trs.scale.size()) + break; + } + } else { SET_ERROR_RETURN(Error::InvalidGltf) } - trs.translation[i] = static_cast(val); - ++i; + break; } - } else { - trs.translation = {{ 0.0f, 0.0f, 0.0f }}; - } - - if (nodeObject["rotation"].get_array().get(array) == SUCCESS) { - auto i = 0U; - for (auto num : array) { - double val; - if (num.get_double().get(val) != SUCCESS) { + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + auto i = 0U; + for (auto num : array) { + double val; + if (num.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + trs.translation[i++] = static_cast(val); + if (i > trs.translation.size()) + break; + } + } else { SET_ERROR_RETURN(Error::InvalidGltf) } - trs.rotation[i] = static_cast(val); - ++i; + break; } - } else { - trs.rotation = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; - } - - node.transform = trs; - } + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + // node.weights.reserve(array.count_elements()); + for (auto weightValue : array) { + double val; + if (weightValue.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + node.weights.emplace_back(static_cast(val)); + } + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + node.name = std::string { name }; + break; + } + case force_consteval: { + ondemand::object extensionsObject; + if (field.value().get_object().get(extensionsObject) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - dom::object extensionsObject; - if (nodeObject["extensions"].get_object().get(extensionsObject) == SUCCESS) { - dom::object lightsObject; - if (extensionsObject[extensions::KHR_lights_punctual].get_object().get(lightsObject) == SUCCESS) { - uint64_t light; - if (lightsObject["light"].get_uint64().get(light) == SUCCESS) { - node.lightsIndex = static_cast(light); + ondemand::object lightsObject; + if (extensionsObject[extensions::KHR_lights_punctual].get_object().get(lightsObject) == SUCCESS) { + uint64_t light; + if (lightsObject["light"].get_uint64().get(light) == SUCCESS) { + node.lightsIndex = static_cast(light); + } + } } } } - std::string_view name; - if (nodeObject["name"].get_string().get(name) == SUCCESS) { - node.name = std::string { name }; + // If no transform matrix was assigned, we'll copy over the TRS. + if (!std::holds_alternative(node.transform)) { + node.transform = trs; } parsedAsset->nodes.emplace_back(std::move(node)); } } -void fg::glTF::parseSamplers(simdjson::dom::array& samplers) { +fg::Error fg::glTF::parsePBRObject(void* pPbrObject, PBRData* pbr) noexcept { + using namespace simdjson; + auto& pbrObject = *static_cast(pPbrObject); + + pbr->baseColorFactor = { 1, 1, 1, 1 }; + pbr->metallicFactor = 1.0F; + pbr->roughnessFactor = 1.0F; + for (auto field : pbrObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidJson) + } + + switch (crc32(key)) { + case force_consteval: { + size_t i = 0; + ondemand::array array; + if (field.value().get_array().get(array) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + for (auto baseColorValue : array) { + double val; + if (baseColorValue.get_double().get(val) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + pbr->baseColorFactor[i++] = static_cast(val); + if (i >= pbr->baseColorFactor.size()) + break; + } + break; + } + case force_consteval: { + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + TextureInfo textureInfo = {}; + auto error = parseTextureObject(&object, &textureInfo); + pbr->baseColorTexture = textureInfo; + break; + } + case force_consteval: { + double factor; + if (field.value().get_double().get(factor) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + pbr->metallicFactor = static_cast(factor); + break; + } + case force_consteval: { + double factor; + if (field.value().get_double().get(factor) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + pbr->roughnessFactor = static_cast(factor); + break; + } + case force_consteval: { + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN_ERROR(Error::InvalidGltf) + } + TextureInfo textureInfo = {}; + auto error = parseTextureObject(&object, &textureInfo); + pbr->metallicRoughnessTexture = textureInfo; + break; + } + } + } + + return Error::None; +} + +void fg::glTF::parseSamplers(simdjson::ondemand::array samplers) { using namespace simdjson; - uint64_t number; - parsedAsset->samplers.reserve(samplers.size()); for (auto samplerValue : samplers) { Sampler sampler = {}; - dom::object samplerObject; + ondemand::object samplerObject; if (samplerValue.get_object().get(samplerObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - // name is optional. - std::string_view name; - if (samplerObject["name"].get_string().get(name) == SUCCESS) { - sampler.name = std::string { name }; - } - - if (samplerObject["magFilter"].get_uint64().get(number) == SUCCESS) { - sampler.magFilter = static_cast(number); - } - if (samplerObject["minFilter"].get_uint64().get(number) == SUCCESS) { - sampler.minFilter = static_cast(number); - } + sampler.wrapS = sampler.wrapT = Wrap::Repeat; + for (auto field : samplerObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - if (samplerObject["wrapS"].get_uint64().get(number) == SUCCESS) { - sampler.wrapS = static_cast(number); - } else { - sampler.wrapS = Wrap::Repeat; - } - if (samplerObject["wrapT"].get_uint64().get(number) == SUCCESS) { - sampler.wrapT = static_cast(number); - } else { - sampler.wrapT = Wrap::Repeat; + switch (crc32(key)) { + case force_consteval: { + uint64_t number; + if (field.value().get_uint64().get(number) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.magFilter = static_cast(number); + break; + } + case force_consteval: { + uint64_t number; + if (field.value().get_uint64().get(number) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.minFilter = static_cast(number); + break; + } + case force_consteval: { + uint64_t number; + if (field.value().get_uint64().get(number) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.wrapS = static_cast(number); + break; + } + case force_consteval: { + uint64_t number; + if (field.value().get_uint64().get(number) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.wrapT = static_cast(number); + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + sampler.name = std::string { name }; + break; + } + } } parsedAsset->samplers.emplace_back(std::move(sampler)); } } -void fg::glTF::parseScenes(simdjson::dom::array& scenes) { +void fg::glTF::parseScenes(simdjson::ondemand::array scenes) { using namespace simdjson; + // parsedAsset->scenes.reserve(scenes.count_elements()); - parsedAsset->scenes.reserve(scenes.size()); for (auto sceneValue : scenes) { // The scene object can be completely empty Scene scene = {}; - dom::object sceneObject; + ondemand::object sceneObject; if (sceneValue.get_object().get(sceneObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - // name is optional. - std::string_view name; - if (sceneObject["name"].get_string().get(name) == SUCCESS) { - scene.name = std::string { name }; - } - - // Parse the array of nodes. - dom::array nodes; - auto nodeError = getJsonArray(sceneObject, "nodes", &nodes); - if (nodeError == Error::None) { - scene.nodeIndices.reserve(nodes.size()); - for (auto nodeValue : nodes) { - uint64_t index; - if (nodeValue.get_uint64().get(index) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - - scene.nodeIndices.emplace_back(static_cast(index)); + for (auto field : sceneObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) } - parsedAsset->scenes.emplace_back(std::move(scene)); - } else if (nodeError != Error::MissingField) { - SET_ERROR_RETURN(nodeError) + switch (crc32(key)) { + case force_consteval: { + ondemand::array array; + auto result = field.value().get_array().get(array); + if (result == SUCCESS) { + // scene.nodeIndices.reserve(array.count_elements()); + for (auto nodeValue : array) { + uint64_t index; + if (nodeValue.get_uint64().get(index) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + + scene.nodeIndices.emplace_back(static_cast(index)); + } + } else { + SET_ERROR_RETURN(Error::InvalidGltf) + } + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + scene.name = std::string { name }; + break; + } + } } + + parsedAsset->scenes.emplace_back(std::move(scene)); } } -void fg::glTF::parseSkins(simdjson::dom::array& skins) { +void fg::glTF::parseSkins(simdjson::ondemand::array skins) { using namespace simdjson; + // parsedAsset->skins.reserve(skins.count_elements()); - parsedAsset->skins.reserve(skins.size()); for (auto skinValue : skins) { Skin skin = {}; - dom::object skinObject; + ondemand::object skinObject; if (skinValue.get_object().get(skinObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - uint64_t index; - if (skinObject["inverseBindMatrices"].get_uint64().get(index) == SUCCESS) { - skin.inverseBindMatrices = static_cast(index); - } - if (skinObject["skeleton"].get_uint64().get(index) == SUCCESS) { - skin.skeleton = static_cast(index); - } + for (auto field : skinObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidJson) + } - dom::array jointsArray; - if (skinObject["joints"].get_array().get(jointsArray) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - skin.joints.reserve(jointsArray.size()); - for (auto jointValue : jointsArray) { - if (jointValue.get_uint64().get(index) != SUCCESS) { - SET_ERROR_RETURN(Error::InvalidGltf) + switch (crc32(key)) { + case force_consteval: { + uint64_t index; + if (field.value().get_uint64().get(index) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + skin.inverseBindMatrices = index; + break; + } + case force_consteval: { + uint64_t index; + if (field.value().get_uint64().get(index) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + skin.skeleton = index; + break; + } + case force_consteval: { + ondemand::array array; + if (field.value().get_array().get(array) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + // skin.joints.reserve(array.count_elements()); + for (auto jointValue : array) { + if (jointValue.get_uint64().get(skin.joints.emplace_back()) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + } + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + skin.name = std::string { name }; + break; + } } - skin.joints.emplace_back(index); } - // name is optional. - std::string_view name; - if (skinObject["name"].get_string().get(name) == SUCCESS) { - skin.name = std::string { name }; + if (skin.joints.empty()) { + SET_ERROR_RETURN(Error::InvalidGltf) } + parsedAsset->skins.emplace_back(std::move(skin)); } } -fg::Error fg::glTF::parseTextureObject(void* object, std::string_view key, TextureInfo* info) noexcept { +fg::Error fg::glTF::parseTextureObject(void* object, TextureInfo* info) noexcept { using namespace simdjson; - auto& obj = *static_cast(object); - - dom::object child; - const auto childErr = obj[key].get_object().get(child); - if (childErr == NO_SUCH_FIELD) { - return Error::MissingField; // Don't set errorCode. - } else if (childErr != SUCCESS) { - SET_ERROR_RETURN_ERROR(Error::InvalidGltf) - } + auto& obj = *static_cast(object); uint64_t index; - if (child["index"].get_uint64().get(index) == SUCCESS) { + if (obj["index"].get_uint64().get(index) == SUCCESS) { info->textureIndex = static_cast(index); } else { SET_ERROR_RETURN_ERROR(Error::InvalidGltf) } - if (child["texCoord"].get_uint64().get(index) == SUCCESS) { + if (obj["texCoord"].get_uint64().get(index) == SUCCESS) { info->texCoordIndex = static_cast(index); } else { info->texCoordIndex = 0; @@ -1938,7 +2380,7 @@ fg::Error fg::glTF::parseTextureObject(void* object, std::string_view key, Textu // scale only applies to normal textures. double scale = 1.0f; - if (child["scale"].get_double().get(scale) == SUCCESS) { + if (obj["scale"].get_double().get(scale) == SUCCESS) { info->scale = static_cast(scale); } else { info->scale = 1.0f; @@ -1948,9 +2390,9 @@ fg::Error fg::glTF::parseTextureObject(void* object, std::string_view key, Textu info->uvOffset = {{ 0.0f, 0.0f }}; info->uvScale = {{ 1.0f, 1.0f }}; - dom::object extensionsObject; - if (child["extensions"].get_object().get(extensionsObject) == SUCCESS) { - dom::object textureTransform; + ondemand::object extensionsObject; + if (obj["extensions"].get_object().get(extensionsObject) == SUCCESS) { + ondemand::object textureTransform; if (hasBit(data->config.extensions, Extensions::KHR_texture_transform) && extensionsObject[extensions::KHR_texture_transform].get_object().get(textureTransform) == SUCCESS) { if (textureTransform["texCoord"].get_uint64().get(index) == SUCCESS) { info->texCoordIndex = index; @@ -1961,24 +2403,28 @@ fg::Error fg::glTF::parseTextureObject(void* object, std::string_view key, Textu info->rotation = static_cast(rotation); } - dom::array array; + ondemand::array array; if (textureTransform["offset"].get_array().get(array) == SUCCESS) { - for (auto i = 0U; i < 2; ++i) { - double val; - if (array.at(i).get_double().get(val) != SUCCESS) { - return Error::InvalidGltf; + size_t i = 0; + double val; + for (auto value : array) { + if (value.get_double().get(val) != SUCCESS) { + errorCode = Error::InvalidGltf; + return errorCode; } - info->uvOffset[i] = static_cast(val); + info->uvOffset[i++] = static_cast(val); } } if (textureTransform["scale"].get_array().get(array) == SUCCESS) { - for (auto i = 0U; i < 2; ++i) { - double val; - if (array.at(i).get_double().get(val) != SUCCESS) { - return Error::InvalidGltf; + size_t i = 0; + double val; + for (auto value : array) { + if (value.get_double().get(val) != SUCCESS) { + errorCode = Error::InvalidGltf; + return errorCode; } - info->uvScale[i] = static_cast(val); + info->uvScale[i++] = static_cast(val); } } } @@ -1987,57 +2433,71 @@ fg::Error fg::glTF::parseTextureObject(void* object, std::string_view key, Textu return Error::None; } -void fg::glTF::parseTextures(simdjson::dom::array& textures) { +void fg::glTF::parseTextures(simdjson::ondemand::array textures) { using namespace simdjson; - parsedAsset->textures.reserve(textures.size()); + // parsedAsset->textures.reserve(textures.count_elements()); for (auto textureValue : textures) { Texture texture; - dom::object textureObject; + ondemand::object textureObject; if (textureValue.get_object().get(textureObject) != SUCCESS) { SET_ERROR_RETURN(Error::InvalidGltf) } - uint64_t sourceIndex; - if (textureObject["source"].get_uint64().get(sourceIndex) == SUCCESS) { - texture.imageIndex = static_cast(sourceIndex); - } + std::optional extensionImageSource; + for (auto field : textureObject) { + std::string_view key; + if (field.unescaped_key().get(key) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } - bool hasExtensions = false; - dom::object extensionsObject; - if (textureObject["extensions"].get_object().get(extensionsObject) == SUCCESS) { - hasExtensions = true; + switch (crc32(key)) { + case force_consteval: { + // The index of the sampler used by this texture. When undefined, a sampler with + // repeat wrapping and auto filtering SHOULD be used. + uint64_t samplerIndex; + if (field.value().get_uint64().get(samplerIndex) == SUCCESS) { + texture.samplerIndex = static_cast(samplerIndex); + } + break; + } + case force_consteval: { + uint64_t sourceIndex; + if (field.value().get_uint64().get(sourceIndex) == SUCCESS) { + texture.imageIndex = static_cast(sourceIndex); + } + break; + } + case force_consteval: { + std::string_view name; + if (field.value().get_string().get(name) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + texture.name = std::string { name }; + break; + } + case force_consteval: { + ondemand::object object; + if (field.value().get_object().get(object) != SUCCESS) { + SET_ERROR_RETURN(Error::InvalidGltf) + } + extensionImageSource = parseTextureExtensions(object, data->config.extensions); + break; + } + } } - // If we have extensions, we'll use the normal "source" as the fallback and then parse - // the extensions for any "source" field. - if (hasExtensions) { + if (extensionImageSource.has_value()) { if (texture.imageIndex.has_value()) { // If the source was specified we'll use that as a fallback. texture.fallbackImageIndex = texture.imageIndex; } - if (!parseTextureExtensions(texture, extensionsObject, data->config.extensions)) { - SET_ERROR_RETURN(Error::InvalidGltf) - } - } - - // The index of the sampler used by this texture. When undefined, a sampler with - // repeat wrapping and auto filtering SHOULD be used. - uint64_t samplerIndex; - if (textureObject["sampler"].get_uint64().get(samplerIndex) == SUCCESS) { - texture.samplerIndex = static_cast(samplerIndex); - } - - // name is optional. - std::string_view name; - if (textureObject["name"].get_string().get(name) == SUCCESS) { - texture.name = std::string { name }; + texture.imageIndex = extensionImageSource.value(); } parsedAsset->textures.emplace_back(std::move(texture)); } } - #pragma endregion #pragma region GltfDataBuffer @@ -2137,7 +2597,7 @@ fastgltf::GltfType fg::determineGltfFileType(GltfDataBuffer* buffer) { } fg::Parser::Parser(Extensions extensionsToLoad) noexcept { - jsonParser = std::make_unique(); + jsonParser = std::make_unique(); config.extensions = extensionsToLoad; } @@ -2180,13 +2640,18 @@ std::unique_ptr fg::Parser::loadGLTF(GltfDataBuffer* buffer, fs::path } auto data = std::make_unique(); - if (jsonParser->parse(padded_string_view(buffer->bufferPointer, jsonLength, buffer->allocatedSize)).get(data->root) != SUCCESS) { + if (jsonParser->iterate(padded_string_view(buffer->bufferPointer, jsonLength, buffer->allocatedSize)).get(data->doc) != SUCCESS) { + errorCode = Error::InvalidJson; + return nullptr; + } + if (data->doc.get_object().get(data->root) != SUCCESS) { errorCode = Error::InvalidJson; return nullptr; } data->config = config; - return std::unique_ptr(new glTF(std::move(data), std::move(directory), options)); + auto* gltf = new glTF(std::move(data), std::move(directory), options); + return std::unique_ptr(gltf); } std::unique_ptr fg::Parser::loadBinaryGLTF(GltfDataBuffer* buffer, fs::path directory, Options options) { @@ -2226,20 +2691,26 @@ std::unique_ptr fg::Parser::loadBinaryGLTF(GltfDataBuffer* buffer, fs: return nullptr; } - std::vector jsonData(jsonChunk.chunkLength + simdjson::SIMDJSON_PADDING); - read(jsonData.data(), jsonChunk.chunkLength); - // We set the padded region to 0 to avoid simdjson reading garbage - std::memset(jsonData.data() + jsonChunk.chunkLength, 0, jsonData.size() - jsonChunk.chunkLength); + // Create a string view of the JSON chunk in the GLB data buffer. The documentation of iterate() + // says the padding can be initialised to anything, apparently. Therefore, this should work. + simdjson::padded_string_view jsonChunkView(buffer->bufferPointer + offset, + jsonChunk.chunkLength, + jsonChunk.chunkLength + SIMDJSON_PADDING); + offset += jsonChunk.chunkLength; - // The 'false' indicates that simdjson doesn't have to copy the data internally. auto data = std::make_unique(); - if (jsonParser->parse(jsonData.data(), jsonChunk.chunkLength, false).get(data->root) != SUCCESS) { + if (jsonParser->iterate(jsonChunkView).get(data->doc) != SUCCESS) { + errorCode = Error::InvalidJson; + return nullptr; + } + if (data->doc.get_object().get(data->root) != SUCCESS) { errorCode = Error::InvalidJson; return nullptr; } data->config = config; - auto gltf = std::unique_ptr(new glTF(std::move(data), std::move(directory), options)); + auto* gltfp = new glTF(std::move(data), std::move(directory), options); + auto gltf = std::unique_ptr(gltfp); // Is there enough room for another chunk header? if (header.length > (offset + sizeof(BinaryGltfChunk))) { diff --git a/src/fastgltf_parser.hpp b/src/fastgltf_parser.hpp index e4207206b..1cc0e66b5 100644 --- a/src/fastgltf_parser.hpp +++ b/src/fastgltf_parser.hpp @@ -37,23 +37,35 @@ #include "fastgltf_types.hpp" #include "fastgltf_util.hpp" +#include "simdjson.h" // TODO: REMOVE!! + #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 5030) // attribute 'x' is not recognized #pragma warning(disable : 4514) // unreferenced inline function has been removed #endif -// fwd -namespace simdjson::dom { - class array; - class object; - class parser; -} +// fwd. Not needed currently because we include simdjson.h +/*namespace simdjson { + struct padded_string; + // We assume that SIMDJSON_BUILTIN_IMPLEMENTATION is always fallback. + // Is this a valid assumption? + namespace fallback::ondemand { + class array; + class object; + } +}*/ namespace fastgltf { struct BinaryGltfChunk; class GltfDataBuffer; + struct BufferView; struct ParserData; + struct PBRData; + struct Mesh; + struct TextureInfo; + enum class DataLocation : uint8_t; + enum class MimeType : uint16_t; enum class Error : uint64_t { None = 0, @@ -200,23 +212,26 @@ namespace fastgltf { static void fillCategories(Category& inputCategories) noexcept; [[nodiscard]] auto decodeUri(std::string_view uri) const noexcept -> std::pair; - [[gnu::always_inline]] inline Error parseTextureObject(void* object, std::string_view key, TextureInfo* info) noexcept; - - void parseAccessors(simdjson::dom::array& array); - void parseAnimations(simdjson::dom::array& array); - void parseBuffers(simdjson::dom::array& array); - void parseBufferViews(simdjson::dom::array& array); - void parseCameras(simdjson::dom::array& array); - void parseExtensions(simdjson::dom::object& extensionsObject); - void parseImages(simdjson::dom::array& array); - void parseLights(simdjson::dom::array& array); - void parseMaterials(simdjson::dom::array& array); - void parseMeshes(simdjson::dom::array& array); - void parseNodes(simdjson::dom::array& array); - void parseSamplers(simdjson::dom::array& array); - void parseScenes(simdjson::dom::array& array); - void parseSkins(simdjson::dom::array& array); - void parseTextures(simdjson::dom::array& array); + [[nodiscard]] Error parseBufferViewObject(void* viewObject, BufferView* view, Extensions extension) noexcept; + [[nodiscard]] Error parsePBRObject(void* pbrObject, PBRData* data) noexcept; + [[nodiscard]] Error parseMeshPrimitives(void* primitives, Mesh* mesh) noexcept; + [[nodiscard]] Error parseTextureObject(void* object, TextureInfo* info) noexcept; + + void parseAccessors(simdjson::ondemand::array array); + void parseAnimations(simdjson::ondemand::array array); + void parseBuffers(simdjson::ondemand::array array); + void parseBufferViews(simdjson::ondemand::array array); + void parseCameras(simdjson::ondemand::array array); + void parseExtensions(simdjson::ondemand::object& extensionsObject); + void parseImages(simdjson::ondemand::array array); + void parseLights(simdjson::ondemand::array& array); + void parseMaterials(simdjson::ondemand::array array); + void parseMeshes(simdjson::ondemand::array array); + void parseNodes(simdjson::ondemand::array array); + void parseSamplers(simdjson::ondemand::array array); + void parseScenes(simdjson::ondemand::array array); + void parseSkins(simdjson::ondemand::array array); + void parseTextures(simdjson::ondemand::array array); public: explicit glTF(const glTF& scene) = delete; @@ -280,6 +295,9 @@ namespace fastgltf { explicit GltfDataBuffer() noexcept; ~GltfDataBuffer() noexcept; + GltfDataBuffer(GltfDataBuffer& other) = delete; + GltfDataBuffer& operator=(GltfDataBuffer& other) = delete; + /** * Saves the pointer including its range. Does not copy any data. This requires the * original allocation to outlive the parsing of the glTF, so after the last relevant @@ -327,7 +345,7 @@ namespace fastgltf { class Parser { // The simdjson parser object. We want to share it between runs, so it does not need to // reallocate over and over again. We're hiding it here to not leak the simdjson header. - std::unique_ptr jsonParser; + std::unique_ptr jsonParser; // Callbacks ParserInternalConfig config = {}; diff --git a/src/fastgltf_util.hpp b/src/fastgltf_util.hpp index 6f64f1ab6..856c49a66 100644 --- a/src/fastgltf_util.hpp +++ b/src/fastgltf_util.hpp @@ -118,7 +118,7 @@ namespace fastgltf { inline void decomposeTransformMatrix(std::array matrix, std::array& scale, std::array& rotation, std::array& translation) { // Extract the translation. We zero the translation out, as we reuse the matrix as // the rotation matrix at the end. - translation = {matrix[12], matrix[13], matrix[14]}; + translation = {{matrix[12], matrix[13], matrix[14]}}; matrix[12] = matrix[13] = matrix[14] = 0; // Extract the scale. We calculate the euclidean length of the columns. We then @@ -127,7 +127,7 @@ namespace fastgltf { auto s1 = sqrtf(matrix[0] * matrix[0] + matrix[1] * matrix[1] + matrix[2] * matrix[2]); auto s2 = sqrtf(matrix[4] * matrix[4] + matrix[5] * matrix[5] + matrix[6] * matrix[6]); auto s3 = sqrtf(matrix[8] * matrix[8] + matrix[9] * matrix[9] + matrix[10] * matrix[10]); - scale = {s1, s2, s3}; + scale = {{s1, s2, s3}}; // Remove the scaling from the matrix, leaving only the rotation. matrix is now the // rotation matrix. diff --git a/tests/basic_test.cpp b/tests/basic_test.cpp index 26a2ac197..d7ac8a024 100644 --- a/tests/basic_test.cpp +++ b/tests/basic_test.cpp @@ -90,6 +90,7 @@ TEST_CASE("Loading some basic glTF", "[gltf-loader]") { auto jsonData = std::make_unique(); REQUIRE(jsonData->loadFromFile(path / "empty_json.gltf")); auto emptyGltf = parser.loadGLTF(jsonData.get(), path); + REQUIRE(parser.getError() == fastgltf::Error::None); REQUIRE(emptyGltf->parse() == fastgltf::Error::InvalidOrMissingAssetField); } @@ -188,7 +189,7 @@ TEST_CASE("Loading KHR_texture_basisu glTF files", "[gltf-loader]") { REQUIRE(parser.getError() == fastgltf::Error::None); REQUIRE(stainedGlassLamp != nullptr); - REQUIRE(stainedGlassLamp->parse(fastgltf::Category::Textures | fastgltf::Category::Images) == fastgltf::Error::None); + REQUIRE(stainedGlassLamp->parse(fastgltf::Category::Textures) == fastgltf::Error::None); REQUIRE(stainedGlassLamp->validate() == fastgltf::Error::None); auto asset = stainedGlassLamp->getParsedAsset(); @@ -210,6 +211,7 @@ TEST_CASE("Loading KHR_texture_basisu glTF files", "[gltf-loader]") { // We specify no extensions, yet the StainedGlassLamp requires KHR_texture_basisu. fastgltf::Parser parser(fastgltf::Extensions::None); auto stainedGlassLamp = parser.loadGLTF(jsonData.get(), path, fastgltf::Options::DontRequireValidAssetMember); + REQUIRE(parser.getError() == fastgltf::Error::None); REQUIRE(stainedGlassLamp->parse() == fastgltf::Error::MissingExtensions); } } @@ -381,6 +383,7 @@ TEST_CASE("Test allocation callbacks for embedded buffers", "[gltf-loader]") { parser.setBufferAllocationCallback(mapCallback, nullptr); auto model = parser.loadGLTF(jsonData.get(), boxPath); REQUIRE(model != nullptr); + REQUIRE(parser.getError() == fastgltf::Error::None); REQUIRE(model->parse(fastgltf::Category::Buffers) == fastgltf::Error::None); REQUIRE(allocations.size() == 1); diff --git a/tests/benchmarks.cpp b/tests/benchmarks.cpp index 008e0f541..07f06b971 100644 --- a/tests/benchmarks.cpp +++ b/tests/benchmarks.cpp @@ -34,7 +34,7 @@ TEST_CASE("Benchmark loading of NewSponza", "[gltf-benchmark]") { }; } -TEST_CASE("Benchmark base64 decoding from glTF file", "[base64-benchmark]") { +TEST_CASE("Benchmark base64 decoding from glTF file", "[gltf-benchmark]") { fastgltf::Parser parser; auto cylinderEngine = sampleModels / "2.0" / "2CylinderEngine" / "glTF-Embedded"; auto jsonData = std::make_unique(); @@ -58,7 +58,7 @@ TEST_CASE("Benchmark raw JSON parsing", "[gltf-benchmark]") { }; } -TEST_CASE("Benchmark massive gltf file", "[base64-benchmark]") { +TEST_CASE("Benchmark massive gltf file", "[gltf-benchmark]") { if (!std::filesystem::exists(bistroPath / "bistro.gltf")) { // Bistro is not part of gltf-Sample-Models, and therefore not always available. SKIP("Amazon's Bistro (GLTF) is required for this benchmark.");