200.計算ライブラリ
この項では、
BaseCrossに内蔵されている
計算ライブラリについて説明します。
計算ライブラリといいましても、ライブラリ内で計算用のコードが実装されているのではなく、ほとんどのケースで
DirectXMathの関数を呼び出しているものです。
DirectXMathというのは
XNAMathという
XBox用の計算ライブラリの互換のある関数群で、
WindowsとDirectXという環境で高速演算できる関数群です。ここでは
DirectXMath関数群の特徴と基本的な使い方を述べるとともに、
BaseCrossとのインターフェイス実現のためにどのような実装がされていて、
DirectXMath関数群を使うためにどのような使い方をするのかを述べたいと思います。
■計算ライブラリの役割■
BaseCrossの、行列やベクトル計算は
DirectXMathを使用しています。これにより、
ストリーミング SIMD 拡張命令 2 (いわゆるSSE2)による計算が実装できます。これは
浮動小数点の計算が最適化されている、ということです。3Dプログラムは、複雑な浮動小数点計算が大量に必要なプログラムです。ですから、こういった
ハードウェア依存の計算は、できれば実装したほうが良い機能でしょう。
BaseCrossでは、
DirectXMathを利用するにあったっての手間を、ゲームプログラマに、なるべく負担を掛けないように、
bsm::Vec3や
bsm::Mat4x4などの計算クラスがあります。こういった計算クラスがある理由は、
DirectXMathの利用にあたって
ややこしい部分があるからです。
まずは、
DirectXMathの利用の、どこがややこしいのかを説明します、
DirectXMathの関数群は
XMMatrixAffineTransformation()のように
XMで始まる関数名を持ちます。
ほとんど(というか、ほぼ全部)、
XMVECTORもしくは
XMMATRIXという型の変数を返す、あるいは引数に必要とします。これらの型は
SSE2とのデータのやり取りに使用します。
この
XMVECTORと
XMMATRIXの2つの型が曲者です。
基本的な情報 - MSDN - Microsoftというマイクロソフトのページに、その要件について記されています)。(かなり下のほうです)
XMVECTORと
XMMATRIXについての内容をまとめると以下のような特徴があります。
1、これらの変数は、16 バイトのアライメント、が必要。
2、スタック上の場合は問題ない
3、データセグメントの場合は問題ない
4、ヒープ領域で、Xbox 360 および Windows x64では問題ない
5、ヒープ領域で、Windows x86 では 8 バイトアラインメントなので問題になる
ここで
5のケースがまさに
BaseCrossによくある環境です。ならば
Windows x64にすればいいだろうと思うかもしれませんが、
Windows x64の場合、size_tの大きさの違いなど、x64の環境に合わせなければいけないので、将来的にはそうすべきですが、現状、
Windows x86を使用しています。
ここで
スタック、データセグメント、ヒープ領域の意味が分からない方のために説明しますと、まず
スタック領域に変数が展開される例は、
関数の内部変数や、呼び出しの引数です。
例えば以下の例は、わかりやすい
スタック上の変数です。
void test(){
//単位クォータニオンの取得
XMVECTOR q = XMQuaternionIdentity();
//単位行列の取得
XMMATRIX m = XMMatrixIdentity()
}
これは、関数内のローカル変数に代入しています。この場合は問題ないということです。
データセグメントは、
グローバル変数がそうです。
XMVECTOR q;
XMMATRIX m;
void test(){
//単位クォータニオンの取得
q = XMQuaternionIdentity();
//単位行列の取得
m = XMMatrixIdentity()
}
これも問題がありません。しかし、以下が
ヒープ領域です。
struct teststr{
XMVECTOR q;
XMMATRIX m;
};
void test(){
//以下は実行できない
auto t = new teststr();
//単位クォータニオンの取得
t->q = XMQuaternionIdentity();
//単位行列の取得
t->m = XMMatrixIdentity()
}
この実行ができないのです。エラーをなくすためには
new演算子を多重定義する必要がありますが、これは負担の重いことです。
解決策として、上記マイクロソフトサイトでは
XMFLOAT3、XMFLOAT4、XMFLOAT4X3、XMFLOAT4X4を使用すべき、という記述があります。これはどういいうことかというと、実際にコード化してみます。
struct teststr{
XMFLOAT4 q;
XMFLOAT4X4 m;
};
void test(){
//以下は実行できる
auto t = new teststr();
//
//ここでtの変数を何らかの形で設定
//
//変数のXMVECTORへのロード
XMVECTOR q = XMLoadFloat4(&t->q);
//クォータニオンの正規化
q = XMQuaternionNormalize(q);
//tの変数に戻す
XMStoreFloat4&t->q,q);
//変数のXMMATRIXへのロード
XMMATRIX m = XMLoadFloat4x4A(&t->m);
//行列の転置
m = XMMatrixTranspose(m);
//tの変数に戻す
XMStoreFloat4x4(&t->m,m);
}
この処理で
newで作成したtの変数を計算することができます。
これで、めでたしめでたし、のようですが、実際にプログラムを組んでみると
常にロードトストアをしないといけないという現実にぶち当たります。これでは意味がありません。
それで、
bsm::Mat4x4、bsm::Quat、bsm::Vec3などの構造体があります。以下、
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の
bsmは
BaSecrossMathの略です。関数名が単純なので、他クラスと混同がないように
bsmネームスペースに入ってます。ただし、サンプルはあらかじめ
using句によって
bsmが指定されてますので省略されています。
このように、
bsm::Quatは
XMFLOAT4の派生クラスとして作成したうえで
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);
のように記述できます。上記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 |
オリジナル名 |
この表で、例えば
Vec3は
Flt3の別名で、
Flt3は
XMFLOAT3の派生クラスとして実装されています。
float型の変数が3つある型が、まず、
Flt3型として実装されていて、その
別名として
Vec3およびPt3があるという形になります。つまり
Vec3とPt3は、名前こそ違いますが、内容は全く同じということです。便宜上、
ベクトルあるいは
ポイントといういい方をしたほうが扱いやすい場合は多々あります。
float型の変数が4つある型も
Flt4型として実装されていて、その
別名として
Vec4、Col4、Plane4があるとなってます。クォータニオンだけ特別で
直接XMFLOAT4の派生となっています。
Mat3x3とMat4x4は行列です。(4x3行列の機能はMat4x4で代用できると思います)。
計算クラスの実装
このように、計算クラスは
DirectXMath関数をより使いやすくするためにありますが、あらかじめラッピング実装されている関数があります。
正規化、単位行列(クォータニオン)、行列転置、逆行列などです。
以下に実装済みの関数の使用方法を紹介します。
SimplSample021の
Player.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 Flt2 & vec)
{
return ((Flt2)XMVector2Length(vec)).x;
}
という関数が呼ばれます。
normalize(正規化を返す)、length(長さを返す)、lengthSqr(長さの2乗を返す)、dot(内積を返す)、cross(外積を返す。Flt3のみ)などの外部関数があります。
そのほか、詳しくは
DxLibプロジェクトの
BaseMath.hおよび
BaseMathInc.hを参照してください。