Writing the Code for a Particle Event (Handler)

This section covers the following topics:

Setting Up Your Scripted Particle Event

Adding and Accessing User Parameters and Particle Attributes

What Kind of Data Can You Access?

Limitations on Accessing Data

Setting Up Your Scripted Particle Event

Scripted Events are set on a ParticleType. You can add your scripted particle event through scripting or the UI:

To set up a Particle Event through the UI

To set up a Particle Event through Scripting

 

Once your particle type is created you can associate it to an emitter and add particle attributes, also called user parameters, which are special custom parameters that allow you to store user data with your particle type (Adding and Accessing User Parameters and Particle Attributes).

To set up a Particle Event through the UI

1. Do one of the following:

- Select a particle cloud and choose Modify > Particles > Add Particle Event from the Simulate toolbar.

or

- On the Event page in the Particle Type property editor, click the New Event button.

2. In the PEvent property editor that opens, select the event Trigger and set its Value.

 

 

If you set an obstacle for a particle cloud using the standard method (see the Simulation (user) guide for more information), an event with Collision as the Trigger is automatically created for the particle type. However, you can still select the type of Action to occur at the moment of impact.

3. Select Script as the Action which will run the script when the trigger’s Value is reached.

You can also open the PEvent property editor in either of these ways:

• Select the event name from the Inspect > Events menu button on the Simulate toolbar, as shown on the left.

 

or

• On the Event page in the Particle Type property editor, right-click the event’s name in the grid and choose Inspect.

When you select Script, the parameters on the Script page in the PEvent property editor are activated. You can either select a script file that has already been created or you can write the script directly in the edit (white) box at the bottom of this page.

 

To set up a Particle Event through Scripting

The AddParticleEvent command adds the event to the specified (or selected) particle type, so you need to start by creating the particle type. The basic workflow is:

1. Create a new ParticleType using the CreateParticleType command (events are always created on a specific particle type). The command uses an output parameter to pass back a pointer to the new type (see Working with Return Values and Output Arguments for information on how to handle these).

You can specify one of the three particle rendering shaders which determine the shape of the particles: Billboard (flattened), Sphere (bulbous) or Blob (well, uhhh...blobby):

   var rtn = CreateParticleType( siBlobType );
   var ptype = rtn(0);

2. Add the new event to the particle type using the AddParticleEvent command (you can use the pointer you got from CreateParticleType to specify which type to add it to). This command also uses an output parameter to pass back a pointer to an XSICollection containing the new event.

While this command allows you to specify a new particle type to emit (Source parameter), scripted events do not emit a new particle type.

   var rtn = AddParticleEvent( ptype );
   var pevcoll = rtn(0);
   var pevent = pevcoll(0);

3. Using the pointer to the new event (a Property object), you can finish setting it up by accessing its ParameterCollection using the ProjectItem.Parameters property. For example, here is a snippet that enumerates the parameters, printing their names and values:

   // Now we can enumerate the ParameterCollection
   // on the PEvent Property
   var param = new Enumerator( pevent.Parameters );
   for ( ; !param.atEnd(); param.moveNext() ) {
       LogMessage( param.item().Name + " = "
          + param.item().Value );
   }
   // The following information is logged:
   //INFO : Name = PEvent
   //INFO : Mute = false
   //INFO : Trigger = 0
   //INFO : Value = 100
   //INFO : Var = 0
   //INFO : Distrib. = 0
   //INFO : Seed = 0
   //INFO : Action = 1
   //INFO : File =
   //INFO : Proc. =
   //INFO : Script =
   //INFO : Use Script File = false
   //INFO : ScriptLanguage = 0
   //INFO : ScriptContext = 0
   //INFO : Emission = undefined
   //INFO : Obstacle = undefined

Alternatively, you can identify each parameter by its Parameter.ScriptName. For example, Use Script File is the parameter name (SIObject.Name) but the ScriptName of the parameter is actually UseScriptFile:

   var eparams = pevent.Parameters;
   LogMessage( eparams( "UseScriptFile" ).Name );
   //INFO : Use Script File

   // Change the trigger to every 30 frames
   eparams( "EventTrigger" ).Value = 3;
   eparams( "TriggerValue" ).Value = 30;

4. To set up a scripted event, we'll need to change the Action value to 6 (which is the value corresponding to the Script option in the UI):

   eparams( "EventAction" ).Value = 6;

5. If you want to implement your scripted event in JScript, you also need to change the ScriptLanguage parameter to 1 (which is the value corresponding to the ScriptLanguage option in the UI):

   eparams( "ScriptLanguage" ).Value = 1;

6. Then, depending on whether you want to use an embedded script or a script defined in a file on disk, you need to set a different set of parameters:

- For embedded scripts, you can set the ScriptContext and Script parameters. The Script parameter holds the string that contains the actual implementation of your scripted event:

   eparams( "ScriptContext" ).Value = 1;
   eparams( "Script" ).Value = "// Put implementation here...";

Contexts are unique to embedded scripts and give you access to special object and data variables (for more information, see What Kind of Data Can You Access?).

- For implementing your scripted event in a script file, you need to set the UseScriptFile, ScriptFile and ScriptProc parameters:

   eparams( "UseScriptFile" ).Value = true;
   var fileName = InstallationPath( siUserPath ) + "\\pevent.js";
   eparams( "ScriptFile" ).Value = fileName;
   eparams( "ScriptProc" ).Value = "PEventHandler";

The ScriptFile parameter takes a full path name and you specify the name of the function to run in the ScriptProc parameter.

Adding and Accessing User Parameters and Particle Attributes

When you need to store extra information (user data) with the particle cloud you can add custom particle attributes, also called user parameters, to a particle type. Although this data pertains directly to the Particle and is read via the Particle.Attributes property, it is stored on the ParticleType object.

 

While ParticleAttribute objects can be used as user parameters, they also serve another purpose: they hold goal information that you can use in a scripted particle event to calculate particle position, velocity, etc. For more information, see Reading Information from Particle Goals.

What Types of Particle Attributes are Available?

The siParticleAttributeType is the enum (constant) containing the complete list of available data types. Except for the boolean type, only numeric attributes are available (including two math objects, SIVector3 and SIQuaternion).

Adding New Particle Attributes

You can add attributes to a particle type either through the UI or through scripting.

To add a Particle Attribute through the UI

You can add attributes from the particle type property page. However, what appears is not the attribute itself, but only its definition.

1. Open the property page for the particle type where you want to store the attributes.

2. Click the User Parameters tab. You can see a small section called Parameter Creation where you will specify the particle attributes.

3. Type a name for the new attribute in the Name text box. The name must be unique for each particle type, although you can use the same name on different particle types.

4. Select one of the data type values from the Type drop-down box.

5. Click New Parameter. The new definition appears as a label/numeric slider pair below the New Parameter button in a new section entitled Existing Parameters.

 

 

Remember that what the sliders represent are the value of the enum that corresponds to the data type of the attribute, not the actual value of the attribute.

To add a Particle Attribute through Scripting

There are two options for adding particle attributes through scripting: the AddUserParameter command and the ParticleType.AddAttribute method. There are two differences between these approaches:

• The method adds the attribute to one specific particle type whereas the command allows you to specify several targets at once.

• The command also allows you to pass in clouds and automatically adds the attribute to every particle type under the cloud at once.

Removing Particle Attributes

You can only remove particle attributes through scripting: there is no way to do it in the UI. As with adding attributes, the command allows more options in specifying which particle types to remove attributes from:

• The ParticleType.RemoveAttribute method removes the attribute from one specific particle type whereas the command allows you to specify several targets at once.

• The RemoveUserParameter command also allows you to pass in clouds and automatically removes the attribute from every particle type under the cloud at once.

Accessing Existing User Parameters and Particle Attributes

You can access this data during execution of your event script only through the object model. The Particle.Attributes property gives you access to all particle attributes defined on the Particle object. Each ParticleAttribute object implements properties that let you access the attribute data type (ParticleAttribute.AttributeType) and value (ParticleAttribute.Value).

JScript Example: Reading from Goal Attributes in a Scripted Event

This example demonstrates how to grab values from Goals via the ParticleAttribute object inside a file-based ParticleEvent script. Things to note are:

• using the 3 input parameters in the function signature

• getting the ParticleCollection from the ParticleCloudPrimitive

• getting the ParticleAttributeCollection from the Particle object

• dealing with stored values in v3 (SIVector3) and v4 (SIQuaternion) ParticleAttribute objects

 

For more information on what information that goals provide through particle attributes, see Reading Information from Particle Goals.

function ReadGoalInfo( inParticleCloud, inTriggerParticleIndices, inSimFrame )
{
   logmessage( "CURRENT FRAME: " + inSimFrame );
   for ( var i=0; i<inParticleCloud.Particles.Count; i++ ) {
       var inParticle = inParticleCloud.Particles(i);
       LogMessage( "Looking at particle ID# " + inParticle.ID );

       var a = new Enumerator( inParticle.Attributes );
       for ( ; !a.atEnd(); a.moveNext()  ) {
          // Grab the current ParticleAttribute for convenience
          var gattr = a.item();

          // This switch block traps any complex data structures like
          // vectors by looking at the ParticleAttribute name before
          // displaying any values and storing any values in an
          // appropriate structure.
          switch (gattr.AttributeType) {
              case 0 :
                 // The UVWI pattern is stored in a vector4. Once you
                 // create a Quat to hold the stored value, you can just
                 // access each member by index (to keep things simple)
                 var q = XSIMath.CreateQuaternion();
                 q = gattr.Value;

                 // Getting the Values by index instead of name
                 LogMessage( "\t" + gattr.Name + " = "
                     + q(0) + ", " + q(1) + ", " + q(2) + ", " + q(3)
                     + " (UVWI)" );

                 // Getting the Values by name would look like this:
                 //LogMessage( "\t" + gattr.Name + " = "
                 //  + q.W + ", " + q.X + ", " + q.Y + ", " + q.Z
                 //  + " (WXYZ)" );

                 // You always have to break out of a switch statement to
                 // avoid running every scenario
                 break;
              case 1 :
                 // The Offset is stored in a vector3. Like the UVWI, you
                 // just create an empty v3 object and then you can access
                 // it either by index or name (which works in this case
                 // because the member names X,Y,Z correspond to the
                 // position of the offset
                 var v3 = XSIMath.CreateVector3();
                 v3 = gattr.Value;

                 // Getting the Values by name instead of index
                 LogMessage( "\t" + gattr.Name + " = "
                     + v3.X + ", " + v3.Y + ", " + v3.Z + " (XYZ)" );

                 // Getting the Values by index would look like this:
                 //LogMessage( "\t" + gattr.Name + " = "
                 //  + v3(0) + ", " + v3(1) + ", " + v3(2) + " (XYZ)" );
                 break;
              default :
                 // NOTE: Relying on 'default' is a little dangerous because
                 // if something wierd happened and the AttributeType was
                 // returning a negative number and the Value was undefined
                 // or null the script would break. To compensate, test
                 // the Value before using it
                 if (gattr.Value != null && typeof(gattr.Value) != "undefined") {
                     LogMessage( "\t" + gattr.Name + " = " + gattr.Value );
                 }
                 break;
          }
       }
   }
}

// This script logs something like this to the Script Editor window
// (except a whole lot more!):
// ...<snip>
//INFO : Looking at particle ID# 49
//INFO :  Goal_0_UVWI = 90, 0.6240180730819702, 0.06097915768623352, 0 (UVWI)
//INFO :  Goal_0_Offset = 0, 0, 0 (XYZ)
//INFO :  Goal_0_Weight = 1
//INFO : Looking at particle ID# 63
//INFO :  Goal_0_UVWI = 36, 0.17234236001968384, 0.23683372139930725, 0 (UVWI)
//INFO :  Goal_0_Offset = 0, 0, 0 (XYZ)
//INFO :  Goal_0_Weight = 1
// ...<snip>

 

What Kind of Data Can You Access?

This section focusses on two key concepts in working with particle events:

Script Contexts: the scope of the information made available in a scripted particle event, especially what scripting objects you can use—see About Context for Scripted Events.

Particle Goals: how the goal is pumping out information which can be trapped and used in a particle script—see Reading Information from Particle Goals.

About Context for Scripted Events

Particle events use Context to provide flexibility in the way that scripts run. There are three contexts available which allow you to fine-tune your effect by controlling exactly when your scripted event gets fired:

Per Cloud: Your script runs only once for the entire simulation (like an immediate plug-in).

Per Particle: Your script runs once for every particle (like a persistent plug-in).

Per Trigger Particle: Your script runs once for every particle that triggered the event.

 

Only embedded scripted events support these three contexts. For file-based scripted events, the script runs once for every particle emitted.

The other benefit that the context provides is a set of special hooks for information relevant to the context. For example, the Per Trigger Particle context provides information very specific to the trigger. The following table lists all the variables and which context supports them:

Variable Name

Supported in Context

Description

inParticleCloud

• All

The ParticleCloudPrimitive.

Available in all three contexts, this is also the first input parameter in a file-based script.

inTriggerParticleIndices

• All

The array of indices of the particles that triggered the event (whatever the context).

Available in all three contexts, this is also the second input parameter in a file-based script.

inSimFrame

• All

The current simulation frame corresponding to when the event occurred.

Available in all three contexts, this is also the third input parameter in a file-based script.

inParticleCollection

• All (not for file)

The ParticleCollection object.

inTriggerParticleCnt

• Per Trigger Particle

The index of the particle within the array of particles that triggered the event.

inParticle

• Per Trigger Particle

• Per Particle

This represents a single particle in the cloud.

For the Per Trigger Particle context, this is specifically one of the particles that triggered the event.

inParticleCnt

• Per Particle

The index of the particle within the cloud

Note: The Per Cloud context does not support any unique variables, but it does support the four variables that are common to all contexts.

Input Parameters for Scripted Event Handlers

For file-based scripted events there are three pieces of data available that you can capture with input parameters following this signature:

   YourFunctionName( inParticleCloud, inTriggerParticleIndices, inSimFrame );

Of course, you can choose any parameter name you want for these data, as long as you treat them the first parameter as a ParticleCloudPrimitive, the second parameter as an array of indices and the third parameter as a frame (double).

 

While the ParticleCloudPrimitive is always available, the second parameter may not be available at all times. For example, if your event has an Every N Frame type of trigger the second parameter will be empty or undefined because no particle triggered the event.

Reading Information from Particle Goals

Each particle has a set of particle attributes that are allocated to it when connecting a goal to the cloud. All attribute names start with Goal_N_ where N is the 0-based index of the goals connected to the particle simulation. For example, if you have three goals for a particle cloud, you would have Goal_0_UVWI , Goal_1_UVWI, and Goal_2_UVWI.

 

Particle goal information is accessible through the ParticleAttributeCollection which you can retrieve using the Particle.Attributes property.

For more information on working with particle attributes, see Adding and Accessing User Parameters and Particle Attributes.

For a specific example of how to read goal information stored in a particle attribute, see JScript Example: Reading from Goal Attributes in a Scripted Event.

Goal_N_UVWI

This is the target position of the particle on the goal object in a vector of four floats (see siParticleAttributeType). The first three values (UVW) of the vector correspond to the surface parametric values, and the last one (I) is an index:

Target Type

Geometry of Goal

Value

Description

Surface

NURBS surface

U, V

the parametric values of the surface

W

represents the depth when the goal target type is Volume

I

the index of the subsurface

polygon mesh

U, V

the barycentric coordinates of the target on that triangle

W

the depth along the triangle normal

I

the index of the target triangle

Point

both

I

the index of the point

Line

both

U

the parametric value along this element

Line

both

I

the index of the edge or subcurve or curve

Possible options for target positions on a goal are:

Surface: each particle is attracted to a randomly selected position (a random U and V value) on a polygon mesh or NURBS surface.

Point: each particle is attracted to a randomly selected point (control vertex) on the goal’s geometry.

Line: each particle is attracted to a randomly selected position on edges, isolines, or curves of the goal geometry.

Center: each particle is attracted to the center of the goal object.

If the goal object is a particle cloud, you can use only the Center as the target. As well, if the Target option you select isn’t supported by the type of object the goal is, the target object’s Center is used.

Volume: each particle is attracted to a randomly selected position inside the target geometry.

 

Goal_N_Offset

This is a vector that offsets the target point (coordinates are in global space). You can offset the target position of the goal for the particles with respect to the goal object. This is particularly useful with variance to blur the target’s position a bit.

For example, if you have particles moving towards a goal, the offset directs the particles to each goal’s vertex plus the offset.

Goal_N_Weight

This allows you to set a different goal weight for each particle. When you set the weight of a particle goal, you are changing how much of an influence it has on the particles that are attracted to it.

While you can set the weight for a single goal, weighting is most useful when particles have multiple goals to which they’re attracted. Each goal acts like a competing force on the particles, which is controlled by its weight. Setting each goal’s weight allows you to blend (and animate) the amount of influence the different goals have on the particles.

Limitations on Accessing Data

Scripted Particle Events are very similar to custom operators: they both read scene data as input, perform certain calculations and then write the modified data back to specific scene items. They also share something else in common: there are restrictions on what SDK functions you can use.

For both these customization types you are not allowed to use custom commands or object model methods and properties that are not marked with the Scripted Operator indicator (*). For a list of methods and properties that are scripted operator-compliant, see What You Can Call from a Custom Operator or a Particle Event.