Redpoll's 60
 Home / 3Dプログラミング入門 / 第4章 $§$4-19
第4章 3D空間におけるオブジェクトの運動

$§$4-19 一体化したオブジェクトの運動 10


図1
本節では図1に示される戦車の運動について扱う。
以下の図2から図6はこの戦車を構成するオブジェクトの初期状態であり、図7はこれらのオブジェクトの階層構造である。図2の Body は一番上の親オブジェクトであり、初期状態では x軸プラス方向を向いている。また、Bodyは初期状態では原点からやや上空に離れた位置に置かれている。
図3の Turret はBodyの子オブジェクトであり、Bodyの上部にアタッチされる (図1)。Turretの向きも初期状態では x軸プラス方向である。
図4の Barrel はTurretの子オブジェクトであり、BarrelはTurretの先端(x軸プラス側の先端)にアタッチされる。Barrelの向きも初期状態においては x軸プラス方向であり、初期状態では原点からやや離れた位置に置かれている (原点に近い方が’根元’であり、原点から離れている方が先端である)。

  • 図2 Body 初期状態
  • 図3 Turret 初期状態
  • 図4 Barrel初期状態

  • 図5 TrackBitL、TrackBitR 初期状態 (それぞれ58個ある)
  • 図6 WheelL、WheelR 初期状態 (それぞれ7個ある)
  • 図7 オブジェクトの階層構造

図8 Track (58個のTrackBitによって構成される)
さらに、Bodyの子オブジェクトとして TrackBitL、TrackBitR がそれぞれ58個ずつあり、WheelL、WheelR がそれぞれ7個ずつある。
戦車における Track とは右図に示されるものであるが、今回のプログラムではこのTrackは上図5のTrackBit 58個によって構成される。Trackの内部に7個のWheelが配置されるが、TrackBitとWheelの間には親子関係はない (両方ともBodyの子オブジェクトである)。
そして、この58個のTrackBitと7個のWheelは2セットあり、Bodyの右側、左側に1セットずつアタッチする。
TrackBit及び Wheelは初期状態ではXY平面に関して対称である。図5のTrackBit 及び図6のWheelは z軸マイナス側から見たときのものであるが、これらを反対側(z軸プラス側)から見ても全く同じ形をしている。
TrackやWheelなどの戦車の下部構造については後に回すことにし、まずは Body、Turret、Barrel の戦車上部の運動から始める。

(上でも触れたが、Trackは58個のTrackBitによって構成されている。以降の文章中において「Track」とあれば、それは58個のTrackBitのことを意味している。また、プログラム中で使用するTrackBit及びWheelはそれぞれ名前の最後に「R」「L」が付いているが、これは右側、左側を意味している。文章中では解説の便宜のため「R」「L」はほとんどの場合において省略する。)


# Code1
次のプログラムはBody、Turret、Barrelを一体化させるものである。
[Beta1]  (実行結果 図9)
Matrix4x4 localBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);
Matrix4x4 localTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);
Matrix4x4 localBody   = Matrix4x4.identity;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody   = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

このプログラムは単に Turret、Barrel をそれぞれのアタッチポジションまで平行移動させるだけに過ぎない (1行目の c_attachPos_Barrel、2行目の c_attachPos_Turret はそれぞれのアタッチポジションを表すVector3型の定数)。

次のプログラムは以下のキー操作によって、一体化した3つのオブジェクトをBodyの向いている方向へ移動させるものである。
  H  :  Bodyの向きを y軸周りに $0.25^\circ$ ずつ回転させる。
  L  :  Bodyの向きを y軸周りに $-0.25^\circ$ ずつ回転させる。
  S  :  Bodyを運動(あるいは停止)させるためのスイッチ。

[Code1]  (実行結果 図10)
if (!i_INITIALIZED)
{
    i_MOVE = false;
    i_degBody = 0.0f;    

    i_INITIALIZED = true;
}


if (Input.GetKeyDown(KeyCode.S))
{
    i_MOVE = !i_MOVE;
}

if (Input.GetKey(KeyCode.H))
{
    i_degBody -= 0.25f;
}
else if (Input.GetKey(KeyCode.L))
{
    i_degBody += 0.25f;
}

Matrix4x4 localBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);
Matrix4x4 localTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);

Matrix4x4 rotBody = TH3DMath.GetRotation4x4(i_degBody, Vector3.up);
Vector3 forwardDir = rotBody * Vector3.right;
float speed = i_MOVE ? 0.01f : 0f;
Vector3 pos = Body.GetWorldPosition() + speed * forwardDir;
Matrix4x4 traBody = TH3DMath.GetTranslation4x4(pos);
Matrix4x4 localBody = traBody * rotBody;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody   = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

初期化ブロック内の i_MOVE はBodyの運動 / 停止ためのbool型インスタンス変数であり、Sキーが押されるたびに値が変わる (10行目のifブロック)。i_degBody はBodyの向きが初期状態からどれだけ回転しているかを表すインスタンス変数である (float型)。Hキー あるいは Lキーが押されるとこの変数の値は $0.25^\circ$ ずつ増減する (15~22行目 ; どちらかのキーが押されていればBodyは毎フレーム $0.25^\circ$ ずつ向きを変える)。
上記のBeta1と異なりこのプログラムではBodyが運動を行う。27行目の rotBody はBodyを y軸周りに角度 i_degBody だけ回転させる回転行列である。28行目の forwardDir はその時点でのBodyの向きを表す変数である。Bodyのその時点での向きは初期状態の向きである x軸プラス方向から y軸周りに角度 i_degBody だけ回転させた方向である。
29行目の speed はBodyの毎フレームの移動量であり、i_MOVEtrueであれば毎フレーム $0.01$ ずつ移動し、falseの際はBodyを停止させるために $0$ がセットされる。
30行目の pos にはそのフレームにおけるBodyの移動先の位置が計算される。Bodyが運動中であれば、Bodyの現在位置(GetWorldPosition()で取得される)からその時点でのBodyの向いている方向に $0.01$ だけ進ませる。
実行結果(図10)に見られるように、Bodyの運動に伴って子オブジェクトであるTurret、及び Turretの子オブジェクトであるBarrelが一体となって、Bodyの向いている方向へ進んでいくだけである。

図9 Beta1 実行結果
図10 Code1 実行結果


# Code2
では続いて、Turret及びBarrelの運動を追加する。
次のプログラムは以下のキー操作によって、アタッチポジションにおいてTurretを回転させるものである。
  D  :  Turretを y軸周りに $-1.0^\circ$ ずつ回転させる。
  F  :  Turretを y軸周りに $1.0^\circ$ ずつ回転させる。

[Beta2A]  (実行結果 図11)
if (!i_INITIALIZED)
{
    i_degTurret = 0.0f;    

    i_INITIALIZED = true;
}

if (Input.GetKey(KeyCode.D))
{
    i_degTurret -= 1.0f;
}
else if (Input.GetKey(KeyCode.F))
{
    i_degTurret += 1.0f;
}

Matrix4x4 localBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);

Matrix4x4 traTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);    
Matrix4x4 rotTurret = TH3DMath.GetRotation4x4(i_degTurret, Vector3.up);
Matrix4x4 localTurret = traTurret * rotTurret;

Matrix4x4 localBody = Matrix4x4.identity;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody   = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

図11 Beta2A 実行結果
プログラム中の i_degTurret はTurretの回転角度を表すfloat型インスタンス変数であり、Dキー あるいは Fキーが押されていればTurretは毎フレーム y軸周りに $1.0^\circ$ ずつ回転する。このプログラムではBodyは初期状態のままであり、BarrelはTurretにアタッチされるだけである。
実行結果(図11)に見られるように、Body上部においてTurretとBarrelが一体となって回転を行うだけである。


次に、Barrelの回転を追加する。BarrelはTurretの先端において垂直方向の回転を行う。
プログラム及びキー操作は以下のとおり。
  J  :  Barrelを z軸周りに $-0.2^\circ$ ずつ回転させる。
  K  :  Barrelを z軸周りに $0.2^\circ$ ずつ回転させる。

[Beta2B]  (実行結果 図13)
if (!i_INITIALIZED)
{
    i_degTurret = 0.0f;    
    i_degBarrel = 0.0f;    

    i_INITIALIZED = true;
}

if (Input.GetKey(KeyCode.D))
{
    i_degTurret -= 1.0f;
}
else if (Input.GetKey(KeyCode.F))
{
    i_degTurret += 1.0f;
}

if (Input.GetKey(KeyCode.J))
{
    i_degBarrel -= 0.2f;
    if(i_degBarrel < 0.0f) { i_degBarrel = 0.0f; }
}
else if (Input.GetKey(KeyCode.K))
{
    i_degBarrel += 0.2f;
    if(i_degBarrel > 20.0f) { i_degBarrel = 20.0f; }
}

Matrix4x4 traBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);    
Matrix4x4 rotBarrel = TH3DMath.GetRotation4x4(i_degBarrel, Vector3.forward);
Matrix4x4 localBarrel = traBarrel * rotBarrel;

Matrix4x4 traTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);    
Matrix4x4 rotTurret = TH3DMath.GetRotation4x4(i_degTurret, Vector3.up);
Matrix4x4 localTurret = traTurret * rotTurret;

Matrix4x4 localBody = Matrix4x4.identity;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody   = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

図12 Barrelの仰角は 0° から 20° まで変化する。
i_degBarrel はBarrelの回転角度を表すfloat型インスタンス変数である。Barrelの回転角度とはいわゆる戦車の仰角のことで、ここで使用しているBarrelの場合 その仰角は $0^\circ$ から $20^\circ$ の間である。
Barrelは初期状態において x軸プラス方向を向いているので、z軸周りに回転角度を増加させるとBarrelは上方へ向きを変えていく。具体的には 18~27行目の部分で、Barrelの向きは Jキーが押されている間は下方へ、Kキーが押されている間は上方へ向きを変えていく (その変化は毎フレーム $0.2^\circ$ ずつ)。
29行目以降については、先程のBeta2Aと比べて Barrelのローカル行列に回転処理が追加されているに過ぎない (30~31行目)。
実行結果(図13)に見られるように、Jキー あるいは Kキーを押すことで、Turretの先端においてBarrelの仰角が変化していく。

図13 Beta2B 実行結果
図14 Code2 実行結果


では、今までの運動を全て統合する。すなわち、Bodyは自身の向いている方向へ進んでいき、TurretはBody上部において回転を行う。BarrelはTurretの先端において仰角を変化させる。
キー操作は以下のとおり。
  H  :  Bodyの向きを y軸周りに $0.25^\circ$ ずつ回転させる。
  L  :  Bodyの向きを y軸周りに $-0.25^\circ$ ずつ回転させる。
  D  :  Turretを y軸周りに $-1.0^\circ$ ずつ回転させる。
  F  :  Turretを y軸周りに $1.0^\circ$ ずつ回転させる。
  J  :  Barrelを z軸周りに $-0.2^\circ$ ずつ回転させる。
  K  :  Barrelを z軸周りに $0.2^\circ$ ずつ回転させる。
  S  :  Bodyを運動(あるいは停止)させるためのスイッチ。

[Code2]  (実行結果 図14)
if (!i_INITIALIZED)
{
    i_MOVE = false;    
    i_degBody = 0.0f;    
    i_degTurret = 0.0f;    
    i_degBarrel = 0.0f;    

    i_INITIALIZED = true;
}

if (Input.GetKeyDown(KeyCode.S))
{
    i_MOVE = !i_MOVE;
}

if (Input.GetKey(KeyCode.H))
{
    i_degBody -= 0.25f;
}
else if (Input.GetKey(KeyCode.L))
{
    i_degBody += 0.25f;
}

if (Input.GetKey(KeyCode.D))
{
    i_degTurret -= 1.0f;
}
else if (Input.GetKey(KeyCode.F))
{
    i_degTurret += 1.0f;
}

if (Input.GetKey(KeyCode.J))
{
    i_degBarrel -= 0.2f;
    if (i_degBarrel < 0.0f) { i_degBarrel = 0.0f; }
}
else if (Input.GetKey(KeyCode.K))
{
    i_degBarrel += 0.2f;
    if (i_degBarrel > 20.0f) { i_degBarrel = 20.0f; }
}

Matrix4x4 traBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);    
Matrix4x4 rotBarrel = TH3DMath.GetRotation4x4(i_degBarrel, Vector3.forward);
Matrix4x4 localBarrel = traBarrel * rotBarrel;

Matrix4x4 traTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);    
Matrix4x4 rotTurret = TH3DMath.GetRotation4x4(i_degTurret, Vector3.up);
Matrix4x4 localTurret = traTurret * rotTurret;

Matrix4x4 rotBody = TH3DMath.GetRotation4x4(i_degBody, Vector3.up);
Vector3 forwardDir = rotBody * Vector3.right;
float speed = i_MOVE ? 0.01f : 0f;
Vector3 pos = Body.GetWorldPosition() + speed * forwardDir;
Matrix4x4 traBody = TH3DMath.GetTranslation4x4(pos);
Matrix4x4 localBody = traBody * rotBody;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody   = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

このプログラムはCode1とBeta2Bをまとめたものに過ぎない。


# Code3
ここからは戦車の下部構造(Track、Wheel)の運動を実装する。
まずは、Trackの回転から始める。

次のプログラムは58個のTrackBitを指定の経路に沿って移動させるものである。
[Beta3A]  (実行結果 図15)
i_mileage += 0.01f;

Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
for (int i = 0; i < TrackBitR.Length; i++)
{
    TrackBitR[i].SetMatrix(mtrcs[i]);
}

図15 Beta3A 実行結果 (Trackの中心は原点)
図16 Trackは2つの線分と2つの半円によって構成される

Trackの回転は、58個のTrackBitが指定の経路を毎フレーム 同じ速さで移動していくことで実現される。プログラム中の i_mileage はBodyの移動距離を表すためのfloat型インスタンス変数である。上記のプログラムではBodyは毎フレーム $0.01$ ずつ移動していたが、毎フレームの移動量が $0.01$ ずつであれば i_mileage は毎フレーム $0.01$ ずつ増加する。
実行結果(図15)のTrackの回転は具体的には、58個のTrackBitが毎フレーム $0.01$ ずつ移動しているのである。
3行目の GetTrackBitMatrices(..) は本節で用意されている補助メソッドである。Trackは図16に示されるように、その経路は2つの線分と2つの半円によって構成される。各TrackBitは向きを変えながらこの経路を同じ速度で移動するわけであるが、GetTrackBitMatrices(..) が返す行列はこの経路上の各TrackBitに実行されている変換行列である。

(GetTrackBitMatrices(..)はカスタムライブラリーのキーフレームアニメーションを用いて実装されており、本節でその解説までも含めると長くなるため、具体的な内容については第6章で解説する)

しかし、このメソッドにおけるTrackの経路はその中心が原点であり、メソッドの返す変換行列をそのまま各TrackBitに実行しても図15に示されるように、各TrackBitの移動経路は原点を中心とする経路になってしまう。
実際には、TrackはBody下部の両側において回転していなければならないので、図15の状態からTrack全体をさらに移動させる必要がある。


次のプログラムは58個のTrackBitをBody右側の指定の位置において回転させるものである。
[Beta3B]  (実行結果 図17)
i_mileage += 0.01f;

Matrix4x4 localBody = Matrix4x4.identity;

Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);
}

Matrix4x4 worldBody = localBody;
Body.SetMatrix(worldBody);

9行目の localTrackBitR[i] は右側の58個のTrackBitのローカル行列である。それは GetTrackBitMatrices(..) から取得した行列にBody右側までの移動を追加しているに過ぎない。c_TrackCenterPosR はBody右側のTrackの中心位置を表す定数である。GetTrackBitMatrices(..) から取得される行列では、Trackの中心位置が原点になってしまうので、Track全体を c_TrackCenterPosR だけ移動させればTrackの中心位置は c_TrackCenterPosR となり、実行結果(図17)に示されるように、58個のTrackBitはBody右側において回転するようになる (図17は戦車を前方から見たときのもので、Bodyの右側は図の左に位置している)

図17 Beta3B 実行結果
図18 Code3 実行結果


Body左側においてTrackを回転させる場合も同様である。GetTrackBitMatrices(..) から取得した行列にBody左側の指定位置への移動を追加すればよい。Beta3Aの実行結果(上図15)は原点周りのTrackの回転であるが、TrackBit(及びWheel)は初期状態ではXY平面に関して対称であり、この回転を z軸マイナス側から見ても z軸プラス側から見ても区別はつかない(時計回りか反時計回りかの違いはあるが)。
したがって、Bodyの両側でTrackを回転させるには、原点の周りを回転しているTrack全体を右、あるいは左に移動させればよい。

以下のプログラムは2つのTrack(58個のTrackBit 2セット)をBody下部の両側において回転させるものである。
[Code3]  (実行結果 図18)
i_mileage += 0.01f;

Matrix4x4 localBody = Matrix4x4.identity;

Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
Matrix4x4 moveLeft = TH3DMath.GetTranslation4x4(c_TrackCenterPosL);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);

    Matrix4x4 localTrackBitL = moveLeft * mtrcs[i];
    Matrix4x4 worldTrackBitL = localBody * localTrackBitL;
    TrackBitL[i].SetMatrix(worldTrackBitL);
}

Matrix4x4 worldBody = localBody;
Body.SetMatrix(worldBody);

先程のBeta3Bでは右側のTrackだけの処理であったが、ここでは左側のTrackに関する処理が追加されているに過ぎない (7行目、14~16行目)。14行目の localTrackBitL[i] は左側の58個のTrackBitのローカル行列であり、これは GetTrackBitMatrices(..) から取得した行列に c_TrackCenterPosL だけの平行移動を追加したものである (c_TrackCenterPosL はBody左側のTrackの中心位置を表す定数)。
なお、ここで使用している GetTrackBitMatrices() は速度の変化にも対応している。このプログラムでは i_mileage の毎フレームの増加量は $0.01$ であるため、各TrackBitの移動量も毎フレーム $0.01$ である。この増加量を例えば $0.02$ にすると、各TrackBitは毎フレーム $0.02$ ずつ移動するようになる。つまり、Trackの回転が倍の速さで行われるようになる。


# Code4
では次に、Trackの内部において7個のWheelを回転させる。今回のプログラムでは戦車自体のスピードはBodyの毎フレームの移動量であり、それは $0.01$ であった。 そのため Code3ではTrackを構成する58個のTrackBitも毎フレーム $0.01$ ずつ移動していた。
ここではWheelの回転を追加するが、Wheelの回転もその移動量に相当するだけの回転にしなければならない。
3-13節 Code6で見たように、Wheelがある地点から距離 $d$ だけ進んだとすると、そこまでにWheelが回転する角度 $\theta$ は、Wheelの半径を $r$ とすれば、\[ d \div 2\pi r \times 360 = \theta \]である。
ここでのWheelの移動距離は戦車の移動距離、すなわち Bodyの移動距離と同じであり、Bodyの移動距離はインスタンス変数 i_mileage に加算されている。したがって、ある距離進んだ際のWheelの回転角度を求めるには上記の式の $d$ を i_mileage とすればよい。

次のプログラムは右側のTrack内部においてWheelを1つだけ回転させるものである。
[Beta4A]  (実行結果 図19)
i_mileage += 0.01f;

Matrix4x4 localBody = Matrix4x4.identity;

// Wheels
float degWheel = (i_mileage / c_WheelLength) * 360.0f;
Matrix4x4 rotWheel = TH3DMath.GetRotation4x4(-degWheel, Vector3.forward);
Matrix4x4 traR = TH3DMath.GetTranslation4x4(c_attachPos_WheelR[3]);
Matrix4x4 localWheelR = traR * rotWheel;
Matrix4x4 worldWheelR = localBody * localWheelR;
WheelR[3].SetMatrix(worldWheelR);

// TrackBits
Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);
}

Matrix4x4 worldBody = localBody;
Body.SetMatrix(worldBody);

6行目の c_WheelLength はWheel 1周分の長さを表す定数であり、上式の $2\pi r$ に相当する。degWheel はWheelの回転角度であるが、このプログラムでは degWheel は毎フレーム 約$1.36^\circ$ずつ増加する (すなわち 毎フレームの移動量 $0.01$ が Wheelの円周上における $1.36^\circ$ 分の長さに等しい)。
Trackの回転は図19に示されるように時計回りである。これはこの回転を(Trackよりも) z軸マイナス側から見たときのものである。Wheelを回転させる場合にも時計回りで回転させなければならないが、Wheelの回転軸は z軸であるため(z軸マイナス側から見て)時計回りに回転させるには回転角度を減少させていく必要がある。7行目の回転行列の計算において -degWheel となっているのはそのためである。
Wheelは初期状態ではその中心が原点に置かれているので、Track内部で回転を行わせるには平行移動させなければならないが、8行目の c_attachPos_WheelR[3] はその移動量を表す定数であり、c_attachPos_WheelR[3] だけの移動を行うとWheelは右側のTrack内の中央で回転を行うようになる (図19 ; ここではBodyを非表示にしてある)。

図19 Beta4A 実行結果
図20 Beta4B 実行結果

Track内にはWheelが7個配置されるが、次のプログラムは右側のTrackの内部において7個のWheelを回転させるものである。
[Beta4B]  (実行結果 図20)
i_mileage += 0.01f;

Matrix4x4 localBody = Matrix4x4.identity;

// Wheels
float degWheel = (i_mileage / c_WheelLength) * 360.0f;
Matrix4x4 rotWheel = TH3DMath.GetRotation4x4(-degWheel, Vector3.forward);
for (int i = 0; i < WheelR.Length; i++)
{
    Matrix4x4 traR = TH3DMath.GetTranslation4x4(c_attachPos_WheelR[i]);
    Matrix4x4 localWheelR = traR * rotWheel;
    Matrix4x4 worldWheelR = localBody * localWheelR;
    WheelR[i].SetMatrix(worldWheelR);
}

// TrackBits
Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);
}

Matrix4x4 worldBody = localBody;
Body.SetMatrix(worldBody);

上のBeta4Aは1つのWheelを回転させるものであったが、今回はその処理を7個のWheelに対して行うようにしただけである (8~14行目)。c_attachPos_WheelR[i] は各Wheelのアタッチポジションであり、各Wheelは図20に示される位置においてそれぞれ同じ速度で回転を行う。


では、左側のTrack内部におけるWheelの回転も追加する。TrackBitと同じようにWheelも初期状態ではXY平面に関して対称である。図6は初期状態のWheelを z軸マイナス側から見たときのものであるが、これを反対側から見ても全く同じである。したがって、回転しているWheelを(z軸マイナス側、プラス側の)どちらから見ても時計回りか反時計回りか以外に違いはない。
つまり、左側のTrack内部でWheelを回転させる場合も、単に原点で回転しているWheelを左側に移動させればよいだけである。

プログラムは以下のとおり。
[Code4]  (実行結果 図21)
i_mileage += 0.01f;

Matrix4x4 localBody = Matrix4x4.identity;

// Wheels
float degWheel = (i_mileage / c_WheelLength) * 360.0f;
Matrix4x4 rotWheel = TH3DMath.GetRotation4x4(-degWheel, Vector3.forward);
for (int i = 0; i < WheelR.Length; i++)
{
    Matrix4x4 traR = TH3DMath.GetTranslation4x4(c_attachPos_WheelR[i]);
    Matrix4x4 localWheelR = traR * rotWheel;
    Matrix4x4 worldWheelR = localBody * localWheelR;
    WheelR[i].SetMatrix(worldWheelR);

    Matrix4x4 traL = TH3DMath.GetTranslation4x4(c_attachPos_WheelL[i]);
    Matrix4x4 localWheelL = traL * rotWheel;
    Matrix4x4 worldWheelL = localBody * localWheelL;
    WheelL[i].SetMatrix(worldWheelL);
}

// TrackBits
Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
Matrix4x4 moveLeft = TH3DMath.GetTranslation4x4(c_TrackCenterPosL);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);

    Matrix4x4 localTrackBitL = moveLeft * mtrcs[i];
    Matrix4x4 worldTrackBitL = localBody * localTrackBitL;
    TrackBitL[i].SetMatrix(worldTrackBitL);
}

Matrix4x4 worldBody = localBody;
Body.SetMatrix(worldBody);

図21 Code4 実行結果
Beta4Bから左側のWheelとTrackの回転が追加されているだけである。15~18行目は左側のWheelの処理であるが、右側のWheelの処理との違いは平行移動の移動先だけである。右側の各Wheelの移動先は c_attachPos_WheelR[i] であるのに対し、左側の各Wheelの移動先が c_attachPos_WheelL[i] となっている点のみが異なる。
それ以降の処理(Trackの回転)についてはCode3の5行目以降と同じものである。
Wheel、Trackの回転は右側、左側で全く同じものであり、それぞれXY平面に関して対称な位置で回転しているに過ぎない。


# Code5
では、今までのすべてのプログラムを統合して戦車を動かしてみよう。
キー操作はCode2と同じであるが、ここではさらに戦車の速度(Bodyの速度)を変えられるようにしてある。
  H  :  Bodyの向きを y軸周りに $0.25^\circ$ ずつ回転させる。
  L  :  Bodyの向きを y軸周りに $-0.25^\circ$ ずつ回転させる。
  D  :  Turretを y軸周りに $-1.0^\circ$ ずつ回転させる。
  F  :  Turretを y軸周りに $1.0^\circ$ ずつ回転させる。
  J  :  Barrelを z軸周りに $-0.2^\circ$ ずつ回転させる。
  K  :  Barrelを z軸周りに $0.2^\circ$ ずつ回転させる。
  S  :  Bodyを運動(あるいは停止)させるためのスイッチ。

  N  :  Bodyの速度を減少させる。
  M  :  Bodyの速度を増加させる。

プログラムは以下のとおり。
[Code5]  
if (!i_INITIALIZED)
{
    i_MOVE = false;    
    i_mileage = 0.0f;
    i_degBody = 0.0f;
    i_degTurret = 0.0f;
    i_degBarrel = 0.0f;
    i_speedBody = 0.01f;    

    i_INITIALIZED = true;
}

if (Input.GetKeyDown(KeyCode.S))
{
    i_MOVE = !i_MOVE;
}

if (Input.GetKey(KeyCode.H))
{
    i_degBody -= 0.25f;
}
else if (Input.GetKey(KeyCode.L))
{
    i_degBody += 0.25f;
}

if (Input.GetKey(KeyCode.D))
{
    i_degTurret -= 1.0f;
}
else if (Input.GetKey(KeyCode.F))
{
    i_degTurret += 1.0f;
}

if (Input.GetKey(KeyCode.J))
{
    i_degBarrel -= 0.2f;
    if (i_degBarrel < 0.0f) { i_degBarrel = 0.0f; }
}
else if (Input.GetKey(KeyCode.K))
{
    i_degBarrel += 0.2f;
    if (i_degBarrel > 20.0f) { i_degBarrel = 20.0f; }
}

if (Input.GetKey(KeyCode.N))
{
    i_speedBody -= 0.0001f;
    if (i_speedBody < 0.01f) { i_speedBody = 0.01f; }
}
else if (Input.GetKey(KeyCode.M))
{
    i_speedBody += 0.0001f;
    if (i_speedBody > 0.06f) { i_speedBody = 0.06f; }
}


// Barrel
Matrix4x4 traBarrel = TH3DMath.GetTranslation4x4(c_attachPos_Barrel);
Matrix4x4 rotBarrel = TH3DMath.GetRotation4x4(i_degBarrel, Vector3.forward);
Matrix4x4 localBarrel = traBarrel * rotBarrel;

// Turret
Matrix4x4 traTurret = TH3DMath.GetTranslation4x4(c_attachPos_Turret);
Matrix4x4 rotTurret = TH3DMath.GetRotation4x4(i_degTurret, Vector3.up);
Matrix4x4 localTurret = traTurret * rotTurret;

// Body
Matrix4x4 rotBody = TH3DMath.GetRotation4x4(i_degBody, Vector3.up);
Vector3 forwardDir = rotBody * Vector3.right;
float speed = i_MOVE ? i_speedBody : 0f;
Vector3 pos = Body.GetWorldPosition() + speed * forwardDir;
Matrix4x4 traBody = TH3DMath.GetTranslation4x4(pos);
Matrix4x4 localBody = traBody * rotBody;

Matrix4x4 worldBarrel = localBody * localTurret * localBarrel;
Matrix4x4 worldTurret = localBody * localTurret;
Matrix4x4 worldBody = localBody;

Barrel.SetMatrix(worldBarrel);
Turret.SetMatrix(worldTurret);
Body.SetMatrix(worldBody);

i_mileage += speed;

// Wheels
float degWheel = (i_mileage / c_WheelLength) * 360.0f;
Matrix4x4 rotWheel = TH3DMath.GetRotation4x4(-degWheel, Vector3.forward);
for (int i = 0; i < WheelR.Length; i++)
{
    Matrix4x4 traR = TH3DMath.GetTranslation4x4(c_attachPos_WheelR[i]);
    Matrix4x4 localWheelR = traR * rotWheel;
    Matrix4x4 worldWheelR = localBody * localWheelR;
    WheelR[i].SetMatrix(worldWheelR);

    Matrix4x4 traL = TH3DMath.GetTranslation4x4(c_attachPos_WheelL[i]);
    Matrix4x4 localWheelL = traL * rotWheel;
    Matrix4x4 worldWheelL = localBody * localWheelL;
    WheelL[i].SetMatrix(worldWheelL);
}

// TrackBits
Matrix4x4[] mtrcs = GetTrackBitMatrices(i_mileage);
Matrix4x4 moveRight = TH3DMath.GetTranslation4x4(c_TrackCenterPosR);
Matrix4x4 moveLeft = TH3DMath.GetTranslation4x4(c_TrackCenterPosL);
for (int i = 0; i < TrackBitR.Length; i++)
{
    Matrix4x4 localTrackBitR = moveRight * mtrcs[i];
    Matrix4x4 worldTrackBitR = localBody * localTrackBitR;
    TrackBitR[i].SetMatrix(worldTrackBitR);

    Matrix4x4 localTrackBitL = moveLeft * mtrcs[i];
    Matrix4x4 worldTrackBitL = localBody * localTrackBitL;
    TrackBitL[i].SetMatrix(worldTrackBitL);
}

8行目の i_speedBody はBodyの速度を表すfloat型のインスタンス変数であり、初期値は $0.01$ である (毎フレーム $0.01$ ずつの移動)。この変数の値は2つのキー N、M によって増減させることができる。その処理は47~56行目の部分であり、Nキーを押し続けると i_speedBody は減少していき、Mキーを押し続けると増加していくが、i_speedBody の上限は $0.06$、下限は $0.01$ である。つまり、このプログラムでは戦車をどれだけ速くしても毎フレーム $0.06$ ずつの移動にしかならない。
また、これに関連して72行目のローカル変数 speed にセットする値も変更されている。speed はCode1やCode2でも使われたが、そこではBodyが停止中は常に $0$ がセットされ、運動中は $0.01$ がセットされていた。しかし、今回はBodyの速度が変化するので運動中にセットされる値は i_speedBody に変わっている。
それ以外の内容についてはCode2及びCode4と同じである。実際、わずかな相違を除けば 83行目までがCode2、それ以降がCode4である。












© 2020-2024 Redpoll's 60 (All rights reserved)