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

$§$5-7 Transformクラスの実装 3


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_parentm_children は前節のRecursiveクラスのものと役割は同じである (ただし、ここでは m_parent 及び m_childrenの要素がTHTransform型になっている)。
3行目のTHObject3D型の m_object は変換の実行対象となるオブジェクトである。9行目、10行目の m_localMatrixm_worldMatrix はオブジェクトのローカル行列、及び ワールド行列を表している。
12行目、13行目の m_UPDATE_LOCALm_UPDATE_WORLD は今回のプログラムではオブジェクトのローカル行列及びワールド行列を更新するかを示すフラグとして使われる。18行目の m_EXE_WORLD はオブジェクトに対してワールド行列を実行するかどうかを示すフラグである。
あるフレームにおいてワールド行列が更新されれば、そのフレームにおいて、更新されたワールド行列をオブジェクトに実行するので m_UPDATE_WORLDtrueになる際には必ず m_EXE_WORLDtrueになる (89~90行目)。m_UPDATE_WORLDm_EXE_WORLD の2つに分けているのは、オブジェクトに対するワールド行列の実行はプログラムの一番最後に行われるが、ワールド行列の更新はプログラムの途中で必要になることがあるためである。この点については後述する。

5-2節で述べたように、オブジェクトのローカル行列を作成するためには、平行移動のための1つの Vector3インスタンスと、回転のための1つの Quaternionインスタンスと、スケールのための1つの Vector3インスタンスがあればよい。回転は Quaternion あるいは 行列で表すが、Quaternionによって回転データを保持していたとしても、いずれは回転行列に変換されるので、このTHTransformクラスでは Quaternionではなく、直接Matrix4x4を使っている。
5行目から7行目の m_localPositionm_localRotationm_localScale はローカル行列作成の際に必要となる、平行移動のための Vector3インスタンス、回転のための Matrix4x4インスタンス、スケールのための Vector3インスタンスである。
この3つのメンバ変数にはそれぞれプロパティが用意されており、外部から値の取得、あるいは更新することができる。具体的には26行目から60行目の部分で、実際に外部から値の取得、更新をする際には、接頭辞にthの付いているプロパティ名 thLocalPositionthLocalRotationthLocalScale を使用する。
前節ではRecursiveクラスの SomeChange() というメソッドが実行された際に、 オブジェクトに何らかの変化が発生したものとして処理を進めた。今回のTHTransformクラスの3つのプロパティ thLocalPositionthLocalRotationthLocalScale は実際にオブジェクトの変化につながるものである。
26行目から60行目の各プロパティの setブロックに記述されているように、thLocalPositionVector3型の値をセットすることは m_localPositionの値を更新することであり、thLocalRotationMatrix4x4型の値をセットすることは m_localRotationの値を更新することであり、thLocalScaleVector3型の値をセットすることは m_localScaleの値を更新することである。この3つのメンバ変数はローカル行列作成の際に必要となるので、3つのメンバ変数のいずれか1つに変化があれば、そのオブジェクトのローカル行列、ワールド行列の内容は更新しなければいけない。
すなわち、thLocalPositionthLocalRotationthLocalScale の値の更新は、オブジェクトに何らかの変化が発生したことを意味している。
前節において SomeChange() が実行された際には、オブジェクトに何らかの変化が発生したものとして、そのオブジェクトと そのオブジェクトのすべての子オブジェクトに対してメッセージ出力のためのフラグを立てた。
今回も事情はおなじである。thLocalPositionthLocalRotationthLocalScale のいずれかの値の更新が行われれば、NotifyChain()がコールされ、それによって再帰的に更新処理のためのフラグが立てられる。具体的には、変化のあったオブジェクトにはローカル行列及びワールド行列の更新、さらにワールド行列の実行が行われるようにm_UPDATE_LOCALm_UPDATE_WORLDm_EXE_WORLD の3つを trueにし、変化のあったオブジェクトのすべての子オブジェクトには、ワールド行列の更新とワールド行列の実行が行われるように m_UPDATE_WORLDm_EXE_WORLD の2つを trueにする。
以上に述べたことが、26行目から60行目の各プロパティの定義、及び 87行目の NotifyChain()の内容である (NotifyChain()m_EXE_WORLDtrueにする処理が追加されている点以外は、前節のRecursiveクラスのものとコードは同じである)。

前節のRecursiveクラスにおける実際の更新処理(メッセージ出力)では、一番上の階層の親オブジェクトが UpdateChain() を実行し、これが一番下の階層の子オブジェクトまで再帰的に実行された。UpdateChain() の中では m_UPDATE_LOCALm_UPDATE_WORLD といったフラグの立っているオブジェクトがメッセージを出力するだけであった。
今回の THTransformクラスにおける UpdateChain() も上の階層の親オブジェクトから下の階層の子オブジェクトへ再帰的に実行されていく点は同じであるが、その内容はただのメッセージ出力ではない。具体的には、ローカル行列及びワールド行列の更新、さらにオブジェクトの状態の更新である(つまり、オブジェクトに最新のワールド行列を実行する)。
前節のRecursiveクラスと同様にTHTransformクラスの UpdateChain() もプログラムの一番最後に実行されることが前提である。一方で、ローカル行列やワールド行列はプログラムの途中において必要とされる場合がある。例えば、以下のプログラムでは、
Obj.thTransform.thLocalPosition = ...新しい値...;
Obj.thTransform.thLocalRotation = ...新しい値...;

// Matrix4x4 localMatrix = ... Objのローカル行列を取得 ...;
// Matrix4x4 worldMatrix = ... Objのワールド行列を取得 ...;

Obj.thTransform.UpdateChain();

1行目、2行目において thLocalPositionthLocalRotation に新しい値をセットしているので、このオブジェクトは m_UPDATE_LOCAL 及び m_UPDATE_WORLDtrue となり、最後の UpdateChain() の再帰処理の中でローカル行列やワールド行列も更新される。
しかし、7行目の UpdateChain() の前の段階でローカル行列やワールド行列が必要になった場合にも、そこで取得されるローカル行列やワールド行列はその内容が最新のものでなければならない。例えば、上の4行目や5行目でローカル行列やワールド行列を取得する場合、取得される行列の内容は1行目、2行目の thLocalPositionthLocalRotation への変更が反映されたものでなければならない。つまり、どの時点でローカル行列やワールド行列を取得する場合でも、その内容は最新のものでなければならない。

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_WORLDtrueであれば、ワールド行列を取得し、それをオブジェクトに実行するだけの処理である。
しかし、上でも述べたが GetWorldMatrix() は常に最新のワールド行列を取得するメソッドであり、そのメソッドの中で、更新が必要なワールド行列及びローカル行列は更新されて最新のものになる (先程の例のようにプログラムの途中でワールド行列を取得する必要がある場合、GetWorldMatrix()がコールされる。その際にはm_UPDATE_WORLDtrueからfalseになるので、UpdateObject()内の112行目の条件式はm_UPDATE_WORLDではなくm_EXE_WORLDでなければならない)。
UpdateChain() は毎フレーム プログラムの最後に一度だけ実行されることが前提であるが、このメソッドは一番上の階層の親オブジェクトから一番下の階層の子オブジェクトまで再帰的に実行されていく。UpdateChain() の中では UpdateObject() が実行されるので、階層構造に含まれる全てのオブジェクトには、必要であればローカル行列やワールド行列の更新が行われ、さらに更新されたワールド行列が実行されることになる (何も更新する必要がないオブジェクトの場合はm_EXE_WORLDfalseなので、そのようなオブジェクトに対しては何も行われない)。


今までに扱ってきたプログラムでは、オブジェクトに実行されるワールド行列の計算はプログラムの最後において次のように記述されていた (Obj1Obj5のワールド行列算出コード。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_localPositionm_localRotationm_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行目の c1c2c3c4 は作成する$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をセットする)。













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