In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: PS2 Firework (Particles and Motion blur)

/**
 * Copyright ryo_2004 ( http://wonderfl.net/user/ryo_2004 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/wqw2
 */

// forked from k0rin's PS2 Firework (Particles and Motion blur)
package {
	import flash.display.*;
	import flash.events.*;
	import flash.filters.*;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	import net.hires.debug.Stats;
	
	[SWF(width="465", height="465", frameRate="90")]
	public class Firework extends Sprite
	{
		// 火花
		private var position:Vector.<Number> = new Vector.<Number>();
		private var velocity:Vector.<Number> = new Vector.<Number>();
		private var diameters:Vector.<Number> = new Vector.<Number>();
		private var sparkNumber:Number = 0;
		
		private var projectedPosition:Vector.<Number> = new Vector.<Number>();
		private var uvts:Vector.<Number> = new Vector.<Number>();
		
		private var sparkBitmapDatas:Vector.<BitmapData> = new Vector.<BitmapData>();
		private var sparkBitmapDataRectangles:Vector.<Rectangle> = new Vector.<Rectangle>();
		private const SPARK_COLOR:uint = 0x664020;
		
		private const GRAVITY:Number = 0.02;
		private const AIR_RESISTANCE:Number = 0.999;
		
		// 1フレームに放出する火花の数
		private const EMIT_SPARK_NUMBER:int = 3;
		
		// 照り返し
		private var reflectionBitmapData:BitmapData;
		private const REFLECTION_COLOR:uint = 0xFF1800;
		private const REFLECTION_RADIUS:Number = 16;
		
		// フレームバッファ
		private var displayBuffer:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false);
		private var splitBuffer:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false);
		private const NUMBER_OF_SPLIT_FRAME:int = 10;
		
		private var perspective:PerspectiveProjection = new PerspectiveProjection();
		private var projectionMatrix:Matrix3D = new Matrix3D();
		
		private var viewRotationY:Number = 0;
		private var viewRotationX:Number = 25;
		
		private const CENTER_X:Number = stage.stageWidth / 2;
		private const CENTER_Y:Number = stage.stageHeight * 0.65;
		
		private var stats:TextField = new TextField();
		
		public function Firework()
		{
			addChild(new Bitmap(displayBuffer));

			//stats.x = 5;
			//stats.y = 5;
			//stats.autoSize = TextFieldAutoSize.LEFT;
			//stats.textColor = 0xFFFFFF;
			//addChild(stats);
			addChild(new Stats());
			
			var shape:Shape = new Shape();
			var g:Graphics = shape.graphics;
			
			// 火花のプリレンダリング
			var bitmapData:BitmapData = new BitmapData(1, 1, true, SPARK_COLOR);
			sparkBitmapDatas.push(bitmapData);
			sparkBitmapDataRectangles.push(bitmapData.rect);
			
			for (var diameter:int = 1; diameter <= 32; diameter++) {
				bitmapData = new BitmapData(diameter, diameter, true, 0);
				
				g.clear();
				g.beginFill(SPARK_COLOR);
				var radius:Number = diameter / 2;
				g.drawCircle(radius, radius, radius);
				g.endFill();
				bitmapData.draw(shape);
				
				sparkBitmapDatas.push(bitmapData);
				sparkBitmapDataRectangles.push(bitmapData.rect);
				// bitmapData.rectはアクセサで重いのでキャッシュしておく。これにより30%ほど高速化
			}
			
			// 照り返しのプリレンダリング
			reflectionBitmapData = new BitmapData(REFLECTION_RADIUS * 2, REFLECTION_RADIUS * 2, true, 0);
			g.clear();
			var matrix:Matrix = new Matrix();
			matrix.createGradientBox(REFLECTION_RADIUS * 2, REFLECTION_RADIUS * 2, 0, 0, 0);
			g.beginGradientFill(GradientType.RADIAL, [ REFLECTION_COLOR, REFLECTION_COLOR ], [ 1, 0 ], [ 0, 255 ], matrix);
			g.drawCircle(REFLECTION_RADIUS, REFLECTION_RADIUS, REFLECTION_RADIUS);
			g.endFill();
			reflectionBitmapData.draw(shape);
			
			createFireworkModel();
			
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
			//stage.addEventListener(MouseEvent.CLICK, function (e:Event):void {
			//	emitSpark();
			//});
		}
		
		private function enterFrameHandler(e:Event):void
		{
			displayBuffer.fillRect(displayBuffer.rect, 0);
			
			setupProjectionMatrix();
			
			//var startTime:uint = getTimer();
			
			renderFloor();
			
			//var floorRenderingTime:uint = getTimer() - startTime;
			
			//startTime = getTimer();
			for (var i:int = 0; i < NUMBER_OF_SPLIT_FRAME; i++) {
				const FACTOR:Number = 0.06;
				viewRotationY += (getTimer() * 0.01 + (-(CENTER_X - mouseX) * 0.4) - viewRotationY) * FACTOR;
				viewRotationX += ((27 + (CENTER_Y - mouseY) * 0.15) - viewRotationX) * FACTOR;
				
				setupProjectionMatrix();
				
				for (var j:int = 0; j < EMIT_SPARK_NUMBER; j++) {
					emitSpark();
				}
				updateSparks();
				renderSparks();
			}
			//var sparksRenderingTime:uint = getTimer() - startTime;
			
			//stats.text = 
				//"Number of Sparks : " + sparkNumber + "\n" + 
				//"Floor Rendering Time : " + floorRenderingTime + " ms\n" + 
				//"Sparks Rendering Time : " + sparksRenderingTime + " ms \n";
		}
		
		private function setupProjectionMatrix():void
		{
			perspective.fieldOfView = 60;
			
			projectionMatrix.identity();
			projectionMatrix.appendRotation(viewRotationY, Vector3D.Y_AXIS);
			projectionMatrix.appendRotation(viewRotationX, Vector3D.X_AXIS);
			projectionMatrix.appendTranslation(0, 0, perspective.focalLength);
			projectionMatrix.append(perspective.toMatrix3D());
			
			correctMatrix3DMultiplyBug(projectionMatrix);
		}
		
		private function correctMatrix3DMultiplyBug(matrix:Matrix3D):void
		{
			// see http://bugs.adobe.com/jira/browse/FP-670
			var m1:Matrix3D = new Matrix3D(Vector.<Number>([ 0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 1, 0 ]));
			var m2:Matrix3D = new Matrix3D(Vector.<Number>([ 0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 1,  0, 0, 0, 0 ]));
			m1.append(m2);
			if (m1.rawData[15] == 20) {
				// バグ持ち!
				var rawData:Vector.<Number> = matrix.rawData;
				rawData[15] *= 0.05;
				matrix.rawData = rawData;
			}
		}
		
		private function createSpark(x:Number, y:Number, z:Number, vx:Number, vy:Number, vz:Number, diameter:Number):void
		{
			
			
			position.push(x, y, z);
			velocity.push(vx, vy, vz);
			diameters.push(diameter);
			sparkNumber++;
		}
		
		private function emitSpark():void
		{
			var azimuth:Number = Math.random() * 2 * Math.PI;
			var vy:Number = -(Math.random() * 0.06 + 0.94);
			var vx:Number = Math.sqrt(1 - vy * vy) * Math.cos(azimuth);
			var vz:Number = Math.sqrt(1 - vy * vy) * Math.sin(azimuth);
			
			var speed:Number = Math.random() * 2.3 + 0.7;
			vx *= speed;
			vy *= speed;
			vz *= speed;
			
			var diameter:Number = 3 + Math.random() * 3;
			createSpark(0, -FIREWORK_HEIGHT, 0, vx, vy, vz, diameter);
		}
		
		private function updateSparks():void
		{
			var xIndex:int, yIndex:int, zIndex:int;
			
			var vy:Number, vx:Number, vz:Number;
			var py:Number, px:Number, pz:Number;

			const DIAMETER_MIN:Number = 2;
			
			for (var i:int = 0; i < sparkNumber; ) 
			{
				xIndex = i * 3;
				yIndex = xIndex + 1;
				zIndex = xIndex + 2;				
				
				//velocity[yIndex] += GRAVITY;
				
				//velocity[xIndex] *= AIR_RESISTANCE;
				//velocity[yIndex] *= AIR_RESISTANCE;
				//velocity[zIndex] *= AIR_RESISTANCE;
				
				vy = velocity[yIndex] + GRAVITY;
				vy *= AIR_RESISTANCE;
				vx = velocity[xIndex] * AIR_RESISTANCE;
				vz = velocity[zIndex] * AIR_RESISTANCE;
				
				py = position[yIndex];
				
				if ( py + vy > 0 ) {
					vy *= - 0.2;
					
					py += vy;				
					px = position[xIndex] + vx;
					pz = position[zIndex] + vz;
					
					// 小さい火花に分割
					
					//var dia:Number = diameters[i];
					if (diameters[i] > DIAMETER_MIN) {
						var diameterLimit:Number = diameters[i];
						
						while (1) {
							var diameter:Number = Math.random() * (4 - DIAMETER_MIN) + DIAMETER_MIN;
							diameterLimit -= diameter;
							if (diameterLimit < 0) {
								break;
							}
							var azimuth:Number = Math.random() * 2 * Math.PI;
							var r:Number = Math.random() * 2;
							
							createSpark(px, py, pz, 
								(vx + Math.cos(azimuth) * r) * 0.5, 
								vy, 
								(vz + Math.sin(azimuth) * r) * 0.5, 
								diameter);
						}
					}
					
					// spliceは重い!
//					position.splice(xIndex, 3);
//					velocity.splice(xIndex, 3); 
//					diameters.splice(i, 1);


					var nxIndex:int = sparkNumber*3 - 3;
					var nyIndex:int = sparkNumber*3 - 2;
					var nzIndex:int = sparkNumber*3 - 1;
					
					position[xIndex] = position[nxIndex];
					position[yIndex] = position[nyIndex];
					position[zIndex] = position[nzIndex];

					velocity[xIndex] = velocity[nxIndex];
					velocity[yIndex] = velocity[nyIndex];
					velocity[zIndex] = velocity[nzIndex];
					
					diameters[i] = diameters[sparkNumber - 1];
				
					//position.pop();
					//position.pop();
					//position.pop();
					
					position.length -= 3;
					
					//velocity.pop();
					//velocity.pop();
					//velocity.pop();
					
					velocity.length -= 3;
					
					//diameters.pop();
					
					diameters.length --;
					
					sparkNumber--;
				
					continue;
				}
				
				position[xIndex] += vx;
				position[yIndex] += vy;
				position[zIndex] += vz;
				
				velocity[xIndex] = vx;
				velocity[yIndex] = vy;
				velocity[zIndex] = vz;
				
				i++;
			}
		}
		
		private var renderP:Point = new Point();
		private function renderSparks():void
		{
			splitBuffer.fillRect(splitBuffer.rect, 0x000000);
			
			Utils3D.projectVectors(projectionMatrix, position, projectedPosition, uvts);
			
			// ループ外に追い出したら30%以上高速化
			//var p:Point = new Point();
			var focalLength:Number = perspective.focalLength;
			var diameter:int;
			for (var i:int = 0; i < sparkNumber; i++) {
				// パーティクルの大きさ = 1/z * focalLength * pixel
				diameter = uvts[i * 3 + 2] * focalLength * diameters[i] + 0.5;
				// (i * 2) => (i << 1)で10%ほど高速化
				renderP.x = CENTER_X + projectedPosition[(i << 1)    ] - diameter / 2;
				renderP.y = CENTER_Y + projectedPosition[(i << 1) + 1] - diameter / 2;
				splitBuffer.copyPixels(sparkBitmapDatas[diameter], sparkBitmapDataRectangles[diameter],renderP);
			}
			
			displayBuffer.draw(splitBuffer, null, null, BlendMode.ADD);
		}
		
		private const FLOOR_SIZE:Number = 300;
		private const FLOOR_HALFSIZE:Number = FLOOR_SIZE / 2;
		private const FLOOR_VERTICES:Vector.<Number> = Vector.<Number>([
			 FLOOR_HALFSIZE, 0,  FLOOR_HALFSIZE, 
			-FLOOR_HALFSIZE, 0,  FLOOR_HALFSIZE, 
			-FLOOR_HALFSIZE, 0, -FLOOR_HALFSIZE, 
			 FLOOR_HALFSIZE, 0, -FLOOR_HALFSIZE, 
			 
			 FLOOR_HALFSIZE, 0,               0, 
			-FLOOR_HALFSIZE, 0,               0, 
			              0, 0,  FLOOR_HALFSIZE, 
			              0, 0, -FLOOR_HALFSIZE, 
		]);
		private const FLOOR_VERTEX_NUMBER:int = FLOOR_VERTICES.length / 3;
		
		private var floorProjectedVertices:Vector.<Number> = new Vector.<Number>();
		private var floorIndices:Vector.<int> = Vector.<int>([
			0, 1, 2,  2, 3, 0
		]);
		private var floorUvts:Vector.<Number> = Vector.<Number>([
			1, 0, 0,  0, 0, 0,  0, 1, 0,  1, 1, 0,  
			0, 0, 0,  0, 0, 0,  0, 0, 0,  0, 0, 0
		]);
		
		private const TEXTURE_SIZE:int = 150;
		private var floorTexture:BitmapData = new BitmapData(TEXTURE_SIZE, TEXTURE_SIZE, false);
		private var flRect:Rectangle = floorTexture.rect;
		private const WIREFRAME_COLOR:uint = 0x555566;
		
		private var sprite:Sprite = new Sprite();
		private var colorTransform:ColorTransform = new ColorTransform();
		private var matrix:Matrix = new Matrix();
		private const THRESHOLD_Y:Number = 50;
		private const ASC:Number = TEXTURE_SIZE / FLOOR_SIZE;
		
		
		private function renderFloor():void
		{
			// 照り返しを描画する閾値
		
			floorTexture.lock();
			displayBuffer.lock();
			
			floorTexture.fillRect(flRect, 0x100C00);
			var i:int;
			
			for (i = 0; i < sparkNumber; i++) {
				var yIndex:int = i * 3 + 1;
				var py:Number = position[yIndex];
				if (py > -THRESHOLD_Y) {
					var xIndex:int = i * 3;
					var zIndex:int = i * 3 + 2;
					
					//matrix.identity();
					var scale:Number = (0.2 - (py / THRESHOLD_Y) * 0.8) * diameters[i];
					//matrix.scale(scale, scale);
					matrix.a = matrix.d = scale;
					//matrix.translate(FLOOR_HALFSIZE + position[xIndex] - REFLECTION_RADIUS * scale, FLOOR_HALFSIZE - position[zIndex] - REFLECTION_RADIUS * scale);
					matrix.tx = FLOOR_HALFSIZE + position[xIndex] - REFLECTION_RADIUS * scale;
					matrix.ty = FLOOR_HALFSIZE - position[zIndex] - REFLECTION_RADIUS * scale;
					//matrix.tx = position[xIndex] - REFLECTION_RADIUS;
					//matrix.ty = position[zIndex] - REFLECTION_RADIUS;
					matrix.scale(ASC, ASC);
					//matrix.a = ASC;
					//matrix.d = ASC;
					
					colorTransform.alphaMultiplier = 1 + py *0.02;
					floorTexture.draw(reflectionBitmapData, matrix, colorTransform, BlendMode.ADD);
				}
			}
						
			Utils3D.projectVectors(projectionMatrix, FLOOR_VERTICES, floorProjectedVertices, floorUvts);
			for (i = 0; i < FLOOR_VERTEX_NUMBER; i++) {
				floorProjectedVertices[(i << 1)    ] += CENTER_X;
				floorProjectedVertices[(i << 1) + 1] += CENTER_Y;
			}
			
			Utils3D.projectVectors(projectionMatrix, fireworkVertices, fireworkProjectedVertices, fireworkUvts);
			for (i = 0; i < fireworkProjectedVertices.length / 2; i++) {
				fireworkProjectedVertices[(i << 1)    ] += CENTER_X;
				fireworkProjectedVertices[(i << 1) + 1] += CENTER_Y;
			}
			
			var g:Graphics = sprite.graphics;
			g.clear();
			g.beginBitmapFill(floorTexture);
			g.drawTriangles(floorProjectedVertices, floorIndices, floorUvts);
			g.endFill();
			displayBuffer.draw(sprite);
			
			g.clear();
			g.lineStyle(1, WIREFRAME_COLOR);
			g.moveTo(floorProjectedVertices[0], floorProjectedVertices[1]);
			g.lineTo(floorProjectedVertices[2], floorProjectedVertices[3]);
			g.lineTo(floorProjectedVertices[4], floorProjectedVertices[5]);
			g.lineTo(floorProjectedVertices[6], floorProjectedVertices[7]);
			g.lineTo(floorProjectedVertices[0], floorProjectedVertices[1]);
			
			g.moveTo(floorProjectedVertices[8], floorProjectedVertices[9]);
			g.lineTo(floorProjectedVertices[10], floorProjectedVertices[11]);
			g.moveTo(floorProjectedVertices[12], floorProjectedVertices[13]);
			g.lineTo(floorProjectedVertices[14], floorProjectedVertices[15]);
			displayBuffer.draw(sprite, null, null, BlendMode.ADD);
			
			g.clear();
			g.beginFill(FIREWORK_COLOR);
			g.drawTriangles(fireworkProjectedVertices, fireworkIndices);
			g.endFill();
			
			displayBuffer.draw(sprite);
			
			floorTexture.unlock();
			displayBuffer.unlock();
		}
		
		private const FIREWORK_RADIUS:Number = 8;
		private const FIREWORK_HEIGHT:Number = 40;
		private const FIREWORK_COLOR:uint = 0xAACCFF;
		
		private var fireworkVertices:Vector.<Number> = new Vector.<Number>();
		private var fireworkIndices:Vector.<int> = new Vector.<int>();
		private var fireworkProjectedVertices:Vector.<Number> = new Vector.<Number>();
		private var fireworkUvts:Vector.<Number> = new Vector.<Number>();
		
		private function createFireworkModel():void
		{
			const SEGMENT:int = 8;
			for (var i:int = 0; i < SEGMENT; i++) {
				var x:Number = Math.cos(2 * Math.PI * i / SEGMENT) * FIREWORK_RADIUS;
				var z:Number = Math.sin(2 * Math.PI * i / SEGMENT) * FIREWORK_RADIUS;
				fireworkVertices.push(x, -FIREWORK_HEIGHT, z);
				fireworkVertices.push(x, 0, z);
				
				var i0:int = (i * 2    ) % (SEGMENT * 2);
				var i1:int = (i * 2 + 1) % (SEGMENT * 2);
				var i2:int = (i * 2 + 2) % (SEGMENT * 2);
				var i3:int = (i * 2 + 3) % (SEGMENT * 2);
				fireworkIndices.push(i2, i0, i1);
				fireworkIndices.push(i1, i3, i2);
			}
		}
	}
}