Edited at
C++Day 2

Boost.QVM入門

Boost.QVMは行列計算のためのライブラリである. QVMはQuaternion, Vector, Matrixの略である. この記事では同ライブラリの使用方法を解説する入門記事である.

本記事では最初に主要なヘッダの概要を述べた上で, 具体的な使用例を例示する形で説明する, 所謂逆引きの形式を採る. これは, 利用したい機能がどのヘッダに定義されているかをファイル名から察する上で, 数学やコンピュータグラフィックスの知識, 文脈に対する理解が必要であり, 初心者に優しくないためである.

ヘッダのインクルードや関数の定義を省いた疑似コードがあるので注意.


主要なヘッダ

boost/qvm/は省略しているので注意.

ヘッダ
概要

vec
ベクトル型

vec_access
ベクトルの要素へのアクセス

vector_operations
ベクトル演算

mat
行列型の定義

mat_access
行列の要素へのアクセス

matrix_operations
行列演算

quat
クォータニオンの定義

quat_access
クォータニオンの要素へのアクセス

quaternion_operations
クォータニオン演算

swizzling
ベクトルや行列の拡張

Vector-to-Matrix View Proxies
ベクトルからの行列の作成

Matrix-to-Matrix View Proxies
行列からの行列の作成

Matrix-to-Vector View Proxies
行列からのベクトルの生成

Matrix-Vector Operations
ベクトルと行列との演算

Quaternion-Vector Operations
クォータニオンとベクトルとの演算

各種算術演算, ベクトルのクロス積, ドット積等はoperationsと名前の付くヘッダに定義されている.


ベクトルの定義と要素アクセス

#include <boost/qvm/vec.hpp>

#include <boost/qvm/vec_access.hpp>

#include <iostream>

int main()
{
boost::qvm::vec<float, 2> v;
boost::qvm::X(v) = 1.f;
boost::qvm::Y(v) = 2.f;
std::cout << "("
<< boost::qvm::X(v) << ", "
<< boost::qvm::Y(v) << ")" << std::endl;
}

クラステンプレートvecvec.hppに, 要素にアクセスする関数XYvec_access.hppに定義されている.

X, YA0, A1で各々代用する事が出来る. これらの関数はA9まで用意されている. また, n次元のベクトルにアクセスしたり, コンパイル時定数によってアクセスする次元を決定したい場合は関数テンプレートAの第1引数に対して次元を表す値を渡す.

これらの関数はオーバーロードされており, 基本的に引数がconst修飾されていれば戻り値は値型に, されていなければ参照型となる.


ベクトルの合成, スカラー積

#include <boost/qvm/vec.hpp>

#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/vec_operations.hpp>

#include <iostream>

int main()
{
using vec = boost::qvm::vec<float, 2>;

auto out = [](vec const& v)
{
std::cout << "("
<< boost::qvm::X(v) << ", "
<< boost::qvm::Y(v) << ")" << std::endl;
};

vec a, b;
boost::qvm::X(a) = 1;
boost::qvm::Y(a) = 2;
boost::qvm::X(b) = 3;
boost::qvm::Y(b) = 4;

out(a);
out(b);

out(-a);
out(a + b);
out(a - b);
out(a * 2);
out(2 * a);
out(a / 2);
}

各種ベクトル演算はvec_operations.hppに定義されている. この他にもドット積, クロス積, 標準化, 大きさの計算等が定義されている. 算術演算子だけでなく, 算術代入演算子も勿論定義されている.


行列

ベクトルと同様に, mat.hpp, mat_access.hpp, mat_operations.hppを使用する. ベクトルとは異なり, 要素アクセスを行う関数はA00, A01, ... A09, A10, A11, ... A99となる. 各値が行と列の次数に対応する. 関数テンプレートAを使用する場合, 次数の指定を第1テンプレート引数と第2テンプレート引数で行う.

#include <boost/qvm/mat.hpp>

#include <boost/qvm/mat_access.hpp>
#include <boost/qvm/mat_operations.hpp>

#include <iostream>

int main()
{
using mat = boost::qvm::mat<float, 2, 2>;

auto out = [](mat const& m)
{
std::cout << "(("
<< boost::qvm::A00(m) << ", "
<< boost::qvm::A01(m) << "), ("
<< boost::qvm::A10(m) << ", "
<< boost::qvm::A11(m) << "))" << std::endl;
};

mat a, b;
boost::qvm::A00(a) = 1;
boost::qvm::A01(a) = 2;
boost::qvm::A10(a) = 3;
boost::qvm::A11(a) = 4;

out(a);

b = 2 * a + a * 3 - a / 4;
out(b);

b += a;
out(b);
b -= a;
out(b);
b *= 2;
out(b);
b /= 3;
out(b);
}


行列演算によるベクトルの移動

#include <boost/qvm/map_vec_mat.hpp>

#include <boost/qvm/swizzle.hpp>
#include <boost/qvm/vec.hpp>
#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/vec_mat_operations.hpp>

int main()
{
using vec = boost::qvm::vec<float, 2>;
vec a, b;
boost::qvm::X(a) = 1;
boost::qvm::Y(a) = 2;
boost::qvm::X(b) = 3;
boost::qvm::Y(b) = 4;
b = boost::qvm::translation_mat(a) * boost::qvm::XY1(b);
}

移動を行う行列の生成にはtranslation_matを使用する. このようなベクトルから行列へのマッピングを行う関数テンプレートはmap_vec_mat.hppに定義されている. 同様に各種マッピングを行う関数テンプレートはmap_xxx.hppmap_xxx_yyy.hppに定義されている.

移動行列はn次元のベクトルに対して, n+1行n+1列の行列となるので, 実際に行列と演算するためには行列側を拡張する必要がある. ここでは2次元から3次元への拡張となるのでXY1を使用する. この関数テンプレートはswizzle.hppに定義されている. 3次元から4次元への拡張の場合XYZ1を使用する. このヘッダには他にもXY, X0, YX, XX等, 次元の縮小や, 要素の位置の変更, 0や1を代入したベクトルを得るための関数テンプレートが定義されている.

最終的にベクトルと行列との演算を行うにはvec_mat_operations.hppを使用する.


ベクトルのスケール

#include <boost/qvm/map_vec_mat.hpp>

#include <boost/qvm/vec.hpp>
#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/vec_mat_operations.hpp>

int main()
{
using vec = boost::qvm::vec<float, 2>;
vec a, b;
boost::qvm::X(a) = 1;
boost::qvm::Y(a) = 2;
boost::qvm::X(b) = 3;
boost::qvm::Y(b) = 4;
a = boost::qvm::diag_mat(a) * b;
}

ベクトルの各要素に対する乗算を行うには対角行列を使用する. 対角行列の生成にはdiag_matを使用する. 移動とは異なり, n次元のベクトルに対してn行n列となるため, swizzleは不要. 移動とスケールの両方を行う行列を作成する場合は事前にスケールを拡張してからdiag_matを使用することになるだろう.


2次元のベクトルの回転

#include <boost/qvm/mat_operations.hpp>

#include <boost/qvm/vec.hpp>
#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/vec_mat_operations.hpp>

int main()
{
boost::qvm::vec<float, 2> a;
boost::qvm::X(a) = 1;
boost::qvm::Y(a) = 2;
a = boost::qvm::rotz_mat<2>(1) * a;
}


クォータニオンによる3次元の回転

qvm/quat_operations.hppをインクルードし, クォータニオンの生成, 操作, 変換を行う.

#include <boost/math/constants/constants.hpp>

#include <boost/qvm/mat.hpp>
#include <boost/qvm/mat_operations.hpp>
#include <boost/qvm/quat.hpp>
#include <boost/qvm/quat_operations.hpp>
#include <boost/qvm/to_string.hpp>
#include <boost/qvm/vec.hpp>
#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/vec_mat_operations.hpp>
#include <boost/qvm/vec_operations.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/irange.hpp>

#include <iostream>

int main()
{
using vector_3 = boost::qvm::vec<float, 3>;
using quaternion = boost::qvm::quat<float>;
using matrix_3_x_3 = boost::qvm::mat<float, 3, 3>;
using matrix_4_x_4 = boost::qvm::mat<float, 4, 4>;

boost::for_each(boost::irange(0,25), [](int const i) {
using boost::qvm::convert_to;

auto const two_pi = boost::math::constants::two_pi<float>();

vector_3 axis;
X(axis) = 3;
Y(axis) = 4;
Z(axis) = 5;

auto const rot_quat = boost::qvm::rot_quat(axis, two_pi * i / 24);
auto const rot_mat_3_x_3 = convert_to<matrix_3_x_3>(rot_quat);
auto const rot_mat_4_x_4 = convert_to<matrix_4_x_4 >(rot_quat);

vector_3 position;
X(position) = 1;
Y(position) = 2;
Z(position) = 3;

std::cout
<< to_string(rot_quat) << '\n'
<< to_string(rot_mat_3_x_3) << '\n'
<< to_string(rot_mat_4_x_4) << '\n'
<< to_string(rot_mat_3_x_3 * position) << '\n'
<< std::endl;
} );
}

rot_quatを使用して生成したクォータニオンをconvert_toを用いて行列に変換し, operator *によって座標計算を行う. vec_mat_operations.hpp, quat_operations.hpp等のインクルード漏れに注意.

なんと, 2017-12-03現在, クォータニオンから行列への変換を行う関数テンプレートはドキュメントのバグにより公式のリファレンスに記載されていない.

その他のクォータニオンに対する操作はリファレンスを参照されたし.


その他

access, operations, swizzle, mapから概ねの要素が成り立っている事を理解すれば, あとはリファレンスを読めばBoost.QVMはほぼ使いこなせる.