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

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


いわゆる機械と呼ばれるもののほとんどは、何らかのスイッチが入った際に決められた運動を自動的に行うが、本節で実装する運動も一般の機械と同様に、あるスイッチによってオブジェクトに指定の運動を行わせるものである。


# Code1
本節で扱うオブジェクトはレコードプレーヤーであり、以下の図1から図5は各部品の初期状態である。

  • 図1 Plinth 初期状態
  • 図2 Platter 初期状態
  • 図3 Record 初期状態

  • 図4 Adjuster 初期状態
  • 図5 Tonearm 初期状態
  • 図6 オブジェクトの階層構造

プログラム中で回転を行うオブジェクトは図2のPlatter、図4のAdjuster、図5のTonearm であり、それぞれの初期状態における回転軸はPlatter、Adjusterが y軸、Tonearmが x軸である。図6はこのレコードプレーヤーを構成するオブジェクトの階層構造である。

最初のプログラムは上記のオブジェクトを一体化した状態にするものである。
[Code1]  (実行結果 図7)
Matrix4x4 localTonearm  = TH3DMath.GetTranslation4x4(c_attachPos_Tonearm);
Matrix4x4 localAdjuster = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);
Matrix4x4 localRecord   = TH3DMath.GetTranslation4x4(c_attachPos_Record);
Matrix4x4 localPlatter  = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 localPlinth   = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldTonearm  = localPlinth * localAdjuster * localTonearm;
Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldRecord   = localPlinth * localPlatter * localRecord;
Matrix4x4 worldPlatter  = localPlinth * localPlatter;
Matrix4x4 worldPlinth   = localPlinth;

Tonearm.SetMatrix(worldTonearm);
Adjuster.SetMatrix(worldAdjuster);
Record.SetMatrix(worldRecord);
Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

このプログラムは単に各オブジェクトをそれぞれのアタッチポジションまで平行移動させているだけである。1~4行目の c_attachPos_## は各オブジェクトのアタッチポジションを表すVector3型の定数である (各オブジェクトの親座標系における位置)。
なお、Plinthは一番上の親オブジェクトであるが、ここでは $(32,\ 16, -8)$ だけの平行移動を行っている (5行目)。この平行移動はレコードプレーヤー全体を図8に示される’置物’の上に置くための移動であり、それ以外の意味はない。

図7 Code1 実行結果
図8 やや離れた位置から(置物の上に置かれている)


# Code2
次に、Recordを回転させる。Recordの回転は、実際には Recordの親オブジェクトであるPlatterを回転させるだけである。これによって、PlatterにアタッチされているRecordが一体となって回転することになる。

次のプログラムはPlatterをアタッチポジションにおいて回転させるものである。
[Beta2]  (実行結果 図9)
i_degPlatter += 3.0f;
Matrix4x4 traPlatter   = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 rotPlatter   = TH3DMath.GetRotation4x4(i_degPlatter, Vector3.up);
Matrix4x4 localPlatter = traPlatter * rotPlatter;

Matrix4x4 localPlinth = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldPlatter = localPlinth * localPlatter;
Matrix4x4 worldPlinth  = localPlinth;

Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

プログラム中の i_degPlatter はPlatterの回転角度を表すインスタンス変数である。ここでは、毎フレーム $3^\circ$ ずつの増加であるためPlatterは毎フレーム $3^\circ$ ずつ回転する (60FPSの設定であるため 1分間の回転数は約30回である。通常のレコードのように 1分間の回転数を33回転にするには、毎フレーム回転角度を $3.3^\circ$ ずつ増加させればよい)。

上のBeta2はPlatterのみの回転であるが、PlatterにRecordをアタッチすればRecordも回転した状態になる。実際のプログラムは次のとおり。
[Code2]  (実行結果 図10)
i_degPlatter += 3.0f;
Matrix4x4 traPlatter   = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 rotPlatter   = TH3DMath.GetRotation4x4(i_degPlatter, Vector3.up);
Matrix4x4 localPlatter = traPlatter * rotPlatter;

Matrix4x4 localRecord = TH3DMath.GetTranslation4x4(c_attachPos_Record);

Matrix4x4 localPlinth = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldRecord  = localPlinth * localPlatter * localRecord;
Matrix4x4 worldPlatter = localPlinth * localPlatter;
Matrix4x4 worldPlinth  = localPlinth;

Record.SetMatrix(worldRecord);
Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

このプログラムは単に上記のBeta2に、Recordに関する記述が追加されているに過ぎない (6、10、14行目 ; RecordはPlatterと一体となって回転するだけであるため、実際にはこの2つはそれぞれ別のオブジェクトとして分ける必要はない)。

図9 Beta2 実行結果
図10 Code2 実行結果


# Code3
図11 Recordに針を落とした状態
続いて、Recordに針を落とした状態にする。「針を落とした状態」とは、TonearmとAdjusterを指定の角度だけ回転させた図11のような状態のことである。まずは、AdjusterとTonearmをそれぞれ独立に回転させた場合を見ていく。

Adjusterの回転は水平方向の位置調整のためである。
次のプログラムはAdjusterがPlinth上のアタッチポジションにおいて、$0^\circ$ から $40^\circ$ の間の往復回転を行うものである。
この往復回転の角度は単振動によって計算されるが、プログラム中で使われる i_shm は単振動の計算に使われる角度を表す型のインスタンス変数である (float型)。
[Beta3A]  (実行結果 図12)
i_shm += 1;
float deg = 0.5f * 40.0f * (-Mathf.Cos(i_shm * Mathf.Deg2Rad) + 1.0f);
Matrix4x4 rotAdjuster   = TH3DMath.GetRotation4x4(deg, Vector3.up);
Matrix4x4 traAdjuster   = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);
Matrix4x4 localAdjuster = traAdjuster * rotAdjuster;

Matrix4x4 localPlinth = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldPlinth   = localPlinth;

Adjuster.SetMatrix(worldAdjuster);
Plinth.SetMatrix(worldPlinth);

図12 Beta3A 実行結果
図13 Adjusterの回転軸 (紫色の軸)

1~5行目はAdjusterに定義される変換であり、その内容はAdjusuterが自身のアタッチポジションにおいて $0^\circ$ から $40^\circ$ の間の往復回転を行うというものである。
実行結果(図12)に見られるように、Adjusterがアタッチポジションにおいて往復回転を行うだけである。また、この回転の回転軸は図13に示される紫色の軸であり、この紫色の軸はAdjusterを初期状態の位置まで戻した際には y軸に一致する。


Tonearmの回転は垂直方向の位置調整のためである。
以下のプログラムBeta3BはTonearmがAdjuster上のアタッチポジションにおいて、$0^\circ$ から $10^\circ$ の間の往復回転を行うものである (AdjusterはPlinthにアタッチされた状態)。
[Beta3B]  (実行結果 図14)
i_shm += 1;
float deg = 0.5f * 10.0f * (-Mathf.Cos(i_shm * Mathf.Deg2Rad) + 1.0f);
Matrix4x4 rotTonearm   = TH3DMath.GetRotation4x4(deg, Vector3.right);
Matrix4x4 traTonearm   = TH3DMath.GetTranslation4x4(c_attachPos_Tonearm);
Matrix4x4 localTonearm = traTonearm * rotTonearm;

Matrix4x4 localAdjuster = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);

Matrix4x4 localPlinth = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldTonearm  = localPlinth * localAdjuster * localTonearm;
Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldPlinth   = localPlinth;

Tonearm.SetMatrix(worldTonearm);
Adjuster.SetMatrix(worldAdjuster);
Plinth.SetMatrix(worldPlinth);

図14 Beta3B 実行結果
図15 Tonearmの回転軸 (黄色の軸)

1~5行目はTonearmに定義される変換であり、その内容はTonearmが自身のアタッチポジションにおいて $0^\circ$ から $10^\circ$ の間の往復回転を行うというものである。また、今回は Adjusterは回転を行わずアタッチポジションまでの平行移動のみとなっている (7行目)。
実行結果(図14)に見られるように、Tonearmがアタッチポジションにおいて往復回転を行うだけである。この回転の回転軸は図15に示される黄色の軸であり、この黄色の軸はTonearmを初期状態の位置まで戻した際には x軸に一致する。


Recordに針を落とした状態にするための具体的な変換手順は、まず Tonearmを x軸周りに $2^\circ$回転させ、Adjusterにアタッチする。続いて、Adjusterを y軸周りに $20^\circ$ 回転させ、Plinthにアタッチする。最終的に、Plinth上において Adjusterが水平方向に $20^\circ$、Tonearmが垂直方向に $2^\circ$ 回転した状態になる。

実際のプログラムは以下のとおり。
[Code3]  (実行結果 上図11)
Matrix4x4 rotTonearm   = TH3DMath.GetRotation4x4(2, Vector3.right);
Matrix4x4 traTonearm   = TH3DMath.GetTranslation4x4(c_attachPos_Tonearm);
Matrix4x4 localTonearm = traTonearm * rotTonearm;

Matrix4x4 rotAdjuster   = TH3DMath.GetRotation4x4(20, Vector3.up);
Matrix4x4 traAdjuster   = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);
Matrix4x4 localAdjuster = traAdjuster * rotAdjuster;

Matrix4x4 localRecord  = TH3DMath.GetTranslation4x4(c_attachPos_Record);
Matrix4x4 localPlatter = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 localPlinth  = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldTonearm  = localPlinth * localAdjuster * localTonearm;
Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldRecord   = localPlinth * localPlatter * localRecord;
Matrix4x4 worldPlatter  = localPlinth * localPlatter;
Matrix4x4 worldPlinth   = localPlinth;

Tonearm.SetMatrix(worldTonearm);
Adjuster.SetMatrix(worldAdjuster);
Record.SetMatrix(worldRecord);
Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

Code1は全てのオブジェクトを一体化するだけのものであったが、このプログラムではCode1にTonearmとAdjusterの回転処理を追加しただけで、それ以外はCode1と同じである (Code1からの変更箇所はTonearmとAdjusterのローカル行列のみである)。


# Code4
下図16は各オブジェクトを一体化した状態、すなわち それぞれのアタッチポジションまで平行移動しただけの状態(Code1の実行結果)であり、図17はRecordに針を落とした状態(Code3の実行結果)である。

図16 各オブジェクトを一体化した状態(それぞれのアタッチポジションまで平行移動しただけの状態)
図17 Recordに針を落とした状態

以下では便宜上、図16の状態(一体化しただけの状態)を「停止状態」、図17の状態(Recordに針を落とした状態)を「再生状態」と呼ぶことにする。

ここでは、指定のキーを押すことによって停止状態から再生状態へ変化するプログラムを作成する。
停止状態から再生状態への変化は次の3段階に分かれる。

  • 図18 Tonearmを垂直方向に8°回転させる
  • 図19 Adjusterを水平方向に20°回転させる
  • 図20 Tonearmを垂直方向に-6°回転させる

まず始めに Tonearmを垂直方向に $8^\circ$ 回転させる (図18)。次に、Adjusterを水平方向に $20^\circ$ 回転させる (図19)。最後に Tonearmを垂直方向に $-6^\circ$ 回転させる (図20)。
最終的に Adjusterが水平方向に $20^\circ$、Tonearmが垂直方向に $2^\circ$ 回転した状態、すなわち 再生状態になる (停止状態においてはTonearm及びAdjusterには回転は行われていないので両者の回転角度は $0^\circ$である)。

次のプログラムは Sキーを押すことで、レコードプレーヤーを停止状態から再生状態へ、240フレームかけて変化させるものである。
[Code4]  (実行結果 図21)
if (!i_INITIALIZED)
{
    i_motionCounter = 1000;
    i_degTonearm = 0.0f;
    i_degAdjuster = 0.0f;

    i_INITIALIZED = true;
}


if (Input.GetKeyDown(KeyCode.S) && i_motionCounter > 240)
{
    i_motionCounter = 0;
    i_degTonearm = 0.0f;
    i_degAdjuster = 0.0f;
}

i_motionCounter++;

if (i_motionCounter <= 60)
{
    float t = i_motionCounter / 60.0f;
    i_degTonearm = 8.0f * t;
}
else if (i_motionCounter <= 180)
{
    float t = (i_motionCounter - 60) / 120.0f;
    i_degAdjuster = 20.0f * t;
}
else if (i_motionCounter <= 240)
{
    float t = (i_motionCounter - 180) / 60.0f;
    i_degTonearm = 8.0f - 6.0f * t;
}

Matrix4x4 rotTonearm   = TH3DMath.GetRotation4x4(i_degTonearm, Vector3.right);
Matrix4x4 traTonearm   = TH3DMath.GetTranslation4x4(c_attachPos_Tonearm);
Matrix4x4 localTonearm = traTonearm * rotTonearm;

Matrix4x4 rotAdjuster   = TH3DMath.GetRotation4x4(i_degAdjuster, Vector3.up);
Matrix4x4 traAdjuster   = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);
Matrix4x4 localAdjuster = traAdjuster * rotAdjuster;

Matrix4x4 localPlatter = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 localRecord  = TH3DMath.GetTranslation4x4(c_attachPos_Record);
Matrix4x4 localPlinth  = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldTonearm  = localPlinth * localAdjuster * localTonearm;
Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldRecord   = localPlinth * localPlatter * localRecord;
Matrix4x4 worldPlatter  = localPlinth * localPlatter;
Matrix4x4 worldPlinth   = localPlinth;

Tonearm.SetMatrix(worldTonearm);
Adjuster.SetMatrix(worldAdjuster);
Record.SetMatrix(worldRecord);
Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

まず、36行目以降から見ていこう。この部分は36行目の GetRotation4x4(..) の第1引数が i_degTonearm になっていることと、40行目の GetRotation4x4(..) の第1引数が i_degAdjuster になっていることを除けばCode3と同じである (Code3ではこの2箇所には $2$ と $20$ という数値が直に設定されていた)。
i_degTonearm はTonearmの回転角度を表すインスタンス変数であり、i_degAdjuster はAdjusterの回転角度を表すインスタンス変数である (ともにfloat型)。今回はプログラム実行中にTonearm及びAdjusterの回転角度が変化するので、これらのインスタンス変数が必要になる。
1行目の初期化ブロックでは3つのインスタンス変数を初期化している。i_degTonearm 及び i_degAdjuster が $0$ で初期化されるのでプログラム開始時点ではTonearm、Adjusterに対して何も回転が実行されていない状態、すなわち 停止状態から始まる。
i_motionCounterint型のインスタンス変数で、その役割は 4-15節で使われた同名の変数と同じである。停止状態から再生状態への変化は240フレームかけて行われるが、その240フレームの間の何フレーム目かを表す変数である。
11行目のifブロックはSキーが押された際に入るブロックであるが、このとき同時に i_motionCounter の値が $240$ よりも大きい値でなければならない。初期化ブロックにおいて i_motionCounter の値を $1000$ で初期化しているのは $240$ より大きい値にするためである (特に$1000$である必要はない)。
プログラム実行開始後、Sキーが押されると11行目のifブロックに入るが、ここで各インスタンス変数が $0$ にリセットされる。これによってレコードプレーヤーは停止状態になり、ここから再生状態への変化が始まる。
20~34行目の3つのif/elseブロックは上記の3段階の変化に対応している。
最初の 60フレームの間にTonearmを垂直方向に $8^\circ$ 回転させる (i_degTonearm を $0$ から $8$ へ変化させる)。続いて、61フレーム目から180フレーム目にかけてAdjusterを水平方向に $20^\circ$ 回転させる (i_degAdjuster を $0$ から $20$ へ変化させる)。最後に 181フレーム目から240フレーム目にかけてTonearmを垂直方向に $-6^\circ$ 回転させる (i_degTonearm を $8$ から $2$ へ変化させる)。
3つのif/elseブロック内のローカル変数 t はそれぞれの段階において $0$ から $1$ へ変化する (実際には $0$ に近い数値から $1$ への変化)。
図21 Code4 実行結果
例えば、25行目の else if ブロックには61フレーム目から180フレーム目の間に入るが、この間に t の値は $0$ (に近い数値)から $1$ へ変化するので、28行目で計算される i_degAdjuster は $0$ から $20$ に変化する。
i_motionCounter の値が $240$ となるフレームでは i_degTonearm の値は $2$、i_degAdjuster の値は $20$、すなわち 再生状態になる。この時点で状態の変化は終了するが、i_motionCounter の値は毎フレーム増加し続ける (i_motionCounterが増加し続けても$240$を越えた状態では20~34行目のif/elseブロックに入ることはないので、i_degTonearm及びi_degAdjusterの値は変わることはない。そのため何も変化は起こらない)。
その後の段階でSキーが押されると11行目のifブロックに入り各インスタンス変数がリセットされるので、再び停止状態から再生状態への変化が始まる。


# Code5
Code4では Sキーが押された際に起こる変化は停止状態から再生状態への変化であり、状態の変化はいわばこの片方向のみの変化であった。ここでは、再生状態において Sキーが押されると再び停止状態に戻る処理を追加する。

プログラムを以下に示す。
[Code5]  (実行結果 図22)
if (!i_INITIALIZED)
{
    i_motionCounter = 1000;
    i_degTonearm = 0.0f;
    i_degAdjuster = 0.0f;
    i_degPlatter = 0.0f;
    i_degAdd = 0.0f;
    i_PLAY = false;

    i_INITIALIZED = true;
}

if (Input.GetKeyDown(KeyCode.S) && i_motionCounter > 240)
{
    i_PLAY = !i_PLAY;
    i_motionCounter = 0;
    i_degAdd = i_PLAY ? 0.0f : 3.0f;
}

i_motionCounter++;

if (i_motionCounter <= 60)
{
    float t = i_motionCounter / 60.0f;
    i_degTonearm = (i_PLAY) ? 8.0f * t : 2.0f + 6.0f * t;
}
else if (i_motionCounter <= 180)
{
    float t = (i_motionCounter - 60) / 120.0f;
    i_degAdjuster = (i_PLAY) ? 20.0f * t : 20.0f - 20.0f * t;
}
else if (i_motionCounter <= 240)
{
    float t = (i_motionCounter - 180) / 60.0f;
    i_degTonearm = (i_PLAY) ? 8.0f - 6.0f * t : 8.0f - 8.0f * t;
}

Matrix4x4 rotTonearm = TH3DMath.GetRotation4x4(i_degTonearm, Vector3.right);
Matrix4x4 traTonearm = TH3DMath.GetTranslation4x4(c_attachPos_Tonearm);
Matrix4x4 localTonearm = traTonearm * rotTonearm;

Matrix4x4 rotAdjuster = TH3DMath.GetRotation4x4(i_degAdjuster, Vector3.up);
Matrix4x4 traAdjuster = TH3DMath.GetTranslation4x4(c_attachPos_Adjuster);
Matrix4x4 localAdjuster = traAdjuster * rotAdjuster;

i_degPlatter += i_degAdd;
Matrix4x4 rotPlatter = TH3DMath.GetRotation4x4(i_degPlatter, Vector3.up);
Matrix4x4 traPlatter = TH3DMath.GetTranslation4x4(c_attachPos_Platter);
Matrix4x4 localPlatter = traPlatter * rotPlatter;
if (i_PLAY && i_degAdd < 3.0f)
{
    i_degAdd += 0.02f;
    if (i_degAdd > 3.0f) { i_degAdd = 3.0f; }
}
else if (!i_PLAY && i_degAdd > 0.0f)
{
    i_degAdd -= 0.02f;
    if (i_degAdd < 0.0f) { i_degAdd = 0.0f; }
}

Matrix4x4 localRecord = TH3DMath.GetTranslation4x4(c_attachPos_Record);
Matrix4x4 localPlinth = TH3DMath.GetTranslation4x4(32.0f, 16.0f, -8.0f);

Matrix4x4 worldTonearm  = localPlinth * localAdjuster * localTonearm;
Matrix4x4 worldAdjuster = localPlinth * localAdjuster;
Matrix4x4 worldRecord   = localPlinth * localPlatter * localRecord;
Matrix4x4 worldPlatter  = localPlinth * localPlatter;
Matrix4x4 worldPlinth   = localPlinth;

Tonearm.SetMatrix(worldTonearm);
Adjuster.SetMatrix(worldAdjuster);
Record.SetMatrix(worldRecord);
Platter.SetMatrix(worldPlatter);
Plinth.SetMatrix(worldPlinth);

初期化ブロックにおける i_motionCounteri_degTonearmi_degAdjusteri_degPlatter については上で述べたとおりである。i_degAdd はPlatterを回転させる際の1フレームあたりの回転角度のことで、float型のインスタンス変数である (詳しくは後述する)。
i_PLAY は再生状態か停止状態かを表すbool型インスタンス変数である。プログラム開始時点では停止状態から始まるので初期化ブロックではfalseがセットされる。停止状態においてSキーを押すとこの変数にはtrueがセットされ、再生状態においてSキーを押すとこの変数にはfalseがセットされる。
i_PLAY の値が変更されるのは13行目のifブロックにおいてのみであるが、停止状態から再生状態への(あるいはその逆への)変化中においてはこのifブロックに入ることはないので、状態を変化させるには完全にどちらかの状態になってからSキーを押す必要がある。
20行目以降に関しては主に2つの処理が追加されている。1つ目は i_PLAY に関する処理である。具体的には、再生状態への変化の過程であるのか、停止状態への変化の過程であるのかによる処理の場合分けである。
22~36行目はレコードプレーヤーの状態を3段階に分けて変化させる処理である。Code4では停止状態から再生状態へ変化させるだけであったが、ここでは再生状態から停止状態へ変化させる処理が追加されている。各if/elseブロック内の三項演算子では i_PLAY による処理分けがあるが、i_PLAYtrueのときは(再生状態への変化過程では)その処理内容はCode4のものと同じである。
i_PLAYfalseであるときは停止状態への変化の過程であるが、停止状態への変化過程も次の3段階に分かれる (停止状態への変化が始まる際の i_degTonearm の値は $2$ であり、i_degAdjuster の値は $20$ である)。
最初の 60フレームの間に i_degTonearm の値を $2$ から $8$ にする。続いて 61フレーム目から 180フレーム目にかけて i_degAdjuster の値を $20$ から $0$ にする。最後に 180フレーム目から 240フレーム目までに i_degTonearm の値を $8$ から $0$ にする。

2つ目の追加処理はPlatterを回転させる際、あるいは停止させる際の1フレームあたりの回転角度の増減である。Code2と同じくここでも再生状態においては、Platterは毎フレーム $3^\circ$ ずつ回転している (46行目 ; i_PLAYtrueの間は一部のフレームを除きi_degPlatterは毎フレーム $3$ ずつ増加する)。
もし、Platterの回転を停止させる際には、単に i_degPlatter に対する加算を行わなければよい。つまり、毎フレームの加算量を $0$ にすれば i_degPlatter の値は変化しないので、その時点でPlatterは回転しなくなる。
しかし、i_degPlatter に対する加算量を突然 $0$ にしてしまうと、Platterが急に停止してしまうので、Platterの停止が徐々に行われるようにするために i_degAdd が使われている。i_degAdd はPlatterの1フレームあたりの回転角度を表すインスタンス変数であり、再生状態から停止状態にする際には13行目のifブロックにおいて $3$ で初期化される。
そして、この値は停止状態へ変化する間に55行目のelse ifブロックにおいて $3$ から $0$ に減衰する。 その減衰は一定時間かけて行われるので、i_degPlatter に対する加算量も $3$ から $0$ に一定時間かけて減衰する。このようにしてPlatterの停止が徐々に行われるわけである。
図22 Code5 実行結果
Platterを停止状態から再生状態にする場合も同様である。i_degPlatter に対する加算を突然 $0$ から $3$ にせずに、i_degAdd の値を変化させてPlatterの回転が徐々に始まるようにしている。具体的には、停止状態から再生状態にする際には13行目のifブロックにおいて i_degAdd を $0$ で初期化し、再生状態へ変化する間に50行目のifブロックにおいて $0$ から $3$ に増加させている。

それ以外の点については上で見てきたプログラムと特に変わりはない。












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