DxLib .mv1 ファイル形式仕様書

対象: DxLib 3.24f の MV1 モデルファイル (Ver.1) 出典: DxModel.cpp / DxArchive_.cpp (loader) + DxModelFile.h / DxLib.h / DxModel.h (バイナリ構造体と enum) 文字コード表記: リトルエンディアン、C 構造体の直接シリアライズ (padding は明示)

目次

  1. 概要
  2. ファイル全体レイアウト
  3. DXA 圧縮アルゴリズム詳細
  4. ルートヘッダ MV1MODEL_FILEHEADER_F1
  5. 階層データ MV1_FRAME_F1
  6. メッシュ MV1_MESH_F1
  7. マテリアル MV1_MATERIAL_F1
  8. テクスチャ MV1_TEXTURE_F1
  9. トライアングルリスト MV1_TRIANGLE_LIST_F1
  10. スキンボーン MV1_SKIN_BONE_F1
  11. ライト MV1_LIGHT_F1
  12. アニメーション
  13. シェイプ (モーフ)
  14. 物理演算
  15. 座標系の注意
  16. 文字列プール
  17. バージョニング
  18. 制限事項
  19. Writer の落とし穴 ⚠️
  20. 付録 A: 全 enum 値一覧
  21. 付録 B: 可変長セクションの byte レイアウト詳細
  22. 付録 C: 16bit 圧縮値のデコード式
  23. 付録 D: MATRIX_4X4CT_F / FLOAT4 の実メモリ配置
  24. 付録 E: リファレンス C パーサ (最小読み取り実装)

★印 = 拡充版で追加された章


概要

.mv1 は DxLib 独自の 3D モデルファイル形式で、他形式 (.mqo / .pmd / .pmx / .x / .fbx / .vmd) を読み込んだ後のランタイム構造体を丸ごとバイナリ化したキャッシュ的なフォーマット。利点:

保存は MV1SaveModelToMV1File() で、読み込みは他形式と同じ MV1LoadModel() の裏で自動判定される (マジック "MV11" で分岐)。


ファイル全体レイアウト

+-------+-----------------------------+
| off   | 内容                        |
+-------+-----------------------------+
| 0x00  | Magic  "MV11" (4 bytes)     |   ← MV1MODEL_FILEHEADER_F1.CheckID[4] と同一のバイト列
+-------+-----------------------------+
| 0x04  | DXA 圧縮ブロック            |
|       |   伸長すると                |
|       |   MV1MODEL_FILEHEADER_F1 の |
|       |   Version 以降 + 可変長     |
|       |   データ群                  |
+-------+-----------------------------+

伸長されたデータは「MV1MODEL_FILEHEADER_F1Version フィールド (offset 4) 以降」としてメモリに貼られ、同じバッファ内の後続オフセットに各種配列やポインタ先が連続配置される。従って パーサ実装側は decode 結果の先頭に "MV11" 4 byte を自前で補って FHeader として reinterpret する のが簡単 (mv1conv はこの方式)。構造体メンバー内の ポインタ相当の DWORD は「このバッファ (CheckID 込み) 先頭からのバイトオフセット」を格納 (ロード時にポインタへ変換される)。ポインタ値 0 は null。


DXA 圧縮アルゴリズム詳細

出典: DxArchive_.cppDXA_Encode / DXA_Decode。純粋 LZSS (Huffman 併用なし)。.mv1 の magic "MV11" 直後から始まるバイト列がこの形式。

9 byte 固定ヘッダ

offset type 意味
0 DWORD (LE) OriginalSize 元のバイト数
4 DWORD (LE) CompressedSize 圧縮後バイト数 (この 9 byte ヘッダを含む)
8 BYTE KeyCode マッチ開始マーカ (圧縮元データに存在しないバイト値が選ばれる)

本体 (stream)

リテラル byte と backref の混在列。どちらかの判別は「現バイトが KeyCode か否か」で行う:

マッチ参照エンコード

KeyCode の直後のバイトを flags と呼ぶ (値が KeyCode 以上の場合は デコード時に -1 する 補正あり):

bit 7-3: (match_length - 4) の下位 5 bit (0..31)
bit 2  : 拡張長フラグ (1 なら次 1 byte で長さ bit 8-5 を追加)
bit 1-0: アドレス byte 数
           00 = 1 byte  (1..256 back)
           01 = 2 byte  (1..65536 back)
           10 = 3 byte  (1..16777216 back)

flags の次は:

パラメータ定数

名称 意味
MIN_COMPRESS 4 これ以上のマッチ長のみ圧縮に使う
MAX_COPYSIZE 8195 (5bit + 8bit + MIN_COMPRESS) 最大マッチ長 (decoder は 13bit しか読まない)
MAX_POSITION 16 MB (1<<24) 最大 lookback 距離

: 旧 spec に 0x7FFF (32,767) と書いてあったのは誤り。実 decoder は length = ((flags>>3) & 0x1F) | (ext_byte << 5); length += 4 で組み立てるので、 最大長は 0x1FFF + 4 = 8195 byte。mv1conv の encoder も 8195 で cap している。

デコード疑似コード

uint32_t DXA_Decode_Size( const uint8_t *src )
{
    return read_u32_le( src + 0 ) ;   // OriginalSize
}

int DXA_Decode( const uint8_t *src, uint8_t *dst )
{
    uint32_t origSize = read_u32_le( src + 0 ) ;
    uint32_t compSize = read_u32_le( src + 4 ) ;
    uint8_t  key      = src[ 8 ] ;
    const uint8_t *sp = src + 9 ;
    const uint8_t *spEnd = src + compSize ;
    uint8_t  *dp = dst ;

    while ( sp < spEnd ) {
        if ( *sp != key ) {                 /* リテラル */
            *dp++ = *sp++ ;
            continue ;
        }
        if ( sp[ 1 ] == key ) {             /* エスケープ済 KeyCode */
            *dp++ = key ;
            sp += 2 ;
            continue ;
        }

        /* マッチ参照 */
        uint8_t flags = sp[ 1 ] ;
        if ( flags > key ) flags-- ;        /* デコード補正 */
        sp += 2 ;

        uint32_t length = ( flags >> 3 ) & 0x1F ;
        if ( flags & 0x04 ) {               /* 拡張長 */
            length |= ( uint32_t )( *sp++ ) << 5 ;
        }
        length += 4 ;                        /* MIN_COMPRESS 加算 */

        uint32_t distance = 0 ;
        switch ( flags & 0x03 ) {
        case 0 : distance =   *sp++ ;                                       break ;
        case 1 : distance =   read_u16_le( sp ) ;                 sp += 2 ; break ;
        case 2 : distance =   read_u16_le( sp ) | ( sp[ 2 ] << 16 ) ; sp += 3 ; break ;
        }
        distance += 1 ;                      /* デコード補正 */

        /* 自己参照コピー (distance < length) の場合、少しずつ展開 */
        if ( distance < length ) {
            uint32_t part = distance ;
            while ( length > part ) {
                memcpy( dp, dp - part, part ) ;
                dp     += part ;
                length -= part ;
                part   += part ;              /* 2 倍ずつ広げる */
            }
            memcpy( dp, dp - distance * 2, length ) ;  /* 残り */
            dp += length ;
        } else {
            memcpy( dp, dp - distance, length ) ;
            dp += length ;
        }
    }
    return ( int )origSize ;
}

.mv1 ファイルから DXA ブロックを取り出す

uint32_t get_dxa_size( const uint8_t *file, size_t fileLen )
{
    if ( fileLen < 4 + 9 ) return 0 ;
    if ( memcmp( file, "MV11", 4 ) != 0 ) return 0 ;
    return read_u32_le( file + 4 + 4 ) ;  /* DXA ヘッダの CompressedSize */
}

Encode 側の注意

暗号化について

本セクションで扱う DXA_Encode/Decodeファイル内部圧縮 専用で、.dxa アーカイブの鍵付き暗号化 (DXArchive クラス内蔵) とは 別物.mv1 には暗号化はない。


ルートヘッダ MV1MODEL_FILEHEADER_F1

正確に 304 バイト (0x130) の固定長ヘッダ (4 byte align)。主要フィールド:

offset type name 意味
0x00 BYTE[4] CheckID[4] "MV11"ファイル magic を流用、DXA ブロックには含まれない
0x04 DWORD Version 0 (DxLib 3.24f 現行。旧仕様書の「1」は誤り)
0x08 int RightHandType TRUE=右手系 / FALSE=左手系
0x0C int AutoCreateNormal 法線自動計算使用
0x10 int ChangeDrawMaterialTableSize 描画マテリアル変更管理用ビット数
0x14 int ChangeMatrixTableSize 行列変更管理用ビット数
0x18 DWORD ChangeDrawMaterialTable (ptr) 描画用マテリアル変更確認用ビットデータ
0x1C DWORD ChangeMatrixTable (ptr) 行列変更確認用ビットデータ
0x20 int FrameNum 階層データ (フレーム) 数
0x24 DWORD Frame (ptr) MV1_FRAME_F1[] の先頭
0x28 int TopFrameNum ルート階層 (親なし) フレーム数
0x2C DWORD FirstTopFrame (ptr) 最初のルートフレーム
0x30 DWORD LastTopFrame (ptr) 最後のルートフレーム
0x34 int FrameUseSkinBoneNum フレームが使用するボーン総数
0x38 DWORD FrameUseSkinBone (ptr) MV1_SKIN_BONE_F1*[] の先頭
0x3C int MaterialNum マテリアル数
0x40 DWORD Material (ptr) MV1_MATERIAL_F1[]
0x44 int TextureNum テクスチャ数
0x48 DWORD Texture (ptr) MV1_TEXTURE_F1[]
0x4C int MeshNum メッシュ数
0x50 DWORD Mesh (ptr) MV1_MESH_F1[]
0x54 int LightNum ライト数
0x58 DWORD Light (ptr) MV1_LIGHT_F1[]
0x5C int SkinBoneNum スキンボーン数
0x60 DWORD SkinBone (ptr) MV1_SKIN_BONE_F1[]
0x64 int SkinBoneUseFrameNum ボーンを使用するフレーム数
0x68 DWORD SkinBoneUseFrame (ptr) MV1_SKIN_BONE_USE_FRAME_F1*[]
0x6C int TriangleListNum トライアングルリスト数
0x70 DWORD TriangleList (ptr) MV1_TRIANGLE_LIST_F1[]
0x74 DWORD VertexData (ptr) 頂点データブロック先頭
0x78 DWORD VertexDataSize 頂点データブロックの合計バイト数
... int×10 TriangleListNormalPositionNum ... TriangleListIndexNum ボーン別頂点総数カウンタ群
... int TriangleNum モデル全体の三角形数
... int TriangleListVertexNum トライアングルリストの頂点総数
... int StringSize 文字列データサイズ
... DWORD StringBuffer (ptr) 文字列プール先頭 (null 終端連結)
... int OriginalAnimKeyDataSize アニメキー元サイズ
... int AnimKeyDataSize アニメキーデータサイズ (圧縮後)
... DWORD AnimKeyData (ptr) アニメキー可変長データ
... int AnimKeySetNum アニメキーセット数
... int AnimKeySetUnitSize MV1_ANIM_KEYSET_F1 サイズ
... DWORD AnimKeySet (ptr) アニメキーセット配列
... int AnimNum アニメ数
... int AnimUnitSize MV1_ANIM_F1 サイズ
... DWORD Anim (ptr) アニメ配列
... int AnimSetNum アニメセット数
... DWORD AnimSet (ptr) アニメセット配列
... DWORD[4] UserData[4] ユーザー任意
... DWORD Shape (ptr) MV1_FILEHEAD_SHAPE_F1*、なければ NULL
... DWORD Physics (ptr) MV1_FILEHEAD_PHYSICS_F1*、なければ NULL
... BYTE MaterialNumberOrderDraw マテリアル番号昇順で描画するか
... BYTE IsStringUTF8 StringBuffer が UTF-8 か (1) / Shift-JIS か (0)
... BYTE[2] Padding1[2] 0
... DWORD[13] Padding2[13] 0 (将来拡張用)

ヘッダ全長: 304 byte (0x130)mv1convstatic_assert(sizeof(MV1MODEL_FILEHEADER_F1) == 304) で確認可能。

検証済サンプル (mv1conv reader 通過): SimpleModel.mv1 / SimpleModelVertexColor.mv1 / SimplePillarStage.mv1 / ColTestStage.mv1 / DxChara.mv1 (DxLib_VC3_24f 同梱)。全てで magic MV11 / Version=0 / IsStringUTF8=1 / MaterialNumberOrderDraw=0 を確認。


階層データ MV1_FRAME_F1

モデルの骨格 (ボーン) + メッシュコンテナ。親子ツリー構造:

役割 フィールド
ツリー連結 DimPrev / DimNext (配列順) / Parent / FirstChild / LastChild / Prev / Next
基本情報 Name / Index / Flag (MV1_FRAMEFLAG_VISIBLE 等)
変換 Translate (VECTOR) / Scale / Rotate + RotateOrder / Quaternion (FLOAT4) / PreRotate / PostRotate
統計 TotalMeshNum / TotalChildNum / TriangleNum / VertexNum
メッシュ MeshNum / Mesh (ptr to MV1_MESH_F1[])
スキンボーン SkinBoneNum / SkinBone / UseSkinBoneNum / UseSkinBone (ボーンリスト)
法線 SmoothingAngle (ラジアン) / AutoCreateNormal
座標/法線データ PositionNum / NormalNum / PositionAndNormalData (ptr)
シェイプ FrameShape (ptr, NULL 可)
変更管理 ChangeDrawMaterialInfo / ChangeMatrixInfo (各 MV1_CHANGE_F1)
その他 Light (ptr、ライト本体) / VertFlag / MaxBoneBlendNum

PositionAndNormalData の構造

VertFlag ビット (MV1_FRAME_VERT_FLAG_* 群) に応じて可変レイアウト。ヘッダで要素タイプを切替:

[MV1_FRAME_VERT_FLAG_POSITION_B16 が立っている場合]
  MV1_POSITION_16BIT_SUBINFO_F1 x ;   // float Min; float Width;
  MV1_POSITION_16BIT_SUBINFO_F1 y ;
  MV1_POSITION_16BIT_SUBINFO_F1 z ;

[PositionNum 回繰返し: 座標]
  POSITION_B16 立っていれば WORD×3、立っていなければ float×3

[PositionNum 回繰返し: スキニング情報 (MATRIX_WEIGHT_NONE でない場合)]
  MaxBoneBlendNum 分、又はインデックス=-1 で終端
    インデックス値 (MATRIX_INDEX_MASK 指定型: u8 / u16)
    ウェイト値    (MATRIX_WEIGHT_MASK 指定型: u8 / u16)

[法線: MV1_FRAME_NORMAL_TYPE_NONE でない場合]
  NormalNum 回繰返し: NORMAL_TYPE_MASK 指定型 (s8 / s16 / float)

法線型フラグ (MV1_FRAME_NORMAL_TYPE_*):

ボーンウェイト圧縮

MATRIX_WEIGHT_TYPE_U8 / U16 で頂点ウェイトを 8/16 bit に圧縮可能 (生 float なら 4 byte/weight)。


メッシュ MV1_MESH_F1

フレームに属する描画単位。1 マテリアル = 1 メッシュが基本。

役割 フィールド
Container (ptr to MV1_FRAME_F1)
マテリアル Material (ptr)
頂点色 UseVertexDiffuseColor / UseVertexSpecularColor / NotOneDiffuseAlpha
形状 Shape (1=シェイプメッシュ)
描画制御 Visible / BackCulling (0=なし, 1=表, 2=裏)
UV UVSetUnitNum (UV ペア数) / UVUnitNum (UV 成分数)
頂点フラグ VertFlag (MV1_MESH_VERT_FLAG_*)
頂点数/面数 VertexNum / FaceNum
頂点データ VertexData (ptr)
トライアングルリスト TriangleListNum / TriangleList (ptr)

VertexData 構造

[先頭に一度だけ: VertFlag に MV1_MESH_VERT_FLAG_COMMON_COLOR が立っている場合]
  COLOR_U8 DiffuseColor ;    // 全頂点共通色
  COLOR_U8 SpecularColor ;

[VertexNum 回繰返し: 座標インデックス]
  POS_IND_TYPE_MASK 指定型 (none / u8 / u16 / u32)

[VertexNum 回繰返し: 法線インデックス]
  NRM_IND_TYPE_MASK 指定型

[VertexNum 回繰返し: 頂点カラー (COMMON_COLOR でない場合)]
  COLOR_U8 diffuse + COLOR_U8 specular

[VertexNum 回繰返し: UV 値]
  UVUnitNum * UVSetUnitNum 個、float×2 又は u16×2 (UV_U16 指定時、65535=1.0)

[VertexNum 回繰返し: トゥーン輪郭抑止情報 (NON_TOON_OUTLINE 立っている場合のみ)]
  1 頂点 1 ビット、余り 8 bit 未満はパディング

マテリアル MV1_MATERIAL_F1

フィールド 意味
Name / Index 名前 / インデックス
Diffuse / Ambient / Specular / Emissive COLOR_F (float×4)
Power スペキュラパワー
Alpha 不透明度
DiffuseLayerNum / DiffuseLayer[8] MV1_MATERIAL_LAYER_F1 の 8 段配列 (1 段目以降は BlendType 有効)
SpecularLayerNum / SpecularLayer[8] スペキュラマップ
NormalLayerNum / NormalLayer[8] 法線マップ
UseAlphaTest / AlphaFunc / AlphaRef アルファテスト
DrawBlendMode / DrawBlendParam 出力時ブレンド
ToonInfo (ptr or NULL) MV1_MATERIAL_TOON_F1 へのオフセット

マテリアルレイヤー MV1_MATERIAL_LAYER_F1

struct MV1_MATERIAL_LAYER_F1 {
    int Texture ;           // MV1_TEXTURE_F1 のインデックス
    int BlendType ;         // DX_MATERIAL_BLENDTYPE_ADDITIVE など
    DWORD Padding[4] ;
} ;

トゥーン情報 MV1_MATERIAL_TOON_F1

struct MV1_MATERIAL_TOON_F1 {
    int   Type ;                    // DX_MATERIAL_TYPE_TOON など
    int   DiffuseGradTexture ;      // ディフューズグラデ texture index (-1=デフォルト)
    int   SpecularGradTexture ;     // スペキュラグラデ
    int   DiffuseGradBlendType ;    // ブレンドタイプ
    int   SpecularGradBlendType ;
    float OutLineWidth ;            // 輪郭線幅 (0..1)
    COLOR_F OutLineColor ;
    float OutLineDotWidth ;         // ドット単位幅
    BYTE  EnableSphereMap ;         // スフィアマップ有効
    BYTE  SphereMapBlendType ;
    short SphereMapTexture ;
    DWORD Padding[2] ;
} ;

テクスチャ MV1_TEXTURE_F1

フィールド 意味
Name / Index 名前 / インデックス
ColorFilePath カラーチャンネル画像の相対パス (文字列プールへのオフセット)
AlphaFilePath アルファチャンネル別画像の相対パス (単独使用時)
BumpImageFlag / BumpImageNextPixelLength バンプマップ情報
AddressModeU / AddressModeV MV1_TEXTURE_ADDRESS_MODE_WRAP / MIRROR / CLAMP / BORDER
FilterMode MV1_TEXTURE_FILTER_MODE_POINT / LINEAR など
Flag MV1_TEXTURE_FLAG_REVERSE / BMP32_ALL_ZERO_ALPHA_TO_XRGB8 / VALID_SCALE_UV
ScaleU / ScaleV UV スケール (VALID_SCALE_UV 時のみ)

トライアングルリスト MV1_TRIANGLE_LIST_F1

struct MV1_TRIANGLE_LIST_F1 {
    DWORD  DimPrev, DimNext ;
    int    Index ;
    DWORD  Container ;                 // ptr to MV1_MESH_F1
    WORD   VertexType ;                // MV1_VERTEX_TYPE_NORMAL / SKIN_4BONE / SKIN_8BONE / SKIN_FREEBONE
    WORD   Flag ;                      // MV1_TRIANGLE_LIST_FLAG_* (インデックス型 + 法線型)
    WORD   VertexNum ;
    WORD   IndexNum ;
    DWORD  MeshVertexIndexAndIndexData ;
    DWORD  Padding[2] ;
} ;

MeshVertexIndexAndIndexData 構造

[VertexType == SKIN_4BONE / SKIN_8BONE の場合]
  WORD UseBoneNum ;          // 使用ボーン数
  WORD BoneIndices[UseBoneNum] ;

[VertexType == SKIN_FREEBONE の場合]
  WORD MaxBoneNum ;          // 最大使用ボーン数のみ

[VertexNum 個のメッシュ頂点インデックス]
  Flag & MV1_TRIANGLE_LIST_FLAG_MVERT_INDEX_MASK に応じた型 (u8/u16/u32)

[IndexNum 個の頂点インデックス]
  Flag & MV1_TRIANGLE_LIST_FLAG_INDEX_MASK に応じた型

スキンボーン MV1_SKIN_BONE_F1

struct MV1_SKIN_BONE_F1 {
    DWORD          DimPrev, DimNext ;
    int            Index ;
    int            BoneFrame ;                       // ボーンとして使うフレーム index
    MATRIX_4X4CT_F ModelLocalMatrix ;                // 4x4 行 3 行形式 (省略 4 行目)
    int            ModelLocalMatrixIsTranslateOnly ; // 並進のみ=1
    int            UseFrameNum ;
    DWORD          UseFrame ;                        // MV1_SKIN_BONE_USE_FRAME_F1[]
    DWORD          Padding[2] ;
} ;

ライト MV1_LIGHT_F1

MV1_LIGHT_TYPE_POINT / DIRECTIONAL / SPOT。色 3 種 (Diffuse/Specular/Ambient)、減衰係数 3 つ (Attenuation0..2)、スポット角 2 つ (Theta/Phi)、フォールオフ、有効距離。


アニメーション

3 層構造:

  1. MV1_ANIMSET_F1 — 「歩行」「攻撃」等のアニメーション集合。Name で名前アクセス可。
  2. MV1_ANIM_F1 — セット内の各アニメ。対象フレーム・回転オーダー・キーセット配列を持つ。
  3. MV1_ANIM_KEYSET_F1 — キーフレーム列本体。

キーセットのデータフォーマット

Type (MV1_ANIMKEY_TYPE_*) × DataType (ROTATE / TRANSLATE / SCALE / MATRIX / SHAPE 等) の組合せでキー格納形式が変わる。Flag ビット (MV1_ANIM_KEYSET_FLAG_*) で時間・キー値の圧縮形態を切替:

KeyData ブロックの可変長レイアウトは DxModelFile.h 130-175 行のコメントに詳細記述あり。

キー値型

16bit 補助情報 MV1_ANIM_KEY_16BIT_F1

struct MV1_ANIM_KEY_16BIT_F1 {
    BYTE Min ;   // bit7: zero指標 / bit6: 符号 / bit5: 指数符号 / bit4-0: 指数 (10^-15 ... 10^15)
    BYTE Unit ;  // bit7: 指数符号 / bit6-4: 指数 (10^-7 ... 10^7) / bit3-0: 乗算整数値 (0-15)
} ;

→ 実値 = Min * 10^sMin + unit16bit * Unit * 10^sUnit * mulInt。16bit インデックスを用いて小さなダイナミックレンジを表現。


シェイプ (モーフ) MV1_FILEHEAD_SHAPE_F1

顔表情等のモーフターゲット。MV1_SHAPE_F1 は複数の MV1_SHAPE_MESH_F1 を持ち、各々が MV1_SHAPE_VERTEX_F1[] を保持。シェイプ頂点は base からの差分を保存 (Position/Normal)。IsVertexPress=1 で頂点データ圧縮対応。


物理演算 MV1_FILEHEAD_PHYSICS_F1

Bullet Physics 用の剛体 + ジョイント情報。


座標系の注意

フィールド 意味
RightHandType TRUE で右手系 (OpenGL 系) / FALSE で左手系 (DirectX 系)
頂点 UV V 下向きが正 (画像メモリ順と一致)

loader 側で座標系変換を行うかどうかは呼び出し側次第。


文字列プール StringBuffer

Name / ColorFilePath 等は "文字列プール" に連結格納された null 終端文字列の先頭からのバイトオフセット。IsStringUTF8 フラグで UTF-8 / Shift-JIS を選択。


バージョニング

現行は Version = 1 のみ。将来の "MV12" 等の拡張に備え、loader は CheckID == "MV11" のみ受理する設計。


制限事項 / 注意点

  1. DXA 圧縮は必須 — 非圧縮の 4 byte 以降を直接書いても読めない。DXA_Encode 経由でのみ生成。
  2. ポインタ相当 DWORD — 32bit バイトオフセット。2GB 超のモデルは未対応。
  3. Padding フィールド — 構造体サイズを 4/8 byte 境界に揃えるための 0 埋め。将来の拡張予約。
  4. エンディアン — リトルエンディアン固定 (big-endian プラットフォームでは loader 側で変換要)。
  5. PMX 由来モデルのトゥーン輪郭MV1_MESH_VERT_FLAG_NON_TOON_OUTLINE フラグで頂点単位に ON/OFF 指定。
  6. LightData (MV1_LIGHT_F1)MV1_LIGHT_F1MV1_FRAME_F1.Light 経由でフレームに属する (独立ライトリストは Light / LightNum 参照)。
  7. ⚠️ ChangeMatrixTable / ChangeDrawMaterialTable は 0 にしてはいけない — 詳細は Writer の落とし穴 を参照。

Writer の落とし穴

DxLib 互換の .mv1 を自作 writer で生成する際、loader で 必ず crash する罠を以下に列挙。

⚠️ ChangeMatrixTable / ChangeDrawMaterialTable は必ず非 0 の buffer を指すべき

フィールド意味
ChangeDrawMaterialTableSizeint描画マテリアル変更追跡用 bit-flag table のバイト数
ChangeMatrixTableSizeint行列変更追跡用 bit-flag table のバイト数
ChangeDrawMaterialTableDWORD (offset)上記 bit-flag table の実データ位置
ChangeMatrixTableDWORD (offset)同上

これらを 0 / 空にしてはいけない。DxLib は frame / mesh / material の状態変化を 1 bit / element で追跡する runtime テーブルを持ち、load 時にこの領域を _MEMCPY して MBase->ChangeMatrixTable 等のポインタに設定する。サイズ 0 で確保すると runtime の bit 操作で境界外 write が発生し、debug allocator の MagicID tag 破壊 → GetAllocSize Error : メモリタグの MagicID が不正です → crash に至る。

データ依存で再現する 厄介な症状で、frame 数が閾値 (5 前後、buffer 配置次第) を超えると初めて crash する。試行錯誤で原因特定が非常に困難。

正しい書き方

// Writer 実装例 (C++)
const int32_t changeTableSize = 256;  // 2048 bit、frame 数に応じて可変でも可
uint32_t offChangeDrawMatTable = append_zero_bytes(changeTableSize);  // 全 0
uint32_t offChangeMatTable     = append_zero_bytes(changeTableSize);  // 全 0

hdr.ChangeDrawMaterialTableSize = changeTableSize;
hdr.ChangeDrawMaterialTable     = offChangeDrawMatTable;
hdr.ChangeMatrixTableSize       = changeTableSize;
hdr.ChangeMatrixTable           = offChangeMatTable;

MV1_FRAME_F1 / MV1_MESH_F1ChangeInfo (MV1_CHANGE_F1) フィールドはすべて 0 で OK (runtime で table への offset を逆算するため、Target / Fill = 0 のままで一貫している)。

⚠️ NON_TOON_OUTLINE フラグを立てたら bit data を忘れずに

MV1_MESH_F1.VertFlag bit 6 (MV1_MESH_VERT_FLAG_NON_TOON_OUTLINE) を立てた場合、VertexData blob の末尾に VertexNum ビット分のデータ を書く必要がある (各頂点の ToonOutLineScale > 0 か否か 1 bit)。省略するとフラグが立っているのに bit data が無いとして load 時に overrun する。

全頂点を「輪郭なし」にするなら 0xFF で埋める (bit=1 = ToonOutLineScale=0)。その後 4 byte 整列までゼロ padding。

⚠️ TriangleListNormalPositionNum は per-corner 合計であるべき

非 skin の MV1_VERTEX_TYPE_NORMAL な triangle list では、この値は 全 triangle list の VertexNum 合計 でなければならない (DxLib は MV1_TLIST_NORMAL_POS[N] のメモリを N 個分確保し、各 TL が自分の VertexNum 分をスライス消費する)。ずれると 16 byte align 再計算で末尾の TL が overflow する。

⚠️ Skin mesh では TriangleListSkinPosition4BNum に per-corner 合計を入れる (非 skin は 0)

MV1_VERTEX_TYPE_SKIN_4BONE の triangle list を含む場合、DxLib は runtime の MV1_TLIST_SKIN_POS_4B[N] 配列を確保する。カウンタは TriangleListSkinPosition4BNum。非 skin 用の TriangleListNormalPositionNum はこのとき 0 でなければならない (両方同時に立てると allocation 境界で overlap → crash)。

VertexTypeTriangleListNormalPositionNumTriangleListSkinPosition4BNum
MV1_VERTEX_TYPE_NORMALsum(TL.VertexNum)0
MV1_VERTEX_TYPE_SKIN_4BONE0sum(TL.VertexNum)
MV1_VERTEX_TYPE_SKIN_8BONE00 (SkinPosition8BNum を使う)

⚠️ Skin mesh では MeshPositionSize = Σ(PositionNum × 44)、非 skin は × 12

DxLib の runtime では MV1_MESH_POSITION 構造体サイズは MaxBoneBlendNum 依存で可変:

PosUnitSize = sizeof(MV1_MESH_POSITION:44) + (MaxBoneBlendNum - 4) × sizeof(MV1_SKINBONE_BLEND:8)
ModeMaxBoneBlendNumPosUnitSize内訳
非 skin012Position (VECTOR=12) のみ、BoneWeight スロット無し
4 ボーン skin444Position (12) + BoneWeight[4] (32)
8 ボーン skin876Position (12) + BoneWeight[8] (64)

Header の MeshPositionSize は全フレーム分の合計: Σ(Frame.PositionNum × PosUnitSize)。非 skin と同じ × 12 で書くと、skin 時は allocation が小さすぎて隣接バッファを上書きしてクラッシュ。

⚠️ Frame 階層は DxLib save と同じ形に

空の root を挟むと一部の FirstChild / LastChild 走査で特定条件下に crash。


参考: MV1LoadModel の主な処理フロー

1. "MV11" マジック確認
2. DXA_Decode で伸長後サイズ取得 → 確保 → 再 DXA_Decode
3. MV1MODEL_FILEHEADER_F1 を解析
4. オフセット → ポインタ変換 (全ての配列とネスト構造)
5. 文字列プールから Name / FilePath 等を wchar_t にコピー
6. MV1_MODEL_BASE 構造体 (ランタイム表現) を malloc 一括確保してフィールド単位コピー
7. テクスチャファイルを LoadGraph で順次ロード (ColorFilePath / AlphaFilePath)
8. ハンドル値を返却 (DX_HANDLETYPE_MODEL)

ファイル構造参考図

┌─────────────────────────────────────────────┐
│ Magic "MV11" (4 bytes)                      │
├─────────────────────────────────────────────┤
│ DXA compressed block                        │
│  ╭─────────────────────────────────╮        │
│  │ MV1MODEL_FILEHEADER_F1 (~128B)  │        │
│  │   CheckID / Version             │        │
│  │   FrameNum + Frame ptr          │────┐   │
│  │   MaterialNum + Material ptr    │──┐ │   │
│  │   TextureNum + Texture ptr      │┐ │ │   │
│  │   MeshNum + Mesh ptr            │┼─┐ │   │
│  │   TriangleListNum + TL ptr      │┼│┼─┐   │
│  │   SkinBoneNum + SkinBone ptr    │┼│┼│┼─┐ │
│  │   VertexData ptr                │┼│┼│┼│┼─┐
│  │   StringBuffer ptr              │┼│┼│┼│┼│┼─┐
│  │   AnimSetNum + AnimSet ptr      │┼│┼│┼│┼│┼│┼─┐
│  │   Shape ptr / Physics ptr       │┼│┼│┼│┼│┼│┼│┼─┐
│  ╰─────────────────────────────────╯│ │ │ │ │ │  │
│  ╭──────────────────────────────────┘ │ │ │ │ │  │
│  │ MV1_TEXTURE_F1 [TextureNum]        │ │ │ │ │  │
│  ╰────────────────────────────────────┘ │ │ │ │  │
│  ╭──────────────────────────────────────┘ │ │ │  │
│  │ MV1_MATERIAL_F1 [MaterialNum]          │ │ │  │
│  ╰────────────────────────────────────────┘ │ │  │
│  ╭──────────────────────────────────────────┘ │  │
│  │ MV1_FRAME_F1 [FrameNum]                    │  │
│  │   with PositionAndNormalData inline        │  │
│  ╰────────────────────────────────────────────┘  │
│  ╭──────────────────────────────────────────────┘
│  │ MV1_MESH_F1 [MeshNum]                       
│  │   with VertexData inline                    
│  ╰────────────────────────────────────────────
│  ...
│  ╭────────────────────────────────────────────
│  │ StringBuffer (UTF-8 or Shift-JIS, null-concat)
│  ╰────────────────────────────────────────────
│  ╭────────────────────────────────────────────
│  │ AnimKeyData (可変長、フラグで圧縮形式切替)
│  ╰────────────────────────────────────────────
└─────────────────────────────────────────────┘

生成ツール

MV1SaveModelToMV1File() の引数:

圧縮フラグを立てるとファイル容量は 40〜60% 削減されるが精度低下に注意。


このドキュメントは DxLib 3.24f 時点の .mv1 フォーマット (Version 1, "MV11" magic) を対象とする。将来のバージョン拡張には未対応。


付録 A: 全 enum 値一覧

本文で参照する定数の実数値。出典: DxLib.h / DxModel.h / DxModelFile.h

A.1 MV1_VERTEX_TYPE_* — 頂点型 (MV1_TRIANGLE_LIST_F1.VertexType)

名称 意味
MV1_VERTEX_TYPE_NORMAL 0 通常メッシュ (スキンなし)
MV1_VERTEX_TYPE_SKIN_4BONE 1 スキンメッシュ 1〜4 ボーン
MV1_VERTEX_TYPE_SKIN_8BONE 2 スキンメッシュ 5〜8 ボーン
MV1_VERTEX_TYPE_SKIN_FREEBONE 3 9 ボーン以上の可変長スキン

A.2 MV1_ROTATE_TYPE_* — 回転表現

名称 意味
MV1_ROTATE_TYPE_XYZROT 0 Euler 角 (XYZ 軸)
MV1_ROTATE_TYPE_QUATERNION 1 クォータニオン
MV1_ROTATE_TYPE_MATRIX 2 行列 (MATRIX_4X4CT_F)
MV1_ROTATE_TYPE_ZAXIS 3 Z 軸 + ローカルベクトル + スケール

A.3 MV1_ROTATE_ORDER_* — Euler 角オーダー

名称
MV1_ROTATE_ORDER_XYZ 0
MV1_ROTATE_ORDER_XZY 1
MV1_ROTATE_ORDER_YZX 2
MV1_ROTATE_ORDER_YXZ 3
MV1_ROTATE_ORDER_ZXY 4
MV1_ROTATE_ORDER_ZYX 5

A.4 MV1_ANIMKEY_TYPE_* — アニメキーのデータエンコード

名称 意味
MV1_ANIMKEY_TYPE_QUATERNION_X 0 クォータニオン (X ファイル形式)
MV1_ANIMKEY_TYPE_VECTOR 1 ベクトル
MV1_ANIMKEY_TYPE_MATRIX4X4C 2 4x4 行列 (4 行目固定)
MV1_ANIMKEY_TYPE_MATRIX3X3 3 3x3 行列
MV1_ANIMKEY_TYPE_FLAT 4 定数補間 (階段関数)
MV1_ANIMKEY_TYPE_LINEAR 5 線形補間
MV1_ANIMKEY_TYPE_BLEND 6 スプライン補間
MV1_ANIMKEY_TYPE_QUATERNION_VMD 7 クォータニオン (VMD 形式)

A.5 MV1_ANIMKEY_DATATYPE_* — アニメキーの対象

名称 意味
MV1_ANIMKEY_DATATYPE_ROTATE 0 全軸回転
MV1_ANIMKEY_DATATYPE_ROTATE_X 1 X 軸回転
MV1_ANIMKEY_DATATYPE_ROTATE_Y 2 Y 軸回転
MV1_ANIMKEY_DATATYPE_ROTATE_Z 3 Z 軸回転
MV1_ANIMKEY_DATATYPE_SCALE 5 全軸スケール
MV1_ANIMKEY_DATATYPE_SCALE_X 6 X スケール
MV1_ANIMKEY_DATATYPE_SCALE_Y 7 Y スケール
MV1_ANIMKEY_DATATYPE_SCALE_Z 8 Z スケール
MV1_ANIMKEY_DATATYPE_TRANSLATE 10 全軸並進
MV1_ANIMKEY_DATATYPE_TRANSLATE_X 11 X 並進
MV1_ANIMKEY_DATATYPE_TRANSLATE_Y 12 Y 並進
MV1_ANIMKEY_DATATYPE_TRANSLATE_Z 13 Z 並進
MV1_ANIMKEY_DATATYPE_MATRIX4X4C 15 4x4 行列
MV1_ANIMKEY_DATATYPE_MATRIX3X3 17 3x3 行列
MV1_ANIMKEY_DATATYPE_SHAPE 18 シェイプ (モーフ) ウェイト
MV1_ANIMKEY_DATATYPE_OTHRE 20 その他 (typo は原文ママ: OTHER ではなく OTHRE)

A.6 MV1_LIGHT_TYPE_*

名称
MV1_LIGHT_TYPE_POINT 0
MV1_LIGHT_TYPE_SPOT 1
MV1_LIGHT_TYPE_DIRECTIONAL 2

A.7 MV1_FRAMEFLAG_* (ビットフラグ)

名称 bit 意味
MV1_FRAMEFLAG_VISIBLE 0x0001 表示
MV1_FRAMEFLAG_IGNOREPARENTTRANS 0x0002 親の変換を無視
MV1_FRAMEFLAG_PREROTATE 0x0004 前回転 (PreRotate) 有効
MV1_FRAMEFLAG_POSTROTATE 0x0008 後回転 (PostRotate) 有効
MV1_FRAMEFLAG_TANGENT_BINORMAL 0x0010 接線 / 従法線データあり

A.8 DX_MATERIAL_TYPE_* (MV1_MATERIAL_TOON_F1.Type)

名称 意味
DX_MATERIAL_TYPE_NORMAL 0 通常
DX_MATERIAL_TYPE_TOON 1 トゥーン
DX_MATERIAL_TYPE_TOON_2 2 トゥーン (MMD 互換)
DX_MATERIAL_TYPE_MAT_SPEC_LUMINANCE_UNORM 3 スペキュラ輝度 (0..1 正規化)
DX_MATERIAL_TYPE_MAT_SPEC_LUMINANCE_CLIP_UNORM 4 同 (クリップ付き)
DX_MATERIAL_TYPE_MAT_SPEC_LUMINANCE_CMP_GREATEREQUAL 5 同 (≥ 比較)
DX_MATERIAL_TYPE_MAT_SPEC_POWER_UNORM 6 スペキュラ強度
DX_MATERIAL_TYPE_MAT_SPEC_POWER_CLIP_UNORM 7 同 (クリップ付き)
DX_MATERIAL_TYPE_MAT_SPEC_POWER_CMP_GREATEREQUAL 8 同 (≥ 比較)

A.9 DX_MATERIAL_BLENDTYPE_* (レイヤブレンド種別)

名称
DX_MATERIAL_BLENDTYPE_TRANSLUCENT 0
DX_MATERIAL_BLENDTYPE_ADDITIVE 1
DX_MATERIAL_BLENDTYPE_MODULATE 2
DX_MATERIAL_BLENDTYPE_NONE 3

A.10 MV1_LAYERBLEND_TYPE_*

名称
MV1_LAYERBLEND_TYPE_TRANSLUCENT 0
MV1_LAYERBLEND_TYPE_ADDITIVE 1
MV1_LAYERBLEND_TYPE_MODULATE 2
MV1_LAYERBLEND_TYPE_MODULATE2 3

A.11 DX_BLENDMODE_* (描画時ブレンド、MV1_MATERIAL_F1.DrawBlendMode)

主要値のみ (全 38 種):

名称
DX_BLENDMODE_NOBLEND 0
DX_BLENDMODE_ALPHA 1
DX_BLENDMODE_ADD 2
DX_BLENDMODE_SUB 3
DX_BLENDMODE_MUL 4
DX_BLENDMODE_INVSRC 10
DX_BLENDMODE_PMA_ALPHA 17
DX_BLENDMODE_PMA_ADD 18
DX_BLENDMODE_CUSTOM 32

A.12 MV1_TEXTURE_ADDRESS_MODE_*

名称
MV1_TEXTURE_ADDRESS_MODE_WRAP 0
MV1_TEXTURE_ADDRESS_MODE_MIRROR 1
MV1_TEXTURE_ADDRESS_MODE_CLAMP 2

A.13 MV1_TEXTURE_FILTER_MODE_*

名称
MV1_TEXTURE_FILTER_MODE_POINT 0
MV1_TEXTURE_FILTER_MODE_LINEAR 1
MV1_TEXTURE_FILTER_MODE_ANISOTROPIC 2

A.14 MV1_ANIM_KEYSET_FLAG_* (16bit ビットフラグ)

名称 bit
MV1_ANIM_KEYSET_FLAG_KEY_ONE 0x0001
MV1_ANIM_KEYSET_FLAG_KEYNUM_B 0x0002
MV1_ANIM_KEYSET_FLAG_KEYNUM_W 0x0004
MV1_ANIM_KEYSET_FLAG_TIME_UNIT 0x0008
MV1_ANIM_KEYSET_FLAG_TIME_UNIT_ST_W 0x0010
MV1_ANIM_KEYSET_FLAG_TIME_UNIT_ST_Z 0x0020
MV1_ANIM_KEYSET_FLAG_TIME_UNIT_UN_W 0x0040
MV1_ANIM_KEYSET_FLAG_TIME_BIT16 0x0080
MV1_ANIM_KEYSET_FLAG_KEY_BIT16 0x0100
MV1_ANIM_KEYSET_FLAG_KEY_MP_PP 0x0200
MV1_ANIM_KEYSET_FLAG_KEY_Z_TP 0x0400

A.15 MV1_FRAME_VERT_FLAG_*

名称 bit
MV1_FRAME_VERT_FLAG_NORMAL_TYPE_MASK 0x0003
MV1_FRAME_VERT_FLAG_POSITION_B16 0x0004
MV1_FRAME_VERT_FLAG_MATRIX_WEIGHT_NONE 0x0008
MV1_FRAME_VERT_FLAG_MATRIX_INDEX_MASK 0x0010
MV1_FRAME_VERT_FLAG_MATRIX_WEIGHT_MASK 0x0020
MV1_FRAME_VERT_FLAG_NOMRAL_TANGENT_BINORMAL 0x0040

A.16 MV1_MESH_VERT_FLAG_*

名称 bit
MV1_MESH_VERT_FLAG_POS_IND_TYPE_MASK 0x0003
MV1_MESH_VERT_FLAG_NRM_IND_TYPE_MASK 0x000c
MV1_MESH_VERT_FLAG_UV_U16 0x0010
MV1_MESH_VERT_FLAG_COMMON_COLOR 0x0020
MV1_MESH_VERT_FLAG_NON_TOON_OUTLINE 0x0040

A.17 MV1_TRIANGLE_LIST_FLAG_*

名称 bit
MV1_TRIANGLE_LIST_FLAG_MVERT_INDEX_MASK 0x0003
MV1_TRIANGLE_LIST_FLAG_INDEX_MASK 0x000c

A.18 MV1_TEXTURE_FLAG_*

名称 bit
MV1_TEXTURE_FLAG_REVERSE 0x0001
MV1_TEXTURE_FLAG_BMP32_ALL_ZERO_ALPHA_TO_XRGB8 0x0002
MV1_TEXTURE_FLAG_VALID_SCALE_UV 0x0004

A.19 各種型コード

MV1_FRAME_NORMAL_TYPE_* (2 bit):

名称 サイズ/頂点
NONE 0 0 (自動計算)
S8 1 3 byte
S16 2 6 byte
F32 3 12 byte

MV1_FRAME_MATRIX_INDEX_TYPE_* / MV1_FRAME_MATRIX_WEIGHT_TYPE_* (各 1 bit):

名称 サイズ
U8 0 1 byte
U16 1 2 byte

MV1_MESH_VERT_INDEX_TYPE_* (2 bit):

名称 サイズ
NONE 0 0
U8 1 1 byte
U16 2 2 byte
U32 3 4 byte

MV1_TRIANGLE_LIST_INDEX_TYPE_* (2 bit):

名称 サイズ
U8 0 1 byte
U16 1 2 byte
U32 2 4 byte

A.20 ハンドル型

名称
DX_HANDLETYPE_MODEL_BASE 13
DX_HANDLETYPE_MODEL 14

MATERIAL_TYPEPARAM_MAX_NUM = 4 (MV1_MATERIAL_BASE.TypeParam の要素数)


付録 B: 可変長セクションの byte レイアウト詳細

B.1 MV1_MESH_F1.VertexData

MV1_MESH_F1.VertFlagUVUnitNum / UVSetUnitNum に応じて可変長。 VertexData の読み出し順序:

┌─────────────────────────────────────────────────────┐
│ [step 1] 共通色 (VertFlag & COMMON_COLOR が true)   │
│    COLOR_U8 DiffuseColor ;  // 4 byte (B,G,R,A)     │
│    COLOR_U8 SpecularColor ; // 4 byte               │
├─────────────────────────────────────────────────────┤
│ [step 2] 位置インデックス × VertexNum               │
│    size_per_elem = sizeof_index_type(               │
│        VertFlag & MV1_MESH_VERT_FLAG_POS_IND_TYPE_MASK)│
│    合計サイズ = VertexNum * size_per_elem           │
├─────────────────────────────────────────────────────┤
│ [step 3] 法線インデックス × VertexNum               │
│    size_per_elem = sizeof_index_type(               │
│        (VertFlag & MV1_MESH_VERT_FLAG_NRM_IND_TYPE_MASK) >> 2)│
├─────────────────────────────────────────────────────┤
│ [step 4] 頂点色 × VertexNum (COMMON_COLOR が false) │
│    COLOR_U8 Diffuse + COLOR_U8 Specular = 8 byte/頂点│
├─────────────────────────────────────────────────────┤
│ [step 5] UV 値 × VertexNum × (UVUnitNum * UVSetUnitNum)│
│    UV_U16 フラグ立ち: WORD (16bit 固定少数、65535=1.0) │
│    UV_U16 フラグ無し: float                         │
│    各 UV ペアは (u, v) の 2 成分                    │
│    合計 = VertexNum * UVUnitNum * UVSetUnitNum * 2 *│
│           (UV_U16 ? 2 : 4)                          │
├─────────────────────────────────────────────────────┤
│ [step 6] トゥーン輪郭抑止ビット × VertexNum         │
│    (NON_TOON_OUTLINE フラグが立っている場合のみ)    │
│    1 頂点 1 bit、LSB から詰める                     │
│    合計 = ceil( VertexNum / 8 ) byte                │
└─────────────────────────────────────────────────────┘

疑似コード:

size_t sizeof_index_type( int type_code ) {
    switch ( type_code ) {
    case 0 /*NONE*/: return 0 ;
    case 1 /*U8*/  : return 1 ;
    case 2 /*U16*/ : return 2 ;
    case 3 /*U32*/ : return 4 ;
    }
    return 0 ;
}

size_t calc_vertex_data_size( MV1_MESH_F1 *m ) {
    size_t s = 0 ;
    if ( m->VertFlag & 0x0020 /*COMMON_COLOR*/ ) s += 8 ;
    int posType = m->VertFlag & 0x3 ;
    int nrmType = ( m->VertFlag >> 2 ) & 0x3 ;
    s += (size_t)m->VertexNum * sizeof_index_type( posType ) ;
    s += (size_t)m->VertexNum * sizeof_index_type( nrmType ) ;
    if ( !( m->VertFlag & 0x0020 ) ) s += (size_t)m->VertexNum * 8 ;
    int uvUnit = m->UVUnitNum * m->UVSetUnitNum ;
    size_t uvElem = ( m->VertFlag & 0x0010 /*UV_U16*/ ) ? 2 : 4 ;
    s += (size_t)m->VertexNum * uvUnit * 2 * uvElem ;
    if ( m->VertFlag & 0x0040 /*NON_TOON_OUTLINE*/ )
        s += ( (size_t)m->VertexNum + 7 ) / 8 ;
    return s ;
}

B.2 MV1_FRAME_F1.PositionAndNormalData

VertFlag + PositionNum / NormalNum / MaxBoneBlendNum に応じて可変:

┌─────────────────────────────────────────────────────┐
│ [step 1] POSITION_B16 フラグ立ちの時のみ            │
│    MV1_POSITION_16BIT_SUBINFO_F1 xSubinfo ; /* 8B */│
│    MV1_POSITION_16BIT_SUBINFO_F1 ySubinfo ; /* 8B */│
│    MV1_POSITION_16BIT_SUBINFO_F1 zSubinfo ; /* 8B */│
│       struct { float Min; float Width; } ;          │
├─────────────────────────────────────────────────────┤
│ [step 2] 座標データ × PositionNum                   │
│    POSITION_B16 立っていれば WORD[3] (6 byte)       │
│                 立っていなければ float[3] (12 byte) │
│                                                     │
│    WORD 値 w (0..65535) からの復元:                 │
│       x = xSubinfo.Min + (w / 65535.0) * xSubinfo.Width│
│       (y, z も同様)                                 │
├─────────────────────────────────────────────────────┤
│ [step 3] スキニング情報 (!MATRIX_WEIGHT_NONE の場合)│
│    座標 × PositionNum 回:                           │
│      MaxBoneBlendNum ボーンぶん or インデックス=-1ま│
│       {                                             │
│         bone_index (U8 or U16, MATRIX_INDEX_MASK 依存)│
│         weight     (U8 or U16, MATRIX_WEIGHT_MASK 依存)│
│       }                                             │
│    終端条件: -1 (U8=0xFF, U16=0xFFFF) を bone_index │
│              で読んだら、その weight は読まず次頂点へ│
├─────────────────────────────────────────────────────┤
│ [step 4] 法線データ × NormalNum                     │
│    (VertFlag & NORMAL_TYPE_MASK) に応じて型:        │
│      NONE (0) = スキップ                            │
│      S8   (1) = s8 × 3    (3 byte)                  │
│      S16  (2) = s16 × 3   (6 byte)                  │
│      F32  (3) = float × 3 (12 byte)                 │
│                                                     │
│    S8/S16 の場合は (-1.0 .. +1.0) を (-127..127) /  │
│    (-32767..32767) にマップ。デコード時に正規化要。 │
│                                                     │
│    TANGENT_BINORMAL フラグ立ち時は法線 3 成分の後に │
│    接線 + 従法線の 2 ベクトル (同じ型) が続く       │
└─────────────────────────────────────────────────────┘

B.3 MV1_TRIANGLE_LIST_F1.MeshVertexIndexAndIndexData

┌─────────────────────────────────────────────────────┐
│ [step 1] 使用ボーン情報 (VertexType 依存)           │
│                                                     │
│   NORMAL (0):        なし (0 byte)                  │
│                                                     │
│   SKIN_4BONE (1):                                   │
│     WORD UseBoneNum ;                               │
│     WORD BoneIndices[ UseBoneNum ] ;                │
│                                                     │
│   SKIN_8BONE (2):                                   │
│     WORD UseBoneNum ;                               │
│     WORD BoneIndices[ UseBoneNum ] ;                │
│                                                     │
│   SKIN_FREEBONE (3):                                │
│     WORD MaxBoneNum ;  /* 1 頂点あたりの最大使用数 */│
├─────────────────────────────────────────────────────┤
│ [step 2] メッシュ頂点インデックス × VertexNum       │
│     型: (Flag & MVERT_INDEX_MASK) に応じて U8/U16/U32│
├─────────────────────────────────────────────────────┤
│ [step 3] 頂点インデックス × IndexNum                │
│     型: ((Flag & INDEX_MASK) >> 2) に応じて U8/U16/U32│
│     3 個で 1 三角形                                 │
└─────────────────────────────────────────────────────┘

SKIN_FREEBONE の追加情報: MaxBoneNum は 1 頂点が参照する最大ボーン数。各頂点のボーン並びは別途 MV1_FRAME_F1.PositionAndNormalData 内に格納される (step 3 の可変長スキニング情報、-1 終端)。

B.4 文字列プール StringBuffer のアクセス

const char *get_string( uint8_t *base, uint32_t strOffset ) {
    return (const char *)( base + header->StringBuffer + strOffset ) ;
}

付録 C: 16bit 圧縮値のデコード式

アニメキー値を 16bit で格納する場合、MV1_ANIM_KEY_16BIT_F1 補助構造体 2 byte で「最小値」と「単位」を記述し、16bit index からの浮動小数復元は以下のビットパック:

C.1 MV1_ANIM_KEY_16BIT_F1

struct MV1_ANIM_KEY_16BIT_F1 {
    uint8_t Min  ;   /* 最小値の符号付き浮動小数 (ビットパック) */
    uint8_t Unit ;   /* 16bit 値 1 あたりの刻み値 (ビットパック)   */
} ;

Min のビットレイアウト (1 byte):

ビット 意味
bit 7 ゼロフラグ (1=Min が 0、以下無視)
bit 6 符号 (0=正, 1=負)
bit 5 指数符号 (0=正指数, 1=負指数)
bit 4-0 指数値 (0..31、実指数は 0..15)

復元式:

if ( bit7 == 1 ) : minVal = 0.0
else            : sign = ( bit6 ? -1.0 : +1.0 )
                  expSign = ( bit5 ? -1 : +1 )
                  exponent = (bit4..bit0) & 0x1F
                  minVal = sign * 10^( expSign * exponent )

Unit のビットレイアウト (1 byte):

ビット 意味
bit 7 指数符号 (0=正, 1=負)
bit 6-4 指数値 (0..7、実指数は 0..7)
bit 3-0 乗算整数値 (0..15)

復元式:

expSign = ( bit7 ? -1 : +1 )
exponent = (bit6..bit4) & 0x07
mulInt   = (bit3..bit0) & 0x0F     /* 0 の場合は 1 扱い */
unitVal  = mulInt * 10^( expSign * exponent )

C.2 16bit キー index から実値へ

u16 rawIndex ;   /* 0..65535 */
float realValue = minVal + rawIndex * unitVal ;

C.3 特別エンコード (MV1_ANIM_KEYSET_FLAG_KEY_MP_PP / _Z_TP)

MP_PP フラグが立っている場合、16bit index 0..65535 は [-π, +π] に直接マッピングされる:

angle = ( rawIndex / 65535.0 ) * 2π - π

Z_TP フラグが立っている場合は [0, 2π]:

angle = ( rawIndex / 65535.0 ) * 2π

これらのフラグ時は Min/Unit 補助情報は 省略される (2 byte 節約)。

C.4 時間値の 16bit 圧縮

TIME_BIT16 フラグ立ちで、時間軸も 16bit 化:

TIME_UNIT フラグ立ちで、全キーが等間隔 (= 個別時刻記録を省略):

C.5 キー数の可変長圧縮

フラグ キー数の格納
KEY_ONE キー数 = 1 (格納なし)
KEYNUM_B 1 byte (0..255)
KEYNUM_W 2 byte (0..65535)
(どれも立ってない) 4 byte (DWORD)

付録 D: MATRIX_4X4CT_F / FLOAT4 の実メモリ配置

D.1 MATRIX_4X4CT_F

4 行 3 列の row-major レイアウト。4 行目は暗黙的に (0, 0, 0, 1) 固定。

typedef struct {
    float m[ 4 ][ 3 ] ;   /* row-major: m[row][col] */
} MATRIX_4X4CT_F ;

メモリ上の並び (48 byte = 12 float):

Offset  Element
0x00    m[0][0]   Basis X axis .x
0x04    m[0][1]   Basis X axis .y
0x08    m[0][2]   Basis X axis .z
0x0C    m[1][0]   Basis Y axis .x
0x10    m[1][1]   Basis Y axis .y
0x14    m[1][2]   Basis Y axis .z
0x18    m[2][0]   Basis Z axis .x
0x1C    m[2][1]   Basis Z axis .y
0x20    m[2][2]   Basis Z axis .z
0x24    m[3][0]   Translation .x
0x28    m[3][1]   Translation .y
0x2C    m[3][2]   Translation .z

並進成分は m[3][0..2] (4 行目の最初の 3 要素)。

D.2 完全 4x4 への展開

[m[0][0]  m[0][1]  m[0][2]  0]
[m[1][0]  m[1][1]  m[1][2]  0]
[m[2][0]  m[2][1]  m[2][2]  0]
[m[3][0]  m[3][1]  m[3][2]  1]

DxLib の変換規約は 行ベクトル × 行列 (v' = v * M)。すなわち、点 p = (px, py, pz, 1) を左から掛けると:

p' = p * M
p'.x = px*m[0][0] + py*m[1][0] + pz*m[2][0] + 1*m[3][0]
p'.y = px*m[0][1] + py*m[1][1] + pz*m[2][1] + 1*m[3][1]
p'.z = px*m[0][2] + py*m[1][2] + pz*m[2][2] + 1*m[3][2]

D.3 FLOAT4 (クォータニオン用)

typedef struct {
    float x, y, z, w ;
} FLOAT4 ;

MV1_FRAME_F1.Quaternion の格納順は (x, y, z, w)。w が最後。ほとんどの 3D ライブラリと同じ順だが、一部の他形式 (例えば .x ファイル) は (w, x, y, z) なので変換時注意。

D.4 COLOR_FCOLOR_U8

typedef struct { float r, g, b, a ; } COLOR_F ;   /* 16 byte */
typedef struct { uint8_t r, g, b, a ; } COLOR_U8 ; /* 4 byte */

警告: COLOR_U8 は DxLib 内部では実際には b, g, r, a の順でメモリに格納 される (BGRA バイト順。DirectX ARGB と互換)。この順序は DxLib.h の定義で確認済。.mv1 内の頂点色でもこの BGRA 順で書き出される。

D.5 VECTOR

typedef struct { float x, y, z ; } VECTOR ;   /* 12 byte */

D.6 Endian / alignment


付録 E: リファレンス C パーサ (最小読み取り実装)

.mv1 ファイルを開いて、モデルの主要情報 (頂点数 / 三角形数 / マテリアル数 / テクスチャパス) を標準出力する最小コード例。C99 移植性を重視し外部依存なし。

/* mv1_dump.c - .mv1 ファイルの最小ダンパ */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/* ---- DXA decode (付録 A の疑似コード実装) ---- */

static uint32_t rd_u32( const uint8_t *p ) {
    return ( uint32_t )p[ 0 ]
         | ( ( uint32_t )p[ 1 ] << 8 )
         | ( ( uint32_t )p[ 2 ] << 16 )
         | ( ( uint32_t )p[ 3 ] << 24 ) ;
}
static uint16_t rd_u16( const uint8_t *p ) {
    return ( uint16_t )p[ 0 ] | ( ( uint16_t )p[ 1 ] << 8 ) ;
}

static int dxa_decode( const uint8_t *src, size_t srcLen, uint8_t *dst, uint32_t *origSizeOut )
{
    if ( srcLen < 9 ) return -1 ;
    uint32_t origSize = rd_u32( src ) ;
    uint32_t compSize = rd_u32( src + 4 ) ;
    uint8_t  key      = src[ 8 ] ;
    if ( origSizeOut ) *origSizeOut = origSize ;
    if ( dst == NULL )   return 0 ;
    if ( srcLen < compSize ) return -1 ;

    const uint8_t *sp = src + 9 ;
    const uint8_t *spEnd = src + compSize ;
    uint8_t *dp = dst ;

    while ( sp < spEnd ) {
        if ( *sp != key ) { *dp++ = *sp++ ; continue ; }
        if ( sp[ 1 ] == key ) { *dp++ = key ; sp += 2 ; continue ; }

        uint8_t flags = sp[ 1 ] ;
        if ( flags > key ) flags-- ;
        sp += 2 ;

        uint32_t length = ( uint32_t )( flags >> 3 ) ;
        if ( flags & 0x04 ) { length |= ( uint32_t )( *sp++ ) << 5 ; }
        length += 4 ;

        uint32_t dist = 0 ;
        switch ( flags & 0x03 ) {
            case 0: dist = *sp++ ; break ;
            case 1: dist = rd_u16( sp ) ; sp += 2 ; break ;
            case 2: dist = rd_u16( sp ) | ( ( uint32_t )sp[ 2 ] << 16 ) ; sp += 3 ; break ;
        }
        dist += 1 ;

        if ( dist < length ) {
            uint32_t part = dist ;
            while ( length > part ) {
                memmove( dp, dp - part, part ) ;
                dp += part ; length -= part ; part += part ;
            }
            memmove( dp, dp - ( part / 2 ) - ( dist > part / 2 ? ( dist - part / 2 ) : 0 ), length ) ;
            dp += length ;
        } else {
            memmove( dp, dp - dist, length ) ;
            dp += length ;
        }
    }
    return 0 ;
}

/* ---- MV1 主要構造体 (read-only、padding は必要最小限まで無視) ---- */

#pragma pack(push, 4)
typedef struct { float r, g, b, a ; } COLOR_F ;

typedef struct {
    uint8_t CheckID[ 4 ] ;
    uint32_t Version ;
    int32_t  RightHandType ;
    int32_t  AutoCreateNormal ;
    int32_t  ChangeDrawMaterialTableSize ;
    int32_t  ChangeMatrixTableSize ;
    uint32_t ChangeDrawMaterialTable ;
    uint32_t ChangeMatrixTable ;
    int32_t  FrameNum ;
    uint32_t Frame ;
    int32_t  TopFrameNum ;
    uint32_t FirstTopFrame ;
    uint32_t LastTopFrame ;
    int32_t  FrameUseSkinBoneNum ;
    uint32_t FrameUseSkinBone ;
    int32_t  MaterialNum ;
    uint32_t Material ;
    int32_t  TextureNum ;
    uint32_t Texture ;
    int32_t  MeshNum ;
    uint32_t Mesh ;
    int32_t  LightNum ;
    uint32_t Light ;
    int32_t  SkinBoneNum ;
    uint32_t SkinBone ;
    int32_t  SkinBoneUseFrameNum ;
    uint32_t SkinBoneUseFrame ;
    int32_t  TriangleListNum ;
    uint32_t TriangleList ;
    uint32_t VertexData ;
    uint32_t VertexDataSize ;
    int32_t  TriangleListNormalPositionNum ;
    int32_t  TriangleListSkinPosition4BNum ;
    int32_t  TriangleListSkinPosition8BNum ;
    int32_t  TriangleListSkinPositionFREEBSize ;
    int32_t  MeshPositionSize ;
    int32_t  MeshNormalNum ;
    int32_t  MeshVertexSize ;
    int32_t  MeshFaceNum ;
    int32_t  MeshVertexIndexNum ;
    int32_t  TriangleListIndexNum ;
    int32_t  TriangleNum ;
    int32_t  TriangleListVertexNum ;
    int32_t  StringSize ;
    uint32_t StringBuffer ;
    /* 以降は省略可 (この demo では使わない) */
} MV1MODEL_FILEHEADER_F1 ;

typedef struct {
    uint32_t DimPrev, DimNext ;
    uint32_t Name ;
    int32_t  Index ;
    uint32_t ColorFilePath ;
    uint32_t AlphaFilePath ;
    int32_t  BumpImageFlag ;
    float    BumpImageNextPixelLength ;
    int32_t  AddressModeU ;
    int32_t  AddressModeV ;
    int32_t  FilterMode ;
    uint32_t UserData[ 2 ] ;
    uint8_t  Flag ;
    uint8_t  Padding1[ 3 ] ;
    float    ScaleU, ScaleV ;
    uint32_t Padding ;
} MV1_TEXTURE_F1 ;

typedef struct {
    uint32_t DimPrev, DimNext ;
    uint32_t Name ;
    int32_t  Index ;
    COLOR_F  Diffuse ;
    COLOR_F  Ambient ;
    COLOR_F  Specular ;
    COLOR_F  Emissive ;
    float    Power ;
    float    Alpha ;
    int32_t  DiffuseLayerNum ;
    struct { int32_t Texture ; int32_t BlendType ; uint32_t Padding[ 4 ] ; } DiffuseLayer[ 8 ] ;
    /* 以降省略 */
} MV1_MATERIAL_F1_HEAD ;
#pragma pack(pop)

/* ---- メイン ---- */

int main( int argc, char **argv ) {
    if ( argc != 2 ) { fprintf( stderr, "usage: %s file.mv1\n", argv[ 0 ] ) ; return 1 ; }

    FILE *fp = fopen( argv[ 1 ], "rb" ) ;
    if ( !fp ) { perror( argv[ 1 ] ) ; return 1 ; }
    fseek( fp, 0, SEEK_END ) ;
    size_t fileLen = ( size_t )ftell( fp ) ;
    fseek( fp, 0, SEEK_SET ) ;
    uint8_t *fileBuf = malloc( fileLen ) ;
    fread( fileBuf, 1, fileLen, fp ) ;
    fclose( fp ) ;

    if ( memcmp( fileBuf, "MV11", 4 ) != 0 ) {
        fprintf( stderr, "Not a MV11 file\n" ) ; return 1 ;
    }

    /* DXA 伸長 */
    uint32_t origSize = 0 ;
    dxa_decode( fileBuf + 4, fileLen - 4, NULL, &origSize ) ;
    uint8_t *base = malloc( origSize ) ;
    dxa_decode( fileBuf + 4, fileLen - 4, base, NULL ) ;
    free( fileBuf ) ;

    /* ルートヘッダ */
    MV1MODEL_FILEHEADER_F1 *h = ( MV1MODEL_FILEHEADER_F1 * )base ;

    printf( "=== MV1 Dump ===\n" ) ;
    printf( "Version:       %u\n", h->Version ) ;
    printf( "RightHand:     %s\n", h->RightHandType ? "yes" : "no" ) ;
    printf( "Frames:        %d\n", h->FrameNum ) ;
    printf( "Meshes:        %d\n", h->MeshNum ) ;
    printf( "Materials:     %d\n", h->MaterialNum ) ;
    printf( "Textures:      %d\n", h->TextureNum ) ;
    printf( "SkinBones:     %d\n", h->SkinBoneNum ) ;
    printf( "Triangles:     %d\n", h->TriangleNum ) ;
    printf( "TotalVertices: %d\n", h->TriangleListVertexNum ) ;
    printf( "StringPool:    %d bytes\n", h->StringSize ) ;

    /* テクスチャ一覧 */
    MV1_TEXTURE_F1 *tex = ( MV1_TEXTURE_F1 * )( base + h->Texture ) ;
    const char *strPool = ( const char * )( base + h->StringBuffer ) ;
    printf( "\n--- Textures ---\n" ) ;
    for ( int i = 0 ; i < h->TextureNum ; ++i ) {
        const char *name = tex[ i ].Name ? ( strPool + tex[ i ].Name ) : "(null)" ;
        const char *path = tex[ i ].ColorFilePath ? ( strPool + tex[ i ].ColorFilePath ) : "(null)" ;
        printf( "  [%d] name=%s  path=%s\n", i, name, path ) ;
    }

    /* マテリアル一覧 */
    printf( "\n--- Materials ---\n" ) ;
    MV1_MATERIAL_F1_HEAD *mat = ( MV1_MATERIAL_F1_HEAD * )( base + h->Material ) ;
    /* 注: MV1_MATERIAL_F1 の実サイズを調べて stride 計算が必要。このデモでは先頭のみ表示 */
    const char *mname = mat[ 0 ].Name ? ( strPool + mat[ 0 ].Name ) : "(null)" ;
    printf( "  [0] name=%s  Diffuse=(%.2f,%.2f,%.2f,%.2f)  DiffuseLayerNum=%d\n",
            mname,
            mat[ 0 ].Diffuse.r, mat[ 0 ].Diffuse.g, mat[ 0 ].Diffuse.b, mat[ 0 ].Diffuse.a,
            mat[ 0 ].DiffuseLayerNum ) ;

    free( base ) ;
    return 0 ;
}

コンパイル例

gcc -O2 -std=c99 -o mv1_dump mv1_dump.c
./mv1_dump model.mv1

想定出力

=== MV1 Dump ===
Version:       1
RightHand:     yes
Frames:        42
Meshes:        8
Materials:     8
Textures:      5
SkinBones:     38
Triangles:     12480
TotalVertices: 38450
StringPool:    1024 bytes

--- Textures ---
  [0] name=body  path=textures/body.png
  [1] name=face  path=textures/face.png
  ...

このコードの限界


改訂履歴

日時 内容
2026-04-22 22:29 初版 (概観 + 主要構造体)
2026-04-22 22:32 拡充版: DXA 圧縮仕様 + 全 enum 値 + 可変長 byte 図 + 16bit 圧縮式 + MATRIX 詳細 + リファレンス C パーサを追加