X-FILE を入力して回転する

X-FILE を入力して、Quaternion の回転情報に従って、自作の Interpolator で回転します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. X-FILE を入力して、自作の Interpolator で回転します。
    DirectX 3D で Form を表示する に習って、空のプロジェクトから作成します。
    次のファイルを格納して、プロジェクトに取り込んで下さい。
    /****************************************************/
    /*★ Men4.x の Quaternion を使って回転    前田 稔 ★*/
    /****************************************************/
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using Microsoft.DirectX;
    using Microsoft.DirectX.Direct3D;
    using Direct3D=Microsoft.DirectX.Direct3D;
    using System.Collections;
    using System.IO;
    
    namespace Anime
    {
        public class Meshes : Form
        {
            Device device = null;       // Our rendering device
            Mesh mesh = null;           // Our mesh object in sysmem
            Direct3D.Material[] meshMaterials;      // Materials for our mesh
            Texture[] meshTextures;                 // Textures for our mesh
            PresentParameters presentParams = new PresentParameters();
            bool pause = false;
            InterRot    interrot;       //Interpolator の定義
            X_Loader    x_load;         //X-File Load Class の定義
    
            //回転情報の定義
            InterRotDat[] Rot;
            int     tim= 0;
            Matrix  mat;
    
            //Constructor
            public Meshes()
            {   // Set the initial size of our form
                this.ClientSize = new System.Drawing.Size(400, 400);
                this.Text = "Direct3D Teapot";
            }
    
            //D3DDevice の取得
            bool InitializeGraphics()
            {
                try
                {   presentParams.Windowed = true;
                    presentParams.SwapEffect = SwapEffect.Discard;
                    presentParams.EnableAutoDepthStencil = true;
                    presentParams.AutoDepthStencilFormat = DepthFormat.D16;
    
                    // Create the D3DDevice
                    device = new Device(0, DeviceType.Hardware, this,
                        CreateFlags.SoftwareVertexProcessing, presentParams);
                    device.DeviceReset += new System.EventHandler(this.OnResetDevice);
                    this.OnResetDevice(device, null);
                    pause = false;
                }
                catch (DirectXException)
                {   return false;  }
                return true;
            }
    
            //Direct3D の初期化
            public void OnResetDevice(object sender, EventArgs e)
            {
                ExtendedMaterial[] materials = null;
                Directory.SetCurrentDirectory(@"c:\data\xfile\");
    
                Device dev = (Device)sender;
                dev.RenderState.ZBufferEnable = true;
                dev.RenderState.Ambient = System.Drawing.Color.White;
                mesh = Mesh.FromFile("Men4Rot.x", MeshFlags.SystemMemory, device, out materials);
    
                meshMaterials = new Material[materials.Length];
                meshTextures = new Texture[materials.Length];
                for(int i = 0; i < meshMaterials.Length; i++)
                {   meshMaterials[i] = materials[i].Material3D;
                    meshMaterials[i].AmbientColor = meshMaterials[i].DiffuseColor;
                    if ((materials[i].TextureFilename != null) && (materials[i].TextureFilename.Length > 0))
                    {   meshTextures[i] = TextureLoader.FromFile(dev, materials[i].TextureFilename);  }
                }
    
                // 法線情報がなければ計算して作成
                if ((mesh.VertexFormat & VertexFormats.Normal) == 0)
                {   Mesh temporaryMesh = mesh.Clone(mesh.Options.Value,
                        mesh.VertexFormat | VertexFormats.Normal, device);
                    // 法線を計算
                    temporaryMesh.ComputeNormals();
                    // 古いメッシュを破棄し、置き換える
                    mesh.Dispose();
                    mesh = temporaryMesh;
                }
    
                // アニメーションデータの入力
                x_load = new X_Loader(@"c:\data\xfile\Men4Rot.x");
                Rot = x_load.LoadRot();
                // 回転 Interpolator の設定
                interrot = new InterRot(Rot);
            }
    
            //描画環境の設定
            void SetupMatrices()
            {
                //mat= interrot.GetMat(tim);
                mat= interrot.GetMat(Environment.TickCount*4);
                device.Transform.World = mat;
                device.Transform.View = Matrix.LookAtLH(new Vector3(0.0f,1.0f,-5.0f),
                    new Vector3(0.0f,0.0f,0.0f), new Vector3(0.0f,1.0f,0.0f));
                device.Transform.Projection = Matrix.PerspectiveFovLH((float)(Math.PI/4),
                    1.0f,1.0f,500.0f);
            }
    
            //ライトの設定
            private void SetupLights()
            {   System.Drawing.Color col = System.Drawing.Color.White;
                Direct3D.Material mtrl = new Direct3D.Material();
                mtrl.Diffuse = col;
                mtrl.Ambient = col;
                device.Material = mtrl;
                
                device.Lights[0].Type = LightType.Directional;
                device.Lights[0].Diffuse = System.Drawing.Color.FromArgb(0xF0F020);
                device.Lights[0].Direction = new Vector3(50.0f, -40.0f, 100.0f);
                device.Lights[0].Enabled = true;
                device.RenderState.Ambient = System.Drawing.Color.FromArgb(0x202020);
            }
    
            //モデルの描画
            private void Render()
            {   if (device == null) return;
                if (pause)          return;
    
                device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Gray, 1.0f, 0);
                device.BeginScene();
                SetupLights();
                SetupMatrices();
                for(int i=0; i<meshMaterials.Length; i++)
                {   device.Material = meshMaterials[i];
                    device.SetTexture(0, meshTextures[i]);
                    mesh.DrawSubset(i);
                }
                device.EndScene();
                device.Present();
            }
    
            //マウスのクリックを検出
            protected override void OnMouseDown(MouseEventArgs e)
            {   tim+= 20;  }        //アニメーションの間隔
    
            protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
            {   this.Render();  }
            protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
            {   if ((int)(byte)e.KeyChar == (int)System.Windows.Forms.Keys.Escape)
                    this.Dispose(); // Esc was pressed
            }
    
            protected override void OnResize(System.EventArgs e)
            {   pause = ((this.WindowState == FormWindowState.Minimized) || !this.Visible);  }
    
            //☆ Main() メソッド
            static void Main()
            {   using (Meshes frm = new Meshes())
                {   if (!frm.InitializeGraphics()) // Initialize Direct3D
                    {   MessageBox.Show("Could not initialize Direct3D.  This tutorial will exit.");
                        return;
                    }
                    frm.Show();
    
                    // While the form is still valid, render and process messages
                    while (frm.Created)
                    {   frm.Render();
                        Application.DoEvents();
                    }
                }
            }
        }
    }
    
    /*******************************************/
    /*★ Interpolator Class(回転)    前田 稔 ★*/
    /*******************************************/
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using Microsoft.DirectX;
    using Microsoft.DirectX.Direct3D;
    using Direct3D=Microsoft.DirectX.Direct3D;
    using System.Collections;
    
    namespace Anime
    {
        //Rotation Interpolator Data
        class InterRotDat
        {
            public int  Tim;
            public Quaternion Qt;
    
            //InterRotDat Constructor
            public InterRotDat(int tim, Quaternion qt)
            {   Tim= tim;
                //Qt= Quaternion.Normalize(qt);
                Qt= qt;
            }
            public void Print()
            {
                Console.WriteLine("Time={0}  {1},{2},{3},{4}", Tim, Qt.X, Qt.Y, Qt.Z, Qt.W);
            }
        }
    
        //☆Interpolator Class
        class InterRot
        {
            InterRotDat[]   Rot;
            int         Leng;
    
            //InterRot Constructor
            public InterRot(InterRotDat[] rot)
            {   Rot= rot;
                Leng= Rot.GetLength(0)-1;
            }
    
            //tim の Matrix を計算
            public Matrix GetMat(int tim)
            {   int     wk,i;
                float   rate;
                Quaternion wqt;
                wk= tim % Rot[Leng].Tim;
                for(i=0; i<Leng && Rot[i].Tim<wk; i++);
                if (Rot[i].Tim==wk) return Matrix.RotationQuaternion(Rot[i].Qt);
                rate= (float)(wk-Rot[i-1].Tim)/(float)(Rot[i].Tim-Rot[i-1].Tim);
                wqt = Quaternion.Slerp(Rot[i-1].Qt, Rot[i].Qt, rate);
                return Matrix.RotationQuaternion(wqt);
            }
        }
    }
    
    /**********************************************/
    /*★ X-FILE の回転情報を入力する    前田 稔 ★*/
    /**********************************************/
    using System;
    using System.IO;    // for File, StreamReader
    using System.Text;  // for Encoding
    using System.Windows.Forms;
    using System.Drawing;
    using Microsoft.DirectX;
    
    namespace Anime
    {
        //X-File Load Class
        class X_Loader : IDisposable
        {
            StreamReader reader;
            public string   BUF;        //文字列入力領域
            int             LT,RT;      //Index Left,Right
            InterRotDat[]   _Rot;       //回転情報の定義
    
            //Constructor
            public X_Loader(string x_file)
            {
                if (!File.Exists(x_file))
                {   MessageBox.Show("X-File が見つかりません",x_file);
                    return;
                }
                reader = new StreamReader(x_file,Encoding.GetEncoding("Shift_JIS"));
            }
    
            //Destructor
            ~X_Loader()
            {   Dispose();  }
    
            //Dispose() 関数
            public void  Dispose()
            {   reader.Close();
                GC.SuppressFinalize(this);
            }
    
            //回転アニメーションデータを入力
            public InterRotDat[] LoadRot()
            {   int     val=0,tim,i;
                float[] fval;
                Skip("AnimationSet ");
                while(Skip("AnimationKey "))
                {   NextRead();
                    val = Val();
                    if (val==0) break;
                }
                if (val!=0)
                {   MessageBox.Show("回転データが見つかりません");
                    return  null;
                }
    
                NextRead();
                val = Val();    //テーブルの件数
                _Rot= new InterRotDat[val];
                for(i=0; i<val; i++)
                {
                    NextRead();
                    tim = Val();    //Time を入力
                    fval= OneLine();
                    _Rot[i] = new InterRotDat(tim, new Quaternion(fval[0], fval[1], fval[2], fval[3]));
                }
                return _Rot;
            }
    
            //一件の回転データ(Quaternion)を設定
            public float[] OneLine()
            {   int     val,i;
                float[] fval;
    
                fval= new float[4];
                val = Val();        //個数を入力
                for(i=0; i<val; i++)
                {   fval[i] = FVal();   }
                return fval;
            }
    
            //次の有効な行を入力(/ はコメント行)
            public bool NextRead()
            {   int i;
                while((BUF=reader.ReadLine()) != null)
                {   for(i=0; i<BUF.Length && (BUF[i]==' ' || BUF[i]=='\t'); i++);
                    if (i<BUF.Length && BUF[i]!='/')
                    {
                        RT = 0;
                        return true;
                    }
                }
                return false;
            }
    
            //key の行まで読み飛ばす
            public bool Skip(string key)
            {
                while((BUF=reader.ReadLine()) != null)
                {   LT = BUF.IndexOf(key);
                    if (LT!=-1)
                    {   RT= LT+key.Length;
                        return true;
                    }
                }
                return false;
            }
    
            //BUF[RT] から次の int を取得
            public int Val()
            {
                string wk;
                while (true)
                {
                    for(LT=RT; LT<BUF.Length && BUF[LT]!='-' && (BUF[LT]<'0' || BUF[LT]>'9'); LT++);
                    if (LT<BUF.Length)
                    {
                        for (RT=LT+1; RT<BUF.Length && (BUF[RT]>='0' && BUF[RT]<='9'); RT++);
                        wk = BUF.Substring(LT, RT-LT);
                        return int.Parse(wk);
                    }
                    if (NextRead()==false)  return -1;
                }
            }
    
            //BUF[RT] から次の float を取得
            public float FVal()
            {
                string wk;
                while (true)
                {
                    for(LT=RT; LT<BUF.Length && BUF[LT]!='-' && (BUF[LT]<'0' || BUF[LT]>'9'); LT++);
                    if (LT<BUF.Length)
                    {
                        for(RT=LT+1; (RT<BUF.Length && (BUF[RT]>='0' && BUF[RT]<='9') || BUF[RT]=='.'); RT++);
                        wk = BUF.Substring(LT, RT-LT);
                        return float.Parse(wk);
                    }
                    if (NextRead()==false)  return -1.0f;
                }
            }
        }
    }
    
  2. このプログラムで描画する X-FILE は一体のモデルで、ボーンなどは設定されていません。
    今回はモデルの描画は DirectX に任せて、回転アニメーション情報のみ処理します。
    X-FILE のアニメーション情報は、モデルの定義が終わった後に「追加するような形」で記述されています。
    "AnimationSet " 以降にアニメーションの動作が記述されています。
    例えば、回転アニメーションの記述は次のようになっています。
    AnimationSet AnimationSet0 {
      Animation Animation0 {
        {Anim_MatrixFrame_Men4}
          AnimationKey {
            0;  // Rotation
            5;
            0;4; 0.00,  0.72,  0.00,  0.70;;
            2000;4; 0.00,  0.00,  0.00,  1.00;;
            4000;4; 0.00, -0.72,  0.00,  0.70;;
            6000;4; 0.00,  0.00,  0.00,  1.00;;
            8000;4; 0.00,  0.72,  0.00,  0.70;;
        }
      }
    }
    
    AnimationKey 以降が今回のプログラムに関係する部分です。
    0; が Quaternion を使った回転アニメーションで、今回処理するのは回転アニメーションだけです。
    5; が定義されている Quaternion の個数で、以降 Quaternion の記述が続きます。
    0,2000,4000,... は Time で、4; はパラメータの数(Quaternion は4)です。
    X-FILE の Quaternion は「Z,Y,X,W」の順に設定されているようです。
  3. X_Loader.cs が回転アニメーション情報を入力する X_Loader Object Class のファイルです。
    X_Loader の Constructor では X-File を確認して StreamReader を設定します。
        public X_Loader(string x_file)
        {
            if (!File.Exists(x_file))
            {   MessageBox.Show("X-File が見つかりません",x_file);
                return;
            }
            reader = new StreamReader(x_file,Encoding.GetEncoding("Shift_JIS"));
        }
        
  4. 実際に回転アニメーション情報を入力する関数は LoadRot() です。
    "AnimationSet " と "AnimationKey " をキーにして、回転情報を検索します。
    Quaternion の回転アニメーションが見つかったら InterRotDat[] _Rot; に格納します。
    詳細は X_Loader.cs のソースコードを参照して下さい。
  5. X_Loader.cs で使われている主要なメソッドです。
    1. public bool NextRead()
      次の行を BUF に入力する。
      / 以降の文字はコメントとみなして読み捨てる。
      有効な文字(空白,/,TAB以外)が格納されていることを確認する。
      一度に改行コードまで入力され、行末の改行コードは削除される。
      ファイルが終了すると false をリターン。
    2. public bool Skip(string key)
      key の行まで読み飛ばす。
      BUF には key を含む行が格納されている。
      ファイルが終了すると false をリターン。
    3. public int Val()
      BUF[RT] の位置から次の int を取得。
      LT に数字(及び -)の index を求め int に変換する。
      BUF の終端に達したときは、次の行を入力。
    4. public int FVal()
      BUF[RT] の位置から次の float を取得。
  6. X-FILE の描画は コーンのメッシュ(cone.x)を描画する を参照して下さい。
    このプログラム(RotAnime.cs)では @"c:\data\xfile\" に格納されている "Men4Rot.x" を入力しています。
    テスト用の X-FILE が必要なら Java のページから取得して下さい。
    Interpolator を使ったアニメーションは ティーポットを回転する を参照して下さい。

超初心者のプログラム入門(C# Frame Work)