【C#】C++で作ったVMDのビューワをC#に移植中

そういえば、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&#91;15&#93; "頭\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&#91;64&#93; 補間パラメータ
            byte&#91;&#93; parameters = reader.ReadBytes(64);

            keyFrame.bezier_x = new Bezier(parameters&#91;0&#93;, parameters&#91;4&#93;, parameters&#91; 8&#93;, parameters&#91;12&#93;);
            keyFrame.bezier_y = new Bezier(parameters&#91;1&#93;, parameters&#91;5&#93;, parameters&#91; 9&#93;, parameters&#91;13&#93;);
            keyFrame.bezier_z = new Bezier(parameters&#91;2&#93;, parameters&#91;6&#93;, parameters&#91;10&#93;, parameters&#91;14&#93;);
            keyFrame.bezier_r = new Bezier(parameters&#91;3&#93;, parameters&#91;7&#93;, parameters&#91;11&#93;, parameters&#91;15&#93;);

            if (vmdData.boneDictionary.ContainsKey(keyFrame.boneName))
            {
                bonIndex = vmdData.boneDictionary&#91;keyFrame.boneName&#93;;
                vmdData.keyFrames&#91;bonIndex&#93;.Add(keyFrame);
            }
            else
            {
                bonIndex = lastBoneIndex;
                lastBoneIndex++;

                vmdData.boneDictionary&#91;keyFrame.boneName&#93; = 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);
    }
}
4年前

コメントを残す

メールアドレスが公開されることはありません。