# 機能設計書 177-WebXRController

## 概要

本ドキュメントは、Three.jsのWebXRコントローラー管理クラス「WebXRController」の機能設計について記述する。WebXRControllerはVR/ARアプリケーションにおけるXRコントローラーの様々な座標空間（target ray、grip、hand）を管理する。

### 本機能の処理概要

WebXRControllerクラスは、WebXR Device APIを使用したVR/ARアプリケーションにおいて、XRコントローラーの3つの座標空間を3Dオブジェクト（Group）として表現する。各座標空間はポインティング方向（target ray）、グリップ位置（grip）、ハンドトラッキング（hand）の異なる用途に対応し、XRセッション中のコントローラー状態をリアルタイムで更新する。

**業務上の目的・背景**：WebXRアプリケーションでは、コントローラーの位置と向きを正確に追跡し、3Dシーン内でのインタラクションを実現する必要がある。WebXRControllerは、WebXR APIから取得した入力情報をThree.jsの座標系に変換し、開発者がコントローラーを3Dオブジェクトとして扱えるようにする。また、ハンドトラッキングの各関節を個別のGroupとして管理することで、詳細なハンドジェスチャーの検出も可能にする。

**機能の利用シーン**：
- VRアプリケーションでコントローラーの位置を追跡する場合
- ARアプリケーションでハンドトラッキングを使用する場合
- コントローラーのボタン入力イベントを処理する場合
- ピンチジェスチャーの検出を行う場合
- レイキャスティングによるオブジェクト選択を実装する場合

**主要な処理内容**：
1. getTargetRaySpace: ポインティング方向の座標空間を取得
2. getGripSpace: グリップ位置の座標空間を取得
3. getHandSpace: ハンドトラッキングの座標空間を取得
4. connect/disconnect: XR入力ソースとの接続・切断
5. update: XRフレームからコントローラー状態を更新

**関連システム・外部連携**：
- XRManager: WebXRControllerを生成・管理
- WebXR Device API: XRInputSource、XRFrame、XRReferenceSpaceを使用
- Group: 各座標空間を3Dオブジェクトとして表現

**権限による制御**：WebXRセッションの権限に依存

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | WebXRサンプル | 主機能 | XRコントローラーの管理 |

## 機能種別

入力管理 / 座標変換 / イベント処理 / XR

## 入力仕様

### 入力パラメータ

#### connect メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| inputSource | XRInputSource | Yes | XR入力ソース | - |

#### disconnect メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| inputSource | XRInputSource | Yes | XR入力ソース | - |

#### update メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| inputSource | XRInputSource | Yes | XR入力ソース | - |
| frame | XRFrame | Yes | XRフレーム | - |
| referenceSpace | XRReferenceSpace | Yes | 参照座標空間 | - |

### 入力データソース

- XRInputSource: WebXR APIからのコントローラー入力情報
- XRFrame: 現在のXRフレーム（ポーズ情報を含む）
- XRReferenceSpace: 座標変換の基準となる参照空間

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| targetRaySpace | Group | ポインティング方向の3Dオブジェクト |
| gripSpace | Group | グリップ位置の3Dオブジェクト |
| handSpace | Group | ハンドトラッキングの3Dオブジェクト |

### 出力先

- 3Dシーン（各Groupはシーンに追加可能）
- イベントリスナー（connected/disconnected/move/pinchstart/pinchendイベント）

## 処理フロー

### 処理シーケンス

#### update

```
1. 各変数を初期化（inputPose, gripPose, handPose）
2. セッションが可視状態か確認
3. ハンドトラッキングが有効な場合：
   └─ 各関節についてループ
       └─ frame.getJointPose()で関節のポーズを取得
       └─ matrixを更新
       └─ jointRadiusを設定
   └─ ピンチジェスチャーを検出
       └─ 親指と人差し指の距離を計算
       └─ 閾値に基づいてpinchstart/pinchendイベントを発火
4. ハンドトラッキングが無効な場合：
   └─ grip空間のポーズを取得・更新
   └─ 線速度・角速度があれば設定
5. target ray空間のポーズを取得・更新
6. 線速度・角速度があれば設定
7. moveイベントを発火
8. 各空間のvisibilityを更新
```

### フローチャート

```mermaid
flowchart TD
    A[update開始] --> B{セッションが可視?}
    B -->|No| C[visibility=false設定]
    B -->|Yes| D{ハンドトラッキング?}
    D -->|Yes| E[各関節のポーズを更新]
    E --> F[ピンチジェスチャー検出]
    F --> G{ピンチ状態変化?}
    G -->|開始| H[pinchstartイベント発火]
    G -->|終了| I[pinchendイベント発火]
    G -->|変化なし| J[target ray更新]
    H --> J
    I --> J
    D -->|No| K[gripポーズを更新]
    K --> J
    J --> L[moveイベント発火]
    L --> M[visibilityを更新]
    M --> N[終了]
    C --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-177-01 | 遅延初期化 | 各座標空間は最初の取得時にのみ初期化 | getXxxSpace()呼び出し時 |
| BR-177-02 | matrixAutoUpdate無効 | 各GroupはmatrixAutoUpdate=falseで手動更新 | 初期化時 |
| BR-177-03 | ピンチ閾値 | ピンチ開始=0.02m未満、終了=0.025m超 | ピンチ検出時 |
| BR-177-04 | visible-blurred除外 | セッションがvisible-blurred状態ではポーズ更新しない | update実行時 |
| BR-177-05 | フォールバックポーズ | target rayポーズがnullの場合はgripポーズを使用 | 一部のランタイム対応 |

### 計算ロジック

**ピンチジェスチャー検出**：
```
distance = indexTip.position.distanceTo(thumbTip.position)
distanceToPinch = 0.02  // 2cm
threshold = 0.005       // 5mm

ピンチ開始: !pinching && distance <= distanceToPinch - threshold
ピンチ終了: pinching && distance > distanceToPinch + threshold
```

**ポーズからの変換行列更新**：
```
matrix.fromArray(pose.transform.matrix)
matrix.decompose(position, rotation, scale)
```

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

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 静かに無視 | ポーズがnullの場合 | visibleをfalseに設定 |
| - | 静かに無視 | 関節ポーズがnullの場合 | その関節のvisibleをfalse |

### リトライ仕様

リトライ処理は実装されていない（毎フレーム更新されるため）

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

トランザクション処理は適用されない（リアルタイム更新処理）

## パフォーマンス要件

- update: 60fps以上のXRフレームレートに対応
- ハンドトラッキング: 25関節×2手=50関節の更新

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

- WebXRセッションの権限が必要
- ハンドトラッキングは追加の権限が必要な場合がある

## 備考

- WebXRController自体は内部クラスで、通常はXRManagerを通じてアクセス
- ハンドトラッキングの関節名はWebXR Hand Input仕様に準拠
- 一部のランタイム（Vive Cosmos with Vive OpenXR Runtime等）ではtarget ray spaceがgrip spaceと同一

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 17-48行目のコンストラクタとプロパティ定義 |
| 1-2 | Group.js | `src/objects/Group.js` | Groupクラスの基本構造 |

**読解のコツ**: 3つのプライベートプロパティ（_targetRay, _grip, _hand）が各座標空間を表す。すべてnullで初期化され、最初のアクセス時に遅延初期化される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 56-70行目 getHandSpace |
| 2-2 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 78-93行目 getTargetRaySpace |
| 2-3 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 101-116行目 getGripSpace |

**主要処理フロー**:
1. **60-67行目**: getHandSpaceの遅延初期化 - joints辞書とinputState追加
2. **82-88行目**: getTargetRaySpaceの初期化 - linearVelocity/angularVelocity追加
3. **105-111行目**: getGripSpaceの初期化 - 同様にvelocity追加

#### Step 3: イベント処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 126-148行目 dispatchEvent |
| 3-2 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 156-179行目 connect |
| 3-3 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 187-211行目 disconnect |

**主要処理フロー**:
- **126-148行目**: dispatchEvent - 3つの空間すべてにイベントを伝播
- **158-173行目**: connect - ハンドトラッキングの場合は関節を初期化
- **189-209行目**: disconnect - すべての空間をvisible=falseに設定

#### Step 4: 更新処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 223-398行目 update |

**主要処理フロー**:
- **233行目**: visible-blurred状態のチェック
- **235-288行目**: ハンドトラッキング処理
  - **239-258行目**: 各関節のポーズ更新
  - **263-288行目**: ピンチジェスチャー検出
- **290-326行目**: グリップ空間の更新
- **330-372行目**: target ray空間の更新
- **369行目**: moveイベント発火

#### Step 5: ヘルパーメソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | WebXRController.js | `src/renderers/webxr/WebXRController.js` | 408-423行目 _getHandJoint |

**主要処理フロー**:
- **410-417行目**: 関節Groupの遅延作成とjoints辞書への登録

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

```
WebXRController
    │
    ├─ getTargetRaySpace()
    │      └─ new Group() [遅延初期化]
    │             ├─ matrixAutoUpdate = false
    │             ├─ visible = false
    │             ├─ hasLinearVelocity
    │             ├─ linearVelocity (Vector3)
    │             ├─ hasAngularVelocity
    │             └─ angularVelocity (Vector3)
    │
    ├─ getGripSpace()
    │      └─ new Group() [遅延初期化]
    │
    ├─ getHandSpace()
    │      └─ new Group() [遅延初期化]
    │             ├─ joints = {}
    │             └─ inputState = { pinching: false }
    │
    ├─ connect(inputSource)
    │      ├─ inputSource.hand → _getHandJoint()で各関節初期化
    │      └─ dispatchEvent({ type: 'connected' })
    │
    ├─ disconnect(inputSource)
    │      ├─ dispatchEvent({ type: 'disconnected' })
    │      └─ 全空間のvisible = false
    │
    ├─ update(inputSource, frame, referenceSpace)
    │      ├─ ハンドトラッキング処理
    │      │      ├─ frame.getJointPose()
    │      │      ├─ matrix更新
    │      │      └─ ピンチ検出 → pinchstart/pinchend
    │      ├─ グリップ処理
    │      │      ├─ frame.getPose(gripSpace)
    │      │      └─ velocity更新
    │      └─ targetRay処理
    │             ├─ frame.getPose(targetRaySpace)
    │             └─ dispatchEvent({ type: 'move' })
    │
    └─ _getHandJoint(hand, inputjoint)
           └─ new Group() → hand.joints[jointName]
```

### データフロー図

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

XRInputSource ────────────▶ connect() ──────────────────────▶ 'connected'イベント
  │                              │
  └─ hand                        └─ _getHandJoint()で関節初期化


XRInputSource ────────────┐
XRFrame ──────────────────┼──▶ update() ─────────────────────▶ Group更新
XRReferenceSpace ─────────┘      │
                                 ├─ getJointPose() → hand.joints[].matrix
                                 │
                                 ├─ getPose(gripSpace) → _grip.matrix
                                 │
                                 ├─ getPose(targetRaySpace) → _targetRay.matrix
                                 │
                                 └─ ピンチ検出 → 'pinchstart'/'pinchend'イベント


XRInputSource ────────────▶ disconnect() ───────────────────▶ 'disconnected'イベント
                                 │
                                 └─ 全空間 visible = false
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WebXRController.js | `src/renderers/webxr/WebXRController.js` | ソース | XRコントローラー管理の本体 |
| XRManager.js | `src/renderers/common/XRManager.js` | ソース | WebXRControllerの生成・管理 |
| Group.js | `src/objects/Group.js` | ソース | 座標空間を表す3Dオブジェクト |
| Vector3.js | `src/math/Vector3.js` | ソース | 速度ベクトルの型 |
| EventDispatcher.js | `src/core/EventDispatcher.js` | ソース | イベント発火機能（Groupが継承） |
