Expression language reference

The OctoMY expression language is a subset of GLSL 4.60 desktop — the same syntax you write in shaders. Everywhere a formula or expression is accepted (spreadsheet cells, FACS rig parameters, Construct varset entries, animation curves), this is the language being parsed.

Pro Tip

The expression language is identical across all OctoMY surfaces. A formula that works in a spreadsheet cell works the same way in a FACS rig or a varset entry — the only thing that differs is which variables are pre-declared for you.


Types

Family Types
Scalars float, double, int, uint, int64_t, uint64_t, bool
Float vectors vec2, vec3, vec4
Double vectors dvec2, dvec3, dvec4
Integer vectors ivec2/3/4, uvec2/3/4, i64vec2/3/4, u64vec2/3/4
Boolean vectors bvec2, bvec3, bvec4
Float matrices mat2, mat3, mat4, plus rectangular mat2x3, mat2x4, mat3x2, mat3x4, mat4x2, mat4x3
Double matrices dmat2, dmat3, dmat4, plus rectangular dmatNxM

Literals

Form Example Type
Integer 42, 0xff, -7 int
Unsigned 42u, 0xffu uint
64-bit 42l, 42ul int64_t, uint64_t
Float 1.0f, 3.14e2, .5f float
Double 1.0lf, 3.14e2lf double
Boolean true, false bool

Operators

Group Operators
Arithmetic + - * / %
Comparison == != < > <= >=
Logical && || !
Bitwise & | ^ ~ << >>
Assignment = and compound += -= *= /= %= &= |= ^= <<= >>=
Ternary cond ? a : b
Unary + - ! ~ ++ --

Operator precedence follows GLSL §5.1 — multiplicative binds tighter than additive, which binds tighter than relational, which binds tighter than logical, and so on. Use parentheses when in doubt.

Vectors

Vector constructors take any combination of scalars and shorter vectors that add up to the right component count, plus a single-scalar splat form:

vec3(1.0, 2.0, 3.0)      // three scalars
vec3(0.0)                // splat — all three components 0.0
vec4(rgb, 1.0)           // vec3 + scalar
vec2(v3.xy)              // truncate a vec3 to a vec2 via swizzle

Swizzles: read or assign 1–4 components from any vector. Three component sets are accepted, but you can't mix them:

Set Components
xyzw spatial / generic
rgba colour
stpq texture coordinates
v.xyz       // vec3 — first three components
v.x         // float — single component
v.rgba      // vec4 of the same components, just named differently
v.xy        // vec2
v.zyx       // vec3, components reordered

Matrices

Matrices are column-major. m[i] returns the i-th column as a vector. Three constructor forms:

mat3(1.0)                            // identity scaled by 1.0
mat3(vec3(1,0,0), vec3(0,1,0), vec3(0,0,1))  // three column vectors
mat3(1,0,0, 0,1,0, 0,0,1)            // nine scalars in column-major order

m1 * m2 is matrix composition (GLSL-style, not component-wise). m * v is matrix-times-column-vector. v * m is row-vector-times-matrix.

For component-wise multiplication use the built-in matrixCompMult(a, b).

Built-in functions

Angle and trigonometry (§8.1)

radians, degrees, sin, cos, tan, asin, acos, atan (1- or 2-arg form), sinh, cosh, tanh, asinh, acosh, atanh

Exponential (§8.2)

pow, exp, log, exp2, log2, sqrt, inversesqrt

Common (§8.3)

abs, sign, floor, ceil, trunc, round, roundEven, fract, mod, min, max, clamp, mix, step, smoothstep, fma, isnan, isinf, floatBitsToInt, intBitsToFloat, bitCount, findLSB, findMSB, bitfieldExtract, bitfieldInsert

Geometric (§8.5)

length, distance, dot, cross, normalize, reflect, refract, faceforward

Matrix (§8.6)

matrixCompMult, transpose, determinant, inverse, outerProduct

Floating-point pack/unpack (§8.4)

packUnorm2x16, unpackUnorm2x16, packSnorm2x16, unpackSnorm2x16, packUnorm4x8, unpackUnorm4x8, packSnorm4x8, unpackSnorm4x8

The packXxxx family takes a vec2 or vec4, quantises each component, and bit-packs the result into a 32-bit container returned as float. The matching unpackXxxx reverses the process. Useful for storing small fixed-point fields inside a single float slot (e.g. normalised colour channels). Half-precision variants (packHalf2x16 etc.) are GPU-only.

Vector relational (§8.7)

lessThan, lessThanEqual, greaterThan, greaterThanEqual, equal, notEqual, any, all, not

Component-wise comparisons take two vectors and return a bvecN (boolean vector) with one component per input pair. any(bvecN) reduces the boolean vector to a scalar bool that's true when any component is true; all(bvecN) is true when every component is true; not(bvecN) flips each component.

// "any cell value exceeds 0.5" pattern
bool overThreshold = any(greaterThan(values, vec3(0.5)));

// "all components positive" pattern
bool allPositive = all(greaterThan(v, vec3(0.0)));

Control flow

When you're writing a longer expression — for example, a Construct varset helper function — you can use the full GLSL statement vocabulary:

float chooseMax(float a, float b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}
Construct Notes
if / else Condition must be bool
for C-style for (init; cond; step) body
while / do-while Condition is bool
switch / case / default break terminates a case; fallthrough by default
break, continue, return Standard semantics
discard Raises a runtime error on CPU (no shader-stage early-out available)

User-defined functions

You can define helper functions and call them from your main expression. Parameter qualifiers control how data flows:

Qualifier Meaning
in (default) Pass by copy; callee can't affect caller
out Caller's name receives whatever the callee writes
inout Read in, write out — full read/modify cycle
const Read-only inside the callee
float doubleInPlace(inout float a) {
    a = a * 2.0f;
    return a;
}

Recursion is rejected by default — direct or indirect call cycles produce a compile-time error. This matches GLSL semantics and keeps the same expression usable in a real shader. If you don't need shader portability and have a use case that genuinely needs recursion, the caller can opt in (talk to the developer surface that owns the graph — Graph::allowRecursion(true)).

Error reporting

When an expression fails to compile or evaluate, you get a Diagnostic with:

  • Category — ParseError, TypeError, NameError, DivByZero, DomainError, Cycle, BackendUnavailable.
  • Message — human-readable explanation.
  • Source span — line, column, length within the expression text.
  • Handle (run-time errors) — the slot that produced the error.
  • Cycle path (cycle errors) — the full chain, e.g. C5 → D5 → C5.

Two evaluation modes

The expression engine has two backends:

Backend When Speed
Interpreted Always available ~50–200 ns per scalar op; runs everywhere
Native (LLVM JIT) When the LLVM toolchain is installed ~3–10 ns per scalar op

For most consumers the choice is automatic. The interpreted backend is fast enough for the spreadsheet, FACS rigs at 60 Hz, and varset evaluation. Native pays off in tight loops processing thousands of elements per frame.


Strict math by default

Under default settings, the engine treats divide-by-zero and other domain errors as diagnostics, not silent NaNs. This protects you from formulas that produce mysterious wrong answers downstream.

If you want IEEE-754 behaviour (silent NaN/Inf propagation, matching what a GPU shader would do), the developer surface that owns the graph can switch it on per-graph.

See also

In this section