2018年11月13日

Three.jsでコントローラーを取得

WebVRのコンテンツでは、コントローラーが重要なユーザーインターフェースになります。そこで、Three.jsのサンプルを参考に、コントローラーの取得方法を調べつつ、ballshooterを試してみました。

Three.jsでコントローラーを取得

コントローラーは、「renderer.vr.getController(0)」で取得できます。両手用でコントローラーが2つある場合は、「renderer.vr.getController(1)」で2つ目のコントローラーを取得できます。

また、コントローラーからビームを出します。



● コントローラーの取得

//コントローラを取得
var controller = renderer.vr.getController(0);

//コントローライベントを登録
controller.addEventListener('selectstart',onSelectStart);
controller.addEventListener('selectend',onSelectEnd);

scene.add(controller);

//コントローラーから出るビームを生成
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position',new THREE.Float32BufferAttribute([0,0,0,0,0,-1],3));
geometry.addAttribute('color',new THREE.Float32BufferAttribute([1.0,1.0,1.0,0,0,0],3));

var material = new THREE.LineBasicMaterial({vertexColors:true,blending:THREE.AdditiveBlending});
controller.add(new THREE.Line(geometry,material));

controller.userData.isSeleting = false;

//コントローライベントを設定
function onSelectStart(){
	this.userData.isSeleting = true;
}
function onSelectEnd(){
	this.userData.isSeleting = false;
}

● コントローラーからボールを発射

ただビームを出すだけではおもしろくないので、Three.jsのサンプルを参考に、コントローラーからボールを発射します。Three.jsのオブジェクトには独自の変数を格納できる「userData」があり、そこに速度を保存しています。また、ボールの物理シミュレーションは、Y軸のみシミュレーションするようにしました。

//透明なボックスを生成
var geometry = new THREE.BoxGeometry(40,40,40,10,10,10);
var material = new THREE.MeshPhongMaterial({color:0xFFFFFF,transparent:true,opacity:0});
var room = new THREE.Mesh(geometry,material);
scene.add(room);

//ボールを生成して、ボックスに追加
var geometry = new THREE.IcosahedronBufferGeometry(radius,2);

for(var i = 0; i < 400; i++){
	var material = new THREE.MeshPhongMaterial({color:0xb6c4c6});
	var object = new THREE.Mesh(geometry,material);

	//ボールの初期位置を設定
	object.position.x = Math.random()*40-20;
	object.position.y = Math.random()*40;
	object.position.z = Math.random()*40-20;

	//速度を設定
	object.userData.velocity = new THREE.Vector3();
	object.userData.velocity.x = Math.random() * 0.01 - 0.005;
	object.userData.velocity.y = Math.random() * 0.01 - 0.005;
	object.userData.velocity.z = Math.random() * 0.01 - 0.005;

	//ボックスに追加
	room.add(object);
}

//ボールの物理シミュレーション
function render() {
	var delta = clock.getDelta() * 0.8;
	var range = 20 - radius;

	if(room){
		for(var i=0; i < room.children.length; i++){
			var object = room.children[i];

			//ボールをシミュレーション
			object.position.x += object.userData.velocity.x * delta;
			object.position.y += object.userData.velocity.y * delta;
			object.position.z += object.userData.velocity.z * delta;

			//Y軸の物理シミュレーション
			if(object.position.y < radius || object.position.y > 20*2){
				object.position.y = Math.max( object.position.y, radius );
				object.userData.velocity.x *= 0.98;
				object.userData.velocity.y = -object.userData.velocity.y * 0.8;
				object.userData.velocity.z *= 0.98;
			}

			//重力の設定
			object.userData.velocity.y -= 9.8 * delta;
		}
	}

	//取得したcontrollerを引数に設定し、handleCotrollerを実行
	handleCotroller(controller);

	renderer.setAnimationLoop(render);
	renderer.render(scene, camera);
}

var count = 0;

//コントローラーのトリガーを押したら、ボールを発射
function handleCotroller(controller){

	if(controller && controller.userData.isSeleting){
		var object = room.children[count++];
		object.position.copy(controller.position);
		object.userData.velocity.x = (Math.random()-0.5)*3;
		object.userData.velocity.y = (Math.random()-0.5)*3;
		object.userData.velocity.z = (Math.random()-9);
		object.userData.velocity.applyQuaternion(controller.quaternion);

		if(count === room.children.length){
			count = 0;
		}
	}
}

● script.js

まず、Three.jsで360°パノラマコンテンツ制作でやったように、WebVRやローディングに必要なライブラリを読み込みます。

<script src="js/lib/preloadjs.min.js"></script>
<script src="js/lib/TweenMax.min.js"></script>
<script src="js/lib/three_vr/three.min.js"></script>
<script src="js/lib/three_vr/webvr-polyfill.min.js"></script>
<script src="js/lib/three_vr/WebVR.js"></script>
<script src="js/lib/three_vr/OrbitControls.js"></script>
<script src="js/script.js"></script>

完成したscript.jsです。

(function () {
	window.addEventListener("load", function () {
	   startLoading();
	});

	var scene,camera,renderer;
	var controller;
	var room;
	var texture;
	var radius = 0.1;
	var count = 0;
	var clock = new THREE.Clock();

	//画像のローディング処理
	function startLoading(){
		TweenMax.to(".loader",0.1,{opacity:1});

		var manifest = [
			{id:'ground',src:'img/ground.png'}
		];
		var loadQueue = new createjs.LoadQueue();

		loadQueue.on('progress',function(e){
			var progress = e.progress;
		});

		loadQueue.on('complete',function(){
			var image = loadQueue.getResult('ground');
			texture = new THREE.Texture(image);
			texture.needsUpdate = true;

			TweenMax.to("#loader_wrapper" , 1 , {opacity:0});

			//ローディング処理が終わったら実行
			init();
			initObject();
			initLight();
			initController();
		});

		loadQueue.loadManifest(manifest);
	}

	//シーン、カメラ、レンダラー生成
	function init(){
		var polyfill = new WebVRPolyfill();

		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight,0.1,500);
		camera.position.set(0, 0, 0);
		scene.add(camera);

		renderer = new THREE.WebGLRenderer({antialias:true});
		renderer.setSize(window.innerWidth, window.innerHeight);
		renderer.render(scene,camera);

		var container = document.createElement('div');
		document.body.appendChild(container);
		container.appendChild(renderer.domElement);
		container.appendChild(WEBVR.createButton(renderer));

		render();
		checkDevice();

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

	//オブジェクト生成
	function initObject(){

		//床
		texture.repeat.set(50, 50);
		texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
		texture.magFilter = THREE.NearestFilter;
		var geometry = new THREE.PlaneGeometry(250,250);
		var material = new THREE.MeshStandardMaterial({map:texture,roughness:0.0,metalness:0.6});

		var floor = new THREE.Mesh(geometry,material);
		floor.rotation.x = -Math.PI / 2;
		scene.add(floor);

		//透明ボックス
		geometry = new THREE.BoxGeometry(40,40,40,10,10,10);
		material = new THREE.MeshPhongMaterial({color:0xFFFFFF,transparent:true,opacity:0});
		room = new THREE.Mesh(geometry,material);
		room.geometry.translate(0,20,0);
		scene.add(room);

		//ボール
		geometry = new THREE.IcosahedronBufferGeometry(radius,2);
		for(var i = 0; i < 400; i++){
			material = new THREE.MeshPhongMaterial({color:0xb6c4c6});
			var object = new THREE.Mesh(geometry,material);
			object.position.x = Math.random()*40-20;
			object.position.y = Math.random()*40;
			object.position.z = Math.random()*40-20;
			object.userData.velocity = new THREE.Vector3();
			object.userData.velocity.x = Math.random() * 0.01 - 0.005;
			object.userData.velocity.y = Math.random() * 0.01 - 0.005;
			object.userData.velocity.z = Math.random() * 0.01 - 0.005;
			room.add(object);
		}
	}

	//ライト生成
	function initLight(){
		var ambientLight = new THREE.AmbientLight(0xFFFFFF);
		scene.add(ambientLight);

		var directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1, 0);
		directionalLight.position.set(0, 100, 0);
		scene.add(directionalLight);
	}

	//コントローラ設定
	function initController(){
		controller = renderer.vr.getController(0);
		controller.addEventListener('selectstart',onSelectStart);
		controller.addEventListener('selectend',onSelectEnd);
		scene.add(controller);

		var geometry = new THREE.BufferGeometry();
		geometry.addAttribute('position',new THREE.Float32BufferAttribute([0,0,0,0,0,-1],3));
		geometry.addAttribute('color',new THREE.Float32BufferAttribute([1.0,1.0,1.0,0,0,0],3));

		var material = new THREE.LineBasicMaterial({vertexColors:true,blending:THREE.AdditiveBlending});
		controller.add(new THREE.Line(geometry,material));

		controller.userData.isSeleting = false;

		function onSelectStart(){
			this.userData.isSeleting = true;
		}
		function onSelectEnd(){
			this.userData.isSeleting = false;
		}
	}

	//コントローラー制御
	function handleCotroller(controller){
		if(controller && controller.userData.isSeleting){
			var object = room.children[count++];
			object.position.copy(controller.position);
			object.userData.velocity.x = (Math.random()-0.5)*3;
			object.userData.velocity.y = (Math.random()-0.5)*3;
			object.userData.velocity.z = (Math.random()-9);
			object.userData.velocity.applyQuaternion(controller.quaternion);

			if(count === room.children.length){
				count = 0;
			}
		}
	}

	//アニメーション
	function render() {
		var delta = clock.getDelta() * 0.8;
		var range = 20 - radius;

		if(room){
			for(var i=0; i < room.children.length; i++){
				var object = room.children[i];
				object.position.x += object.userData.velocity.x * delta;
				object.position.y += object.userData.velocity.y * delta;
				object.position.z += object.userData.velocity.z * delta;

				if(object.position.y < radius || object.position.y > 20*2){
					object.position.y = Math.max( object.position.y, radius );
					object.userData.velocity.x *= 0.98;
					object.userData.velocity.y = -object.userData.velocity.y * 0.8;
					object.userData.velocity.z *= 0.98;
				}

				object.userData.velocity.y -= 9.8 * delta;
			}
		}

		handleCotroller(controller);

	    renderer.setAnimationLoop(render);
	    renderer.render(scene, camera);
	}

	//デバイス判定
	function checkDevice(){
		var ua = window.navigator.userAgent.toLowerCase();
		var _iOS,_Android,_Tablet,_Pc,_vrDisplay;

		_iOS = /ipad|iphone|ipod/.test(ua);
		_Android = /android/.test(ua);
		_Tablet = /ipad|Nexus (7|9)|xoom|sch-i800|playbook|tablet|kindle/i.test(ua);

		if (navigator.getVRDisplays) {
			navigator.getVRDisplays().then(function (displays) {
		    	var vrDisplay = displays.length && displays[0];
		    	if(vrDisplay == 0){
		    		if(!_iOS && !_Android && !_Tablet){

						orbitControls = new THREE.OrbitControls(camera);
						orbitControls.target.set(
							camera.position.x + 0.01,
							camera.position.y,
							camera.position.z
							);
					}
				}else{
					renderer.vr.enabled = true;
				}
			});
		}
	}
})();

完成したscript.jsを調整したデモになります。VRヘッドセットでアクセスして、トリガーを押すとボールが発射されます!

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

Three.jsで360°パノラマコンテンツ制作

次の記事へ

Three.jsでオブジェクトを選択