414 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			414 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import * as THREE from 'three'; | ||
|  | 
 | ||
|  | const PINCH_MAX = 0.05; | ||
|  | const PINCH_THRESHOLD = 0.02; | ||
|  | const PINCH_MIN = 0.01; | ||
|  | const POINTER_ADVANCE_MAX = 0.02; | ||
|  | const POINTER_OPACITY_MAX = 1; | ||
|  | const POINTER_OPACITY_MIN = 0.4; | ||
|  | const POINTER_FRONT_RADIUS = 0.002; | ||
|  | const POINTER_REAR_RADIUS = 0.01; | ||
|  | const POINTER_REAR_RADIUS_MIN = 0.003; | ||
|  | const POINTER_LENGTH = 0.035; | ||
|  | const POINTER_SEGMENTS = 16; | ||
|  | const POINTER_RINGS = 12; | ||
|  | const POINTER_HEMISPHERE_ANGLE = 110; | ||
|  | const YAXIS = /* @__PURE__ */ new THREE.Vector3( 0, 1, 0 ); | ||
|  | const ZAXIS = /* @__PURE__ */ new THREE.Vector3( 0, 0, 1 ); | ||
|  | 
 | ||
|  | const CURSOR_RADIUS = 0.02; | ||
|  | const CURSOR_MAX_DISTANCE = 1.5; | ||
|  | 
 | ||
|  | class OculusHandPointerModel extends THREE.Object3D { | ||
|  | 
 | ||
|  | 	constructor( hand, controller ) { | ||
|  | 
 | ||
|  | 		super(); | ||
|  | 
 | ||
|  | 		this.hand = hand; | ||
|  | 		this.controller = controller; | ||
|  | 
 | ||
|  | 		// Unused
 | ||
|  | 		this.motionController = null; | ||
|  | 		this.envMap = null; | ||
|  | 		this.mesh = null; | ||
|  | 
 | ||
|  | 		this.pointerGeometry = null; | ||
|  | 		this.pointerMesh = null; | ||
|  | 		this.pointerObject = null; | ||
|  | 
 | ||
|  | 		this.pinched = false; | ||
|  | 		this.attached = false; | ||
|  | 
 | ||
|  | 		this.cursorObject = null; | ||
|  | 
 | ||
|  | 		this.raycaster = null; | ||
|  | 
 | ||
|  | 		this._onConnected = this._onConnected.bind( this ); | ||
|  | 		this._onDisconnected = this._onDisconnected.bind( this ); | ||
|  | 		this.hand.addEventListener( 'connected', this._onConnected ); | ||
|  | 		this.hand.addEventListener( 'disconnected', this._onDisconnected ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_onConnected( event ) { | ||
|  | 
 | ||
|  | 		const xrInputSource = event.data; | ||
|  | 		if ( xrInputSource.hand ) { | ||
|  | 
 | ||
|  | 			this.visible = true; | ||
|  | 			this.xrInputSource = xrInputSource; | ||
|  | 
 | ||
|  | 			this.createPointer(); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_onDisconnected() { | ||
|  | 
 | ||
|  | 		this.visible = false; | ||
|  | 		this.xrInputSource = null; | ||
|  | 
 | ||
|  | 		if ( this.pointerGeometry ) this.pointerGeometry.dispose(); | ||
|  | 		if ( this.pointerMesh && this.pointerMesh.material ) this.pointerMesh.material.dispose(); | ||
|  | 
 | ||
|  | 		this.clear(); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_drawVerticesRing( vertices, baseVector, ringIndex ) { | ||
|  | 
 | ||
|  | 		const segmentVector = baseVector.clone(); | ||
|  | 		for ( let i = 0; i < POINTER_SEGMENTS; i ++ ) { | ||
|  | 
 | ||
|  | 			segmentVector.applyAxisAngle( ZAXIS, ( Math.PI * 2 ) / POINTER_SEGMENTS ); | ||
|  | 			const vid = ringIndex * POINTER_SEGMENTS + i; | ||
|  | 			vertices[ 3 * vid ] = segmentVector.x; | ||
|  | 			vertices[ 3 * vid + 1 ] = segmentVector.y; | ||
|  | 			vertices[ 3 * vid + 2 ] = segmentVector.z; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_updatePointerVertices( rearRadius ) { | ||
|  | 
 | ||
|  | 		const vertices = this.pointerGeometry.attributes.position.array; | ||
|  | 		// first ring for front face
 | ||
|  | 		const frontFaceBase = new THREE.Vector3( | ||
|  | 			POINTER_FRONT_RADIUS, | ||
|  | 			0, | ||
|  | 			- 1 * ( POINTER_LENGTH - rearRadius ) | ||
|  | 		); | ||
|  | 		this._drawVerticesRing( vertices, frontFaceBase, 0 ); | ||
|  | 
 | ||
|  | 		// rings for rear hemisphere
 | ||
|  | 		const rearBase = new THREE.Vector3( | ||
|  | 			Math.sin( ( Math.PI * POINTER_HEMISPHERE_ANGLE ) / 180 ) * rearRadius, | ||
|  | 			Math.cos( ( Math.PI * POINTER_HEMISPHERE_ANGLE ) / 180 ) * rearRadius, | ||
|  | 			0 | ||
|  | 		); | ||
|  | 		for ( let i = 0; i < POINTER_RINGS; i ++ ) { | ||
|  | 
 | ||
|  | 			this._drawVerticesRing( vertices, rearBase, i + 1 ); | ||
|  | 			rearBase.applyAxisAngle( | ||
|  | 				YAXIS, | ||
|  | 				( Math.PI * POINTER_HEMISPHERE_ANGLE ) / 180 / ( POINTER_RINGS * - 2 ) | ||
|  | 			); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// front and rear face center vertices
 | ||
|  | 		const frontCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ); | ||
|  | 		const rearCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ) + 1; | ||
|  | 		const frontCenter = new THREE.Vector3( | ||
|  | 			0, | ||
|  | 			0, | ||
|  | 			- 1 * ( POINTER_LENGTH - rearRadius ) | ||
|  | 		); | ||
|  | 		vertices[ frontCenterIndex * 3 ] = frontCenter.x; | ||
|  | 		vertices[ frontCenterIndex * 3 + 1 ] = frontCenter.y; | ||
|  | 		vertices[ frontCenterIndex * 3 + 2 ] = frontCenter.z; | ||
|  | 		const rearCenter = new THREE.Vector3( 0, 0, rearRadius ); | ||
|  | 		vertices[ rearCenterIndex * 3 ] = rearCenter.x; | ||
|  | 		vertices[ rearCenterIndex * 3 + 1 ] = rearCenter.y; | ||
|  | 		vertices[ rearCenterIndex * 3 + 2 ] = rearCenter.z; | ||
|  | 
 | ||
|  | 		this.pointerGeometry.setAttribute( | ||
|  | 			'position', | ||
|  | 			new THREE.Float32BufferAttribute( vertices, 3 ) | ||
|  | 		); | ||
|  | 		// verticesNeedUpdate = true;
 | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	createPointer() { | ||
|  | 
 | ||
|  | 		let i, j; | ||
|  | 		const vertices = new Array( | ||
|  | 			( ( POINTER_RINGS + 1 ) * POINTER_SEGMENTS + 2 ) * 3 | ||
|  | 		).fill( 0 ); | ||
|  | 		// const vertices = [];
 | ||
|  | 		const indices = []; | ||
|  | 		this.pointerGeometry = new THREE.BufferGeometry(); | ||
|  | 
 | ||
|  | 		this.pointerGeometry.setAttribute( | ||
|  | 			'position', | ||
|  | 			new THREE.Float32BufferAttribute( vertices, 3 ) | ||
|  | 		); | ||
|  | 
 | ||
|  | 		this._updatePointerVertices( POINTER_REAR_RADIUS ); | ||
|  | 
 | ||
|  | 		// construct faces to connect rings
 | ||
|  | 		for ( i = 0; i < POINTER_RINGS; i ++ ) { | ||
|  | 
 | ||
|  | 			for ( j = 0; j < POINTER_SEGMENTS - 1; j ++ ) { | ||
|  | 
 | ||
|  | 				indices.push( | ||
|  | 					i * POINTER_SEGMENTS + j, | ||
|  | 					i * POINTER_SEGMENTS + j + 1, | ||
|  | 					( i + 1 ) * POINTER_SEGMENTS + j | ||
|  | 				); | ||
|  | 				indices.push( | ||
|  | 					i * POINTER_SEGMENTS + j + 1, | ||
|  | 					( i + 1 ) * POINTER_SEGMENTS + j + 1, | ||
|  | 					( i + 1 ) * POINTER_SEGMENTS + j | ||
|  | 				); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			indices.push( | ||
|  | 				( i + 1 ) * POINTER_SEGMENTS - 1, | ||
|  | 				i * POINTER_SEGMENTS, | ||
|  | 				( i + 2 ) * POINTER_SEGMENTS - 1 | ||
|  | 			); | ||
|  | 			indices.push( | ||
|  | 				i * POINTER_SEGMENTS, | ||
|  | 				( i + 1 ) * POINTER_SEGMENTS, | ||
|  | 				( i + 2 ) * POINTER_SEGMENTS - 1 | ||
|  | 			); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// construct front and rear face
 | ||
|  | 		const frontCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ); | ||
|  | 		const rearCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ) + 1; | ||
|  | 
 | ||
|  | 		for ( i = 0; i < POINTER_SEGMENTS - 1; i ++ ) { | ||
|  | 
 | ||
|  | 			indices.push( frontCenterIndex, i + 1, i ); | ||
|  | 			indices.push( | ||
|  | 				rearCenterIndex, | ||
|  | 				i + POINTER_SEGMENTS * POINTER_RINGS, | ||
|  | 				i + POINTER_SEGMENTS * POINTER_RINGS + 1 | ||
|  | 			); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		indices.push( frontCenterIndex, 0, POINTER_SEGMENTS - 1 ); | ||
|  | 		indices.push( | ||
|  | 			rearCenterIndex, | ||
|  | 			POINTER_SEGMENTS * ( POINTER_RINGS + 1 ) - 1, | ||
|  | 			POINTER_SEGMENTS * POINTER_RINGS | ||
|  | 		); | ||
|  | 
 | ||
|  | 		const material = new THREE.MeshBasicMaterial(); | ||
|  | 		material.transparent = true; | ||
|  | 		material.opacity = POINTER_OPACITY_MIN; | ||
|  | 
 | ||
|  | 		this.pointerGeometry.setIndex( indices ); | ||
|  | 
 | ||
|  | 		this.pointerMesh = new THREE.Mesh( this.pointerGeometry, material ); | ||
|  | 
 | ||
|  | 		this.pointerMesh.position.set( 0, 0, - 1 * POINTER_REAR_RADIUS ); | ||
|  | 		this.pointerObject = new THREE.Object3D(); | ||
|  | 		this.pointerObject.add( this.pointerMesh ); | ||
|  | 
 | ||
|  | 		this.raycaster = new THREE.Raycaster(); | ||
|  | 
 | ||
|  | 		// create cursor
 | ||
|  | 		const cursorGeometry = new THREE.SphereGeometry( CURSOR_RADIUS, 10, 10 ); | ||
|  | 		const cursorMaterial = new THREE.MeshBasicMaterial(); | ||
|  | 		cursorMaterial.transparent = true; | ||
|  | 		cursorMaterial.opacity = POINTER_OPACITY_MIN; | ||
|  | 
 | ||
|  | 		this.cursorObject = new THREE.Mesh( cursorGeometry, cursorMaterial ); | ||
|  | 		this.pointerObject.add( this.cursorObject ); | ||
|  | 
 | ||
|  | 		this.add( this.pointerObject ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_updateRaycaster() { | ||
|  | 
 | ||
|  | 		if ( this.raycaster ) { | ||
|  | 
 | ||
|  | 			const pointerMatrix = this.pointerObject.matrixWorld; | ||
|  | 			const tempMatrix = new THREE.Matrix4(); | ||
|  | 			tempMatrix.identity().extractRotation( pointerMatrix ); | ||
|  | 			this.raycaster.ray.origin.setFromMatrixPosition( pointerMatrix ); | ||
|  | 			this.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_updatePointer() { | ||
|  | 
 | ||
|  | 		this.pointerObject.visible = this.controller.visible; | ||
|  | 		const indexTip = this.hand.joints[ 'index-finger-tip' ]; | ||
|  | 		const thumbTip = this.hand.joints[ 'thumb-tip' ]; | ||
|  | 		const distance = indexTip.position.distanceTo( thumbTip.position ); | ||
|  | 		const position = indexTip.position | ||
|  | 			.clone() | ||
|  | 			.add( thumbTip.position ) | ||
|  | 			.multiplyScalar( 0.5 ); | ||
|  | 		this.pointerObject.position.copy( position ); | ||
|  | 		this.pointerObject.quaternion.copy( this.controller.quaternion ); | ||
|  | 
 | ||
|  | 		this.pinched = distance <= PINCH_THRESHOLD; | ||
|  | 
 | ||
|  | 		const pinchScale = ( distance - PINCH_MIN ) / ( PINCH_MAX - PINCH_MIN ); | ||
|  | 		const focusScale = ( distance - PINCH_MIN ) / ( PINCH_THRESHOLD - PINCH_MIN ); | ||
|  | 		if ( pinchScale > 1 ) { | ||
|  | 
 | ||
|  | 			this._updatePointerVertices( POINTER_REAR_RADIUS ); | ||
|  | 			this.pointerMesh.position.set( 0, 0, - 1 * POINTER_REAR_RADIUS ); | ||
|  | 			this.pointerMesh.material.opacity = POINTER_OPACITY_MIN; | ||
|  | 
 | ||
|  | 		} else if ( pinchScale > 0 ) { | ||
|  | 
 | ||
|  | 			const rearRadius = | ||
|  |         ( POINTER_REAR_RADIUS - POINTER_REAR_RADIUS_MIN ) * pinchScale + | ||
|  |         POINTER_REAR_RADIUS_MIN; | ||
|  | 			this._updatePointerVertices( rearRadius ); | ||
|  | 			if ( focusScale < 1 ) { | ||
|  | 
 | ||
|  | 				this.pointerMesh.position.set( | ||
|  | 					0, | ||
|  | 					0, | ||
|  | 					- 1 * rearRadius - ( 1 - focusScale ) * POINTER_ADVANCE_MAX | ||
|  | 				); | ||
|  | 				this.pointerMesh.material.opacity = | ||
|  |           POINTER_OPACITY_MIN + | ||
|  |           ( 1 - focusScale ) * ( POINTER_OPACITY_MAX - POINTER_OPACITY_MIN ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				this.pointerMesh.position.set( 0, 0, - 1 * rearRadius ); | ||
|  | 				this.pointerMesh.material.opacity = POINTER_OPACITY_MIN; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			this._updatePointerVertices( POINTER_REAR_RADIUS_MIN ); | ||
|  | 			this.pointerMesh.position.set( | ||
|  | 				0, | ||
|  | 				0, | ||
|  | 				- 1 * POINTER_REAR_RADIUS_MIN - POINTER_ADVANCE_MAX | ||
|  | 			); | ||
|  | 			this.pointerMesh.material.opacity = POINTER_OPACITY_MAX; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		this.cursorObject.material.opacity = this.pointerMesh.material.opacity; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateMatrixWorld( force ) { | ||
|  | 
 | ||
|  | 		super.updateMatrixWorld( force ); | ||
|  | 		if ( this.pointerGeometry ) { | ||
|  | 
 | ||
|  | 			this._updatePointer(); | ||
|  | 			this._updateRaycaster(); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	isPinched() { | ||
|  | 
 | ||
|  | 		return this.pinched; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	setAttached( attached ) { | ||
|  | 
 | ||
|  | 		this.attached = attached; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	isAttached() { | ||
|  | 
 | ||
|  | 		return this.attached; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	intersectObject( object, recursive = true ) { | ||
|  | 
 | ||
|  | 		if ( this.raycaster ) { | ||
|  | 
 | ||
|  | 			return this.raycaster.intersectObject( object, recursive ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	intersectObjects( objects, recursive = true ) { | ||
|  | 
 | ||
|  | 		if ( this.raycaster ) { | ||
|  | 
 | ||
|  | 			return this.raycaster.intersectObjects( objects, recursive ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	checkIntersections( objects, recursive = false ) { | ||
|  | 
 | ||
|  | 		if ( this.raycaster && ! this.attached ) { | ||
|  | 
 | ||
|  | 			const intersections = this.raycaster.intersectObjects( objects, recursive ); | ||
|  | 			const direction = new THREE.Vector3( 0, 0, - 1 ); | ||
|  | 			if ( intersections.length > 0 ) { | ||
|  | 
 | ||
|  | 				const intersection = intersections[ 0 ]; | ||
|  | 				const distance = intersection.distance; | ||
|  | 				this.cursorObject.position.copy( direction.multiplyScalar( distance ) ); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				this.cursorObject.position.copy( direction.multiplyScalar( CURSOR_MAX_DISTANCE ) ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	setCursor( distance ) { | ||
|  | 
 | ||
|  | 		const direction = new THREE.Vector3( 0, 0, - 1 ); | ||
|  | 		if ( this.raycaster && ! this.attached ) { | ||
|  | 
 | ||
|  | 			this.cursorObject.position.copy( direction.multiplyScalar( distance ) ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	dispose() { | ||
|  | 
 | ||
|  | 		this._onDisconnected(); | ||
|  | 		this.hand.removeEventListener( 'connected', this._onConnected ); | ||
|  | 		this.hand.removeEventListener( 'disconnected', this._onDisconnected ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | export { OculusHandPointerModel }; |