🧅

[Three.js] クォータニオンでスマホと連動してオニオンを回転させる

2021/08/10に公開

スマホの傾き(ジャイロ)に連動して回転するオニオンを実装します。

demo

DeviceOrientationEvent API

DeviceOrientationEvent API を使ってデバイスの方向を取得します。
主に、alpha beta gamma の角度を使います。
それぞれの回転軸についてはこちらの記事がわかりやすいです。

執筆時点でExperimentalとなっていますが、主要モバイルブラウザは対応しています
ただし条件として、https対応していること と何かしらのユーザーアクションがないとイベントを取得することができません。(iOS13-)
一時、iOS12ではブラウザから離れてユーザーの端末設定画面で許可を促す仕様になっていたので、実装者としてはホッとしました..!

許可の具体的な実装方法についてはこちらの記事を参考にさせていただきました。
https://qiita.com/okumura_daiki/items/16a09c9c0d0b2509d261

3Dオブジェクトにスマホの回転を適応する

先程取得した3つの値はZ-X'-Y'' 型のオイラー角というやつなので、クォータニオンに変換して扱いやすくします。クォータニオンと聞くと難しそうですが、特に3Dの世界でオブジェクトの姿勢を扱いやすくしてくれる便利なやーつだと思っていただければ大丈夫です(私も深くは分かっていないです😅)

Three.jsには3Dグラフィックの計算でよく扱うUtil系の関数も入っているので、そのまま利用します。

// オニオンオブジェクトの生成は割愛

window.addEventListener("orientationchange", onScreenOrientationChange);
window.addEventListener("deviceorientation", onOrientationChange, true);

const qt = new THREE.Quaternion();
const _qt = new THREE.Quaternion();
const _q0 = new THREE.Quaternion();
const _q1 = new THREE.Quaternion().setFromAxisAngle(
  new THREE.Vector3(1, 0, 0),
  -Math.PI / 2
); // x軸-90度回転
const _zee = new THREE.Vector3(0, 0, 1);
const _euler = new THREE.Euler();
let initialQt // キャリブレーション用
let screenOrientation = window.orientation || 0


function onOrientationChange(e) {
  const beta = THREE.MathUtils.degToRad(e.beta);
  const alpha = THREE.MathUtils.degToRad(e.alpha);
  const gamma = THREE.MathUtils.degToRad(-e.gamma);
  const orient = THREE.MathUtils.degToRad(this.screenOrientation);

  _euler.set(beta, alpha, -gamma, "ZXY"); // 仕様で決まってる順番

  _qt.setFromEuler(_euler); // ①
  _qt.multiply(_q1); // ②
  _qt.multiply(_q0.setFromAxisAngle(_zee, -orient)); // ③
  if (initialQt) _qt.multiply(initialQt); // ④
  _qt.z *= -1; // ⑤

  // Calibration
  if (!initialQt) {
    initialQt = _qt.clone().invert();
  }

  qt.copy(_qt);
}

function onScreenOrientationChange() {
  screenOrientation = window.orientation || 0;
}

function draw() {
  const amount = -Math.PI * 0.15;
  onion.quaternion.slerp(qt, 0.1); // ⑥
  // ⑦
  onion.rotation.x = THREE.MathUtils.clamp(
    onion.rotation.x,
    amount * 0.6,
    -amount * 0.6
  );
  onion.rotation.y = THREE.MathUtils.clamp(
    onion.rotation.y,
    amount * 0.5,
    -amount * 0.5
  );
  onion.rotation.z = THREE.MathUtils.clamp(cube.rotation.z, amount, -amount);

  // render処理
}

① オイラー角からクォータニオンに変換しています(簡単ですね・・!)
② デフォルトだとスマホを寝かせた状態(ユーザーの視点と平行)を基準とした回転の値になっているので、スマホの画面とユーザーの視点が直行するようにX軸に対して-90度回転させます
③ 最初の行で deviceorientation と同時に orientationchange イベントも登録していて、スマホを縦にしたり横にしたりしたときに起きる画面の回転を取得しています。
④ ユーザーアクションを起こした時点でのスマホの傾きが基準になるように補正しています
⑤ ユーザー視点でスマホとオブジェクトの回転を同じ方向にするためミラーリングします
⑥ オブジェクトの動きになめらかさを加えるため、現在のオブジェクトの回転から新しい回転への間を補完しながら、新しい回転に近づける処理を行っています
⑦ それぞれの軸について、回転の最小値、最大値を指定しています(値はお好みで)

おわり

クォータォニオンべんり〜(これを言うためだけにオニオンをモデリングしました)

Discussion