package { import flash.display.Sprite; import flash.display.Bitmap; import flash.display.BitmapData; import flash.utils.getTimer; import flash.events.Event; import flash.geom.Rectangle; public class SnowField extends Sprite { private var _fieldBMD:BitmapData; private var _fieldBM:Bitmap; private var _locked:Boolean = false; private var _updateQueue:Object = {callInitializeBitmap: false, callInitializeFlakeField: false, callUpdateBitmap: false}; private var _screenHeight:uint; private var _screenWidth:uint; private var _screenRadius:uint; private var _numberOfFlakes:uint; private var _halfFieldOfView:Number; private var _visibility:Number; private var _flakesList:Array = []; private var _snowSpeed:Number; private var _carSpeed:Number; private var _animationState:Boolean = false; private var _timeLast:Number; public function SnowField():void { lock(); screenWidth = 500; screenHeight = 400; numberOfFlakes = 10000; fieldOfView = 110; visibility = 100; carSpeed = 20; snowSpeed = 1; unlock(); } public function refresh():void { initializeFlakeField() updateBitmap(); } public function lock():void { _locked = true; } public function unlock():void { _locked = false; if (_updateQueue.callInitializeBitmap) initializeBitmap(); if (_updateQueue.callInitializeFlakeField) initializeFlakeField(); if (_updateQueue.callUpdateBitmap) updateBitmap(); _updateQueue.callInitializeBitmap = false; _updateQueue.callInitializeFlakeField = false; _updateQueue.callUpdateBitmap = false; } public function get locked():Boolean { return _locked; } public function set locked(arg:Boolean):void { arg ? lock() : unlock(); } public function get screenHeight():uint { return _screenHeight; } public function set screenHeight(arg:uint):void { _screenHeight = arg; initializeBitmap(); updateBitmap(); } public function get screenWidth():uint { return _screenWidth; } public function set screenWidth(arg:uint):void { _screenWidth = arg; initializeBitmap(); updateBitmap(); } public function get numberOfFlakes():uint { return _numberOfFlakes; } public function set numberOfFlakes(arg:uint):void { _numberOfFlakes = arg; } public function get fieldOfView():Number { return 2*_halfFieldOfView*(180/Math.PI); } public function set fieldOfView(arg:Number):void { if (isNaN(arg) || !isFinite(arg) || arg<=0 || arg>=140) return; _halfFieldOfView = arg*(Math.PI/180)/2; initializeFlakeField(); updateBitmap(); } public function get visibility():Number { return _visibility; } public function set visibility(arg:Number):void { if (isNaN(arg) || !isFinite(arg) || arg<=0) return; _visibility = arg; initializeFlakeField(); updateBitmap(); } public function get snowSpeed():Number { return _snowSpeed*1000; } public function set snowSpeed(arg:Number):void { if (isNaN(arg) || !isFinite(arg) || arg<0) return; _snowSpeed = arg/1000; } public function get carSpeed():Number { return _carSpeed*1000; } public function set carSpeed(arg:Number):void { if (isNaN(arg) || !isFinite(arg) || arg<0) return; _carSpeed = arg/1000; } public function get animationState():Boolean { return _animationState; } public function set animationState(arg:Boolean):void { if (arg && !_animationState) { // start animating _timeLast = getTimer(); addEventListener(Event.ENTER_FRAME, animationEnterFrameFunc); } else if (!arg && _animationState) { // stop animating removeEventListener(Event.ENTER_FRAME, animationEnterFrameFunc); } _animationState = arg; } private function animationEnterFrameFunc(...ignored):void { var timeNow:Number = getTimer(); advanceFlakes(timeNow-_timeLast); updateBitmap(); _timeLast = timeNow; } private function advanceFlakes(deltaT:Number):void { var a:Number = _visibility*Math.sin(_halfFieldOfView); var b:Number = 2*a; var c:Number = deltaT*_carSpeed; var d:Number = deltaT*_snowSpeed; var e:Number = _visibility + c; var boxVolume:Number = b*b*_visibility; var targetDensity:Number = _numberOfFlakes/boxVolume; var topMarginVolume:Number = e*d*b; var frontMarginVolume:Number = b*b*c; var totalMarginVolume:Number = topMarginVolume + frontMarginVolume; var topMarginFraction:Number = topMarginVolume/totalMarginVolume; var numberOfNewFlakes:Number = targetDensity*totalMarginVolume; var flakesList_NEW:Array = []; var recycleList:Array = []; var dy:Number = -c; var dz:Number = -d; var i:int; var j:int = 0; var f:Flake; var len:int = _flakesList.length; for (i=0; i0 && f.z>-a) flakesList_NEW[j++] = f; } // now inject new particles for (i=0; i=0 && sx<_screenWidth && sy>=0 && sy<_screenHeight) { g = (0xf0 - (0xf0*Math.sqrt(f.x*f.x + f.y*f.y + f.z*f.z)/_visibility)); if (g<0) continue; g += (0xff & _fieldBMD.getPixel(sx, sy)); if (g>0xff) g = 0xff; _fieldBMD.setPixel(sx, sy, uint((g<<16) | (g<<8) | g)); } } _fieldBMD.unlock(); } private function initializeFlakeField():void { if (_locked) { _updateQueue.callInitializeFlakeField = true; return; } // make _flakesList the same length as _numberOfFlakes var delta:int = _numberOfFlakes - _flakesList.length; if (delta<0) { // remove flakes _flakesList.splice(_flakesList.length + delta); } else if (delta>0) { // add flakes for (var i:int = 0; i