AR.jsにおけるgroupを作成して表示を可能にする理由について

`markerRoot = new THREE.Group()` を使っているのは、AR.js のマーカー検出結果を「まとめて受け止める”空のコンテナ”」として機能させるためです。詳しく分解すると…

1. **マーカー座標系の原点を作る**  
   - `ArMarkerControls` は「マーカーを見つけたら、その位置・向きの変換行列（model‐view 行列）を指定のオブジェクトにコピーする」仕組みです。  
   - 直接 `scene` や `camera` にコピーするとシーン全体やカメラの挙動がおかしくなるので、まずは **“何も描かれていない空の Object3D”** に書き込みます。

2. **複数のメッシュを一括管理できる**  
   - `THREE.Group` は子オブジェクトを複数持てるコンテナです。  
   - `markerRoot.add(meshA, meshB, …)` のように、マーカー上に表示したいモデル（立方体や glTF、UI など）をまとめて `markerRoot` の子にぶら下げれば、  → `markerRoot` の行列更新だけで **中のすべての子メッシュ** が一斉に正しい位置・角度に動きます。

3. **シーン構造をシンプルに保つ**  
   - `Group` を使わずに、いきなり `scene.add(mesh)` をしてしまうと、マーカーが見えないときでもオブジェクトがちらついたり、座標系の切り替え処理が複雑になります。  
   - `markerRoot.visible` を使えば「マーカー未検出時は `markerRoot` 以下をまるごと非表示にする」なども簡単に制御できます。

---

### まとめ

- **`markerRoot`（`THREE.Group`）＝マーカーの座標系（原点）**  
- **`ArMarkerControls` → markerRoot.matrix を更新**  
- **markerRoot の子に追加したモデル群が「マーカーにくっついて動く」**

という役割分担にすることで、シーン／カメラ本体には手を入れずに、マーカーに連動したコンテンツだけをグルーピングして管理できるようになるわけです。
ほぼそのとおりです。

厳密には、`markerRoot` 自体が「マーカー検出結果を書き込まれるオブジェクト（＝ArMarkerControls が行列を設定するターゲット）」で、その下に「実際に表示したいメッシュやモデル」をぶら下げるイメージです。

1. **ArMarkerControls** が起動時に登録された `markerRoot` の行列（`markerRoot.matrix`）を、毎フレーム「検出されたマーカーの位置・向き」に書き換え  
2. その結果として、  
   ```js
   markerRoot.add( yourMesh );  // 例えばこんな感じで
   ```  
   でぶら下がった `yourMesh` は自動的に「マーカーに追従して動く」  

──という順序になります。ですので「①マーカー検出結果をどうオブジェクトに反映させるか」は `markerRoot` 自体の役割、「②表示するオブジェクト」はその子要素、という扱いで正しい理解です。

　たとえば、以下のように具体的な数値を使って考えてみましょう。

---

## 例：マーカー検出結果から得られる `markerRoot.matrix`  

AR.js 側でマーカーを検出すると、以下のようなワールド変換行列（4×4）が `markerRoot.matrix` にセットされたとします。

|   |   |   |   |
|---|---|---|---|
| R₁₁ = cos 45° ≈ 0.707 | 0 | R₁₃ = sin 45° ≈ 0.707 | Tₓ = 0.2 |
| 0 | 1 | 0 | Tᵧ = 0.1 |
| R₃₁ = –sin 45° ≈ –0.707 | 0 | R₃₃ = cos 45° ≈ 0.707 | T_z = –1.3 |
| 0 | 0 | 0 | 1 |

- ここで **回転**は Y 軸まわりに 45°（マーカー面がやや横を向いている）  
- **並進**（Tₓ, Tᵧ, T_z）はマーカー原点がワールド（カメラ座標系）上の (0.2, 0.1, –1.3) にある

---

## `markerRoot` の子オブジェクト（メッシュ）の配置例

### 1. 子メッシュをマーカー原点に置く  
```js
// ローカル座標で (0, 0.5, 0)＝マーカーの真上 0.5m
cube.position.set(0, 0.5, 0);
markerRoot.add(cube);
```
**ワールド座標** は  
```
x = Tₓ + (0.707 * 0 + 0.707 * 0) = 0.2  
y = Tᵧ +      1 * 0.5        = 0.1 + 0.5 = 0.6  
z = T_z + (–0.707 * 0 + 0.707 * 0) = –1.3
```  
→ **(0.2, 0.6, –1.3)** にキューブが置かれる

---

### 2. 子メッシュをマーカー原点の右側に 1mずらす  
```js
// ローカル座標で (1, 0, 0)＝マーカー右側 1m
cube2.position.set(1, 0, 0);
markerRoot.add(cube2);
```
**ワールド座標** は  
```
x = Tₓ + (0.707 * 1 + 0.707 * 0) = 0.2 + 0.707 = 0.907  
y = Tᵧ +      1 * 0          = 0.1  
z = T_z + (–0.707 * 1 + 0.707 * 0) = –1.3 – 0.707 = –2.007
```  
→ **(0.907, 0.1, –2.007)** にキューブ２が置かれる

---

## なぜ `Group()` が必要か、振り返り

1. **マーカーの回転・平行移動**（上の R, T）をまず `markerRoot.matrix` に一括で設定  
2. その下にぶら下がっているすべての **子オブジェクト（`cube`, `cube2` …）** が  
   - ローカル座標で指定した (x, y, z) を  
   - 上記行列でワールド座標に変換され  

   自動的に「マーカーに対する相対位置」を保ったまま動きます。

---

### 具体的な流れ

1. AR.js → `markerRoot.matrix =` 上の回転＋並進行列  
2. Three.js → `markerRoot.updateMatrixWorld()` により、  
   - 各子オブジェクトのワールド行列が `markerRoot.matrix * 子オブジェクト.matrix` として計算  
3. レンダラー → それぞれのワールド行列から画面に投影  

――というわけで、**「① マーカー検出結果（R,T）を書き込むのが `markerRoot`」「② その下に置いたメッシュが具体的に描画される」** という役割分担だ、という実感が掴めると思います。