【Three.js】ジョイスティック風UIでARオブジェクトを操作する
概要
こんにちは、株式会社palanのxR事業部でWebAR/VRの開発をしているdamiと申します。
本記事では、ジョイスティック風UIを使用してARオブジェクトを操作(移動・回転) する方法をまとめようと思います。
▼完成イメージ
(こちらは有料のWebARライブラリ「8th Wall Web」のWorld Trackingを使用した例ですが、ARでなくてもThree.jsの3Dシーンでそのまま使えます)
1. ジョイスティックの実装
ジョイスティックの処理(傾き、大きさの計算など)については、同僚の sasanda_panda さんの記事をかなり参考にさせていただきましたのでこちらをご参考に。
ジョイスティックだけ実装するとこんな感じです。
※モバイル端末からのtouchイベントのみ発火します。
2. モデルの移動・回転
ジョイスティックUIの用意ができたら、3Dオブジェクトを連動させていきましょう。
2-1. 回転
まずはジョイスティックが傾いてる方向に3Dオブジェクトが向く(回転する)ようにするため、ジョイスティックの傾きを2次元ベクトルとして用意します。
ジョイスティックが傾いたとき(変化があったとき)のみ更新したいので、touchmove
のイベントに処理を追加します。
let joystickCenterX: number;
let joystickCenterY: number;
let joystickLimitNumber: number = 35;
joystickBall.addEventListener("touchmove", dragMove);
const dragMove = (event: TouchEvent) => {
event.preventDefault();
const pageX = event.touches[0].pageX;
const pageY = event.touches[0].pageY;
let touchX =
Math.abs(pageX - joystickCenterX) < joystickLimitNumber
? pageX - joystickCenterX
: pageX - joystickCenterX > 0
? joystickLimitNumber
: -joystickLimitNumber;
let touchY =
Math.abs(pageY - joystickCenterY) < joystickLimitNumber
? pageY - joystickCenterY
: pageY - joystickCenterY > 0
? joystickLimitNumber
: -joystickLimitNumber;
// @@@ ジョイスティックの傾きを2次元として用意
const vector2d = new THREE.Vector2(touchX, touchY);
// 後略
};
次にジョイスティックの傾き(二次元ベクトル)を、3Dオブジェクトに適用できるように三次元に変えます。
三次元ベクトルの作り方はシンプルに、以下のように
ジョイスティックxを3Dシーンx、ジョイスティックyを3Dシーンz、というThree.jsの座標系に合わせてそのまま入れます。
const vector3d = new THREE.Vector3(vector2d.x, 0, vector2d.y);
3Dオブジェクトをこのベクトルの向きに回転させましょう。
今回はちょっとハック的に…lookAt(vec3)
を使って、ジョイスティックの傾きを直接オブジェクトの向きに適用させます。
(vec3の値に1000かけることで、lookAt(vec3)
で向く方向が内側にならないようにしてます…。
もっといい方法ありそうですが一旦。)
// 2次元ベクトルを3次元ベクトルに
const vector3d = new THREE.Vector3(vector2d.x * 1000, 0, vector2d.y * 1000);
// オブジェクトを3Dベクトルの方向に向かせる(回転)
model.lookAt(vector3d);
これでジョイスティックの傾きに合わせて、オブジェクトが回転するようになりました。
2-2. 移動
次に移動の処理を書いていきます。
ここでの移動は、シンプルなxyzを使用したposition移動ではなくオブジェクトの正面方向に移動させる必要があります。
(※ワールド座標での移動ではなく、オブジェクトのローカル座標でz方向/前に動かすイメージ)
オブジェクトをローカル座標で動かす場合にはtranslate
メソッドが使用できます。
let updateRequestId;
joystickBall.addEventListener("touchstart", dragStart);
joystickBall.addEventListener("touchend", dragLeave);
const dragStart = () => {
if (!model) return;
dragUpdate();
};
const dragUpdate = () => {
// @@@ モデルを正面へ前進させる
model.translateZ(0.1);
updateRequestId = requestAnimationFrame(dragUpdate);
};
const dragLeave = () => {
cancelAnimationFrame(updateRequestId);
};
移動は回転と違って、ジョイスティックに指を置いてる限り常に呼ばれて欲しいので、touchstartで毎フレーム呼び出し、touchendでキャンセルされる処理として書いています。
また、ジョイスティックコントローラーは、傾きの大きさ(中心からの距離)に応じてスピードが速くなる特徴があるので、傾きの大きさを取得してtranslateZ()
に速さとして適用させます。
let vectorMagnitude: number = 0;
const dragMove = (event: TouchEvent) => {
// 中略
const vector2d = new THREE.Vector2(touchX, touchY);
// @@@ ベクトルの大きさ
// 三平方の定理に倣ってx, yそれぞれの二乗の和とする
vectorMagnitude = vector2d.x * vector2d.x + vector2d.y * vector2d.y;
// 中略
};
const dragUpdate = () => {
if (vectorMagnitude !== 0) {
// @@@ 大きさをスピードとして適用
// 10000の数字はスピード見ながら任意で変えてください…
model.translateZ(vectorMagnitude / 10000);
}
updateRequestId = requestAnimationFrame(dragUpdate);
};
ここまで書くと、以下のようにオブジェクトを操作できるようになります。
正面がわかりやすい3Dモデルなどを使うと、かなりそれっぽくなります。
WebAR
こちらのジョイスティックのオブジェクト操作を、そのまま8th wall webのworld trackingで応用すると現実世界の中で3Dモデルを操作するようなコンテンツを作ることができます。
Discussion