ActionScript 3.0で始めるオブジェクト指向スクリプティング

第51回 ベクトルの内積で面の向きを調べる

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

それでは,面の表と裏を塗り分ける仕上げにかかろう。第1に,⁠ライブラリ]に加えたビットマップ(クラスImage2)をインスタンスにして,裏面用の変数(backTexture)に納めておく。

第2に,正方形を回すDisplayObject.enterFrameイベントのリスナー関数(xRotate())から呼出す面を塗る関数(xDraw())に,前掲の関数(xIsFront())で調べた面の表裏を引数として渡す。

そして第3に,面を塗る関数(xDraw())は,引数(bFront)のブール値により表裏のテクスチャ(texture)を切替えて,回転する正方形にマッピングしている。これで,回る正方形の裏表によって,塗られるビットマップが切り替わる(前掲図1参照⁠⁠。

// フレームアクションに追加
var backTexture:BitmapData = new Image2();   // 追加

function xRotate(eventObject:Event):void {
  var nRotationY:Number = mySprite.mouseX * nDeceleration;
  var vertices2D:Vector.<Number> = new Vector.<Number>();
  xTransform(vertices2D, nRotationY);
  var bFront:Boolean = xIsFront(faceVector3D, worldMatrix3D);
  // trace(bFront);  // 確認用
  // xDraw(vertices2D);
  xDraw(vertices2D, bFront);
}

// function xDraw(vertices2D:Vector.<Number>):void {
function xDraw(vertices2D:Vector.<Number>, bFront:Boolean):void {
  var texture:BitmapData = bFront ? myTexture : backTexture;   // 追加
  myGraphics.clear();
  // myGraphics.beginBitmapFill(myTexture);
  myGraphics.beginBitmapFill(texture);
  myGraphics.drawTriangles(vertices2D, indices, uvtData);
  myGraphics.endFill();
}

修正し終えたフレームアクション全体を,スクリプト1として掲げる。

スクリプト1 水平に回す立方体の裏表を異なるテクスチャでマッピング

// フレームアクション
var nUnit:Number = 100 / 2;
var mySprite:Sprite = new Sprite();
var myTexture:BitmapData = new Image();
var backTexture:BitmapData = new Image2();
var viewVector3D:Vector3D = Vector3D.Z_AXIS;
var faceVector3D:Vector3D = new Vector3D(0, 0, -1);
var vertices:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var uvtData:Vector.<Number> = new Vector.<Number>();
var nDeceleration:Number = 0.3;
var myGraphics:Graphics = mySprite.graphics;
var myPerspective:PerspectiveProjection = transform.perspectiveProjection;
var worldMatrix3D:Matrix3D = new Matrix3D();
var viewMatrix3D:Matrix3D = myPerspective.toMatrix3D();
viewMatrix3D.prependTranslation(0, 0, myPerspective.focalLength);
mySprite.x = stage.stageWidth / 2;
mySprite.y = stage.stageHeight / 2;
vertices.push(-nUnit, -nUnit, 0);
vertices.push(nUnit, -nUnit, 0);
vertices.push(nUnit, nUnit, 0);
vertices.push(-nUnit, nUnit, 0);
indices.push(0, 1, 3);
indices.push(1, 2, 3);
uvtData.push(0, 0, 0);
uvtData.push(1, 0, 0);
uvtData.push(1, 1, 0);
uvtData.push(0, 1, 0);
addChild(mySprite);
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = mySprite.mouseX * nDeceleration;
  var vertices2D:Vector.<Number> = new Vector.<Number>();
  xTransform(vertices2D, nRotationY);
  var bFront:Boolean = xIsFront(faceVector3D, worldMatrix3D);
  xDraw(vertices2D, bFront);
}
function xTransform(vertices2D:Vector.<Number>, myRotation:Number):void {
  worldMatrix3D.prependRotation(myRotation, Vector3D.Y_AXIS);
  var myMatrix3D:Matrix3D = worldMatrix3D.clone();
  myMatrix3D.append(viewMatrix3D);
  Utils3D.projectVectors(myMatrix3D, vertices, vertices2D, uvtData);
}
function xDraw(vertices2D:Vector.<Number>, bFront:Boolean):void {
  var texture:BitmapData = bFront ? myTexture : backTexture;
  myGraphics.clear();
  myGraphics.beginBitmapFill(texture);
  myGraphics.drawTriangles(vertices2D, indices, uvtData);
  myGraphics.endFill();
}
function xIsFront(myVector3D:Vector3D, myMatrix3D:Matrix3D):Boolean {
  var directionVector3D:Vector3D = myMatrix3D.transformVector(myVector3D);
  var bFront:Boolean = (viewVector3D.dotProduct(directionVector3D) < 0);
  return bFront;
}

ベクトルの内積を数学的に理解する

ベクトルの内積について,数学的な説明を簡単に加えよう。ベクトル同士の掛け算は定義されていない。しかし,乗算つまり積と似た計算に「内積」がある。ベクトルAとBの内積は「A・B」で表される。結果は実数であり,ベクトルではないので注意してほしい。

ベクトルAとBの内積は,ふたつのベクトルのなす角をθとすると,つぎの式で求められる(三角関数のcosについては,⁠座標と三角関数と,時々,ベクトル」の2.三角関数で角度と距離から位置を計算するをお読みいただきたい⁠⁠。なお,|A|はベクトルAの大きさで,⁠絶対値」とも呼ばれる。

  • A・B = |A||B|cosθ

  • ベクトルAとBの始点を結んだとき,ベクトルAの終点からBに下ろした垂線との交点までの長さをAの射影といい,|A|cosθになる。この長さとベクトルBの大きさ|B|の積が内積A・Bだ図6⁠。なお,ふたつのベクトルのなす角θは,小さい方の角度つまり0以上π(180度)以下の範囲で定める。また,ベクトルAとBのどちらかひとつでも絶対値が0のとき,内積A・B = 0とされる。

    図6 内積はベクトルAのBへの射影と|B|との積

    図6 内積はベクトルAのBへの射影と|B|との積

    ベクトルAとBの絶対値は,どちらも0ではないとしよう。すると,ふたつのベクトルの絶対値の積は,つねに正になる。

    • A||B| > 0 (|A|≠0,|B|≠0とする)

    つまり,内積A・Bの値の正負は,cosθによって決まる。cosθは角θが0からπ(180度)までの範囲について,鋭角のとき正,直角であれば0,そして鈍角では負の値になる表3⁠。したがって,内積からふたつのベクトルの互いの向きがわかるのだ。

    表3 ベクトルのなす角と内積の正負

    内積の値ベクトルのなす角(θ) cosθ
    +(正)90度より小さい(鋭角)+(正)画像
    090度(直角)0
    負(-)90度より大きい(鈍角)負(-)

    内積は,ふたつのベクトルの座標(⁠⁠成分」ともいう)から導くこともできる。まず,ベクトルAとBを2次元平面で考えたとき,座標をそれぞれ(ax, ayおよび(bx, byとする。そのとき,内積A・Bはつぎの式で導かれる※1⁠。

    • A・B = axbx + ayby

    3次元空間のベクトルA(ax, ay, azとB(bx, by, bzについても,同じように求められる。つまり,各座標ごとに掛合わせてそれらの和をとる。Vector3D.dotProduct()メソッドは,内部的にはこの計算をしているはずだ。

    • A・B = axbx + ayby + azbz

    次回は,立方体を上下左右に自由に回す前回第50回のネタに戻る。ただし,回転軸をx軸とかy軸とせず,自由に定めてみたい。その場合には,ベクトルの外積を使う。

    ※1)
    ふたつのベクトルの絶対値およびcosによる内積の定義と,座標による計算の結果は等しくなる。しかし,それは自明ではなく,余弦定理により証明しなければならない。興味のある読者はVector3D.dotProduct()メソッド「数学解説」をお読みいただきたい。

    今回解説した次のサンプルファイルがダウンロードできます。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書