How To ... Transfer ParticleFlow Particle Motion To Scene Objects

This simple tutorial explains the basics of moving scene objects using Particle Flow. The objects can be anything from lights through helpers to mesh objects.

In this example, we will move a large number of Atmospheric Gizmos with a Fire Effect assigned to simulate a steaming Teapot.

NATURAL LANGUAGE - SETUP

Go to Create tab > Helpers > Atmospheric Apparatus > Sphere Gizmo.

Create a single object called SphereGizmo01 with any radius.

Shift-Move the object and enter 29 to create 30 copies of the Sphere Gizmo (not Instances).

Create a Particle Flow with Birth operator generating particles from start to end frame at the rate of 20.0.

Use Speed operator to accelerate the particles up. Use some variation to get a more random look.

Set the Display to Geometry.

Add an AgeTest to kill the particles after 50 frames. Wire to a Delete operator in a separate event.

Add a Force and some Wind if you want more motion.

To get the scene working completely, assign a Fire Effect in the Environment dialog and assign ALL 30 SphereGizmos to it. Play with the color settings, density, and others to get the look you want.

Create a Teapot and place the Particle Flow at the spout to get the steam coming out of the right place.

SCRIPT OPERATOR CODE:

on ChannelsUsed pCont do
(
 pCont.useTM = true
 pCont.useAge = true
)

on Init pCont do
(
 global My_Atmospheric_Gizmos_01 = $SphereGizmo*
 My_Atmospheric_Gizmos_01.pos = [0,0,-100000]
)

on Proceed pCont do
(
 partcount = pCont.NumParticles()
 count = amin #(partcount, My_Atmospheric_Gizmos_01.count)
 for i in 1 to count do
 (
  pCont.particleIndex = i
  My_Atmospheric_Gizmos_01[i].transform = pCont.ParticleTM
  My_Atmospheric_Gizmos_01[i].radius = 10 + pCont.ParticleAge*2
 )
)

on Release pCont do ( )

RESULT:

Step-By-Step

on ChannelsUsed pCont do
(

The ChannelsUsed handler defines the channels to be used by the Script Operator. You cannot get or set particle related values from the particle container without specifying which properties you need access to. This way, Particle Flow does not have to provide the Script Operator with all possible channels (and there can be an arbitrary number of channels in Particle Flow), but only with those that are actually needed. This conserves memory.

The parameter pCont contains the Particle Container.

pCont.useTM = true

We want to copy the complete transformation of the particle to the scene object, so we will need access to that channel.

pCont.useAge = true

To get an even nicer effect, we will want to read the age of the particle and assign it to the gizmos' radius property to make them grow over time.

)

on Init pCont do
(

The Init handler is used to initialize the Script Operator. The parameter pCont contains the Particle Container.

global My_Atmospheric_Gizmos_01 = $SphereGizmo*

We define a global variable that will contain an array of the scene objects to be driven by the particles. In this case, we collect all SphereGizmos from the scene using their common base name.

My_Atmospheric_Gizmos_01.pos = [0,0,-100000]

To remove the gizmos from the scene in the first frames of the animation, we move them away from the camera field of view, in this case way down.

)

on Proceed pCont do
(

The Proceed handler is called each time the Script Operator is evaluated by Particle Flow. It contains the actual body of the script. The parameter pCont contains the Particle Container that contains all particles the Operator is applied to.

partcount = pCont.NumParticles()

First, we read the number of particles in the current Event. There can be zero or millions of particles in the event.

count = amin #(partcount, My_Atmospheric_Gizmos_01.count)

Then, we compare the number of particles to the number of collected gizmos. The function amin() returns the smallest value in the array.

THE SAME CAN BE EXPRESSED AS

if partcount < My_Atmospheric_Gizmos_01.count then
count = partcount
else
count = My_Atmospheric_Gizmos_01.count

As you see, the first version is much shorter. Also, it works with more than two values if necessary, always picking the smallest value.

WHY DO WE NEED THIS?

Because we have a limited number of particles in the beginning of the animation, less than the number of gizmos. After a while, the number of particles will be higher than the available gizmos. In both cases, we want to build "pairs" of gizmo+particle, and some of the particles or gizmos might have no corresponding partner at certain parts of the animation. Knowing the lowest count of the two sets of objects gives us the number of possible pairs that we can process.

 for i in 1 to count do
(

Now, we will repeat the following code as many times as there are possible particle+gizmo pairs in the scene.

pCont.particleIndex = i

To read data from a particle, we must make it the current one. To do so, we assign the index i to the particleIndex property of the particleContainer of the current event. After this, any particle-related queries or assignments will be performed on the i-th particle only.

My_Atmospheric_Gizmos_01[i].transform = pCont.ParticleTM

Now, we can assign the transformation matrix of the current i-th particle to the i-th gizmo in the array. Note that because of our smart amin test above, i is guaranteed smaller or equal to both the available particle and gizmo counts.

My_Atmospheric_Gizmos_01[i].radius = 10 + pCont.ParticleAge*2

Finally, we change the radius of the i-th gizmo to 10 units plus twice the age of the i-th particle. 10 is the minimum size of a gizmo (when particleAge is 0), at age of 10 the gizmo's radius will be 30 units or so. You can use different values to get different cloud behaviors.

)
)

on Release pCont do ()