Browse Source

WebXRManager: Added setReferenceSpace() (#20949)

* Added "controllermove" event to WebXRController

* Added offsetReferenceSpace to WebXRController and teleport example.

* Removed controllermove events.

* Renamed offsetReferenceSpace to customReferenceSpace in webXRManager

Co-authored-by: Aki Rodic <[email protected]>
Aki Rodić 3 years ago
parent
commit
591d359d4a

+ 1 - 0
examples/files.json

@@ -349,6 +349,7 @@
 		"webxr_vr_rollercoaster",
 		"webxr_vr_rollercoaster",
 		"webxr_vr_sandbox",
 		"webxr_vr_sandbox",
 		"webxr_vr_sculpt",
 		"webxr_vr_sculpt",
+		"webxr_vr_teleport",
 		"webxr_vr_video"
 		"webxr_vr_video"
 	],
 	],
 	"games": [
 	"games": [

BIN
examples/screenshots/webxr_vr_teleport.jpg


+ 278 - 0
examples/webxr_vr_teleport.html

@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js vr - teleport</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> vr - teleport
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { BoxLineGeometry } from './jsm/geometries/BoxLineGeometry.js';
+			import { VRButton } from './jsm/webxr/VRButton.js';
+			import { XRControllerModelFactory } from './jsm/webxr/XRControllerModelFactory.js';
+
+			let camera, scene, raycaster, renderer;
+			let controller1, controller2;
+			let controllerGrip1, controllerGrip2;
+
+			let room, marker, floor;
+
+			let INTERSECTION;
+			const tempMatrix = new THREE.Matrix4();
+
+			const clock = new THREE.Clock();
+
+			init();
+			animate();
+
+			function init() {
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x505050 );
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 10 );
+				camera.position.set( 0, 1, 3 );
+
+				room = new THREE.LineSegments(
+					new BoxLineGeometry( 6, 6, 6, 10, 10, 10 ).translate( 0, 3, 0 ),
+					new THREE.LineBasicMaterial( { color: 0x808080 } )
+				);
+				scene.add( room );
+
+				scene.add( new THREE.HemisphereLight( 0x606060, 0x404040 ) );
+
+				const light = new THREE.DirectionalLight( 0xffffff );
+				light.position.set( 1, 1, 1 ).normalize();
+				scene.add( light );
+
+				marker = new THREE.Mesh(
+					new THREE.CircleGeometry( 0.25, 32 ).rotateX( - Math.PI / 2 ),
+					new THREE.MeshBasicMaterial( { color: 0x808080 } )
+				);
+				scene.add( marker );
+
+				floor = new THREE.Mesh(
+					new THREE.PlaneGeometry( 4.8, 4.8, 2, 2 ).rotateX( - Math.PI / 2 ),
+					new THREE.MeshBasicMaterial( { color: 0x808080, transparent: true, opacity: 0.25 } )
+				);
+				scene.add( floor );
+
+				raycaster = new THREE.Raycaster();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				renderer.xr.enabled = true;
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				document.body.appendChild( VRButton.createButton( renderer ) );
+
+				// controllers
+
+				function onSelectStart() {
+
+					this.userData.isSelecting = true;
+
+				}
+
+				function onSelectEnd() {
+
+					this.userData.isSelecting = false;
+
+					if ( INTERSECTION ) {
+
+						const baseReferenceSpace = renderer.xr.getReferenceSpace();
+						const offsetPosition = { x: - INTERSECTION.x, y: - INTERSECTION.y, z: - INTERSECTION.z, w: 1 };
+						const offsetRotation = new THREE.Quaternion();
+						const transform = new XRRigidTransform( offsetPosition, offsetRotation );
+						const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace( transform );
+
+						renderer.xr.setReferenceSpace( teleportSpaceOffset );
+
+					}
+
+				}
+
+				controller1 = renderer.xr.getController( 0 );
+				controller1.addEventListener( 'selectstart', onSelectStart );
+				controller1.addEventListener( 'selectend', onSelectEnd );
+				controller1.addEventListener( 'connected', function ( event ) {
+
+					this.add( buildController( event.data ) );
+
+				} );
+				controller1.addEventListener( 'disconnected', function () {
+
+					this.remove( this.children[ 0 ] );
+
+				} );
+				scene.add( controller1 );
+
+				controller2 = renderer.xr.getController( 1 );
+				controller2.addEventListener( 'selectstart', onSelectStart );
+				controller2.addEventListener( 'selectend', onSelectEnd );
+				controller2.addEventListener( 'connected', function ( event ) {
+
+					this.add( buildController( event.data ) );
+
+				} );
+				controller2.addEventListener( 'disconnected', function () {
+
+					this.remove( this.children[ 0 ] );
+
+				} );
+				scene.add( controller2 );
+
+				// The XRControllerModelFactory will automatically fetch controller models
+				// that match what the user is holding as closely as possible. The models
+				// should be attached to the object returned from getControllerGrip in
+				// order to match the orientation of the held device.
+
+				const controllerModelFactory = new XRControllerModelFactory();
+
+				controllerGrip1 = renderer.xr.getControllerGrip( 0 );
+				controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
+				scene.add( controllerGrip1 );
+
+				controllerGrip2 = renderer.xr.getControllerGrip( 1 );
+				controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
+				scene.add( controllerGrip2 );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function buildController( data ) {
+
+				let geometry, material;
+
+				switch ( data.targetRayMode ) {
+
+					case 'tracked-pointer':
+
+						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 ) );
+
+						material = new THREE.LineBasicMaterial( { vertexColors: true, blending: THREE.AdditiveBlending } );
+
+						return new THREE.Line( geometry, material );
+
+					case 'gaze':
+
+						geometry = new THREE.RingGeometry( 0.02, 0.04, 32 ).translate( 0, 0, - 1 );
+						material = new THREE.MeshBasicMaterial( { opacity: 0.5, transparent: true } );
+						return new THREE.Mesh( geometry, material );
+
+				}
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function handleController( controller ) {
+
+				if ( controller.userData.isSelecting ) {
+
+					const 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 animate() {
+
+				renderer.setAnimationLoop( render );
+
+			}
+
+			function render() {
+
+				INTERSECTION = undefined;
+
+				if ( controller1.userData.isSelecting === true ) {
+
+					tempMatrix.identity().extractRotation( controller1.matrixWorld );
+
+					raycaster.ray.origin.setFromMatrixPosition( controller1.matrixWorld );
+					raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
+
+					const intersects = raycaster.intersectObjects( [ floor ] );
+
+					if ( intersects.length > 0 ) {
+
+						INTERSECTION = intersects[ 0 ].point;
+
+					}
+
+				} else if ( controller2.userData.isSelecting === true ) {
+
+					tempMatrix.identity().extractRotation( controller2.matrixWorld );
+
+					raycaster.ray.origin.setFromMatrixPosition( controller2.matrixWorld );
+					raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
+
+					const intersects = raycaster.intersectObjects( [ floor ] );
+
+					if ( intersects.length > 0 ) {
+
+						INTERSECTION = intersects[ 0 ].point;
+
+					}
+
+				}
+
+				if ( INTERSECTION ) marker.position.copy( INTERSECTION );
+
+				marker.visible = INTERSECTION !== undefined;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 10 - 3
src/renderers/webxr/WebXRManager.js

@@ -30,6 +30,7 @@ class WebXRManager extends EventDispatcher {
 
 
 		let referenceSpace = null;
 		let referenceSpace = null;
 		let referenceSpaceType = 'local-floor';
 		let referenceSpaceType = 'local-floor';
+		let customReferenceSpace = null;
 
 
 		let pose = null;
 		let pose = null;
 		let glBinding = null;
 		let glBinding = null;
@@ -187,7 +188,13 @@ class WebXRManager extends EventDispatcher {
 
 
 		this.getReferenceSpace = function () {
 		this.getReferenceSpace = function () {
 
 
-			return referenceSpace;
+			return customReferenceSpace || referenceSpace;
+
+		};
+
+		this.setReferenceSpace = function ( space ) {
+
+			customReferenceSpace = space;
 
 
 		};
 		};
 
 
@@ -560,7 +567,7 @@ class WebXRManager extends EventDispatcher {
 
 
 		function onAnimationFrame( time, frame ) {
 		function onAnimationFrame( time, frame ) {
 
 
-			pose = frame.getViewerPose( referenceSpace );
+			pose = frame.getViewerPose( customReferenceSpace || referenceSpace );
 			xrFrame = frame;
 			xrFrame = frame;
 
 
 			if ( pose !== null ) {
 			if ( pose !== null ) {
@@ -647,7 +654,7 @@ class WebXRManager extends EventDispatcher {
 
 
 				if ( controller !== undefined ) {
 				if ( controller !== undefined ) {
 
 
-					controller.update( inputSource, frame, referenceSpace );
+					controller.update( inputSource, frame, customReferenceSpace || referenceSpace );
 
 
 				}
 				}