Quellcode durchsuchen

Editor: Improved XR mode. (#27802)

mrdoob vor 1 Jahr
Ursprung
Commit
3f1253a2b6

+ 3 - 2
editor/js/Editor.js

@@ -5,7 +5,7 @@ import { Loader } from './Loader.js';
 import { History as _History } from './History.js';
 import { Strings } from './Strings.js';
 import { Storage as _Storage } from './Storage.js';
-import { Selector } from './Viewport.Selector.js';
+import { Selector } from './Selector.js';
 
 var _DEFAULT_CAMERA = new THREE.PerspectiveCamera( 50, 1, 0.01, 1000 );
 _DEFAULT_CAMERA.name = 'Camera';
@@ -97,9 +97,9 @@ function Editor() {
 
 	this.config = new Config();
 	this.history = new _History( this );
+	this.selector = new Selector( this );
 	this.storage = new _Storage();
 	this.strings = new Strings( this.config );
-	this.selector = new Selector( this );
 
 	this.loader = new Loader( this );
 
@@ -109,6 +109,7 @@ function Editor() {
 	this.scene.name = 'Scene';
 
 	this.sceneHelpers = new THREE.Scene();
+	this.sceneHelpers.add( new THREE.HemisphereLight( 0xffffff, 0x888888, 2 ) );
 
 	this.object = {};
 	this.geometries = {};

+ 35 - 0
editor/js/Viewport.Selector.js → editor/js/Selector.js

@@ -1,3 +1,8 @@
+import * as THREE from 'three';
+
+const mouse = new THREE.Vector2();
+const raycaster = new THREE.Raycaster();
+
 class Selector {
 
 	constructor( editor ) {
@@ -37,6 +42,36 @@ class Selector {
 
 	}
 
+	getIntersects( raycaster ) {
+
+		const objects = [];
+
+		this.editor.scene.traverseVisible( function ( child ) {
+
+			objects.push( child );
+
+		} );
+
+		this.editor.sceneHelpers.traverseVisible( function ( child ) {
+
+			if ( child.name === 'picker' ) objects.push( child );
+
+		} );
+
+		return raycaster.intersectObjects( objects, false );
+
+	}
+
+	getPointerIntersects( point, camera ) {
+
+		mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );
+
+		raycaster.setFromCamera( mouse, camera );
+
+		return this.getIntersects( raycaster );
+
+	}
+
 	select( object ) {
 
 		if ( this.editor.selected === object ) return;

+ 89 - 62
editor/js/Viewport.XR.js

@@ -1,8 +1,5 @@
 import * as THREE from 'three';
 
-import { SetPositionCommand } from './commands/SetPositionCommand.js';
-import { SetRotationCommand } from './commands/SetRotationCommand.js';
-
 import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
 import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
 
@@ -10,10 +7,12 @@ import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFa
 
 class XR {
 
-	constructor( editor ) {
+	constructor( editor, controls ) {
 
+		const selector = editor.selector;
 		const signals = editor.signals;
 
+		let controllers = null;
 		let group = null;
 		let renderer = null;
 
@@ -29,93 +28,97 @@ class XR {
 
 			//
 
-			if ( group === null ) {
-
-				group = new InteractiveGroup();
-				group.listenToXRControllerEvents( renderer );
+			if ( controllers === null ) {
 
-				const mesh = new HTMLMesh( sidebar );
-				mesh.position.set( 1, 1.5, - 0.5 );
-				mesh.rotation.y = - 0.5;
-				mesh.scale.setScalar( 2 );
-				group.add( mesh );
+				const geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 0, - 5 ], 3 ) );
 
-				// controllers
+				const line = new THREE.Line( geometry );
 
 				const raycaster = new THREE.Raycaster();
-				const tempMatrix = new THREE.Matrix4();
 
-				function getIntersections( controller ) {
+				function onSelect( event ) {
 
-					tempMatrix.identity().extractRotation( controller.matrixWorld );
+					const controller = event.target;
 
-					raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
-					raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
+					controller1.userData.active = false;
+					controller2.userData.active = false;
 
-					return raycaster.intersectObjects( editor.scene.children, false );
+					if ( controller === controller1 ) {
 
-				}
+						controller1.userData.active = true;
+						controller1.add( line );
 
-				function onSelectStart( event ) {
+					}
 
-					const controller = event.target;
-					const intersections = getIntersections( controller );
+					if ( controller === controller2 ) {
+
+						controller2.userData.active = true;
+						controller2.add( line );
+
+					}
 
-					if ( intersections.length > 0 ) {
+					raycaster.setFromXRController( controller );
 
-						const intersection = intersections[ 0 ];
-						const object = intersection.object;
+					const intersects = selector.getIntersects( raycaster );
 
-						signals.objectSelected.dispatch( object );
+					if ( intersects.length > 0 ) {
 
-						controller.userData.selected = object;
-						controller.userData.position = object.position.clone();
-						controller.userData.rotation = object.rotation.clone();
+						// Ignore menu clicks
 
-						controller.attach( object );
+						const intersect = intersects[ 0 ];
+						if ( intersect.object === group.children[ 0 ] ) return;
 
 					}
 
+					signals.intersectionsDetected.dispatch( intersects );
+
 				}
 
-				function onSelectEnd( event ) {
+				function onControllerEvent( event ) {
 
 					const controller = event.target;
 
-					if ( controller.userData.selected !== undefined ) {
-
-						const object = controller.userData.selected;
-						editor.scene.attach( object );
+					if ( controller.userData.active === false ) return;
 
-						controller.userData.selected = undefined;
+					controls.getRaycaster().setFromXRController( controller );
 
-						editor.execute( new SetPositionCommand( editor, object, object.position, controller.userData.position ) );
-						editor.execute( new SetRotationCommand( editor, object, object.rotation, controller.userData.rotation ) );
+					switch ( event.type ) {
 
-						signals.objectChanged.dispatch( object );
+						case 'selectstart':
+							controls.pointerDown( null );
+							break;
 
-					} else {
+						case 'selectend':
+							controls.pointerUp( null );
+							break;
 
-						signals.objectSelected.dispatch( null );
+						case 'move':
+							controls.pointerHover( null );
+							controls.pointerMove( null );
+							break;
 
 					}
 
 				}
 
-				const geometry = new THREE.BufferGeometry();
-				geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
+				controllers = new THREE.Group();
 
 				const controller1 = renderer.xr.getController( 0 );
-				controller1.addEventListener( 'selectstart', onSelectStart );
-				controller1.addEventListener( 'selectend', onSelectEnd );
-				controller1.add( new THREE.Line( geometry ) );
-				group.add( controller1 );
+				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( 'selectstart', onSelectStart );
-				controller2.addEventListener( 'selectend', onSelectEnd );
-				controller2.add( new THREE.Line( geometry ) );
-				group.add( controller2 );
+				controller2.addEventListener( 'select', onSelect );
+				controller2.addEventListener( 'selectstart', onControllerEvent );
+				controller2.addEventListener( 'selectend', onControllerEvent );
+				controller2.addEventListener( 'move', onControllerEvent );
+				controller2.userData.active = true;
+				controllers.add( controller2 );
 
 				//
 
@@ -123,15 +126,29 @@ class XR {
 
 				const controllerGrip1 = renderer.xr.getControllerGrip( 0 );
 				controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
-				group.add( controllerGrip1 );
+				controllers.add( controllerGrip1 );
 
 				const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
 				controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
-				group.add( 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 );
@@ -143,6 +160,7 @@ class XR {
 		const onSessionEnded = async () => {
 
 			editor.sceneHelpers.remove( group );
+			editor.sceneHelpers.remove( controllers );
 
 			const sidebar = document.getElementById( 'sidebar' );
 			sidebar.style.width = '';
@@ -164,21 +182,30 @@ class XR {
 
 		signals.enterXR.add( ( mode ) => {
 
-			navigator.xr.requestSession( mode, sessionInit ).then( onSessionStarted );
+			if ( 'xr' in navigator ) {
+
+				navigator.xr.requestSession( mode, sessionInit )
+					.then( onSessionStarted );
+
+			}
 
 		} );
 
 		signals.offerXR.add( function ( mode ) {
 
-			navigator.xr.offerSession( mode, sessionInit )
-				.then( onSessionStarted );
-
-			signals.leaveXR.add( function () {
+			if ( 'xr' in navigator ) {
 
 				navigator.xr.offerSession( mode, sessionInit )
 					.then( onSessionStarted );
-	
-			} );
+
+				signals.leaveXR.add( function () {
+
+					navigator.xr.offerSession( mode, sessionInit )
+						.then( onSessionStarted );
+
+				} );
+
+			}
 
 		} );
 

+ 5 - 30
editor/js/Viewport.js

@@ -21,6 +21,7 @@ import { ViewportPathtracer } from './Viewport.Pathtracer.js';
 
 function Viewport( editor ) {
 
+	const selector = editor.selector;
 	const signals = editor.signals;
 
 	const container = new UIPanel();
@@ -55,7 +56,6 @@ function Viewport( editor ) {
 	grid.add( grid2 );
 
 	const viewHelper = new ViewHelper( camera, container );
-	const xr = new XR( editor );
 
 	//
 
@@ -141,10 +141,9 @@ function Viewport( editor ) {
 
 	sceneHelpers.add( transformControls );
 
-	// object picking
+	//
 
-	const raycaster = new THREE.Raycaster();
-	const mouse = new THREE.Vector2();
+	const xr = new XR( editor, transformControls );
 
 	// events
 
@@ -155,30 +154,6 @@ function Viewport( editor ) {
 
 	}
 
-	function getIntersects( point ) {
-
-		mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );
-
-		raycaster.setFromCamera( mouse, camera );
-
-		const objects = [];
-
-		scene.traverseVisible( function ( child ) {
-
-			objects.push( child );
-
-		} );
-
-		sceneHelpers.traverseVisible( function ( child ) {
-
-			if ( child.name === 'picker' ) objects.push( child );
-
-		} );
-
-		return raycaster.intersectObjects( objects, false );
-
-	}
-
 	const onDownPosition = new THREE.Vector2();
 	const onUpPosition = new THREE.Vector2();
 	const onDoubleClickPosition = new THREE.Vector2();
@@ -194,7 +169,7 @@ function Viewport( editor ) {
 
 		if ( onDownPosition.distanceTo( onUpPosition ) === 0 ) {
 
-			const intersects = getIntersects( onUpPosition );
+			const intersects = selector.getPointerIntersects( onUpPosition, camera );
 			signals.intersectionsDetected.dispatch( intersects );
 
 			render();
@@ -256,7 +231,7 @@ function Viewport( editor ) {
 		const array = getMousePosition( container.dom, event.clientX, event.clientY );
 		onDoubleClickPosition.fromArray( array );
 
-		const intersects = getIntersects( onDoubleClickPosition );
+		const intersects = selector.getPointerIntersects( onDoubleClickPosition, camera );
 
 		if ( intersects.length > 0 ) {
 

+ 1 - 1
editor/sw.js

@@ -142,6 +142,7 @@ const assets = [
 	'./js/Menubar.View.js',
 	'./js/Menubar.Status.js',
 	'./js/Resizer.js',
+	'./js/Selector.js',
 	'./js/Sidebar.js',
 	'./js/Sidebar.Scene.js',
 	'./js/Sidebar.Project.js',
@@ -188,7 +189,6 @@ const assets = [
 	'./js/Viewport.js',
 	'./js/Viewport.Controls.js',
 	'./js/Viewport.Info.js',
-	'./js/Viewport.Selector.js',
 	'./js/Viewport.ViewHelper.js',
 	'./js/Viewport.XR.js',
 

+ 6 - 6
examples/jsm/controls/TransformControls.js

@@ -223,7 +223,7 @@ class TransformControls extends Object3D {
 
 		if ( this.object === undefined || this.dragging === true ) return;
 
-		_raycaster.setFromCamera( pointer, this.camera );
+		if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
 
 		const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster );
 
@@ -241,11 +241,11 @@ class TransformControls extends Object3D {
 
 	pointerDown( pointer ) {
 
-		if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return;
+		if ( this.object === undefined || this.dragging === true || ( pointer != null && pointer.button !== 0 ) ) return;
 
 		if ( this.axis !== null ) {
 
-			_raycaster.setFromCamera( pointer, this.camera );
+			if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
 
 			const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
 
@@ -289,9 +289,9 @@ class TransformControls extends Object3D {
 
 		}
 
-		if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return;
+		if ( object === undefined || axis === null || this.dragging === false || ( pointer !== null && pointer.button !== - 1 ) ) return;
 
-		_raycaster.setFromCamera( pointer, this.camera );
+		if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
 
 		const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
 
@@ -539,7 +539,7 @@ class TransformControls extends Object3D {
 
 	pointerUp( pointer ) {
 
-		if ( pointer.button !== 0 ) return;
+		if ( pointer !== null && pointer.button !== 0 ) return;
 
 		if ( this.dragging && ( this.axis !== null ) ) {
 

+ 9 - 16
examples/jsm/interactive/InteractiveGroup.js

@@ -7,6 +7,8 @@ import {
 const _pointer = new Vector2();
 const _event = { type: '', data: _pointer };
 
+const _raycaster = new Raycaster();
+
 class InteractiveGroup extends Group {
 
 	listenToPointerEvents( renderer, camera ) {
@@ -55,12 +57,10 @@ class InteractiveGroup extends Group {
 
 	}
 
-	listenToXRControllerEvents( renderer ) {
+	listenToXRControllerEvents( controller ) {
 
 		const scope = this;
 
-		const raycaster = new Raycaster();
-
 		// TODO: Dispatch pointerevents too
 
 		const events = {
@@ -74,9 +74,9 @@ class InteractiveGroup extends Group {
 
 			const controller = event.target;
 
-			raycaster.setFromXRController( controller );
+			_raycaster.setFromXRController( controller );
 
-			const intersections = raycaster.intersectObjects( scope.children, false );
+			const intersections = _raycaster.intersectObjects( scope.children, false );
 
 			if ( intersections.length > 0 ) {
 
@@ -94,17 +94,10 @@ class InteractiveGroup extends Group {
 
 		}
 
-		const controller1 = renderer.xr.getController( 0 );
-		controller1.addEventListener( 'move', onXRControllerEvent );
-		controller1.addEventListener( 'select', onXRControllerEvent );
-		controller1.addEventListener( 'selectstart', onXRControllerEvent );
-		controller1.addEventListener( 'selectend', onXRControllerEvent );
-
-		const controller2 = renderer.xr.getController( 1 );
-		controller2.addEventListener( 'move', onXRControllerEvent );
-		controller2.addEventListener( 'select', onXRControllerEvent );
-		controller2.addEventListener( 'selectstart', onXRControllerEvent );
-		controller2.addEventListener( 'selectend', onXRControllerEvent );
+		controller.addEventListener( 'move', onXRControllerEvent );
+		controller.addEventListener( 'select', onXRControllerEvent );
+		controller.addEventListener( 'selectstart', onXRControllerEvent );
+		controller.addEventListener( 'selectend', onXRControllerEvent );
 
 	}
 

+ 2 - 1
examples/webxr_vr_layers.html

@@ -248,7 +248,8 @@
 				}
 
 				const group = new InteractiveGroup();
-				group.listenToXRControllerEvents( renderer );
+				group.listenToXRControllerEvents( controllers[ 0 ] );
+				group.listenToXRControllerEvents( controllers[ 1 ] );
 				scene.add( group );
 
 				guiMesh = new HTMLMesh( gui.domElement );

+ 2 - 1
examples/webxr_vr_sandbox.html

@@ -185,7 +185,8 @@
 
 				const group = new InteractiveGroup();
 				group.listenToPointerEvents( renderer, camera );
-				group.listenToXRControllerEvents( renderer );
+				group.listenToXRControllerEvents( controller1 );
+				group.listenToXRControllerEvents( controller2 );
 				scene.add( group );
 
 				const mesh = new HTMLMesh( gui.domElement );