クォータニオンを使ったカメラの回転

#初めに
ゲームを作っているときに3D空間でカメラを自由に動かしたい！と思った人のとっかかりになるといいなと思って記事にしました。
触りの部分だけなのでクォータニオンについて詳しく知りたい方は、他の方が書いている記事を読んでいただけると助かります。

#クォータニオンとは
3D空間の物体は軸と角度があれば好きな方向に回転させることができます。
その物体を動かす際に使うのがクォータニオンです。
つまり、3D空間の物体を回転させるときに使う一つの数値です。

####計算方法
座標の求め方は、元の座標をp、計算後の座標をp'、回転させたいクォータニオンをq、qに対して共役なものをq*とすると

```math
p′=qpq^∗
```
という計算式で求められます。(q*はqの複素数部分をマイナスにしてあるだけです)

つまり、
1. クォータニオンの掛け算のやり方
2. クォータニオンの求め方
この2つがわかれば回転後の座標を求めることができます。


まずクォータニオンの掛け算の仕方です。
クォータニオンq1 = (w1,x1,y1,z1)、q2=(w2,x2,y2,z2)の掛け算q1q2は

```math
q_1q_2 = (w_1w_2-x_1x_2-y_1y_2-z_1z_2) \\
+ (w_1x_2+x_1w_2+y_1z_2-z_1y_2)i \\
+ (w_1y_2-x_1z_2+y_1w_2+z_1x_2)j \\
+ (w_1z_2+x_1y_2-y_1x_2+z_1w_2)k
```

次にクォータニオンの求め方です
クォータニオンは回転軸となる単位ベクトルv = (x,y,z)と動かす角度θがあれば求められます。

```math
q=cos\frac{θ}{2}+ixsin\frac{θ}{2}+jysin\frac{θ}{2}+kzsin\frac{θ}{2}
```

式の中にi,j,kが入っていますが使うことはないので気にしなくて大丈夫です。
角度はそのまま計算すると2倍の角度分動いてしまうので半分にしておきます。
#プログラム

```cpp
/**
 * @brief クォータニオン
 */
struct Quaternion
{
	float w;
	float x;
	float y;
	float z;
};

/**
 * @brief 3次元ベクトル
 */
struct Vector3
{
	float x;
	float y;
	float z;
	Vector3 operator*(const float num) {
		Vector3 ret;
		ret.x = x * num;
		ret.y = y * num;
		ret.z = z * num;
		return ret;
	}
};

/**
 * @brief   クォータニオン作成
 * @param   axis    回転させる軸
 * @param   radian  回転させる角度(ラジアン)
 * @return  作成したクォータニオン
 */
Quaternion MakeQuaternion(Vector3 axis, float radian)
{
	Quaternion quaternion;      //!< 作成するクォータニオン
	float halfSin, halfCos;      //!< 動かす角度の半分のsin,cos
	float normal;

	quaternion = { 0,0,0,0 };
	// 回転軸の長さを求める
	normal = axis.x * axis.x + axis.y * axis.y + axis.z * axis.z;
	if (normal <= 0.0f) return quaternion;

	// 方向ベクトルへ（単位ベクトル：長さは1）
	normal = 1.0f / sqrtf(normal);
	axis = axis * normal;


	halfSin = sinf(radian * 0.5f);
	halfCos = cosf(radian * 0.5f);

	quaternion.w = halfCos;
	quaternion.x = axis.x * halfSin;
	quaternion.y = axis.y * halfSin;
	quaternion.z = axis.z * halfSin;

	return quaternion;
}

/**
 * @brief   クォータニオンの掛け算
 * @param   left    計算の左の項
 * @param   right   計算の右の項
 * @return  計算したクォータニオン
 */
Quaternion CalcQuaternion(Quaternion left, Quaternion right)
{
	Quaternion quaternion;
	float   num1, num2, num3, num4;

	num1 =  left.w * right.w;
	num2 = -left.x * right.x;
	num3 = -left.y * right.y;
	num4 = -left.z * right.z;
	quaternion.w = num1 + num2 + num3 + num4;

	num1 =  left.w * right.x;
	num2 =  left.x * right.w;
	num3 =  left.y * right.z;
	num4 = -left.z * right.y;
	quaternion.x = num1 + num2 + num3 + num4;

	num1 =  left.w * right.y;
	num2 =  left.y * right.w;
	num3 =  left.z * right.x;
	num4 = -left.x * right.z;
	quaternion.y = num1 + num2 + num3 + num4;

	num1 =  left.w * right.z;
	num2 =  left.z * right.w;
	num3 =  left.x * right.y;
	num4 = -left.y * right.x;
	quaternion.z = num1 + num2 + num3 + num4;

	return   quaternion;
}

/**
 * @brief   クォータニオンによる回転
 * @param   axis    回転させたい軸
 * @param   pos     回転させるオブジェクトの座標
 * @param   radius  回転させる角度
 * @return  回転後の座標
 */
Vector3 RotateQuaternionPosition(Vector3 axis, Vector3 pos, float radius)
{
	Quaternion  complexNumber, complexConjugateNumber;
	Quaternion  posQuaternion = { 0, pos.x, pos.y, pos.z };
	Vector3     resultPosition;

	if (axis.x == 0 && axis.y == 0 && axis.z == 0 ||
		radius == 0) {
		return pos;
	}

	complexNumber = MakeQuaternion(axis, radius);
	complexConjugateNumber = MakeQuaternion(axis, -radius);

	posQuaternion = CalcQuaternion(complexNumber, posQuaternion);
	posQuaternion = CalcQuaternion(posQuaternion, complexConjugateNumber);

	resultPosition.x = posQuaternion.x;
	resultPosition.y = posQuaternion.y;
	resultPosition.z = posQuaternion.z;

	return resultPosition;
}

int main(void) 
{
	Vector3 cameraPos = { 1,0,0 };	//!< カメラの座標
	Vector3 cameraUp = { 0,1,0 };	//!< カメラのアップベクトル
	Vector3 axis = { 0,1,0 };		//!< 回転させる軸
	float rad = 90 * 3.14f / 180;	//!< 回転角度

	// 横移動
	cameraPos = RotateQuaternionPosition(axis, cameraPos, rad);

	// 縦移動
	axis.x = cameraPos.y * cameraUp.z - cameraPos.z * cameraUp.y;
	axis.y = cameraPos.z * cameraUp.x - cameraPos.x * cameraUp.z;
	axis.z = cameraPos.x * cameraUp.y - cameraPos.y * cameraUp.x;
	cameraPos = RotateQuaternionPosition(axis, cameraPos, rad);
	cameraUp  = RotateQuaternionPosition(axis,  cameraUp, rad);

	return 0;
}
```
カメラを縦方向に移動する場合は、座標とアップベクトルの外積を軸として回転させればいいと思います。

#最後に
実際にプログラムに組み込むのに必要な部分しか書いていません。
動かしながらの方が理解しやすいのでプログラムの部分のみに絞りました。（私がそこしかわかってない）
なので、プログラムでクォータニオンの動きを何となくわかってもらって、クォータニオンを触ってみよう！となってくれたら嬉しいです。
クォータニオンの詳細はたくさんの素晴らしいウェブページがあるので、そちらを拝見してください。

またね～