HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第7回 粒子同士が引き合う力を直線の濃淡で示す

この記事を読むのに必要な時間:およそ 9.5 分

前回は,ランダムに動く粒子それぞれの間にバネのような力を与えてアニメーションさせた。すると,粒子は互いに集まっては離れることを繰り返す,という面白い動きになった(再掲第6回図3)。ここまでのコードは,以下のjsdo.itに掲げてある第6回コード2)。

第6回 図3 オブジェクトがひと固まりに集まっては離れる(再掲)

第6回 図3 オブジェクトがひと固まりに集まっては離れる

第6回 図3 オブジェクトがひと固まりに集まっては離れる

オブジェクトが引合う力の範囲をかぎる

互いに離れれば離れるほど,引き戻す力は強まるというのがバネの性質だ。ただし,それを単純に当てはめたために,仲間から外れた粒子の激しい動きが止められず,しかも仲間はずれは次第に増えてしまうというのが前回の問題だった。

もともと,バネはいくらでも伸び縮みするものではない。むしろ,その動きを正しく保てる範囲はかぎられる。そこで,この粒子のアニメーションでも,互いの間に引合う力が及ぶ距離を決めてしまおう。2点の座標からその間の距離を導くには,三平方の定理を用いる図1)。2点の水平・垂直の各座標の差をそれぞれ2乗し,足し合わせてから平方根を求めればよい。

図1 2点の座標から三平方の定理で距離を求める

</span>図1 2点の座標から三平方の定理で距離を求める </span>図1 2点の座標から三平方の定理で距離を求める

そこで,以下のように力の働く最大距離を変数(limit)で定める。そして,オブジェクト間にバネの力を加える関数(spring())の中で,その距離より近いオブジェクト同士だけ引合うようにする。

水平・垂直それぞれの座標の差(distanceXとdistanceY)は,もともと求めていた。だから,それぞれを2乗して(squareXとsquareY)加え,平方根を計算すれば,距離(distance)が導ける。平方根はMath.sqrt()メソッドで求まる。なお,累乗にはMath.pow()メソッドが使える。だが,Mathクラスの演算は遅くなりがちだ。2乗くらいなら,同じ値を2度掛けたほうが速い。

function spring(object0, object1) {
  var distanceX = object1.x - object0.x;
  var distanceY = object1.y - object0.y;
  var squareX = distanceX * distanceX;
  var squareY = distanceY * distanceY;
  var distance = Math.sqrt(squareX + squareY);
  if (distance < limit) {
    var accelX = distanceX * ratio;
    var accelY = distanceY * ratio;
    object0.velocityX += accelX;
    object0.velocityY += accelY;
    object1.velocityX -= accelX;
    object1.velocityY -= accelY;
  }
}

さて,処理の速さを考えるなら,もう一歩踏み込みたい。オブジェクト間の距離を条件に,互いに引合うかどうかを決めた。けれども,距離(distance)の値そのものは計算に使っていない。単に,if条件であらかじめ定めた制限値(limit)と比べただけだ。そうであれば,平方根など求めることはない。比べる制限値の方を2乗しておく。これで,またひとつ計算が減り,Mathクラスのメソッドも使わずに済んだ。

// var limit = 100;
var limit = 100 * 100;

function spring(object0, object1) {
  var distanceX = object1.x - object0.x;
  var distanceY = object1.y - object0.y;
  var squareX = distanceX * distanceX;
  var squareY = distanceY * distanceY;
  // var distance = Math.sqrt(squareX + squareY);
  // if (distance < limit) {
  if (squareX + squareY < limit) {
    var accelX = distanceX * ratio;
    var accelY = distanceY * ratio;
    object0.velocityX += accelX;
    object0.velocityY += accelY;
    object1.velocityX -= accelX;
    object1.velocityY -= accelY;
  }
}

書き直したJavaScript全体は,以下のコード1のとおりだ。仲間から大きく外れると力が加えられなくなり,近づけば引き戻される。激しくやんちゃな動きをする粒子はめっきり減るはずだ図2)。

図2 オブジェクトの集まりから激しく飛び出す粒子は減る

</span>図2 オブジェクトの集まりから激しく飛び出す粒子は減る

コード1 オブジェクト同士が引合う力の及ぶ範囲をかぎった

var stage;
var stageWidth;
var stageHeight;
var balls = [];
var ballCount = 25;
var ratio = 1 / 2000;
var limit = 100 * 100;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  stageHeight = canvasElement.height;
  for (var i = 0; i < ballCount; i++) {
    var nX = Math.random() * stageWidth;
    var nY = Math.random() * stageHeight;
    var velocityX = (Math.random() - 0.5) * 5;
    var velocityY = (Math.random() - 0.5) * 5;
    var ball = createBall(3, "black", nX, nY, velocityX, velocityY);
    balls.push(ball);
    stage.addChild(ball);
  }
  createjs.Ticker.addEventListener("tick", move);
}
function createBall(radius, color, nX, nY, velocityX, velocityY) {
  var ball = new createjs.Shape();
  drawBall(ball.graphics, radius, color);
  ball.x = nX;
  ball.y = nY;
  ball.velocityX = velocityX;
  ball.velocityY = velocityY;
  return ball;
}
function drawBall(myGraphics, radius, color) {
  myGraphics.beginFill(color);
  myGraphics.drawCircle(0, 0, radius);
}
function move(eventObject) {
  for (var i = 0; i < ballCount; i++) {
    var ball = balls[i];
    var nX = ball.x;
    var nY = ball.y;
    nX += ball.velocityX;
    nY += ball.velocityY;
    ball.x = roll(nX, stageWidth);
    ball.y = roll(nY, stageHeight);
  }
  for (i = 0; i < ballCount - 1; i++) {
    var ball0 = balls[i];
    for (var j = i + 1; j < ballCount; j++) {
      var ball1 = balls[j];
      spring(ball0, ball1);
    }
  }
  stage.update();
}
function roll(value, length) {
  if (value > length) {
    value -= length;
  } else if (value < 0) {
    value += length;
  }
  return value;
}
function spring(object0, object1) {
  var distanceX = object1.x - object0.x;
  var distanceY = object1.y - object0.y;
  var squareX = distanceX * distanceX;
  var squareY = distanceY * distanceY;
  if (squareX + squareY < limit) {
    var accelX = distanceX * ratio;
    var accelY = distanceY * ratio;
    object0.velocityX += accelX;
    object0.velocityY += accelY;
    object1.velocityX -= accelX;
    object1.velocityY -= accelY;
  }
}

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入