Browse Source

Add VR example

Garrett Johnson 4 years ago
parent
commit
54abfe7600
2 changed files with 320 additions and 0 deletions
  1. 29 0
      example/vr.html
  2. 291 0
      example/vr.js

+ 29 - 0
example/vr.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+        <meta charset="utf-8"/>
+
+        <title>3D Tiles Renderer WebVR Example</title>
+
+        <style>
+            * {
+                margin: 0;
+                padding: 0;
+            }
+
+            html {
+                overflow: hidden;
+                font-family: Arial, Helvetica, sans-serif;
+                user-select: none;
+            }
+
+            canvas {
+                outline: none;
+            }
+        </style>
+    </head>
+    <body>
+        <script src="./vr.js"></script>
+    </body>
+</html>

+ 291 - 0
example/vr.js

@@ -0,0 +1,291 @@
+import {
+	DebugTilesRenderer as TilesRenderer,
+	NONE,
+	SCREEN_ERROR,
+	GEOMETRIC_ERROR,
+	DISTANCE,
+	DEPTH,
+	RELATIVE_DEPTH,
+	IS_LEAF,
+	RANDOM_COLOR,
+} from '../src/index.js';
+import {
+	Scene,
+	DirectionalLight,
+	AmbientLight,
+	WebGLRenderer,
+	PerspectiveCamera,
+	Box3,
+	Raycaster,
+	Mesh,
+	MeshBasicMaterial,
+	Group,
+	TorusBufferGeometry,
+	sRGBEncoding,
+	GridHelper,
+	BufferGeometry,
+	Float32BufferAttribute,
+	LineBasicMaterial,
+	AdditiveBlending,
+	Line,
+	Vector3,
+} from 'three';
+import * as dat from 'three/examples/jsm/libs/dat.gui.module.js';
+import { FlyOrbitControls } from './FlyOrbitControls.js';
+import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';
+import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
+
+let camera,  scene, renderer, tiles;
+let workspace;
+let box, grid;
+let raycaster, fwdVector, intersectRing;
+let offsetParent;
+let controller, controllerGrip;
+
+let params = {
+
+	'displayBoxBounds': false,
+	'colorMode': 0,
+	'displayGrid': true,
+
+};
+
+init();
+animate();
+
+function init() {
+
+	scene = new Scene();
+
+	// primary camera view
+	renderer = new WebGLRenderer( { antialias: true } );
+	renderer.setPixelRatio( window.devicePixelRatio );
+	renderer.setSize( window.innerWidth, window.innerHeight );
+	renderer.setClearColor( 0x151c1f );
+	renderer.outputEncoding = sRGBEncoding;
+	renderer.xr.enabled = true;
+
+	document.body.appendChild( renderer.domElement );
+	renderer.domElement.tabIndex = 1;
+
+	// create workspace
+	workspace = new Group();
+	scene.add( workspace );
+
+	grid = new GridHelper( 10, 10, 0xffffff, 0xffffff );
+	grid.material.transparent = true;
+	grid.material.opacity = 0.5;
+	grid.material.depthWrite = false;
+	workspace.add( grid );
+
+	camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 4000 );
+	camera.position.set( 0, 1, 0 );
+	workspace.add( camera );
+
+	// lights
+	const dirLight = new DirectionalLight( 0xffffff );
+	dirLight.position.set( 1, 2, 3 );
+	scene.add( dirLight );
+
+	const ambLight = new AmbientLight( 0xffffff, 0.2 );
+	scene.add( ambLight );
+
+	// tile set
+	box = new Box3();
+
+	// parent for centering the tileset
+	offsetParent = new Group();
+	scene.add( offsetParent );
+
+	tiles = new TilesRenderer( '../data/tileset.json' );
+	offsetParent.add( tiles.group );
+
+	// Raycasting init
+	raycaster = new Raycaster();
+	fwdVector = new Vector3( 0, 0, 1 );
+
+	const rayIntersectMat = new MeshBasicMaterial( { color: 0xe91e63 } );
+	intersectRing = new Mesh( new TorusBufferGeometry( 1.5, 0.2, 16, 100 ), rayIntersectMat );
+	intersectRing.visible = false;
+	scene.add( intersectRing );
+
+	// vr setup
+	document.body.appendChild( VRButton.createButton( renderer ) );
+
+	controller = renderer.xr.getController( 0 );
+	controller.addEventListener( 'selectstart', () => {
+
+		workspace.position.copy( intersectRing.position );
+
+	} );
+	controller.addEventListener( 'connected', function ( event ) {
+
+		this.controllerActive = true;
+		this.add( buildController( event.data ) );
+
+	} );
+	controller.addEventListener( 'disconnected', function () {
+
+		this.controllerActive = false;
+		this.remove( this.children[ 0 ] );
+
+	} );
+	workspace.add( controller );
+
+	// controller models
+	const controllerModelFactory = new XRControllerModelFactory();
+	controllerGrip = renderer.xr.getControllerGrip( 0 );
+	controllerGrip.add( controllerModelFactory.createControllerModel( controllerGrip ) );
+	scene.add( controllerGrip );
+
+	onWindowResize();
+	window.addEventListener( 'resize', onWindowResize, false );
+
+	// GUI
+	const gui = new dat.GUI();
+	gui.width = 300;
+	gui.add( params, 'displayGrid' );
+	gui.add( params, 'displayBoxBounds' );
+	gui.add( params, 'colorMode', {
+
+		NONE,
+		SCREEN_ERROR,
+		GEOMETRIC_ERROR,
+		DISTANCE,
+		DEPTH,
+		RELATIVE_DEPTH,
+		IS_LEAF,
+		RANDOM_COLOR,
+
+	} );
+	gui.open();
+
+}
+
+function buildController( data ) {
+
+	let geometry, material;
+
+	switch ( data.targetRayMode ) {
+
+		case 'tracked-pointer':
+
+			geometry = new BufferGeometry();
+			geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, - 1 ], 3 ) );
+			geometry.setAttribute( 'color', new Float32BufferAttribute( [ 0.5, 0.5, 0.5, 0, 0, 0 ], 3 ) );
+
+			material = new LineBasicMaterial( {
+				vertexColors: true,
+				blending: AdditiveBlending,
+				depthWrite: false,
+				transparent: true,
+			} );
+
+			return new Line( geometry, material );
+
+		case 'gaze':
+
+			geometry = new RingBufferGeometry( 0.02, 0.04, 32 ).translate( 0, 0, - 1 );
+			material = new MeshBasicMaterial( { opacity: 0.5, transparent: true } );
+			return new Mesh( geometry, material );
+
+	}
+
+}
+
+function onWindowResize() {
+
+	camera.updateProjectionMatrix();
+	renderer.setPixelRatio( window.devicePixelRatio );
+	renderer.setSize( window.innerWidth, window.innerHeight );
+
+	camera.aspect = window.innerWidth / window.innerHeight;
+	camera.updateProjectionMatrix();
+
+}
+
+function animate() {
+
+	renderer.setAnimationLoop( render );
+
+}
+
+
+function render() {
+
+	requestAnimationFrame( animate );
+
+	grid.visible = params.displayGrid;
+
+	// update options
+	tiles.displayBoxBounds = params.displayBoxBounds;
+	tiles.colorMode = parseFloat( params.colorMode );
+
+	// update tiles center
+	if ( tiles.getBounds( box ) ) {
+
+		box.getCenter( tiles.group.position );
+		tiles.group.position.multiplyScalar( - 1 );
+
+	}
+
+	// remove all cameras so we can use the VR camera instead
+	tiles.cameras.forEach( c => tiles.deleteCamera( camera ) );
+
+	// get the XR camera with a combined frustum for culling
+	let currCamera = camera;
+	if ( renderer.xr.isPresenting ) {
+
+		currCamera = renderer.xr.getCamera( camera );
+
+	}
+
+	tiles.setCamera( currCamera );
+	tiles.setResolutionFromRenderer( currCamera, renderer );
+	tiles.update();
+
+
+	if ( controller.controllerActive ) {
+
+		const { ray } = raycaster;
+		raycaster.firstHitOnly = true;
+
+		// get the controller ray
+		ray.origin
+			.copy( controller.position )
+			.applyMatrix4( workspace.matrixWorld );
+		controller
+			.getWorldDirection( ray.direction )
+			.multiplyScalar( - 1 );
+
+		const results = raycaster.intersectObject( tiles.group, true );
+		if ( results.length ) {
+
+			const hit = results[ 0 ];
+			intersectRing.position.copy( hit.point );
+			intersectRing.quaternion.setFromUnitVectors(
+				fwdVector,
+				hit.face.normal,
+			);
+			intersectRing.visible = true;
+
+			// scale ring based on distance
+			const scale = workspace.position.distanceTo( intersectRing.position ) * camera.fov / 4000;
+			intersectRing.scale.setScalar( scale );
+
+		} else {
+
+			intersectRing.visible = false;
+
+		}
+
+	} else {
+
+		intersectRing.visible = false;
+
+	}
+
+	// render primary view
+	renderer.render( scene, camera );
+
+}