こちらです。遊んでみて下さい。HTML はここのとほぼ同じです(読み込む JavaScript のURL を変えるだけ)。円は完全弾性衝突をします。
canvas_sample3.js
var wd, cvs, ctx, r, num; var Circle = function () {}; //円クラス var Vector = function (x, y) { //ベクトル・クラス this.x = x; this.y = y; }; var cir = []; //あとで円クラスのインスタンスに使う wd = 500; r = 20; num = 0; onload = function() { cvs = document.getElementById("Canvas"); ctx = cvs.getContext("2d"); cvs.height = wd; cvs.width = wd; cvs.onclick = setBall; setInterval(f, 10); }; function f() { if (num == 0) {return;} ctx.clearRect(0, 0, wd, wd); var fl = geneArray(num); //フラグ行列の生成 for (var i = 0; i < num; i++) { ctx.beginPath(); ctx.fillStyle = cir[i].cl; ctx.arc(cir[i].o.x, cir[i].o.y, r, 0, Math.PI * 2); ctx.fill(); cir[i].o = add(cir[i].o, cir[i].v); if (cir[i].o.x >= wd - r || cir[i].o.x <= r) {cir[i].v.x = -cir[i].v.x;} else if (cir[i].o.y >= wd - r || cir[i].o.y <= r) { cir[i].v.y = -cir[i].v.y; } for (var j = 0; j < num; j++) { if (!fl[i][j]) {continue;} var c1 = cir[i], c2 = cir[j]; if (dist(c1, c2) <= r * 2) { // 衝突時の速度ベクトルの変換 var x1 = new Vector(c1.o.x, c1.o.y), x2 = new Vector(c2.o.x, c2.o.y); var v1 = new Vector(c1.v.x, c1.v.y), v2 = new Vector(c2.v.x, c2.v.y); var ra = sub(x1, x2); var tmp = mul(innPro(ra, sub(v1, v2)) / absl2(ra), ra); c1.v = sub(c1.v, tmp); c2.v = add(c2.v, tmp); fl[i][j] = false; } } } } function geneArray(n) { var a = new Array(n); for (var i = 0; i < n; i++) { a[i] = new Array(n); for (var j = 0; j < n; j++) { a[i][j] = true; if (i <= j) {a[i][j] = false;} } } return a; } //ベクトルの演算(上から定数倍、内積、和、差、絶対値の二乗) function mul(a, v) { return new Vector(a * v.x, a * v.y); } function innPro(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } function add(v1, v2) { return new Vector(v1.x + v2.x, v1.y + v2.y); } function sub(v1, v2) { return new Vector(v1.x - v2.x, v1.y - v2.y); } function absl2(v) { return Math.pow(v.x, 2) + Math.pow(v.y, 2); } //クリック時の円の生成 function setBall(e) { var x1, y1; x1 = e.pageX - cvs.offsetLeft; y1 = e.pageY - cvs.offsetTop; if (x1 <= r || x1 >= wd - r || y1 <= r || y1 >= wd - r) {return;} num++; //円クラスのインスタンスの生成。cir.o は円の中心の点を指すベクトル、cir.v は速度ベクトル、cir.cl は円の色 cir[num - 1] = new Circle(); cir[num - 1].o = new Vector(x1, y1); cir[num - 1].v = new Vector((Math.random() * 2 + 2) * (Math.random() - 0.5) * 2, (Math.random() * 2 + 2) * (Math.random() - 0.5) * 2); cir[num - 1].cl = "rgb(" + rnd() + "," + rnd() + "," + rnd() + ")"; if (near(num - 1)) {num--; pop(cir); return;} } function near(i) { //円どうしが近すぎて生成できないなら true if (i == 0) {return false;} for (var j = 0; j < i; j++) { if (dist(cir[i], cir[j]) <= r * 2) {return true;} } return false; } function dist(c1, c2) { return Math.sqrt(absl2(sub(c1.o, c2.o))); } function rnd() { return String(Math.floor(Math.random()*256)); } function cvsClear() { ctx.clearRect(0, 0, wd, wd); num = 0; }
JavaScript で初めてオブジェクト指向プログラミングをやってみたけれど、意外とできるものだな。というか、OOP を使わなければこんなに簡単にできなかったと思う。OOP はむずかしいのではなくて、プログラミングをわかりやすく、容易にするための手法なのだということを実感する。例えば上のプログラムなら、cir[i].v は i番目の円の速度、cir[i].o.x なら i番目の円の中心の x座標、dist(c1, c2) なら円c1(の中心)と円c2(の中心)の距離と、すぐわかるわけだ。
追記
トップレベルの関数をインスタンス・メッソドに書きなおしました。(11/4)
canvas_sample3a.js
var wd, cvs, ctx, r, num; //円クラス var Circle = function () { this.dist = function (c) { return Math.sqrt(this.o.sub(c.o).absl2()) }; }; //ベクトル・クラス var Vector = function (x, y) { this.x = x; this.y = y; //ベクトルの演算(上から定数倍、内積、和、差、絶対値の二乗) this.mul = function (a) { return new Vector(a * this.x, a * this.y); }; this.innPro = function (v) { return this.x * v.x + this.y * v.y; }; this.add = function (v) { return new Vector(this.x + v.x, this.y + v.y); }; this.sub = function (v) { return new Vector(this.x - v.x, this.y - v.y); }; this.absl2 = function () { return Math.pow(this.x, 2) + Math.pow(this.y, 2); }; }; var cir = []; //あとで円クラスのインスタンスに使う wd = 500; r = 20; num = 0; onload = function() { cvs = document.getElementById("Canvas"); ctx = cvs.getContext("2d"); cvs.height = wd; cvs.width = wd; cvs.onclick = setBall; setInterval(f, 10); } function f() { if (num == 0) {return;} ctx.clearRect(0, 0, wd, wd); var fl = geneArray(num); //フラグ行列の生成 for (var i = 0; i < num; i++) { ctx.beginPath(); ctx.fillStyle = cir[i].cl; ctx.arc(cir[i].o.x, cir[i].o.y, r, 0, Math.PI * 2); ctx.fill(); cir[i].o = cir[i].o.add(cir[i].v); if (cir[i].o.x >= wd - r || cir[i].o.x <= r) {cir[i].v.x = -cir[i].v.x;} else if (cir[i].o.y >= wd - r || cir[i].o.y <= r) { cir[i].v.y = -cir[i].v.y; } for (var j = 0; j < num; j++) { if (!fl[i][j]) {continue;} var c1 = cir[i], c2 = cir[j]; if (c1.dist(c2) <= r * 2) { // 衝突時の速度ベクトルの変換 var v1 = c1.v, v2 = c2.v; var ra = c1.o.sub(c2.o); var tmp = ra.mul(ra.innPro(v1.sub(v2)) / ra.absl2()); c1.v = v1.sub(tmp); c2.v = v2.add(tmp); fl[i][j] = false; } } } } function geneArray(n) { var a = new Array(n); for (var i = 0; i < n; i++) { a[i] = new Array(n); for (var j = 0; j < n; j++) { a[i][j] = true; if (i <= j) {a[i][j] = false;} } } return a; } //クリック時の円の生成 function setBall(e) { var x1, y1; x1 = e.pageX - cvs.offsetLeft; y1 = e.pageY - cvs.offsetTop; if (x1 <= r || x1 >= wd - r || y1 <= r || y1 >= wd - r) {return;} num++; //円クラスのインスタンスの生成。cir.o は円の中心の点を指すベクトル、cir.v は速度ベクトル、cir.cl は円の色 cir[num - 1] = new Circle(); cir[num - 1].o = new Vector(x1, y1); cir[num - 1].v = new Vector((Math.random() * 2 + 2) * (Math.random() - 0.5) * 2, (Math.random() * 2 + 2) * (Math.random() - 0.5) * 2); cir[num - 1].cl = "rgb(" + rnd() + "," + rnd() + "," + rnd() + ")"; if (near(num - 1)) {num--; pop(cir); return;} } function near(i) { //円どうしが近すぎて生成できないなら true if (i == 0) {return false;} for (var j = 0; j < i; j++) { if (cir[i].dist(cir[j]) <= r * 2) {return true;} } return false; } function rnd() { return String(Math.floor(Math.random()*256)); } function cvsClear() { ctx.clearRect(0, 0, wd, wd); num = 0; }