playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
247 lines (246 loc) • 7.93 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
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";
let fingerJointIds = [];
const vecA = new Vec3();
const vecB = new Vec3();
const 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"]
];
}
class XrHand extends EventHandler {
/**
* Represents a hand with fingers and joints.
*
* @param {XrInputSource} inputSource - Input Source that hand is related to.
* @ignore
*/
constructor(inputSource) {
super();
/**
* @type {XrManager}
* @private
*/
__publicField(this, "_manager");
/**
* @type {XrInputSource}
* @private
*/
__publicField(this, "_inputSource");
/** @private */
__publicField(this, "_tracking", false);
/**
* @type {XrFinger[]}
* @private
*/
__publicField(this, "_fingers", []);
/**
* @type {XrJoint[]}
* @private
*/
__publicField(this, "_joints", []);
/**
* @type {Object<string, XrJoint>}
* @private
*/
__publicField(this, "_jointsById", {});
/**
* @type {XrJoint[]}
* @private
*/
__publicField(this, "_tips", []);
/**
* @type {XrJoint|null}
* @private
*/
__publicField(this, "_wrist", null);
const xrHand = inputSource._xrInputSource.hand;
this._manager = inputSource._manager;
this._inputSource = inputSource;
if (xrHand.get("wrist")) {
const joint = new XrJoint(0, "wrist", this, null);
this._wrist = joint;
this._joints.push(joint);
this._jointsById.wrist = joint;
}
for (let f = 0; f < fingerJointIds.length; f++) {
const finger = new XrFinger(f, this);
for (let j = 0; j < fingerJointIds[f].length; j++) {
const jointId = fingerJointIds[f][j];
if (!xrHand.get(jointId)) continue;
const joint = new XrJoint(j, jointId, this, finger);
this._joints.push(joint);
this._jointsById[jointId] = joint;
if (joint.tip) {
this._tips.push(joint);
finger._tip = joint;
}
finger._joints.push(joint);
}
}
}
/**
* @param {XRFrame} frame - XRFrame from requestAnimationFrame callback.
* @ignore
*/
update(frame) {
const xrInputSource = this._inputSource._xrInputSource;
for (let j = 0; j < this._joints.length; j++) {
const joint = this._joints[j];
const jointSpace = xrInputSource.hand.get(joint._id);
if (jointSpace) {
let pose;
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) {
if (this._tracking) {
this._tracking = false;
this.fire("trackinglost");
}
break;
}
}
}
const j1 = this._jointsById["thumb-metacarpal"];
const j4 = this._jointsById["thumb-tip"];
const j6 = this._jointsById["index-finger-phalanx-proximal"];
const j9 = this._jointsById["index-finger-tip"];
const j16 = this._jointsById["ring-finger-phalanx-proximal"];
const j21 = this._jointsById["pinky-finger-phalanx-proximal"];
if (j1 && j4 && j6 && j9 && j16 && j21) {
this._inputSource._dirtyRay = true;
this._inputSource._rayLocal.origin.lerp(j4._localPosition, j9._localPosition, 0.5);
let jointL = j1;
let jointR = j21;
if (this._inputSource.handedness === XRHAND_LEFT) {
const t = jointL;
jointL = jointR;
jointR = t;
}
vecA.sub2(jointL._localPosition, this._wrist._localPosition);
vecB.sub2(jointR._localPosition, this._wrist._localPosition);
vecC.cross(vecA, vecB).normalize();
vecA.lerp(j6._localPosition, j16._localPosition, 0.5);
vecA.sub(this._wrist._localPosition).normalize();
this._inputSource._rayLocal.direction.lerp(vecC, vecA, 0.5).normalize();
}
const 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) {
const 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;
}
}
/**
* Fired when tracking becomes available.
*
* @event
* @example
* hand.on('tracking', () => {
* console.log('Hand tracking is available');
* });
*/
__publicField(XrHand, "EVENT_TRACKING", "tracking");
/**
* Fired when tracking is lost.
*
* @event
* @example
* hand.on('trackinglost', () => {
* console.log('Hand tracking is lost');
* });
*/
__publicField(XrHand, "EVENT_TRACKINGLOST", "trackinglost");
export {
XrHand
};