そういえば、PS4の『Destiny』のベータテスト、始まってましたな。
いま、PS4つけてダウンロード中です。約13G。
明日は定休日だし、ちょっと遊んでみようと思う。
さて、以前、C++で解析した3DアニメーションのロジックをC#に移植している。
せっかく学んだことの理解を深めるという意味合いと、C#での3Dアニメーションのグラフィックスパイプラインを押さえておこうと。
なので、ボーンの表示とFKによるアニメーションまでできたら満足して終わりそう。
IKやスキンメッシュの移植は、余力があれば挑戦するかも。
今日は、VMDデータの読み込みまでやってみた。
データフォーマットはこちらのサイトを参考にさせていただいた。
ベジエ補完を実現するクラスがC#に用意されてなさそうなのな。
結局、C++のソースを流用して作った。
C#でのVMDデータの読み込みソースはこんな感じになる。
エラー処理とかは割愛。データも構造体にせず、VMDフォーマットに近い形で持つようにした。
キーフレームは、ボーンごとの配列にまとめてある。
(ほんとはさらにフレーム番号でソートかけないとダメなんだけどめんどくさ自明なので割愛)
using System; using System.Collections.Generic; using System.Text; using System.IO; class VmdData { //ボーン名に対応するインデックスの辞書 Dictionary<string, int> boneDictionary; //VMDヘッダ情報 public string vmdHead; //モデル名 public string modelName; //フレーム数 UInt32 frameCount; //キーフレーム配列をボーン毎にまとめた配列 List<List<VmdFrame>> keyFrames = new List<List<VmdFrame>>(); /// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /// 指定したファイルパスのVMDファイルを読込む /// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- public static VmdData load(string filePath) { //インデックス int bonIndex = 0; //辞書にない場合、このインデックスから辞書に追加していく int lastBoneIndex = 0; VmdData vmdData = new VmdData(); vmdData.boneDictionary = new Dictionary<string, int>(); FileStream stream = File.Open(filePath, FileMode.Open); //先頭から stream.Seek(0, SeekOrigin.Begin); //バイナリ形式 BinaryReader reader = new BinaryReader(stream); // 30 bytes char[30] "Vocaloid Motion Data 0002\0" の文字列 vmdData.vmdHead = Encoding.Default.GetString(reader.ReadBytes(30)); // 20 bytes char[20] "初音ミク\0"などのモデル名の文字列 vmdData.modelName = Encoding.Default.GetString(reader.ReadBytes(20)); // 4 bytes unsigned long フレームデータ数 UInt32 frameCount = reader.ReadUInt32(); vmdData.frameCount = frameCount; for (int index = 0; index < frameCount; index++) { VmdFrame keyFrame = new VmdFrame(); // 15 bytes char[15] "頭\0"などのボーン名の文字列 keyFrame.boneName = Encoding.Default.GetString(reader.ReadBytes(15)); // 4 bytes unsigned long フレーム番号 keyFrame.frameNo = reader.ReadUInt32(); // 4 bytes float ボーンのX軸位置。位置データがない場合は0 keyFrame.localPosX = reader.ReadSingle(); // 4 bytes float ボーンのX軸位置。位置データがない場合は0 keyFrame.localPosY = reader.ReadSingle(); // 4 bytes float ボーンのX軸位置。位置データがない場合は0 keyFrame.localPosZ = reader.ReadSingle(); // 4 bytes float ボーンのクォータニオンのX。データがない場合は0 keyFrame.localRotX = reader.ReadSingle(); // 4 bytes float ボーンのクォータニオンのX。データがない場合は0 keyFrame.localRotY = reader.ReadSingle(); // 4 bytes float ボーンのクォータニオンのX。データがない場合は0 keyFrame.localRotZ = reader.ReadSingle(); // 4 bytes float ボーンのクォータニオンのX。データがない場合は0 keyFrame.localRotW = reader.ReadSingle(); // 64 bytes char[64] 補間パラメータ byte[] parameters = reader.ReadBytes(64); keyFrame.bezier_x = new Bezier(parameters[0], parameters[4], parameters[ 8], parameters[12]); keyFrame.bezier_y = new Bezier(parameters[1], parameters[5], parameters[ 9], parameters[13]); keyFrame.bezier_z = new Bezier(parameters[2], parameters[6], parameters[10], parameters[14]); keyFrame.bezier_r = new Bezier(parameters[3], parameters[7], parameters[11], parameters[15]); if (vmdData.boneDictionary.ContainsKey(keyFrame.boneName)) { bonIndex = vmdData.boneDictionary[keyFrame.boneName]; vmdData.keyFrames[bonIndex].Add(keyFrame); } else { bonIndex = lastBoneIndex; lastBoneIndex++; vmdData.boneDictionary[keyFrame.boneName] = bonIndex; vmdData.keyFrames.Add(new List<VmdFrame>()); vmdData.keyFrames[bonIndex].Add(keyFrame); } } stream.Close(); return vmdData; } }
こっちが、ベジエ補完用のクラス。
using Microsoft.Xna.Framework; class Bezier { public Vector2 p1; public Vector2 p2; public Bezier(byte x1, byte y1, byte x2, byte y2) { p1.X = x1 / 127.0f; p1.Y = y1 / 127.0f; p2.X = x2 / 127.0f; p2.Y = y2 / 127.0f; } /// X = (1 - t)^3*p0_x + 3*(1 - t)^2*t*p1_x + 3*(1 - t)*t^2*p2_x + t^3*p3_x /// Y = (1 - t)^3*p0_y + 3*(1 - t)^2*t*p1_y + 3*(1 - t)*t^2*p2_y + t^3*p3_y /// 0 ≦ t ≦ 1 /// (X, Y) = (0, 0), (X, Y) = (1, 1)を通る /// 規格化されたベジェ曲線を考える → p0_x = p0_y = p3_x = p3_y = 0 /// t = x からtを変化させ、x = XとなるときのYを求める public float GetY(float x) { float t = x; float max_t = 1.0f; float min_t = 0.0f; float val1 = 0.0f; float val2 = 0.0f; float val3 = 0.0f; // 計算繰り返し回数 const int N = 8; for (int i = 0; i < N; i++) { float it = 1.0f - t; val1 = 3.0f * t * it * it; val2 = 3.0f * t * t * it; val3 = t * t * t; float x_diff = x - (val1 * p1.X) - (val2 * p2.X) - val3; if (System.Math.Abs(x_diff) < 1e-6) { // 誤差が定数以内なら終了 break; } if (x_diff > 0) { // 範囲を変更して再計算 min_t = t; t = ((max_t - t) / 2.0f) + t; } else { max_t = t; t = ((t - min_t) / 2.0f) + min_t; } } return ((val1 * p1.Y) + (val2 * p2.Y) + val3); } }
Related Posts
カテゴリー: csharp