2020年05月23日

Three.jsでコントローラーを制御

Three.jsでコントローラーを取得」でコントローラーの取得方法を調べましたが、WebVRコンテンツの空間を自由に動きたいと思い、Three.jsでコントローラーを制御する方法を調べてみました。

Three.jsでコントローラーを制御

● WebXR API Emulator

WebVRコンテンツの制作は、エミュレーターでデバイスの動作確認ができたり、Consoleでデバッグできるので、WebXR API Emulatorを使用すると便利です。

● WebXRManager

Three.jsのWebXRManagerは、WebXR Device APIのデバイスや接続情報を管理するクラスで、オブジェクトはrenderer.xrのプロパティに保持されます。また、WebXRManagerには、XRレンダリングの有効化やコントローラーの取得など、XRに関連するタスクを実行する機能もあります。

● getControllerとgetControllerGrip

WebXRManagerにはgetControllerとgetControllerGripと、コントローラーに関する2つのメソッドがあります。getControllerは、TRHEE.Groupのオブジェクトを返し、光線を設置するのに使います。

//右側の光線
const controller1 = renderer.xr.getController(0);
controller1.addEventListener('connected',function(event){
	this.add(buildController(event.data));
});
scene.add(controller1);

//左側の光線
const controller2 = renderer.xr.getController(1);
controller2.addEventListener('connected',function(event){
	this.add(buildController(event.data));
});
scene.add(controller2);

//光線の生成
function buildController(data){

	//ターゲットレイモードの判定
	switch(data.targetRayMode){

		case 'tracked-pointer':
			const geometry = new THREE.BufferGeometry();
			geometry.setAttribute('position', new THREE.Float32BufferAttribute([0,0,0,0,0,-1],3));
			geometry.setAttribute('color', new THREE.Float32BufferAttribute([0.5,0.5,0.5,0,0,0],3));
			const material = new THREE.LineBasicMaterial({vertexColors:true,blending:THREE.AdditiveBlending});
			return new THREE.Line(geometry,material);

		case 'gaze':
			const gaze_geometry = new THREE.RingBufferGoemetry(0.02,0.04,32).translate(0,0,-1);
			const gaze_material = new THREE.MeshBesicMaterial({opacity:0.5,transparent:true});
			return new THREE.Mesh(gaze_geometry,gaze_material);
	}
}

getControllerGripは、TRHEE.Groupのオブジェクトを返し、コントローラーデバイスを設置するのに使います。

//右側のコントローラー
const controllerGrip1 = renderer.xr.getControllerGrip(0);
scene.add(controllerGrip1);

//左側のコントローラー
const controllerGrip2 = renderer.xr.getControllerGrip(1);
scene.add(controllerGrip2);

● XRControllerModelFactory.js

コントローラーデバイスは、ユーザーの環境に合わせて最適なものを設置してくれるXRControllerModelFactory.jsが、Three.jsにあります。XRControllerModelFactory.jsは「examples > jsm > webxr」にあり、使用するにはmotion-controllers.module.jsとGLTFLoader.jsが必要です

import { XRControllerModelFactory } from './lib/three_jsm/XRControllerModelFactory.js';

const controllerModelFactory = new XRControllerModelFactory();

//右側のコントローラー
const controllerGrip1 = renderer.xr.getControllerGrip(0);
controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
scene.add(controllerGrip1);

//左側のコントローラー
const controllerGrip2 = renderer.xr.getControllerGrip(1);
controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
scene.add(controllerGrip2);

● script.js

必要なライブラリを読み込みます。

<script src="js/preloadjs.min.js"></script>
<script src="js/TweenMax.min.js"></script>
<script src="js/script.js" type="module"></script>

完成したscript.jsになります。

//===============================================================
// Import Library
//===============================================================
import * as THREE from './lib/three_jsm/three.module.js';
import { VRButton } from './lib/three_jsm/VRButton.js';
import { XRControllerModelFactory } from './lib/three_jsm/XRControllerModelFactory.js';

//===============================================================
// Main
//===============================================================
window.addEventListener('load',function(){
   init();
});

let scene,camera,renderer;

function init(){
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,100);
    camera.position.set(0,1.6,3);
    scene.add(camera);
    renderer = new THREE.WebGLRenderer({antialias:true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth,window.innerHeight);
    renderer.physicallyCorrectLights = true;

    const container = document.querySelector('#canvas_vr');
    container.appendChild(renderer.domElement);

	document.body.appendChild(VRButton.createButton(renderer));

    window.addEventListener('resize',function(){
        camera.aspect = window.innerWidth/window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth,window.innerHeight);
    },false);

    checkDevice();
    setLoading();
}

function checkDevice(){
	if ('xr' in navigator) {
		navigator.xr.isSessionSupported('immersive-vr').then(function(supported){
			if(supported){
        		renderer.xr.enabled = true;
        		setVrControll();
			}
		});
	}
}

function setLoading(){
	TweenMax.to('.loader',0.1,{opacity:1});
	TweenMax.to('#loader_wrapper',1,{
        opacity:0,
        delay:1,
        onComplete: function(){
            document.getElementById('loader_wrapper').style.display ='none';
        }
    });
    threeWorld();
	setLight();
	rendering();
}

function threeWorld(){
		scene.background = new THREE.Color(0x505050);
		const gridHelper = new THREE.GridHelper(50,50);
		scene.add(gridHelper);
		renderer.outputEncoding = THREE.sRGBEncoding;
}

function setLight(){
	const directionalLight = new THREE.DirectionalLight(0XFFFFFF);
	directionalLight.position.set(0,4,0);
	scene.add(directionalLight);
}

function setVrControll(){
	const controller1 = renderer.xr.getController(0);
	controller1.name = 'RightController';
	controller1.addEventListener('selectstart',onSelectStart);
	controller1.addEventListener('selectend',onSelectEnd);
	controller1.addEventListener('connected',function(event){
		this.add(buildController(event.data));
	});
	scene.add(controller1);

	const controller2 = renderer.xr.getController(1);
	controller2.name = 'LeftController';
	controller2.addEventListener('selectstart',onSelectStart);
	controller2.addEventListener('selectend',onSelectEnd);
	controller2.addEventListener('connected',function(event){
		this.add(buildController(event.data));
	});
	scene.add(controller2);

	function onSelectStart(){
		console.log(this.name + 'の選択ボタンを押した');
	}
	function onSelectEnd(){
		console.log(this.name +'の選択ボタン離した');
	}

	const controllerModelFactory = new XRControllerModelFactory();

	const controllerGrip1 = renderer.xr.getControllerGrip(0);
	controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
	scene.add(controllerGrip1);

	const controllerGrip2 = renderer.xr.getControllerGrip(1);
	controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
	scene.add(controllerGrip2);

	function buildController(data){
		switch(data.targetRayMode){
			case 'tracked-pointer':
				const geometry = new THREE.BufferGeometry();
				geometry.setAttribute('position', new THREE.Float32BufferAttribute([0,0,0,0,0,-1],3));
				geometry.setAttribute('color', new THREE.Float32BufferAttribute([0.5,0.5,0.5,0,0,0],3));
				const material = new THREE.LineBasicMaterial({vertexColors:true,blending:THREE.AdditiveBlending});
				return new THREE.Line(geometry,material);

			case 'gaze':
				const gaze_geometry = new THREE.RingBufferGoemetry(0.02,0.04,32).translate(0,0,-1);
				const gaze_material = new THREE.MeshBesicMaterial({opacity:0.5,transparent:true});
				return new THREE.Mesh(gaze_geometry,gaze_material);
		}
	}
}

function rendering(){
    renderer.setAnimationLoop(animate);
}

function animate(){
	renderer.render(scene,camera);
}

完成したデモになります。WebXR API Emulatorかヘッドマウントディスプレイで確認すると、コントローラーが表示されます。XRControllerModelFactory.jsを使用すると、自動的にそれぞれのデバイスに合ったコントローラーが表示されるので便利です!

  • このエントリーをはてなブックマークに追加

関連記事

前の記事へ

WebXR API Emulatorについて