サイトトップ

Director Flash 書籍 業務内容 プロフィール
 

HTML5 / Flash ActionScript講座
Webクリエイターのための
CreateJSスタイルブック
gihyo.jp連載
「HTML5のCanvasでつくる
ダイナミックな表現
―CreateJSを使う」
■Twitter: @FumioNonaka / Facebook Page: CreateJS

Creators MeetUp

珍味ベクトル外積3種盛り

本稿は、2014年1月18日土曜日に催された第12回「Creators MeetUp」で務めた一席「新しいCreateJSのマウスイベント」のまとめとして書いた。サンプルコードはjsdo.itに掲げている。また、当日の映像はUSTREAM録画として公開された。

ベクトルの中でも「外積」は、考え方のわかりにくい計算といえる。だが、いきなり概念を捉えようとするのでなく、何に使えるのかを知れば、計算式そのものは簡単な四則演算だ。今回は2次元のベクトルに絞って、3つのインタラクティブなサンプルを例に、外積がどう使われているのかをご紹介する。


01 ベクトルの外積とは

3次元空間におけるベクトルAとBの外積はA×Bで表され、つぎのようなベクトルとして定められる。

表001■3次元ベクトルの外積A×Bで求められるベクトル
外積の要素 外積のベクトルとふたつのベクトルの関係
角度 ふたつのベクトルAとBを含む平面に垂直(図2左)。
方向 ベクトルAからBに向かう回転を考えたとき、その回し方で右ネジの進む方向(図2左)。
大きさ ベクトルAとBを隣り合う2辺とした平行四辺形の面積(図2右)。

図001■3次元ベクトルの外積
図001左 図001右

2次元のxy平面上のベクトルの外積は、つねにz軸と同じ角度の(平行な)ベクトル(xy成分値が0)になる。そこで、2次元ベクトルの外積は、ベクトルでなく、3次元ベクトルの場合のz成分(座標)値で定められた。2次元平面のベクトルA(ax, ay)とB(bx, by)の外積は、つぎの簡単な四則演算の式で表される。その値は、ベクトルAに対してBが右ネジの位置にあるとき正、左ネジの位置にあれば負となる(図002)。

【2次元ベクトルA(ax, ay)とB(bx, by)の外積】
A×B =axby - aybx
図002■2次元ベクトルの外積の正負は位置が右ネジか左ネジかで決まる
図002左 図002右

一般に、外積の計算に交換法則は成立たない。2次元ベクトルでは、計算の順序を逆にすると、外積の値の正負が変わる。

    【2次元ベクトルの外積からわかること】
  1. ふたつのベクトルの位置関係
    • 外積が正なら右ネジの位置
    • 外積が負なら左ネジの位置
  2. ふたつのベクトルでつくられる平行四辺形の面積
    • 半分にすればふたつのベクトルでつくられる三角形の面積

で、それが何の役に立つか? 3つのインタラクティブなサンプルをご紹介する。


02 面の向きが表か裏かを調べる

3次元空間のオブジェクトを画面に(透視)投影するときは、描く順序を整えなければならない(図003)。一般には、配列の並べ替え(Array.sort()メソッド)により、重ね順を定める(gihyo.jp連載第19回「3次元空間で弾むオブジェクトとz座標による重ね順の並べ替え」の「オブジェクトの重なりをz座標値の順に並べ替える」がその例)。しかし、配列エレメントの並べ替えは負荷が高い。

図003■3次元のオブジェクトは描く順序を整えなければならない
図003左 図003右

しかし、閉じた凸の多面体であれば、裏返った面は描かなければ済む。面の裏表は、2次元ベクトルの外積から調べられる。

    【閉じた凸の多面体】
  • 面の裏は決して見えない
  • 表向きの面は重ならない

面の3頂点からふたつのベクトルを定め、予め表のときの位置関係を(たとえば右ネジに)決めておく(図004)。

図004■3頂点から定めたふたつのベクトルの位置関係を予め決める
図004

右ネジを表としたなら、画面に投影したとき左ネジになった面は裏返ったことになる(AKB48に見る右ネジと左ネジ)。右ネジか左ネジかは、外積の正負で確かめられる(図005)。裏向きの面を描かなければ、表向きの面の重ね順は気にしなくてよい。

図005■外積の正負から面の向きを調べて裏返った面は描かない
図005左
外積が正の面は描画する
図005右
外積が負の面は描画しない

つぎのコード001は、面を描く関数(drawFaces())が、取出した面の頂点座標(facePoints)から裏表を確かめて、表向きの面だけを描く。面の裏表を調べる関数(isFront())は、3頂点座標から定めたふたつのベクトル(point0とpoint1)の外積(crossProduct2D())が0以上かどうかを返した。

コード001■面の頂点座標から定めたベクトルの外積により表向きの面のみを描く

function drawFaces(points, faces) {
  var numFaces = faces.length;

  for (var i = 0; i < numFaces; i++) {
    var face = faces[i];
    var facePoints = face.getFacePoints(points);
    if (isFront(facePoints)) {
      draw(facePoints, face.color);
    }
  }

}
function isFront(facePoints) {
  var origin = facePoints[0];
  var point0 = MathUtils.subtractVectors(origin, facePoints[1]);
  var point1 = MathUtils.subtractVectors(origin, facePoints[2]);
  return (0 <= crossProduct2D(point0, point1));
}
function crossProduct2D(point0, point1) {
  return point0.x * point1.y - point0.y * point1.x;
}

前掲コード001は、つぎのサンプル001から抜書きした。このサンプルはgihyo.jp連載「HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う」の次回第20回「 立方体のワイヤーフレームを水平に回す」(1月最後の週に公開予定)から3回にわたって解説する予定だ。

[追記: 2014/02/07] 外積で面の裏表を調べる手法は、第21回「水平に回す立方体の面を塗る」で解説した。

サンプル001■立方体をマウスポインタの位置に応じてx軸およびy軸で回す

03 ふたつの直線の交点を求める

つぎは、ふたつの直線の交点を求める。線分が直接交わらないときは、直線に延ばして交点を導く。

サンプル002■ふたつの直線の交点を求める

こうした場合、直線をつぎのような式で表し、連立2元1次方程式として解くことが多い(図006)。ただ、この式ではy軸に平行な直線(x = n)が示せないので、別に考えることになる。また、2直線が平行な場合も分けなければならない。

y = mx + n

図006■直線の1次方程式
図006

ふたつの線分(ACとBD)の交点は、一方(BD)を底辺として他方の端(AとC)を結んだ三角形の面積比(△ABD/△CBD)で分けられた点(AP/CP)と捉えることができる。

図007■線分はふたつの三角形の面積の比率で分けられる
図007

ふたつのベクトルがつくる三角形の面積は外積の半分の大きさに等しい。すると、線分(AC)に対する交点(P)までの比率(AP / AC)はつぎの式で導ける。この考え方によるなら、2直線が平行の場合(BD×AB + BD×BC = 0)のみ分ければ足りる。

AP / AC = (BD×AB / 2) / {(BD×AB / 2) + (BD×BC / 2)} = BD×AB / (BD×AB + BD×BC)

したがって、点AとCの座標をそれぞれ(ax, ay)および(cx, xy)とし、ベクトルの外積から求めた比率(AP / AC = BD×AB / (BD×AB + BD×BC))をkとすると、交点P(x, y)の座標はつぎのとおりだ。

x = ax + k×(cx - ax)
y = ay + k×(cy - ay)

前掲サンプル002から抜書きした、上記の計算式により交点を求める関数(getIntersection())が、つぎのコード002だ。外積を返す関数(crossProduct2D())は、前掲コード001と変わらない。サンプル002について、詳しくは「平面上の2直線の交点を外積で求めるサンプルコード」をお読みいただきたい。

コード002■平面上の2直線の交点を外積で求める

function crossProduct2D(point0, point1) {
  return point0.x * point1.y - point0.y * point1.x;
}
function getIntersection(from_0, to_0, from_1, to_1) {
  var intersection = new createjs.Point();
  // AC
  var vector_0 = new createjs.Point(to_0.x - from_0.x, to_0.y - from_0.y);
  // BD
  var vector_1 = new createjs.Point(to_1.x - from_1.x, to_1.y - from_1.y);
  // AB
  var vector_2 = new createjs.Point(from_1.x - from_0.x, from_1.y - from_0.y);
  // BC
  var vector_3 = new createjs.Point(to_0.x - from_1.x, to_0.y - from_1.y);
  // BD×AB
  var area_0 = crossProduct2D(vector_1, vector_2);
  // BD×BC
  var area_1 = crossProduct2D(vector_1, vector_3);
  // BD×AB + BD×BC
  var area_total = area_0 + area_1;

  // k = BD×AB / (BD×AB + BD×BC)
  var ratio = area_0 / area_total;
  // x = ax + k×(cx - ax)
  intersection.x = from_0.x + ratio * vector_0.x;
  // y = ay + k×(cy - ay)
  intersection.y = from_0.y + ratio * vector_0.y;

}


04 回す力のかかりやすさを決める

最後は、ドラッグする勢いでオブジェクトを回す。「intentionallies」のインタラクティブな表現だ。回りやすさやその速さには、てこの原理が働く(「つりあいとてこ」参照)。支点から遠い力点に、てこに対して垂直に力を加えると能率がよい(図008)。

図008■てこと力を加える向き
図008上
図008下

オブジェクトを回す力のかかり方は、てこの長さと垂直方向にかけた力で決まる。この掛け算は、ふたつのベクトルを2辺とする平行四辺形の面積に等しい。つまり、てこ(r)とかけた力(F)の外積(r×F)に比例するということだ(図009)。物理では「力のモーメント」として説明される。

図009■回す力のかかり方はてこと力の外積に比例する
図009

以下のサンプル003の中で、回す速さを決める関数(drag())は、つぎのコード003のように定めた。オブジェクト(card)の中心(重心)からドラッグするマウスポインタの座標(mouseXとmouseY)までをてこのベクトル(radius)とする。そして、マウスポインタの動き(force)との外積(moment)を求めて、回す角速度(angularVelocity)に加えた。なお、角速度には調整係数(ratio)が乗じてある。

コード003■重心からドラッグする座標とマウスポインタの動きの外積で回す角速度を定める

var lastMouse;
var ratio = 0.01;
var angularVelocity = 0;

function drag(eventObject) {
  var mouseX = eventObject.stageX;
  var mouseY = eventObject.stageY;
  var radius = new createjs.Point(lastMouse.x - card.x, lastMouse.y - card.y);
  var force = new createjs.Point(mouseX - lastMouse.x, mouseY - lastMouse.y);
  var moment = crossProduct2D(radius, force);
  angularVelocity += moment * ratio;
  lastMouse = new createjs.Point(mouseX, mouseY);

}

function crossProduct2D(point0, point1) {
  return point0.x * point1.y - point0.y * point1.x;
}

サンプル003のスクリプトは、『WebクリエイターのためのCreateJSスタイルブック』03-01「EaselJS でインスタンスをドラッグする勢いで加速して回す」で解説した。

サンプル003■インスタンスをドラッグする勢いで加速して回す

作成者: 野中文雄
更新日: 2014年2月7日 gihyo.jp連載第21回「水平に回す立方体の面を塗る」へのリンクを追記。
作成日: 2014年1月18日


Copyright © 2001-2014 Fumio Nonaka.  All rights reserved.