Intro
Particles are essential in 3D. They can be used to represent fire, smoke, water, a swarm of bees, or a flock of birds. But presently Papervision doesn’t have a well-developed particle system. In the book, we develop a number of classes to enhance Papervision’s particle framework.
Particles systems typically have the following properties;
- New particles are generated from an emitter
- Each particle has unique attributes
- Particles have a life time (or designated space) and afterwards are removed or reused
- Particles are governed by scripts (classes that determine their behavior)
- Particles are rendered
But in Flash the best way to handle particle lifetime is to regenerate them when their life time is over.
Click the image below to see the program in action. The particle emitter will follow your mouse, respond to gravity, bounce off a floor 3 times and then be regenerated back to the mouse pointer.
There are a number of key concepts here and particles are discussed in detail in the book. In this post, we shall lightly touch on the important concepts and provide you with all the code.
Demo
http://www.professionalpapervision.com/demos/web/pemitter/
Download
http://flex3cookbook2.googlecode.com/files/particleEmitter.zip
Discussion
Here are the key concepts:
Emitter
Creating an emitter is simple – you just tell the particles where to be created. In the code, we generate them at the mouse pointer with a small variation on velocity.
myParticle.ypos = mouseY-vpY;
myParticle.xpos = mouseX-vpX;
myParticle.vx = Math.random() * 6 – 3;
myParticle.vy = Math.random() * 6 – 6;
myParticle.vz = Math.random() * 6 – 3;
Regenerator
When a particle has outlived its lifetime we regenerate it by sending it back to the source. As discussed in previous posts regenerating particles is preferable based on how Flash handles memory resources.
myParticle.ypos = mouseY-vpY;
myParticle.xpos = mouseX-vpX;
z-Sorter
Since we are dealing with multiple particles we must sort them based on z-position.
private function sortZ():void
{
myParticles.sortOn(“zpos”, Array.DESCENDING | Array.NUMERIC);
for(var i:uint = 0; i < nummyParticles; i++)
{
var myParticle:ParticleEmitter = myParticles[i];
setChildIndex(myParticle, i);
}
Velocity
Velocity was added by incrementing it in the onEnterFrame loop.
myParticle.xpos += myParticle.vx;
myParticle.ypos += myParticle.vy;
myParticle.zpos += myParticle.vz;
Gravity
Gravity is created by iterating the y-velocity. To get smoke you would just change the sign of the gravity term and iterate upwards.
myParticle.vy += gravity;
Bounce
When the particle exceeds the floor then bounce is created by reversing velocity and doing an decimal multiplication on velocity.
myParticle.bounceNum++;
myParticle.vy *= bounce;
In the example above, bounce is equal to -.6 which reverses the velocity and reduces it exponentially.
Perspective Scaling
Thales theorem is used for perspective scaling in the z-coordinates.
var scale:Number = fl / (fl + myParticle.zpos);
myParticle.scaleX = myParticle.scaleY = scale;
myParticle.x = vpX + myParticle.xpos * scale;
myParticle.y = vpY + myParticle.ypos * scale;
Primitive
Even though this is not Papervision, it demonstrates how a primitive is set up. But the physics is in the primitive but Papervision does not have a physics engine. So the big questions is where should the physics go. Should it go on the primitive or on the DisplayObject3D. In this case, it is on the primitive, but putting it on the DisplayObject3D gives you the advantage of have the physics on all objects …not just primitives … more on this later.
In our primitive we have four possible shapes and they are selected randomly and thrown onto the stage using the switch case.
The random selector is on the main program side given by
var myParticle:ParticleEmitter= new ParticleEmitter(10, Math.random() * 0xffffff, Math.round(Math.random()*3));
where Math.round(Math.random()*3 selects from four possible particles found in the switch case from 0 to 3 on the primitive side.
switch(switchNum)
{
case 0:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawRect(0,0,2*radius,2*radius);
graphics.endFill();
break;
case 1:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawCircle(0, 0, radius);
graphics.endFill();
break;
case 2:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawEllipse(0, 0, 2*radius, radius);
graphics.endFill();
break;
case 3:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawEllipse(0, 0, radius, 2*radius);
graphics.endFill();
break;
default:
trace(“warning – Particle material has no valid shape.”);
break;
}
}
To see the full source code click the more link below.
Particle Primitive (4 possible particles)
package primitive{
import flash.display.Sprite;
public class ParticleEmitter extends Sprite {
public var radius:Number;
private var color:uint;
public var xpos:Number = 0;
public var ypos:Number = 0;
public var zpos:Number = 0;
public var vx:Number = 0;
public var vy:Number = 0;
public var vz:Number = 0;
public var mass:Number = 1;
public var bounceNum:int=0;
public var switchNum:int;
public function ParticleEmitter(radius:Number=40, color:uint=0xff0000, switchNum:int=0 ) {
this.radius = radius;
this.color = color;
this.switchNum=switchNum;
init();
}
public function init():void {
switch(switchNum)
{
case 0:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawRect(0,0,2*radius,2*radius);
graphics.endFill();
break;
case 1:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawCircle(0, 0, radius);
graphics.endFill();
break;
case 2:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawEllipse(0, 0, 2*radius, radius);
graphics.endFill();
break;
case 3:
graphics.lineStyle(0);
graphics.beginFill(color);
graphics.drawEllipse(0, 0, radius, 2*radius);
graphics.endFill();
break;
default:
trace(“warning – Particle material has no valid shape.”);
break;
}
}
}
}
Particle Emitter
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.events.MouseEvent;
import primitive.ParticleEmitter;
public class Particles extends Sprite
{
private var myParticles:Array;
private var nummyParticles:uint = 100;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
private var gravity:Number = 0.2;
private var floor:Number = 200;
private var bounce:Number = -0.6;
public function Particles()
{
init();
}
private function init():void
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
myParticles = new Array();
for(var i:uint = 0; i < nummyParticles; i++)
{
var myParticle:ParticleEmitter= new ParticleEmitter(10, Math.random() * 0xffffff, Math.round(Math.random()*3));
myParticles.push(myParticle);
myParticle.ypos =200;
myParticle.xpos = mouseX-vpX;
myParticle.zpos=0;
myParticle.vx = Math.random() * 6 – 3;
myParticle.vy = Math.random() * 6 – 6;
myParticle.vz = Math.random() * 6 – 3;
myParticle.visible=false;
addChild(myParticle);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
for(var i:uint = 0; i < nummyParticles; i++)
{
var myParticle:ParticleEmitter = myParticles[i];
move(myParticle);
}
sortZ();
}
private function move(myParticle:ParticleEmitter):void
{
var radius:Number = myParticle.radius;
myParticle.vy += gravity;
myParticle.xpos += myParticle.vx;
myParticle.ypos += myParticle.vy;
myParticle.zpos += myParticle.vz;
if(myParticle.ypos > floor)
{
myParticle.bounceNum++;
myParticle.vy *= bounce;
if(myParticle.bounceNum>=4){
myParticle.ypos = mouseY-vpY;
myParticle.xpos = mouseX-vpX;
myParticle.zpos = 0;
myParticle.vx = Math.random() * 6 – 3;
myParticle.vy = Math.random() * 6 – 6;
myParticle.vz = Math.random() * 6 – 3;
myParticle.visible=true;
myParticle.bounceNum=0;
}
}
if(myParticle.zpos > -fl)
{
var scale:Number = fl / (fl + myParticle.zpos);
myParticle.scaleX = myParticle.scaleY = scale;
myParticle.x = vpX + myParticle.xpos * scale;
myParticle.y = vpY + myParticle.ypos * scale;
}
else
{
myParticle.visible = false;
}
}
private function sortZ():void
{
myParticles.sortOn(“zpos”, Array.DESCENDING | Array.NUMERIC);
for(var i:uint = 0; i < nummyParticles; i++)
{
var myParticle:ParticleEmitter = myParticles[i];
setChildIndex(myParticle, i);
}
}
}
}

[...] > Creating a Simple Flash Particle Emitter « Professional Papervision3D Book [...]