The Old Snow Trick – In the Third Dimension

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″ /]