05.計算ライブラリ

 この項では、BaseCross64に内蔵されている計算ライブラリについて説明します。
 計算ライブラリといいましても、ライブラリ内で計算用のコードが実装されているのではなく、ほとんどのケースでDirectXMathの関数を呼び出しているものです。
 DirectXMathというのはXNAMathというXBox用の計算ライブラリの互換のある関数群で、WindowsとDirectXという環境で高速演算できる関数群です。ここではDirectXMath関数群の特徴と基本的な使い方を述べるとともに、BaseCross64とのインターフェイス実現のためにどのような実装がされていて、DirectXMath関数群を使うためにどのような使い方をするのかを述べたいと思います。

■計算ライブラリの役割■

 BaseCross64の、行列やベクトル計算はDirectXMathを使用しています。コンパイラオプションを/arch:AVXを追加することにより、64ビット環境でストリーミング SIMD 拡張命令 2 (いわゆるSSE2)による計算が実装できます(サンプルはすべてこのオプションがついています)。これは浮動小数点の計算が最適化されている、ということです。3Dプログラムは、複雑な浮動小数点計算が大量に必要なプログラムです。ですから、こういったハードウェア依存の計算は、できれば実装したほうが良い機能でしょう。
 BaseCross64では、DirectXMathを利用するにあったっての手間を、ゲームプログラマに、なるべく負担を掛けないように、bsm::Vec3bsm::Mat4x4などの計算クラスがあります。
 以下、bsm::Quat構造体の宣言ですが
namespace basecross {
namespace bsm {
//中略
    //--------------------------------------------------------------------------------------
    /// Quat(クオータニオン)
    //--------------------------------------------------------------------------------------
    struct Quat : public XMFLOAT4
    {
        //--------------------------------------------------------------------------------------
        /*!
        @brief  コンストラクタ
        */
        //--------------------------------------------------------------------------------------
        inline Quat();
//中略
        //--------------------------------------------------------------------------------------
        /*!
        @brief  コンストラクタ
        @param[in]  v   XMFLOAT3構造体
        */
        //--------------------------------------------------------------------------------------
        explicit inline Quat(const XMFLOAT4& v);
//中略
        //--------------------------------------------------------------------------------------
        /*!
        @brief  コンストラクタ(XMVECTORで初期化)
        @param[in]  vec 値
        */
        //--------------------------------------------------------------------------------------
        explicit inline Quat(const XMVECTOR& other);
        //--------------------------------------------------------------------------------------
        /*!
        @brief  XMVECTORへのキャスト(thisをキャストして返す)
        @return XMVECTOR型の値
        */
        //--------------------------------------------------------------------------------------
        inline operator XMVECTOR() const;
//中略
        //--------------------------------------------------------------------------------------
        /*!
        @brief  XMFLOAT4からの代入
        @param[in]  other   相手
        @return *thisの参照
        */
        //--------------------------------------------------------------------------------------
        inline Quat& operator=(const XMFLOAT4& other);
        //--------------------------------------------------------------------------------------
        /*!
        @brief  XMVECTORからの代入
        @param[in]  other   相手
        @return *thisの参照
        */
        //--------------------------------------------------------------------------------------
        inline Quat& operator=(const XMVECTOR& other);
//中略
    };
}
//end bsm
}
//end basecross
 こんな感じで宣言されています。namespaceのbsmBaSecrossMathの略です。関数名が単純なので、他クラスと混同がないようにbsmネームスペースに入ってます。ただし、サンプルはあらかじめusing句によってbsmが指定されてますので省略されています。
 このように、bsm::QuatXMFLOAT4の派生クラスとして作成したうえで
1、XMFLOAT4を引数としたコンストラクタ
2、XMVECTORを引数としたコンストラクタ
3、XMFLOAT4を引数としたコピー
4、XMVECTORを引数としたコピー
5、XMVECTORへのキャスト演算子の多重定義
 を作成し、その中でロード関数とストア関数が実装されています。
 以下、上記の実体です。
namespace basecross {
namespace bsm {
//中略
    //--------------------------------------------------------------------------------------
    /// Quatインライン関数
    //--------------------------------------------------------------------------------------
    inline Quat::Quat():
        XMFLOAT4()
    {
        identity();
    }

//中略

    inline Quat::Quat(const XMFLOAT4& v) :
        XMFLOAT4(v)
    {
    }

//中略

    inline Quat::Quat(const XMVECTOR& other) :
        XMFLOAT4() 
    {
        XMVECTOR temp = other;
        XMStoreFloat4((XMFLOAT4*)this, temp);
    }

    inline Quat::operator XMVECTOR() const {
        XMFLOAT4 temp = *this;
        XMVECTOR Vec = XMLoadFloat4(&temp);
        return Vec;
    }


    inline Quat& Quat::operator=(const XMFLOAT4& other) {
        (XMFLOAT4)*this = other;
        return *this;
    }


    inline Quat& Quat::operator=(const XMVECTOR& other) {
        XMVECTOR temp = other;
        XMStoreFloat4((XMFLOAT4*)this, temp);
        return *this;
    }
}
//end bsm
}
//end basecross
 このようにロードとストアが実装されています。ですので、
        bsm::Quat Qt;

        //Qtに対する何らかの操作...

        //正規化
        Qt = XMQuaternionNormalize(Qt);

 のように記述できます。

計算クラスの種類

 計算ライブラリは、実装はbasecross::bsmというネームスペースに実装があります。DxLibプロジェクトのBaseMath.hおよびBaseMathInc.hが実装ファイルです。
 以下のような種類があります。ちょっと気を付けたいのは、Vec2やVec3などは、Flt2やFlt3の別名として実装されているところです。
型名 用途 親クラス 備考
Vec2 2次元ベクトル XMFLOAT2 Flt2の別名
Vec3 3次元ベクトル XMFLOAT3 Flt3の別名
Vec4 4次元ベクトル XMFLOAT4 Flt4の別名
Pt2 2次元ポイント XMFLOAT2 Flt2の別名
Pt3 3次元ポイント XMFLOAT3 Flt3の別名
Col4 RGBAカラー XMFLOAT4 Flt4の別名
Plane4 平面方程式 XMFLOAT4 Flt4の別名
Quat クォータニオン XMFLOAT4 オリジナル名
Mat3x3 3x3行列 XMFLOAT3X3 オリジナル名
Mat4x4 4x4行列 XMFLOAT4X4 オリジナル名

 この表で、例えばVec3Flt3の別名で、Flt3XMFLOAT3の派生クラスとして実装されています。
 float型の変数が3つある型が、まず、Flt3型として実装されていて、その別名としてVec3およびPt3があるという形になります。つまりVec3とPt3は、名前こそ違いますが、内容は全く同じということです。便宜上、ベクトルあるいはポイントといういい方をしたほうが扱いやすい場合は多々あります。
 float型の変数が4つある型Flt4型として実装されていて、その別名としてVec4、Col4、Plane4があるとなってます。クォータニオンだけ特別で直接XMFLOAT4の派生となっています。
 Mat3x3とMat4x4は行列です。(4x3行列の機能はMat4x4で代用できると思います)。

計算クラスの実装

 このように、計算クラスはDirectXMath関数をより使いやすくするためにありますが、あらかじめラッピング実装されている関数があります。正規化、単位行列(クォータニオン)、行列転置、逆行列などです。
 以下に実装済みの関数の使用方法を紹介します。SimplSample021Player.cppにありますPlayer::GetMoveVector()関数です。
Vec3 Player::GetMoveVector() const {
    Vec3 Angle(0, 0, 0);
    auto PtrGameStage = GetStage<GameStage>();
    Vec3 CameraEye, CameraAt;
    PtrGameStage->GetCamera().GetCameraEyeAt(CameraEye, CameraAt);

    //コントローラの取得
    auto CntlVec = App::GetApp()->GetInputDevice().GetControlerVec();
    if (CntlVec[0].bConnected) {
        if (CntlVec[0].fThumbLX != 0 || CntlVec[0].fThumbLY != 0) {
            float MoveLength = 0;   //動いた時のスピード
            //進行方向の向きを計算
            Vec3 Front = m_Pos - CameraEye;
            Front.y = 0;
            Front.normalize();
            //進行方向向きからの角度を算出
            float FrontAngle = atan2(Front.z, Front.x);
            //コントローラの向き計算
            float MoveX = CntlVec[0].fThumbLX;
            float MoveZ = CntlVec[0].fThumbLY;
            Vec2 MoveVec(MoveX, MoveZ);
            float MoveSize = length(MoveVec);
            //コントローラの向きから角度を計算
            float CntlAngle = atan2(-MoveX, MoveZ);
            //トータルの角度を算出
            float TotalAngle = FrontAngle + CntlAngle;
            //角度からベクトルを作成
            Angle = Vec3(cos(TotalAngle), 0, sin(TotalAngle));
            //正規化する
            Angle.normalize();
            //移動サイズを設定。
            Angle *= MoveSize;
            //Y軸は変化させない
            Angle.y = 0;
        }
    }
    return Angle;
}
 赤くなっているところが、ライブラリでの実装を使用しているところです。例えば
        //進行方向の向きを計算
        Vec3 Front = m_Pos - CameraEye;
 はベクトル同士の引き算ですが、実体はFlt3型として実装され
    inline const Flt3 Flt3::operator -(const Flt3 & vec) const
    {
        return (Flt3)XMVectorSubtract(*this, vec);
    }
 のように実装されています。単なる引き算であっても、XMVectorSubtract()関数というDirectXMath関数を使用する形で実装されています。
 また、ちょっと気を付けたいのは
    float MoveSize = length(MoveVec);
 ここでlength()という関数は構造体の外部で実装されている
    inline float length(const Flt3 & vec)
    {
        return ((Flt3)XMVector3Length(vec)).x;
    }
 という関数が呼ばれます。normalize(正規化を返す)、length(長さを返す)、lengthSqr(長さの2乗を返す)、dot(内積を返す)、cross(外積を返す。Flt3のみ)などの外部関数があります。
 そのほか、詳しくはDxLibプロジェクトのBaseMath.hおよびBaseMathInc.hを参照してください。