Canvasで花火を打ち上げる(まだ書きかけ)

http://jsdo.it/babu_baboo/Oj11
iPadで連打すると意外に綺麗な花火が打ち上がる
大曲の花火大会は見たことが無いけれど、
こりゃ〜洋野町の花火大会より好いんじゃないかい?!

<!DOCTYPE html>
<meta charset="UTF-8">
<title>N個の点を持つ球体。それはこの夏の花火につながる?!まだ書きかけ</title>
<style>
body {
  color: white;
  background: black;
}
</style>

<body>
<canvas width="1000" height="700"></canvas>


<script>

(function () {

  //球体の n個の点を生成。rは半径
  function Sphere (n, r) {
    var A = Math.asin;
    var C = Math.cos;
    var S = Math.sin;
    var G = (Math.sqrt (5) + 1) / 4;
    var B = n + 0.5;
    var D = Math.PI * 2 * (G - 1);
    var R = [ ];

    if ('undefined' === typeof r)
      r = 1;

    for (var a, b, c, d = -n; d < n; d += 2)
      R.push([ (b = C (a = A (d / B)) * r) * C (c = D * d), b * S (c) , S (a) * r ]);

    return R; // [[x0, y0, z0], .. , [xn, yn, zn]]
  }


  //花火を生成
  function Hanabi (point, vector, color, kayaku, gain) {
    this.point = point;
    this.vector = vector;
    this.color = color;
    this.kayaku = kayaku;
    this.gain = gain;
  }


  //花火の燃焼中
  function diffusion () {
    var i, p, v;
    var k = this.kayaku + Math.random() * 2;
    var vector = this.vector;

    for (i = 0; p = this.point[i]; i += 1) {
      v = vector[i];
      this.point[i][0] += v[0] * k;
      this.point[i][1] += v[1] * k + 2;
      this.point[i][2] += v[2] * k;
    }
    this.kayaku *= this.gain;

    return (this.kayaku > .1);
  }

  function initPoint (_) {
    return [this.x-0, this.y-0, this.z-0];
  }

  function create (n, color, kayaku, gain, offset3D) {
    offset3D.y = Math.random () * 200 + 500;
    var vector = Sphere (n);
    var point = [ ];
    var i, I = vector.length;

    for (i = 0; i < I; i += 1) {
      point.push([offset3D.x, offset3D.y, offset3D.z, color]);
    }
    var obj = new Hanabi (point, vector, color , kayaku || 100, gain || .99);

    return obj;
  }

  Hanabi.prototype.diffusion = diffusion;

  Hanabi.create = create;

  this.HANABI = Hanabi;
}) ();


//___________

//花火師職人を雇う
(function (hanabi) {

  var HColor = [//そのうち花火の種類のオブジェクトを作れ
    'RGB(255,50,50)', 'RGB(255,100,0)', 'RGB(255,200,50)', 'RGB(255,255,100)','RGB(255,0,50)',
    'RGB(255,0,150)', 'RGB(200,0,255)', 'RGB(0,255,0)', 'RGB(100,255,30)','RGB(0,200,255)'
  ];
  var int = Math.floor;
  var rnd = Math.random;


  //花火師職人を雇用する
  function Hanabisi (offset3D, hosiMin, hosiMax) {
    this.offset3D = offset3D; //立ち位置
    this.hosiMin = hosiMin; //扱える花火の星の最低数
    this.hosiMax = hosiMax; //最大数
  }

  function bomb (hanabi) {//花火の爆発
    return hanabi.diffusion ();
  }

  function getHosi (hanabi) {//花火の星の座標を返す
    return hanabi.point;
  }

  function admiring () {//花火師が夜空を見上げる
    Hanabisi.LIST = Hanabisi.LIST.filter (bomb);//燃焼中のものだけ集める
    return Array.prototype.concat.apply ([ ], Hanabisi.LIST.map (getHosi));//一次元配列化
  }

  function fire (event) {//花火の発射(click handler)
    var e = event.target;

    var n = int (rnd () * (this.hosiMax - this.hosiMin)) + this.hosiMin; //星の数
    var c = HColor[int (rnd () * HColor.length)];
    var k = rnd () * 20 + 10;
    var g = .9;

    var tmp = {//試しに立ち位置をランダムにする
      x: int (rnd () * 600) - 300,
      y: 0,
      z: int (rnd () * -200)
    };
    var h = hanabi.create (n, c, k, g, tmp /*this.offset3D*/);
    Hanabisi.LIST.push (h);
  }

  function create (tatiiti, hosiMin, hosiMax) {
    if ('undefined' === typeof tatiiti)
      tatiiti = {x: 0, y: 0, z: 0};
    return new Hanabisi (tatiiti, hosiMin || 30, hosiMax || 300);
  }


  Hanabisi.prototype.list = [ ];
  Hanabisi.prototype.admiring = admiring;
  Hanabisi.prototype.handleEvent = fire;

  Hanabisi.create = create;
  Hanabisi.LIST = [ ];

  this.HANABISI = Hanabisi;

}) (HANABI);


//___________


//Canvas をコントロールするためのオブジェクト
(function () {

  function Ctx (canvas, ctx, width, height, offset, distance) {
    this.canvas = canvas;
    this.ctx = ctx;
    this.width = width;
    this.height = height;
    this.offset = offset;
    this.distance = distance;
  }


  //夜空を好きな色で染める
  function clsScreen (rgba) {
    this.ctx.fillStyle = rgba ? rgba: 'rgb(0,0,0)';
    this.ctx.fillRect (0,0, this.width, this.height);
  }


  //夜空に光を放つ
  function pset (aryPoint) {
    var i, z, a;
    var x = [ ], y = [ ], c = [ ];
    var d = this.distance, cx = this.offset.x, cy = this.offset.y;
    var ctx = this.ctx;

    for (i = 0; a = aryPoint[i]; i += 1) {
      z = - (d - a[2]) / d;
      ctx.fillStyle = a[3];
      ctx.fillRect (cx + a[0] * z, cy - a[1] * z, 2, 2);//大きさも考えろよ
    }
  }


  //Canvasコントローラを作る
  function create (canvas, offset, distance) {
    var ctx = canvas.getContext ('2d');
    var width = canvas.width;
    var height = canvas.height;
    var offset_default = { x: width / 2, y: height / 2};
    var distance_default = 10000;
    var p;

    if ('undefined' === typeof offset)
      offset = offset_default;
    else
      for (p in offset_default) //標準設定で上書き
        if (offset_default.hasOwnProperty (p))
          if (! offset.hasOwnProperty (p))
            offset[p] = offset_default[p];

    return new Ctx (canvas, ctx, width, height, offset, distance || distance_default);
  }

  //___________


  Ctx.prototype.clsScreen = clsScreen;
  Ctx.prototype.pset = pset;

  Ctx.create = create;

  this.CTX = Ctx;
})();


//___________

//アニメーションの環境か?
if ('undefined' === typeof window.requestAnimationFrame) {
  window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame||
    window.mozRequestAnimationFrame   ||
    window.oRequestAnimationFrame     ||
    window.msRequestAnimationFrame    ||
    function (callback, that) {
      var tmpFunc = function () {
        var timestamp = +(new Date); //(new Date).getTime ();
        callback (timestamp); //callback.call (that, timestamp);
      };
      window.setTimeout (tmpFunc, Math.floor (1000/60));
    };
}

var canvas = document.querySelector ('canvas');
var hanabisi = HANABISI.create ();
var yozora = CTX.create (canvas, {y: -300});

canvas.addEventListener ('click', hanabisi, false);
canvas.addEventListener ('touchstart', hanabisi, false);

//___________

//アニメーションの実行
function demo () {
  yozora.clsScreen ('rgba(0,0,0,.1)');
  yozora.pset (hanabisi.admiring ());
  requestAnimationFrame (demo);
}

demo ();

</script>