Răsfoiți Sursa

Merge pull request #19936 from fernandojsg/hand_mesh

WebXR hand mesh and profiles updated
Mr.doob 5 ani în urmă
părinte
comite
44d73b5ec5

+ 1 - 0
examples/files.js

@@ -330,6 +330,7 @@ var files = {
 		"webxr_vr_dragging",
 		"webxr_vr_handinput",
 		"webxr_vr_handinput_simple",
+		"webxr_vr_handinput_profiles",
 		"webxr_vr_lorenzattractor",
 		"webxr_vr_panorama",
 		"webxr_vr_panorama_depth",

+ 35 - 56
examples/jsm/webxr/WebXRHandController.js

@@ -1,39 +1,24 @@
 import {
-	Object3D,
-	SphereBufferGeometry,
-	MeshStandardMaterial,
-	Mesh
+	Object3D
 } from "../../../build/three.module.js";
 
+import {
+	XRHandPrimitiveModel
+} from "./XRHandPrimitiveModel.js";
+
+import {
+	XRHandOculusMeshModel
+} from "./XRHandOculusMeshModel.js";
+
 function XRHandModel( controller ) {
 
 	Object3D.call( this );
 
 	this.controller = controller;
+	this.motionController = null;
 	this.envMap = null;
 
-	if ( window.XRHand ) {
-
-		var geometry = new SphereBufferGeometry( 1, 10, 10 );
-		var jointMaterial = new MeshStandardMaterial( { color: 0x000000, roughness: 0.2, metalness: 0.8 } );
-		var tipMaterial = new MeshStandardMaterial( { color: 0x333333, roughness: 0.2, metalness: 0.8 } );
-
-		const tipIndexes = [
-			XRHand.THUMB_PHALANX_TIP,
-			XRHand.INDEX_PHALANX_TIP,
-			XRHand.MIDDLE_PHALANX_TIP,
-			XRHand.RING_PHALANX_TIP,
-			XRHand.LITTLE_PHALANX_TIP
-		];
-		for ( let i = 0; i <= XRHand.LITTLE_PHALANX_TIP; i ++ ) {
-
-			var cube = new Mesh( geometry, tipIndexes.indexOf( i ) !== - 1 ? tipMaterial : jointMaterial );
-			cube.castShadow = true;
-			this.add( cube );
-
-		}
-
-	}
+	this.mesh = null;
 
 }
 
@@ -45,34 +30,13 @@ XRHandModel.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
 		Object3D.prototype.updateMatrixWorld.call( this, force );
 
-		this.updateMesh();
-
-	},
-
-	updateMesh: function () {
-
-		const defaultRadius = 0.008;
+		if ( this.motionController ) {
 
-		// XR Joints
-		const XRJoints = this.controller.joints;
-		for ( var i = 0; i < this.children.length; i ++ ) {
-
-			const jointMesh = this.children[ i ];
-			const XRJoint = XRJoints[ i ];
-
-			if ( XRJoint.visible ) {
-
-				jointMesh.position.copy( XRJoint.position );
-				jointMesh.quaternion.copy( XRJoint.quaternion );
-				jointMesh.scale.setScalar( XRJoint.jointRadius || defaultRadius );
-
-			}
-
-			jointMesh.visible = XRJoint.visible;
+			this.motionController.updateMesh();
 
 		}
 
-	}
+	},
 } );
 
 
@@ -84,7 +48,7 @@ var XRHandModelFactory = ( function () {
 
 		constructor: XRHandModelFactory,
 
-		createHandModel: function ( controller ) {
+		createHandModel: function ( controller, profile, options ) {
 
 			const handModel = new XRHandModel( controller );
 			let scene = null;
@@ -92,21 +56,36 @@ var XRHandModelFactory = ( function () {
 			controller.addEventListener( 'connected', ( event ) => {
 
 				const xrInputSource = event.data;
-				console.log( "Connected!", xrInputSource );
 
-				if ( xrInputSource.hand ) {
+				if ( xrInputSource.hand && ! handModel.motionController ) {
 
+					handModel.visible = true;
 					handModel.xrInputSource = xrInputSource;
 
+					// @todo Detect profile if not provided
+					if ( profile === undefined || profile === "spheres" ) {
+
+						handModel.motionController = new XRHandPrimitiveModel( handModel, controller, xrInputSource.handedness, { primitive: "sphere" } );
+
+					} else if ( profile === "boxes" ) {
+
+						handModel.motionController = new XRHandPrimitiveModel( handModel, controller, xrInputSource.handedness, { primitive: "box" } );
+
+					} else if ( profile === "oculus" ) {
+
+						handModel.motionController = new XRHandOculusMeshModel( handModel, controller, xrInputSource.handedness, options );
+
+					}
+
 				}
 
 			} );
 
 			controller.addEventListener( 'disconnected', () => {
 
-				handModel.motionController = null;
-				handModel.remove( scene );
-				scene = null;
+				// handModel.motionController = null;
+				// handModel.remove( scene );
+				// scene = null;
 
 			} );
 

+ 105 - 0
examples/jsm/webxr/XRHandOculusMeshModel.js

@@ -0,0 +1,105 @@
+import { FBXLoader } from "../loaders/FBXLoader.js";
+
+class XRHandOculusMeshModel {
+
+	constructor( handModel, controller, handedness, options ) {
+
+		this.controller = controller;
+		this.handModel = handModel;
+
+		this.bones = [];
+		var loader = new FBXLoader();
+		const low = options && options.model === "lowpoly" ? "_low" : "";
+
+		loader.load( `/examples/models/fbx/OculusHand_${handedness === "right" ? "R" : "L"}${low}.fbx`, object => {
+
+			this.handModel.add( object );
+			// Hack because of the scale of the skinnedmesh
+			object.scale.setScalar( 0.01 );
+			object.getObjectByProperty( "type", "SkinnedMesh" ).frustumCulled = false;
+
+			const bonesMapping = [
+				'b_%_wrist', // XRHand.WRIST,
+
+				'b_%_thumb1', // XRHand.THUMB_METACARPAL,
+				'b_%_thumb2', // XRHand.THUMB_PHALANX_PROXIMAL,
+				'b_%_thumb3', // XRHand.THUMB_PHALANX_DISTAL,
+				'b_%_thumb_null', // XRHand.THUMB_PHALANX_TIP,
+
+				null, //'b_%_index1', // XRHand.INDEX_METACARPAL,
+				'b_%_index1', // XRHand.INDEX_PHALANX_PROXIMAL,
+				'b_%_index2', // XRHand.INDEX_PHALANX_INTERMEDIATE,
+				'b_%_index3', // XRHand.INDEX_PHALANX_DISTAL,
+				'b_%_index_null', // XRHand.INDEX_PHALANX_TIP,
+
+				null, //'b_%_middle1', // XRHand.MIDDLE_METACARPAL,
+				'b_%_middle1', // XRHand.MIDDLE_PHALANX_PROXIMAL,
+				'b_%_middle2', // XRHand.MIDDLE_PHALANX_INTERMEDIATE,
+				'b_%_middle3', // XRHand.MIDDLE_PHALANX_DISTAL,
+				'b_%_middlenull', // XRHand.MIDDLE_PHALANX_TIP,
+
+				null, //'b_%_ring1', // XRHand.RING_METACARPAL,
+				'b_%_ring1', // XRHand.RING_PHALANX_PROXIMAL,
+				'b_%_ring2', // XRHand.RING_PHALANX_INTERMEDIATE,
+				'b_%_ring3', // XRHand.RING_PHALANX_DISTAL,
+				'b_%_ring_inull', // XRHand.RING_PHALANX_TIP,
+
+				'b_%_pinky0', // XRHand.LITTLE_METACARPAL,
+				'b_%_pinky1', // XRHand.LITTLE_PHALANX_PROXIMAL,
+				'b_%_pinky2', // XRHand.LITTLE_PHALANX_INTERMEDIATE,
+				'b_%_pinky3', // XRHand.LITTLE_PHALANX_DISTAL,
+				'b_%_pinkynull', // XRHand.LITTLE_PHALANX_TIP
+			];
+			bonesMapping.forEach( boneName => {
+
+				if ( boneName ) {
+
+					const bone = object.getObjectByName( boneName.replace( "%", handedness === "right" ? "r" : "l" ) );
+					this.bones.push( bone );
+
+				} else {
+
+					this.bones.push( null );
+
+				}
+
+			} );
+
+		} );
+
+	}
+
+	updateMesh() {
+
+		// XR Joints
+		const XRJoints = this.controller.joints;
+		for ( var i = 0; i < this.bones.length; i ++ ) {
+
+			const bone = this.bones[ i ];
+			const XRJoint = XRJoints[ i ];
+
+			if ( XRJoint ) {
+
+				if ( XRJoint.visible ) {
+
+					let position = XRJoint.position;
+
+					if ( bone ) {
+
+						bone.position.copy( position.clone().multiplyScalar( 100 ) );
+						bone.quaternion.copy( XRJoint.quaternion );
+						// bone.scale.setScalar( XRJoint.jointRadius || defaultRadius );
+
+					}
+
+				}
+
+			}
+
+		}
+
+	}
+
+}
+
+export { XRHandOculusMeshModel };

+ 84 - 0
examples/jsm/webxr/XRHandPrimitiveModel.js

@@ -0,0 +1,84 @@
+import {
+	SphereBufferGeometry,
+	BoxBufferGeometry,
+	MeshStandardMaterial,
+	Mesh,
+	Group
+} from "../../../build/three.module.js";
+
+class XRHandPrimitiveModel {
+
+	constructor( handModel, controller, handedness, options ) {
+
+		this.controller = controller;
+		this.handModel = handModel;
+
+	  this.envMap = null;
+
+		this.handMesh = new Group();
+		this.handModel.add( this.handMesh );
+
+		if ( window.XRHand ) {
+
+			var geometry;
+			if ( ! options || ! options.primitive || options.primitive === "sphere" ) {
+
+				geometry = new SphereBufferGeometry( 1, 10, 10 );
+
+			} else if ( options.primitive === "box" ) {
+
+				geometry = new BoxBufferGeometry( 1, 1, 1 );
+
+			}
+
+			var jointMaterial = new MeshStandardMaterial( { color: 0xffffff, roughness: 1, metalness: 0 } );
+			var tipMaterial = new MeshStandardMaterial( { color: 0x999999, roughness: 1, metalness: 0 } );
+
+			const tipIndexes = [
+				XRHand.THUMB_PHALANX_TIP,
+				XRHand.INDEX_PHALANX_TIP,
+				XRHand.MIDDLE_PHALANX_TIP,
+				XRHand.RING_PHALANX_TIP,
+				XRHand.LITTLE_PHALANX_TIP
+			];
+			for ( let i = 0; i <= XRHand.LITTLE_PHALANX_TIP; i ++ ) {
+
+				var cube = new Mesh( geometry, tipIndexes.indexOf( i ) !== - 1 ? tipMaterial : jointMaterial );
+				cube.castShadow = true;
+				this.handMesh.add( cube );
+
+			}
+
+		}
+
+	}
+
+	updateMesh() {
+
+		const defaultRadius = 0.008;
+		const objects = this.handMesh.children;
+
+		// XR Joints
+		const XRJoints = this.controller.joints;
+		for ( var i = 0; i < objects.length; i ++ ) {
+
+			const jointMesh = objects[ i ];
+			const XRJoint = XRJoints[ i ];
+
+			if ( XRJoint.visible ) {
+
+				jointMesh.position.copy( XRJoint.position );
+				jointMesh.quaternion.copy( XRJoint.quaternion );
+				jointMesh.scale.setScalar( XRJoint.jointRadius || defaultRadius );
+
+			}
+
+			jointMesh.visible = XRJoint.visible;
+
+		}
+
+	}
+
+}
+
+export { XRHandPrimitiveModel };

BIN
examples/models/fbx/OculusHand_L.fbx


BIN
examples/models/fbx/OculusHand_L_low.fbx


BIN
examples/models/fbx/OculusHand_R.fbx


BIN
examples/models/fbx/OculusHand_R_low.fbx


BIN
examples/screenshots/webxr_vr_handinput_profiles.jpg


+ 221 - 0
examples/webxr_vr_handinput_profiles.html

@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js vr - hand and controller - simple</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> vr - hand and controller support (profiles)
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { VRButton } from './jsm/webxr/VRButton.js';
+			import { XRControllerModelFactory } from './jsm/webxr/XRControllerModelFactory.js';
+			import { XRHandModelFactory } from './jsm/webxr/WebXRHandController.js';
+
+			var container;
+			var camera, scene, renderer;
+			var hand1, hand2;
+			var controller1, controller2;
+			var controllerGrip1, controllerGrip2;
+
+			var currentHandModel = {
+				left: 0,
+				right: 0
+			};
+
+			var handModels = {
+				left: null,
+				right: null
+			};
+
+			var controls;
+
+			init();
+			animate();
+
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				scene = new THREE.Scene();
+				window.scene = scene;
+				scene.background = new THREE.Color( 0x444444 );
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 10 );
+				camera.position.set( 0, 1.6, 3 );
+
+				controls = new OrbitControls( camera, container );
+				controls.target.set( 0, 1.6, 0 );
+				controls.update();
+
+				var geometry = new THREE.PlaneBufferGeometry( 4, 4 );
+				var material = new THREE.MeshStandardMaterial( {
+					color: 0x222222,
+					roughness: 1.0,
+					metalness: 0.0
+				} );
+				var floor = new THREE.Mesh( geometry, material );
+				floor.rotation.x = - Math.PI / 2;
+				floor.receiveShadow = true;
+				scene.add( floor );
+
+				scene.add( new THREE.HemisphereLight( 0x808080, 0x606060 ) );
+
+				var light = new THREE.DirectionalLight( 0xffffff );
+				light.position.set( 0, 6, 0 );
+				light.castShadow = true;
+				light.shadow.camera.top = 2;
+				light.shadow.camera.bottom = - 2;
+				light.shadow.camera.right = 2;
+				light.shadow.camera.left = - 2;
+				light.shadow.mapSize.set( 4096, 4096 );
+				scene.add( light );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				renderer.shadowMap.enabled = true;
+				renderer.xr.enabled = true;
+
+				container.appendChild( renderer.domElement );
+
+				document.body.appendChild( VRButton.createButton( renderer ) );
+
+				// controllers
+
+				controller1 = renderer.xr.getController( 0 );
+				scene.add( controller1 );
+
+				controller2 = renderer.xr.getController( 1 );
+				scene.add( controller2 );
+
+				var controllerModelFactory = new XRControllerModelFactory();
+				var handModelFactory = new XRHandModelFactory();
+
+				// Hand 1
+
+				controllerGrip1 = renderer.xr.getControllerGrip( 0 );
+				controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
+				scene.add( controllerGrip1 );
+
+				hand1 = renderer.xr.getHand( 0 );
+				scene.add( hand1 );
+
+				handModels.left = [
+					handModelFactory.createHandModel( hand1, "boxes" ),
+					handModelFactory.createHandModel( hand1, "spheres" ),
+					handModelFactory.createHandModel( hand1, "oculus", { model: "lowpoly" } ),
+					handModelFactory.createHandModel( hand1, "oculus" )
+				];
+
+				handModels.left.forEach( model => {
+
+					model.visible = false;
+					hand1.add( model );
+
+				} );
+
+				handModels.left[ currentHandModel.left ].visible = true;
+
+				function cycleHandModel( hand ) {
+
+					handModels[ hand ][ currentHandModel[ hand ] ].visible = false;
+					currentHandModel[ hand ] = ( currentHandModel[ hand ] + 1 ) % handModels[ hand ].length;
+					handModels[ hand ][ currentHandModel[ hand ] ].visible = true;
+
+				}
+
+				hand1.addEventListener( 'pinchend', evt => {
+
+					cycleHandModel( evt.handedness );
+
+				} );
+
+				// Hand 2
+
+				controllerGrip2 = renderer.xr.getControllerGrip( 1 );
+				controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
+				scene.add( controllerGrip2 );
+
+				hand2 = renderer.xr.getHand( 1 );
+				scene.add( hand2 );
+
+				handModels.right = [
+					handModelFactory.createHandModel( hand2, "boxes" ),
+					handModelFactory.createHandModel( hand2, "spheres" ),
+					handModelFactory.createHandModel( hand2, "oculus", { model: "lowpoly" } ),
+					handModelFactory.createHandModel( hand2, "oculus" )
+				];
+				handModels.right.forEach( model => {
+
+					model.visible = false;
+					hand2.add( model );
+
+				} );
+
+				handModels.right[ currentHandModel.right ].visible = true;
+
+				window.handModels = handModels;
+
+				hand2.addEventListener( 'pinchend', evt => {
+
+					cycleHandModel( evt.handedness );
+
+				} );
+
+				//
+				window.hands = [ hand1, hand2 ];
+
+				var geometry = new THREE.BufferGeometry().setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 1 ) ] );
+
+				var line = new THREE.Line( geometry );
+				line.name = 'line';
+				line.scale.z = 5;
+
+				controller1.add( line.clone() );
+				controller2.add( line.clone() );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+			//
+
+			function animate() {
+
+				renderer.setAnimationLoop( render );
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 2 - 0
src/renderers/webxr/WebXRController.js

@@ -174,6 +174,7 @@ Object.assign( WebXRController.prototype, {
 							hand.inputState.pinching = false;
 							this.dispatchEvent( {
 								type: "pinchend",
+								handedness: inputSource.handedness,
 								target: this
 							} );
 
@@ -182,6 +183,7 @@ Object.assign( WebXRController.prototype, {
 							hand.inputState.pinching = true;
 							this.dispatchEvent( {
 								type: "pinchstart",
+								handedness: inputSource.handedness,
 								target: this
 							} );