Transformクラスを実装することの そもそもの発端は5-5節において述べたように、階層構造に含まれるオブジェクトに何らかの変化が発生した際に、その後に行われる一連のオブジェクトの更新処理が、必要なときに必要な分だけ行われるようにするためであった。
そして、前節においては階層関係にあるオブジェクトの更新処理を単純な形でモデル化した例を用いて、あるオブジェクトに変化が発生した際に、その後の更新処理(実際にはメッセージ出力)が、再帰的な実装によって、必要なオブジェクトに対してのみ1回だけ行われることを見たのであった。
本節において解説される独自のTransformクラスは、Unityに用意されている
Transformクラスを必要最小限の機能のみで構成するものである。
その構造自体は前節の Recursiveクラスと基本的には同じであり、階層関係にあるオブジェクトに変化が発生した際に、まず そのオブジェクトと そのオブジェクトのすべての子オブジェクトに対して更新処理のためのフラグを立てる。この処理は変化のあったオブジェクトから一番下の階層の子オブジェクトまで再帰的に行われる。さらに、実際の更新処理についても、一番上の親オブジェクトから一番下の階層まで、すべてのオブジェクトを1つ1つ再帰的にたどっていき、更新処理のフラグの立っているオブジェクトのみに実際の更新が行われるようにする。
A) 独自実装のTransformクラス
まず始めに、独自に実装されたTransformクラスである
THTransformクラスのプログラムを以下に示す。
(この
THTransformクラスは、ゲームエンジン Godotの Spatialクラスのソースコード(GitHub上のファイル名は node_3d.cpp)を参考にして作成したものである)
[class THTransform (21/08/23 修正版)]
public class THTransform
{
private THObject3D m_object;
private Vector3 m_localPosition = new Vector3(0.0f, 0.0f, 0.0f);
private Matrix4x4 m_localRotation = Matrix4x4.identity;
private Vector3 m_localScale = new Vector3(1.0f, 1.0f, 1.0f);
private Matrix4x4 m_localMatrix = Matrix4x4.identity;
private Matrix4x4 m_worldMatrix = Matrix4x4.identity;
private bool m_UPDATE_LOCAL = false;
private bool m_UPDATE_WORLD = false;
private THTransform m_parent = null;
private List<THTransform> m_children = new List<THTransform>();
private bool m_EXE_WORLD = false;
public THTransform(THObject3D o)
{
m_object = o;
}
public Vector3 thLocalPosition
{
get { return m_localPosition; }
set {
m_localPosition = value;
m_UPDATE_LOCAL = true;
NotifyChain();
}
}
public Matrix4x4 thLocalRotation
{
get { return m_localRotation; }
set {
m_localRotation = value;
m_UPDATE_LOCAL = true;
NotifyChain();
}
}
public Vector3 thLocalScale
{
get { return m_localScale; }
set {
m_localScale = value;
m_UPDATE_LOCAL = true;
NotifyChain();
}
}
public Matrix4x4 GetLocalMatrix()
{
if (m_UPDATE_LOCAL)
{
m_localMatrix = CalcLocalMatrix();
m_UPDATE_LOCAL = false;
}
return m_localMatrix;
}
public Matrix4x4 GetWorldMatrix()
{
if (m_UPDATE_WORLD)
{
m_worldMatrix = CalcWorldMatrix();
m_UPDATE_WORLD = false;
}
return m_worldMatrix;
}
private void NotifyChain()
{
m_UPDATE_WORLD = true;
m_EXE_WORLD = true;
foreach (var child in m_children)
{
child.NotifyChain();
}
}
public void UpdateChain()
{
UpdateObject();
foreach(var child in m_children)
{
child.UpdateChain();
}
}
private void UpdateObject()
{
if (m_EXE_WORLD)
{
Matrix4x4 worldM = GetWorldMatrix();
m_object.SetMatrix(worldM);
m_EXE_WORLD = false;
}
}
private Matrix4x4 CalcLocalMatrix()
{
float tx = m_localPosition.x;
float ty = m_localPosition.y;
float tz = m_localPosition.z;
float sx = m_localScale.x;
float sy = m_localScale.y;
float sz = m_localScale.z;
Matrix4x4 r = m_localRotation;
Vector4 c1 = new Vector4(sx * r[0, 0], sx * r[1, 0], sx * r[2, 0], 0.0f); // 1列目
Vector4 c2 = new Vector4(sy * r[0, 1], sy * r[1, 1], sy * r[2, 1], 0.0f); // 2列目
Vector4 c3 = new Vector4(sz * r[0, 2], sz * r[1, 2], sz * r[2, 2], 0.0f); // 3列目
Vector4 c4 = new Vector4(tx, ty, tz, 1.0f); // 4列目
return new Matrix4x4(c1, c2, c3, c4);
}
private Matrix4x4 CalcWorldMatrix()
{
Matrix4x4 localM = GetLocalMatrix();
Matrix4x4 worldM;
if (m_parent == null)
{
worldM = localM;
}
else
{
worldM = m_parent.GetWorldMatrix() * localM;
}
return worldM;
}
public void SetParent(THTransform p)
{
if(p == null)
{
if(m_parent != null)
{
m_parent.m_children.Remove(this);
m_parent = null;
}
return;
}
if(p == this)
{
Debug.Log("Error : 1 [THTransform_SetParent()]");
return;
}
if(m_parent != null)
{
Debug.Log("Error : 2 [THTransform_SetParent()]");
return;
}
THTransform w = p;
while (true)
{
if (w.m_parent == null) { break; }
if (w.m_parent == this)
{
Debug.Log("Error : 3 [THTransform_SetParent()]");
return;
}
w = w.m_parent;
}
m_parent = p;
m_parent.m_children.Add(this);
}
public bool HasParent()
{
return (m_parent != null);
}
public void Clear()
{
m_parent = null;
m_children = new List<THTransform>();
m_localPosition = new Vector3(0.0f, 0.0f, 0.0f);
m_localRotation = Matrix4x4.identity;
m_localScale = new Vector3(1.0f, 1.0f, 1.0f);
m_localMatrix = Matrix4x4.identity;
m_worldMatrix = Matrix4x4.identity;
m_UPDATE_LOCAL = false;
m_UPDATE_WORLD = false;
m_EXE_WORLD = false;
}
}
3行目から18行目まではメンバ変数の定義である (メンバ変数はすべて
privateである)。15行目、16行目メンバ変数
m_parent、
m_children は前節の
Recursiveクラスのものと役割は同じである (ただし、ここでは
m_parent 及び
m_childrenの要素が
THTransform型になっている)。
3行目の
THObject3D型の
m_object は変換の実行対象となるオブジェクトである。9行目、10行目の
m_localMatrix、
m_worldMatrix はオブジェクトのローカル行列、及び ワールド行列を表している。
12行目、13行目の
m_UPDATE_LOCAL、
m_UPDATE_WORLD は今回のプログラムではオブジェクトのローカル行列及びワールド行列を更新するかを示すフラグとして使われる。18行目の
m_EXE_WORLD はオブジェクトに対してワールド行列を実行するかどうかを示すフラグである。
あるフレームにおいてワールド行列が更新されれば、そのフレームにおいて、更新されたワールド行列をオブジェクトに実行するので
m_UPDATE_WORLD が
trueになる際には必ず
m_EXE_WORLD も
trueになる (89~90行目)。
m_UPDATE_WORLD、
m_EXE_WORLD の2つに分けているのは、オブジェクトに対するワールド行列の実行はプログラムの一番最後に行われるが、ワールド行列の更新はプログラムの途中で必要になることがあるためである。この点については後述する。
5-2節で述べたように、オブジェクトのローカル行列を作成するためには、平行移動のための1つの
Vector3インスタンスと、回転のための1つの
Quaternionインスタンスと、スケールのための1つの
Vector3インスタンスがあればよい。回転は
Quaternion あるいは 行列で表すが、
Quaternionによって回転データを保持していたとしても、いずれは回転行列に変換されるので、この
THTransformクラスでは
Quaternionではなく、直接
Matrix4x4を使っている。
5行目から7行目の
m_localPosition、
m_localRotation、
m_localScale はローカル行列作成の際に必要となる、平行移動のための
Vector3インスタンス、回転のための
Matrix4x4インスタンス、スケールのための
Vector3インスタンスである。
この3つのメンバ変数にはそれぞれプロパティが用意されており、外部から値の取得、あるいは更新することができる。具体的には26行目から60行目の部分で、実際に外部から値の取得、更新をする際には、接頭辞に
thの付いているプロパティ名
thLocalPosition、
thLocalRotation、
thLocalScale を使用する。
前節では
Recursiveクラスの
SomeChange() というメソッドが実行された際に、 オブジェクトに何らかの変化が発生したものとして処理を進めた。今回の
THTransformクラスの3つのプロパティ
thLocalPosition、
thLocalRotation、
thLocalScale は実際にオブジェクトの変化につながるものである。
26行目から60行目の各プロパティの
setブロックに記述されているように、
thLocalPositionに
Vector3型の値をセットすることは
m_localPositionの値を更新することであり、
thLocalRotationに
Matrix4x4型の値をセットすることは
m_localRotationの値を更新することであり、
thLocalScaleに
Vector3型の値をセットすることは
m_localScaleの値を更新することである。この3つのメンバ変数はローカル行列作成の際に必要となるので、3つのメンバ変数のいずれか1つに変化があれば、そのオブジェクトのローカル行列、ワールド行列の内容は更新しなければいけない。
すなわち、
thLocalPosition、
thLocalRotation、
thLocalScale の値の更新は、オブジェクトに何らかの変化が発生したことを意味している。
前節において
SomeChange() が実行された際には、オブジェクトに何らかの変化が発生したものとして、そのオブジェクトと そのオブジェクトのすべての子オブジェクトに対してメッセージ出力のためのフラグを立てた。
今回も事情はおなじである。
thLocalPosition、
thLocalRotation、
thLocalScale のいずれかの値の更新が行われれば、
NotifyChain()がコールされ、それによって再帰的に更新処理のためのフラグが立てられる。具体的には、変化のあったオブジェクトにはローカル行列及びワールド行列の更新、さらにワールド行列の実行が行われるように
m_UPDATE_LOCAL、
m_UPDATE_WORLD、
m_EXE_WORLD の3つを
trueにし、変化のあったオブジェクトのすべての子オブジェクトには、ワールド行列の更新とワールド行列の実行が行われるように
m_UPDATE_WORLD、
m_EXE_WORLD の2つを
trueにする。
以上に述べたことが、26行目から60行目の各プロパティの定義、及び 87行目の
NotifyChain()の内容である (
NotifyChain()は
m_EXE_WORLDを
trueにする処理が追加されている点以外は、前節の
Recursiveクラスのものとコードは同じである)。
前節の
Recursiveクラスにおける実際の更新処理(メッセージ出力)では、一番上の階層の親オブジェクトが
UpdateChain() を実行し、これが一番下の階層の子オブジェクトまで再帰的に実行された。
UpdateChain() の中では
m_UPDATE_LOCAL や
m_UPDATE_WORLD といったフラグの立っているオブジェクトがメッセージを出力するだけであった。
今回の
THTransformクラスにおける
UpdateChain() も上の階層の親オブジェクトから下の階層の子オブジェクトへ再帰的に実行されていく点は同じであるが、その内容はただのメッセージ出力ではない。具体的には、ローカル行列及びワールド行列の更新、さらにオブジェクトの状態の更新である(つまり、オブジェクトに最新のワールド行列を実行する)。
前節の
Recursiveクラスと同様に
THTransformクラスの
UpdateChain() もプログラムの一番最後に実行されることが前提である。一方で、ローカル行列やワールド行列はプログラムの途中において必要とされる場合がある。例えば、以下のプログラムでは、
Obj.thTransform.thLocalPosition = ...新しい値...;
Obj.thTransform.thLocalRotation = ...新しい値...;
// Matrix4x4 localMatrix = ... Objのローカル行列を取得 ...;
// Matrix4x4 worldMatrix = ... Objのワールド行列を取得 ...;
Obj.thTransform.UpdateChain();
1行目、2行目において
thLocalPosition、
thLocalRotation に新しい値をセットしているので、このオブジェクトは
m_UPDATE_LOCAL 及び
m_UPDATE_WORLD が
true となり、最後の
UpdateChain() の再帰処理の中でローカル行列やワールド行列も更新される。
しかし、7行目の
UpdateChain() の前の段階でローカル行列やワールド行列が必要になった場合にも、そこで取得されるローカル行列やワールド行列はその内容が最新のものでなければならない。例えば、上の4行目や5行目でローカル行列やワールド行列を取得する場合、取得される行列の内容は1行目、2行目の
thLocalPosition や
thLocalRotation への変更が反映されたものでなければならない。つまり、どの時点でローカル行列やワールド行列を取得する場合でも、その内容は最新のものでなければならない。
THTransformクラスの63行目から84行目にはローカル行列やワールド行列を取得するメソッドが定義されているが、それは以下のようなものである。
public Matrix4x4 GetLocalMatrix()
{
if (m_UPDATE_LOCAL)
{
m_localMatrix = CalcLocalMatrix();
m_UPDATE_LOCAL = false;
}
return m_localMatrix;
}
public Matrix4x4 GetWorldMatrix()
{
if (m_UPDATE_WORLD)
{
m_worldMatrix = CalcWorldMatrix();
m_UPDATE_WORLD = false;
}
return m_worldMatrix;
}
GetLocalMatrix()、
GetWorldMatrix() のいずれも、まず ローカル行列やワールド行列を更新する必要があるかどうかを調べ、その必要があれば更新する。したがって、これらのメソッドからは常に最新のローカル行列やワールド行列が返される。
CalcLocalMatrix()(67行目) や
CalcWorldMatrix()(79行目) は最新のローカル行列やワールド行列を計算するものであり、詳しい内容については後述するが、特に
GetWorldMatrix() 内からコールされる
CalcWorldMatrix() は その中で
GetLocalMatrix() を実行するので、結局
GetWorldMatrix() を実行すればワールド行列だけでなくローカル行列も更新されるのである。
THTransformクラスの
UpdateChain() は具体的には、次のように記述されている。
public void UpdateChain()
{
UpdateObject();
foreach(var child in m_children)
{
child.UpdateChain();
}
}
private void UpdateObject()
{
if (m_EXE_WORLD)
{
Matrix4x4 worldM = GetWorldMatrix();
m_object.SetMatrix(worldM);
m_EXE_WORLD = false;
}
}
UpdateChain() の定義自体は前節のものとほとんど同じである。前節において
UpdateMessage() となっていた部分が、ここでは
UpdateObject() となっている点のみが異なる (
UpdateObject()はわざわざメソッドとして独立させるほどの処理量ではないが、
UpdateChain()の内容を前節と同様のものにするために、ここではあえてメソッド化している)。
ローカル行列やワールド行列の更新、及びオブジェクトへの(最新の)ワールド行列の実行を実際に行うのが110行目の
UpdateObject() である。
UpdateObject() の内容自体は、ワールド行列を実行するかどうかを示すフラグ
m_EXE_WORLD が
trueであれば、ワールド行列を取得し、それをオブジェクトに実行するだけの処理である。
しかし、上でも述べたが
GetWorldMatrix() は常に最新のワールド行列を取得するメソッドであり、そのメソッドの中で、更新が必要なワールド行列及びローカル行列は更新されて最新のものになる (先程の例のようにプログラムの途中でワールド行列を取得する必要がある場合、
GetWorldMatrix()がコールされる。その際には
m_UPDATE_WORLDが
trueから
falseになるので、
UpdateObject()内の112行目の条件式は
m_UPDATE_WORLDではなく
m_EXE_WORLDでなければならない)。
UpdateChain() は毎フレーム プログラムの最後に一度だけ実行されることが前提であるが、このメソッドは一番上の階層の親オブジェクトから一番下の階層の子オブジェクトまで再帰的に実行されていく。
UpdateChain() の中では
UpdateObject() が実行されるので、階層構造に含まれる全てのオブジェクトには、必要であればローカル行列やワールド行列の更新が行われ、さらに更新されたワールド行列が実行されることになる (何も更新する必要がないオブジェクトの場合は
m_EXE_WORLDが
falseなので、そのようなオブジェクトに対しては何も行われない)。
今までに扱ってきたプログラムでは、オブジェクトに実行されるワールド行列の計算はプログラムの最後において次のように記述されていた (
Obj1~
Obj5のワールド行列算出コード。
Obj1が一番上の親オブジェクトであり、
Obj5が一番下の子オブジェクトである)。
Matrix4x4 worldObj5 = localObj1 * localObj2 * localObj3 * localObj4 * localObj5;
Matrix4x4 worldObj4 = localObj1 * localObj2 * localObj3 * localObj4;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;
そして、何度か指摘してきたことであるが、この記述には明らかに無駄な計算が含まれている。それを示すために、ワールド行列の算出順序を逆にして、ワールド行列の算出が一番上の親オブジェクトから先に始まるようにした場合を見てみよう。
Matrix4x4 worldObj1 = localObj1;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj4 = localObj1 * localObj2 * localObj3 * localObj4;
Matrix4x4 worldObj5 = localObj1 * localObj2 * localObj3 * localObj4 * localObj5;
ただ順序が入れ替わっただけであるが、この順序にすることで改善すべき点がより明らかになる。
例えば、2行目では
localObj1 * localObj2 という計算を行っているが、この計算は3行目以降でも行われている。3行目の
localObj1 * localObj2 * localObj3 という計算にしても同様で、この同じ計算が次の行でもその次の行でも行われている。これは明らかに無駄である。
しかし、この無駄な処理を改善することは容易である。次のように書き改めれば解決する。
Matrix4x4 worldObj1 = localObj1;
Matrix4x4 worldObj2 = worldObj1 * localObj2;
Matrix4x4 worldObj3 = worldObj2 * localObj3;
Matrix4x4 worldObj4 = worldObj3 * localObj4;
Matrix4x4 worldObj5 = worldObj4 * localObj5;
実際、このコードはワールド行列計算の改善版であり、書き改める前のコードと全く同じ内容である。
一番上の親オブジェクトのワールド行列は自身のローカル行列の内容をコピーするだけのものである (1行目)。子オブジェクトのワールド行列の場合は、自身のローカル行列に親オブジェクトのワールド行列を掛けたものがセットされる (2行目~5行目)。
THTransformクラスにおいて、実際にワールド行列の計算を行うのは以下の
CalcWorldMatrix() というメソッドである。このメソッドは
GetWorldMatrix() からのみコールされ、それ以外の箇所からはコールされない。
private Matrix4x4 CalcWorldMatrix()
{
Matrix4x4 localM = GetLocalMatrix();
Matrix4x4 worldM;
if (m_parent == null)
{
worldM = localM;
}
else
{
worldM = m_parent.GetWorldMatrix() * localM;
}
return worldM;
}
141行目では
GetLocalMatrix()をコールしているが、これによってその時点までのオブジェクトに対する変更がすべて反映されたローカル行列が返される。
144行目から151行目の処理は、上記の改善されたワールド行列計算コードと同様のものである。144行目の
ifブロックは階層構造における一番上の親オブジェクト用のワールド行列の計算であり(実際にはローカル行列をコピーするだけ)、148行目の
elseブロックはすべての子オブジェクト用のワールド行列の計算である。
この
CalcWorldMatrix()も
UpdateChain()と同様に再帰メソッドである (正確には
GetWorldMatrix()を経由する形で再帰メソッドになっている)。150行目において親オブジェクトのワールド行列を取得するために、親オブジェクトの
GetWorldMatrix()を実行しているが、もし親オブジェクトのワールド行列の更新が必要な場合には親オブジェクトの
GetWorldMatrix()の中で
CalcWorldMatrix()がコールされる。そして、その親オブジェクトが他のオブジェクトの子オブジェクトであれば、その親オブジェクトがさらに1つ上の親オブジェクトの
GetWorldMatrix() を実行することになる (上の階層のすべての親オブジェクトのワールド行列の更新が必要な場合は、一番上の親オブジェクトまで再帰的にこの過程が繰り返される)。
あるオブジェクトのワールド行列を計算するためには、上で見たように そのオブジェクトの親オブジェクトのワールド行列が必要になるが、親オブジェクトのワールド行列もその時点において最新のものでなければならない (つまり、親オブジェクトに対して行われた変更が反映されていなければならない)。
ワールド行列を計算する
CalcWorldMatrix()が上記のような再帰メソッドになっているので、あるオブジェクトが
GetWorldMatrix() を実行すれば、その中で(更新の必要がある場合に)
CalcWorldMatrix()がコールされ、そこから再帰的にそのオブジェクトより上の階層のすべての親オブジェクトのワールド行列が更新されるのである。
つまり、 プログラムのどの時点においても
GetWorldMatrix() を実行すれば、オブジェクトのその時点における最新のワールド行列が返されるのである。
また、実際に変化のあったオブジェクトだけはローカル行列も更新されるが、最新のローカル行列の計算は以下の
CalcLocalMatrix() において行われる (
CalcLocalMatrix()は
GetLocalMatrix()からのみコールされ、それ以外の箇所からはコールされない)。
private Matrix4x4 CalcLocalMatrix()
{
float tx = m_localPosition.x;
float ty = m_localPosition.y;
float tz = m_localPosition.z;
float sx = m_localScale.x;
float sy = m_localScale.y;
float sz = m_localScale.z;
Matrix4x4 r = m_localRotation;
Vector4 c1 = new Vector4(sx * r[0, 0], sx * r[1, 0], sx * r[2, 0], 0.0f); // 1列目
Vector4 c2 = new Vector4(sy * r[0, 1], sy * r[1, 1], sy * r[2, 1], 0.0f); // 2列目
Vector4 c3 = new Vector4(sz * r[0, 2], sz * r[1, 2], sz * r[2, 2], 0.0f); // 3列目
Vector4 c4 = new Vector4(tx, ty, tz, 1.0f); // 4列目
return new Matrix4x4(c1, c2, c3, c4);
}
CalcLocalMatrix() は3つのメンバ変数
m_localPosition、
m_localRotation、
m_localScale からローカル行列を作成するメソッドである。
Vector3型の
m_localPositionのx座標、y座標、z座標を $tx$、$ty$、$tz$ とし、それらを元にして作成された平行移動行列を $T$ とする。
Matrix4x4型の
m_localRotationは$4\times4$の回転行列であるが、これを $R$ で表す。また、
Vector3型の
m_localScaleのx座標、y座標、z座標を $s_x$、$s_y$、$s_z$ とし、それらを元にして作成されたスケール行列を $S$ とする。
\[T = \begin{pmatrix}1 &0 &0 &t_x \\0 &1 &0 &t_y \\ 0 &0 &1 &t_z \\0 &0 &0 &1\end{pmatrix} \qquad R = \begin{pmatrix}r_{00} &r_{01} &r_{02} &0 \\r_{10} &r_{11} &r_{12} &0 \\r_{20} &r_{21} &r_{22} &0 \\0 &0 &0 &1 \end{pmatrix} \qquad S = \begin{pmatrix}s_x &0 &0 &0 \\0 &s_y &0 &0 \\ 0 &0 &s_z &0 \\0 &0 &0 &1\end{pmatrix}\]
4-6節で計算したようにこの3つの変換行列の積は
\begin{align*}TRS &= \begin{pmatrix}1 &0 &0 &t_x \\0 &1 &0 &t_y \\ 0 &0 &1 &t_z \\0 &0 &0 &1\end{pmatrix}\begin{pmatrix}r_{00} &r_{01} &r_{02} &0 \\r_{10} &r_{11} &r_{12} &0 \\r_{20} &r_{21} &r_{22} &0 \\0 &0 &0 &1 \end{pmatrix}\begin{pmatrix}s_x &0 &0 &0 \\0 &s_y &0 &0 \\ 0 &0 &s_z &0 \\0 &0 &0 &1\end{pmatrix} \\\\&=\begin{pmatrix}s_x r_{00} &s_y r_{01} &s_z r_{02} &t_x \\s_x r_{10} &s_y r_{11} &s_z r_{12} &t_y \\s_x r_{20} &s_y r_{21} &s_z r_{22} &t_z \\0 &0 &0 &1 \end{pmatrix} \\\\\end{align*}
である。
CalcLocalMatrix() では、この計算結果の$4\times4$行列を作成しているのである (3つの変換行列$T$、$R$、$S$を作成して、それらの積を計算するよりも、その計算結果である$4\times4$行列を直接作成するほうがもちろん効率的である)。
130行目から133行目の
c1、
c2、
c3、
c4 は作成する$4\times4$行列の第1列目~第4列目である (行ではなく列であることに注意)。
Matrix4x4型のインスタンスを作成する際には、コンストラクタの1番目の引数から4番目の引数に、$4\times4$行列の第1列目~第4列目をセットする。
最後に
SetParent(THTransform p) (157行目)について触れておこう。
public void SetParent(THTransform p)
{
if(p == null)
{
if(m_parent != null)
{
m_parent.m_children.Remove(this);
m_parent = null;
}
return;
}
if(p == this)
{
Debug.Log("Error : 1 [THTransform_SetParent()]");
return;
}
if(m_parent != null)
{
Debug.Log("Error : 2 [THTransform_SetParent()]");
return;
}
THTransform w = p;
while (true)
{
if (w.m_parent == null) { break; }
if (w.m_parent == this)
{
Debug.Log("Error : 3 [THTransform_SetParent()]");
return;
}
w = w.m_parent;
}
m_parent = p;
m_parent.m_children.Add(this);
}
まず、170行目以降から見ていく。このメソッドでは親子関係の設定の際に、3つのチェックが入る。170行目の
ifブロックは第1のチェックであり、自分自身を親オブジェクトには設定できないようにするためのチェックである。
176行目の第2のチェックは、すでに親オブジェクトを持っている場合は新たに親オブジェクトを設定することはできないので、そのためのチェックである (新たに親オブジェクトを設定するためには一度既存の親子関係をリセットする必要がある)。
183行目の
while文のチェックは、自分の子オブジェクトを親オブジェクトにすることはできないので、そのためのチェックである。いわば、循環参照的な親子関係回避のためのチェックである。
196~197行目は親子関係の設定であり、このメソッドの冒頭の
ifブロック(159行目)はすでに設定されている親子関係をリセットする処理である (親子関係をリセットする際にはメソッドの引数に
nullをセットする)。