Open1

THREE.jsのpointerlock controlsをスマホ対応にする

EriFuruyama@KEY4d LAB.EriFuruyama@KEY4d LAB.

■やりたいこと
スマホでも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デバイス対応の例はほぼなかったので、そもそもカスタムコントロールを書けばいいじゃんか、と作ってから思った次第。
いちいち配列を作っていることにも疑問を感じるし、コードとして洗練されてはいないと思うけど、
まぁ頑張ったかと思っている。