カウンタ

  クォータニオンによる回転

Google
▲探し物はこちら

 今までの回転は Matrix.RotateX メソッドなどを使用して軸ごとに回転していましたが、Y や Z など、複数の軸が絡んでくると、思うように回転してくれないこともあります。

 これらは「クォータニオン(Quaternion)」を使用することにより解決できる場合があります。また、前までの軸ごとの回転とは違った特別な計算、値を使用するので、マトリックスを使用しなくても回転を合成できたり、ジンバルロックの回避球面補間など応用すればとても使いやすいものだと実感できると思います。

 今回はその中の「回転の合成」をやります。実は回転の合成は、マトリックスでも同じように出来るのですが、メモリ、計算速度などの面でクォータニオンの方が有利です。回転はワールド座標の XYZ 軸に対して回転していますが、各面を個別に計算する方法ではなく、「ヨー(Yaw)」「ピッチ(Pitch)」「ロール(Roll)」を使った方法でやってみたいと思います。

クォータニオンによる回転

 下のリンクから今回のプロジェクトをダウンロードできます。

ファイル名 言語 サイズ バージョン
quaternion_cs_1_1.zip C# 25KB 1.1
quaternion_vb_1_1.zip VB.NET 31KB 1.1
quaternion_cpp_1_1.zip C++/CLI 14KB 1.1

 今回のメインコードファイルを載せます。重要なコードを赤色で表示させています

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>全ての初期化がOKなら true, ひとつでも失敗したら false を返すようにする</returns>
        /// <remarks>
        /// false を返した場合は、自動的にアプリケーションが終了するようになっている
        /// </remarks>
        public bool InitializeApplication(MainForm topLevelForm)
        {
            // フォームの参照を保持
            this._form = topLevelForm;

            // 入力イベント作成
            this.CreateInputEvent(topLevelForm);

            try
            {
                // Direct3D デバイス作成
                this.CreateDevice(topLevelForm);

                // フォントの作成
                this.CreateFont();
            }
            catch (DirectXException ex)
            {
                // 例外発生
                MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            // XYZライン作成
            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);


            // 描画内容を単色でクリアし、Zバッファもクリア
            this._device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);

            // 「BeginScene」と「EndScene」の間に描画内容を記述する
            this._device.BeginScene();


            // ライトを無効
            this._device.RenderState.Lighting = false;

            // 原点に配置
            this._device.SetTransform(TransformType.World, Matrix.Identity);

            // XYZラインを描画
            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
    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);

 メインループのキー操作です。ここで「ヨー(Yaw)」「ピッチ(Pitch)」「ロール(Roll)」の値を操作します。そもそも、この3つはいったい何なのか、という話ですが、言葉で説明するのも面倒なので、下の図を見てください。

YawPitchRoll

 簡単な飛行物体をイメージしたものですが、図をみれば大体分かると思います。コードではこれらの値(Yaw, Pitch, Roll)を宣言しておいて、キーを押したときにどのくらい回転するかを代入しています。

 その後、この3つの値を「Quaternion.RotationYawPitchRoll」メソッドにセットして回転クォータニオンを出力します。その値をフィールドの「quaternion」に掛け合わせて合成しています。これによって前回の回転から新しい回転を加える(合成)ことが出来ます。

 今回使用したクォータニオンはあまり利便性は感じないように思えますが、クォータニオンならではの計算方法も用意されていますので、調べてみるのもいいでしょう。


// 座標変換
this._device.SetTransform(TransformType.World,
    Matrix.RotationQuaternion(this._quaternion));

 クォータニオンをマトリックスに変換するのに「Matrix.RotationQuaternion」メソッドを使用しています。

その他の関連情報です▼