PLY scalar types: names, sizes, and decoding.
Type names
The canonical PLY specification
defines eight scalar types under C names — char, uchar, short,
ushort, int, uint, float, double. Real files also use explicit
width aliases (int8, uint8, float32, …) which are not in the spec
but are emitted by common tooling. Both spellings are accepted and
normalised to a single canonical atom:
iex> Ply.Types.normalize("float")
{:ok, :float32}
iex> Ply.Types.normalize("float32")
{:ok, :float32}Note that char is a signed 8-bit integer, not a character.
Non-finite floats
BEAM floats cannot represent IEEE NaN or infinity — a bit-syntax match
such as <<f::float-32>> = <<0x7F, 0xC0, 0, 0>> raises MatchError
rather than producing a value. Since real Gaussian-splat exports do
contain NaN (hence splat-transform's --filter-nan flag), decoding
checks for those bit patterns first and yields :nan, :infinity, or
:neg_infinity instead of crashing.
iex> Ply.Types.decode_scalar(<<0x7F, 0xC0, 0x00, 0x00>>, :float32, :big)
:nan
Summary
Types
A decoded scalar. Floats may decode to a non-finite atom — see the module docs.
Functions
All canonical type atoms.
Checks that a value can be stored by a type without being destroyed.
Decodes one scalar from the front of a binary.
Encodes a scalar value to its binary representation.
Normalises a PLY type name to its canonical atom.
Parses a scalar from ASCII text.
Size in bytes of a scalar type.
The canonical specification spelling for a type, used when writing headers.
Types
@type endianness() :: :little | :big
@type t() ::
:int8 | :uint8 | :int16 | :uint16 | :int32 | :uint32 | :float32 | :float64
A decoded scalar. Floats may decode to a non-finite atom — see the module docs.
Functions
@spec all() :: [t()]
All canonical type atoms.
Checks that a value can be stored by a type without being destroyed.
encode_scalar/3 assumes valid input for speed; this is the gate callers
use before writing. Without it an out-of-range integer wraps silently —
300 stored as uchar becomes 44 — an oversized float becomes infinity,
and an undersized one becomes zero; all three corrupt a file that reports
as written cleanly.
This checks representability, not precision. Storing 0.1 as float32
loses digits and is accepted, because that rounding is inherent to the
format; rejecting it would make the type unusable. What is rejected is a
value that cannot survive at all.
iex> Ply.Types.check(300, :uint8)
{:error, "300 is outside the range of uint8 (0..255)"}
iex> Ply.Types.check(200, :uint8)
:ok
@spec decode_scalar(binary(), t(), endianness()) :: value()
Decodes one scalar from the front of a binary.
The binary must be at least size(type) bytes; callers are expected to
have checked. Floats that carry a non-finite bit pattern decode to
:nan / :infinity / :neg_infinity rather than raising.
@spec encode_scalar(value(), t(), endianness()) :: binary()
Encodes a scalar value to its binary representation.
Accepts the non-finite atoms produced by decode_scalar/3, so a
decode/encode round trip preserves them.
Assumes the value fits the type — call check/2 first for untrusted input.
Normalises a PLY type name to its canonical atom.
Accepts both spec names and width aliases (see the module docs).
Parses a scalar from ASCII text.
Handles the C %g output real writers emit — exponent notation, -0,
and the unspecified-but-common nan / inf spellings.
@spec size(t()) :: pos_integer()
Size in bytes of a scalar type.
iex> Ply.Types.size(:float32)
4
The canonical specification spelling for a type, used when writing headers.
iex> Ply.Types.spec_name(:float32)
"float"