Skip to content

Commit

Permalink
Editor: Improved XR mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
mrdoob committed Feb 22, 2024
1 parent 49d425c commit 44043fc
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 119 deletions.
5 changes: 3 additions & 2 deletions editor/js/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 );

Expand All @@ -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 = {};
Expand Down
35 changes: 35 additions & 0 deletions editor/js/Viewport.Selector.js → editor/js/Selector.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import * as THREE from 'three';

const mouse = new THREE.Vector2();
const raycaster = new THREE.Raycaster();

class Selector {

constructor( editor ) {
Expand Down Expand Up @@ -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;
Expand Down
151 changes: 89 additions & 62 deletions editor/js/Viewport.XR.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
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';

import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';

class XR {

constructor( editor ) {
constructor( editor, controls ) {

const selector = editor.selector;
const signals = editor.signals;

let controllers = null;
let group = null;
let renderer = null;

Expand All @@ -29,109 +28,127 @@ 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 );

//

const controllerModelFactory = new XRControllerModelFactory();

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 );
Expand All @@ -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 = '';
Expand All @@ -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 );

} );

}

} );

Expand Down
35 changes: 5 additions & 30 deletions editor/js/Viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -55,7 +56,6 @@ function Viewport( editor ) {
grid.add( grid2 );

const viewHelper = new ViewHelper( camera, container );
const xr = new XR( editor );

//

Expand Down Expand Up @@ -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 );

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable xr.

// events

Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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 ) {

Expand Down
Loading

0 comments on commit 44043fc

Please sign in to comment.