A-Frame で WebXR hit-test を利用する方法
A-Frame v1.1.0 でDom-Overlay、hit-testが使えるようになったのは、以前記事にしました。
Dom-Overlayについては、以下に記載しています。
今回は hit-test について書いていきます。
これで現実空間の垂直面/水平面を取得することができます。
WebXR の設定
前回も書きましたが、A-FrameにWebXRを利用することを知らせる必要があります。
そのため、A-Frameにはwebxrコンポーネントが用意されています。
実装は簡単で、a-sceneタグにwebxrコンポーネントを設定するだけ
<a-scene webxr>
hit-test の設定
これだけでは hit-test を利用することはできません。
以下の4ステップの設定が必要です。
- webxr コンポーネントの設定
- xrHitTestSource の取得
- hitTestResult の取得
- 位置情報・回転情報の反映
それぞれ見ていきましょう。
webxr コンポーネントの設定
先ほど設定したwebxrコンポーネントの引数として、
- optionalFeatures: hit-test
を設定します。
<a-scene webxr="optionalFeatures: hit-test">
xrHitTestSource の取得
xrHitTestSource とはhit-testの結果を取得するための事前準備・インターフェースと考えてください。
詳しくは以下を参考に。
具体的にどのように取得するかは、
- a-scene から renderer を取得
- renderer から session を取得
- session からxrRefereneceSpaceを取得します。
- 取得したxrReferenceSpace を引数にxrHitTestSourceを取得します。
let scene = document.querySelector('a-scene'); let renderer = scene.renderer; let session = renderer.xr.getSession(); let viewerSpace = await session.requestReferenceSpace("viewer"); let xrHitTestSource = await session.requestHitTestSource({ space: viewerSpace });
xrReferenceSpace について別途説明を書こうと思います。
hitTestResult の取得
取得した xrHitTestSource を使って、hit-testを実行して結果(hitTestResult)を取得します。
- AnimationFrameを取得する。
- xrReferenceSpaceを取得する。
- AnimationFrameからhitTestResultを取得する
let frame = scene.frame; let refSpace = renderer.xr.getReferenceSpace(); let hitTestResults = frame.getHitTestResults(refSpace);
位置情報・回転情報の反映
取得したhitTestResultを解析して、位置・回転情報を取得する。
- hitTestResultからpose情報を取得する
- pose情報から位置、回転情報を取得する
- それぞれを対象に反映する
const pose = hitTestResults[0].getPose(refSpace); target.setAttribute("position", pose.transform.position); target.object3D.quaternion.copy(pose.transfrom.orientation);
コンポーネント化する
実際に使うことを考えてコンポーネント化しておくと便利になります。
hit-test結果を自分自身に反映するコンポーネントを作成します。
ARセッションスタート時にxrHitTestSourceを取得して、
フレーム毎にhitTestResultを取得して、自分自身の位置、回転情報を更新します。
AFRAME.registerComponent("ar-hit-test", { init: function() { // session start this.el.sceneEl.renderer.xr.addEventListener("sessionstart", async () => { if (this.el.sceneEl.is("ar-mode")) { this.renderer = this.el.sceneEl.renderer; let session = this.renderer.xr.getSession(); let viewerSpace = await session.requestReferenceSpace("viewer"); this.xrHitTestSource = await session.requestHitTestSource({ space: viewerSpace }); } }); // session end this.el.sceneEl.renderer.xr.addEventListener("sessionend", async () => { this.xrHitTestSource = null; }); }, tick: function() { const frame = this.el.sceneEl.frame; if (!frame) return; // hit-test in real world const xrHitTestSource = this.xrHitTestSource; if (xrHitTestSource) { const refSpace = this.renderer.xr.getReferenceSpace(); const hitTestResults = frame.getHitTestResults(xrHitTestSource); if (hitTestResults.length > 0) { const pose = hitTestResults[0].getPose(refSpace); this.el.setAttribute("position", pose.transform.position); this.el.object3D.quaternion.copy(pose.transform.orientation); } } } });