# 機能設計書 85-SkinnedMesh

## 概要

本ドキュメントは、Three.jsにおけるスケルタルアニメーション対応メッシュ `SkinnedMesh` クラスの機能設計を定義する。SkinnedMeshはSkeleton（骨格）にバインドされ、ボーンの変形に応じて頂点が変形するスキニングアニメーションを実現する。

### 本機能の処理概要

SkinnedMeshクラスは、Meshを拡張し、Skeleton（ボーン階層）と連携してスキニングアニメーションを実現する。各頂点にはskinIndex（影響するボーンのインデックス）とskinWeight（各ボーンの影響度）が設定され、ボーンの変形が頂点位置に反映される。

**業務上の目的・背景**：3Dキャラクターアニメーション、機械のアーム動作、生物的な動きの表現など、変形を伴うアニメーションを実現するために必要な機能である。GLTFやFBXなどの3Dモデルフォーマットからインポートされるキャラクターモデルの多くがこの機能を使用する。

**機能の利用シーン**：
- 人体キャラクターのアニメーション（歩行、走行、ジャンプ等）
- 動物・クリーチャーのアニメーション
- ロボットアームや機械装置の動作表現
- 顔のフェイシャルアニメーション
- 布や髪のシミュレーション（ボーンベース）

**主要な処理内容**：
1. Skeletonとのバインド処理
2. bindModeによるワールド空間共有の制御
3. スキンウェイトの正規化
4. ボーン変形の頂点への適用
5. バウンディングボックス/球の計算

**関連システム・外部連携**：Skeleton、Bone、AnimationMixer、GLTFLoader/FBXLoaderと連携。

**権限による制御**：特になし（Three.jsはクライアントサイドライブラリ）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 37 | Bones Browser | 主機能 | スキンメッシュのプレビュー |

## 機能種別

アニメーション / 頂点変形

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| geometry | BufferGeometry | No | メッシュジオメトリ（skinIndex/skinWeight属性必須） | - |
| material | Material\|Array\<Material\> | No | メッシュマテリアル | - |

### 入力データソース

- BufferGeometry: position, skinIndex, skinWeight属性
- Skeleton: bind()で関連付け
- AnimationMixer: ボーンアニメーションの駆動

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| isSkinnedMesh | boolean | 型判定フラグ（常にtrue） |
| bindMode | string | バインドモード（AttachedBindMode/DetachedBindMode） |
| bindMatrix | Matrix4 | バインド時の変換行列 |
| bindMatrixInverse | Matrix4 | バインド行列の逆行列 |
| skeleton | Skeleton | バインドされたスケルトン |
| boundingBox | Box3\|null | バウンディングボックス |
| boundingSphere | Sphere\|null | バウンディング球 |

### 出力先

スキニングが適用された頂点位置でレンダリングされる。

## 処理フロー

### 処理シーケンス

```
1. コンストラクタ呼び出し
   └─ Meshの初期化、bindMode/bindMatrix等の初期化
2. bind()でSkeletonをバインド
   └─ skeleton設定、bindMatrix計算、boneInverses計算
3. 毎フレームの更新
   └─ updateMatrixWorld() → bindMatrixInverseの更新
4. 頂点位置取得時
   └─ applyBoneTransform()でスキニング適用
5. レイキャスト時
   └─ 境界球判定 → スキニング後の頂点で交差判定
```

### フローチャート

```mermaid
flowchart TD
    A[SkinnedMesh生成] --> B[Mesh初期化]
    B --> C[bindMode/bindMatrix初期化]
    C --> D[bind呼び出し]
    D --> E[Skeleton設定]
    E --> F[bindMatrix計算]
    F --> G[calculateInverses呼び出し]
    G --> H[レンダリング/アニメーション待機]
    H --> I{updateMatrixWorld?}
    I -->|Yes| J{bindMode判定}
    J -->|Attached| K[bindMatrixInverse = matrixWorld.invert]
    J -->|Detached| L[bindMatrixInverse = bindMatrix.invert]
    K --> M[頂点変形]
    L --> M
    M --> N[レンダリング]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-85-01 | スキンウェイト合計 | 各頂点のskinWeightは合計1.0に正規化推奨 | 常時 |
| BR-85-02 | 最大影響ボーン数 | 1頂点あたり最大4ボーンの影響 | 常時 |
| BR-85-03 | AttachedBindMode | スケルトンとワールド空間を共有 | デフォルト |
| BR-85-04 | DetachedBindMode | 複数メッシュでスケルトン共有時に使用 | 明示設定時 |
| BR-85-05 | バウンディング再計算 | アニメーション時は毎フレーム再計算推奨 | アニメーション中 |

### 計算ロジック

ボーン変形の頂点適用（applyBoneTransform）:
```javascript
// 各ボーンの影響を合算
for (let i = 0; i < 4; i++) {
    const weight = skinWeight.getComponent(i);
    if (weight !== 0) {
        const boneIndex = skinIndex.getComponent(i);
        _matrix4.multiplyMatrices(skeleton.bones[boneIndex].matrixWorld, skeleton.boneInverses[boneIndex]);
        target.addScaledVector(_basePosition.applyMatrix4(_matrix4), weight);
    }
}
return target.applyMatrix4(bindMatrixInverse);
```

## データベース操作仕様

該当なし（Three.jsはクライアントサイドライブラリ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 警告 | 認識されないbindMode | コンソールに警告出力 |

### リトライ仕様

特になし

## トランザクション仕様

該当なし

## パフォーマンス要件

- スキニング計算はGPU（シェーダー）で実行され高速
- バウンディング計算はCPUで行われるため、毎フレーム実行は負荷が高い
- normalizeSkinWeights()は初期化時に一度実行

## セキュリティ考慮事項

特になし（クライアントサイドレンダリング）

## 備考

- 通常はGLTFLoader等でインポートされ、手動作成は稀
- pose()でバインドポーズ（初期姿勢）に戻せる
- レイキャストは境界球/ボックス判定後、スキニング後の頂点位置で判定

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

スキニングに必要なデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Skeleton.js | `src/objects/Skeleton.js` | ボーン配列とboneInverses |
| 1-2 | Bone.js | `src/objects/Bone.js` | ボーンオブジェクトの構造 |
| 1-3 | BufferGeometry.js | `src/core/BufferGeometry.js` | skinIndex/skinWeight属性 |

**読解のコツ**: skinIndex/skinWeightは4成分（XYZW/RGBA）で、最大4ボーンの影響を表す。

#### Step 2: エントリーポイントを理解する

SkinnedMeshクラス本体を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SkinnedMesh.js | `src/objects/SkinnedMesh.js` | メインクラス |

**主要処理フロー**:
1. **46-101行目**: コンストラクタでbindMode、bindMatrix等を初期化
2. **230-247行目**: bind()でSkeletonをバインド
3. **319-348行目**: applyBoneTransform()でスキニング計算
4. **262-288行目**: normalizeSkinWeights()でウェイト正規化
5. **290-308行目**: updateMatrixWorld()でbindMatrixInverse更新

#### Step 3: バウンディング計算を理解する

スキニング後のバウンディング計算を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SkinnedMesh.js | `src/objects/SkinnedMesh.js` | computeBoundingBox/Sphere |

**主要処理フロー**:
- **109-130行目**: computeBoundingBox()で全頂点をスキニング後に計算
- **138-159行目**: computeBoundingSphere()で同様に球を計算

### プログラム呼び出し階層図

```
SkinnedMesh
    │
    ├─ Mesh (継承)
    │      └─ Object3D (継承)
    │
    ├─ Skeleton (skeleton)
    │      ├─ bones[] (Bone配列)
    │      └─ boneInverses[] (Matrix4配列)
    │
    ├─ bind()
    │      ├─ skeleton設定
    │      ├─ bindMatrix計算
    │      └─ Skeleton.calculateInverses()
    │
    ├─ applyBoneTransform()
    │      ├─ skinIndex/skinWeight取得
    │      ├─ ボーンごとの変換計算
    │      └─ bindMatrixInverse適用
    │
    ├─ updateMatrixWorld()
    │      └─ bindMatrixInverse更新（bindMode依存）
    │
    └─ raycast()
           ├─ computeBoundingSphere()
           ├─ 境界球判定
           └─ _computeIntersections() (Meshから継承)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

BufferGeometry ────────▶ SkinnedMesh Constructor ─▶ SkinnedMesh Object
(position, skinIndex,         │                        │
 skinWeight)                   │                        ├─ isSkinnedMesh: true
                               │                        ├─ bindMode
Skeleton ──────────────▶ bind() ─────────────────▶     ├─ bindMatrix
(bones, boneInverses)          │                        └─ skeleton
                               │
                               └─ calculateInverses()

Bone.matrixWorld ──────▶ applyBoneTransform() ──▶ 変形後の頂点位置
skinIndex/skinWeight          │
bindMatrixInverse             │
                              ├─ ボーン行列 × boneInverse
                              ├─ ウェイト乗算
                              └─ bindMatrixInverse適用
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SkinnedMesh.js | `src/objects/SkinnedMesh.js` | ソース | スキンメッシュのメインクラス |
| Mesh.js | `src/objects/Mesh.js` | ソース | メッシュ基底クラス |
| Skeleton.js | `src/objects/Skeleton.js` | ソース | スケルトン（骨格）クラス |
| Bone.js | `src/objects/Bone.js` | ソース | ボーンクラス |
| Object3D.js | `src/core/Object3D.js` | ソース | 3Dオブジェクト基底クラス |
| Matrix4.js | `src/math/Matrix4.js` | ソース | 行列変換 |
| Vector3.js | `src/math/Vector3.js` | ソース | 3Dベクトル |
| Vector4.js | `src/math/Vector4.js` | ソース | 4Dベクトル（skinIndex/Weight） |
| Box3.js | `src/math/Box3.js` | ソース | バウンディングボックス |
| Sphere.js | `src/math/Sphere.js` | ソース | バウンディング球 |
| constants.js | `src/constants.js` | ソース | AttachedBindMode/DetachedBindMode定義 |
