THREE.jsのpointerlock controlsをスマホ対応にする
■やりたいこと
スマホでもPointerlockControlsが使える様にする。
移動に関しては擬似十字キー(新たなDOM要素)を使用する。
■前提
Pointerlock APIはDOM拡張のためのAPIで、ビューポート内のマウスカーソルの絶対位置だけでなく、時間の経過に伴うマウスの動き (すなわち、デルタ) に基づく入力方法を提供。
Three.jsではこれを利用したカメラコントロール「PointerLockContolrs」が提供されている。
しかし、マウスカーソルの位置のデルタを元にしているために、Touchデバイスに対応していない。
これをTouchデバイス対応、すなわちレスポンシブ対応可能な形で実装する。
■方向性
・THREE.jsの公式に掲載されているブラウザ用の下記コード(https://threejs.org/examples/misc_controls_pointerlock.html)をTouchデバイス用に改変する
function onMouseMove(event) {
if (scope.isLocked === false) return;
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
euler.setFromQuaternion(camera.quaternion);
euler.y -= movementX * 0.002;
euler.x -= movementY * 0.002;
euler.x = Math.max(PI_2 - scope.maxPolarAngle, Math.min(PI_2 - scope.minPolarAngle, euler.x));
euler.y = Math.max(-PI_2y, Math.min(PI_2y, euler.y));
camera.quaternion.setFromEuler(euler);
scope.dispatchEvent(changeEvent);
}
・要はMouseMoveにふられているイベントをTouchイベントに振り分けること
・Touchイベントはtouchstart、touchmove、touchendの3種類がある。
・必要なことは一度指を離しても、同じ地点からカメラの回転を再スタートできること
・カメラスタート時にTouchend時の座標をtouchstart時に保存できれば完璧
■トライアル:touchmoveのみにイベントをふってみる。
・カメラを回すところまではtouchmoveに割り振れば良いので問題なし。
・しかし、指を離すと(=touchendが記録されると)、カメラの角度が振り出しに戻ってしまう。
■方向性の修正
・touchmove時の座標を収集 →配列に
・touchmove時のオイラー角を収集 →配列に
・取得した配列から回転を規定
・touchend時に最後のtouchmoveからの差分をオフセットする。
■完成コード
//Function for getting arrays on touchmove
function arrayTouches(e) {
// array for touches
touches_x = e.changedTouches[0].clientX - ww
touches_y = e.changedTouches[0].clientY - wh
array_x.push(touches_x)
array_y.push(touches_y)
last_x = array_x[array_x.length - 2];
last_y = array_y[array_y.length - 2];
// array for euler
eulerY = (touches_x - last_x) * 0.004;
eulerX = (touches_y - last_y) * 0.004;
if (eulerY) {
eulerY_angle.push(eulerY)
eulerX_angle.push(eulerX)
} else {
eulerY_angle.push(0);
eulerX_angle.push(0);
}
scope.dispatchEvent(changeEvent);
}
//Function for set rotation from the arrays
function onTouchMove(e) {
euler.setFromQuaternion(camera.rotation);
eulerY_total = eulerY_angle.reduce(function (sum, element) {
return sum + element;
}, 0);
eulerX_total = eulerX_angle.reduce(function (sum, element) {
return sum + element;
}, 0);
euler.y = eulerY_final + eulerY_total
euler.x = eulerX_final + eulerX_total
camera.quaternion.setFromEuler(euler);
scope.dispatchEvent(changeEvent);
};
// the delta value of euler and touchmove should be offset for recalibration from where the last move stopped
function onTouchEnd(e) {
eulerY_final = euler.y;
eulerX_final = euler.x;
eulerY_angle = [];
eulerX_angle = [];
array_x = [];
array_y = [];
scope.dispatchEvent(changeEvent);
}
■所感
なんとかできたという感じ。web上を探してもpointerlockcontrolsでのtouchデバイス対応の例はほぼなかったので、そもそもカスタムコントロールを書けばいいじゃんか、と作ってから思った次第。
いちいち配列を作っていることにも疑問を感じるし、コードとして洗練されてはいないと思うけど、
まぁ頑張ったかと思っている。