A-Frame で WebXR hit-test を利用する方法
A-Frame v1.1.0 でDom-Overlay、hit-testが使えるようになったのは、以前記事にしました。
A-Frame v1.1.0 から 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の結果を取得するための事前準備・インターフェースと考えてください。
詳しくは以下を参考に。
https://immersive-web.github.io/hit-test/#hit-test-source-interface
具体的にどのように取得するかは、
- 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 についてどこかで別途説明を書こうと思います。
https://immersive-web.github.io/webxr/#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);
}
}
}
});