エンティティの作成、制御、管理

Jetpack XR SDK を使用すると、Jetpack SceneCore を使用して、3D モデル立体視動画PanelEntity などの Entity インスタンスを作成、制御、管理できます。

Jetpack SceneCore は、3D 開発をサポートするために、シーングラフエンティティ コンポーネント システム(ECS)という 2 つの一般的なアーキテクチャ パターンを採用しています。

シーングラフを使用してエンティティを作成して制御する

3D 空間でオブジェクトを作成して制御するには、Jetpack SceneCore の Session API を使用してシーングラフにアクセスします。シーングラフはユーザーの現実世界と一致しており、パネルや 3D モデルなどの 3D エンティティを階層構造に整理し、それらのエンティティの状態を保持できます。

シーングラフにアクセスしたら、Jetpack Compose for XR の API を使用して、シーングラフ内に空間 UI(SpatialPanelOrbiter インスタンスなど)を作成できます。3D モデルなどの 3D コンテンツの場合は、セッションに直接アクセスできます。詳しくは、このページのActivitySpace についてをご覧ください。

エンティティ コンポーネント システム

エンティティ コンポーネント システムは、継承よりもコンポジションの原則に従います。動作を定義するコンポーネントをアタッチすることで、エンティティの動作を拡張できます。これにより、異なるタイプのエンティティに同じ動作を適用できます。詳細については、このページのエンティティに共通の動作を追加するをご覧ください。

ActivitySpace について

Session には、Session とともに自動的に作成される ActivitySpace があります。ActivitySpace は、シーングラフの最上位の Entity です。

ActivitySpace は、右手系座標系(原点に対して x 軸は右、y 軸は上、z 軸は後ろ)と、現実世界に合わせたメートル単位の 3 次元空間を表します。ActivitySpace の原点はやや任意です(ユーザーは現実世界で ActivitySpace の位置をリセットできるため)。そのため、コンテンツを原点に対して相対的に配置するのではなく、コンテンツ同士を相対的に配置することをおすすめします。

エンティティを操作する

エンティティは SceneCore の中心です。ユーザーが目にして操作するもののほとんどは、パネルや 3D モデルなどを表すエンティティです。

ActivitySpace はシーングラフの最上位ノードであるため、デフォルトでは、すべての新しいエンティティが ActivitySpace に直接配置されます。エンティティをシーングラフに沿って再配置するには、parent を設定するか、addChild() を使用します。

エンティティには、位置、回転、可視性の変更など、すべてのエンティティに共通する動作のデフォルト設定があります。GltfModelEntity などの特定の Entity サブクラスには、サブクラスをサポートする追加の動作があります。

エンティティを操作する

基本 Entity クラスに属する Entity プロパティを変更すると、その変更はすべての子にカスケードされます。たとえば、親 EntityPose を調整すると、すべての子にも同じ調整が適用されます。子 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 を使用して Entity をユーザー移動可能にする

MovableComponent を使用すると、ユーザーが Entity を移動できるようになります。

装飾が操作されると、移動イベントがコンポーネントにディスパッチされます。MovableComponent.createSystemMovable() で作成されたデフォルトのシステム動作では、デコレーションがドラッグされると Entity が移動します。

val movableComponent = MovableComponent.createSystemMovable(session)
entity.addComponent(movableComponent)

オプションの scaleInZ パラメータ(デフォルトでは true に設定)を使用すると、ホーム空間でシステムがパネルのスケールを調整するのと同様に、ユーザーから離れるにつれてエンティティのスケールが自動的に調整されます。エンティティ コンポーネント システムの「カスケード」の性質により、親のスケールはすべての子に影響します。

エンティティを水平面や垂直面などのサーフェス タイプ、またはテーブル、壁、天井などの特定のセマンティック サーフェスにアンカーできるかどうかを指定することもできます。アンカー オプションを指定するには、MovableComponent の作成時に AnchorPlacement のセットを指定します。この例では、任意の床またはテーブルの水平面に移動して固定できるエンティティ:

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 を使用して Entity のサイズをユーザーが変更できるようにする

ResizableComponent を使用すると、ユーザーは Entity のサイズを変更できます。ResizableComponent には、ユーザーが Entity のサイズを変更するよう促す視覚的な操作キューが含まれています。ResizableComponent を作成する際に、最小サイズまたは最大サイズ(メートル単位)を指定できます。サイズ変更時に固定のアスペクト比を指定して、幅と高さの比率を維持することもできます。

ResizableComponent を作成するときに、更新イベントを処理する resizeEventListener を指定します。RESIZE_STATE_ONGOINGRESIZE_STATE_END など、さまざまな ResizeState イベントに応答できます。

SurfaceEntity で固定アスペクト比の ResizableComponent を使用する例を次に示します。

val resizableComponent = ResizableComponent.create(session) { event ->
    if (event.resizeState == ResizeEvent.ResizeState.RESIZE_STATE_END) {
        // update the Entity to reflect the new size
        surfaceEntity.canvasShape = SurfaceEntity.CanvasShape.Quad(event.newSize.width, event.newSize.height)
    }
}
resizableComponent.minimumEntitySize = FloatSize3d(177f, 100f, 1f)
resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio

surfaceEntity.addComponent(resizableComponent)

InteractableComponent を使用してユーザー入力イベントをキャプチャする

InteractableComponent を使用すると、ユーザーが Entity を操作したり、Entity にカーソルを合わせたりしたときなど、ユーザーからの入力イベントをキャプチャできます。InteractableComponent を作成するときは、入力イベントを受信するリスナーを指定します。ユーザーが入力アクションを行うと、InputEvent パラメータで提供された入力情報とともにリスナーが呼び出されます。

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.SOURCE_HANDS && it.action == InputEvent.Action.ACTION_UP) {
        // increase size with right hand and decrease with left
        if (it.pointerType == InputEvent.Pointer.POINTER_TYPE_RIGHT) {
            entity.setScale(1.5f)
        } else if (it.pointerType == InputEvent.Pointer.POINTER_TYPE_LEFT) {
            entity.setScale(0.5f)
        }
    }
}
entity.addComponent(interactableComponent)