# 3D シーン形式

> Nodaro の 3D シーンプランがどのように構成されるか、バージョン 1 のプリミティブとキーフレームから、バージョン 2 の GLB アセット、セマンティックエンティティ、カメラトラック、ショット、編集オーバーレイまでを説明します。

Source: https://nodaro.ai/ja/docs/developers/embed/scene3d-format

**3D シーンプラン**は、編集可能な 3D シーンのために Nodaro が保存するデータです。`planType: "3d-scene"` と、明示的な `schemaVersion` を持つコンポジションです。[**3D シーン生成**（Generate 3D Scene）](https://nodaro.ai/docs/nodes/video/generate-3d-scene) と [**3D シーン編集**（Edit 3D Scene）](https://nodaro.ai/docs/nodes/video/edit-3d-scene) のジョブは `output_data.scenePlan` にシーンプランを 1 つ返し、[**動画レンダリング**（Render Video）](https://nodaro.ai/docs/nodes/video/render-video) はそれを MP4 に変換し、[3D シーンビューポート](https://nodaro.ai/docs/developers/embed/scene3d) はそれを描画します。このページでは、プランがどのように構成されているかを説明します。自分のコードでプランを保存、検証、表示、編集できるようになります。

## 全バージョン共通のルール
- **単位と軸。**位置はメートル単位で、Y 軸を上向きとする右手座標系です。オイラー回転はラジアンで表します。
- **時間。**フレームは 0 から数えます。シーンの長さは 1〜60 秒で、フレームレートは 15〜60 fps です。デフォルトは 24 fps で 4 秒です。
- **リビジョン。**すべてのプランは `revisionId` を持ちます。編集がプランを直接変更することはなく、`parentRevisionId` に元になったリビジョンを持つ、新しいリビジョンを作成します。レンダリングされた MP4 は、それぞれ 1 つの正確なリビジョンを使います。
- **バージョン。**特定のバージョンに属するフィールドを読み取る前に、`schemaVersion` を読み取ってください。バージョン 1 はプリミティブなジオメトリを記述し、バージョン 2 はベイク済みのジオメトリとショットを追加します。
- **検出。**`GET /v1/3d-scene/capabilities` は、デプロイ環境がどのオーサリングエンジンを、つまりどのシーンバージョンをサポートしているかを示します。オプションのエンジンは、デプロイ環境の設定によって異なります。デフォルトのエンジンである Basic は、バージョン 1 を生成します。

## 型とスキーマ
`@nodaro/shared` パッケージは、型とバリデーターをエクスポートします。

| エクスポート | 内容 |
| --- | --- |
| `Scene3DPlanV1`、`Scene3DPlanV2` | 各バージョンの型です。 |
| `Scene3DPlan` | 両方のバージョンのユニオン型です。 |
| `scene3DAnyPlanSchema` | 両方のバージョンを受け付けるバリデーターです。 |
| `scene3DPlanSchema` | バージョン 1 専用のバリデーターです。 |
| `scene3DPlanV2Schema` | バージョン 2 専用のバリデーターです。 |

バリデーターは、構造と、フィールドをまたいだルールを検証します。親の循環参照、存在しない親、最終フレームより後にあるキーフレーム、長さの上限などです。検証に失敗したプランは、全体が拒否されます。

## バージョン 1：プリミティブとキーフレーム
バージョン 1 のシーンは、単体で完結しています。簡易的なキャラクターの代役を含む、境界のあるプリミティブな形状とグループを、各オブジェクトとカメラのまばらなキーフレームとともに記述します。詳細なテクスチャ付きメッシュや物理シミュレーションを復元することはありません。

バージョン 1 のプランが保存するのは、次の情報です。

- オブジェクトの ID、トランスフォーム、寸法
- カメラの位置、注視点、レンズ
- ライティングと背景
- 各オブジェクトとカメラのキーフレーム

| 上限 | 値 |
| --- | --- |
| シーンあたりのオブジェクト数 | 100 |
| オブジェクトまたはカメラトラックあたりのキーフレーム数 | 240 |
| オブジェクト ID の長さ | 64 文字 |
| フレームの幅と高さ | 各軸 100〜2,560 px |

### バージョン 1 のシーンを編集する
`POST /v1/3d-scene/edit` は、`scenePlan` と `expectedRevisionId`、それに加えて、指示となる `prompt` か `operations` のリストのどちらかを受け取ります。リビジョンが一致しない場合は拒否されます。操作はモデルを呼び出さずに実行されます。

| 操作 | フィールド |
| --- | --- |
| `set-object` | `objectId`、および ID 以外のオブジェクトフィールドへの `changes` |
| `add-object` | `object` |
| `remove-object` | `objectId` |
| `set-camera` | `changes` |
| `set-lighting` | `changes` |
| `set-background` | `color` |

操作は、指定したフィールドだけを正確に変更します。キーフレームを持つポーズを変更するには、キーフレームの変更も含めてください。編集後のシーン全体があらためて検証されるため、編集によって、親を失ったオブジェクトや参照切れが残ることはありません。指示による編集の間、オブジェクトを変更しないままにするには `lockedObjectIds` を使います。[3D シーン編集](https://nodaro.ai/docs/nodes/video/edit-3d-scene)を参照してください。

## バージョン 2：ベイク済みジオメトリ
バージョン 2 のシーンは、バージョン 1 の概念に、次の 4 つを追加します。

- **保持された GLB ジオメトリ。**不変のアセットとして保存されます。
- **セマンティックエンティティ。**ユーザーが選択し、編集する、シーンの名前付きパーツです。
- **密なカメラトラック。**すべてのフレームに、カメラのポーズを持ちます。
- **連続するショット。**フレームの範囲で、その間には正確なカット点があります。

### アセット
バージョン 2 のアセット参照は、不透明な `assetId`、種類、役割、バイト長、SHA-256 ダイジェストを保持します。ストレージの認証情報やダウンロード URL は保持しません。

- リーダーが必要とするのは、参照先のバイトそのものだけです。アセットが見つからない、大きすぎる、変更されている場合は、何かを描画する前に拒否します。
- 再生時には、ジオメトリとカメラデータが読み込まれます。シーンのネイティブなソースファイルは別のダウンロードで、独自の権限が必要です。
- 保持された各リビジョンは、以前のリビジョンから再利用したバイトも含め、そのすべてのアセットを固定します。

### エンティティ
セマンティックエンティティは、GLB ジオメトリ内のルートと、そのルートのマテリアルの役割に、選択と編集のための名前を付けます。

- **マテリアル。**編集可能なマテリアルの役割は、それぞれ、そのエンティティ自身のジオメトリ内にあるマテリアルを指します。クレイシェーディングは各マテリアルのベースカラーを保つため、車両のボディカラーを変更しても、タイヤは変わりません。
- **親。**エンティティは親を宣言できます。その場合、エクスポートされるルートは親のルートの中にネストされ、そのノードのトランスフォームは親を基準とした相対値になります。親の配置とベイク済みのアニメーションは、ファイル自体を通じて子に伝わります。
- **一致。**プランと GLB ファイルは、同じ親子関係を記述している必要があります。両者が一致しないシーンは、リーダーによって拒否されます。
- **所有権。**ジオメトリ、マテリアル、選択は、それらを宣言したエンティティに属します。親の色を変更しても、その中にネストされたエンティティには決して及びません。
- **整理用のルート。**親エンティティは、ジオメトリを一切持たず、その中にネストされたエンティティのための、ベイク済みの（アニメーションする場合もある）トランスフォームだけを持つこともできます。そこに何かがネストされている限り、リーダーはそれを受け入れます。

### アンカー
エンティティの**アンカー**は、安定した `name` と、そのエンティティのローカル空間における `position` を持つ、名前付きのポイントです。

- アセットエンティティの場合、任意の `nodeName` によって、アンカーをそのエンティティ自身のルート内にある、生の GLB ノードに結び付けられます。その場合、位置と任意の回転は、そのノードのローカル座標を使い、ノードのアニメーション、その祖先、エンティティへの手動編集に追従します。
- たとえば `{ "name": "door.tip", "nodeName": "car/door.hinge", "position": [1, 0, 0] }` は、ヒンジの X 軸に沿って 1 メートルの位置にポイントを置きます。
- 存在しないノードや、ネストされた子エンティティへのバインドは、再生前に拒否されます。プリミティブとグループのアンカーは、エンティティのローカル空間にとどまります。
- オーサリングエンジンがこうしたバインドを作成できるかどうかは、エンジンによって異なります。

### カメラとアニメーション
密なカメラトラックは、すべてのフレームについて、位置、回転、投影を保持します。リーダーは、ロールやショット間の正確なカット点を含め、それらの値をそのまま保ちます。アニメーションは要求されたフレームからサンプリングされるため、後方にスクラブしても、どの 1 フレームをレンダリングしても、同じポーズになります。

### 表示状態
エンティティの任意の `visible` フラグは、そのベイク済みの表示状態を記録します。存在しない場合、そのエンティティは表示されます。

- 非表示のジオメトリは読み込まれたままで、アニメーションも動き続けるため、オーバーレイはそれをすぐに再表示できます。
- オーバーレイはベイク済みの値より優先され、オーバーレイを取り除くとベイク済みの値に戻ります。
- 親を非表示にすると、その中にネストされたものもすべて非表示になります。
- 非表示のオブジェクトは、プレビューでクリックを受け付けません。選択して表示するには、エンティティの一覧を使います。
- ベイク済みの表示状態は、リビジョンのコンテンツダイジェストの一部です。

## オーバーレイでバージョン 2 のシーンを編集する
バージョン 2 の編集は、ベイク済みのシーンの上にある、不変の**オーバーレイ**です。種類は、トランスフォーム、マテリアルカラー、表示状態、ショットのカメラオフセットの 4 つです。オーバーレイが、ベースのジオメトリやカメラのバイトに触れることはありません。

- **トランスフォームオーバーレイ**は、エンティティとその祖先のベイク済みの配置の上に適用されます。その `space` は、値をどの座標系で読み取るかを示します。
  - `local` は、ベイク済みの配置を含む、エンティティ自身の親の座標系を意味します。値はそこで一定に保たれ、編集は動く親と一緒に移動します。
  - `world` は、シーンの座標軸を意味します。値はエンティティのワールド位置、回転、スケールであり、回転、拡大縮小、アニメーションする祖先は、エンティティの最終的な位置を変えますが、数値の意味そのものは変わりません。エンティティは親を持ったままで、自身のベイク済みのアニメーションも保ち、編集が指定するチャンネルだけが固定されます。
  - リーダーは、どちらの空間でも表現できない、ただ 1 つのケースを拒否します。回転の内側に不均一なスケールを持つ祖先がある場合で、この場合、結果のトランスフォームを位置・回転・スケールの組で表すことができません。
- **表示状態のオーバーレイ**は、エンティティと、その中にネストされたすべてのものを非表示にします。各エンティティはそれぞれ自身の表示状態を保つため、祖先を再表示すると、それ自体としては非表示にされていなかった子孫だけが復元されます。
- **編集はすべて、新しいリビジョンを作成します。**親リビジョンと、新しいコンテンツダイジェストを持ちます。エンティティのロックは適用され、古いリビジョンやダイジェストに対して行われた編集は拒否されます。
- **派生ファイルは引き継がれません。**ポスター、検証レポート、ネイティブなダウンロード用ファイルは、編集後のリビジョンのために作り直されてから、そのリビジョンに紐付けられます。

生成ジョブを使わず、LLM の料金もかけずにオーバーレイを保存するには、`newRevisionId`、ベースとなる `expectedContentHash`、`operations`、任意の `lockedObjectIds` を添えて `POST /v1/3d-scene/revisions/:revisionId/edits` を呼び出すか、SDK の `client.scene3d.applyEdits()` を使います。OAuth アプリの場合、これには `workflows:write` スコープが必要です。[3D シーンビューポート](https://nodaro.ai/docs/developers/embed/scene3d#edit-a-baked-scene)は、これらの操作を代わりに生成します。

## レンダリング
ブラウザーのプレビューと MP4 のレンダリングは、同じリーダーを使うため、シーンはどちらでも同じように見えます。バージョン 2 が受け付けるのは、クレイジオメトリとリジッドアニメーションです。テクスチャ付き、スキン付き、モーフターゲット付きのアセットは拒否されます。

| バージョン 2 の上限 | 値 |
| --- | --- |
| セマンティックエンティティ | 100 |
| メッシュノード | 2,000 |
| 三角形 | 200,000 |
| ショット | 32 |
| アセット | 64 |
| オーバーライド | 200 |
| 再生用アセットのバイト数 | 64 MiB |

スキーマは、タイミング、寸法、階層の深さ、マニフェストのサイズにも上限を設けます。レンダリングの料金は、プランのフレームサイズによって変わります。[動画レンダリング](https://nodaro.ai/docs/nodes/video/render-video)を参照してください。

## インポートされた GLB
新しいシーンは、保持されたリビジョンにすでに存在する GLB ファイルから始めることもできます。`POST /v1/3d-scene/generate` では、`inputAssets` によって、それぞれ `{ id, revisionId, assetId, label? }` の形式で、最大 8 個までを選択できます。画像や動画の `references` は、別のフィールドのままです。

- インポートには、インポートに対応したオーサリングエンジンが必要です。Basic を含む、インポートに対応していないエンジンは、課金前にインポートを拒否します。
- 送るのは ID だけです。Nodaro が、その正確なリビジョンへのアクセス権を確認し、ダイジェストとバイト長も Nodaro 自身が補います。呼び出し元からの URL やレシートは拒否されます。
- エンジンがインポートされた GLB を読み込むとき、Nodaro は再度アクセス権を確認し、短命なダウンロード許可を発行します。許可は転送用の認証情報であり、シーンプランの一部になることはありません。
- 保存されたリビジョンは、そのビルド元になったファイルの非公開コピーを保持できます。これらはリビジョンの所有者に属し、手動編集を経ても固定されたままで、再生用アセットや公開ダウンロードの中に現れることはありません。

## API 経由でバイナリファイルを読み取る
バージョン 2 のマニフェストはそのアセットの名前を記載しますが、バイト自体は、通常のベアラー認証を使う、認証が必要な API から取得します。

| メソッド | パス | 返す内容 |
| --- | --- | --- |
| `GET` | `/v1/3d-scene/revisions/:revisionId` | シーンのマニフェストと、そのアセットの記述子です。 |
| `GET` | `/v1/3d-scene/revisions/:revisionId/assets/:assetId` | GLB やカメラトラックなど、1 つの再生用アセットです。 |
| `GET` | `/v1/3d-scene/revisions/:revisionId/source` | リビジョンが保持している場合の、編集可能なネイティブソースです。 |

- 再生用アセットには、そのリビジョンのワークフローを閲覧する権限が必要です。ネイティブソースには、それを編集する権限が必要です。個人のリビジョンは、所有者だけが読み取れます。
- 削除された、またはアクセスできないリビジョンには `404` が返り、すべてのレスポンスに `Cache-Control: no-store` が付きます。
- SDK では、`client.scene3d.assetBytes(revisionId, asset)` と `client.scene3d.sourceBytes(revisionId)` が `ArrayBuffer` を返し、宣言された長さを確認します。

生成、編集、レンダリングの各エンドポイントについては、[3D シーン API](https://nodaro.ai/docs/developers/api/3d-scenes) を参照してください。

## Frequently asked questions

### Nodaro の 3D シーンでは、どの単位と軸を使いますか？

単位はメートルで、Y 軸を上向きとする右手座標系です。フレームは 0 から数え、オイラー回転はラジアンで表します。

### バージョン 1 のシーンと、バージョン 2 のシーンは何が違いますか？

バージョン 1 のシーンは、プリミティブな形状と、オブジェクトおよびカメラのまばらなキーフレームを、すべてプラン内に記述します。バージョン 2 のシーンでは、保持された GLB ジオメトリ、セマンティックエンティティ、密なカメラトラック、連続するショットが加わり、バイナリファイルは認証が必要な API の向こう側にとどまります。

### 自分のコードでシーンプランを検証するには、どうすればよいですか？

@nodaro/shared パッケージのスキーマを使います。scene3DAnyPlanSchema はどちらのバージョンも受け付け、scene3DPlanSchema はバージョン 1 だけを検証します。

### シーンを編集すると、元のプランは変わりますか？

いいえ。編集はすべて、独自のリビジョン ID と、親リビジョンへのリンクを持つ、新しい不変のリビジョンを作成します。編集前のプランはそのまま変わらず、バージョン 2 の編集でも、ベースのジオメトリとカメラのファイルはそのままです。

### バージョン 2 のシーンでは、どの GLB ファイルを使えますか？

リジッドアニメーションを伴うクレイジオメトリです。テクスチャ付き、スキン付き、モーフターゲット付きのアセットは拒否されます。1 つのシーンに含められるのは、最大 100 個のエンティティ、2,000 個のメッシュノード、200,000 個の三角形、32 個のショット、64 MiB の再生用アセットまでです。
