Gltf.Accessor (Gltf v0.1.0)

Copy Markdown View Source

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

component()

@type component() :: number() | :nan | :infinity | :neg_infinity

A single component: a number, or an atom when a float is not finite.

element()

@type element() :: component() | [component()]

A decoded element: a bare component for SCALAR, else a list of them.

layout()

@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

binary(accessor, document, buffers, opts \\ [])

@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.

component_count(type)

@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

component_type(code)

@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

decode(accessor, document, buffers, opts \\ [])

@spec decode(map(), map(), [binary()], keyword()) ::
  {:ok, [element()]} | {:error, Gltf.Error.t()}

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.

element_size(type, component_type_code)

@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}

layout(accessor, opts \\ [])

@spec layout(
  map(),
  keyword()
) :: {:ok, layout()} | {:error, Gltf.Error.t()}

Describes an accessor's shape without reading any data.