Programming Manual Chap. 14

Particle Basics
The WildPockets particle system is a work in progress. It is designed to be very powerful, but it is not entirely complete right now.

Particle systems are created by writing a tiny program in "ParticleScript." To those familiar with GPU shader languages, ParticleScript will look rather familiar. By providing a scripting language to control the behavior of individual particles, you gain the maximum flexibility and programmability. It would be too expensive to execute an interpreted script for each particle, so ParticleScript is designed as follows: the script is executed once per frame, not once per particle. Each command in the language implicitly affects all the particles, or a selected subset of the particles.

The following chapters describe the ParticleScript language and the Particle API. Please note that our Lua interpreter will not directly interpret your ParticleScript. Chapters 14A-14C will describe how to use ParticleScript, and Chapter 14D will explain how to integrate it into your Lua code.

=Chap. 14A --- ParticleScript Concepts=

ParticleScript Basics
This chapter is about the ParticleScript language.

The first step in a ParticleScript program is to declare particle attributes. Here is a typical set of attribute-declarations: ==

varying float3 position varying float3 velocity varying float4 color

This means that every particle has a position and velocity, both of which are float3. The keyword 'varying' means that different particles might have different positions - in other words, it means that the particles don't all have the same position.

The words 'position' and 'velocity' are just attribute names, not keywords. The system does not assign any particular meaning to these words. There is no built-in physics calculation in ParticleScript.

The opposite of 'varying' is 'uniform'. For example:

varying float3 position varying float3 velocity uniform float3 gravity

This means that all particles individually have position and velocity. There is also a gravity force which all particles share - ie, the gravity force is the same for all particles. Uniform attributes can be given initial values:

varying float3 position varying float3 velocity uniform float3 gravity = [0, 0, -9.8]

Attributes can be of type float1, float2, float3, or float4. There are no other attribute types. Constants in ParticleScript can also be float1, float2, float3, or float4. Constants are written as: an open bracket, a series of floating-point literals separated by commas, and a close-bracket. Constants of type float1 can omit the brackets.

Assignment Statements
After the attribute declarations come the executable statements. The most common of the executable statements is the assignment statement: position = position + velocity * 0.01 At any given time, a subset of the particles are selected. Assignment statements implicitly apply to every selected particle. If there are no commands to select particles, then by default, every particle is selected. So if the statement above were the only executable statement in the particle system, it would implicitly be done to every particle.

Assignment statements cannot assign to uniform attributes. Uniforms can only be set at initialization time, or in the Lua code.

Selection Sets
It is possible to write an assignment statement that operates on a subset of the particles using the select operator. Here is an example:

select (position.z < 0) color = [0,0,1,1]

The first command will select all particles whose Z-coordinate is less than zero. The second statement will set the color attribute (which must be declared float4) to blue. The second statement, an assignment statement, will implicitly affect only the currently-selected particles.

The select command above contains a boolean expression: (position.z < 0). Boolean expressions return "selection sets." The select command requires that the right-hand side be an expression that returns a selection set.

It is possible to store a selection set for later use. The following code does the same thing as the previous code:

selection lowparticles lowparticles = (position.z < 0) select lowparticles color = [0,0,1,1]

Now you have given a name, lowparticles, to the set of particles whose Z-coordinate is less than zero. You can select this group of particles again using "select lowparticles."

There is a built-in named selection set "all". If you say "select all," you will select all particles.

Spawning New Particles
To create new particles, you need to use one of the spawning operators. A common operator is spawnsteady, which spawns particles at a steady, fixed rate, specified in particles per second. The particles that are created have zeros in all their varying attributes. They must be initialized.

All spawning operators implicitly create a named selection set called "new", which contains a list of all the particles that were just spawned. This selection set makes it possible to initialize the new particles.

Here is some sample code that creates new particles and initializes them:

spawnsteady 5.0 select new position = [0,0,0] velocity = rand.unitvec

The first statement causes particles to be spawned, at a rate of 5.0 particles per second. It also implicitly sets the selection set 'new' to the new particles. The second statement selects the new particles. The two assignment statements only affect the new particles - they initialize the position to zero, and the velocity to a random unit vector.

Deleting Particles
The delete command requires you to pass in a selection set. It removes the selected particles from the system:

delete (position.z < 0)

In the example above, the boolean expression returns a selection set containing all particles whose Z-coordinate is less than zero. The delete command deletes them. This is a common command: it gets rid of particles that have fallen through the floor.

After a delete operator, all named selection sets are zeroed out (excepting 'all', which still references all particles).

Rendering Particles
To render particles, use one of the rendering commands. A typical example is renderorbs, which renders blurry round balls at specified locations:

select all renderorbs pos:position color:[1,0,0,1] size:0.1

Rendering commands cannot render a subset of the particles. For clarity, you must 'select all' before rendering.

Rendering commands usually use keyword-style parameters, explained below.

Keyword-Style Parameters
Some commands, especially the rendering commands, take lots of parameters. To make these commands easier to understand, the parameters are passed "keyword-style". Each parameter consists of a keyword, a colon, and an expression. In addition, some commands follow the keyword-parameters with keyword-flags: single words that alter the behavior of the command. The "renderorbs" command is a good example: varying float3 position varying float4 color ... renderorbs pos:position renderorbs pos:position color:color renderorbs color:color pos:position renderorbs pos:position add The renderorbs command takes three keyword-parameters: "pos," "color," and "size." It also accepts the keyword-flag "add". As you can see, the keyword-parameters can be specified in any order. The flags can also be specified in any order, though they must follow the keyword-parameters.

Automatic Type Conversions
Some expressions expect a value of a given type. If you pass in an expression of a different type, automatic conversion will take place.

When converting to a smaller type, the conversion involves truncation: the extra values are discarded. For example, converting [1,2,3,4] to float2 yields [1,2].

When converting a float1 to a larger type, the conversion involves replication. For example, converting [7] to float4 yields [7,7,7,7].

When converting anything other than float1 to a larger type, the conversion involves padding with zero. For example, converting [5,6] to float4 yields [5,6,0,0]. However, float1 is an exception.

Swizzling
The four elements of a float4 are called X,Y,Z,W in that order. It is possible to rearrange the values using a "swizzling operator." The swizzling operator consists of a dot followed by the letters X,Y,Z,W - not necessarily in that order. The swizzling operator extracts the named portions, in the named order, and constructs a new value from those:

position = [1,2,3,4].zzz position = [1,2,3,4].wyz
 * 1) extracts three copies of Z from the constant - therefore, sets position to 3,3,3
 * 1) extracts W,Y,Z from the constant - therefore, sets position to 4,2,3

Swizzling can be combined with automatic conversion. For example, let's say I do this:

varying float4 attr1 attr1 = [1,2,3,4].z

In this example, the swizzle operator takes the input [1,2,3,4] and returns the value [3]. Then, this value [3] is automatically converted to float4 to assign it to attr1. The automatic conversion produces [3,3,3,3].

Groups
Particle systems can contain multiple 'groups' of particles. Groups are almost completely disjoint - it is almost as if you had two separate particle systems. Particles in different groups have different attributes, different commands, and different selection sets.

To create a group, use the group name command. All commands that follow the group-declaration implicitly apply to particles in that group only.

Currently, groups are truly disjoint: there are not currently any commands that communicate between two groups. However, there will eventually be commands that pass data from one group to another.

Command List
This section is a list of all the commands in particle script.

varying

Creates a varying attribute. Examples:

varying float3 position varying float4 color '''

uniform'''

Creates a uniform attribute. Examples:

uniform float3 gravity = [0,0,-9.8] uniform float1 spawnrate = 5 selection

Creates a named selection set. Examples: selection lowparticles '''

select'''

Selects a subset of the particles. The right hand side must evaluate to a selection set: this can either be a named selection set or a boolean expression.

select lowparticles select new select (position.z < 0.0) '''

assignment'''

The assignment statement is the only command that doesn't start with a keyword. Instead, it starts with an attribute or named selection set. The right hand side must be a float-expression (in the case of an attribute) or a boolean expression (if the left hand side is a named selection set).

position = position + velocity lowparticles = (position.z < 0) '''

delete'''

Deletes a subset of the particles. The right hand side must evaluate to a selection set: this can either be a named selection set or a boolean expression.

delete (position.z < 0) delete all '''

spawnsteady'''

Spawn particles at a steady rate. The right hand side is a spawn-rate. The particle attributes are filled with zeros. The named selection set 'new' is set to the new particles.

spawnsteady 5.0 '''

spawnuntil'''

Spawn particles at a steady rate until a specified limit is reached. The parameters are spawn-rate and maximum-total-particles. The particle attributes are filled with zeros. The named selection set 'new' is set to the new particles.

spawnuntil 20.0 100 '''

renderpoints'''

Renders particles as points using GL_POINTS. Keyword parameters: pos, color. Keyword flags: blend, add. The blend flag enables regular blending, the add flag enables additive blending (you can't specify both).

renderpoints pos:position renderpoints color:clr pos:position add '''

renderlines'''

Renders particles as line segments using GL_LINES. Keyword parameters: begin, end, begincolor, endcolor, color. Keyword flags: blend, add. The begin and end parameters specify the two endpoints of the line. The begincolor and endcolor specified the colors of the two endpoints. The color, if present, sets both begincolor and endcolor. The blend flag enables regular blending, the add flag enables additive blending (you can't specify both).

renderlines begin:position1 end:position2 begincolor:color1 endcolor:color2 renderlines color:[1,0,0,1] begin:position1 end:position2 add '''

renderorbs'''

Renders particles as hazy, blurry orbs. Keyword parameters: pos, color, size. Keyword flags: blend, add. The blend flag enables regular blending, the add flag enables additive blending (you can't specify both).

renderorbs pos:position1 size:0.2 renderorbs color:clr pos:position add '''

renderbillboards'''

Renders particles as textured square billboards. Keyword parameters: pos, color, rotate, size, and texture. Keyword flags: blend, add. The blend flag enables regular blending, the add flag enables additive blending (you can't specify both).

renderbillboards texture:"username/image" size:0.2 '''

group'''

The group command delineates disjoint groups of particles. A group command should be followed by attributes, selection sets, executable commands, and rendering commands for that group.

group fire ... group smoke ... '''

What's Missing'''

Many important commands are obviously missing: Ways to spawn particles at non-steady rates is one. Another clear gap is the the inability for different groups to interact with each other. The particle system is a work in progress, these expansions are coming soon.

Built-in Functions
ParticleScript has a number of built-in operators and functions. These are listed here:

math operators

The operators + - * / are all supplied. These operate componentwise. The type returned is equal to the type you pass in - ie, if you multiply float3, it returns float3. If the inputs are not of the same type, it converts the smaller one to the larger.

swizzle operators

A period followed by the letters X,Y,Z,W is a swizzle. This rearranges the components, and possibly changes the number of components in the input value.

comparison operators

Comparison operators like > < >= <= == work on values of type float1. If you pass in a value that is not float1, only the first component will be used. These operators return selection sets. Boolean operators like 'and' and 'or' are not yet implemented.

new

New is a built-in expression that returns a selection set containing the most-recently-spawned particles.

all

All is a built-in expression that returns a selection set containing all particles.

host.position

The expression host.position returns the current position of the SceneObject to which this particle system is applied.

host.forward, etc

The expressions host.right, host.left, host.forward, host.backward, host.up, and host.down return the six axis vectors of the SceneObject to which this particle system is applied.

rand.unitvec

Returns a random float3 unit vector, evenly distributed on the unit circle.

rand.negpos

Returns a random float4 vector, with each component between -1 and 1.

rand.zeroone

Returns a random float4 vector, with each component between 0 and 1.

age

Returns the age of the particle, in seconds.

time.frame

Returns the amount of time elapsed in this rendering frame. Note that particles are one of the few aspects of Wild Pockets that do not run at a fixed framerate.

time.curr

Returns the real-time clock of the current frame.

time.prev

Returns the real-time clock of the previous frame.

trajectory(...)

Computes the position of an inertial particle, given four parameters: initial position, initial velocity, gravity, and time elapsed. Uses a closed-form equation.

undulate(...)

Returns a smooth waveform that rises and falls randomly. The waveform was constructed by generating short samples of sinusoidal waveforms at random frequencies and overlapping these short samples at random offsets. The undulating waveform is good for modeling many physical phenomena, such as the way a spark drifts away from a camp-fire in a semi-random walk. The return value is float4, each component undulates independently. The first parameter is time (offset within the waveform), the second is a random seed.

lerp(...)

Interpolation. Takes three parameters: A, B, fraction. If fraction is zero, returns A. If fraction is 1, returns B. Otherwise, interpolates.

circle(...)

Computes the position of a particle that is rotating in a unit circle around the Z-axis at a rate of 1 revolution per second. The input is an amount of time elapsed.

normalize(...)

Given a float3, returns a normalized version (a vector of unit length).

What's Missing?
Clearly, this is a very skeletal function list. There should be dozens, if not hundreds of useful functions. This will be expanded in time.

Particles and Lua
The command to set a particle system on a SceneObject is setParticleSystem, which takes a string. The string should be a ParticleScript program. Here's an example:

obj:setParticleSystem

The scene object will immediately begin generating particles. You can remove the particle system by calling SceneObject:clearParticleSystem.

Adjusting Uniform Values
The Lua code can reach into the particle system and adjust the values of uniform attributes. To do this, use the methods SceneObject:particles, which returns the particle system handle, and the method ParticleSystem:setUniformValue:

obj:particles:setUniformValue("Main.spawnrate", 1.0)

When using commands like setUniformValue, you must refer to attributes using their extended name, which consists of the group name, a dot, and the attribute name. The default group name, if not specified in the particle system code, is "Main."

Particle Dissipation
Removing a particle system from a scene object using clearParticleSystem will instantly cause all the particles to disappear. Sometimes, you don't want the particles to vanish in a blink, sometimes you want them to dissipate in a more natural way. One way to achieve this is to have a uniform spawnrate variable in your particle system. To get rid of the particle system, set the spawn rate to zero. Then, give the particle system time for the particles to go away on their own. Finally, several seconds later, clear the particle system.