Jetpack XR SDK を使用すると、Jetpack SceneCore を使用して、
Entity インスタンス(3D モデル、立体視動画、
PanelEntity など)を作成、制御、管理できます。
Jetpack SceneCore は、3D 開発をサポートするために、シーングラフとエンティティ コンポーネント システム(ECS)という 2 つの一般的なアーキテクチャ パターンを採用しています。
シーングラフを使用してエンティティを作成して制御する
3D 空間でオブジェクトを作成して制御するには、Jetpack SceneCore's Session API を使用してシーングラフにアクセスします。シーングラフはユーザーの現実世界と一致しており、パネルや 3D モデルなどの 3D エンティティを階層構造に整理し、それらのエンティティの状態を保持できます。
シーングラフにアクセスしたら、Jetpack
Compose for XR の API を使用して、シーングラフ内に空間 UI(SpatialPanel や
Orbiter インスタンスなど)を作成できます。3D モデルなどの 3D コンテンツの場合は、Session に直接アクセスできます。詳細については、このページの About the
ActivitySpace についてをご覧ください。
エンティティ コンポーネント システム
エンティティ コンポーネント システムは、継承よりも構成を優先する原則に従います。動作を定義するコンポーネントをエンティティにアタッチすることで、エンティティの動作を拡張できます。これにより、さまざまなタイプのエンティティに同じ動作を適用できます。詳細については、このページのエンティティに共通の 動作を追加するをご覧ください。
ActivitySpace について
各 Session には、Session とともに自動的に作成される ActivitySpace があります。ActivitySpace は、シーングラフ内の最上位の Entity です。
ActivitySpace は、右手座標系(原点を基準として、x 軸は右、y 軸は上、z 軸は奥を向く)と、現実世界に一致するメートル単位の 3 次元空間を表します。ActivitySpace の原点はやや任意です(ユーザーは現実世界で ActivitySpace の位置をリセットできるため)。そのため、原点を基準とするのではなく、コンテンツを互いに相対的に配置することをおすすめします。
エンティティを操作する
エンティティは SceneCore の中心です。ユーザーが目にして操作するほとんどのものは、パネルや 3D モデルなどを表すエンティティです。
ActivitySpace はシーングラフの最上位ノードであるため、デフォルトでは、新しいエンティティはすべて ActivitySpace に直接配置されます。エンティティをシーングラフに沿って再配置するには、
parent を設定するか、
addChild() を使用します。
エンティティには、位置、回転、表示 / 非表示の変更など、すべてのエンティティに共通するデフォルトの動作がいくつかあります。
サブクラスなどの特定の GltfModelEntity には、サブクラスをサポートする追加の動作があります。Entity
エンティティを操作する
基本 Entity クラスに属する Entity プロパティを変更すると、その変更はすべての子にカスケードされます。たとえば、
親 Entity の Pose を調整すると、すべての子が同じ調整になります。子 Entity を変更しても、親には影響しません。
Pose は、3D 空間内のエンティティの位置と回転を表します。位置は、x、y、z の数値位置で構成される Vector3 です。回転は Quaternionで表されます。Entity の位置は常に親エンティティを基準とします。つまり、位置が(0, 0, 0)の Entity は、親エンティティの原点に配置されます。
// Place the entity forward 2 meters val newPosition = Vector3(0f, 0f, -2f) // Rotate the entity by 180 degrees on the up axis (upside-down) val newOrientation = Quaternion.fromEulerAngles(0f, 0f, 180f) // Update the position and rotation on the entity entity.setPose(Pose(newPosition, newOrientation))
Entity を無効にするには、setEnabled() を使用します。これにより、非表示になり、処理がすべて停止します。
// Disable the entity. entity.setEnabled(false)
全体的な形状を維持しながら Entity のサイズを変更するには、setScale() を使用します。
// Double the size of the entity entity.setScale(2f)
エンティティに共通の動作を追加する
次のコンポーネントを使用して、エンティティに共通の動作を追加できます。
MovableComponent: ユーザーがエンティティを移動できるようにします。ResizableComponent: ユーザーがエンティティのサイズを変更できるようにします。UI パターンは一貫しています。InteractableComponent: カスタム操作の入力イベントをキャプチャできます。
コンポーネントのインスタンス化は、Session クラスの適切な作成メソッドを使用して行う必要があります。たとえば、ResizableComponent を作成するには、
ResizableComponent.create() を呼び出します。
特定のコンポーネントの動作を Entity に追加するには、
addComponent() メソッドを使用します。
MovableComponent を使用して、エンティティをユーザーが移動できるようにする
MovableComponent を使用すると、ユーザーが Entity を移動できます。
装飾が操作されると、移動イベントがコンポーネントにディスパッチされます。
MovableComponent.createSystemMovable() で作成されたデフォルトのシステム動作では、装飾をドラッグすると Entity が移動します。
val movableComponent = MovableComponent.createSystemMovable(session) entity.addComponent(movableComponent)
オプションの scaleInZ パラメータ(デフォルトでは true に設定)を使用すると、エンティティがユーザーから離れるにつれて、ホーム空間でパネルがシステムによってスケーリングされるのと同様に、スケールが自動的に調整されます。エンティティ コンポーネント システムは「カスケード」されるため、親のスケールはすべての子に影響します。
エンティティを水平面や垂直面などのサーフェス タイプ、またはテーブル、壁、天井などの特定のセマンティック サーフェスに固定できるかどうかを指定することもできます。アンカー オプションを指定するには、AnchorPlacement
の作成時に MovableComponentのセットを指定します。この例では、任意の床またはテーブルの水平面に移動して固定できるエンティティを示します。
val anchorPlacement = AnchorPlacement.createForPlanes( anchorablePlaneOrientations = setOf(PlaneOrientation.VERTICAL), anchorablePlaneSemanticTypes = setOf(PlaneSemanticType.FLOOR, PlaneSemanticType.TABLE) ) val movableComponent = MovableComponent.createAnchorable( session = session, anchorPlacement = setOf(anchorPlacement) ) entity.addComponent(movableComponent)
ResizableComponent を使用して、エンティティをユーザーがサイズ変更できるようにする
ResizableComponent を使用すると、ユーザーは Entity のサイズを変更できます。ResizableComponent には、ユーザーに Entity のサイズ変更を促す視覚的な操作キューが含まれています。ResizableComponent を作成するときに、最小サイズまたは最大サイズ(メートル単位)を指定できます。サイズ変更時に固定のアスペクト比を指定して、幅と高さの比率を維持しながらサイズ変更することもできます。
ResizableComponent を作成するときに、更新イベントを処理する resizeEventListener を指定します。さまざまな ResizeState
イベントに応答できます。たとえば、RESIZE_STATE_ONGOING や RESIZE_STATE_END などです。
以下に、SurfaceEntity で固定アスペクト比の ResizableComponent を使用する例を示します。
val resizableComponent = ResizableComponent.create(session) { event -> if (event.resizeState == ResizeEvent.ResizeState.END) { // update the Entity to reflect the new size surfaceEntity.shape = SurfaceEntity.Shape.Quad(FloatSize2d(event.newSize.width, event.newSize.height)) } } resizableComponent.minimumEntitySize = FloatSize3d(177f, 100f, 1f) resizableComponent.isFixedAspectRatioEnabled = true // Maintain a fixed aspect ratio when resizing surfaceEntity.addComponent(resizableComponent)
InteractableComponent を使用してユーザー入力イベントをキャプチャする
InteractableComponent を使用すると、ユーザーが Entity を操作したり、エンティティにカーソルを合わせたりしたときなど、ユーザーからの入力イベントをキャプチャできます。InteractableComponent を作成するときに、入力イベントを受け取るリスナーを指定します。ユーザーが入力操作を行うと、リスナーは
入力情報を使用して InputEvent パラメータで呼び出されます。
InputEvent.actionは、エンティティへの カーソル合わせ や タップなどの入力タイプを指定します。InputEvent.sourceは、入力元( 手やコントローラの入力など)を指定します。InputEvent.pointerTypeは、入力が右手からか左手からかを指定します。
すべての InputEvent 定数の一覧については、リファレンス
ドキュメントをご覧ください。
次のコード スニペットは、InteractableComponent を使用して、右手でエンティティのサイズを大きくし、左手で小さくする例を示しています。
val executor = Executors.newSingleThreadExecutor() val interactableComponent = InteractableComponent.create(session, executor) { // when the user disengages with the entity with their hands if (it.source == InputEvent.Source.HANDS && it.action == InputEvent.Action.UP) { // increase size with right hand and decrease with left if (it.pointerType == InputEvent.Pointer.RIGHT) { entity.setScale(1.5f) } else if (it.pointerType == InputEvent.Pointer.LEFT) { entity.setScale(0.5f) } } } entity.addComponent(interactableComponent)
実行時にカスタム 3D モデルを作成する
カスタム メッシュ API を使用すると、glTF ファイルなどの静的アセットを読み込むのではなく、コード内で 3D 形状をプログラムで直接生成できます。カスタム ジオメトリをオンザフライで構築することで、手続き型データ、動的なカスタム形状、ユーザーが探索するにつれて継続的に生成される地形などの無限に見える 3D 環境をレンダリングできます。また、実行時にメッシュを生成することで、単一の 3D アセットの無数のバリエーションをパッケージ化する必要がなくなり、バイナリサイズを小さくできます。