Accessor tools

fastgltf provides a utility header for working with accessors. 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 header was written by forenoonwatch with the help of Eearslya and me.

All related functions are templated and take T as an argument. This type has to be 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, glm::vec3 would be a vector of 3 floats, which would be defined like this:

template <>
struct fastgltf::ElementTraits<glm::vec3> : fastgltf::ElementTraitsBase<glm::vec3, AccessorType::Vec3, float> {};

Note

Note that, for glm types, there is a header with all pre-defined types shipped with fastgltf: fastgltf/glm_element_traits.hpp. This header includes the ElementTraits definition for all relevant glm types.

Warning

Note that, 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.

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.

Warning

doxygenfunction: Unable to resolve function “copyFromAccessor” with arguments (const Asset&, const Accessor&, void*, const BufferDataAdapter&) in doxygen xml output for project “fastgltf” from directory: /home/docs/checkouts/readthedocs.org/user_builds/fastgltf/checkouts/v0.7.x/build/docs/doxygen/xml. Potential matches:

- template<typename ElementType, std::size_t TargetStride = sizeof(ElementType), typename BufferDataAdapter = DefaultBufferDataAdapter> void 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

Public Functions

inline auto operator()(const Buffer &buffer) const

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 fastgltf::Buffer& buffer) const {
    return fileBytes.data();
});

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<glm::vec3>(
      asset, positionAccessor, [&](glm::vec3 pos, std::size_t idx) {
   vertices[idx].position = pos;
   vertices[idx].uv = glm::vec2();
});