クォータニオンによる回転
今までの回転は Matrix.RotateX メソッドなどを使用して軸ごとに回転していましたが、Y や Z など、複数の軸が絡んでくると、思うように回転してくれないこともあります。
これらは「クォータニオン(Quaternion)」を使用することにより解決できる場合があります。また、前までの軸ごとの回転とは違った特別な計算、値を使用するので、マトリックスを使用しなくても回転を合成できたり、ジンバルロックの回避、球面補間など応用すればとても使いやすいものだと実感できると思います。
今回はその中の「回転の合成」をやります。実は回転の合成は、マトリックスでも同じように出来るのですが、メモリ、計算速度などの面でクォータニオンの方が有利です。回転はワールド座標の
XYZ 軸に対して回転していますが、各面を個別に計算する方法ではなく、「ヨー(Yaw)」「ピッチ(Pitch)」「ロール(Roll)」を使った方法でやってみたいと思います。

下のリンクから今回のプロジェクトをダウンロードできます。
今回のメインコードファイルを載せます。重要なコードを赤色で表示させています。
MainSample.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace MDXSample
{
/// <summary>
///
/// </summary>
public partial class MainSample : IDisposable
{
/// <summary>
///
/// </summary>
private Mesh _mesh = null;
/// <summary>
/// 回転記憶用クォータニオン
/// </summary>
private Quaternion _quaternion = Quaternion.Identity;
/// <summary>
///
/// </summary>
/// <param name="topLevelForm"></param>
/// <returns></returns>
/// <remarks>
///
/// </remarks>
public bool InitializeApplication(MainForm topLevelForm)
{
this._form = topLevelForm;
this.CreateInputEvent(topLevelForm);
try
{
this.CreateDevice(topLevelForm);
this.CreateFont();
}
catch (DirectXException ex)
{
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
this.CreateXYZLine();
this._mesh = Mesh.Teapot(this._device);
this.SettingLight();
return true;
}
/// <summary>
///
/// </summary>
public void MainLoop()
{
this.SettingCamera();
// ティーポットの回転操作
float yaw = 0.0f;
float pitch = 0.0f;
float roll = 0.0f;
if (this._keys[(int)Keys.Left])
{
// Yaw
yaw = 0.1f;
}
if (this._keys[(int)Keys.Right])
{
// -Yaw
yaw = -0.1f;
}
if (this._keys[(int)Keys.Down])
{
// -Pitch
pitch = -0.1f;
}
if (this._keys[(int)Keys.Up])
{
// Pitch
pitch = 0.1f;
}
if (this._keys[(int)Keys.A])
{
// Roll
roll = 0.1f;
}
if (this._keys[(int)Keys.Z])
{
// -Roll
roll = -0.1f;
}
this._quaternion *= Quaternion.RotationYawPitchRoll(yaw, pitch, roll);
this._device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);
this._device.BeginScene();
this._device.RenderState.Lighting = false;
this._device.SetTransform(TransformType.World, Matrix.Identity);
this.RenderXYZLine();
this._device.RenderState.Lighting = true;
Material mtrl = new Material();
mtrl.Diffuse = Color.SkyBlue;
mtrl.Ambient = Color.FromArgb(255, 128, 128, 128);
this._device.Material = mtrl;
// 座標変換
this._device.SetTransform(TransformType.World,
Matrix.RotationQuaternion(this._quaternion));
this._mesh.DrawSubset(0);
this._font.DrawText(null, "[Escape]終了", 0, 0, Color.White);
this._font.DrawText(null, "[↓]-Pitch [↑]+Pitch [←]+Yaw [→]-Yaw [A]+Roll [Z]-Roll",
0, 12, Color.White);
this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 24, Color.White);
this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 36, Color.White);
this._font.DrawText(null, "[quaternion]" + Environment.NewLine + this._quaternion,
0, 48, Color.White);
this._device.EndScene();
this._device.Present();
if (this._keys[(int)Keys.Escape])
{
this._form.Close();
}
}
/// <summary>
///
/// </summary>
public void Dispose()
{
if (this._mesh != null)
{
this._mesh.Dispose();
}
this.DisposeResource();
}
}
}
|
では、赤文字の部分を説明していきます。MainSamplePartial.cs ファイルのコードはこちらです。
/// <summary>
///
/// </summary>
private Quaternion _quaternion = Quaternion.Identity;
|
回転データを保持するために「Quaternion」を定義します。Matrix.RotateX などを使用していたときは float や Vector3 でもっている場合がありますが、今回は
Quaternion ひとつでいいです。これを「Quaternion.Identity」で初期化します。Quaternion.Identity は Matrix.Identity と同じように回転していない状態と同じです。
|
Quaternion.Identity
|
| X |
0 |
| Y |
0 |
| Z |
0 |
| W |
1 |
float yaw = 0.0f;
float pitch = 0.0f;
float roll = 0.0f;
if (this._keys[(int)Keys.Left])
{
yaw = 0.1f;
}
if (this._keys[(int)Keys.Right])
{
yaw = -0.1f;
}
if (this._keys[(int)Keys.Down])
{
pitch = -0.1f;
}
if (this._keys[(int)Keys.Up])
{
pitch = 0.1f;
}
if (this._keys[(int)Keys.A])
{
roll = 0.1f;
}
if (this._keys[(int)Keys.Z])
{
roll = -0.1f;
}
this._quaternion *= Quaternion.RotationYawPitchRoll(yaw, pitch, roll);
|
メインループのキー操作です。ここで「ヨー(Yaw)」「ピッチ(Pitch)」「ロール(Roll)」の値を操作します。そもそも、この3つはいったい何なのか、という話ですが、言葉で説明するのも面倒なので、下の図を見てください。

簡単な飛行物体をイメージしたものですが、図をみれば大体分かると思います。コードではこれらの値(Yaw, Pitch, Roll)を宣言しておいて、キーを押したときにどのくらい回転するかを代入しています。
その後、この3つの値を「Quaternion.RotationYawPitchRoll」メソッドにセットして回転クォータニオンを出力します。その値をフィールドの「quaternion」に掛け合わせて合成しています。これによって前回の回転から新しい回転を加える(合成)ことが出来ます。
今回使用したクォータニオンはあまり利便性は感じないように思えますが、クォータニオンならではの計算方法も用意されていますので、調べてみるのもいいでしょう。
this._device.SetTransform(TransformType.World,
Matrix.RotationQuaternion(this._quaternion));
|
クォータニオンをマトリックスに変換するのに「Matrix.RotationQuaternion」メソッドを使用しています。
|