Files
QuaternionEngine/third_party/fastgltf/include/fastgltf/tools.hpp

654 lines
25 KiB
C++

/*
* Copyright (C) 2022 - 2023 spnda
* This file is part of fastgltf <https://github.com/spnda/fastgltf>.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <cstring>
#include <iterator>
#include "types.hpp"
namespace fastgltf {
template <typename>
struct ComponentTypeConverter;
template<>
struct ComponentTypeConverter<std::int8_t> {
static constexpr auto type = ComponentType::Byte;
};
template<>
struct ComponentTypeConverter<std::uint8_t> {
static constexpr auto type = ComponentType::UnsignedByte;
};
template<>
struct ComponentTypeConverter<std::int16_t> {
static constexpr auto type = ComponentType::Short;
};
template<>
struct ComponentTypeConverter<std::uint16_t> {
static constexpr auto type = ComponentType::UnsignedShort;
};
template<>
struct ComponentTypeConverter<std::int32_t> {
static constexpr auto type = ComponentType::Int;
};
template<>
struct ComponentTypeConverter<std::uint32_t> {
static constexpr auto type = ComponentType::UnsignedInt;
};
template<>
struct ComponentTypeConverter<float> {
static constexpr auto type = ComponentType::Float;
};
template<>
struct ComponentTypeConverter<double> {
static constexpr auto type = ComponentType::Double;
};
template <typename ElementType, AccessorType EnumAccessorType, typename ComponentType = ElementType>
struct ElementTraitsBase {
using element_type = ElementType;
using component_type = ComponentType;
static constexpr auto type = EnumAccessorType;
static constexpr auto enum_component_type = ComponentTypeConverter<ComponentType>::type;
};
template <typename>
struct ElementTraits;
template<>
struct ElementTraits<std::int8_t> : ElementTraitsBase<std::int8_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<std::uint8_t> : ElementTraitsBase<std::uint8_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<std::int16_t> : ElementTraitsBase<std::int16_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<std::uint16_t> : ElementTraitsBase<std::uint16_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<std::int32_t> : ElementTraitsBase<std::int32_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<std::uint32_t> : ElementTraitsBase<std::uint32_t, AccessorType::Scalar> {};
template<>
struct ElementTraits<float> : ElementTraitsBase<float, AccessorType::Scalar> {};
template<>
struct ElementTraits<double> : ElementTraitsBase<double, AccessorType::Scalar> {};
#if FASTGLTF_HAS_CONCEPTS
template <typename ElementType>
concept Element = std::is_arithmetic_v<typename ElementTraits<ElementType>::component_type>
&& ElementTraits<ElementType>::type != AccessorType::Invalid
&& ElementTraits<ElementType>::enum_component_type != ComponentType::Invalid
&& std::is_default_constructible_v<ElementType>
&& std::is_constructible_v<ElementType>
&& std::is_move_assignable_v<ElementType>;
#endif
namespace internal {
template <typename DestType, typename SourceType>
constexpr DestType convertComponent(const SourceType& source, bool normalized) {
if (normalized) {
if constexpr (std::is_floating_point_v<SourceType> && std::is_integral_v<DestType>) {
// float -> int conversion
return static_cast<DestType>(std::round(source * static_cast<SourceType>(std::numeric_limits<DestType>::max())));
} else if constexpr (std::is_integral_v<SourceType> && std::is_floating_point_v<DestType>) {
// int -> float conversion
DestType minValue;
if constexpr (std::is_signed_v<DestType>) {
minValue = static_cast<DestType>(-1.0);
} else {
minValue = static_cast<DestType>(0.0);
}
// We have to use max here because for uchar -> float we could have -128 but 1.0 should represent 127,
// which is why -128 and -127 both equate to 1.0.
return fastgltf::max(static_cast<DestType>(source) / static_cast<DestType>(std::numeric_limits<SourceType>::max()),
minValue);
}
}
return static_cast<DestType>(source);
}
template <typename SourceType, typename DestType, std::size_t Index>
constexpr DestType convertComponent(const std::byte* bytes, bool normalized) {
return convertComponent<DestType>(reinterpret_cast<const SourceType*>(bytes)[Index], normalized);
}
template <typename ElementType, typename SourceType, std::size_t... I>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
constexpr ElementType convertAccessorElement(const std::byte* bytes, bool normalized, std::index_sequence<I...>) {
using DestType = typename ElementTraits<ElementType>::component_type;
static_assert(std::is_arithmetic_v<DestType>, "Accessor traits must provide a valid component type");
if constexpr (std::is_aggregate_v<ElementType>) {
return {convertComponent<SourceType, DestType, I>(bytes, normalized)...};
} else {
return ElementType{convertComponent<SourceType, DestType, I>(bytes, normalized)...};
}
}
template <typename ElementType,
typename Seq = std::make_index_sequence<getNumComponents(ElementTraits<ElementType>::type)>>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
ElementType getAccessorElementAt(ComponentType componentType, const std::byte* bytes, bool normalized = false) {
switch (componentType) {
case ComponentType::Byte:
return convertAccessorElement<ElementType, std::int8_t>(bytes, normalized, Seq{});
case ComponentType::UnsignedByte:
return convertAccessorElement<ElementType, std::uint8_t>(bytes, normalized, Seq{});
case ComponentType::Short:
return convertAccessorElement<ElementType, std::int16_t>(bytes, normalized, Seq{});
case ComponentType::UnsignedShort:
return convertAccessorElement<ElementType, std::uint16_t>(bytes, normalized, Seq{});
case ComponentType::Int:
return convertAccessorElement<ElementType, std::int32_t>(bytes, normalized, Seq{});
case ComponentType::UnsignedInt:
return convertAccessorElement<ElementType, std::uint32_t>(bytes, normalized, Seq{});
case ComponentType::Float:
return convertAccessorElement<ElementType, float>(bytes, normalized, Seq{});
case ComponentType::Double:
return convertAccessorElement<ElementType, double>(bytes, normalized, Seq{});
case ComponentType::Invalid:
default:
return ElementType{};
}
}
// Performs a binary search for the index into the sparse index list whose value matches the desired index
template <typename ElementType>
bool findSparseIndex(const std::byte* bytes, std::size_t indexCount, std::size_t desiredIndex,
std::size_t& resultIndex) {
auto* elements = reinterpret_cast<const ElementType*>(bytes);
auto count = indexCount;
resultIndex = 0;
while (count > 0) {
auto step = count / 2;
auto index = resultIndex + step;
if (elements[index] < static_cast<ElementType>(desiredIndex)) {
resultIndex = index + 1;
count -= step + 1;
} else {
count = step;
}
}
return resultIndex < indexCount && elements[resultIndex] == static_cast<ElementType>(desiredIndex);
}
// Finds the index of the nearest sparse index to the desired index
inline bool findSparseIndex(ComponentType componentType, const std::byte* bytes, std::size_t indexCount,
std::size_t desiredIndex, std::size_t& resultIndex) {
switch (componentType) {
case ComponentType::Byte:
return findSparseIndex<std::int8_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::UnsignedByte:
return findSparseIndex<std::uint8_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::Short:
return findSparseIndex<std::int16_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::UnsignedShort:
return findSparseIndex<std::uint16_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::Int:
return findSparseIndex<std::int32_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::UnsignedInt:
return findSparseIndex<std::uint32_t>(bytes, indexCount, desiredIndex, resultIndex);
case ComponentType::Float:
case ComponentType::Double:
case ComponentType::Invalid:
return false;
}
return false;
}
} // namespace internal
struct DefaultBufferDataAdapter {
const std::byte* operator()(const Buffer& buffer) const {
return std::visit(visitor {
[](auto&) -> const std::byte* {
return nullptr;
},
[&](const sources::Vector& vec) {
return reinterpret_cast<const std::byte*>(vec.bytes.data());
},
[&](const sources::ByteView& bv) {
return bv.bytes.data();
},
}, buffer.data);
}
};
template <typename ElementType, typename BufferDataAdapter>
class IterableAccessor;
template <typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
class AccessorIterator {
protected:
const IterableAccessor<ElementType, BufferDataAdapter>* accessor;
std::size_t idx;
std::size_t sparseIdx = 0;
std::size_t nextSparseIndex = 0;
public:
using value_type = ElementType;
using reference = ElementType&;
using pointer = ElementType*;
using difference_type = std::ptrdiff_t;
// This iterator isn't truly random access (as per the C++ definition), but we do want to support
// some things that these come with (e.g. std::distance using operator-).
using iterator_category = std::random_access_iterator_tag;
AccessorIterator(const IterableAccessor<ElementType, BufferDataAdapter>* accessor, std::size_t idx = 0)
: accessor(accessor), idx(idx) {
if (accessor->accessor.sparse.has_value()) {
// Get the first sparse index.
nextSparseIndex = internal::getAccessorElementAt<std::uint32_t>(accessor->indexComponentType,
accessor->indicesBytes + accessor->indexStride * sparseIdx);
}
}
AccessorIterator& operator++() noexcept {
++idx;
return *this;
}
AccessorIterator operator++(int) noexcept {
auto x = *this;
++(*this);
return x;
}
[[nodiscard]] difference_type operator-(const AccessorIterator& other) const noexcept {
return static_cast<difference_type>(idx - other.idx);
}
[[nodiscard]] bool operator==(const AccessorIterator& iterator) const noexcept {
// We don't compare sparse properties
return idx == iterator.idx &&
accessor->bufferBytes == iterator.accessor->bufferBytes &&
accessor->stride == iterator.accessor->stride &&
accessor->componentType == iterator.accessor->componentType;
}
[[nodiscard]] bool operator!=(const AccessorIterator& iterator) const noexcept {
return !(*this == iterator);
}
[[nodiscard]] ElementType operator*() noexcept {
if (accessor->accessor.sparse.has_value()) {
if (idx == nextSparseIndex) {
// Get the sparse value for this index
auto value = internal::getAccessorElementAt<ElementType>(accessor->componentType,
accessor->valuesBytes + accessor->valueStride * sparseIdx,
accessor->accessor.normalized);
// Find the next sparse index.
++sparseIdx;
if (sparseIdx < accessor->sparseCount) {
nextSparseIndex = internal::getAccessorElementAt<std::uint32_t>(accessor->indexComponentType,
accessor->indicesBytes + accessor->indexStride * sparseIdx);
}
return value;
}
}
return internal::getAccessorElementAt<ElementType>(accessor->componentType,
accessor->bufferBytes + idx * accessor->stride,
accessor->accessor.normalized);
}
};
template <typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
class IterableAccessor {
friend class AccessorIterator<ElementType, BufferDataAdapter>;
const Asset& asset;
const Accessor& accessor;
const std::byte* bufferBytes;
std::size_t stride;
fastgltf::ComponentType componentType;
// Data needed for sparse accessors
fastgltf::ComponentType indexComponentType;
const std::byte* indicesBytes;
const std::byte* valuesBytes;
std::size_t indexStride;
std::size_t valueStride;
std::size_t sparseCount;
public:
using iterator = AccessorIterator<ElementType, BufferDataAdapter>;
explicit IterableAccessor(const Asset& asset, const Accessor& accessor, const BufferDataAdapter& adapter) : asset(asset), accessor(accessor) {
componentType = accessor.componentType;
const auto& view = asset.bufferViews[*accessor.bufferViewIndex];
stride = view.byteStride ? *view.byteStride : getElementByteSize(accessor.type, accessor.componentType);
bufferBytes = adapter(asset.buffers[view.bufferIndex]);
bufferBytes += view.byteOffset + accessor.byteOffset;
if (accessor.sparse.has_value()) {
const auto& indicesView = asset.bufferViews[accessor.sparse->indicesBufferView];
indicesBytes = adapter(asset.buffers[indicesView.bufferIndex])
+ indicesView.byteOffset + accessor.sparse->indicesByteOffset;
indexStride = getElementByteSize(AccessorType::Scalar, accessor.sparse->indexComponentType);
const auto& valuesView = asset.bufferViews[accessor.sparse->valuesBufferView];
valuesBytes = adapter(asset.buffers[valuesView.bufferIndex])
+ valuesView.byteOffset + accessor.sparse->valuesByteOffset;
// "The index of the bufferView with sparse values. The referenced buffer view MUST NOT
// have its target or byteStride properties defined."
valueStride = getElementByteSize(accessor.type, accessor.componentType);
indexComponentType = accessor.sparse->indexComponentType;
sparseCount = accessor.sparse->count;
}
}
[[nodiscard]] iterator begin() const noexcept {
return iterator(this, 0);
}
[[nodiscard]] iterator end() const noexcept {
return iterator(this, accessor.count);
}
};
template <typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
ElementType getAccessorElement(const Asset& asset, const Accessor& accessor, size_t index,
const BufferDataAdapter& adapter = {}) {
using Traits = ElementTraits<ElementType>;
static_assert(Traits::type != AccessorType::Invalid, "Accessor traits must provide a valid Accessor Type");
static_assert(std::is_default_constructible_v<ElementType>, "Element type must be default constructible");
static_assert(std::is_constructible_v<ElementType>, "Element type must be constructible");
static_assert(std::is_move_assignable_v<ElementType>, "Element type must be move-assignable");
if (accessor.sparse) {
const auto& indicesView = asset.bufferViews[accessor.sparse->indicesBufferView];
auto* indicesBytes = adapter(asset.buffers[indicesView.bufferIndex])
+ indicesView.byteOffset + accessor.sparse->indicesByteOffset;
const auto& valuesView = asset.bufferViews[accessor.sparse->valuesBufferView];
auto* valuesBytes = adapter(asset.buffers[valuesView.bufferIndex])
+ valuesView.byteOffset + accessor.sparse->valuesByteOffset;
// "The index of the bufferView with sparse values. The referenced buffer view MUST NOT
// have its target or byteStride properties defined."
auto valueStride = getElementByteSize(accessor.type, accessor.componentType);
std::size_t sparseIndex{};
if (internal::findSparseIndex(accessor.sparse->indexComponentType, indicesBytes, accessor.sparse->count,
index, sparseIndex)) {
return internal::getAccessorElementAt<ElementType>(accessor.componentType,
valuesBytes + valueStride * sparseIndex,
accessor.normalized);
}
}
// 5.1.1. accessor.bufferView
// The index of the buffer view. When undefined, the accessor MUST be initialized with zeros; sparse
// property or extensions MAY override zeros with actual values.
if (!accessor.bufferViewIndex) {
if constexpr (std::is_aggregate_v<ElementType>) {
return {};
} else {
return ElementType{};
}
}
const auto& view = asset.bufferViews[*accessor.bufferViewIndex];
auto stride = view.byteStride ? *view.byteStride : getElementByteSize(accessor.type, accessor.componentType);
auto* bytes = adapter(asset.buffers[view.bufferIndex]);
bytes += view.byteOffset + accessor.byteOffset;
return internal::getAccessorElementAt<ElementType>(accessor.componentType, bytes + index * stride, accessor.normalized);
}
template<typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
IterableAccessor<ElementType, BufferDataAdapter> iterateAccessor(const Asset& asset, const Accessor& accessor, const BufferDataAdapter& adapter = {}) {
return IterableAccessor<ElementType, BufferDataAdapter>(asset, accessor, adapter);
}
template <typename ElementType, typename Functor, typename BufferDataAdapter = DefaultBufferDataAdapter>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
void iterateAccessor(const Asset& asset, const Accessor& accessor, Functor&& func,
const BufferDataAdapter& adapter = {}) {
using Traits = ElementTraits<ElementType>;
static_assert(Traits::type != AccessorType::Invalid, "Accessor traits must provide a valid accessor type");
static_assert(Traits::enum_component_type != ComponentType::Invalid, "Accessor traits must provide a valid component type");
static_assert(std::is_default_constructible_v<ElementType>, "Element type must be default constructible");
static_assert(std::is_constructible_v<ElementType>, "Element type must be constructible");
static_assert(std::is_move_assignable_v<ElementType>, "Element type must be move-assignable");
if (accessor.type != Traits::type) {
return;
}
if (accessor.sparse && accessor.sparse->count > 0) {
auto& indicesView = asset.bufferViews[accessor.sparse->indicesBufferView];
auto* indicesBytes = adapter(asset.buffers[indicesView.bufferIndex])
+ indicesView.byteOffset + accessor.sparse->indicesByteOffset;
auto indexStride = getElementByteSize(AccessorType::Scalar, accessor.sparse->indexComponentType);
auto& valuesView = asset.bufferViews[accessor.sparse->valuesBufferView];
auto* valuesBytes = adapter(asset.buffers[valuesView.bufferIndex])
+ valuesView.byteOffset + accessor.sparse->valuesByteOffset;
// "The index of the bufferView with sparse values. The referenced buffer view MUST NOT
// have its target or byteStride properties defined."
auto valueStride = getElementByteSize(accessor.type, accessor.componentType);
const std::byte* srcBytes = nullptr;
std::size_t srcStride = 0;
// 5.1.1. accessor.bufferView
// The index of the buffer view. When undefined, the accessor MUST be initialized with zeros; sparse
// property or extensions MAY override zeros with actual values.
if (accessor.bufferViewIndex) {
auto& view = asset.bufferViews[*accessor.bufferViewIndex];
srcBytes = adapter(asset.buffers[view.bufferIndex]) + view.byteOffset + accessor.byteOffset;
srcStride = view.byteStride ? *view.byteStride
: getElementByteSize(accessor.type, accessor.componentType);
}
auto nextSparseIndex = internal::getAccessorElementAt<std::uint32_t>(
accessor.sparse->indexComponentType, indicesBytes);
std::size_t sparseIndexCount = 0;
for (std::size_t i = 0; i < accessor.count; ++i) {
if (i == nextSparseIndex) {
func(internal::getAccessorElementAt<ElementType>(accessor.componentType,
valuesBytes + valueStride * sparseIndexCount,
accessor.normalized));
++sparseIndexCount;
if (sparseIndexCount < accessor.sparse->count) {
nextSparseIndex = internal::getAccessorElementAt<std::uint32_t>(
accessor.sparse->indexComponentType, indicesBytes + indexStride * sparseIndexCount);
}
} else if (accessor.bufferViewIndex) {
func(internal::getAccessorElementAt<ElementType>(accessor.componentType,
srcBytes + srcStride * i,
accessor.normalized));
} else {
func(ElementType{});
}
}
return;
}
// 5.1.1. accessor.bufferView
// The index of the buffer view. When undefined, the accessor MUST be initialized with zeros; sparse
// property or extensions MAY override zeros with actual values.
if (!accessor.bufferViewIndex) {
for (std::size_t i = 0; i < accessor.count; ++i) {
func(ElementType{});
}
}
else {
auto& view = asset.bufferViews[*accessor.bufferViewIndex];
auto stride = view.byteStride ? *view.byteStride : getElementByteSize(accessor.type, accessor.componentType);
auto* bytes = adapter(asset.buffers[view.bufferIndex]);
bytes += view.byteOffset + accessor.byteOffset;
for (std::size_t i = 0; i < accessor.count; ++i) {
func(internal::getAccessorElementAt<ElementType>(accessor.componentType, bytes + i * stride, accessor.normalized));
}
}
}
template <typename ElementType, typename Functor, typename BufferDataAdapter = DefaultBufferDataAdapter>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
void iterateAccessorWithIndex(const Asset& asset, const Accessor& accessor, Functor&& func,
const BufferDataAdapter& adapter = {}) {
std::size_t idx = 0;
iterateAccessor<ElementType>(asset, accessor, [&](auto&& elementType) {
func(std::forward<ElementType>(elementType), idx++);
}, adapter);
}
template <typename ElementType, std::size_t TargetStride = sizeof(ElementType),
typename BufferDataAdapter = DefaultBufferDataAdapter>
#if FASTGLTF_HAS_CONCEPTS
requires Element<ElementType>
#endif
void copyFromAccessor(const Asset& asset, const Accessor& accessor, void* dest,
const BufferDataAdapter& adapter = {}) {
using Traits = ElementTraits<ElementType>;
static_assert(Traits::type != AccessorType::Invalid, "Accessor traits must provide a valid accessor type");
static_assert(Traits::enum_component_type != ComponentType::Invalid, "Accessor traits must provide a valid component type");
static_assert(std::is_default_constructible_v<ElementType>, "Element type must be default constructible");
static_assert(std::is_constructible_v<ElementType>, "Element type must be constructible");
static_assert(std::is_move_assignable_v<ElementType>, "Element type must be move-assignable");
if (accessor.type != Traits::type) {
return;
}
auto* dstBytes = reinterpret_cast<std::byte*>(dest);
if (accessor.sparse && accessor.sparse->count > 0) {
return iterateAccessorWithIndex<ElementType>(asset, accessor, [&](auto&& value, std::size_t index) {
auto* pDest = reinterpret_cast<ElementType*>(dstBytes + TargetStride * index);
*pDest = std::forward<ElementType>(value);
}, adapter);
}
auto elemSize = getElementByteSize(accessor.type, accessor.componentType);
// 5.1.1. accessor.bufferView
// The index of the buffer view. When undefined, the accessor MUST be initialized with zeros; sparse
// property or extensions MAY override zeros with actual values.
if (!accessor.bufferViewIndex) {
if constexpr (std::is_trivially_copyable_v<ElementType>) {
if (TargetStride == elemSize) {
std::memset(dest, 0, elemSize * accessor.count);
} else {
for (std::size_t i = 0; i < accessor.count; ++i) {
std::memset(dstBytes + i * TargetStride, 0, elemSize);
}
}
} else {
for (std::size_t i = 0; i < accessor.count; ++i) {
auto* pDest = reinterpret_cast<ElementType*>(dstBytes + TargetStride * i);
if constexpr (std::is_aggregate_v<ElementType>) {
*pDest = {};
} else {
*pDest = ElementType{};
}
}
}
return;
}
auto& view = asset.bufferViews[*accessor.bufferViewIndex];
auto srcStride = view.byteStride ? *view.byteStride
: getElementByteSize(accessor.type, accessor.componentType);
auto* srcBytes = adapter(asset.buffers[view.bufferIndex]) + view.byteOffset + accessor.byteOffset;
// We have to perform normalization if the accessor is marked as containing normalized data, which is why
// we can't just memcpy then.
if (std::is_trivially_copyable_v<ElementType> && !accessor.normalized && accessor.componentType == Traits::enum_component_type) {
if (srcStride == elemSize && srcStride == TargetStride) {
std::memcpy(dest, srcBytes, elemSize * accessor.count);
} else {
for (std::size_t i = 0; i < accessor.count; ++i) {
std::memcpy(dstBytes + TargetStride * i, srcBytes + srcStride * i, elemSize);
}
}
} else {
for (std::size_t i = 0; i < accessor.count; ++i) {
auto* pDest = reinterpret_cast<ElementType*>(dstBytes + TargetStride * i);
*pDest = internal::getAccessorElementAt<ElementType>(accessor.componentType, srcBytes + srcStride * i);
}
}
}
} // namespace fastgltf