While working on a little Valentine’s Day ecard for the most wonderful wife in the world, I suddenly had an idea…
Snow effects have been done in Flash since at least Flash 5 (maybe even Flash 4 if I think back hard enough), but I have yet to see a Flash snow storm in “true 3d”. Well, at least not until now. Seemed the new Particle API in Papervision3d was just the way to go for such a thing. Probably one of the oldest and nicest looking examples of snow in Flash, is the one presented on Kirupa.com. I really like the motion of that version, so borrowed a bit of the math for my own updated version. Also, I like the idea of using depth of field with papervision, so whilst borrowing, I boosted a bit of mrdoob’s “brute force” approach for depth of field. I didn’t go crazy with 200 textures though. I figured 4 were enough. No blur, a light blur, a medium blur, and a heavy blur. To be honest, you can’t really even notice the DOF anyway, but it’s there, and that’s all that matters. Add a little panorama and suddenly it’s snowing everywhere you look. Now, if I could just figure out how to keep the flakes from “sticking” to the camera, I’d have it made in the shade. I’m not sure if this is a clipping problem or what. If anyone knows why that happens, post a comment here and let me know.
The code for some snow:
/**
* Snow in the Third Dimension (with some math from Kirupa.com)
* @author Devon O.
* @version 0.1
*/
package {
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Sprite;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import org.papervision3d.cameras.FreeCamera3D;
import org.papervision3d.materials.special.BitmapParticleMaterial;
import org.papervision3d.view.BasicView;
import flash.events.Event;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.core.geom.Particles;
import org.papervision3d.core.geom.renderables.Particle;
public class Snow3d extends BasicView {
private var snowHolder:DisplayObject3D;
private var flakes:Array;
private const NUM_FLAKES:int = 300;
private var cam:FreeCamera3D;
private var noBlurFlake:BitmapData;
private var ltBlurFlake:BitmapData;
private var medBlurFlake:BitmapData;
private var heavyBlurFlake:BitmapData;
public function Snow3d() {
super(stage.stageWidth, stage.stageHeight, true, false, FreeCamera3D.TYPE);
init();
}
private function init():void {
cam = cameraAsFreeCamera3D;
cam.zoom = 1;
cam.focus = 400;
cam.z = 10;
createBitmaps();
snowHolder = new DisplayObject3D("snow_holder");
flakes = new Array();
for(var i:int = 0; i < NUM_FLAKES; i++) {
var mat:BitmapParticleMaterial = new BitmapParticleMaterial(noBlurFlake);
mat.smooth = true;
var flakeHolder:Particles = new Particles("flake" + i);
var flake:Particle = new Particle(mat, 10, 0, 0, 0);
flakeHolder.addParticle(flake);
flakeHolder.x = randRange(-2000, 2000);
flakeHolder.y = randRange(-2000, 2000);
flakeHolder.z = randRange( -2000, 2000);
flakeHolder.extra = { radians: 0, I:randRange(2, 8), K: (-Math.PI + Math.random() * Math.PI), noblur: new BitmapParticleMaterial(noBlurFlake), blur1: new BitmapParticleMaterial(ltBlurFlake), blur2: new BitmapParticleMaterial(medBlurFlake), blur3: new BitmapParticleMaterial(heavyBlurFlake) };
snowHolder.addChild(flakeHolder);
flakes.push(flakeHolder);
}
scene.addChild(snowHolder);
singleRender();
addEventListener(Event.ENTER_FRAME, enterFrame);
}
private function createBitmaps():void {
var lightBlur:BlurFilter = new BlurFilter(4, 4, 1);
var medBlur:BlurFilter = new BlurFilter (8, 8, 1);
var heavyBlur:BlurFilter = new BlurFilter(16, 16, 1);
var type:String = GradientType.RADIAL;
var colors:Array = [0xFFFFFF, 0xFFFFFF, 0xFFFFFF];
var alphas:Array = [1, .1, 0];
var ratios:Array = [0, 200, 255];
var mat:Matrix = new Matrix();
mat.createGradientBox(10, 10);
var baseFlake:Sprite = new Sprite();
baseFlake.graphics.beginGradientFill(type, colors, alphas, ratios, mat);
baseFlake.graphics.drawCircle(5, 5, 5);
baseFlake.graphics.endFill();
noBlurFlake = new BitmapData(10, 10, true, 0x00000000);
noBlurFlake.draw(baseFlake);
ltBlurFlake = new BitmapData(10, 10, true, 0x00000000);
ltBlurFlake.draw(baseFlake);
ltBlurFlake.applyFilter(ltBlurFlake, baseFlake.getBounds(baseFlake), new Point(), lightBlur);
medBlurFlake = new BitmapData(10, 10, true, 0x00000000);
medBlurFlake.draw(baseFlake);
medBlurFlake.applyFilter(medBlurFlake, baseFlake.getBounds(baseFlake), new Point(), medBlur);
heavyBlurFlake = new BitmapData(10, 10, true, 0x00000000);
heavyBlurFlake.draw(baseFlake);
heavyBlurFlake.applyFilter(heavyBlurFlake, baseFlake.getBounds(baseFlake), new Point(), heavyBlur);
}
function enterFrame(e:Event):void {
var pan:Number = cam.rotationY - 210 * stage.mouseX / (stage.stageWidth/2);
pan = Math.max( -100, Math.min( pan, 100 ) );
cam.rotationY -= pan / 12;
var len:int = flakes.length;
for(var i:int = 0; i < len; i++) {
var p:Particles = flakes[i];
if (Math.abs(p.sceneZ) <= 100) {
p.particles[0].material = p.extra["noblur"]
}
if (Math.abs(p.sceneZ) >= 101 && Math.abs(p.z) <= 200) {
p.particles[0].material = p.extra["blur1"]
}
if (Math.abs(p.sceneZ) >= 201 && Math.abs(p.z) <= 300) {
p.particles[0].material = p.extra["blur2"]
}
if (Math.abs(p.sceneZ) >= 301) {
p.particles[0].material = p.extra["blur3"]
}
p.extra["radians"] += (p.extra["K"] / 180) * Math.PI;
p.x -= 3 * (Math.cos(p.extra["radians"]));
p.z -= 3 * (Math.sin(p.extra["radians"]));
p.y -= p.extra["I"];
if (p.y < -2000) {
p.y = 2000;
p.x = randRange(-2000, 2000);
p.z = randRange( -2000, 2000);
p.extra["I"] = randRange(2, 8);
}
}
singleRender();
}
private function randRange(min:Number, max:Number):Number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
}
And an example (with panorama):
[kml_flashembed movie=”../wp-content/uploads/2008/02/snow3d.swf” height=”400″ width=”500″ fversion=”9″ /]