Procházet zdrojové kódy

Editor: Rescued VR mode.

Mr.doob před 4 roky
rodič
revize
312ee7b016

+ 5 - 0
editor/js/Editor.js

@@ -26,6 +26,11 @@ function Editor() {
 		startPlayer: new Signal(),
 		stopPlayer: new Signal(),
 
+		// vr
+
+		toggleVR: new Signal(),
+		exitedVR: new Signal(),
+
 		// notifications
 
 		editorCleared: new Signal(),

+ 25 - 0
editor/js/Menubar.View.js

@@ -46,6 +46,31 @@ function MenubarView( editor ) {
 	} );
 	options.add( option );
 
+	// VR (Work in progress)
+
+	if ( 'xr' in navigator ) {
+
+		navigator.xr.isSessionSupported( 'immersive-vr' )
+			.then( function ( supported ) {
+
+				if ( supported ) {
+
+					var option = new UIRow();
+					option.setClass( 'option' );
+					option.setTextContent( 'VR' );
+					option.onClick( function () {
+
+						editor.signals.toggleVR.dispatch();
+
+					} );
+					options.add( option );
+
+				}
+
+			} );
+
+	}
+
 	return container;
 
 }

+ 92 - 0
editor/js/Viewport.VR.js

@@ -0,0 +1,92 @@
+import * as THREE from '../../build/three.module.js';
+
+import { HTMLMesh } from './libs/three.html.js';
+
+import { XRControllerModelFactory } from '../../examples/jsm/webxr/XRControllerModelFactory.js';
+
+class VR {
+
+	constructor( editor ) {
+
+		const signals = editor.signals;
+
+		let group = null;
+		let renderer = null;
+
+		this.currentSession = null;
+
+		const onSessionStarted = async ( session ) => {
+
+			if ( group === null ) {
+
+				group = new THREE.Group();
+				editor.sceneHelpers.add( group );
+
+				const mesh = new HTMLMesh( document.getElementById( 'sidebar' ) );
+				mesh.position.set( 1, 1.5, 0 );
+				mesh.rotation.y = - 0.5;
+				group.add( mesh );
+
+				//
+
+				const controllerModelFactory = new XRControllerModelFactory();
+
+				const controllerGrip1 = renderer.xr.getControllerGrip( 0 );
+				controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
+				group.add( controllerGrip1 );
+
+				const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
+				controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
+				group.add( controllerGrip2 );
+
+			}
+
+			group.visible = true;
+
+			this.currentSession = session;
+			this.currentSession.addEventListener( 'end', onSessionEnded );
+
+			await renderer.xr.setSession( this.currentSession );
+
+		}
+
+		 const onSessionEnded = async () => {
+
+			group.visible = false;
+
+			this.currentSession.removeEventListener( 'end', onSessionEnded );
+			this.currentSession = null;
+
+			await renderer.xr.setSession( null );
+
+			signals.exitedVR.dispatch();
+
+		};
+
+		signals.toggleVR.add( () => {
+
+			if ( this.currentSession === null ) {
+
+				const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor' ] };
+				navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
+
+			} else {
+
+				this.currentSession.end();
+
+			}
+
+		} );
+
+		signals.rendererChanged.add( ( value ) => {
+
+			renderer = value;
+			renderer.xr.enabled = true;
+
+		} );
+
+	}
+
+}
+
+export { VR };

+ 13 - 5
editor/js/Viewport.js

@@ -9,6 +9,7 @@ import { EditorControls } from './EditorControls.js';
 import { ViewportCamera } from './Viewport.Camera.js';
 import { ViewportInfo } from './Viewport.Info.js';
 import { ViewHelper } from './Viewport.ViewHelper.js';
+import { VR } from './Viewport.VR.js';
 
 import { SetPositionCommand } from './commands/SetPositionCommand.js';
 import { SetRotationCommand } from './commands/SetRotationCommand.js';
@@ -42,6 +43,7 @@ function Viewport( editor ) {
 
 	var grid = new THREE.GridHelper( 30, 30, 0x444444, 0x888888 );
 	var viewHelper = new ViewHelper( camera, container );
+	var vr = new VR( editor );
 
 	//
 
@@ -335,6 +337,7 @@ function Viewport( editor ) {
 
 		if ( renderer !== null ) {
 
+			renderer.setAnimationLoop( null );
 			renderer.dispose();
 			pmremGenerator.dispose();
 			pmremTexture = null;
@@ -345,6 +348,7 @@ function Viewport( editor ) {
 
 		renderer = newRenderer;
 
+		renderer.setAnimationLoop( animate );
 		renderer.setClearColor( 0xaaaaaa );
 
 		if ( window.matchMedia ) {
@@ -659,6 +663,8 @@ function Viewport( editor ) {
 
 	} );
 
+	signals.exitedVR.add( render );
+
 	//
 
 	signals.windowResize.add( function () {
@@ -695,8 +701,6 @@ function Viewport( editor ) {
 
 	function animate() {
 
-		requestAnimationFrame( animate );
-
 		var mixer = editor.mixer;
 		var delta = clock.getDelta();
 
@@ -716,12 +720,16 @@ function Viewport( editor ) {
 
 		}
 
+		if ( vr.currentSession !== null ) {
+
+			needsUpdate = true;
+
+		}
+
 		if ( needsUpdate === true ) render();
 
 	}
 
-	requestAnimationFrame( animate );
-
 	//
 
 	var startTime = 0;
@@ -743,7 +751,7 @@ function Viewport( editor ) {
 
 			renderer.autoClear = false;
 			if ( showSceneHelpers === true ) renderer.render( sceneHelpers, camera );
-			viewHelper.render( renderer );
+			if ( vr.currentSession === null ) viewHelper.render( renderer );
 			renderer.autoClear = true;
 
 		}

+ 237 - 0
editor/js/libs/three.html.js

@@ -0,0 +1,237 @@
+import {
+	CanvasTexture,
+	Mesh,
+	MeshBasicMaterial,
+	PlaneGeometry,
+	sRGBEncoding
+} from '../../../build/three.module.js';
+
+class HTMLMesh extends Mesh {
+
+	constructor( dom ) {
+
+		const texture = new HTMLTexture( dom );
+
+		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 );
+
+	}
+
+}
+
+class HTMLTexture extends CanvasTexture {
+
+	constructor( dom ) {
+
+		super( html2canvas( dom ) );
+
+		this.dom = dom;
+		this.encoding = sRGBEncoding;
+		this.anisotropy = 16;
+
+	}
+
+	update() {
+
+		this.image = html2canvas( this.dom );
+		this.needsUpdate = true;
+
+	}
+
+}
+
+//
+
+function html2canvas( element ) {
+
+	var range = document.createRange();
+
+	function Clipper( context ) {
+
+		var clips = [];
+		var isClipping = false;
+
+		function doClip() {
+
+			if ( isClipping ) {
+
+				isClipping = false;
+				context.restore();
+
+			}
+
+			if ( clips.length === 0 ) return;
+
+			var minX = - Infinity, minY = - Infinity;
+			var maxX = Infinity, maxY = Infinity;
+
+			for ( var i = 0; i < clips.length; i ++ ) {
+
+				var clip = clips[ i ];
+
+				minX = Math.max( minX, clip.x );
+				minY = Math.max( minY, clip.y );
+				maxX = Math.min( maxX, clip.x + clip.width );
+				maxY = Math.min( maxY, clip.y + clip.height );
+
+			}
+
+			context.save();
+			context.beginPath();
+			context.rect( minX, minY, maxX - minX, maxY - minY );
+			context.clip();
+
+			isClipping = true;
+
+		}
+
+		return {
+			add: function ( clip ) {
+
+				clips.push( clip );
+				doClip();
+
+			},
+			remove: function () {
+
+				clips.pop();
+				doClip();
+
+			}
+
+		};
+
+	}
+
+	function drawText( style, x, y, string ) {
+
+		if ( string !== '' ) {
+
+			context.font = style.fontSize + ' ' + style.fontFamily;
+			context.textBaseline = 'top';
+			context.fillStyle = style.color;
+			context.fillText( string, x, y );
+
+		}
+
+	}
+
+	function drawBorder( style, which, x, y, width, height ) {
+
+		var borderWidth = style[ which + 'Width' ];
+		var borderStyle = style[ which + 'Style' ];
+		var borderColor = style[ which + 'Color' ];
+
+		if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
+
+			context.strokeStyle = borderColor;
+			context.beginPath();
+			context.moveTo( x, y );
+			context.lineTo( x + width, y + height );
+			context.stroke();
+
+		}
+
+	}
+
+	function drawElement( element, style ) {
+
+		var x = 0, y = 0, width = 0, height = 0;
+
+		if ( element.nodeType === 3 ) {
+
+			// text
+
+			range.selectNode( element );
+
+			var rect = range.getBoundingClientRect();
+
+			x = rect.left - offset.left - 0.5;
+			y = rect.top - offset.top - 0.5;
+			width = rect.width;
+			height = rect.height;
+
+			drawText( style, x, y, element.nodeValue.trim() );
+
+		} else {
+
+			if ( element.style.display === 'none' ) return;
+
+			var rect = element.getBoundingClientRect();
+
+			x = rect.left - offset.left - 0.5;
+			y = rect.top - offset.top - 0.5;
+			width = rect.width;
+			height = rect.height;
+
+			style = window.getComputedStyle( element );
+
+			var backgroundColor = style.backgroundColor;
+
+			if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
+
+				context.fillStyle = backgroundColor;
+				context.fillRect( x, y, width, height );
+
+			}
+
+			drawBorder( style, 'borderTop', x, y, width, 0 );
+			drawBorder( style, 'borderLeft', x, y, 0, height );
+			drawBorder( style, 'borderBottom', x, y + height, width, 0 );
+			drawBorder( style, 'borderRight', x + width, y, 0, height );
+
+			if ( element.type === 'color' || element.type === 'text' ) {
+
+				clipper.add( { x: x, y: y, width: width, height: height } );
+
+				drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
+
+				clipper.remove();
+
+			}
+
+		}
+
+		/*
+		// debug
+		context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
+		context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
+		*/
+
+		var isClipping = style.overflow === 'auto' || style.overflow === 'hidden';
+
+		if ( isClipping ) clipper.add( { x: x, y: y, width: width, height: height } );
+
+		for ( var i = 0; i < element.childNodes.length; i ++ ) {
+
+			drawElement( element.childNodes[ i ], style );
+
+		}
+
+		if ( isClipping ) clipper.remove();
+
+	}
+
+	var offset = element.getBoundingClientRect();
+
+	var canvas = document.createElement( 'canvas' );
+	canvas.width = offset.width;
+	canvas.height = offset.height;
+
+	var context = canvas.getContext( '2d'/*, { alpha: false }*/ );
+
+	var clipper = new Clipper( context );
+
+	console.time( 'drawElement' );
+
+	drawElement( element );
+
+	console.timeEnd( 'drawElement' );
+
+	return canvas;
+
+}
+
+export { HTMLMesh, HTMLTexture };