Skip to content

Commit

Permalink
Add: Accessor function to copy component-wise
Browse files Browse the repository at this point in the history
  • Loading branch information
spnda committed Jun 22, 2024
1 parent 9068102 commit 8ad84e3
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 27 deletions.
102 changes: 84 additions & 18 deletions include/fastgltf/tools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,46 @@ constexpr DestType convertComponent(const SourceType& source, bool normalized) {
return static_cast<DestType>(source);
}

template <typename DestType, typename SourceType, AccessorType ElementAccessorType, std::size_t Index>
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<DestType>(deserializeComponent<SourceType>(bytes, index), normalized);
} else {
return convertComponent<DestType>(deserializeComponent<SourceType>(bytes, Index), normalized);
}
} else {
return convertComponent<DestType>(deserializeComponent<SourceType>(bytes, Index), normalized);
}
template <typename DestType, typename SourceType>
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<DestType>(deserializeComponent<SourceType>(bytes, paddedIndex), normalized);
}

return convertComponent<DestType>(deserializeComponent<SourceType>(bytes, index), normalized);
}

return convertComponent<DestType>(deserializeComponent<SourceType>(bytes, index), normalized);
}

template <typename DestType>
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<DestType, std::int8_t>(bytes, componentIdx, type, normalized);
case ComponentType::UnsignedByte:
return internal::convertComponent<DestType, std::uint8_t>(bytes, componentIdx, type, normalized);
case ComponentType::Short:
return internal::convertComponent<DestType, std::int16_t>(bytes, componentIdx, type, normalized);
case ComponentType::UnsignedShort:
return internal::convertComponent<DestType, std::uint16_t>(bytes, componentIdx, type, normalized);
case ComponentType::Int:
return internal::convertComponent<DestType, std::int32_t>(bytes, componentIdx, type, normalized);
case ComponentType::UnsignedInt:
return internal::convertComponent<DestType, std::uint32_t>(bytes, componentIdx, type, normalized);
case ComponentType::Float:
return internal::convertComponent<DestType, float>(bytes, componentIdx, type, normalized);
case ComponentType::Double:
return internal::convertComponent<DestType, double>(bytes, componentIdx, type, normalized);
case ComponentType::Invalid:
default:
return DestType {};
}
}

template <typename ElementType, typename SourceType, std::size_t... I>
Expand All @@ -313,11 +338,11 @@ constexpr ElementType convertAccessorElement(const std::byte* bytes, bool normal
using DestType = typename ElementTraits<ElementType>::component_type;
static_assert(std::is_arithmetic_v<DestType>, "Accessor traits must provide a valid component type");

const auto accessorType = ElementTraits<ElementType>::type;
constexpr auto accessorType = ElementTraits<ElementType>::type;
if constexpr (std::is_aggregate_v<ElementType>) {
return {convertComponent<DestType, SourceType, accessorType, I>(bytes, normalized)...};
return {convertComponent<DestType, SourceType>(bytes, I, accessorType, normalized)...};
} else {
return ElementType(convertComponent<DestType, SourceType, accessorType, I>(bytes, normalized)...);
return ElementType(convertComponent<DestType, SourceType>(bytes, I, accessorType, normalized)...);
}
}

Expand Down Expand Up @@ -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 <typename ComponentType, typename BufferDataAdapter = DefaultBufferDataAdapter>
void copyComponentsFromAccessor(const Asset& asset, const Accessor& accessor, void* dest, const BufferDataAdapter& adapter = {}) {
constexpr auto DestType = ComponentTypeConverter<ComponentType>::type;

assert((!bool(accessor.sparse) || accessor.sparse->count == 0) && "copyComponentsFromAccessor currently does not support sparse accessors.");

auto* dstBytes = static_cast<std::byte*>(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<ComponentType*>(dstBytes + elemSize * i) + j;
*pDest = internal::getAccessorComponentAt<ComponentType>(
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.
*/
Expand Down
53 changes: 44 additions & 9 deletions tests/accessor_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@
#include <fastgltf/tools.hpp>
#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<const std::byte*>(vec.bytes.data());
return reinterpret_cast<const std::byte*>(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]") {
Expand Down Expand Up @@ -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<std::uint16_t>(
fastgltf::ComponentType::UnsignedShort, fastgltf::AccessorType::Mat2, reinterpret_cast<const std::byte*>(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<std::uint8_t, 8> paddedMat2 {{
1, 2, 0, 0,
Expand All @@ -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<std::uint8_t>(
fastgltf::ComponentType::UnsignedByte, fastgltf::AccessorType::Mat2, reinterpret_cast<const std::byte*>(paddedMat2.data()), i, false);
REQUIRE(std::uint8_t(i + 1) == val);
}

std::array<std::uint8_t, 12> paddedMat3 {{
1, 2, 3, 0,
4, 5, 6, 0,
Expand All @@ -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<std::uint8_t>(
fastgltf::ComponentType::UnsignedByte, fastgltf::AccessorType::Mat3, reinterpret_cast<const std::byte*>(paddedMat3.data()), i, false);
REQUIRE(std::uint8_t(i + 1) == val);
}

// This now uses 16-bit shorts for the component types.
std::array<std::uint16_t, 12> padded2BMat3 {{
1, 2, 3, 0,
Expand All @@ -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<std::uint16_t>(
fastgltf::ComponentType::UnsignedShort, fastgltf::AccessorType::Mat3, reinterpret_cast<const std::byte*>(padded2BMat3.data()), i, false);
REQUIRE(std::uint16_t(i + 1) == val);
}
}

TEST_CASE("Test accessor", "[gltf-tools]") {
Expand Down Expand Up @@ -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<float>") {
auto componentCount = fastgltf::getNumComponents(secondAccessor.type);
auto dstCopy = std::make_unique<float[]>(secondAccessor.count * componentCount);
fastgltf::copyComponentsFromAccessor<float>(asset.get(), secondAccessor, dstCopy.get());
fastgltf::iterateAccessorWithIndex<fastgltf::math::fvec3>(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);
});
}
}
}

Expand Down

0 comments on commit 8ad84e3

Please sign in to comment.