Decodes accessor data.
An accessor is glTF's typed view onto bytes: it names a buffer view, an
element type (VEC3), a component type (float), and a count. Everything
geometric in a glTF — positions, normals, indices, animation keyframes —
arrives through one.
Three details account for most of the difficulty, and all three are where naive readers go wrong:
Interleaved strides. A buffer view may declare byteStride, in which
case consecutive elements are not adjacent — positions and normals are
commonly woven together in one view so the GPU can read a whole vertex in
one pass. Reading elements back to back decodes a plausible-looking mixture
of two attributes.
Sparse substitution. An accessor may carry a sparse block that overrides some elements by index, on top of a base view — or with no base view at all, in which case the base is all zeros. Ignoring it yields a model that is subtly and silently wrong.
Normalised integers. With normalized: true, integer components encode
fractions: unsigned types map to 0.0..1.0, signed to -1.0..1.0 with the
most negative value clamped. Returning the raw integers gives colours in the
hundreds.
Non-finite floats
The BEAM has no float term for NaN or infinity, so a float32 component
holding one cannot be returned as a number. Those come back as :nan,
:infinity, and :neg_infinity. Exporters do emit them — a NaN in a
normal vector is a common artefact of a degenerate triangle — and raising
would make an otherwise readable file unreadable.
Summary
Types
A single component: a number, or an atom when a float is not finite.
A decoded element: a bare component for SCALAR, else a list of them.
The shape of a decoded accessor, as reported alongside raw binary output.
Functions
Decodes an accessor into tightly packed little-endian binary.
How many components an element type holds.
Component type name and byte size for a glTF component-type constant.
Decodes an accessor into a list of elements.
The byte size of one element, ignoring any stride.
Describes an accessor's shape without reading any data.
Types
@type component() :: number() | :nan | :infinity | :neg_infinity
A single component: a number, or an atom when a float is not finite.
A decoded element: a bare component for SCALAR, else a list of them.
@type layout() :: %{ type: String.t(), component: atom(), component_size: pos_integer(), components: pos_integer(), element_size: pos_integer(), count: non_neg_integer(), normalized: boolean() }
The shape of a decoded accessor, as reported alongside raw binary output.
Functions
@spec binary(map(), map(), [binary()], keyword()) :: {:ok, binary(), layout()} | {:error, Gltf.Error.t()}
Decodes an accessor into tightly packed little-endian binary.
The result is the accessor's own bytes with any interleaving removed and any sparse block applied — the form a GPU buffer wants. Nothing is converted, so normalised integers stay integers and non-finite floats keep their bits; the returned layout says how to interpret them.
@spec component_count(String.t()) :: {:ok, pos_integer()} | :error
How many components an element type holds.
iex> Gltf.Accessor.component_count("VEC3")
{:ok, 3}
iex> Gltf.Accessor.component_count("VEC7")
:error
@spec component_type(integer()) :: {:ok, {atom(), pos_integer()}} | :error
Component type name and byte size for a glTF component-type constant.
iex> Gltf.Accessor.component_type(5126)
{:ok, {:float32, 4}}
iex> Gltf.Accessor.component_type(9999)
:error
Decodes an accessor into a list of elements.
SCALAR accessors yield bare numbers; every other type yields lists.
For large vertex data, prefer binary/3 — building millions of boxed floats
only to hand them straight to a GPU is wasted work.
@spec element_size(String.t(), integer()) :: {:ok, pos_integer()} | :error
The byte size of one element, ignoring any stride.
iex> Gltf.Accessor.element_size("VEC3", 5126)
{:ok, 12}
@spec layout( map(), keyword() ) :: {:ok, layout()} | {:error, Gltf.Error.t()}
Describes an accessor's shape without reading any data.