Creating an Operator to Manipulate Particle Clouds

This example demonstrates how to write a compiled Softimage operator that manipulates a particle cloud. In this example, the particles in the cloud are made to flock together, and a custom property page allows the user to tweak the variables that control the flocking behavior. This example uses Softimage's C++ API.

 

This case study is divided into two parts:

Part 1

Getting Started

 

Installation

Drag and drop the ExampleParticleOp.xsiaddon file into Softimage or choose the File > Add-ons > Install command. Creating your own add-on is discussed further in Installing the Plug-in. An .xsiaddon file demonstrates how easily a plug-in can be distributed, even when it contains a compiled .dll. 

 

using the example

The add-on file creates the ExampleParticleOp toolbar. Click the Demo button to launch a sample script (ExampleParticleOpDemo.vbs), which creates a scene to demonstrate the capabilities of the operator. Move the sliders on the property page to change the space in which the simulation occurs and the flocking behavior of the particles.

 

You can use the operator on basically any particle cloud. You can freeze the particle cloud and apply the operator with the "ApplyOp" command. For more details see ExampleParticleOpDemo.vbs.

 

Compilation

From Microsoft Visual Studio, load the ExampleParticleOp.dsp file as the workspace and compile. Visual Studio needs to be configured with the Softimage SDK include path in the list of include paths. See the Plug-in Integration guide for more information about setting up your environment to compile an Softimage plug-in.

 

Part 2

Case Study - building the Plug-in

This section gives some additional details on the workflow used to develop this plug-in, with the aim of giving you an idea how to build your own Softimage plug-in operators. The following steps are covered:

§         Choosing the type of plug-in and doing some design.

§         Prototyping as a scripted operator.

§         Converting from a scripted operator to a C++ plug-in, getting it compiled, installed and ready for debugging.

§         Implementing the algorithm.

§         Exposing pParameters to control the algorithm's behavior.

§         Providing a custom UI for the operator's property page.

§         Packing the finished plug-in as an add-on.

Planning/Design

The first step in writing a plug-in is careful consideration of the purpose of the plug-in. This is important for any software project, but specifically in the context of Softimage this includes the consideration of:

In this case, with the goal of creating a custom particle simulation, a persistent effect, in particular an Softimage operator, is the most suitable type of plug-in because it provides behavior similar to that of Softimage's built-in cloud simulation--when the user presses Play, they automatically see the simulation occur. An immediate effect, like a custom command, could also change the positions of particles in the cloud, but it would only do this when the command is executed. 

 

Prototyping from Scripted Operator

The initial proof-of-concept work was all done in Softimage using a Scripted Operator. 

 

When developing a plug-in it is often very useful to establish a demo script immediately.  This can be better than reusing a scene file, because it makes the steps to use the plug-in very clear, and avoids unexpected dependencies on the contents of a scene file.  So, in this case, the first step was to create a simple script to create a frozen cloud:

'ExampleParticleOpDemo.vbs
'
'Initial script to set up the particle cloud
'with a specified number of particles
 
option explicit
 
dim g_NumberOfParticles
g_NumberOfParticles = 27
 
SetupScene()
 
sub SetupScene()
 
          dim oCloud, oEmitter, oCluster, oUDM, oOps, oOp
 
          NewScene ,false
          set oCloud = CreateParticleCloud().Item( 0 )
 
          'Use an emitter to create our particles
          set oEmitter= CreatePrim( "Sphere", "MeshSurface", "ParticleEmitter" )
          AddParticleEmitter oCloud, oEmitter 
 
          'At 1 second the number of particles specified by the
          'emission rate will have been reached
          SetValue oEmitter & ".ParticleEmitter_emission.Rate", g_NumberOfParticles
          SetValue "PlayControl.Current", 30
 
          'Now get rid of the emitter and freeze the cloud - we will
          'simulate the particles ourselves
          FreezeObj oCloud
          DeleteObj oEmitter
          oEmitter = Empty
 
          SetValue "PlayControl.Current", 1
 
          
 
end sub

The cloud is frozen to remove the built-in Softimage particle simulator operator.  When the scene is played back after running this scrip,  the particles do not move at all.  Next a simple scripted operator was attached to the particle cloud primitive, which will take care of driving the particle simulation:

 

 

At this point there is no flocking algorithm, just some simple code to access the input and output object and to move the particles.  By default a scripted operator will only be reevaluated if one of its input objects has changed.  To force this operator to reevaluate at each frame, an animated parameter called “TimeVar” is added. 

 

Notice how Application.Logmessage is used to print values into the script window as a means of debugging.

 

Moving to C++

Once the basic concept of controlling the cloud shape is working, you can start moving the implementation to C++. The first step is saving the Scripted Operator's SPDL file as ExampleParticleOp.spdl. The SPDL file is shown below: 

SPDL
Version = "2.0.0.1";
Reference = "{CCEEAC35-D311-4F71-9D71-96E0C337D021}";
Name = "ParticleOpExample";
 
PropertySet "ExampleParticleOp_Params"
{
          Parameter "ExampleParticleOp" input
          {
                               GUID  = {CCEEAC35-D311-4F71-9D71-96E0C337D021};
                    Type  = VT_EMPTY;
                    Caps  = Persistable; /* 0x00000004 */
          }
 
          Parameter "TimeVar" input
          {
                    GUID  = {A5572AA8-FD13-41B2-857B-4F26729DE222};
                    Name  = "TimeVar";
                    Description = "TimeVar";
                    Type  = VT_I4;
                    Caps  = Persistable, Animatable; /* 0x00000005 */
                    Class = Visualization, 0x00004060; /* 0x00004061 */
                    Value = 0;
                    Range = 0 To 400;
          }
}
 
PortSet "ScriptedOpPrototype_Ports"
{
          Group "Group_0"
          {
                    Origin = Select;
 
                    Output "Out_Cloud"
                    {
                               Major = {E80A1590-CA13-11CF-91B9-00AA00624C2D};
                               Minor = {3A9ECA60-4F91-11D0-AA46-00A0243E34C4};
                               Interface = {CDEA6775-415B-11D0-AA40-00A0243E34C4};
                               Attributes = 0x00000001;
                    }
          }
}
 
Plugin = Scripted
{
          Language = "VBScript";
 
          Update =
          BeginScript
dim oCloud 
 
'Output port is connected to the ParticleCloudPrimitve obj
set oCloud = Out.Value
 
logmessage "Evaluation Ouput:" & oCloud & ", Input: " & oUDM
 
set oPos = xsimath.CreateVector3
 
set oParticleColl = oCloud.Particles
 
'For the purposes of our protype just demonstrate
'that we can move all the particles each frame
for cnt = 0 to oParticleColl.Count - 1
          set oParticle = oParticleColl.Item( cnt )
 
          oPos.x = 0.1 * oParticle.ID
          oPos.y = -2 + 0.05 * In_UpdateContext.CurrentFrame
          oPos.z = 0
 
          oParticle.Position = oPos
 
next
          EndScript
 
          Helpers =
          BeginScript
 
          EndScript
 
}

There are three major sections to this file: PropertySet, PortSet, and Plugin. In order to port to C++, the Plugin section is changed to the following (the # symbol denotes a comment line):

Plugin = Library
{
          #defines the dll name
          Filename = "ExampleParticleOp";
 
          EntryPoints
          {
                    #Name of the exported API function
                    Update = "ExampleParticleOp_Update";
                    Term = "ExampleParticleOp_Term";
          }
}

The next step is creating a .dll (ExampleParticleOp) with  two entry points: ExampleParticleOp_Update and ExampleParticleOp_Term.  The .dll is created in the same directory as ExampleParticleOp.spdl. Initially, empty implementations are created as follows:

 

CStatus ExampleParticleOp_Init

(

                UpdateContext&       in_ctx,

                OutputPort&                        in_outPort

)

{

                return CStatus::OK ; 

}

CStatus ExampleParticleOp_Term(

                                                                XSI::UpdateContext& in_rCtx,

                                                                long )

{

                return CStatus::OK ; 

}

 

An empty implementation of ExampleParticleOp_Update is implemented (returning S_OK) and the project compiles properly, and is ready for testing in Softimage. 

 

Installing the SPDL and dll

For the end user of a plug-in, the Softimage add-on format makes installing and uninstalling the plug-in easy. However as a plug-in developer you need to do some more manual work to get the plug-in installed.

 

Once the debug configuration is compiled, the ExampleParticleOp.dll file is created in the Debug subdirectories. (Make sure that only one of either Debug or Release is present, otherwise Softimage will install both). Next this command is used from the Softimage command prompt to copy the SPDL file and .dll into the Softimage user directory.

 

xsi -i ExampleParticleOpDemo.SPDL

This operation also registers the plug-in and creates the ExampleParticleOp.preset file. Preset files contain the initial values for the parameters.

 

Once installed it is time to extend the demo script, ExampleParticleOpDemo.vbs, so that it applies the new operator.  This is done by adding the following code.

           on error resume next
          set oOps = ApplyOp("ExampleParticleOpCpp")
          
          if err <> 0 then
                    logmessage "The ExampleParticleOp is not properly installed.  Please try re-installing the addon file"
                    exit sub
          end if 
 
          on error goto 0
 
          set oOp = oOps.Item(0)
          SetKey oOp & ".TimeVar", 1, 0
          SetKey oOp & ".TimeVar", 100, 400
 

Although the .dll was still just an empty stub, this demonstrates that everything is working well enough to start doing some actual development. Because you know the original VBScript code works, the next step is to implement ExampleParticleOp_Update by porting the VBScript code into C++, for example: 

             set oCloud = Out.Value

becomes

                  ParticleCloudPrimitive outCloudPrim(in_outPort.GetValue());
 

Now that you have a new .dll, Softimage needs to be updated by uninstalling and then reinstalling the .dll using:

 

xsi -u ExampleParticleOpDemo.SPDL -silent
xsi -i ExampleParticleOpDemo.SPDL

 

After various iterations, you'll have a working operator that moves the particles in the cloud, at which point you're ready to start working on the details.

 

Debugging

Problems in the SPDL file or missing .dlls will result in error messages inside Softimage or when "xsi -i" is run. However once this is working, you may want to debug your actual C++ code. Rather than resorting to printf statements, you can use the Visual Studio debugger effectively to step through the code and even use the Apply Code Changes feature to make code changes in memory without restarting the debugger.

When you install your operator, your DLL is copied into the user directory, for example: C:\users\<yourname>\Softimage\XSI_3.5\Application\bin\nt-x86\ParticleOpDemo.dll. Normally it is this copy that will get loaded when you use your operator. However if you wish to debug you can force Softimage to load the DLL from its original location by launching Softimage from the directory containing that DLL.

 

You can do this in Visual Studio by specifying two settings under "Project\Settings\Debug".

1) Set the "Executable for Debug Session" to the full path of XSI.exe

2) Set the "Working Directory" to the path of your operator's source code (where the .dll was compiled)

 

You cannot initially set a breakpoint.  However if you launch Softimage from the debugger and then apply your operator it will load the DLL and you will be able to set break points. (When you launch the debugger it will give a warning that XSI.exe does not contain debugger symbols.  You can safely ignore this).

Once you are done developing and debugging be sure to re-run:

xsi -u ExampleParticleOpDemo.SPDL -silent
xsi -i ExampleParticleOpDemo.SPDL
 

so that the copy of the .dll in the user directory is also updated.

 

The Algorithm

Each evaluation of the operator is straightforward. It reads the previous positions, calculates the interactions between the particles, and moves each particle a small amount to represent their next positions. Based on research into flocking algorithms and experimentation, it was decided that the previous velocity vector of each particle was required. This avoids the boredom of each particle having a constant speed but more importantly defines the direction that the particle is moving.

 

The flocking aspect of the algorithm uses the concept that a particle will tend to go in the same direction but that it will also be attracted to other particles that are reasonably close. It also uses random jitter and repulsion to make sure that the particles don't just all rush to the exact same point in space. A bounding sphere is used to keep the particles on the screen. There are some "constants" that give the user control over the behavior. For example the FlockInfluence is how much to weight the influence of other particles against the particle's natural momentum. For more details, see the actual implementation.

 

Simulation State

As described in the section on the Algorithm, it is necessary to know each particle's Velocity and Position at the previous frame. You may be tempted to use the Particle's Velocity and Position attributes to store this information. However, this is not a reliable method because of the possible interaction with other operators. Instead, the state of the simulation is tracked inside the operator using the GetUserData and SetUserData methods of the UpdateContext object. The plug-in creates an array containing the following structure for each particle.

struct PerParticleData
 
{
          double pos[ 3 ] ;
          double vel[ 3 ] ;
} ;

In theory, it is possible to imagine using a global variable to store this information.  However by saving the buffer using UpdateContext::SetUserData there will be no risk of conflict with other instances of the operator in the same scene.

 

Adding Parameters

With a clear design from the onset you can potentially specify all the parameters directly at the time of prototyping the scripted operator. However commonly there is more an iterative process of adding parameters at a later time to offer more functionality to the user. 

 

The most direct way to add or change parameters to a compiled operator is to edit the SPDL file directly.  The format for the parameter section is fairly straightforward.  For example, the Bounding Sphere Radius is a double value with a default value of 6, a slider from 0.01 to 100 and a valid range from 0.01 to 1000.

 

Bounding Spere Radius appears like this in the SPDL file:

                Parameter "BoundaryLength" input

                {

                                Name                   = "Bounding Sphere Radius";

                                Description            = "Bounding Sphere Radius";

                                Guid                   = "{5CE83718-8581-4F08-A15D-FB6E2242E0D2}";

                                Type                   = VT_R8;

                                Range                  = 0.01 to 1000;

                                Caps                   = Persistable, Animatable;

                                Class                  = Unknown;

                                Value = 6.0;            

                                UIType                 = "Number";

                                UIRange                = 0.01 to 100;

                }

 

The most important thing to remember when tweaking parameters is to make sure that each parameter has a unique GUID. If you ever cut and paste from another SPDL file you must change the GUID. Microsoft Visual Studio comes with a utility called guidgen which allows you to generate GUIDs. You will also need to reinstall the SPDL file each time you make a change to the parameter section. When editing the SPDL file it is important to edit the original copy of the SPDL file in the plug-in directory, not the copy of the SPDL file that is saved in the user directory.  Any changes in the user directory version of the SPDL will be lost when the plug-in is reinstalled.

 

Having these parameters on the Operators has no influence over the flocking behavior until you add more C++ code to read the values of the parameters. You can separate the code that performs the core algorithm from the code that communicates with Softimage. A simple structure called SimulationParams and a helper function called GetParameters() reads the state of the parameters and fills in the structure. The core algorithm just uses the SimulationParams structure.

 

This is a screen shot showing how the operator appears in the Softimage explorer once all the parameters are added:

 

 

Customizing the Layout

You can also design your own custom layout for the property page. In this case related properties are grouped inside boxes. The TimeVar parameter is not included because this is a hidden parameter, used just to force evaluation at each frame.

Layout "Default"
{
          group "Behavior"
          {
                    "FlockInfluence";
                    "FlockDistance";
                    "RepulsiveDistance";
                    "Velocity";
                    "DirectionVariance";
          }
 
          group "Particle Boundary"
          {
                    "BoundaryLength";
          }
 
          group "Centre of Simulation" 
          {
                    "x";
                    "y";
                    "z";
          }
}

Packaging the Example

Once satisfied that the plug-in works well enough to make an example, the SPDL is uninstalled,  the debug .dll is deleted from the Debug subdirectory inside the project, a release version is compiled, and the SPDL is reinstalled.

 

Softimage is restarted and some final touches are added to the example script and a new toolbar called ExampleParticleOp is created. Drag the script to the toolbar to create a new button called "Demo" that will execute this script. 

 

Use the File > Add-on > Package dialog to prepare the add-on. Pick the ExampleParticleOp toolbar and the ExampleParticleOp preset. Softimage automatically recognizes that the ExampleParticleOp.SPDL, and ExampleParticleOp.dll and ExampleParticleOpDemo.vbs files should also be included. The resulting xsiaddon file is a completely self contained file containing all these different elements.