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
- Spreadsheet how-to — using formulas in spreadsheet cells.
- Construct varset expressions — driving Construct parameter expressions and animation curves.
- GLSL 4.60 specification — the upstream language reference. OctoMY's expression language is a subset; everything documented above behaves identically.