kelvinluck.com

a stroke of luck

Experiment with Papervision 3D particles and effects

A while back I was prototyping something for a client which involved lots of red dots moving around in 3D space, realised using Papervision 3D. I didn’t end up persuing this route with the client in the end but the effect was pretty cool so I thought I might as well share it here.

The idea is that there is a bunch of particles who are bouncing around randomly stuck within an invisible cube. The effect looked OK by itself but then I decided to try adding effects. I used a BlurFilter and a BitmapColorEffect to give the each of the particles trails. Then I changed the clipping point like in the original borg cube effects demo to give the impression of the particles falling. I like this version the best – if you move your mouse from side to side around the bottom of the demo swf then it starts to look like some kind of flocking is going on (like in my perlin noise experiment).

Click on the image below to see the demo. Click inside the demo swf to give it focus and then you can use the following keys:
  • 1 – Sets the render mode to normal clean particles (the default).
  • 2 – Sets the render mode to particles with trails.
  • 3 – Sets the render mode to falling particles with trails.
  • c – Toggles display of a cube showing the area the particles are contained within.

Particles and effects in Papervision 3D

The sourcecode for this example is pretty simple. You can see it below or you can download it from here.

package  
{
   import org.papervision3d.core.effects.BitmapColorEffect;
   import org.papervision3d.core.effects.BitmapLayerEffect;
   import org.papervision3d.core.geom.Particles;
   import org.papervision3d.materials.WireframeMaterial;
   import org.papervision3d.materials.utils.MaterialsList;
   import org.papervision3d.objects.DisplayObject3D;
   import org.papervision3d.objects.primitives.Cube;
   import org.papervision3d.view.AbstractView;
   import org.papervision3d.view.BasicView;
   import org.papervision3d.view.layer.BitmapEffectLayer;
   
   import flash.display.StageQuality;
   import flash.events.Event;
   import flash.events.KeyboardEvent;
   import flash.filters.BlurFilter;
   import flash.geom.Point;      

   /**
    * @author Kelvin Luck
    */

   [SWF(width='450', height='450', backgroundColor='#000000', frameRate='41')]

   public class ParticlesCube extends BasicView
   {
     
      public static const NUM_PARTICLES:int = 300;
      public static const CONTAINING_CUBE_SIZE:int = 500;
     
      public static const RENDER_MODE_CLEAN:int = 0;
      public static const RENDER_MODE_TRAILS:int = 1;
      public static const RENDER_MODE_FALLING:int = 2;
     
      private var particlesContainer:DisplayObject3D;
      private var particlesHolder:Particles;
      private var particles:Array;
      private var boundsCube:Cube;

      private var bfx:BitmapEffectLayer;
     
      private var _renderMode:int;
      public function set renderMode(value:int):void
      {
         if (value == _renderMode) return;
         
         clearBitmapEffects();
         
         var clippingPoint:Point = new Point();
         
         switch (value) {
            case RENDER_MODE_CLEAN:
               // nothing - effects already cleared above...
               break;
            case RENDER_MODE_FALLING:
               clippingPoint.y = -2;
               // fall through...
            case RENDER_MODE_TRAILS:
               bfx = new BitmapEffectLayer(viewport, stage.stageWidth, stage.stageHeight, true, 0xffffff);
               
               bfx.addEffect(new BitmapLayerEffect(new BlurFilter(2, 2, 2)));
               bfx.addEffect(new BitmapColorEffect(1, 1, 1, .9));
               
               bfx.clippingPoint = clippingPoint;
               
               bfx.addDisplayObject3D(particlesHolder);
               
               viewport.containerSprite.addLayer(bfx);
               break;
            default:
               throw new Error(value + ' is an invalid render mode');
         }
         _renderMode = value;
      }
     
      private var _displayCube:Boolean = true;
      public function set displayCube(value:Boolean):void
      {
         if (value != _displayCube) {
            _displayCube = value;
            boundsCube.visible = value;
         }
      }

      public function ParticlesCube()
      {
         super(550, 550);
         
         stage.quality = StageQuality.MEDIUM;
         
         particlesContainer = new DisplayObject3D();
         scene.addChild(particlesContainer);
         
         var cubeMaterial:WireframeMaterial = new WireframeMaterial(0x0000ff, 1, 2);
         var materialsList:MaterialsList = new MaterialsList();
         materialsList.addMaterial(cubeMaterial, 'all');
         
         boundsCube = new Cube(materialsList, CONTAINING_CUBE_SIZE, CONTAINING_CUBE_SIZE, CONTAINING_CUBE_SIZE);
         particlesContainer.addChild(boundsCube);
         displayCube = false;
         
         particlesHolder = new Particles();
         particlesContainer.addChild(particlesHolder);
         
         init(NUM_PARTICLES, CONTAINING_CUBE_SIZE);
         
         stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
         
         startRendering();
      }

      public function init(numParticles:int, containingCubeSize:int):void
      {
         var movingParticle:MovingParticle;
         
         if (particles) {
            particlesHolder.removeAllParticles();
         }
         
         particles = [];
         
         var i:int = numParticles;
         while (i--) {
            movingParticle = new MovingParticle(containingCubeSize);
            particlesHolder.addParticle(movingParticle);
            particles.push(movingParticle);
         }
         
      }

      override protected function onRenderTick(event:Event = null):void
      {
         // move each particle
         var movingParticle:MovingParticle;
         for each (movingParticle in particles) {
            movingParticle.position();
         }
         
         // twist the container based on mouse position
         particlesContainer.rotationY+=((stage.stageWidth/2)-mouseX)/200;
         particlesContainer.rotationX+=((stage.stageHeight/2)-mouseY)/200;
         
         // render
         super.onRenderTick(event);
      }
     
      private function clearBitmapEffects():void
      {
         if (bfx) {
            viewport.containerSprite.removeLayer(bfx);
            bfx = null;
         }
      }
     
      private function onKeyDown(event:KeyboardEvent):void
      {
         switch (String.fromCharCode(event.keyCode)) {
            case '1':
               renderMode = RENDER_MODE_CLEAN;
               break;
            case '2':
               renderMode = RENDER_MODE_TRAILS;
               break;
            case '3':
               renderMode = RENDER_MODE_FALLING;
               break;
            case 'c':
            case 'C':
               displayCube = !_displayCube;
               break;
         }
      }
   }
}

import org.papervision3d.core.geom.renderables.Particle;
import org.papervision3d.materials.special.ParticleMaterial;

internal class MovingParticle extends Particle
{
   
   public static const PARTICLE_SIZE:int = 10;
   public static const MAX_SPEED:int = 5;
   
   private var dX:Number;
   private var dY:Number;
   private var dZ:Number;
   private var halfSize:Number;

   public function MovingParticle(containingCubeSize:int)
   {
      var mat:ParticleMaterial = new ParticleMaterial(0xff0000, 1, ParticleMaterial.SHAPE_CIRCLE);
      super(mat, PARTICLE_SIZE);
     
      var size:int = containingCubeSize;
      halfSize = size / 2;
     
      x = (Math.random() * size) - halfSize;
      y = (Math.random() * size) - halfSize;
      z = (Math.random() * size) - halfSize;
     
      dX = Math.random() * MAX_SPEED;
      dY = Math.random() * MAX_SPEED;
      dZ = Math.random() * MAX_SPEED;
     
   }
   
   public function position():void
   {
      x += dX;
      if (x > halfSize || x < -halfSize) dX *= -1;
      y += dY;
      if (y > halfSize || y < -halfSize) dY *= -1;
      z += dZ;
      if (z > halfSize || z < -halfSize) dZ *= -1;
   }
}
// This line is just to stop the code formatter on my blog getting confused! >

No Comments, Comment or Ping

Reply to “Experiment with Papervision 3D particles and effects”