playcanvas
Version:
PlayCanvas WebGL game engine
263 lines (260 loc) • 9.17 kB
JavaScript
import { EventHandler } from '../../core/event-handler.js';
import { platform } from '../../core/platform.js';
import { Vec3 } from '../../core/math/vec3.js';
import { XRHAND_LEFT } from './constants.js';
import { XrFinger } from './xr-finger.js';
import { XrJoint } from './xr-joint.js';
/**
* @import { XrInputSource } from './xr-input-source.js'
* @import { XrManager } from './xr-manager.js'
*/ /**
* @type {string[][]}
*/ var fingerJointIds = [];
var vecA = new Vec3();
var vecB = new Vec3();
var vecC = new Vec3();
if (platform.browser && window.XRHand) {
fingerJointIds = [
[
'thumb-metacarpal',
'thumb-phalanx-proximal',
'thumb-phalanx-distal',
'thumb-tip'
],
[
'index-finger-metacarpal',
'index-finger-phalanx-proximal',
'index-finger-phalanx-intermediate',
'index-finger-phalanx-distal',
'index-finger-tip'
],
[
'middle-finger-metacarpal',
'middle-finger-phalanx-proximal',
'middle-finger-phalanx-intermediate',
'middle-finger-phalanx-distal',
'middle-finger-tip'
],
[
'ring-finger-metacarpal',
'ring-finger-phalanx-proximal',
'ring-finger-phalanx-intermediate',
'ring-finger-phalanx-distal',
'ring-finger-tip'
],
[
'pinky-finger-metacarpal',
'pinky-finger-phalanx-proximal',
'pinky-finger-phalanx-intermediate',
'pinky-finger-phalanx-distal',
'pinky-finger-tip'
]
];
}
/**
* Represents a hand with fingers and joints.
*
* @category XR
*/ class XrHand extends EventHandler {
/**
* @param {XRFrame} frame - XRFrame from requestAnimationFrame callback.
* @ignore
*/ update(frame) {
var xrInputSource = this._inputSource._xrInputSource;
// joints
for(var j = 0; j < this._joints.length; j++){
var joint = this._joints[j];
var jointSpace = xrInputSource.hand.get(joint._id);
if (jointSpace) {
var pose = void 0;
if (frame.session.visibilityState !== 'hidden') {
pose = frame.getJointPose(jointSpace, this._manager._referenceSpace);
}
if (pose) {
joint.update(pose);
if (joint.wrist && !this._tracking) {
this._tracking = true;
this.fire('tracking');
}
} else if (joint.wrist) {
// lost tracking
if (this._tracking) {
this._tracking = false;
this.fire('trackinglost');
}
break;
}
}
}
var j1 = this._jointsById['thumb-metacarpal'];
var j4 = this._jointsById['thumb-tip'];
var j6 = this._jointsById['index-finger-phalanx-proximal'];
var j9 = this._jointsById['index-finger-tip'];
var j16 = this._jointsById['ring-finger-phalanx-proximal'];
var j21 = this._jointsById['pinky-finger-phalanx-proximal'];
// ray
if (j1 && j4 && j6 && j9 && j16 && j21) {
this._inputSource._dirtyRay = true;
// ray origin
// get point between thumb tip and index tip
this._inputSource._rayLocal.origin.lerp(j4._localPosition, j9._localPosition, 0.5);
// ray direction
var jointL = j1;
var jointR = j21;
if (this._inputSource.handedness === XRHAND_LEFT) {
var t = jointL;
jointL = jointR;
jointR = t;
}
// (A) calculate normal vector between 3 joints: wrist, thumb metacarpal, little phalanx proximal
vecA.sub2(jointL._localPosition, this._wrist._localPosition);
vecB.sub2(jointR._localPosition, this._wrist._localPosition);
vecC.cross(vecA, vecB).normalize();
// get point between: index phalanx proximal and right phalanx proximal
vecA.lerp(j6._localPosition, j16._localPosition, 0.5);
// (B) get vector between that point and a wrist
vecA.sub(this._wrist._localPosition).normalize();
// mix normal vector (A) with hand directional vector (B)
this._inputSource._rayLocal.direction.lerp(vecC, vecA, 0.5).normalize();
}
// emulate squeeze events by folding all 4 fingers
var squeezing = this._fingerIsClosed(1) && this._fingerIsClosed(2) && this._fingerIsClosed(3) && this._fingerIsClosed(4);
if (squeezing) {
if (!this._inputSource._squeezing) {
this._inputSource._squeezing = true;
this._inputSource.fire('squeezestart');
this._manager.input.fire('squeezestart', this._inputSource);
}
} else {
if (this._inputSource._squeezing) {
this._inputSource._squeezing = false;
this._inputSource.fire('squeeze');
this._manager.input.fire('squeeze', this._inputSource);
this._inputSource.fire('squeezeend');
this._manager.input.fire('squeezeend', this._inputSource);
}
}
}
/**
* @param {number} index - Finger index.
* @returns {boolean} True if finger is closed and false otherwise.
* @private
*/ _fingerIsClosed(index) {
var finger = this._fingers[index];
vecA.sub2(finger.joints[0]._localPosition, finger.joints[1]._localPosition).normalize();
vecB.sub2(finger.joints[2]._localPosition, finger.joints[3]._localPosition).normalize();
return vecA.dot(vecB) < -0.8;
}
/**
* Returns joint by its XRHand id.
*
* @param {string} id - Id of a joint based on specs ID's in XRHand: https://immersive-web.github.io/webxr-hand-input/#skeleton-joints-section.
* @returns {XrJoint|null} Joint or null if not available.
*/ getJointById(id) {
return this._jointsById[id] || null;
}
/**
* Array of fingers of the hand.
*
* @type {XrFinger[]}
*/ get fingers() {
return this._fingers;
}
/**
* Array of joints in the hand.
*
* @type {XrJoint[]}
*/ get joints() {
return this._joints;
}
/**
* Array of joints that are fingertips.
*
* @type {XrJoint[]}
*/ get tips() {
return this._tips;
}
/**
* Wrist of a hand, or null if it is not available by WebXR underlying system.
*
* @type {XrJoint|null}
*/ get wrist() {
return this._wrist;
}
/**
* True if tracking is available, otherwise tracking might be lost.
*
* @type {boolean}
*/ get tracking() {
return this._tracking;
}
/**
* Represents a hand with fingers and joints.
*
* @param {XrInputSource} inputSource - Input Source that hand is related to.
* @ignore
*/ constructor(inputSource){
super(), /**
* @type {boolean}
* @private
*/ this._tracking = false, /**
* @type {XrFinger[]}
* @private
*/ this._fingers = [], /**
* @type {XrJoint[]}
* @private
*/ this._joints = [], /**
* @type {Object<string, XrJoint>}
* @private
*/ this._jointsById = {}, /**
* @type {XrJoint[]}
* @private
*/ this._tips = [], /**
* @type {XrJoint|null}
* @private
*/ this._wrist = null;
var xrHand = inputSource._xrInputSource.hand;
this._manager = inputSource._manager;
this._inputSource = inputSource;
if (xrHand.get('wrist')) {
var joint = new XrJoint(0, 'wrist', this, null);
this._wrist = joint;
this._joints.push(joint);
this._jointsById.wrist = joint;
}
for(var f = 0; f < fingerJointIds.length; f++){
var finger = new XrFinger(f, this);
for(var j = 0; j < fingerJointIds[f].length; j++){
var jointId = fingerJointIds[f][j];
if (!xrHand.get(jointId)) continue;
var joint1 = new XrJoint(j, jointId, this, finger);
this._joints.push(joint1);
this._jointsById[jointId] = joint1;
if (joint1.tip) {
this._tips.push(joint1);
finger._tip = joint1;
}
finger._joints.push(joint1);
}
}
}
}
/**
* Fired when tracking becomes available.
*
* @event
* @example
* hand.on('tracking', () => {
* console.log('Hand tracking is available');
* });
*/ XrHand.EVENT_TRACKING = 'tracking';
/**
* Fired when tracking is lost.
*
* @event
* @example
* hand.on('trackinglost', () => {
* console.log('Hand tracking is lost');
* });
*/ XrHand.EVENT_TRACKINGLOST = 'trackinglost';
export { XrHand };