Redpoll's 60
第5章 変換のオブジェクト化

$§$5-5 Transformクラスの実装 1


今までのプログラムでは、一体化したオブジェクトの運動は次のような形で記述されていた。

Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;

Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;

Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;

Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;

Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);

図1 オブジェクトの階層構造
このプログラムでは3つのオブジェクトObj1、Obj2、Obj3が使われており、それらの階層構造は図1に示されるとおりである。
まず、各オブジェクトの独自の運動をローカル行列に定義するが、そのローカル行列はスケール行列、回転行列、平行移動行列の積である。例えば Obj3の場合では、ローカル行列は localObj3 であり、localObj3traObj3rotObj3sclObj3 の積である (4行目)。
最終的にオブジェクトに実行される行列はワールド行列であり、ワールド行列は各オブジェクトのローカル行列の積である。例えば、Obj2の場合では、ワールド行列は worldObj2 であり、worldObj2localObj1localObj2 の積である (17行目)。


では、図1の階層構造を持つ3つのオブジェクト Obj1、Obj2、Obj3 がある状態で静止しているときに、何らかの変化が発生したとして、その変化による影響についてそれぞれの場合で見ていこう。

プログラム実行中のあるフレームにおいて Obj3に変化が発生したとしよう。例えば、Obj3のスケール行列 sclObj3 の内容が更新されたとする。
Matrix4x4 sclObj3 = .. 何らかの値の更新 .. ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;

Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;

Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;

Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;

Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);

上のプログラムにおいて赤いフォントで表示されている変数は、行列を表す変数のうちで sclObj3 の変化によって、(行列の)内容が変化する変数である (以降のプログラムにおいても行列を表す変数で赤く表示されているものは、行列の内容がそれ以前のものとは変化していることを示している)。
4行目の localObj3 は3つの行列の積であり、その1つに sclObj3を含むので、sclObj3の変化の影響を受ける。16行目の worldObj3 も3つの行列の積であり、その1つに localObj3を含むので、やはり sclObj3が変化した際には(間接的にではあるが)影響を受けることになる。
したがって、Obj3に変化が発生した場合、ワールド行列の中では worldObj3だけが それ以前のフレームのものと内容が異なるが、他の2つのワールド行列はそれ以前のフレームのものと内容に違いはない。つまり、Obj3に変化が発生した場合には、Obj3のみが動き、他の2つのオブジェクトは静止したままであるということである。


次に、あるフレームにおいて Obj2に変化が発生した場合を見てみよう。例えば、Obj2の回転行列 rotObj2 の内容が更新されたとする。
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;

Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = .. 何らかの値の更新 .. ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;

Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;

Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;

Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);

9行目の localObj2 は3つの行列の積であり、その1つに rotObj2を含むので、rotObj2の変化の影響を受ける。また、16行目の worldObj3、17行目の worldObj2はローカル行列同士の積であるが、その1つに localObj2を含むので、やはり rotObj2の内容が変化すれば それらのワールド行列の内容も変化する。
したがって、Obj2に変化が生じた場合、2つのワールド行列 worldObj3worldObj2の内容が変化することになる。これは、静止状態にあった3つのオブジェクトのうちで、Obj2に変化が発生した場合には Obj2、Obj3の2つのオブジェクトが動くことを意味している。


最後に、あるフレームにおいて Obj1に変化が発生した場合を見てみよう。例えば、Obj1の平行移動行列 traObj1 の内容が更新されたとする。
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;

Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;

Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = .. 何らかの値の更新 .. ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;

Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1; 

Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);

14行目の localObj1 は3つの行列の積であり、その1つに traObj1を含むので、traObj1が変化すれば localObj1の内容も変化する。また、16行目~18行目の3つのワールド行列はいずれも localObj1を用いた計算の結果(あるいはlocalObj1のコピー)であるため、traObj1の内容が変化すれば、この3つのワールド行列の内容も変化することになる。
したがって、Obj1に変化が生じた場合、3つのワールド行列 worldObj3worldObj2worldObj1の内容が変化することになる。これは、静止状態にあった3つのオブジェクトのうちで、Obj1に変化が発生した場合には3つのオブジェクトすべてが動くことを意味している。

上で述べたことは、一体化するオブジェクトの数が増えても変わらない。
すなわち、あるオブジェクトが平行移動、あるいは回転、スケールによって、オブジェクトの状態に変化が発生した場合、そのオブジェクトのローカル行列、ワールド行列、そしてそのオブジェクトのすべての子オブジェクトのワールド行列の内容が変化する (「すべての子オブジェクト」とは2階層下の子、3階層下の子、あるいはそれより下の階層の子オブジェクトすべてのことである)。
しかし、そのオブジェクトより上の階層のオブジェクト(1階層上の親、2階層上の親、あるいはそれより上の親オブジェクト)のワールド行列は変化しない(もちろんローカル行列も変化しない)。
これは、あるオブジェクトに変化が発生した場合、そのオブジェクトより下の階層の子オブジェクトには変化が発生するが、そのオブジェクトより上の階層のオブジェクトには何も変化は起こらないということを意味する (オブジェクトに最終的に実行されるのはワールド行列であるから、ワールド行列に変化がなければオブジェクトの状態は変化しない)。

要約すると、あるオブジェクトに(平行移動、回転、スケールによる)変化が発生した場合には、そのオブジェクトのローカル行列とワールド行列、及び そのオブジェクトのすべての子オブジェクトのワールド行列 に対して更新処理が必要となる。そのオブジェクトの親オブジェクトを含む上の階層のオブジェクトに対する更新処理については何も行う必要はない。


上のプログラムでは毎フレームすべてのオブジェクトのローカル行列、ワールド行列の計算が行われているので、処理的には冗長なコードとなっている。仮に、各オブジェクトのローカル行列、ワールド行列をインスタンス変数にして、それらの変数については必要な場合にだけ更新が起こるようにプログラムを以下のように書き換えたとしよう。

bool fWorld1 =false;
bool fWorld2 =false;
bool fWorld3 =false;

bool fLocal3 = .. Obj3に何らかの変化があれば true .. ;  
if(fLocal3){
    Matrix4x4 sclObj3 = ... ;
    Matrix4x4 rotObj3 = ... ;
    Matrix4x4 traObj3 = ... ;
    i_localObj3 = traObj3 * rotObj3 * sclObj3;
    fWorld3 = true;    
}

bool fLocal2 = .. Obj2に何らかの変化があれば true .. ;  
if(fLocal2){
    Matrix4x4 sclObj2 = ... ;
    Matrix4x4 rotObj2 = ... ;
    Matrix4x4 traObj2 = ... ;
    i_localObj2 = traObj2 * rotObj2 * sclObj2;
    fWorld2 = true;    
}

bool fLocal1 = .. Obj1に何らかの変化があれば true .. ;  
if(fLocal1){
    Matrix4x4 sclObj1 = ... ;
    Matrix4x4 rotObj1 = ... ;
    Matrix4x4 traObj1 = ... ;
    i_localObj1 = traObj1 * rotObj1 * sclObj1;
    fWorld1 = true;    
}

if(fWorld1){
    i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
    i_worldObj2 = i_localObj1 * i_localObj2;
    i_worldObj1 = i_localObj1;
    Obj3.SetMatrix(i_worldObj3);
    Obj2.SetMatrix(i_worldObj2);
    Obj1.SetMatrix(i_worldObj1);
}
else if(fWorld2){
    i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
    i_worldObj2 = i_localObj1 * i_localObj2;
    Obj3.SetMatrix(i_worldObj3);
    Obj2.SetMatrix(i_worldObj2);
}
else if(fWorld3){
    i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
    Obj3.SetMatrix(i_worldObj3);
}


プログラムにおいてローカル行列、ワールド行列を表す変数には "i_" という接頭辞が追加されているが、これはそれらの変数がインスタンス変数であることを意味している。
このプログラムは各オブジェクトのローカル行列、ワールド行列が、必要な場合だけ更新されるように上のプログラムを変更したものである。
各オブジェクトの平行移動行列、回転行列、スケール行列に変化が生じた場合には fLocal# というbool型ローカル変数がtrueになる。そして、それぞれのオブジェクトに用意されているローカル行列更新用のifブロックにおいてローカル行列の更新を行うが、このときに そのオブジェクトのワールド行列、及び すべての子オブジェクトのワールド行列を更新するように fWorld# というbool型ローカル変数の値をtrueにする。例えば Obj2に変化が発生し、fLocal2trueになったとき、15行目のifブロックに入るが、そこでは自身のローカル行列である i_localObj2 を更新し、自身のワールド行列と子オブジェクトのワールド行列の更新処理が行われるように fWorld2 の値をtrueにしている。32行目以降のワールド行列更新のためのif elseブロックでは、そのオブジェクトとそのオブジェクトのすべての子オブジェクトのワールド行列が更新される (ワールド行列更新用のフラグであるfWorld1fWorld2fWorld3は2つ同時、あるいは3つ同時にtrueになる場合があるので、32行目以降のif elseブロックの順序には注意が必要である)
しかし、このプログラムは応急処置的な変更であるため見やすさの点では改善が必要であり、さらに 一体化するオブジェクトの数が増え階層構造がより複雑になるような場合を考えると、プログラムをより汎用的な構造にする必要がある。

次節ではこの問題に対する解決策として、各オブジェクトのローカル行列、ワールド行列の更新を再帰処理によって実装する。














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