Particle Motion Transfer to Scene Objects |
This simple tutorial
provides the basics of moving scene objects using Particle Flow.
The objects can be anything from lights to helpers or mesh objects.
In this example, we will move a large number of Atmospheric Gizmos with a Fire Effect assigned to simulate a steaming Teapot.
Click here to download the MAX Scene (56K, 3dsmax 5.1 with PFlow, 3dsmax 6 or higher)
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 operatator 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 etc. 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!
Particle View Setup |
Script |
on ChannelsUsed pCont do
(
pCont.useTM = true
pCont.useAge = true
)
on Init pCont do
(
global My_Athmospheric_Gizmos_01
= $SphereGizmo*
My_Athmospheric_Gizmos_01.pos = [0,0,-100000]
)
on Proceed pCont do
(
partcount = pCont.NumParticles()
count = amin #(partcount,My_Athmospheric_Gizmos_01.count)
for i in
1 to count do
(
pCont.particleIndex = i
My_Athmospheric_Gizmos_01[i].transform = pCont.ParticleTM
My_Athmospheric_Gizmos_01[i].radius =
10 + pCont.ParticleAge*2
)
)
on Release pCont do
(
)
Results |
The following QuickTime
animation (6MB) shows the resulting animation (rendered in Brazil r/s, still
image below rendered in Scanline)
Step-By-Step Comments |
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 copu 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_Athmospheric_Gizmos_01 = $SphereGizmo*
We define a global variable which 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_Athmospheric_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 every time the Script Operator is evaluated by Particle Flow.
It contains the actual body of the script.
The parameter pCont contains the Particle Container which 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 0 or millions of particles in the event.
count = amin #(partcount,My_Athmospheric_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 could be expressed with
if partcount < My_Athmospheric_Gizmos_01.count then count = partcount else count = My_Athmospheric_Gizmos_01.count
but you must admit the first version looks cooler! Also, it would work 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 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
In order 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_Athmospheric_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_Athmospheric_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 minumum size of a gizmo (when particleAge is 0), at age of 10 the gizmo's radius will be 30 units etc.
You can play with different values to get different cloud behaviours.
)
)
on Release pCont do
(
)
Copyright © 2004 by Borislav 'Bobo' Petrov. |