# GOM（Game Oriented Model Format)草案

# GOMフォーマット仕様書

Game Oriented Model Format (.gom)
Version 1.0 (Complete Specification)

---

# 1. 目的

GOMは、FBX等のDCCデータをゲーム実行向けに最適化し、
オフライン変換して保存するためのフォーマットである。

## 設計目標

* スキニングはモデル（geometry）空間で完結
* インスタンスごとに別Transform・別アニメ時間を安全に扱える
* FBX差（ノード階層・geometry transform）を吸収
* ワールド直スキニング事故を防止
* 実行時にFBXSDKやufbxなどのライブラリに依存しない
* 同一FBXから常に同一GOMが生成される（決定性保証）

---

# 2. 空間設計ポリシー（不変条件）

## Invariant A: 頂点空間

* 頂点は常に geometry（=モデルローカル）空間
* スキニング結果も geometry 空間
* instanceWorld は描画直前に1回のみ適用

### 禁止事項

* スキニング途中で world に焼く
* geometry_to_world を途中適用する

---

# 3. 行列規約（最終固定）

GOM v1では以下を固定する。

* 数値型：float32
* メモリ並び：row-major
* 演算規約：row-vector 右掛け（p' = p * M）
* 行列は常に 4x4
* 座標系：Left-handed / Y-up / meters

---

# 4. ファイル形式

GOMはテキスト形式（GOMT0001）とバイナリ形式（GOMB0001）を持つ。

## マジックナンバー（先頭8バイト）

* GOMT0001
* GOMB0001

---

# 5. テキスト形式（GOMT0001）

UTF-8（BOMなし）、構造リテラル形式。

## 5.1 Skeleton

```
Skeleton = {
  boneCount = <int>,
  bones = [
    {
      parent = <int>,
      name   = "<string>",
      path   = "<string>",
      bindLocal = [[...],[...],[...],[...]]
    }
  ]
}
```

* bones配列順がboneIndex
* pathは同名衝突対策

## 5.2 MeshPart

```
Meshes = [
  {
    name = "<string>",
    meshBindGeoToModel = [[...],[...],[...],[...]],
    vertices = [ { p,n,t,uv,bi,bw } ],
    indices = [ ... ],
    submeshes = [ { materialIndex, indexStart, indexCount } ],
    inverseBind = [ [[...]] ]
  }
]
```

### ウェイト規約（必須）

* 最大4影響
* 合計1.0正規化
* weight降順、同値はboneIndex昇順タイブレーク

## 5.3 Animation

```
Animations = [
  {
    name = "<string>",
    duration = <float>,
    rootBoneIndex = <int>,
    flags = ["HasRootMotion"],
    tracks = [ { bone, keys[] } ]
  }
]
```

補間：T/S=linear、R=slerp

---

# 6. MeshPart規約

## Rule M1

* FBXメッシュノード単位でMeshPart生成
* マテリアル分割はSUB0で表現

## Rule M2

以下の場合はMeshPartを分割する：

* 別スケルトン参照
* inverseBindが異なる
* 頂点レイアウトが異なる

---

# 7. バイナリ形式（GOMB0001）

## 7.1 固定ヘッダ（512 bytes）

Little Endian固定。

### 7.1.1 固定値

* `magic` は常に `GOMB0001`（8 bytes ASCII）
* `headerSize` は常に `512`
* `formatVersion` は常に `1`

### 7.1.2 列挙値（enum）定義

本仕様の enum はすべて `uint32` とする。
未知の enum 値を受け取った場合、読み込みはエラーとする（v1）。

#### endianness（将来用）

v1ではファイルはLittle Endian固定だが、将来拡張のためヘッダに enum を持つ。

* `ENDIAN_LE = 1`（v1は常にこれ）
* `ENDIAN_BE = 2`（予約）

#### coordinateSystem

* `CS_LH_YUP_METERS = 1`

#### matrixConvention

行列の「格納順＋演算規約」を表す。

* `MC_ROW_MAJOR_ROW_VECTOR_RIGHT_MUL = 1`

定義：

* 行列は row-major で格納される（m00,m01,m02,m03,m10,...）
* 位置は row-vector として扱い `p' = p * M`（p=[x y z 1]）

### 7.1.3 validationFlags（bitfield）

`validationFlags` は `uint32`。

* bit0（1<<0）: bindPosePass
* bit1（1<<1）: weightsPass
* bit2（1<<2）: submeshMaterialsPass

予約：bit3以降は将来用。v1では0。

### 7.1.4 ChunkTableの指定

* `chunkTableOffset == 0` の場合：ChunkTableなし
* `chunkTableOffset != 0` の場合：ファイル先頭からのバイトオフセットで ChunkTable 先頭を指す
* `chunkTableCount` はエントリ数

---

```
char   magic[8]              // GOMB0001
uint32 headerSize            // 512
uint32 formatVersion         // 1
uint32 coordinateSystem      // enum
uint32 matrixConvention      // enum
uint8  sourceHash[32]        // SHA-256 raw
uint32 converterVersionStr   // STR0 index
uint32 validationFlags       // bitfield
uint32 chunkTableOffset
uint32 chunkTableCount
uint8  reserved[...]         // 0埋め
```

---

# 8. バイナリ全体の堅牢ルール（バイナリ専用）

## 8.1 Endianness

* Little Endian固定

## 8.2 アライメント

* チャンクは16バイト境界にアライン

## 8.3 チャンク基本構造

```
uint32 fourcc
uint32 size
uint32 version
uint32 flags
byte payload[size]
pad to 16
```

## 8.4 チャンクサイズ上限チェック（必須）

* sizeがファイル末尾を超える場合は即エラー
* sizeが実装上限を超える場合はエラー

## 8.5 未知チャンク

* 未対応fourccはsize分スキップ可能

## 8.6 ChunkTable（任意）

* chunkTableOffset!=0 の場合、ChunkEntry配列を保持

## 8.7 MSH0.flags（任意）

* bit0 = indices16
* bit1 = hasGBTM
* 予約ビットは0

---

# 9. バイナリチャンク厳密レイアウト

## STR0

```
uint32 stringCount
uint32 offsets[stringCount]
uint32 dataSize
uint8  data[dataSize]
```

## SKEL

```
uint32 boneCount
int32 parent[boneCount]
uint32 nameId[boneCount]
uint32 pathId[boneCount]
float32 bindLocal[boneCount][16]
```

## MSH0

```
uint32 meshNameId
uint32 vertexFormat
uint32 vertexCount
uint32 indexCount
uint32 boneCount
uint32 flags
uint32 reserved[2]
```

### VB0

```
uint32 vertexCount
uint32 stride
uint32 vertexFormat
uint32 reserved
uint8  data[vertexCount*stride]
```

### IB0

```
uint32 indexCount
uint32 indexSizeBytes
uint32 reserved[2]
uint8 data[indexCount*indexSizeBytes]
```

### SUB0

```
uint32 submeshCount
struct { int32 materialIndex; uint32 indexStart; uint32 indexCount; }
```

### IBND

```
uint32 boneCount
float32 inverseBind[boneCount][16]
```

### BND0

```
float32 aabbMin[3]
float32 aabbMax[3]
float32 sphereCenter[3]
float32 sphereRadius
```

### GBTM（任意）

```
float32 meshBindGeoToModel[16]
```

## ANIM

```
uint32 clipCount
```

## CLP0

```
uint32 clipNameId
float32 duration
float32 sampleRate
int32 rootBoneIndex
uint32 flags
uint32 trackCount
```

Track構造：

```
int32 boneIndex
uint32 keyCount
for each key:
  float32 time
  float32 tr[3]
  float32 rot[4]
  float32 sc[3]
```

---

# 10. 変換ツール検証（必須）

* Bind整合
* ウェイト合計
* Submesh完全被覆
* boneIndex範囲チェック
* index範囲チェック

---

---

# 11. 実装安全規約（v1推奨）

## 11.1 読み込み上限値（推奨）

実装は以下の上限を持つことを推奨する（超過時はエラー）。

* maxBoneCount = 4096
* maxVertexCount = 10,000,000
* maxIndexCount = 30,000,000
* maxClipCount = 1024
* maxTrackCount = 8192
* maxKeyCount = 1,000,000（クリップ単位）

※ 実装側で変更可能だが、無制限読み込みは禁止。

---

## 11.2 必須チャンク条件（バイナリ）

各 MSH0 は以下の子チャンクを必須とする：

* VB0
* IB0
* SUB0
* IBND
* BND0

GBTM は任意。

IB0.indexSizeBytes は 2 または 4 のみ許可。
それ以外は読み込みエラー。

---

## 11.3 アニメキー時刻規約

* keys[].time は昇順必須
* 0 <= time <= duration
* ループ再生時は time = fmod(time, duration) を推奨

---

## 11.4 inverseBind の定義

inverseBind[i] は、bind姿勢における boneWorld[i] の逆行列に相当する。

スキニングパレットは以下で構成される：

palette[i] = boneWorld_current[i] * inverseBind[i]

これにより、geometry 空間内でスキニングが完結する。

---

End of GOM Specification v1.0