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 };
 |