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

第31回 位置座標の相互作用で弾力を表す

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

ふたつの点を棒でつなぐ

つぎに,落とす点をふたつにして棒でつなぐ。この棒もクラス(VerletStick)で定める。また,クラスのプロパティとメソッドを先に紹介しよう。プロパティは,棒でつなぐふたつの点のVerletPointオブジェクト(_point0と_point1)のほか,棒の長さ(_length)とその長さの伸び縮みをどれくらい許すかという固さ(elasticity)だ。

コンストラクタVerletStick()には,これら4つのプロパティ値を引数に渡す。update()メソッドは,ふたつの点のオブジェクトの位置を棒の長さに合わせて調整して更新する。そして,render()メソッドが,引数のGraphicsオブジェクトに,棒を直線で描く。

プロパティ
  • _point0, _point1:棒でつなぐふたつの点のVerletPointオブジェクト。
  • _length:棒の長さ。
  • elasticity:棒の伸縮を許す固さ。
メソッド
  • VerletStick(point0, point1, length, elasticity):コンストラクタ。前述4つのプロパティ値を引数に渡す。
  • update():ふたつの点の座標を棒がつなげるように調整して更新する。
  • render(graphics):引数に受取ったGraphicsオブジェクトに,棒を直線で描く。

これからつくるのは,ふたつの点を棒でつないで落とすアニメーションだ。それがどういう動きになるのか,先に見てしまった方がわかりやすいだろう。点がひとつのときと同じように,棒につながれたふたつの点が放物線を描いて落ち,Canvas下端を滑っていく。違うのは,右端で跳ね返ることだ。

棒のクラス(VerletStick)を定める前に,点のクラス(VerletPoint)にメソッドを加える。棒のオブジェクトが長さを計算するときに,ふたつの点の間の距離を求めなければならない。そのためのメソッドとして,以下の3つを新たに備えた。まず,subtract()メソッドは,引数に受取った点との差をVerletPointオブジェクト(ベクトル)で返す。つぎに,getLength()メソッドが,その点の原点からの長さを求める。すると,3つ目のgetDistance()メソッドは,前のふたつのメソッドにより,引数の点との距離を導いて返す。

VerletPoint.prototype.subtract = function(_point) {
  var subtractedPoint = new VerletPoint(this.x - _point.x, this.y - _point.y);
  return subtractedPoint;
};
VerletPoint.prototype.getLength = function() {
  var dx = this.x;
  var dy = this.y;
  var length = Math.sqrt(dx * dx + dy * dy);
  return length;
};
VerletPoint.prototype.getDistance = function(_point) {
  var distancePoint = this.subtract(_point);
  return distancePoint.getLength();
};

そして,棒のクラス(VerletStick)を定める。コンストラクタとrender()メソッドは,つぎのような簡単なJavaScriptコードだ。コンストラクタは,4つのプロパティに値を与えている。棒の長さ(_lengthプロパティ)は,先ほど点のクラス(VerletPoint)に加えたメソッドgetDistance()で求めた。render()メソッドは,引数のGraphicsオブジェクトに直線を引くだけだ。

function VerletStick(point0, point1) {
  this.elasticity = 0.5;
  this._point0 = point0;
  this._point1 = point1;
  this._length = point0.getDistance(point1);
}

VerletStick.prototype.render = function(graphics) {
  graphics.beginStroke("black")
  .setStrokeStyle(0.5)
  .moveTo(this._point0.x, this._point0.y)
  .lineTo(this._point1.x, this._point1.y);
};

update()メソッドは,ふたつの点が棒から外れないようにそれらの位置を調整する。以下の図2と併せて,計算を見てゆこう。ふたつの点(_point0と_point1プロパティ)の差のベクトル(delta)の長さ(distance)が,2点間の実際の距離になる。その値と棒に定められた長さ(_lengthプロパティ)との誤差(difference)を埋めなければならない。

そこで,三角比を用いた。2点の差のベクトル(delta)のx成分(delta.x)とy(delta.y)成分を,それぞれ2点間の距離(distance)で割ると,cos(= delta.x / distance)とsin(= delta.y / distance)が求まる。それらを誤差(difference)に乗じれば,誤差のxy成分が得られる(三角関数については,第11回「マウスポインタの動きに合わせてインスタンスをランダムに落とす」インスタンスの動きに水平方向の初速を加える参照⁠⁠。その値を半分(elasticityプロパティ)にしてふたつの点の座標に正負逆で加えれば,互いに同じだけ近づいたり遠ざかったりすることによって棒の長さが保たれる。

VerletStick.prototype.update = function() {
  var delta = this._point1.subtract(this._point0);
  var distance = delta.getLength();
  var difference = this._length - distance;
  var offsetX = (difference * delta.x / distance)  * this.elasticity;
  var offsetY = (difference * delta.y / distance)  * this.elasticity;
  this._point0.addCoordinates(-offsetX, -offsetY);
  this._point1.addCoordinates(offsetX, offsetY);
};

図2 2点間の距離と棒に定められた長さの誤差を埋める

図2 2点間の距離と棒に定められた長さの誤差を埋める

点(VerletPoint)と棒(VerletStick)のクラスは,それぞれ以下のコード3コード4のように定められた。これらのクラスからつくる棒でつながれたふたつの点を,この後放物線状に落とす。

コード3 点を定めるクラスVerletPointにメソッドを追加

function VerletPoint(x, y) {
  this.x = this._oldX = x;
  this.y = this._oldY = y;
}
VerletPoint.prototype.update = function() {
  var tempX = this.x;
  var tempY = this.y;
  var velocity = this.getVelocity();
  this.addCoordinates(velocity.x, velocity.y);
  this._oldX = tempX;
  this._oldY = tempY;
};
VerletPoint.prototype.constrain = function(rect) {
  var left = rect.x;
  var right = left + rect.width;
  var top = rect.y;
  var bottom = top + rect.height;
  if (this.x < left) {
    this.x = left;
  } else if (this.x > right) {
    this.x = right;
  }
  if (this.y < top) {
    this.y = top;
  } else if (this.y > bottom) {
    this.y = bottom;
  }
};
VerletPoint.prototype.getVelocity = function() {
  var velocity = new createjs.Point(this.x - this._oldX, this.y - this._oldY);
  return velocity;
};
VerletPoint.prototype.render = function(graphics) {
  graphics.beginFill("black")
  .drawCircle(this.x, this.y, 2.5)
  .endFill();
};
VerletPoint.prototype.addCoordinates = function(x, y) {
  this.x += x;
  this.y += y;
};
VerletPoint.prototype.subtract = function(_point) {
  var subtractedPoint = new VerletPoint(this.x - _point.x, this.y - _point.y);
  return subtractedPoint;
};
VerletPoint.prototype.getLength = function() {
  var dx = this.x;
  var dy = this.y;
  var length = Math.sqrt(dx * dx + dy * dy);
  return length;
};
VerletPoint.prototype.getDistance = function(_point) {
  var distancePoint = this.subtract(_point);
  return distancePoint.getLength();
};

コード4 棒を定めるクラスVerletStick

function VerletStick(point0, point1) {
  this.elasticity = 0.5;
  this._point0 = point0;
  this._point1 = point1;
  this._length = point0.getDistance(point1);
}
VerletStick.prototype.update = function() {
  var delta = this._point1.subtract(this._point0);
  var distance = delta.getLength();
  var difference = this._length - distance;
  var offsetX = (difference * delta.x / distance)  * this.elasticity;
  var offsetY = (difference * delta.y / distance)  * this.elasticity;
  this._point0.addCoordinates(-offsetX, -offsetY);
  this._point1.addCoordinates(offsetX, offsetY);
};
VerletStick.prototype.render = function(graphics) {
  graphics.beginStroke("black")
  .setStrokeStyle(0.5)
  .moveTo(this._point0.x, this._point0.y)
  .lineTo(this._point1.x, this._point1.y);
};

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書