Selaa lähdekoodia

Bring dat.gui inside vr (#21700)

* Bring dat.gui inside vr

* Example code clean up.

* Refactored interaction code.

* InteractiveGroup: Added pointer events support

* Improved webxr_vr_sandbox scene
Mr.doob 4 vuotta sitten
vanhempi
commit
e9f910c8b1

+ 9 - 45
editor/js/Viewport.VR.js

@@ -1,6 +1,7 @@
 import * as THREE from '../../build/three.module.js';
 
-import { HTMLMesh } from './libs/three.html.js';
+import { HTMLMesh } from '../../examples/jsm/interactive/HTMLMesh.js';
+import { InteractiveGroup } from '../../examples/jsm/interactive/InteractiveGroup.js';
 
 import { XRControllerModelFactory } from '../../examples/jsm/webxr/XRControllerModelFactory.js';
 
@@ -29,31 +30,29 @@ class VR {
 
 			if ( group === null ) {
 
-				group = new THREE.Group();
+				group = new InteractiveGroup( renderer );
 				editor.sceneHelpers.add( group );
 
 				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 );
 
 				intersectables.push( mesh );
 
 				// controllers
 
-				const controller1 = renderer.xr.getController( 0 );
-				controller1.addEventListener( 'select', onSelect );
-				group.add( controller1 );
-
-				const controller2 = renderer.xr.getController( 1 );
-				controller2.addEventListener( 'selectstart', onSelect );
-				group.add( controller2 );
-
 				const geometry = new THREE.BufferGeometry();
 				geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
 
+				const controller1 = renderer.xr.getController( 0 );
 				controller1.add( new THREE.Line( geometry ) );
+				group.add( controller1 );
+
+				const controller2 = renderer.xr.getController( 1 );
 				controller2.add( new THREE.Line( geometry ) );
+				group.add( controller2 );
 
 				//
 
@@ -101,41 +100,6 @@ class VR {
 
 		};
 
-		//
-
-		function onSelect( event ) {
-
-			const controller = event.target;
-
-			const intersections = getIntersections( controller );
-
-			if ( intersections.length > 0 ) {
-
-				const intersection = intersections[ 0 ];
-
-				const object = intersection.object;
-				const uv = intersection.uv;
-
-				object.material.map.click( uv.x, 1 - uv.y );
-
-			}
-
-		}
-
-		const raycaster = new THREE.Raycaster();
-		const tempMatrix = new THREE.Matrix4();
-
-		function getIntersections( controller ) {
-
-			tempMatrix.identity().extractRotation( controller.matrixWorld );
-
-			raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
-			raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
-
-			return raycaster.intersectObjects( intersectables );
-
-		}
-
 		// signals
 
 		signals.toggleVR.add( () => {

+ 3 - 1
editor/sw.js

@@ -57,6 +57,9 @@ const assets = [
 	'../examples/jsm/curves/NURBSCurve.js',
 	'../examples/jsm/curves/NURBSUtils.js',
 
+	'../examples/jsm/interactive/HTMLMesh.js',
+	'../examples/jsm/interactive/InteractiveGroup.js',
+
 	'../examples/jsm/environments/RoomEnvironment.js',
 
 	'../examples/jsm/exporters/ColladaExporter.js',
@@ -108,7 +111,6 @@ const assets = [
 	'./js/libs/tern-threejs/threejs.js',
 
 	'./js/libs/signals.min.js',
-	'./js/libs/three.html.js',
 	'./js/libs/ui.js',
 	'./js/libs/ui.three.js',
 

+ 37 - 18
editor/js/libs/three.html.js → examples/jsm/interactive/HTMLMesh.js

@@ -1,21 +1,39 @@
-import * as THREE from '../../../build/three.module.js';
+import {
+	CanvasTexture,
+	LinearFilter,
+	Mesh,
+	MeshBasicMaterial,
+	PlaneGeometry,
+	sRGBEncoding
+} from '../../../build/three.module.js';
 
-class HTMLMesh extends THREE.Mesh {
+class HTMLMesh extends Mesh {
 
 	constructor( dom ) {
 
 		const texture = new HTMLTexture( dom );
 
-		const geometry = new THREE.PlaneGeometry( texture.image.width * 0.002, texture.image.height * 0.002 );
-		const material = new THREE.MeshBasicMaterial( { map: texture, toneMapped: false } );
+		const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 );
+		const material = new MeshBasicMaterial( { map: texture, toneMapped: false } );
 
 		super( geometry, material );
 
+		function onEvent( event ) {
+
+			material.map.dispatchEvent( event );
+
+		}
+
+		this.addEventListener( 'mousedown', onEvent );
+		this.addEventListener( 'mousemove', onEvent );
+		this.addEventListener( 'mouseup', onEvent );
+		this.addEventListener( 'click', onEvent );
+
 	}
 
 }
 
-class HTMLTexture extends THREE.CanvasTexture {
+class HTMLTexture extends CanvasTexture {
 
 	constructor( dom ) {
 
@@ -24,15 +42,15 @@ class HTMLTexture extends THREE.CanvasTexture {
 		this.dom = dom;
 
 		this.anisotropy = 16;
-		this.encoding = THREE.sRGBEncoding;
-		this.minFilter = THREE.LinearFilter;
-		this.magFilter = THREE.LinearFilter;
+		this.encoding = sRGBEncoding;
+		this.minFilter = LinearFilter;
+		this.magFilter = LinearFilter;
 
 	}
 
-	click( x, y ) {
+	dispatchEvent( event ) {
 
-		htmlclick( this.dom, x, y );
+		htmlevent( this.dom, event.type, event.data.x, event.data.y );
 
 		this.update();
 
@@ -93,12 +111,14 @@ function html2canvas( element ) {
 		}
 
 		return {
+
 			add: function ( clip ) {
 
 				clips.push( clip );
 				doClip();
 
 			},
+
 			remove: function () {
 
 				clips.pop();
@@ -235,26 +255,25 @@ function html2canvas( element ) {
 
 	var clipper = new Clipper( context );
 
-	console.time( 'drawElement' );
+	// console.time( 'drawElement' );
 
 	drawElement( element );
 
-	console.timeEnd( 'drawElement' );
+	// console.timeEnd( 'drawElement' );
 
 	return canvas;
 
 }
 
-function htmlclick( element, x, y ) {
+function htmlevent( element, event, x, y ) {
 
-	/*
 	const mouseEventInit = {
 		clientX: ( x * element.offsetWidth ) + element.offsetLeft,
 		clientY: ( y * element.offsetHeight ) + element.offsetTop,
 		view: element.ownerDocument.defaultView
 	};
-	element.dispatchEvent( new MouseEvent( 'click', mouseEventInit ) );
-	*/
+
+	window.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
 
 	const rect = element.getBoundingClientRect();
 
@@ -269,7 +288,7 @@ function htmlclick( element, x, y ) {
 
 			if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) {
 
-				element.click();
+				element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
 
 			}
 
@@ -287,4 +306,4 @@ function htmlclick( element, x, y ) {
 
 }
 
-export { HTMLMesh, HTMLTexture };
+export { HTMLMesh };

+ 114 - 0
examples/jsm/interactive/InteractiveGroup.js

@@ -0,0 +1,114 @@
+import {
+	Group,
+	Matrix4,
+	Raycaster,
+	Vector2
+} from '../../../build/three.module.js';
+
+const _pointer = new Vector2();
+const _event = { type: '', data: _pointer };
+
+class InteractiveGroup extends Group {
+
+	constructor( renderer, camera ) {
+
+		super();
+
+		const scope = this;
+
+		const raycaster = new Raycaster();
+		const tempMatrix = new Matrix4();
+
+		// Pointer Events
+
+		const element = renderer.domElement;
+
+		function onPointerEvent( event ) {
+
+			event.stopPropagation();
+
+			_pointer.x = ( event.clientX / element.clientWidth ) * 2 - 1;
+			_pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1;
+
+			raycaster.setFromCamera( _pointer, camera );
+
+			const intersects = raycaster.intersectObjects( scope.children );
+
+			if ( intersects.length > 0 ) {
+
+				const intersection = intersects[ 0 ];
+
+				const object = intersection.object;
+				const uv = intersection.uv;
+
+				_event.type = event.type;
+				_event.data.set( uv.x, 1 - uv.y );
+
+				object.dispatchEvent( _event );
+
+			}
+
+		}
+
+		element.addEventListener( 'pointerdown', onPointerEvent );
+		element.addEventListener( 'pointerup', onPointerEvent );
+		element.addEventListener( 'pointermove', onPointerEvent );
+		element.addEventListener( 'mousedown', onPointerEvent );
+		element.addEventListener( 'mouseup', onPointerEvent );
+		element.addEventListener( 'mousemove', onPointerEvent );
+		element.addEventListener( 'click', onPointerEvent );
+
+		// WebXR Controller Events
+		// TODO: Dispatch pointerevents too
+
+		const events = {
+			'move': 'mousemove',
+			'select': 'click',
+			'selectstart': 'mousedown',
+			'selectend': 'mouseup'
+		};
+
+		function onXRControllerEvent( event ) {
+
+			const controller = event.target;
+
+			tempMatrix.identity().extractRotation( controller.matrixWorld );
+
+			raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
+			raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
+
+			const intersections = raycaster.intersectObjects( scope.children );
+
+			if ( intersections.length > 0 ) {
+
+				const intersection = intersections[ 0 ];
+
+				const object = intersection.object;
+				const uv = intersection.uv;
+
+				_event.type = events[ event.type ];
+				_event.data.set( uv.x, 1 - uv.y );
+
+				object.dispatchEvent( _event );
+
+			}
+
+		}
+
+		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 );
+
+	}
+
+}
+
+export { InteractiveGroup };

BIN
examples/screenshots/webxr_vr_sandbox.jpg


+ 85 - 17
examples/webxr_vr_sandbox.html

@@ -15,10 +15,24 @@
 			import { Reflector } from './jsm/objects/Reflector.js';
 			import { VRButton } from './jsm/webxr/VRButton.js';
 
-			let camera, scene, renderer;
+			import { HTMLMesh } from './jsm/interactive/HTMLMesh.js';
+			import { InteractiveGroup } from './jsm/interactive/InteractiveGroup.js';
+			import { XRControllerModelFactory } from './jsm/webxr/XRControllerModelFactory.js';
+
+			import { GUI } from './jsm/libs/dat.gui.module.js';
 
+			let camera, scene, renderer;
 			let reflector;
 
+			const parameters = {
+				radius: 0.5,
+				tube: 0.2,
+				tubularSegments: 150,
+				radialSegments: 20,
+				p: 2,
+				q: 3
+			};
+
 			init();
 			animate();
 
@@ -32,25 +46,27 @@
 				scene.background = background;
 
 				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 10 );
-				camera.position.set( 0, 1.6, 2 );
+				camera.position.set( 0, 1.6, 1.5 );
+
+				//
 
-				const torusGeometry = new THREE.TorusKnotGeometry( 0.4, 0.15, 150, 20 );
+				const torusGeometry = new THREE.TorusKnotGeometry( ...Object.values( parameters ) );
 				const torusMaterial = new THREE.MeshStandardMaterial( { roughness: 0.01, metalness: 0.2, envMap: background } );
 				const torus = new THREE.Mesh( torusGeometry, torusMaterial );
-				torus.position.y = 0.75;
+				torus.name = 'torus';
+				torus.position.y = 1.25;
 				torus.position.z = - 2;
 				torus.castShadow = true;
 				torus.receiveShadow = true;
 				scene.add( torus );
 
-				const boxGeometry = new THREE.BoxGeometry( 1.5, 0.1, 1.5 );
-				const boxMaterial = new THREE.MeshPhongMaterial();
-				const box = new THREE.Mesh( boxGeometry, boxMaterial );
-				box.position.y = - 0.2;
-				box.position.z = - 2;
-				box.castShadow = true;
-				box.receiveShadow = true;
-				scene.add( box );
+				const cylinderGeometry = new THREE.CylinderGeometry( 1, 1, 0.1, 50 );
+				const cylinderMaterial = new THREE.MeshPhongMaterial();
+				const cylinder = new THREE.Mesh( cylinderGeometry, cylinderMaterial );
+				cylinder.position.z = - 2;
+				cylinder.castShadow = true;
+				cylinder.receiveShadow = true;
+				scene.add( cylinder );
 
 				const light1 = new THREE.DirectionalLight( 0x8800ff );
 				light1.position.set( - 1, 1.5, - 1.5 );
@@ -90,17 +106,17 @@
 
 				//
 
-				reflector = new Reflector( new THREE.PlaneGeometry( 1.4, 1.4 ), {
+				reflector = new Reflector( new THREE.PlaneGeometry( 2, 2 ), {
 					textureWidth: window.innerWidth * window.devicePixelRatio,
 					textureHeight: window.innerHeight * window.devicePixelRatio
 				} );
 				reflector.position.x = 1;
-				reflector.position.y = 0.5;
+				reflector.position.y = 1.25;
 				reflector.position.z = - 3;
 				reflector.rotation.y = - Math.PI / 4;
 				scene.add( reflector );
 
-				const frameGeometry = new THREE.BoxGeometry( 1.5, 1.5, 0.1 );
+				const frameGeometry = new THREE.BoxGeometry( 2.1, 2.1, 0.1 );
 				const frameMaterial = new THREE.MeshPhongMaterial();
 				const frame = new THREE.Mesh( frameGeometry, frameMaterial );
 				frame.position.z = - 0.07;
@@ -120,9 +136,61 @@
 
 				document.body.appendChild( VRButton.createButton( renderer ) );
 
+				window.addEventListener( 'resize', onWindowResize );
+
 				//
 
-				window.addEventListener( 'resize', onWindowResize );
+				const geometry = new THREE.BufferGeometry();
+				geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
+
+				const controller1 = renderer.xr.getController( 0 );
+				controller1.add( new THREE.Line( geometry ) );
+				scene.add( controller1 );
+
+				const controller2 = renderer.xr.getController( 1 );
+				controller2.add( new THREE.Line( geometry ) );
+				scene.add( controller2 );
+
+				//
+
+				const controllerModelFactory = new XRControllerModelFactory();
+
+				const controllerGrip1 = renderer.xr.getControllerGrip( 0 );
+				controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
+				scene.add( controllerGrip1 );
+
+				const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
+				controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
+				scene.add( controllerGrip2 );
+
+				// GUI
+
+				function onChange() {
+
+					torus.geometry.dispose();
+					torus.geometry = new THREE.TorusKnotGeometry( ...Object.values( parameters ) );
+
+				}
+
+				const gui = new GUI( { width: 300 } );
+				gui.add( parameters, 'radius', 0.0, 1.0 ).onChange( onChange );
+				gui.add( parameters, 'tube', 0.0, 1.0 ).onChange( onChange );
+				gui.add( parameters, 'tubularSegments', 10, 150, 1 ).onChange( onChange );
+				gui.add( parameters, 'radialSegments', 2, 20, 1 ).onChange( onChange );
+				gui.add( parameters, 'p', 1, 10, 1 ).onChange( onChange );
+				gui.add( parameters, 'q', 0, 10, 1 ).onChange( onChange );
+				gui.domElement.style.visibility = 'hidden';
+
+				const group = new InteractiveGroup( renderer, camera );
+				scene.add( group );
+
+				const mesh = new HTMLMesh( gui.domElement );
+				mesh.position.x = - 0.75;
+				mesh.position.y = 1.5;
+				mesh.position.z = - 0.5;
+				mesh.rotation.y = Math.PI / 4;
+				mesh.scale.setScalar( 2 );
+				group.add( mesh );
 
 			}
 
@@ -144,7 +212,7 @@
 			function render() {
 
 				const time = performance.now() * 0.0002;
-				const torus = scene.children[ 0 ];
+				const torus = scene.getObjectByName( 'torus' );
 				torus.rotation.x = time * 2;
 				torus.rotation.y = time * 5;