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);
}
}
}
}