Accessor tools
fastgltf provides utility functionality for working with accessors, found in the fastgltf/tools.hpp header.
The header contains various functions and utilities for reading, copying, and converting accessor data.
All of these tools also directly support sparse accessors to help add support for these without having to understand how they work.
This functionality was written by forenoonwatch with the help of Eearslya and me.
Warning
By default, these functions will only be able to load from buffers where the source is either a sources::ByteView, a sources::Array, or a sources::Vector.
For other data sources, you’ll need to provide a functor similar to the already provided DefaultBufferDataAdapter to the last parameter of each function.
For more detailed documentation about this see this section.
Warning
When an accessor contains, for example, Vec3 data, the functions will assert when you try to specify a type which is not a vector of 3.
That means that the type you specify in the template for T needs to have AccessorType::Vec3 specified for its ElementTraits.
ElementTraits
All related functions are templated and take T as an argument, which allows the functions to read data directly into your data types,
while converting the data correctly, if necessary.
This type has to have a ElementTraits specialization, which provides information about the vector properties and data properties.
Using this information, fastgltf can convert the accessor data into your preferred format.
For example, if you had a custom vector type like MyVec3, you’d just need to specialize the template for it like this:
template <>
struct fastgltf::ElementTraits<MyVec3> : fastgltf::ElementTraitsBase<MyVec3, AccessorType::Vec3, float> {};
Note
fastgltf includes these definitions for types from other math libraries. Currently, fastgltf supports
DirectXMath with fastgltf/dxmath_element_traits.hpp and glm with fastgltf/glm_element_traits.hpp.
Simply including these headers will allow the tools to convert to your preferred types directly.
These headers could also be used as a reference on how to add support for other types.
Note
fastgltf also includes its own vector and matrix types, which can be used instead.
These also have a ElementTraits specialization included in the standard header.
getAccessorElement
This function can be used to retrieve a single element from an accessor using an index. It handles sparse accessors and can properly convert the type.
-
template<typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
ElementType fastgltf::getAccessorElement(const Asset &asset, const Accessor &accessor, size_t index, const BufferDataAdapter &adapter = {})
iterateAccessor
Using iterateAccessor you can iterate over the data of an accessor using a lambda, similarly to std::for_each.
-
template<typename ElementType, typename Functor, typename BufferDataAdapter = DefaultBufferDataAdapter>
void fastgltf::iterateAccessor(const Asset &asset, const Accessor &accessor, Functor &&func, const BufferDataAdapter &adapter = {})
fastgltf::Primitive& primitive = ...;
std::vector<std::uint32_t> indices;
if (primitive.indicesAccessor.has_value()) {
auto& accessor = asset->accessors[primitive.indicesAccessor.value()];
indices.resize(accessor.count);
std::size_t idx = 0;
fastgltf::iterateAccessor<std::uint32_t>(asset.get(), accessor, [&](std::uint32_t index) {
indices[idx++] = index;
});
}
iterateAccessorWithIndex
Functionally identical to iterateAccessor, but provides you with the current index as the second parameter to the lambda.
-
template<typename ElementType, typename Functor, typename BufferDataAdapter = DefaultBufferDataAdapter>
void fastgltf::iterateAccessorWithIndex(const Asset &asset, const Accessor &accessor, Functor &&func, const BufferDataAdapter &adapter = {})
copyFromAccessor
This function essentially does a memcpy on the contents of the accessor data.
In cases where the ElementType is default-constructible, and the accessor type allows direct copying, this performs a direct memcpy.
Otherwise, this function properly respects normalization and sparse accessors while copying and converting the data.
-
template<typename ElementType, std::size_t TargetStride = sizeof(ElementType), typename BufferDataAdapter = DefaultBufferDataAdapter>
void fastgltf::copyFromAccessor(const Asset &asset, const Accessor &accessor, void *dest, const BufferDataAdapter &adapter = {})
Accessor iterators
fastgltf also provides C++ iterators over accessor data to support the syntactic sugar of C++11’s range-based for-loops.
These iterators can be obtained using iterateAccessor, and can be used like so:
-
template<typename ElementType, typename BufferDataAdapter = DefaultBufferDataAdapter>
IterableAccessor<ElementType, BufferDataAdapter> fastgltf::iterateAccessor(const Asset &asset, const Accessor &accessor, const BufferDataAdapter &adapter = {})
std::size_t idx = 0;
for (auto element : fastgltf::iterateAccessor(asset.get(), accessor)) {
array[idx++] = element;
}
BufferDataAdapter interface
The accessor tools acquire the binary data through this functional interface.
By default, fastgltf provides a DefaultBufferDataAdapter struct.
The accessor functions also default to using this class,
however it is important to note that this default interface only works with buffers or images that have a sources::ByteView, a sources::Array, or a sources::Vector in the DataSource member.
-
struct DefaultBufferDataAdapter
If you do not provide Options::LoadExternalBuffers to the Parser while loading the glTF,
external buffers will be available as sources::URI and will not work with the DefaultBufferDataAdapter.
Therefore, you’ll either have to set that option or provide a custom functional interface that properly returns a pointer to the memory.
As this is a functional interface it is possible to also use lambdas for this:
std::vector<std::byte> fileBytes;
std::vector<std::uint8_t> accessorData(accessor.count);
fastgltf::copyFromAccessor(asset.get(), accessor, accessorData.data(), [&](const Asset& asset, const std::size_t bufferViewIdx) const {
const auto& bufferView = asset.bufferViews[bufferViewIdx];
return span(fileBytes).subspan(bufferView.byteOffset, bufferView.byteLength);
});
Example: Loading primitive positions
The following snippet illustrates how one could potentially load vertex positions for a primitive into a OpenGL buffer using the accessor tools.
fastgltf::Primitive* primitive = ...;
// findAttribute returns a iterator into the underlying vector of primitive attributes.
// Note that the glTF spec requires every primitive to have a POSITION,
// so it's perfectly valid to assert that positionIt is never nullptr.
auto* positionIt = primitive->findAttribute("POSITION");
auto& positionAccessor = asset.accessors[positionIt->second];
if (!positionAccessor.bufferViewIndex.has_value())
continue;
// Create the vertex buffer for this primitive,
// and use the accessor tools to copy directly into the mapped buffer.
glCreateBuffers(1, &primitive.vertexBuffer);
glNamedBufferData(primitive.vertexBuffer,
positionAccessor.count * sizeof(Vertex), nullptr, GL_STATIC_DRAW);
auto* vertices = static_cast<Vertex*>(glMapNamedBuffer(primitive.vertexBuffer, GL_WRITE_ONLY));
// Iterates over the accessor (potentially handling any sparse accessors),
// and gives each vertex UV a default value, which need to be loaded separately.
fastgltf::iterateAccessorWithIndex<fastgltf::math::fvec3>(
asset, positionAccessor, [&](fastgltf::math::fvec3 pos, std::size_t idx) {
vertices[idx].position = pos;
vertices[idx].uv = fastgltf::math::fvec2();
});