easy-three

xr

Web XR を利用するための仕組みを提供します。

使用方法

まず、init() の戻り値から xr を取得します。

const { create, camera, animate, xr } = init();

その後、setup() を呼び出すことで有効化されます。

setup() の引数で、コントローラーやハンドの有効化、ボタンの設置先などを指定することができます。

  • leftController: 左コントローラーを有効化するかどうかを指定します。デフォルトは true です。
  • rightController: 右コントローラーを有効化するかどうかを指定します。デフォルトは true です。
  • leftHand: 左ハンドを有効化するかどうかを指定します。デフォルトは true です。
  • rightHand: 右ハンドを有効化するかどうかを指定します。デフォルトは true です。
  • buttonTarget: ボタンの設置先を指定します。デフォルトは domElement です。
  • selectableObjects: コントローラーで選択可能なオブジェクトの配列を指定します。デフォルトは [] です。
  • getIntersections: コントローラーのレイキャスターと交差するオブジェクトを取得する関数です。引数は、コントローラーのオブジェクト、交差判定するオブジェクトの配列、再帰的に判定するかどうかのフラグです。 基本的にこの関数を直接呼び出す必要はありません。

setup() の戻り値として、コントローラーやハンドのオブジェクトが返されます。

  • leftController: 左コントローラーのオブジェクトです。
  • rightController: 右コントローラーのオブジェクトです。
  • leftHand: 左ハンドのオブジェクトです。
  • rightHand: 右ハンドのオブジェクトです。
  • cameraGroup: カメラとコントローラー、ハンドをまとめたグループです。XRモードでは、カメラの位置や向きはユーザーの頭の動きに合わせて自動的に更新されるため、カメラの位置や向きを直接変更するコードは無視されます。
const {
  leftController,
  rightController,
  leftHand,
  rightHand,
  cameraGroup,
} = xr.setup({
  leftController: true,
  rightController: true,
  leftHand: true,
  rightHand: true,
  selectableObjects: [],
})

selectableObjects に指定したオブジェクトは、コントローラーで選択可能になります。選択されたオブジェクトの配列は、コントローラーの userData.selected に格納されます。

const selected = rightController.userData.selected;

コントローラには、あらかじめ以下のように selectstart と selectend のイベントリスナーが設定されており、コントローラーのトリガーを引くと、selectstart が発火し、トリガーを離すと selectend が発火します。

controller.addEventListener('selectstart', (event) => {
  const controller = event.target
  const intersections = getIntersections(controller, selectableObjects)
  if (intersections.length > 0) {
    const intersection = intersections
    controller.userData.selected = intersection
  }
})

controller.addEventListener('selectend', (event) => {
  const controller = event.target
  if (controller.userData.selected) {
    controller.userData.selected = undefined
  }
})

コントローラには、getIntersections という関数が設定されており、コントローラーのレイキャスターと交差するオブジェクトを取得することができます。
引数は、交差判定するオブジェクトの配列(デフォルトは selectableObjects)、再帰的に判定するかどうかのフラグ(デフォルトは true)です。
コントローラに selectstart 以外のイベントリスナーを設定する場合に利用してください。

fpv や controls などのカメラ操作系の機能は、XR モードでは使わないでください。
XR モードでは、カメラの位置や向きは、ユーザーの頭の動きに合わせて自動的に更新されます。
camera.position など、カメラの位置や向きを直接変更するコードは、XR モードでは無視されます。

サンプルコード

以下のサンプルコードでは、2つの立方体を作成し、右コントローラーで選択可能にしています。
選択された立方体は、赤色に変化し、回転します。
また、海面を作成し、アニメーションで波を表現しています。

XRモードではカメラの位置などはユーザーの頭の動きに合わせて自動的に更新されるため、カメラの位置や向きを直接変更するコードは無視されます。
しかし、このサンプルコードのように、プレビュー画面で確認しやすいようにカメラの位置や向きを設定すると良いです。

どうしてもカメラの初期位置を変更したい場合は、xr.setup() の戻り値の cameraGroup の位置を変更することで、カメラの初期位置を変更することができます。
例えば、cameraGroup.position.set(0, 0, 3) のように設定すると、カメラの初期位置が z=3 の位置になります。

const { camera, create, animate, controls, xr } = init();

camera.position.set(0, 1.6, 0)
controls.target.set(0, 1.6, -1)

create.ambientLight();
create.directionalLight();
create.sky()

const cube1 = create.cube({
  position: [1, 1, -3],
});
const cube2 = create.cube({
  position: [-1, 1, -3],
});

const ocean = create.ocean("/easy-three/texture/water/NormalMap-1.jpg");

const { rightController } = xr.setup({
  selectableObjects: [cube1, cube2],
});

animate(({ delta }) => {
  ocean.update(delta);

  cube1.material.color.set(0x0000ff)
  cube2.material.color.set(0x0000ff)

  if (rightController.userData.selected) {
    const object = rightController.userData.selected[0].object;
    object.rotation.y += delta;
    object.material.color.set(0xff0000)
  }
});