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

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 に直接配置されます。setParent() または 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 の表示設定を変更するには、setHidden() を使用します。

// Hide the entity
entity.setHidden(true)

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 の作成時に AnchorPlacement のセットを指定します。

任意の垂直サーフェスと床と天井の水平サーフェスにのみ移動してアンカーできるエンティティの例を次に示します。

val anchorPlacement = AnchorPlacement.createForPlanes(
    planeTypeFilter = setOf(PlaneSemantic.FLOOR, PlaneSemantic.TABLE),
    planeSemanticFilter = setOf(PlaneType.VERTICAL)
)

val movableComponent = MovableComponent.create(
    session = session,
    systemMovable = false,
    scaleInZ = false,
    anchorPlacement = setOf(anchorPlacement)
)
entity.addComponent(movableComponent)

ユーザーがエンティティを移動すると、scaleInZ パラメータは、ホーム スペースでシステムがパネルのスケールを調整するのと同様に、エンティティがユーザーから離れるにつれてエンティティのスケールを自動的に調整します。エンティティ コンポーネント システムの「カスケード」の性質により、親のスケールはすべての子に影響します。

ResizableComponent を使用して Entity のサイズをユーザーが変更できるようにする

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

ResizableComponent を使用する場合は、onResizeUpdateonResizeEnd などの特定のリサイズ イベントに応答するために ResizeListener を指定する必要があります。

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

val resizableComponent = ResizableComponent.create(session)
resizableComponent.minimumSize = Dimensions(177f, 100f, 1f)
resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio

resizableComponent.addResizeListener(
    executor,
    object : ResizeListener {
        override fun onResizeEnd(entity: Entity, finalSize: Dimensions) {

            // update the size in the component
            resizableComponent.size = finalSize

            // update the Entity to reflect the new size
            (entity as SurfaceEntity).canvasShape = SurfaceEntity.CanvasShape.Quad(finalSize.width, finalSize.height)
        }
    },
)

entity.addComponent(resizableComponent)

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

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

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_TYPE_RIGHT) {
            entity.setScale(1.5f)
        } else if (it.pointerType == InputEvent.POINTER_TYPE_LEFT) {
            entity.setScale(0.5f)
        }
    }
}
entity.addComponent(interactableComponent)