From 8ad84e34c101bc89e71cd438268c923b3b9eac80 Mon Sep 17 00:00:00 2001 From: sean Date: Sun, 23 Jun 2024 01:34:12 +0200 Subject: [PATCH] Add: Accessor function to copy component-wise --- include/fastgltf/tools.hpp | 102 ++++++++++++++++++++++++++++++------- tests/accessor_tests.cpp | 53 +++++++++++++++---- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/include/fastgltf/tools.hpp b/include/fastgltf/tools.hpp index cad8d7966..1ffc19a0c 100644 --- a/include/fastgltf/tools.hpp +++ b/include/fastgltf/tools.hpp @@ -288,21 +288,46 @@ constexpr DestType convertComponent(const SourceType& source, bool normalized) { return static_cast(source); } -template -constexpr DestType convertComponent(const std::byte* bytes, bool normalized) { - if constexpr (isMatrix(ElementAccessorType)) { - const auto rowCount = getElementRowCount(ElementAccessorType); - const auto componentSize = sizeof(SourceType); - if constexpr ((rowCount * componentSize) % 4 != 0) { - // There's only four cases where this happens, but the glTF spec requires us to insert some padding for each column. - auto index = Index + (Index / rowCount) * (4 - (rowCount % 4)); - return convertComponent(deserializeComponent(bytes, index), normalized); - } else { - return convertComponent(deserializeComponent(bytes, Index), normalized); - } - } else { - return convertComponent(deserializeComponent(bytes, Index), normalized); - } +template +constexpr DestType convertComponent(const std::byte* bytes, std::size_t index, AccessorType accessorType, bool normalized) { + if (isMatrix(accessorType)) { + const auto rowCount = getElementRowCount(accessorType); + const auto componentSize = sizeof(SourceType); + if ((rowCount * componentSize) % 4 != 0) { + // There's only four cases where this happens, but the glTF spec requires us to insert some padding for each column. + auto paddedIndex = index + (index / rowCount) * (4 - (rowCount % 4)); + return convertComponent(deserializeComponent(bytes, paddedIndex), normalized); + } + + return convertComponent(deserializeComponent(bytes, index), normalized); + } + + return convertComponent(deserializeComponent(bytes, index), normalized); +} + +template +constexpr DestType getAccessorComponentAt(ComponentType componentType, AccessorType type, const std::byte* bytes, std::size_t componentIdx, bool normalized) { + switch (componentType) { + case ComponentType::Byte: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::UnsignedByte: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::Short: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::UnsignedShort: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::Int: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::UnsignedInt: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::Float: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::Double: + return internal::convertComponent(bytes, componentIdx, type, normalized); + case ComponentType::Invalid: + default: + return DestType {}; + } } template @@ -313,11 +338,11 @@ constexpr ElementType convertAccessorElement(const std::byte* bytes, bool normal using DestType = typename ElementTraits::component_type; static_assert(std::is_arithmetic_v, "Accessor traits must provide a valid component type"); - const auto accessorType = ElementTraits::type; + constexpr auto accessorType = ElementTraits::type; if constexpr (std::is_aggregate_v) { - return {convertComponent(bytes, normalized)...}; + return {convertComponent(bytes, I, accessorType, normalized)...}; } else { - return ElementType(convertComponent(bytes, normalized)...); + return ElementType(convertComponent(bytes, I, accessorType, normalized)...); } } @@ -802,6 +827,47 @@ void copyFromAccessor(const Asset& asset, const Accessor& accessor, void* dest, } } +/** + * This function allows copying each component into a linear list, instead of copying per-element, + * while still performing the correct conversions for the destination type. + * It is advised to *not* use this function unless necessary, like for example when implementing + * a generic animation interface. + */ +FASTGLTF_EXPORT template +void copyComponentsFromAccessor(const Asset& asset, const Accessor& accessor, void* dest, const BufferDataAdapter& adapter = {}) { + constexpr auto DestType = ComponentTypeConverter::type; + + assert((!bool(accessor.sparse) || accessor.sparse->count == 0) && "copyComponentsFromAccessor currently does not support sparse accessors."); + + auto* dstBytes = static_cast(dest); + + auto elemSize = getElementByteSize(accessor.type, accessor.componentType); + auto componentCount = getNumComponents(accessor.type); + + auto& view = asset.bufferViews[*accessor.bufferViewIndex]; + auto srcStride = view.byteStride.value_or(elemSize); + + auto srcBytes = adapter(asset, *accessor.bufferViewIndex).subspan(accessor.byteOffset); + + if (accessor.componentType == DestType && !accessor.normalized && !isMatrix(accessor.type)) { + if (srcStride == elemSize) { + std::memcpy(dest, srcBytes.data(), elemSize * accessor.count); + } else { + for (std::size_t i = 0; i < accessor.count; ++i) { + std::memcpy(dstBytes + elemSize * i, &srcBytes[srcStride * i], elemSize); + } + } + } else { + for (std::size_t i = 0; i < accessor.count; ++i) { + for (std::size_t j = 0; j < componentCount; ++j) { + auto* pDest = reinterpret_cast(dstBytes + elemSize * i) + j; + *pDest = internal::getAccessorComponentAt( + accessor.componentType, accessor.type, &srcBytes[i * srcStride], j, accessor.normalized); + } + } + } +} + /** * Computes the transform matrix for a given node, and multiplies the given base with that matrix. */ diff --git a/tests/accessor_tests.cpp b/tests/accessor_tests.cpp index 907f0f824..f60ccbb6c 100644 --- a/tests/accessor_tests.cpp +++ b/tests/accessor_tests.cpp @@ -5,20 +5,19 @@ #include #include "gltf_path.hpp" -static const std::byte* getBufferData(const fastgltf::Buffer& buffer) { - const std::byte* result = nullptr; - - std::visit(fastgltf::visitor { - [](auto&) {}, +static auto getBufferData(const fastgltf::Buffer& buffer) { + return std::visit(fastgltf::visitor { + [](auto&) -> const std::byte* { + assert(false); + return nullptr; + }, [&](const fastgltf::sources::Array& vec) { - result = reinterpret_cast(vec.bytes.data()); + return reinterpret_cast(vec.bytes.data()); }, [&](const fastgltf::sources::ByteView& bv) { - result = bv.bytes.data(); + return bv.bytes.data(); }, }, buffer.data); - - return result; } TEST_CASE("Test data type conversion", "[gltf-tools]") { @@ -67,6 +66,12 @@ TEST_CASE("Test matrix data padding", "[gltf-tools]") { REQUIRE(umat2[0] == fastgltf::math::fvec2(1, 2)); REQUIRE(umat2[1] == fastgltf::math::fvec2(3, 4)); + for (std::size_t i = 0; i < fastgltf::getNumComponents(fastgltf::AccessorType::Mat2); ++i) { + auto val = fastgltf::internal::getAccessorComponentAt( + fastgltf::ComponentType::UnsignedShort, fastgltf::AccessorType::Mat2, reinterpret_cast(unpaddedMat2.data()), i, false); + REQUIRE(std::uint16_t(i + 1) == val); + } + // This will simulate a padded 2x2 matrix with the correct 4-byte padding per column std::array paddedMat2 {{ 1, 2, 0, 0, @@ -79,6 +84,12 @@ TEST_CASE("Test matrix data padding", "[gltf-tools]") { REQUIRE(mat2[0] == fastgltf::math::fvec2(1, 2)); REQUIRE(mat2[1] == fastgltf::math::fvec2(3, 4)); + for (std::size_t i = 0; i < fastgltf::getNumComponents(fastgltf::AccessorType::Mat2); ++i) { + auto val = fastgltf::internal::getAccessorComponentAt( + fastgltf::ComponentType::UnsignedByte, fastgltf::AccessorType::Mat2, reinterpret_cast(paddedMat2.data()), i, false); + REQUIRE(std::uint8_t(i + 1) == val); + } + std::array paddedMat3 {{ 1, 2, 3, 0, 4, 5, 6, 0, @@ -92,6 +103,12 @@ TEST_CASE("Test matrix data padding", "[gltf-tools]") { REQUIRE(mat3[1] == fastgltf::math::fvec3(4, 5, 6)); REQUIRE(mat3[2] == fastgltf::math::fvec3(7, 8, 9)); + for (std::size_t i = 0; i < fastgltf::getNumComponents(fastgltf::AccessorType::Mat3); ++i) { + auto val = fastgltf::internal::getAccessorComponentAt( + fastgltf::ComponentType::UnsignedByte, fastgltf::AccessorType::Mat3, reinterpret_cast(paddedMat3.data()), i, false); + REQUIRE(std::uint8_t(i + 1) == val); + } + // This now uses 16-bit shorts for the component types. std::array padded2BMat3 {{ 1, 2, 3, 0, @@ -105,6 +122,12 @@ TEST_CASE("Test matrix data padding", "[gltf-tools]") { REQUIRE(mat3_2[0] == fastgltf::math::fvec3(1, 2, 3)); REQUIRE(mat3_2[1] == fastgltf::math::fvec3(4, 5, 6)); REQUIRE(mat3_2[2] == fastgltf::math::fvec3(7, 8, 9)); + + for (std::size_t i = 0; i < fastgltf::getNumComponents(fastgltf::AccessorType::Mat3); ++i) { + auto val = fastgltf::internal::getAccessorComponentAt( + fastgltf::ComponentType::UnsignedShort, fastgltf::AccessorType::Mat3, reinterpret_cast(padded2BMat3.data()), i, false); + REQUIRE(std::uint16_t(i + 1) == val); + } } TEST_CASE("Test accessor", "[gltf-tools]") { @@ -181,6 +204,18 @@ TEST_CASE("Test accessor", "[gltf-tools]") { } REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(fastgltf::math::fvec3)) == 0); } + + SECTION("copyComponentsFromAccessor") { + auto componentCount = fastgltf::getNumComponents(secondAccessor.type); + auto dstCopy = std::make_unique(secondAccessor.count * componentCount); + fastgltf::copyComponentsFromAccessor(asset.get(), secondAccessor, dstCopy.get()); + fastgltf::iterateAccessorWithIndex(asset.get(), secondAccessor, [&](auto&& p1, std::size_t idx) { + auto p2 = fastgltf::math::fvec3(dstCopy.get()[idx * componentCount + 0], + dstCopy.get()[idx * componentCount + 1], + dstCopy.get()[idx * componentCount + 2]); + REQUIRE(p1 == p2); + }); + } } }