Reads glTF 2.0 assets — .gltf JSON and .glb binary containers.
glTF is the interchange format essentially every 3D tool can write, which makes it the practical way to get geometry into or out of an Elixir service. This library reads one; it does not render, and it does not fetch anything over the network.
Reading
{:ok, gltf} = Gltf.read("model.glb")
Gltf.info(gltf)
#=> %{version: "2.0", generator: "Blender glTF 2.0 I/O", meshes: 3, ...}
{:ok, positions} = Gltf.attribute(gltf, 0, 0, "POSITION")
#=> [[0.0, 1.0, 0.0], [-1.0, -1.0, 0.0], ...]For anything vertex-sized, take the bytes rather than the boxed floats:
{:ok, bytes, layout} = Gltf.attribute_binary(gltf, 0, 0, "POSITION")bytes is tightly packed, little-endian, de-interleaved, with any sparse
block applied — which is what a GPU buffer, a file, or a socket wants.
What this handles that a quick parser does not
- Interleaved
byteStride— vertex attributes woven together in one buffer view, which read back to back decode as a mixture. - Sparse accessors — elements overridden by index, possibly with no base view at all.
- Normalised integers —
normalized: truemeans0..255is a fraction, not a colour channel in the hundreds. - Non-finite floats — the BEAM cannot hold NaN or infinity as a float
term, so those decode to
:nan,:infinityand:neg_infinityrather than raising on a file every other viewer opens. - Node transform order —
T * R * S, andmatrixwhen present wins. - Scene-graph cycles — reported, not looped on.
- Untrusted paths — an external buffer URI cannot escape the asset's own directory unless you say so.
What it does not handle
Draco and meshopt compression, and writing. Extension objects are preserved in the document but not interpreted. See the README for the scope argument.
Summary
Functions
Decodes accessor index into a list of elements.
Decodes accessor index into tightly packed little-endian binary.
Decodes one attribute of one primitive — "POSITION", "NORMAL",
"TEXCOORD_0", and so on.
As attribute/4, but returns packed binary and a layout.
The axis-aligned bounds of an accessor, as {min, max}.
The index list of a primitive, or {:ok, nil} when it draws unindexed.
A summary of what an asset contains.
Parses a glTF asset already in memory.
The primitives of mesh index.
Reads a glTF asset from disk.
Reads a glTF asset, raising on failure.
World transforms for the nodes of a scene, keyed by node index.
Types
Functions
@spec accessor(t(), non_neg_integer()) :: {:ok, [Gltf.Accessor.element()]} | {:error, Gltf.Error.t()}
Decodes accessor index into a list of elements.
SCALAR accessors give bare numbers; everything else gives lists.
@spec accessor_binary(t(), non_neg_integer()) :: {:ok, binary(), Gltf.Accessor.layout()} | {:error, Gltf.Error.t()}
Decodes accessor index into tightly packed little-endian binary.
Returns the bytes and a layout describing how to read them. Nothing is converted — normalised integers stay integers — because the point is to hand the bytes onward, not to look at them.
@spec attribute(t(), non_neg_integer(), non_neg_integer(), String.t()) :: {:ok, [Gltf.Accessor.element()]} | {:error, Gltf.Error.t()}
Decodes one attribute of one primitive — "POSITION", "NORMAL",
"TEXCOORD_0", and so on.
{:ok, positions} = Gltf.attribute(gltf, 0, 0, "POSITION")
@spec attribute_binary(t(), non_neg_integer(), non_neg_integer(), String.t()) :: {:ok, binary(), Gltf.Accessor.layout()} | {:error, Gltf.Error.t()}
As attribute/4, but returns packed binary and a layout.
@spec bounds(t(), non_neg_integer()) :: {:ok, {[number()], [number()]} | nil} | {:error, Gltf.Error.t()}
The axis-aligned bounds of an accessor, as {min, max}.
Taken from the accessor's own declared min/max when it has them — every
exporter writes them for positions, and reading three million vertices to
recompute what the file already states is waste. Falls back to decoding.
Returns {:ok, nil} for an accessor with no elements.
@spec indices(t(), non_neg_integer(), non_neg_integer()) :: {:ok, [non_neg_integer()] | nil} | {:error, Gltf.Error.t()}
The index list of a primitive, or {:ok, nil} when it draws unindexed.
The {:ok, nil} is not an oversight: a primitive without indices is
perfectly valid and means the vertices are already in draw order.
A summary of what an asset contains.
Cheap — it counts collections rather than decoding anything.
@spec parse( binary(), keyword() ) :: {:ok, t()} | {:error, Gltf.Error.t()}
Parses a glTF asset already in memory.
Accepts either a GLB container or JSON. Without a :base option, an asset
with external buffers will fail to resolve them — there is nothing to
resolve against.
@spec primitives(t(), non_neg_integer()) :: {:ok, [map()]} | {:error, Gltf.Error.t()}
The primitives of mesh index.
@spec read( Path.t(), keyword() ) :: {:ok, t()} | {:error, Gltf.Error.t()}
Reads a glTF asset from disk.
The format is detected from the content, not the extension — a .gltf name
on a GLB file is common enough to be worth tolerating.
Options
Passed through to Gltf.Buffer.resolve/2:
:external— read sibling buffer files. Defaults totrue.:allow_outside_base— permit buffer paths outside the asset's own directory. Defaults tofalse.:buffers— supply resolved buffers yourself and skip resolution entirely, for assets whose data you fetched by other means.
Reads a glTF asset, raising on failure.
@spec world_transforms( t(), keyword() ) :: {:ok, %{required(non_neg_integer()) => Gltf.Node.matrix()}} | {:error, Gltf.Error.t()}
World transforms for the nodes of a scene, keyed by node index.