This page goes through the main syntactic elements of PopcornFX scripts.

The PopcornFX script general syntax is very similar to high-level shader languages such as GLSL or HLSL.

Every name/symbol in PopcornFX-scripts is case-sensitive, and each statement must end with a semicolon.

## Comments

You can add comments anywhere in a script. There are two ways to write comments, just as in shader languages:

- by preceding them with a double forward slash:
`//`

the whole rest of the line will be treated as a comment. - by embedding the comment in a
`/*`

and`*/`

pair (allows multiline comments)

Example:

some_statement; // comment something /* comment */ something_else; this /*is a multiline*/ comment;

## Types

Data-types in PopcornFX scripts are almost identical to the data-types available in the nodegraph. See the particle graph page for more details.

The following data-types are available in scripts:

**Boolean scalar & vectors**

1-bit `true`

/ `false`

boolean values

bool, bool2, bool3, bool4

You can directly create boolean values with the following syntax:

false // immediate bool value bool(0) // immediate bool value, same as above bool2(true, false) // immediate bool2 value bool3(false, 1, true) // immediate bool3 value bool4(0, 1, false, 1) // immediate bool4 value

When given to an explicit `bool`

constructor, integer values `0`

and `1`

will automatically be converted to boolean `false`

/ `true`

values respectively.

**Integer scalar & vectors**

32-bits signed integer values

int, int2, int3, int4

You can directly create integer values with the following syntax:

3 // immediate int value int(3) // immediate int value, same as above int2(3, -4) // immediate int2 value int3(3, -4, 5) // immediate int3 value int4(3, -4, 5, 6) // immediate int4 value

You can use different prefixes to specify which integer format you’re about to write:

42 // no specific prefix: will be treated as a decimal value 042 // no specific prefix: 42 in decimal 0x42 // '0x' hexadecimal prefix: 66 in decimal 0x100 // '0x' hexadecimal prefix: 256 in decimal 0xC0FFEE // '0x' hexadecimal prefix: 12648430 in decimal 0b101 // '0b' binary prefix: 5 in decimal 0b1000010010010 // '0b' binary prefix: 4242 in decimal

**Floating-point scalar & vectors**

32-bit floating-point values

float, float2, float3, float4

You can directly create floating-point values with the following syntax:

1.2 // immediate float value float(1.2) // immediate float value, same as above float2(1.2, 3.4) // immediate float2 value float3(-0.5, 4, 5.5) // immediate float3 value float4(3, 4, 5, 6) // immediate float4 value

note that floating point numbers can be followed by an optional `f`

, like so:

1.2f

The explicit suffix `f`

won’t make any concrete difference on the script compiler’s point of view, it is supported mainly to be able to copy/paste values from HLSL or C++.

**Orientations**

128-bits value encoding a 3D rotation.

orientation

Orientations cannot be directly constructed with an inline constructor like the bool, int, or float types.

You will need to call one of the explicit orientation construction functions, or grab the orientation from one of your script’s input nodes of type orientation.

orientation_ea(pitch, yaw, roll); // returns a value of type 'orientation', constructed from 3 euler angles in degrees orientation_aa(float3(0,1,0), 45.0); // returns a value of type 'orientation' constructed from an axis and an angle in degrees along that axis

## Type promotion

PopcornFX script performs implicit type promotion.

This means that a value of a given type combined with a value of another type through a mathematical operation will lead to a value of a third type, that might or might not be equal to one of the first two types.

- floats combined with integers always leads to floats.
- vectors combined with scalars will always lead to vectors whose dimension is equal to that of the original vector.

For example, if we consider the following expression:

123.4 + int3(5,6,7)

- the float <-> integer operation will lead to floats
- the scalar <-> vector3 operation will lead to a vector3

this expression will therefore give the following result:

float3(128.4, 129.4, 130.4)

this automatic type promotion also allows to construct float vectors from integers, without the need to worry about the decimal point:

float2(42, 69)

will be understood by the popcorn-script compiler as:

float2(42.0, 69.0)

## Local variables

Local variables are useful to store intermediate computation results and reuse them in multiple places further down in your script.

They are also good for readability in general, as it allows you to explicitly “name” computation results and make clear what they are.

### Declaration

You can declare local variables in the following ways:

type name; type name = value; type name(arguments);

the first form declares the variable without assigning anything to it. its contents are undefined, and you will have to initialize it to something before using it in a computation.

the second form contains the initial assignment directly on the same line as the declaration.

the third form is an immediate initialization constructor. you use it the same way as immediate vector creation:

as you’d write:

float3(1,2,3)

to create the vector `{ 1.0, 2.0, 3.0 }`

, you would write:

float3 myVar(1,2,3);

to create a variable named `myVar`

of type `float3`

, containing the 3 values `1.0, 2.0, 3.0`

The following are all equivalent:

float3 myVar; myVar = float3(1,2,3);

float3 myVar = float3(1,2,3);

float3 myVar(1,2,3);

### Scopes

scopes can be seen as “blocks” of script, delimited by curly braces. they can be nested inside each other, and have an arbitrary depth:

{ // we are in the first scope { // we are in the second scope } // we are back in the first scope { // we are in the third scope } // we are back in the first scope }

Local variables are active and can be accessed in the current scope and all its child scopes.

You cannot access a local variable once it is said to have gone “out of scope”, that is, once its containing scope has been closed:

{ int a; float b; // can access a and b { int c; // can access a, b, c int d; // can access a, b, c, d { float e; // can access a, b, c, d, e } // can access a, b, c, d float f; // can access a, b, c, d, f } // can access a, b }

Variables can be declared anywhere within a scope, but they must not have the same name as another variable inside the same scope:

{ int a; int a; // error float b; int b; // error }

variables in child scopes can have the same name as a variable in a parent scope, and if they do, they “hide” the parent variable in their scope, and all the child scopes:

{ // scope1 int a; // we have access to a in scope1 { // scope2 int b; // we have access to a in scope1, and b in scope2 { // scope3 int a; // overrides a in scope1 // we have access to b in scope2, and a in scope3 { // we have access to b in scope2, and a in scope3 } // we have access to b in scope2, and a in scope3 } // we have access to a in scope1, and b in scope2 } // we have access to a in scope1 }

## Vector scalar access and shuffling

Most builtin math functions and operators fully handle native vector types, as well as scalars. However, it is sometimes necessary to access individual components of a vector.

In order to do so, vector types expose 4 accessors: `x`

, `y`

, `z`

, and `w`

, one for each of the 4 respective dimensions.

trying to access a dimension that goes beyond the vector’s dimension count will result in a compile error (ex: accessing the `z`

component of a 2 dimensional vector is invalid. only `x`

and `y`

are available).

```
float3 vec3 = float3(0.1, 1.42, 100);
float xValue = vec3.x;
float yValue = vec3.y;
float zValue = vec3.z;
float wValue = vec3.w; // error: 'w' is not defined for 3-dimensional vectors
```

Vector shuffling is often also called swizzling.

The individual components of builtin native vector types can be accessed and swizzled around freely, allowing implicit generation of another vector, possibly of different dimension.

Swizzles are built using a combination of their respective scalar member accessors `x`

, `y`

, `z`

, or `w`

, if valid with respect to the vector dimension (for example, the `w`

accessor won’t be available in a 3 dimensional vector).

So if we have:

int4 vec4(5,6,7,8); float3 vec3(0.5,0.6,0.7); int2 vec2(0); int vec(1);

we can write:

```
int2 a = vec4.xz;
int4 b = vec4.xxzy;
int4 c = vec.xxxx;
float2 d = vec3.zx;
int3 e = vec2.xyx;
float2 f = vec3.wx; // error: 'w' isn't valid because 'vec3' is a float3, not a float4.
float3 g = float3(vec3.xy, vec4.w);
float2 h = float3(vec4.x, vec3.z);
```

#### Other swizzle codes

In addition to the `x`

, `y`

, `z`

, and `w`

swizzle codes, you can also use `0`

and `1`

to easily insert zeroes or ones in the final result:

float2(5,6).x01y --> float4(5, 0, 1, 6); float2(5,6).01yx --> float4(0, 1, 6, 5)

note that a side-effect of scalars being vectors of dimension 1 is that swizzles can also be used on scalar values:

1.5.xx01 --> float4(1.5, 1.5, 0, 1)

Note that there will be an ambiguity if you want to apply a swizzle like `0x1`

to an integer value, as when the parser will think you are trying to add a decimal point when it sees the `0`

after the first dot:

2.0x1 --> error

You can do either of these to disambiguate:

2.0.0x1 --> float3(0,2,1) 2..0x1 --> float3(0,2,1) (2).0x1 --> int3(0,2,1)

You can also use swizzles on more complex expressions using parentheses in order to isolate the sub-expression you want to apply it to:

(1.5 + 4*5).0x1 --> float3(0, 21.5, 1)

In addition to `x`

, `y`

, `z`

, and `w`

, you can also use `r`

, `g`

, `b`

, and `a`

, in case you find this more readable when maniplulating color values in a `float4`

.

## Functions

Some operations are done by calling “functions”.

Functions receive parameters (or sometimes no parameters at all), and usually return a value.

Parameters are passed in parentheses after the function name, and are separated by commas.

**You cannot currently define custom functions inside a PopcornFX script.**

For example, to compute the length of a float3 vector, you can use the `length`

function, which takes the vector as a parameter, and returns its length:

float3 v = float3(1, 2, 3); // some 3D vector named "v" float vecLength = length(v) // compute the length of "v" and store it in "vecLength"

To compute a random value between -5 and 10.5, you would typically use the `rand`

function, which takes two parameters: the min and the max value, and returns the random number:

`float myRandValue = rand(-5, 10.5); // picks a random number in the [-5, 10.5[ range and stores it in the "myRandValue" variable`

To rotate a 3D vector around an axis and an angle, you can use the `rotate`

function, which takes three parameters: the vector to rotate, the axis along which it should be rotated, and the rotation angle in degrees:

float3 v = float3(1, 2, 3); // some 3D vector named "v" float3 vRot = rotate(v, float3(0,1,-2.5), 45); // rotates 'v' along the axis {0, 1, -2.5} by a 45 degrees angle

Functions taking no parameters are called using an empty parameter list `()`

Note: constructors, such as `float3(1, 2, 3)`

, are functions.

### Member functions

Member functions are a special kind of functions that belong to a specific type, for example samplers.

You call them by using the name of the object, then the dot operator ‘`.`

‘, followed by the function name.

For example, if you create a script with a `float3`

output named `Output`

, and an input of type dataGeometry named `MyShape`

, and wire a shape node in, you can pick a random position on the shape by calling the `samplePosition`

member function:

`Output = MyShape.samplePosition(); // member function taking no arguments`

## if/else

Popcorn scripts **do not support any flow-control constructs at the moment**. This includes if/else, switch/case, as well as loop constructs such as for, while, or foreach.

The usual way to do an `if`

is to use **masking and selection** primitives, such as the ‘select‘, or ‘iif‘ builtins. (iif stands for “inline if” and is just a select in disguise)

For example, instead of writing this:

// when the particle goes slower than 1.2 units/s, set its color to green, otherwise, set it to red: if (length(Velocity) < 1.2) Color = float4(0.1,0.8,0.05,1); // green else Color = float4(1.5,0,0,1); // bright red

You can do:

```
bool selector = length(Velocity) < 1.2; // returns true if length < 1.2, returns false
Color = select(float4(1.5,0,0,1), float4(0.1,0.8,0.05,1), selector);
```

or shorter:

Color = select(float4(1.5,0,0,1), float4(0.1,0.8,0.05,1), length(Velocity) < 1.2);

or, if you prefer the `iif`

notation:

Color = iif(length(Velocity) < 1.2, float4(0.1,0.8,0.05,1), float4(1.5,0,0,1));

## Version

The `version`

keyword is a static compile-time `if`

statement, that allows you to switch between different behaviors based on the build version tags.

Build version tags are a way to toggle different parts of an effect simulation graph on or off based on which target platform it is exported for.

For example, when exporting the effect for mobile platforms such as android or iOS, you might want to disable a more expensive computation that you’re willing to include on PC/desktop builds, or simply change some LOD metrics if you’re building for mobile, consoles, or PC.

The `version`

keyword works like an `if/else`

statement would. Because `version`

is statically evaluated at compile time, it does not suffer from the limitations of a dynamic runtime `if/else`

construct.

It expects a list of comma-separated version tags. The commas act as an “or” operator. If any of the build tags are active in the comma-separated list, it will evaluate to ‘true’ and execute the following scope. Otherwise, it will enter the `else`

statement, if it exists.

Here is an example of a turbulence computation getting killed on mobile but not on other platforms:

float3 windStrength = 0.0f; version (android, ios) { windStrength = float3suf(10, 0, 0); // 10 units/s along the side axis } else { float turbFadeOff = MyFadingCurve.sample(eval.full(self.lifeRatio)); // Fade the tubulence over life (moderately expensive) float3 turb = MyTurbulence.sample(Position); // Sample the turbulence vector-field (very expensive) windStrength = float3suf(10, 0, 0) + turb * turbFadeOff; // 10 units/s along the side axis, plus the faded turbulence }

## Namespaces

Namespaces are prefixes used to organize a set of functions or symbols in distinct categories. They are not instances of actual objects like samplers, but follow the same syntax.

For example, the `scene`

and `effect`

namespaces exposes a set of helper functions and symbols, such as `scene.time`

, `scene.dt`

, `effect.age()`

, `scene.axisUp()`

, `scene.intersect(position, rayDir, rayLength)`

, and more:

```
float3 raycastDir = -scene.axisUp();
float4 raycastResult = scene.intersect(Position, -raycastDir, 100.0f); // raycast 100 units downwards
```

The `degrees`

and `radians`

namespaces contain alternative versions of all functions manipulating angles, which by default, when called outside the namespace, manipulate degrees:

```
float angleInDegrees = x;
float a = sin(angleInDegrees);
float b = degrees.sin(angleInDegrees);
float c = radians.sin(deg2rad(angleInDegrees));
float d = radians.sin(pi / 3.0); // equal to 'sin(60)'
```

Namespaces can contain sub namespaces, for example `fast.radians.sin(...)`

, `fast.degrees.tan(...)`

, etc…

#### Namespaces available in PopcornFX scripts

`self`

: Access to the current particle (ex:`self.lifeRatio`

,`self.kill()`

, …)`effect`

: Access to the current effect (ex:`effect.age()`

,`effect.isRunning()`

,`effect.position()`

,`effect.orientation()`

, …)`view`

: Access to views / cameras (ex:`view.position()`

,`view.closest(...)`

,`view.axisForward()`

, …)`scene`

: Access to the scene (ex:`scene.axisUp()`

,`scene.intersect(...)`

,`scene.time`

,`scene.dt`

, …)`debug`

: Debug helpers (ex:`debug.assert(...)`

,`debug.warning(...)`

, …)`shape`

: Contains shape-related functions (ex:`shape.buildPCoordsMesh(...)`

,`shape.buildPCoordsBox(...)`

, …)`shapeType`

: Contains shape types (ex:`shapeType.Box`

,`shapeType.Mesh`

, …)`textureAddr`

: Contains texture sampler address modes (ex:`textureAddr.Wrap`

,`textureAddr.Clamp`

, …)`textureFilter`

: Contains texture sampler filter modes (ex:`textureFilter.Point`

,`textureFilter.Linear`

, …)`degrees`

: Contains all functions working with degrees (ex:`degrees.sin(...)`

,`degrees.cos(...)`

,`degrees.rotate(...)`

, …)`radians`

: Contains all functions working with radians (ex:`radians.sin(...)`

,`radians.cos(...)`

,`radians.rotate(...)`

, …)`fast`

: Contains all fast versions of complex math functions (ex:`fast.sqrt(...)`

,`fast.exp(...)`

,`fast.sin(...)`

, …)`fast.degrees`

: Contains all fast versions of functions working with degrees (ex:`fast.degrees.sin(...)`

,`fast.degrees.cos(...)`

,`fast.degrees.atan2(...)`

, …)`fast.radians`

: Contains all fast versions of functions working with radians (ex:`fast.radians.sin(...)`

,`fast.radians.cos(...)`

,`fast.radians.atan2(...)`

, …)`accurate`

: Contains all accurate (but slower) versions of complex math functions (ex:`accurate.sqrt(...)`

,`accurate.exp(...)`

,`accurate.sin(...)`

, …)`accurate.degrees`

: Contains all accurate (but slower) versions of functions working with degrees (ex:`accurate.degrees.sin(...)`

,`accurate.degrees.cos(...)`

,`accurate.degrees.atan2(...)`

, …)`accurate.radians`

: Contains all accurate (but slower) versions of functions working with radians (ex:`accurate.radians.sin(...)`

,`accurate.radians.cos(...)`

,`accurate.radians.atan2(...)`

, …)`sim`

: [v2.2.0] Contains all internal simulation details functions (ex:`sim.lod()`

,`sim.updateRate()`

, …)`sim.wave`

: [v2.2.0] Contains all internal simulation unit (wavefront) reduction functions (ex:`sim.wave.min()`

,`sim.wave.any()`

, …)

See the scripting reference pages for more details on these namespaces and functions

## Operators

Operators are all the special symbols in the scripts. You will find the exhaustive list of supported operators and what they do in the table below:

Operator | Description | Example | UsageDetails |

`(` | parenthesis open | used for function calls or expression isolation | |

`)` | parenthesis close | every opened parenthesis must be matched by a closing parenthesis | |

`++` | post increment | `a++` | adds 1 to a variable, and returns the value the var had before the incrementation: `b = a++;` -> `b = a; a = a + 1;` |

`--` | post decrement | `a--` | subtracts 1 from a variable, and returns the value the var had before the decrementation: `b = a--;` -> `b = a; a = a - 1;` |

`~` | binary not | `~a` | [integer only] returns the binary inverse of all bits: `a = 0b01100101` will give: `~a == 0b10011010` |

`!` | logical not | `!a` | [boolean test] returns the opposite boolean value: `if (!(a > 3 || a <= -2))` -> `if (a <= 3 && a > -2)` |

`++` | pre increment | `++a` | same as post-increment, except the value returned is the value after the incrementation: `b = ++a;` -> `a = a + 1; b = a;` |

`--` | pre decrement | `--a` | same as pre-increment, but with a decrementation |

`+` | unary plus | `+a` | does nothing for basic types: `+a` is still equal to `a` |

`-` | unary minus | `-a` | negates a value: `-a` -> `a * -1` |

`*` | mul | `a * b` | multiplies `a` and `b` together |

`/` | div | `a / b` | divides `a` by `b` |

`%` | mod | `a % b` | returns the remainder of the division of `a` by `b` : `a % b` –> `a - (((int)(a / b)) * b)` |

`+` | add | `a + b` | adds `a` and `b` together |

`-` | sub | `a - b` | subtracts `b` from `a` |

`<<` | shift left | `a << b` | [integer only] performs a binary shift to the left: `0b10101110 << 2` -> `0b10111000` the result is an integer multiplication by 2^shiftCount |

`>>` | shift right | `a >> b` | [integer only] performs a binary shift to the right: `0b10101110 >> 2` -> `0b00101011` the result is an integer division by 2^shiftCount |

`<` | lower | `a < b` | [boolean test] checks if `a` is strictly lower than `b` : `a < b` |

`<=` | lower or equal | `a <= b` | [boolean test] checks if `a` is lower or equal to `b` : `a <= b` |

`>` | greater | `a > b` | [boolean test] checks if `a` is strictly greater than `b` : `a > b` |

`>=` | greater or equal | `a >= b` | [boolean test] checks if `a` is greater or equal to `b` : `a >= b` |

`==` | equal | `a == b` | [boolean test] checks if `a` and `b` are equal: `a == b` |

`!=` | not equal | `a != b` | [boolean test] checks if `a` and `b` are not equal: `a != b` |

`&` | and | `a & b` | [integer only] binary AND between `a` and `b` : `a & b` |

`^` | xor | `a ^ b` | [integer only] binary XOR between `a` and `b` : `a ^ b` |

`|` | or | `a | b` | [integer only] binary OR between `a` and `b` : `a | b` |

`&&` | logical and | `a && b` | [boolean test] checks if `a` AND `b` are both ‘true’: `a && b` |

`||` | logical or | `a || b` | [boolean test] checks if `a` OR `b` are not equal: `a || b` |

`=` | assign | `a = b` | assigns `b` to `a` : `a = b;` |

`+=` | add and assign | `a += b` | adds `b` to `a` , and assigns the result to `a` : `a += b;` –> `a = a + b;` |

`-=` | sub and assign | `a -= b` | subtracts `b` from `a` , and assigns the result to `a` |

`*=` | mul and assign | `a *= b` | multiplies `a` by `b` , and assigns the result to `a` |

`/=` | div and assign | `a /= b` | divides `a` by `b` , and assigns the result to `a` |

`%=` | mod and assign | `a %= b` | computes `a` modulo `b` , and assigns the result to `a` |

`&=` | and and assign | `a &= b` | [integer only] computes the binary ‘AND’ of `a` and `b` , and assigns the result to `a` |

`|=` | or and assign | `a |= b` | [integer only] computes the binary ‘OR’ of `a` and `b` , and assigns the result to `a` |

`^=` | xor and assign | `a ^= b` | [integer only] computes the binary ‘XOR’ of `a` and `b` , and assigns the result to `a` |

`<<=` | shift left and assign | `a <<= b` | [integer only] binary-shifts left `a` by `b` bits, and assigns the result to `a` |

`>>=` | shift right and assign | `a >>= b` | [integer only] binary-shifts right (arithmetic) `a` by `b` bits, and assigns the result to `a` |

`;` | semicolon | `a;` | used to delimit expressions |

`{` | scope open | used to open a new scope | |

`}` | scope close | every scope opening bracket must be matched by a scope closing bracket. |