|
@@ -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 );
|
|
|
+
|
|
|
+}
|