You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
5.2 KiB
JavaScript
223 lines
5.2 KiB
JavaScript
import * as THREE from 'three';
|
|
|
|
import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
|
|
import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
|
|
|
|
import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
|
|
|
|
class XR {
|
|
|
|
constructor( editor, controls ) {
|
|
|
|
const selector = editor.selector;
|
|
const signals = editor.signals;
|
|
|
|
let controllers = null;
|
|
let group = null;
|
|
let renderer = null;
|
|
|
|
const camera = new THREE.PerspectiveCamera();
|
|
|
|
const onSessionStarted = async ( session ) => {
|
|
|
|
camera.copy( editor.camera );
|
|
|
|
const sidebar = document.getElementById( 'sidebar' );
|
|
sidebar.style.width = '350px';
|
|
sidebar.style.height = '700px';
|
|
|
|
//
|
|
|
|
if ( controllers === null ) {
|
|
|
|
const geometry = new THREE.BufferGeometry();
|
|
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 0, - 5 ], 3 ) );
|
|
|
|
const line = new THREE.Line( geometry );
|
|
|
|
const raycaster = new THREE.Raycaster();
|
|
|
|
function onSelect( event ) {
|
|
|
|
const controller = event.target;
|
|
|
|
controller1.userData.active = false;
|
|
controller2.userData.active = false;
|
|
|
|
if ( controller === controller1 ) {
|
|
|
|
controller1.userData.active = true;
|
|
controller1.add( line );
|
|
|
|
}
|
|
|
|
if ( controller === controller2 ) {
|
|
|
|
controller2.userData.active = true;
|
|
controller2.add( line );
|
|
|
|
}
|
|
|
|
raycaster.setFromXRController( controller );
|
|
|
|
const intersects = selector.getIntersects( raycaster );
|
|
|
|
if ( intersects.length > 0 ) {
|
|
|
|
// Ignore menu clicks
|
|
|
|
const intersect = intersects[ 0 ];
|
|
if ( intersect.object === group.children[ 0 ] ) return;
|
|
|
|
}
|
|
|
|
signals.intersectionsDetected.dispatch( intersects );
|
|
|
|
}
|
|
|
|
function onControllerEvent( event ) {
|
|
|
|
const controller = event.target;
|
|
|
|
if ( controller.userData.active === false ) return;
|
|
|
|
controls.getRaycaster().setFromXRController( controller );
|
|
|
|
switch ( event.type ) {
|
|
|
|
case 'selectstart':
|
|
controls.pointerDown( null );
|
|
break;
|
|
|
|
case 'selectend':
|
|
controls.pointerUp( null );
|
|
break;
|
|
|
|
case 'move':
|
|
controls.pointerHover( null );
|
|
controls.pointerMove( null );
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controllers = new THREE.Group();
|
|
|
|
const controller1 = renderer.xr.getController( 0 );
|
|
controller1.addEventListener( 'select', onSelect );
|
|
controller1.addEventListener( 'selectstart', onControllerEvent );
|
|
controller1.addEventListener( 'selectend', onControllerEvent );
|
|
controller1.addEventListener( 'move', onControllerEvent );
|
|
controller1.userData.active = false;
|
|
controllers.add( controller1 );
|
|
|
|
const controller2 = renderer.xr.getController( 1 );
|
|
controller2.addEventListener( 'select', onSelect );
|
|
controller2.addEventListener( 'selectstart', onControllerEvent );
|
|
controller2.addEventListener( 'selectend', onControllerEvent );
|
|
controller2.addEventListener( 'move', onControllerEvent );
|
|
controller2.userData.active = true;
|
|
controllers.add( controller2 );
|
|
|
|
//
|
|
|
|
const controllerModelFactory = new XRControllerModelFactory();
|
|
|
|
const controllerGrip1 = renderer.xr.getControllerGrip( 0 );
|
|
controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
|
|
controllers.add( controllerGrip1 );
|
|
|
|
const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
|
|
controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
|
|
controllers.add( controllerGrip2 );
|
|
|
|
// menu
|
|
|
|
group = new InteractiveGroup();
|
|
|
|
const mesh = new HTMLMesh( sidebar );
|
|
mesh.name = 'picker'; // Make Selector be aware of the menu
|
|
mesh.position.set( 0.5, 1.0, - 0.5 );
|
|
mesh.rotation.y = - 0.5;
|
|
group.add( mesh );
|
|
|
|
group.listenToXRControllerEvents( controller1 );
|
|
group.listenToXRControllerEvents( controller2 );
|
|
|
|
}
|
|
|
|
editor.sceneHelpers.add( group );
|
|
editor.sceneHelpers.add( controllers );
|
|
|
|
renderer.xr.enabled = true;
|
|
renderer.xr.addEventListener( 'sessionend', onSessionEnded );
|
|
|
|
await renderer.xr.setSession( session );
|
|
|
|
};
|
|
|
|
const onSessionEnded = async () => {
|
|
|
|
editor.sceneHelpers.remove( group );
|
|
editor.sceneHelpers.remove( controllers );
|
|
|
|
const sidebar = document.getElementById( 'sidebar' );
|
|
sidebar.style.width = '';
|
|
sidebar.style.height = '';
|
|
|
|
renderer.xr.removeEventListener( 'sessionend', onSessionEnded );
|
|
renderer.xr.enabled = false;
|
|
|
|
editor.camera.copy( camera );
|
|
|
|
signals.windowResize.dispatch();
|
|
signals.leaveXR.dispatch();
|
|
|
|
};
|
|
|
|
// signals
|
|
|
|
const sessionInit = { optionalFeatures: [ 'local-floor' ] };
|
|
|
|
signals.enterXR.add( ( mode ) => {
|
|
|
|
if ( 'xr' in navigator ) {
|
|
|
|
navigator.xr.requestSession( mode, sessionInit )
|
|
.then( onSessionStarted );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
signals.offerXR.add( function ( mode ) {
|
|
|
|
if ( 'xr' in navigator ) {
|
|
|
|
navigator.xr.offerSession( mode, sessionInit )
|
|
.then( onSessionStarted );
|
|
|
|
signals.leaveXR.add( function () {
|
|
|
|
navigator.xr.offerSession( mode, sessionInit )
|
|
.then( onSessionStarted );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
signals.rendererCreated.add( ( value ) => {
|
|
|
|
renderer = value;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export { XR };
|