PMXのモーフ(表情)の種別と、パーサがずれる原因
モーフは「頂点を動かすもの」だけではありません。材質の色を変えるもの、他のモーフをまとめるもの、剛体に力を加えるものまであり、種別ごとにレコードの長さが違います。 この長さを1つでも間違えると、モーフより後ろのブロックが全部ずれます。
モーフの種別は11種類
| 種別 | 名前 | 1件のバイト数(indexを除く) | 何をするか |
|---|---|---|---|
| 0 | グループ | モーフindex + 4 | 他のモーフをまとめて動かす |
| 1 | 頂点 | 頂点index + 12 | 頂点を動かす。表情の大半はこれ |
| 2 | ボーン | ボーンindex + 12 + 16 | ボーンを移動・回転させる |
| 3 | UV | 頂点index + 16 | UV座標をずらす(目の位置を動かす等) |
| 4〜7 | 追加UV1〜4 | 頂点index + 16 | 追加UVをずらす |
| 8 | 材質 | 材質index + 113 | 色・エッジ・各種係数を変える |
| 9 | フリップ | モーフindex + 4 | PMX 2.1。モーフを切り替える |
| 10 | インパルス | 剛体index + 25 | PMX 2.1。剛体に力を加える |
index のバイト数はファイルのヘッダに書かれています(頂点・材質・ボーン・剛体・モーフでそれぞれ別に指定され、1・2・4バイトのいずれか)。モデルの規模によって変わるので、固定値で書くと大きいモデルで壊れます。
材質モーフの113バイトの内訳は、適用対象1 + 拡散色16 + 反射色12 + 反射強度4 + 環境色12 + エッジ色16 + エッジサイズ4 + テクスチャ係数16 + スフィア係数16 + Toon係数16 です。
実モデルでの内訳
| モデル | モーフ合計 | グループ | 頂点 | ボーン | UV | 材質 |
|---|---|---|---|---|---|---|
| アリシア | 63 | 0 | 59 | 0 | 0 | 4 |
| ad_org8 | 159 | 6 | 86 | 4 | 8 | 55 |
| ad_ik2 | 157 | 6 | 77 | 4 | 8 | 62 |
材質モーフが意外に多いのが実データの特徴です。ad_org8 は159件のうち55件が材質モーフで、これは「この材質を隠す」という使い方(涙・汗・ハイライトの出し入れ、衣装のパーツ切り替え)が定番だからです。頂点モーフだけを見ていると、表情の半分を取りこぼします。
なお、three.js(r171)の MMDLoader が実際に当てるのは頂点モーフだけです(材質・ボーン・UVは未実装)。このサイトは材質モーフの色・不透明度・輪郭線を自前で当てています(材質モーフの記事)。
追加UVモーフ(4〜7)を読み飛ばし損ねる
three.js r171 が内部で使う mmdparser は、種別4〜7の分岐が // TODO: implement のまま空です。 実際のソースがこうなっています。
} else if ( p.type === 4 ) { // additional uv1
// TODO: implement
} else if ( p.type === 5 ) { // additional uv2
// TODO: implementこれは「その情報が取れない」で済む話ではありません。 読み進める位置(バイトカーソル)が1バイトも進まないので、そのモーフのデータが次のモーフの先頭として読まれます。 以降のモーフ・表示枠・剛体・ジョイントが全部ずれます。
壊れ方は「読み込めません」という素直な失敗にならず、とんでもない位置に剛体が置かれる・意味不明な値で例外が出るという形になります。原因がモーフにあると気づくまでに時間がかかる種類の不具合です。種別9(フリップ)・10(インパルス)にも分岐がありません。
読み飛ばすだけなら実装は1行です。 追加UVモーフはUVモーフと同じ並び(頂点index + Float32×4)なので、skip(頂点indexのバイト数 + 16) で済みます。自前でPMXを歩くなら、使わない種別も長さだけは正しく進めてください。
実測した3体には追加UVモーフは含まれていませんでした(UVモーフは ad_org8 / ad_ik2 に8件)。当たらないから気づかない類の問題で、当たったときだけ突然壊れます。
VMD側の表情は名前とウェイトだけ
モーションファイル(VMD)の表情キーフレームは23バイトで、モーフ名15バイト + フレーム番号4バイト + ウェイト4バイト(float、0.0〜1.0)しかありません。
- 補間曲線を持ちません。 キーフレーム間は線形に繋ぐのが仕様です。ボーンと違って緩急は付けられません。
- モーフ名も15バイトのShift_JIS固定長です。日本語なら7文字まで(名前の記事)。実測した425種のうち7種が上限に達していました。
- モデルに無いモーフ名は無視されるだけです。表情モーションが他のモデルで「一部だけ効かない」のはこれで、名前が完全一致しないと当たりません。
表情名の命名は標準化されていません。実測した425種には ウィンク ウィンク2右(半角カナ) まつ毛-まばたき のような揺れがあります。「ウィンク」が左目を指すという慣習があるため、ウィンク右 の反対側は ウィンク左 ではなく無印の ウィンク になっていることが普通です。
ウェイトの種別(モーフとは別物だが同じ落とし穴がある)
モーフと並んで種別で長さが変わるのが頂点のウェイトです。BDEF1(0)・BDEF2(1)・BDEF4(2)・SDEF(3)・QDEF(4)で、それぞれボーン番号とウェイトの個数が違います。実測:
| モデル | BDEF1 | BDEF2 | BDEF4 | SDEF | 合計 |
|---|---|---|---|---|---|
| アリシア | 8,858 | 10,121 | 3,332 | 0 | 22,311 |
| ad_org8 | 19,200 | 16,813 | 1,543 | 3,132 | 40,688 |
SDEF(球状変形)は実データに普通にあります。 ad_org8 は3,132頂点がSDEFでした。three.js の MMDLoader はSDEFをBDEF2として扱う割り切りをしており、このサイトも同じです。肩とひじの潰れ方が本家MMDと厳密には一致しません。