必要なヘッダは swizzle_vmath.hpp のみです。
また、一連の機能は SwizzleVmath という名前空間上に定義されています。
リンクが必要なコードは一切ありません。
つまり、ソース冒頭に
#include "swizzle_vmath.hpp"
using namespace SwizzleVmath ;
の 2 行を追加すれば、導入完了です。
-
ベクトルの宣言方法
ベクトルの宣言は、次のように行います。
N の位置には、次元数を書きます。次元数は、1 〜 4 が指定できます。
ベクトル宣言時に、ベクトルに保持させる値を指定できます。
以下のような、いくつかのスタイルが利用できます。
-
ベクトルの各成分を指定する
最大 4 つの成分を指定できます。
Vec4_t vecA(1,2,3,4);
Vec3_t vecB(1,2,3);
-
ベクトルの次元数と一致する配列を引数として与える
float paramA[4] = {1,2,3,4};
Vec4_t vecA(paramA);
float paramB[3] = {1,2,3};
Vec3_t vecB(paramB);
-
すでに作成されているベクトルからコピーする
-
ベクトル成分の参照方法
任意の成分を参照するには、次のように記述します。
x y z w の名前で参照するには、次のように記述します。
vecA.x()
vecA.y()
vecA.z()
vecA.w()
r g b a の名前で参照するには、次のように記述します。
vecA.r()
vecA.g()
vecA.b()
vecA.a()
いずれも、float 型の値と見なされます。
-
ベクトル同士の演算
例えば、ベクトル同士の加算は次のように記述します。
ベクトルの全成分に対する並列加算が実行され、
vecA には (6,8,10,12) が得られます。
Vec4_t vecB(1,2,3,4);
Vec4_t vecC(5,6,7,8);
Vec4_t vecA = vecB + vecC ;
同様に、並列減算、並列乗算、並列除算が記述できます。
vecA = vecB - vecC ;
vecA = vecB * vecC ;
vecA = vecB / vecC ;
-
符号反転
'-' を付けることで符号反転が可能です。
次の例では、vecA には (-1,-2,-3,-4) が得られます。
Vec4_t vecB(1,2,3,4);
Vec4_t vecA = -vecB ;
-
swizzle 指定方法
演算対象のメンバを swizzle 指定メソッドで指定可能です。
vecA.xyzw() 演算対象 = x y z w
vecA.rgba() 演算対象 = r g b a
vecA.xxyy() 演算対象 = x x y y
vecA.gb() 演算対象 = g b
swizzle 指定を行うとどのような解釈が行われるかの例を、次に示します。
Vec4_t vecA(1,2,3,4);
vecA.xyzw() => ベクトル(1,2,3,4) と見なされる。
vecA.rgba() => ベクトル(1,2,3,4) と見なされる。
vecA.xyz() => ベクトル(1,2,3) と見なされる。
vecA.xyxy() => ベクトル(1,2,1,2) と見なされる。
vecA.aa() => ベクトル(4,4) と見なされる。
vecA.x() => float 値 (1) と見なされる。
vecA.y() => float 値 (2) と見なされる。
vecA.z() => float 値 (3) と見なされる。
vecA.w() => float 値 (4) と見なされる。
vecA.r() => float 値 (1) と見なされる。
vecA.g() => float 値 (2) と見なされる。
vecA.b() => float 値 (3) と見なされる。
vecA.a() => float 値 (4) と見なされる。
具体的には、次のように用います。
Vec4_t vecB(1,2,3,4);
Vec4_t vecC(5,6,7,8);
Vec4_t vecA;
vecA.wzyx() = vecB.xyxy() + vecC.zwzw() ;
この記述は、
vecA.w() = vecB.x() + vecC.z() ;
vecA.z() = vecB.y() + vecC.w() ;
vecA.y() = vecB.x() + vecC.z() ;
vecA.x() = vecB.y() + vecC.w() ;
のように解釈されるので、vecA には (10,8,10,8) が得られます。
ここで注意が必要なのが、宣言時の次元数と、
swizzle 指定部の次元数は、
必ずしも一致させる必要はないということです。
例えば、次のように 4 次元ベクトルとして宣言しておいて、
2 次元ベクトルとして操作を行うことができます。
Vec4_t vecA(1,2,3,4);
Vec4_t vecB(5,6,7,8);
vecA.wx() = vecB.yz() ;
この記述は、
vecA.w() = vecB.y();
vecA.x() = vecB.z();
のように解釈されるので、vecA には (7,2,3,6) が得られます。
-
ブロードキャスト演算について
ベクトルの全フィールドに対して、
スカラ値との演算を行うことを、
ブロードキャスト演算と呼ぶことにします。
次の例は、ベクトルの全成分を 2 倍するブロードキャスト乗算です。
この記述は、
vecA.x() *= 2.0f ;
vecA.y() *= 2.0f ;
vecA.z() *= 2.0f ;
のように解釈されます。
同様に、ブロードキャスト除算が記述できます。
ブロードキャスト除算は、内部的には高速化のため、
引数の逆数をブロードキャスト乗算する仕様になっています。
よってこの記述は、
float Tmp = 1.0f / vecA.w() ;
vecA.x() *= Tmp ;
vecA.y() *= Tmp ;
vecA.z() *= Tmp ;
のように解釈されます。
-
行列の宣言方法
行列の宣言は、次のように行います。
N M の位置には、次元数を書きます。次元数は、1 〜 4 が指定できます。
行列宣言時に、行列に保持させる値を指定できます。
以下のような、いくつかのスタイルが利用できます。
-
行列の各成分を指定する
最大 16 個の成分を指定でき、
省略した部分には 0 を指定したものとみなされます。
Mat4x4_t matA(
1, 2, 3, 4
, 5, 6, 7, 8
, 9,10,11,12
, 13,14,15,16
);
4x4 以下の行列でも、次の様に 16 個のパラメータを要求します。
次の例では、
2x3 部分(1 〜 6 が指定されている部分)以外は無視されます。
Mat2x3_t matB(
1, 2, 3, 0
, 4, 5, 6, 0
, 0, 0, 0, 0
, 0, 0, 0, 0
);
-
行列の次元数と一致する配列を引数として与える
float paramA[4][4]
= {
{ 1, 2, 3, 4}
, { 5, 6, 7, 8}
, { 9,10,11,12}
, {13,14,15,16}
};
Mat4x4_t matA(paramA);
float paramB[2][3]
= {
{ 1, 2, 3}
, { 4, 5, 6}
};
Mat2x3_t matB(paramB);
-
すでに作成されている行列からコピーする
-
行列成分の参照方法
任意の成分を参照するには、次のように記述します。
指定の行を、行ベクトルとしてアクセスするには、
次のように記述します。
-
行列同士の演算
加算減算については、各成分に対する並列の演算となります。
Mat4x4_t matB(
1,1,1,1
, 1,1,1,1
, 1,1,1,1
, 1,1,1,1
);
Mat4x4_t matC(
2,2,2,2
, 2,2,2,2
, 2,2,2,2
, 2,2,2,2
);
Mat4x4_t matA = matB + matC ;
上記の例では、matA には、
3,3,3,3
,3,3,3,3
,3,3,3,3
,3,3,3,3
が得られます。
乗算については、行列の積が得られます。
例えば、Local → World 行列と、World → Screen 行列を合成して、
Local → Screen 行列を作成するには、次のように記述します。
matLocalToScreen = matLocalToWorld * matWorldToScreen ;
ベクトルを行ベクトルとして扱うので、掛け順序に注意してください。
なお、行列の除算は用意されていません。
-
行列とベクトルの演算
ベクトルを行列で変換することが出来ます。
例えば、Local → Screen 行列を使って、
Local 座標を Screen 座標に変換するには、
次のように記述します。
vecScreen = vecLocal * matLocalToScreen ;
ベクトルを行ベクトルとして扱うので、掛け順序に注意してください。
-
演算対象の行数列数の指定方法
演算対象の行数列数を指定することが可能です。
具体的には、'.m' + 行次元数 + 'x' + 列次元数 + '()' というメソッドで、
指定の行数列数の参照を取得します。
例えば、次のようにします。
matA.m4x4() 4x4 行列
matA.m3x2() 3x2 行列
matA.m1x4() 1x4 行列
ベクトルの場合と同様に、
行列の宣言時の次元数と、行数列数指定部の次元数は、
必ずしも一致させる必要はありません。
例えば次のように、4x4 行列として宣言しておき、
3x3 行列とみなして演算することが可能です。
Mat4x4_t matA( 省略 );
Vec4_t vecA( 省略 );
Vec4_t vecB( 省略 );
vecB.xyz() = vecA.xyz() * matA.m3x3() ;
このような演算は、4x4 行列で 3 次元の回転+平行移動を表現している場合に、
回転のみの変換結果を得たい場合に利用できます。
-
符号反転
'-' を付けることで符号反転が可能です。
Mat4x4_t matB(
1, 2, 3, 4
, 5, 6, 7, 8
, 9,10,11,12
, 13,14,15,16
);
Mat4x4_t matA = -matB ;
上記の例では、matA には、
-1, -2, -3, -4
, -5, -6, -7, -8
, -9,-10,-11,-12
,-13,-14,-15,-16
が得られます。
-
ブロードキャスト演算について
ベクトルと同様、行列の全要素に対して、
スカラ値との並列演算を行うような演算が可能です。
詳細は、ベクトルのブロードキャスト演算の項目を参照してください。
3D 分野で良く用いられる一般的な算術関数が用意されています。
-
Dot
内積の計算を行います。
Vec3_t vecA(1,0,0);
Vec3_t vecB(0,1,0);
float Result = Dot( vecA , vecB );
vecA と vecB は直交しているので、Result は 0 となります。
-
Cross
3 次元の外積の計算を行います。
Vec3_t vecB(1,0,0);
Vec3_t vecC(0,1,0);
Vec3_t vecA = Cross( vecB , vecC );
vecA は (0,0,1) となります。
-
Length2
各成分の 2 乗の和を求めます。
Vec4_t vecA(1,1,1,1);
float Result = Length2( vecA );
Result は 4 になります。
-
Length
各成分の 2 乗の和の平方根を求めます。
Vec4_t vecA(1,1,1,1);
float Result = Length( vecA );
Result は 2 になります。
-
Max
ベクトルの成分ごとの最大値を求めます。
Vec4_t vecB(1,2,3,4);
Vec4_t vecC(4,3,2,1);
Vec4_t vecA = Max( vecB , vecC );
vecA は (4,3,3,4) になります。
-
Min
ベクトルの成分ごとの最小値を求めます。
Vec4_t vecB(1,2,3,4);
Vec4_t vecC(4,3,2,1);
Vec4_t vecA = Min( vecB , vecC );
vecA は (1,2,2,1) になります。
-
Clamp
ベクトルの成分ごとのクランプ結果を求めます。
Vec4_t vecB(0,1,2,3);
Vec4_t vecMin(1,1,1,1); /* 成分ごとの下限 */
Vec4_t vecMax(2,2,2,2); /* 成分ごとの上限 */
Vec4_t vecA = Clamp( vecB , vecMin , vecMax );
vecA は (1,1,2,2) になります。
-
Abs
ベクトルの成分ごとの絶対値を求めます。
Vec4_t vecB(-1,0,1,2);
Vec4_t vecA = Abs( vecB );
vecA は (1,0,1,2) になります。
-
Lerp
ベクトルの線形補間を行います。
Vec4_t vecB(0,1,2,3);
Vec4_t vecC(0,3,6,9);
Vec4_t vecA = Lerp( vecB , vecC , 0.5f );
vecA は (0,2,4,6) になります。
-
Normalize
ベクトルの正規化を行います。
Vec3_t vecB(1,2,3);
Vec3_t vecA = Normalize( vecB );
vecA = vecA / Length( vecA );
と同じ効果が得られます。
-
Transpose
行列の転置を行います。
Mat4x4_t matA(
1, 2, 3, 4
, 5, 6, 7, 8
, 9,10,11,12
, 13,14,15,16
);
matA.m3x3() = Transpose( matA.m3x3() );
上記の例では、matA には、
1, 5, 9, 4
, 2, 6,10, 8
, 3, 7,11,12
,13,14,15,16
が得られます。
-
Inverse
転置と平行移動を用いた逆行列計算を行います。
Mat4x4_t matA(
1, 2, 3, 4
, 5, 6, 7, 8
, 9,10,11,12
, 13,14,15,16
);
matA = Inverse( matA );
上記の例では、matA には、
1, 5, 9, 0
, 2, 6,10, 0
, 3, 7,11, 0
,-86,-254,-422,1
が得られます。
本項目では、注意事項についてまとめます。
すでに触れた内容についても、改めてまとめます。
-
次元数に矛盾のある処理を記述してはならない
次元数に矛盾のある処理を記述しても、
コンパイル段階ではエラーとならず、
実行結果は不定となります。
(大抵は、矛盾のある要素を 0 に置き換えて処理が成されます。)
次元数に矛盾のある処理とは、例えば以下のようなものを指します。
-
次元数拡張
次の例では、3 次元ベクトルをコピーして、4 次元ベクトルを作成しています。
誤
Vec3_t vecA(1,2,3);
Vec4_t vecB(vacA);
正しく記述するには、次のようにします。
正
Vec3_t vecA(1,2,3);
Vec4_t vecB ;
vecB.xyz() = vecA.xyz();
vecB.w() = 0 ;
-
swizzle 指定による次元数拡張
3 次元ベクトルに対して、
4 次元ベクトルの swizzle 指定を行っています。
誤
Vec3_t vecA(1,2,3);
Vec4_t vecB = vecA.xyzw();
-
次元の一致しない演算(ベクトルの例)
誤
Vec4_t vecA(1,2,3,4);
Vec4_t vecB(5,6,7,8);
Vec4_t vecC ;
vecC.xyzw() = vecA.xyz() + vecB.xy();
-
次元の一致しない演算(行列の例)
誤
matC.m4x4() = matA.m2x4() * matB.m2x4();
-
次元の一致しない演算(演算関数の例)
Cross 関数は、3 次元ベクトルを入力として受け取り、
3 次元ベクトルを返すので、次のような記述は誤りです。
誤
Vec4_t vecA(1,2,3,4);
Vec4_t vecB(5,6,7,8);
Vec4_t vecC ;
vecC.xy() = Cross( vecA.xy() , vecB.xy() );
-
演算の優先度に配慮する
ベクトルと行列の関わる計算は、式の解釈順序により、
実行速度が大きく異なってくる場合があります。
非推奨
Vec4_t matA(省略);
Vec4_t matB(省略);
Vec4_t vecA(1,2,3,4);
Vec4_t vecB = vecA * matA * matB ;
推奨
Vec4_t matA(省略);
Vec4_t matB(省略);
Vec4_t vecA(1,2,3,4);
Vec4_t vecB = (vecA * matA) * matB ;
「非推奨」の側では、
乗算処理部にて vecB = vecA * (matA * matB) と解釈されているため、
演算コストは
- 行列 対 行列 の乗算 1 回
- ベクトル 対 行列 の乗算 1 回
であるのに対して、「推奨」の側では、
演算コストは
と、軽くなっています。
|