Three.js で Web VR/AR アプリを作るとき、スマホの向きに応じて空間内のカメラの向きを操作するのに THREE.DeviceOrientationControls が使われる。しかしこのクラスを使うのは一筋縄ではいかず、その課題と対処法についてまとめる。
THREE.DeviceOrientationControls のデバイス差問題
Three.js でスマホのジャイロセンサーと空間内のカメラを同期させる方法を調べると、だいたい THREE.DeviceOrientationControls を使用するやり方が出てくる。
しかしこのクラスは2021年10月にリリースされた r134 にて削除されてしまっている。
関連する Issue を読んでいくと、以下のことがわかる。
-
#15828
- デバイスによって
THREE.DeviceOrientationControls
を使用した場合の初期方向が異なってしまう問題が発生している - → これはデバイスの実装に依存するもので、Three.js 側では対応しない
- デバイスによって
-
#22613
- (Issue の内容としては同様)
- → このクラスを削除することに (r134 で削除)
-
#22996
-
THREE.DeviceOrientationControls
を復活させてほしいという Issue - → クラスを復活させることはしないが、ユーザが過去のコミットからクラスを復元して使用しても問題ない
-
クラスそのものは過去のものを復元して使用すればいいとして、デバイス差が発生する問題は解決されていない。
実際に自分で試した際にも、iPhone 11 (Safari) で初期状態で正面になるようにオブジェクトを配置したところ、それを Pixel 6 (Chrome) で見た際には左側に表示されてしまった。(初期状態でのカメラの向きが90°ズレている)
デバイス差を無理矢理に吸収する
初期状態でのジャイロセンサーの向きの値を取得して、それをオフセットとして設定して相殺してやればデバイス間の差分は吸収できるだろう、という雑な考え。
const controls = ... // DeviceOrientationControls オブジェクト
/**
* スクリーンが垂直になるようにスマホを持っているときの、スクリーン裏面が向いている方角 (-180 〜 180) を計算する
*/
function calcDeviceDirection(e: DeviceOrientationEvent): number {
const ry = ((e.gamma || 0) * Math.PI) / 180
const rx = ((e.beta || 0) * Math.PI) / 180
const rz = ((e.alpha || 0) * Math.PI) / 180
const cy = Math.cos(ry)
const sy = Math.sin(ry)
const cx = Math.cos(rx)
const sx = Math.sin(rx)
const cz = Math.cos(rz)
const sz = Math.sin(rz)
const x = -(sy * cz + cy * sx * sz)
const y = -(sy * sz - cy * sx * cz)
const z = -(cy * cx)
const angle = Math.atan2(-x, y) * (180.0 / Math.PI)
return angle
}
window.addEventListener(
'deviceorientation',
(event: DeviceOrientationEvent) => {
const deg = calcDeviceDirection(event)
const rad = deg * (Math.PI / 180) // deg2rad (-π 〜 π)
if (controls) controls.alphaOffset -= rad
},
{ once: true }
)
一度だけ 'deviceorientation'
イベントを受け取って DeviceOrientationControls
のオフセットを更新している。
例えば この example コード でいえば、init 関数の末尾にこの処理を追加しておくだけでよい。これで各端末で同じ方向を向けるようになった。