2015年01月17日

[MMMプラグイン] ポーズ固定プラグイン

LockPosePlugin説明

マーカー位置の直前に打たれたキーフレームにおけるポーズを、現在マーカー位置まで固定するMMM用プラグイン「ポーズ固定プラグイン(LockPosePlugin)」を配布します。

ポーズAからポーズBにうつるとき、ポーズAを取っている長さを調整する目的で使います。マーカー位置と、その直前に打たれたキーフレーム位置の全ボーンにキーフレームを打つことでポーズを固定します。シーン切り替え時などは特に役立つかと思います。

配布:https://bowlroll.net/file/61468

というわけで、MMMプラグインの制作に挑戦してみた。

MMMとはMikuMikuMovingの略で、MMD(MikuMikuDance)クローンとして開発されたのち独自の進化を遂げ、MMDにはない便利な機能を多数実装した第2のツールである。

mikumikumoving
https://sites.google.com/site/mikumikumoving/home

私ちょむはいわゆる「MMD静画」をニコニコ静画に不定期に投稿しているが、実は、最初の数枚を除いたほぼ全ての作品がこのMMMを使用して制作されている。先日気まぐれで久々に本家MMDでも作ってみようと戻ってみたが、あまりの使い勝手の違いに一瞬でMMMに戻ってしまった。それくらい、慣れると離れられないツールである(もちろん、MMMならではの不便な面も多数あるわけだが…)。

さてMMDにはないMMM独自の機能のひとつとして、プラグインの存在がある。これは機能を持った外部DLLを読み込むことで、タイムラインの内容やモデルの動きを直接加工するような複雑な処理を適用できる。

冒頭にある機能がどれだけ有用かの解説はおいておいて、このブログではMMMプラグインの開発に関係する個人的なメモも、機会の折に取り上げて行きたい。

まず、MMM本体をダウンロードするとPluginsフォルダの中に圧縮されたzipが入っており、展開すると中からプラグインのサンプルソースが出てくる。基本的にはこれを改造する形で開発を始められる。

サンプルソースはVisual Studioで開くことができる。先日フリー化した Visual Studio 2013 Community で全く問題なくビルドできるので、プラグイン開発も実質無料で可能である。

リファレンスはWeb上のMMMのマニュアルに記載されていて、これとにらめっこしながらコーディングを進めるわけだが、オブジェクトやインスタンスの説明に終始しており、基本的な開発手順や内部構造の仕組みについてはかなり省かれている。さらにリファレンスはアルファベット順であり機能別にも並んでいないので何も知らない状態でいきなり把握するのは難しい。

例えばMMM内部のタイムラインのオブジェクト構造は以下のようになっている。

データ構造

(※MMDにはないMMMの独自の機能としてボーンレイヤーがある。MMDではモデルの改造が必須だったボーンの多重化をソフトウェア上で手軽に再現できるという強力な機能だが、これのおかげでボーン状態を記録したコレクションの中にレイヤーというコレクションが一個余計に多く挟まる形になっている。)

冒頭の、タイムラインを直接書き換える動作をさせるには、SceneからMotionFrameDataまでかなり深い階層を掘り進む必要がある。とはいうものの、構造さえ把握してしまえばforeachでブン回して書き換えるだけなのでそれほど難しくはない。

メイン処理部のソースは以下のようになった。なお、例によって言語はC#である。

    public void Run(CommandArgs e)
    {
        //モデルがアクティブかどうか
        if (Scene.ActiveModel != null)
        {
            //現在マーカー位置の取得
            long markerPosition = Scene.MarkerPosition;

            /////////////////////////////////////////////
            //最終キーフレーム位置の検索

            long lastkeyframeposition = -1;

            //全てのボーンに対して
            foreach (Bone tempBone in Scene.ActiveModel.Bones)
            {
                //全てのレイヤーに対して
                foreach (MotionLayer layer in tempBone.Layers)
                {
                    //キーフレームのリストのなかから探す
                    foreach (MotionFrameData framedata in layer.Frames.GetKeyFrames())
                    {
                        //マーカー位置を超えたら飛ばす
                        if (framedata.FrameNumber >= markerPosition) continue;

                        //より大きいフレームナンバーを持つフレームデータを保存
                        if (framedata.FrameNumber > lastkeyframeposition)
                        {
                            lastkeyframeposition = framedata.FrameNumber;
                        }
                    }
                }
            }

            /////////////////////////////////////////////
            //全てのモーフに対して
            foreach (Morph morphData in Scene.ActiveModel.Morphs)
            {
                //キーフレームのリストのなかから探す
                foreach (MorphFrameData framedata in morphData.Frames.GetKeyFrames())
                {
                    //マーカー位置を超えたら飛ばす
                    if (framedata.FrameNumber >= markerPosition) continue;

                    //より大きいフレームナンバーを持つフレームデータを保存
                    if (framedata.FrameNumber > lastkeyframeposition)
                    {
                        lastkeyframeposition = framedata.FrameNumber;
                    }
                }
            }

            /////////////////////////////////////////////
            //プロパティフレームに対して

            List<ModelPropertyFrameData> lmpfdc = Scene.ActiveModel.PropertyFrames.GetKeyFrames();
            foreach (ModelPropertyFrameData propertydata in lmpfdc)
            {
                //マーカー位置を超えたら飛ばす
                if (propertydata.FrameNumber >= markerPosition) continue;
                //より大きいフレームナンバーを持つフレームデータを保存
                if (propertydata.FrameNumber > lastkeyframeposition)
                {
                    lastkeyframeposition = propertydata.FrameNumber;
                }
            }

            //最終キー登録フレーム取得完了

            /////////////////////////////////////////////
            //最終キー登録フレームに対して全ボーン/モーフ/プロパティをキー登録する

            //全てのボーンに対して
            foreach (Bone tempBone in Scene.ActiveModel.Bones)
            {
                //全てのレイヤーに対して
                foreach (MotionLayer layer in tempBone.Layers)
                {
                    //最終キー登録フレームに登録
                    MotionFrameData mfd = layer.Frames.GetFrame(lastkeyframeposition);
                    layer.Frames.AddKeyFrame( mfd );

                    //マーカーフレームに登録
                    mfd.FrameNumber = markerPosition;
                    layer.Frames.AddKeyFrame(mfd);

                    //表示状態を反映
                    MotionData md = new MotionData(mfd.Position, mfd.Quaternion);
                    layer.CurrentLocalMotion = md;
                }
            }

            //全てのモーフに対して
            foreach (Morph morphData in Scene.ActiveModel.Morphs)
            {
                //最終キー登録フレームに登録
                MorphFrameData mfd = morphData.Frames.GetFrame(lastkeyframeposition);
                morphData.Frames.AddKeyFrame(mfd);

                //マーカーフレームに登録
                mfd.FrameNumber = markerPosition;
                morphData.Frames.AddKeyFrame(mfd);
            }

            //プロパティフレームに対して
            //最終キー登録フレームに登録
            ModelPropertyFrameData mpfd = Scene.ActiveModel.PropertyFrames.GetFrame(lastkeyframeposition);
            mpfd.FrameNumber = lastkeyframeposition;
            Scene.ActiveModel.PropertyFrames.AddKeyFrame(mpfd);

            //最終キー登録フレームに登録
            mpfd.FrameNumber = markerPosition;
            Scene.ActiveModel.PropertyFrames.AddKeyFrame(mpfd);

            /////////////////////////////////////////////
        }
        else
        {
            //モデルがアクティブでない場合はメッセージを出して終了
            if (Scene.Language == "ja")
                MessageBox.Show(ApplicationForm, "モデルが選択された状態で実行してください", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
                MessageBox.Show(ApplicationForm, "Please select a model.", "Note", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

            e.Cancel = true;
        }
    }

あまりスマートではないかもしれないが、前半部分で「マーカー位置より前で、かつ、最後のキーフレーム挿入位置」をぶん回してスキャンしている。仕様上、ボーン/モーフ/モデルプロパティごとにマーカー位置を頼りにスキャンする必要があり、フレームごとに縦にスキャンすることはできないようだ。

そして後半部分では上記で確定した最終キーフレーム位置と、マーカー位置の全ボーン/モーフ/モデルプロパティにキーを打つことによってポーズを固定するというわけである。

躓いたところといえば、マーカー位置のキーフレームが書き換わっても表示上のモデルのポーズは自動的に変更されないため、各レイヤーのCurrentLocalMotionを更新しなければならないことだ。なおボーンの表示状態は各レイヤーの合成値だが、レイヤーそれぞれにCurrentLocalMotionを指定してやれば自動的にボーンの表示状態は更新されるらしい。

また、MMMの仕様上、プラグインの動作に対してアンドゥが出来ない。理由はわからない。一応、e.Cancelをtrueにセットすると更新フラグがセットされないという機能があるが、e.Cancelを明示的にfalseに設定してもアンドゥに影響を与えることはできない。

タイムラインのデータ構造とそれを書き換える方法はわかったので、ネタと暇さえあればまた新しいプラグインを作るかもしれない。

posted by ちょむ at 14:06| Comment(0) | TrackBack(0) | MMMプラグイン
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

※ブログオーナーが承認したコメントのみ表示されます。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/112260856
※ブログオーナーが承認したトラックバックのみ表示されます。
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック