VMDの切り出し・結合・速度変更で壊れるところ

VMDの編集は、やることだけ見ればフレーム番号の足し算・引き算・割り算です。ところが実物に当てると、キーフレームが消える・重複する・先頭のポーズが変わる、という形で壊れます。どこで何件壊れるのかを実測しました。

最終更新: 2026-09-13 · kphpリポジトリ同梱の実VMD 31本で実測。件数はそのうち gal.vmd(ボーン2,294件)・kimino.vmd(35,311件)・man.vmd(30,610件)を実際に変換して数えた値

レコードは生バイト列のまま持ち回る

まず前提として、編集のためにレコードを構造体へ展開しないことをすすめます。ボーンのキーフレームは111バイトで、そのうち64バイトは補間曲線です。編集で触る必要があるのはフレーム番号(15バイト目からの4バイト)だけなので、レコードを丸ごとコピーして、その4バイトだけをDataViewで書き換えるのがいちばん安全です。

floatを読んで書き戻すだけでも下位ビットは変わりえますし、補間曲線のような「意味を理解していないデータ」を展開して書き戻すと、往復で取りこぼす余地が生まれます。実際にこの方式で読み書きすると、ファイルがバイト単位で完全に一致します(254,708 / 3,919,595 / 3,408,867バイトの3本で確認。31本すべてで「読み→書き→読み」の結果が一致することはテストで固定しています)。

切り出し(トリミング)— 先頭のポーズは保証されない

「100〜300フレームだけを取り出す」は、その範囲のキーフレームだけを残してフレーム番号から100を引く処理になります。ここに落とし穴が2つあります。

1つめ。切り出した先頭に、キーフレームが無いボーンがある。 VMDは「変化した瞬間」だけを持つので、腰やセンターのように動きの少ないボーンは数百フレームに1件しかキーがありません。100フレーム目から切り出したとき、そのボーンの直近のキーが80フレーム目にあれば、それは範囲外なので落ちます。再生すると、そのボーンだけ初期姿勢に戻った状態から始まります。

切り出し残ったボーン(種類)0フレーム目にキーを持つボーン
gal.vmd を 1262フレーム目から158種(元と同数)0種
man.vmd を 1090フレーム目から93種(元は98種)18種
kimino.vmd を 1821フレーム目から70種(元は335種)1種

2つめ。後半では一度も動かないボーンが、まるごと消える。 kimino.vmd は335種のボーンを動かしますが、後半を切り出すと70種しか残りません(265種は前半にしかキーがない)。これは「消えて困る」ものと「元から動いていないので消えて当然」ものが混ざっています — 消えたボーンは初期姿勢のままになるので、多くの場合それで正しいのですが、切り出した時点の姿勢を保ちたければ、その直前の値を0フレーム目へ書き足す必要があります。

このサイトの切り出しは、この書き足しに対応しています(「切り出した先頭に、その時点の姿勢のキーを置く」。既定で入っています)。切り出す時点の姿勢を、MMDと同じ補間曲線で求めて0フレーム目のキーフレームとして書き足します。上の表は、その処理が無いと何が起きるかの実測です。外すと表のとおりになります。

書き足す値は「直前のキーの値」では足りません。 切り出す位置がキーとキーのあいだにあると、その時点の姿勢は前後のキーを補間した値で、直前のキーの値とは違います。直前の値をそのまま置くと、切り出した先頭でボーンが一瞬前の姿勢へ戻ります。実物のVMD 29本を、それぞれ真ん中あたりから切って比べると、位置のずれは大きいもので 5.98ユニット(約48cm)(out.vmd)、ダンスの kimino.vmd でも0.8ユニット(約6cm)ありました。このサイトでは2026-10-02に補間した値を置く形へ直しています。

結合 — 後ろのVMDをずらさないと必ず壊れる

2つのVMDはどちらも0フレーム目から始まります。そのまま配列を連結すると、同じ(ボーン, フレーム)のキーフレームが2件できます。 MMDは同じ位置に2件あることを想定していないので、どちらが採用されるかは実装任せになり、再生が破綻します。

正しくは、後ろのVMDの全キーフレームを「前のVMDの最終フレーム番号」だけずらしてから連結します。実測すると、gal.vmd を自分自身と繋いだ場合:

最終フレームボーンのキーフレーム重複
元5,048 (168.3秒)2,294件—
ずらして結合10,096 (336.5秒)4,588件0件
ずらさず結合5,048のまま4,588件2,294件すべてが重複

現行のmmdbox.netが持っていた merged() は、この「ずらす」処理を持たないまま単純結合していました。あれは本来「モーション + 表情」のように別の系統を同じ時間軸に重ねるための関数で、独立した2本を前後に繋ぐ用途では使えません。同じ名前の関数を流用するときは、何を想定して書かれたものか確かめてください。

速度変更 — 速くすると丸めでキーフレームが重なる

速度はフレーム番号の割り算です(2倍速なら番号が半分)。フレーム番号は整数なので、割ったあとに丸めると、隣り合っていたキーフレームが同じ番号に落ちます。 結合のときと同じ重複がここでも起きます。

1.5倍速2倍速3倍速0.5倍速
gal.vmd (2,294件)0件0件42件 (1.8%)0件
man.vmd (30,610件)402件 (1.3%)530件 (1.7%)712件 (2.3%)0件
kimino.vmd (35,311件)206件 (0.6%)341件 (1.0%)1,221件 (3.5%)0件
  • 遅くする側では起きません(番号が広がるだけなので重複しない)。0.5倍速はどれも0件でした。
  • キーフレームの密度で決まります。 gal.vmd は2倍速でも0件です — 元のキーが粗く、2フレーム以上離れているためです。逆に毎フレームにキーがあるモーションは、2倍速にすると原理的に半分が重なります。
  • 落とした件数は必ず利用者に見せること。 黙って捨てると「速くしたら動きが変わった」の原因が分かりません。このサイトは変換後に件数を表示します。

重複は先に出てきたものを残すのが無難です。VMDのレコードはファイル内で時間順に並んでいるとは限らないので、「後勝ち」にすると結果がファイルの並び順に左右されます。

フレーム番号の位置はブロックごとに違う

どの編集でも同じ場所を書き換えるので、ここだけは間違えないようにしてください。

ブロックフレーム番号の位置レコード長
ボーン15バイト目(名前15バイトの後ろ)111
表情15バイト目23
カメラ / 照明 / セルフ影 / IK・表示0バイト目61 / 28 / 9 / 可変

ボーンと表情だけ名前が先に来るので15バイト目、それ以外は先頭です。IK/表示切替だけはレコード長が可変(9 + 21 × IK数)なので、件数から一気に読み飛ばせません。