@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
915 lines • 130 kB
JavaScript
/** This file must only contain pure code and pure imports */
import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js";
import { WebXRFeatureName, WebXRFeaturesManager } from "../webXRFeaturesManager.js";
import { Matrix, Quaternion, Vector3 } from "../../Maths/math.vector.pure.js";
import { Observable } from "../../Misc/observable.pure.js";
import { TransformNode } from "../../Meshes/transformNode.pure.js";
import { Logger } from "../../Misc/logger.js";
/**
* All 83 body joint names as defined by the WebXR Body Tracking specification.
* @see https://immersive-web.github.io/body-tracking/#xrbody-interface
*/
export var WebXRBodyJoint;
(function (WebXRBodyJoint) {
/** The center of the hips / pelvis */
WebXRBodyJoint["HIPS"] = "hips";
/** Lower spine (lumbar) */
WebXRBodyJoint["SPINE_LOWER"] = "spine-lower";
/** Middle spine (thoracic) */
WebXRBodyJoint["SPINE_MIDDLE"] = "spine-middle";
/** Upper spine */
WebXRBodyJoint["SPINE_UPPER"] = "spine-upper";
/** Chest */
WebXRBodyJoint["CHEST"] = "chest";
/** Neck */
WebXRBodyJoint["NECK"] = "neck";
/** Head */
WebXRBodyJoint["HEAD"] = "head";
// ── Left Arm ──────────────────────────────────────────────
/** Left shoulder */
WebXRBodyJoint["LEFT_SHOULDER"] = "left-shoulder";
/** Left scapula */
WebXRBodyJoint["LEFT_SCAPULA"] = "left-scapula";
/** Left upper arm */
WebXRBodyJoint["LEFT_ARM_UPPER"] = "left-arm-upper";
/** Left forearm (lower arm) */
WebXRBodyJoint["LEFT_ARM_LOWER"] = "left-arm-lower";
/** Left hand wrist twist (forearm twist) */
WebXRBodyJoint["LEFT_HAND_WRIST_TWIST"] = "left-hand-wrist-twist";
// ── Right Arm ─────────────────────────────────────────────
/** Right shoulder */
WebXRBodyJoint["RIGHT_SHOULDER"] = "right-shoulder";
/** Right scapula */
WebXRBodyJoint["RIGHT_SCAPULA"] = "right-scapula";
/** Right upper arm */
WebXRBodyJoint["RIGHT_ARM_UPPER"] = "right-arm-upper";
/** Right forearm (lower arm) */
WebXRBodyJoint["RIGHT_ARM_LOWER"] = "right-arm-lower";
/** Right hand wrist twist (forearm twist) */
WebXRBodyJoint["RIGHT_HAND_WRIST_TWIST"] = "right-hand-wrist-twist";
// ── Left Hand ─────────────────────────────────────────────
/** Left palm center */
WebXRBodyJoint["LEFT_HAND_PALM"] = "left-hand-palm";
/** Left wrist */
WebXRBodyJoint["LEFT_HAND_WRIST"] = "left-hand-wrist";
/** Left thumb metacarpal */
WebXRBodyJoint["LEFT_HAND_THUMB_METACARPAL"] = "left-hand-thumb-metacarpal";
/** Left thumb proximal phalanx */
WebXRBodyJoint["LEFT_HAND_THUMB_PHALANX_PROXIMAL"] = "left-hand-thumb-phalanx-proximal";
/** Left thumb distal phalanx */
WebXRBodyJoint["LEFT_HAND_THUMB_PHALANX_DISTAL"] = "left-hand-thumb-phalanx-distal";
/** Left thumb tip */
WebXRBodyJoint["LEFT_HAND_THUMB_TIP"] = "left-hand-thumb-tip";
/** Left index finger metacarpal */
WebXRBodyJoint["LEFT_HAND_INDEX_METACARPAL"] = "left-hand-index-metacarpal";
/** Left index finger proximal phalanx */
WebXRBodyJoint["LEFT_HAND_INDEX_PHALANX_PROXIMAL"] = "left-hand-index-phalanx-proximal";
/** Left index finger intermediate phalanx */
WebXRBodyJoint["LEFT_HAND_INDEX_PHALANX_INTERMEDIATE"] = "left-hand-index-phalanx-intermediate";
/** Left index finger distal phalanx */
WebXRBodyJoint["LEFT_HAND_INDEX_PHALANX_DISTAL"] = "left-hand-index-phalanx-distal";
/** Left index finger tip */
WebXRBodyJoint["LEFT_HAND_INDEX_TIP"] = "left-hand-index-tip";
/** Left middle finger metacarpal */
WebXRBodyJoint["LEFT_HAND_MIDDLE_METACARPAL"] = "left-hand-middle-metacarpal";
/** Left middle finger proximal phalanx */
WebXRBodyJoint["LEFT_HAND_MIDDLE_PHALANX_PROXIMAL"] = "left-hand-middle-phalanx-proximal";
/** Left middle finger intermediate phalanx */
WebXRBodyJoint["LEFT_HAND_MIDDLE_PHALANX_INTERMEDIATE"] = "left-hand-middle-phalanx-intermediate";
/** Left middle finger distal phalanx */
WebXRBodyJoint["LEFT_HAND_MIDDLE_PHALANX_DISTAL"] = "left-hand-middle-phalanx-distal";
/** Left middle finger tip */
WebXRBodyJoint["LEFT_HAND_MIDDLE_TIP"] = "left-hand-middle-tip";
/** Left ring finger metacarpal */
WebXRBodyJoint["LEFT_HAND_RING_METACARPAL"] = "left-hand-ring-metacarpal";
/** Left ring finger proximal phalanx */
WebXRBodyJoint["LEFT_HAND_RING_PHALANX_PROXIMAL"] = "left-hand-ring-phalanx-proximal";
/** Left ring finger intermediate phalanx */
WebXRBodyJoint["LEFT_HAND_RING_PHALANX_INTERMEDIATE"] = "left-hand-ring-phalanx-intermediate";
/** Left ring finger distal phalanx */
WebXRBodyJoint["LEFT_HAND_RING_PHALANX_DISTAL"] = "left-hand-ring-phalanx-distal";
/** Left ring finger tip */
WebXRBodyJoint["LEFT_HAND_RING_TIP"] = "left-hand-ring-tip";
/** Left little finger metacarpal */
WebXRBodyJoint["LEFT_HAND_LITTLE_METACARPAL"] = "left-hand-little-metacarpal";
/** Left little finger proximal phalanx */
WebXRBodyJoint["LEFT_HAND_LITTLE_PHALANX_PROXIMAL"] = "left-hand-little-phalanx-proximal";
/** Left little finger intermediate phalanx */
WebXRBodyJoint["LEFT_HAND_LITTLE_PHALANX_INTERMEDIATE"] = "left-hand-little-phalanx-intermediate";
/** Left little finger distal phalanx */
WebXRBodyJoint["LEFT_HAND_LITTLE_PHALANX_DISTAL"] = "left-hand-little-phalanx-distal";
/** Left little finger tip */
WebXRBodyJoint["LEFT_HAND_LITTLE_TIP"] = "left-hand-little-tip";
// ── Right Hand ────────────────────────────────────────────
/** Right palm center */
WebXRBodyJoint["RIGHT_HAND_PALM"] = "right-hand-palm";
/** Right wrist */
WebXRBodyJoint["RIGHT_HAND_WRIST"] = "right-hand-wrist";
/** Right thumb metacarpal */
WebXRBodyJoint["RIGHT_HAND_THUMB_METACARPAL"] = "right-hand-thumb-metacarpal";
/** Right thumb proximal phalanx */
WebXRBodyJoint["RIGHT_HAND_THUMB_PHALANX_PROXIMAL"] = "right-hand-thumb-phalanx-proximal";
/** Right thumb distal phalanx */
WebXRBodyJoint["RIGHT_HAND_THUMB_PHALANX_DISTAL"] = "right-hand-thumb-phalanx-distal";
/** Right thumb tip */
WebXRBodyJoint["RIGHT_HAND_THUMB_TIP"] = "right-hand-thumb-tip";
/** Right index finger metacarpal */
WebXRBodyJoint["RIGHT_HAND_INDEX_METACARPAL"] = "right-hand-index-metacarpal";
/** Right index finger proximal phalanx */
WebXRBodyJoint["RIGHT_HAND_INDEX_PHALANX_PROXIMAL"] = "right-hand-index-phalanx-proximal";
/** Right index finger intermediate phalanx */
WebXRBodyJoint["RIGHT_HAND_INDEX_PHALANX_INTERMEDIATE"] = "right-hand-index-phalanx-intermediate";
/** Right index finger distal phalanx */
WebXRBodyJoint["RIGHT_HAND_INDEX_PHALANX_DISTAL"] = "right-hand-index-phalanx-distal";
/** Right index finger tip */
WebXRBodyJoint["RIGHT_HAND_INDEX_TIP"] = "right-hand-index-tip";
/** Right middle finger metacarpal */
WebXRBodyJoint["RIGHT_HAND_MIDDLE_METACARPAL"] = "right-hand-middle-metacarpal";
/** Right middle finger proximal phalanx */
WebXRBodyJoint["RIGHT_HAND_MIDDLE_PHALANX_PROXIMAL"] = "right-hand-middle-phalanx-proximal";
/** Right middle finger intermediate phalanx */
WebXRBodyJoint["RIGHT_HAND_MIDDLE_PHALANX_INTERMEDIATE"] = "right-hand-middle-phalanx-intermediate";
/** Right middle finger distal phalanx */
WebXRBodyJoint["RIGHT_HAND_MIDDLE_PHALANX_DISTAL"] = "right-hand-middle-phalanx-distal";
/** Right middle finger tip */
WebXRBodyJoint["RIGHT_HAND_MIDDLE_TIP"] = "right-hand-middle-tip";
/** Right ring finger metacarpal */
WebXRBodyJoint["RIGHT_HAND_RING_METACARPAL"] = "right-hand-ring-metacarpal";
/** Right ring finger proximal phalanx */
WebXRBodyJoint["RIGHT_HAND_RING_PHALANX_PROXIMAL"] = "right-hand-ring-phalanx-proximal";
/** Right ring finger intermediate phalanx */
WebXRBodyJoint["RIGHT_HAND_RING_PHALANX_INTERMEDIATE"] = "right-hand-ring-phalanx-intermediate";
/** Right ring finger distal phalanx */
WebXRBodyJoint["RIGHT_HAND_RING_PHALANX_DISTAL"] = "right-hand-ring-phalanx-distal";
/** Right ring finger tip */
WebXRBodyJoint["RIGHT_HAND_RING_TIP"] = "right-hand-ring-tip";
/** Right little finger metacarpal */
WebXRBodyJoint["RIGHT_HAND_LITTLE_METACARPAL"] = "right-hand-little-metacarpal";
/** Right little finger proximal phalanx */
WebXRBodyJoint["RIGHT_HAND_LITTLE_PHALANX_PROXIMAL"] = "right-hand-little-phalanx-proximal";
/** Right little finger intermediate phalanx */
WebXRBodyJoint["RIGHT_HAND_LITTLE_PHALANX_INTERMEDIATE"] = "right-hand-little-phalanx-intermediate";
/** Right little finger distal phalanx */
WebXRBodyJoint["RIGHT_HAND_LITTLE_PHALANX_DISTAL"] = "right-hand-little-phalanx-distal";
/** Right little finger tip */
WebXRBodyJoint["RIGHT_HAND_LITTLE_TIP"] = "right-hand-little-tip";
// ── Left Leg / Foot ───────────────────────────────────────
/** Left upper leg (thigh) */
WebXRBodyJoint["LEFT_UPPER_LEG"] = "left-upper-leg";
/** Left lower leg (shin) */
WebXRBodyJoint["LEFT_LOWER_LEG"] = "left-lower-leg";
/** Left foot ankle twist */
WebXRBodyJoint["LEFT_FOOT_ANKLE_TWIST"] = "left-foot-ankle-twist";
/** Left foot ankle */
WebXRBodyJoint["LEFT_FOOT_ANKLE"] = "left-foot-ankle";
/** Left foot subtalar */
WebXRBodyJoint["LEFT_FOOT_SUBTALAR"] = "left-foot-subtalar";
/** Left foot transverse */
WebXRBodyJoint["LEFT_FOOT_TRANSVERSE"] = "left-foot-transverse";
/** Left foot ball */
WebXRBodyJoint["LEFT_FOOT_BALL"] = "left-foot-ball";
// ── Right Leg / Foot ──────────────────────────────────────
/** Right upper leg (thigh) */
WebXRBodyJoint["RIGHT_UPPER_LEG"] = "right-upper-leg";
/** Right lower leg (shin) */
WebXRBodyJoint["RIGHT_LOWER_LEG"] = "right-lower-leg";
/** Right foot ankle twist */
WebXRBodyJoint["RIGHT_FOOT_ANKLE_TWIST"] = "right-foot-ankle-twist";
/** Right foot ankle */
WebXRBodyJoint["RIGHT_FOOT_ANKLE"] = "right-foot-ankle";
/** Right foot subtalar */
WebXRBodyJoint["RIGHT_FOOT_SUBTALAR"] = "right-foot-subtalar";
/** Right foot transverse */
WebXRBodyJoint["RIGHT_FOOT_TRANSVERSE"] = "right-foot-transverse";
/** Right foot ball */
WebXRBodyJoint["RIGHT_FOOT_BALL"] = "right-foot-ball";
})(WebXRBodyJoint || (WebXRBodyJoint = {}));
/**
* The ordered array of all 83 body joints, matching the iteration order defined
* by the WebXR Body Tracking specification.
* @see https://immersive-web.github.io/body-tracking/#xrbody-interface
*/
const BodyJointReferenceArray = [
"hips" /* WebXRBodyJoint.HIPS */,
"spine-lower" /* WebXRBodyJoint.SPINE_LOWER */,
"spine-middle" /* WebXRBodyJoint.SPINE_MIDDLE */,
"spine-upper" /* WebXRBodyJoint.SPINE_UPPER */,
"chest" /* WebXRBodyJoint.CHEST */,
"neck" /* WebXRBodyJoint.NECK */,
"head" /* WebXRBodyJoint.HEAD */,
"left-shoulder" /* WebXRBodyJoint.LEFT_SHOULDER */,
"left-scapula" /* WebXRBodyJoint.LEFT_SCAPULA */,
"left-arm-upper" /* WebXRBodyJoint.LEFT_ARM_UPPER */,
"left-arm-lower" /* WebXRBodyJoint.LEFT_ARM_LOWER */,
"left-hand-wrist-twist" /* WebXRBodyJoint.LEFT_HAND_WRIST_TWIST */,
"right-shoulder" /* WebXRBodyJoint.RIGHT_SHOULDER */,
"right-scapula" /* WebXRBodyJoint.RIGHT_SCAPULA */,
"right-arm-upper" /* WebXRBodyJoint.RIGHT_ARM_UPPER */,
"right-arm-lower" /* WebXRBodyJoint.RIGHT_ARM_LOWER */,
"right-hand-wrist-twist" /* WebXRBodyJoint.RIGHT_HAND_WRIST_TWIST */,
"left-hand-palm" /* WebXRBodyJoint.LEFT_HAND_PALM */,
"left-hand-wrist" /* WebXRBodyJoint.LEFT_HAND_WRIST */,
"left-hand-thumb-metacarpal" /* WebXRBodyJoint.LEFT_HAND_THUMB_METACARPAL */,
"left-hand-thumb-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_THUMB_PHALANX_PROXIMAL */,
"left-hand-thumb-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_THUMB_PHALANX_DISTAL */,
"left-hand-thumb-tip" /* WebXRBodyJoint.LEFT_HAND_THUMB_TIP */,
"left-hand-index-metacarpal" /* WebXRBodyJoint.LEFT_HAND_INDEX_METACARPAL */,
"left-hand-index-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_PROXIMAL */,
"left-hand-index-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_INTERMEDIATE */,
"left-hand-index-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_DISTAL */,
"left-hand-index-tip" /* WebXRBodyJoint.LEFT_HAND_INDEX_TIP */,
"left-hand-middle-metacarpal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_METACARPAL */,
"left-hand-middle-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_PROXIMAL */,
"left-hand-middle-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_INTERMEDIATE */,
"left-hand-middle-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_DISTAL */,
"left-hand-middle-tip" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_TIP */,
"left-hand-ring-metacarpal" /* WebXRBodyJoint.LEFT_HAND_RING_METACARPAL */,
"left-hand-ring-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_PROXIMAL */,
"left-hand-ring-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_INTERMEDIATE */,
"left-hand-ring-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_DISTAL */,
"left-hand-ring-tip" /* WebXRBodyJoint.LEFT_HAND_RING_TIP */,
"left-hand-little-metacarpal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_METACARPAL */,
"left-hand-little-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_PROXIMAL */,
"left-hand-little-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_INTERMEDIATE */,
"left-hand-little-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_DISTAL */,
"left-hand-little-tip" /* WebXRBodyJoint.LEFT_HAND_LITTLE_TIP */,
"right-hand-palm" /* WebXRBodyJoint.RIGHT_HAND_PALM */,
"right-hand-wrist" /* WebXRBodyJoint.RIGHT_HAND_WRIST */,
"right-hand-thumb-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_METACARPAL */,
"right-hand-thumb-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_PHALANX_PROXIMAL */,
"right-hand-thumb-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_PHALANX_DISTAL */,
"right-hand-thumb-tip" /* WebXRBodyJoint.RIGHT_HAND_THUMB_TIP */,
"right-hand-index-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_METACARPAL */,
"right-hand-index-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_PROXIMAL */,
"right-hand-index-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_INTERMEDIATE */,
"right-hand-index-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_DISTAL */,
"right-hand-index-tip" /* WebXRBodyJoint.RIGHT_HAND_INDEX_TIP */,
"right-hand-middle-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_METACARPAL */,
"right-hand-middle-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_PROXIMAL */,
"right-hand-middle-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_INTERMEDIATE */,
"right-hand-middle-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_DISTAL */,
"right-hand-middle-tip" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_TIP */,
"right-hand-ring-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_RING_METACARPAL */,
"right-hand-ring-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_PROXIMAL */,
"right-hand-ring-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_INTERMEDIATE */,
"right-hand-ring-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_DISTAL */,
"right-hand-ring-tip" /* WebXRBodyJoint.RIGHT_HAND_RING_TIP */,
"right-hand-little-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_METACARPAL */,
"right-hand-little-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_PROXIMAL */,
"right-hand-little-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_INTERMEDIATE */,
"right-hand-little-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_DISTAL */,
"right-hand-little-tip" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_TIP */,
"left-upper-leg" /* WebXRBodyJoint.LEFT_UPPER_LEG */,
"left-lower-leg" /* WebXRBodyJoint.LEFT_LOWER_LEG */,
"left-foot-ankle-twist" /* WebXRBodyJoint.LEFT_FOOT_ANKLE_TWIST */,
"left-foot-ankle" /* WebXRBodyJoint.LEFT_FOOT_ANKLE */,
"left-foot-subtalar" /* WebXRBodyJoint.LEFT_FOOT_SUBTALAR */,
"left-foot-transverse" /* WebXRBodyJoint.LEFT_FOOT_TRANSVERSE */,
"left-foot-ball" /* WebXRBodyJoint.LEFT_FOOT_BALL */,
"right-upper-leg" /* WebXRBodyJoint.RIGHT_UPPER_LEG */,
"right-lower-leg" /* WebXRBodyJoint.RIGHT_LOWER_LEG */,
"right-foot-ankle-twist" /* WebXRBodyJoint.RIGHT_FOOT_ANKLE_TWIST */,
"right-foot-ankle" /* WebXRBodyJoint.RIGHT_FOOT_ANKLE */,
"right-foot-subtalar" /* WebXRBodyJoint.RIGHT_FOOT_SUBTALAR */,
"right-foot-transverse" /* WebXRBodyJoint.RIGHT_FOOT_TRANSVERSE */,
"right-foot-ball" /* WebXRBodyJoint.RIGHT_FOOT_BALL */,
];
/**
* Reverse lookup: {@link WebXRBodyJoint} → index in {@link BodyJointReferenceArray}.
* Used for O(1) name-based access where previously `indexOf` was called in hot paths.
*/
const BodyJointNameToIndex = /*#__PURE__*/ new Map(BodyJointReferenceArray.map((j, i) => [j, i]));
/**
* The total number of joints in the body tracking spec.
* The XRBody size attribute MUST return this value.
*/
const BODY_JOINT_COUNT = 83;
/**
* Parent index for each joint in {@link BodyJointReferenceArray} order.
* -1 means "root" (no parent). Used to convert world-space XR poses to
* local-space transforms suitable for skeleton bones.
*
* Hierarchy follows the WebXR Body Tracking specification and standard
* humanoid anatomy.
*/
// prettier-ignore
export const BodyJointParentIndex = [
// 0: hips (root)
-1,
// 1: spine-lower → hips
0,
// 2: spine-middle → spine-lower
1,
// 3: spine-upper → spine-middle
2,
// 4: chest → spine-upper
3,
// 5: neck → chest
4,
// 6: head → neck
5,
// Left arm (7-11)
4, // 7: left-shoulder → chest
7, // 8: left-scapula → left-shoulder
8, // 9: left-arm-upper → left-scapula
9, // 10: left-arm-lower → left-arm-upper
10, // 11: left-hand-wrist-twist → left-arm-lower
// Right arm (12-16)
4, // 12: right-shoulder → chest
12, // 13: right-scapula → right-shoulder
13, // 14: right-arm-upper → right-scapula
14, // 15: right-arm-lower → right-arm-upper
15, // 16: right-hand-wrist-twist → right-arm-lower
// Left hand (17-42)
11, // 17: left-hand-palm → left-hand-wrist-twist
11, // 18: left-hand-wrist → left-hand-wrist-twist
18, // 19: left-hand-thumb-metacarpal → left-hand-wrist
19, // 20: left-hand-thumb-phalanx-proximal
20, // 21: left-hand-thumb-phalanx-distal
21, // 22: left-hand-thumb-tip
18, // 23: left-hand-index-metacarpal → left-hand-wrist
23, // 24: left-hand-index-phalanx-proximal
24, // 25: left-hand-index-phalanx-intermediate
25, // 26: left-hand-index-phalanx-distal
26, // 27: left-hand-index-tip
18, // 28: left-hand-middle-metacarpal → left-hand-wrist
28, // 29: left-hand-middle-phalanx-proximal
29, // 30: left-hand-middle-phalanx-intermediate
30, // 31: left-hand-middle-phalanx-distal
31, // 32: left-hand-middle-tip
18, // 33: left-hand-ring-metacarpal → left-hand-wrist
33, // 34: left-hand-ring-phalanx-proximal
34, // 35: left-hand-ring-phalanx-intermediate
35, // 36: left-hand-ring-phalanx-distal
36, // 37: left-hand-ring-tip
18, // 38: left-hand-little-metacarpal → left-hand-wrist
38, // 39: left-hand-little-phalanx-proximal
39, // 40: left-hand-little-phalanx-intermediate
40, // 41: left-hand-little-phalanx-distal
41, // 42: left-hand-little-tip
// Right hand (43-68)
16, // 43: right-hand-palm → right-hand-wrist-twist
16, // 44: right-hand-wrist → right-hand-wrist-twist
44, // 45: right-hand-thumb-metacarpal → right-hand-wrist
45, // 46: right-hand-thumb-phalanx-proximal
46, // 47: right-hand-thumb-phalanx-distal
47, // 48: right-hand-thumb-tip
44, // 49: right-hand-index-metacarpal → right-hand-wrist
49, // 50: right-hand-index-phalanx-proximal
50, // 51: right-hand-index-phalanx-intermediate
51, // 52: right-hand-index-phalanx-distal
52, // 53: right-hand-index-tip
44, // 54: right-hand-middle-metacarpal → right-hand-wrist
54, // 55: right-hand-middle-phalanx-proximal
55, // 56: right-hand-middle-phalanx-intermediate
56, // 57: right-hand-middle-phalanx-distal
57, // 58: right-hand-middle-tip
44, // 59: right-hand-ring-metacarpal → right-hand-wrist
59, // 60: right-hand-ring-phalanx-proximal
60, // 61: right-hand-ring-phalanx-intermediate
61, // 62: right-hand-ring-phalanx-distal
62, // 63: right-hand-ring-tip
44, // 64: right-hand-little-metacarpal → right-hand-wrist
64, // 65: right-hand-little-phalanx-proximal
65, // 66: right-hand-little-phalanx-intermediate
66, // 67: right-hand-little-phalanx-distal
67, // 68: right-hand-little-tip
// Left leg / foot (69-75)
0, // 69: left-upper-leg → hips
69, // 70: left-lower-leg → left-upper-leg
70, // 71: left-foot-ankle-twist → left-lower-leg
71, // 72: left-foot-ankle → left-foot-ankle-twist
72, // 73: left-foot-subtalar → left-foot-ankle
73, // 74: left-foot-transverse → left-foot-subtalar
74, // 75: left-foot-ball → left-foot-transverse
// Right leg / foot (76-82)
0, // 76: right-upper-leg → hips
76, // 77: right-lower-leg → right-upper-leg
77, // 78: right-foot-ankle-twist → right-lower-leg
78, // 79: right-foot-ankle → right-foot-ankle-twist
79, // 80: right-foot-subtalar → right-foot-ankle
80, // 81: right-foot-transverse → right-foot-subtalar
81, // 82: right-foot-ball → right-foot-transverse
];
/**
* Logical body parts for convenient grouping of joints.
*/
export var BodyPart;
(function (BodyPart) {
/** Torso / spine (hips through head) */
BodyPart["TORSO"] = "torso";
/** Left arm (shoulder through wrist twist) */
BodyPart["LEFT_ARM"] = "left-arm";
/** Right arm (shoulder through wrist twist) */
BodyPart["RIGHT_ARM"] = "right-arm";
/** Left hand (palm through finger tips) */
BodyPart["LEFT_HAND"] = "left-hand";
/** Right hand (palm through finger tips) */
BodyPart["RIGHT_HAND"] = "right-hand";
/** Left leg (upper leg through foot ball) */
BodyPart["LEFT_LEG"] = "left-leg";
/** Right leg (upper leg through foot ball) */
BodyPart["RIGHT_LEG"] = "right-leg";
})(BodyPart || (BodyPart = {}));
/**
* Which body joints belong to each body part.
*/
const BodyPartsDefinition = {
["torso" /* BodyPart.TORSO */]: [
"hips" /* WebXRBodyJoint.HIPS */,
"spine-lower" /* WebXRBodyJoint.SPINE_LOWER */,
"spine-middle" /* WebXRBodyJoint.SPINE_MIDDLE */,
"spine-upper" /* WebXRBodyJoint.SPINE_UPPER */,
"chest" /* WebXRBodyJoint.CHEST */,
"neck" /* WebXRBodyJoint.NECK */,
"head" /* WebXRBodyJoint.HEAD */,
],
["left-arm" /* BodyPart.LEFT_ARM */]: [
"left-shoulder" /* WebXRBodyJoint.LEFT_SHOULDER */,
"left-scapula" /* WebXRBodyJoint.LEFT_SCAPULA */,
"left-arm-upper" /* WebXRBodyJoint.LEFT_ARM_UPPER */,
"left-arm-lower" /* WebXRBodyJoint.LEFT_ARM_LOWER */,
"left-hand-wrist-twist" /* WebXRBodyJoint.LEFT_HAND_WRIST_TWIST */,
],
["right-arm" /* BodyPart.RIGHT_ARM */]: [
"right-shoulder" /* WebXRBodyJoint.RIGHT_SHOULDER */,
"right-scapula" /* WebXRBodyJoint.RIGHT_SCAPULA */,
"right-arm-upper" /* WebXRBodyJoint.RIGHT_ARM_UPPER */,
"right-arm-lower" /* WebXRBodyJoint.RIGHT_ARM_LOWER */,
"right-hand-wrist-twist" /* WebXRBodyJoint.RIGHT_HAND_WRIST_TWIST */,
],
["left-hand" /* BodyPart.LEFT_HAND */]: [
"left-hand-palm" /* WebXRBodyJoint.LEFT_HAND_PALM */,
"left-hand-wrist" /* WebXRBodyJoint.LEFT_HAND_WRIST */,
"left-hand-thumb-metacarpal" /* WebXRBodyJoint.LEFT_HAND_THUMB_METACARPAL */,
"left-hand-thumb-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_THUMB_PHALANX_PROXIMAL */,
"left-hand-thumb-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_THUMB_PHALANX_DISTAL */,
"left-hand-thumb-tip" /* WebXRBodyJoint.LEFT_HAND_THUMB_TIP */,
"left-hand-index-metacarpal" /* WebXRBodyJoint.LEFT_HAND_INDEX_METACARPAL */,
"left-hand-index-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_PROXIMAL */,
"left-hand-index-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_INTERMEDIATE */,
"left-hand-index-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_INDEX_PHALANX_DISTAL */,
"left-hand-index-tip" /* WebXRBodyJoint.LEFT_HAND_INDEX_TIP */,
"left-hand-middle-metacarpal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_METACARPAL */,
"left-hand-middle-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_PROXIMAL */,
"left-hand-middle-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_INTERMEDIATE */,
"left-hand-middle-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_PHALANX_DISTAL */,
"left-hand-middle-tip" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_TIP */,
"left-hand-ring-metacarpal" /* WebXRBodyJoint.LEFT_HAND_RING_METACARPAL */,
"left-hand-ring-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_PROXIMAL */,
"left-hand-ring-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_INTERMEDIATE */,
"left-hand-ring-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_RING_PHALANX_DISTAL */,
"left-hand-ring-tip" /* WebXRBodyJoint.LEFT_HAND_RING_TIP */,
"left-hand-little-metacarpal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_METACARPAL */,
"left-hand-little-phalanx-proximal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_PROXIMAL */,
"left-hand-little-phalanx-intermediate" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_INTERMEDIATE */,
"left-hand-little-phalanx-distal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_PHALANX_DISTAL */,
"left-hand-little-tip" /* WebXRBodyJoint.LEFT_HAND_LITTLE_TIP */,
],
["right-hand" /* BodyPart.RIGHT_HAND */]: [
"right-hand-palm" /* WebXRBodyJoint.RIGHT_HAND_PALM */,
"right-hand-wrist" /* WebXRBodyJoint.RIGHT_HAND_WRIST */,
"right-hand-thumb-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_METACARPAL */,
"right-hand-thumb-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_PHALANX_PROXIMAL */,
"right-hand-thumb-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_THUMB_PHALANX_DISTAL */,
"right-hand-thumb-tip" /* WebXRBodyJoint.RIGHT_HAND_THUMB_TIP */,
"right-hand-index-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_METACARPAL */,
"right-hand-index-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_PROXIMAL */,
"right-hand-index-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_INTERMEDIATE */,
"right-hand-index-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_PHALANX_DISTAL */,
"right-hand-index-tip" /* WebXRBodyJoint.RIGHT_HAND_INDEX_TIP */,
"right-hand-middle-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_METACARPAL */,
"right-hand-middle-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_PROXIMAL */,
"right-hand-middle-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_INTERMEDIATE */,
"right-hand-middle-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_PHALANX_DISTAL */,
"right-hand-middle-tip" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_TIP */,
"right-hand-ring-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_RING_METACARPAL */,
"right-hand-ring-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_PROXIMAL */,
"right-hand-ring-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_INTERMEDIATE */,
"right-hand-ring-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_RING_PHALANX_DISTAL */,
"right-hand-ring-tip" /* WebXRBodyJoint.RIGHT_HAND_RING_TIP */,
"right-hand-little-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_METACARPAL */,
"right-hand-little-phalanx-proximal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_PROXIMAL */,
"right-hand-little-phalanx-intermediate" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_INTERMEDIATE */,
"right-hand-little-phalanx-distal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_PHALANX_DISTAL */,
"right-hand-little-tip" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_TIP */,
],
["left-leg" /* BodyPart.LEFT_LEG */]: [
"left-upper-leg" /* WebXRBodyJoint.LEFT_UPPER_LEG */,
"left-lower-leg" /* WebXRBodyJoint.LEFT_LOWER_LEG */,
"left-foot-ankle-twist" /* WebXRBodyJoint.LEFT_FOOT_ANKLE_TWIST */,
"left-foot-ankle" /* WebXRBodyJoint.LEFT_FOOT_ANKLE */,
"left-foot-subtalar" /* WebXRBodyJoint.LEFT_FOOT_SUBTALAR */,
"left-foot-transverse" /* WebXRBodyJoint.LEFT_FOOT_TRANSVERSE */,
"left-foot-ball" /* WebXRBodyJoint.LEFT_FOOT_BALL */,
],
["right-leg" /* BodyPart.RIGHT_LEG */]: [
"right-upper-leg" /* WebXRBodyJoint.RIGHT_UPPER_LEG */,
"right-lower-leg" /* WebXRBodyJoint.RIGHT_LOWER_LEG */,
"right-foot-ankle-twist" /* WebXRBodyJoint.RIGHT_FOOT_ANKLE_TWIST */,
"right-foot-ankle" /* WebXRBodyJoint.RIGHT_FOOT_ANKLE */,
"right-foot-subtalar" /* WebXRBodyJoint.RIGHT_FOOT_SUBTALAR */,
"right-foot-transverse" /* WebXRBodyJoint.RIGHT_FOOT_TRANSVERSE */,
"right-foot-ball" /* WebXRBodyJoint.RIGHT_FOOT_BALL */,
],
};
// ────────────────────────────────────────────────────────────────────────────
// Built-in rig mappings
// ────────────────────────────────────────────────────────────────────────────
/**
* Default rig mapping for Mixamo-rigged humanoid characters.
*
* Maps each supported {@link WebXRBodyJoint} to the corresponding Mixamo bone
* name, **without** the `mixamorig:` prefix. When the feature applies this
* mapping, it auto-detects whether the skeleton uses the `mixamorig:` prefix
* and prepends it as needed, so the same table works for both prefixed and
* unprefixed exports.
*
* @example
* ```ts
* xr.featuresManager.enableFeature(WebXRFeatureName.BODY_TRACKING, "latest", {
* bodyMesh: myMixamoMesh,
* isMixamoModel: true,
* });
* ```
*
* Or, if you want to extend or customize it:
* ```ts
* import { MixamoRigMapping } from "@babylonjs/core";
* const rigMapping: XRBodyMeshRigMapping = { ...MixamoRigMapping, [WebXRBodyJoint.NECK]: "MyNeckBone" };
* ```
*/
export const MixamoRigMapping = {
["hips" /* WebXRBodyJoint.HIPS */]: "Hips",
["spine-lower" /* WebXRBodyJoint.SPINE_LOWER */]: "Spine",
["spine-middle" /* WebXRBodyJoint.SPINE_MIDDLE */]: "Spine1",
["spine-upper" /* WebXRBodyJoint.SPINE_UPPER */]: "Spine2",
["neck" /* WebXRBodyJoint.NECK */]: "Neck",
["head" /* WebXRBodyJoint.HEAD */]: "Head",
["left-shoulder" /* WebXRBodyJoint.LEFT_SHOULDER */]: "LeftShoulder",
["left-arm-upper" /* WebXRBodyJoint.LEFT_ARM_UPPER */]: "LeftArm",
["left-arm-lower" /* WebXRBodyJoint.LEFT_ARM_LOWER */]: "LeftForeArm",
["left-hand-wrist" /* WebXRBodyJoint.LEFT_HAND_WRIST */]: "LeftHand",
["right-shoulder" /* WebXRBodyJoint.RIGHT_SHOULDER */]: "RightShoulder",
["right-arm-upper" /* WebXRBodyJoint.RIGHT_ARM_UPPER */]: "RightArm",
["right-arm-lower" /* WebXRBodyJoint.RIGHT_ARM_LOWER */]: "RightForeArm",
["right-hand-wrist" /* WebXRBodyJoint.RIGHT_HAND_WRIST */]: "RightHand",
["left-upper-leg" /* WebXRBodyJoint.LEFT_UPPER_LEG */]: "LeftUpLeg",
["left-lower-leg" /* WebXRBodyJoint.LEFT_LOWER_LEG */]: "LeftLeg",
["left-foot-ankle" /* WebXRBodyJoint.LEFT_FOOT_ANKLE */]: "LeftFoot",
["left-foot-ball" /* WebXRBodyJoint.LEFT_FOOT_BALL */]: "LeftToeBase",
["right-upper-leg" /* WebXRBodyJoint.RIGHT_UPPER_LEG */]: "RightUpLeg",
["right-lower-leg" /* WebXRBodyJoint.RIGHT_LOWER_LEG */]: "RightLeg",
["right-foot-ankle" /* WebXRBodyJoint.RIGHT_FOOT_ANKLE */]: "RightFoot",
["right-foot-ball" /* WebXRBodyJoint.RIGHT_FOOT_BALL */]: "RightToeBase",
};
/**
* Default aim-child overrides for Mixamo-rigged humanoids.
*
* Redirects the short / noisy XR spine segments to longer, stable ones so that
* {@link IWebXRBodyTrackingOptions.useBoneOrientationOffsets} produces clean
* torso rotations. In WebXR data, `hips`→`spine-lower` is typically only ~1 cm
* apart — too short to give a stable aim direction — so we reroute Mixamo's
* Hips/Spine/Spine1 bones to aim at `spine-upper` / `neck` instead.
*/
export const MixamoAimChildOverrides = {
["hips" /* WebXRBodyJoint.HIPS */]: "spine-upper" /* WebXRBodyJoint.SPINE_UPPER */,
["spine-lower" /* WebXRBodyJoint.SPINE_LOWER */]: "neck" /* WebXRBodyJoint.NECK */,
["spine-middle" /* WebXRBodyJoint.SPINE_MIDDLE */]: "neck" /* WebXRBodyJoint.NECK */,
// Hands have no mapped finger descendants on a typical Mixamo rig. Aim
// them at the tracked Middle-finger metacarpal joint to give the wrist
// a real orientation reference instead of relying on Meta's raw wrist
// rotation (which tends to flop around when the cameras can't resolve
// the hand pose).
["left-hand-wrist" /* WebXRBodyJoint.LEFT_HAND_WRIST */]: "left-hand-middle-metacarpal" /* WebXRBodyJoint.LEFT_HAND_MIDDLE_METACARPAL */,
["right-hand-wrist" /* WebXRBodyJoint.RIGHT_HAND_WRIST */]: "right-hand-middle-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_MIDDLE_METACARPAL */,
};
const HandTwistReferenceJoints = {
["left-hand-wrist" /* WebXRBodyJoint.LEFT_HAND_WRIST */]: {
first: "left-hand-index-metacarpal" /* WebXRBodyJoint.LEFT_HAND_INDEX_METACARPAL */,
second: "left-hand-little-metacarpal" /* WebXRBodyJoint.LEFT_HAND_LITTLE_METACARPAL */,
},
["right-hand-wrist" /* WebXRBodyJoint.RIGHT_HAND_WRIST */]: {
first: "right-hand-index-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_INDEX_METACARPAL */,
second: "right-hand-little-metacarpal" /* WebXRBodyJoint.RIGHT_HAND_LITTLE_METACARPAL */,
},
};
/**
* Resolve the Mixamo rig mapping for a given body mesh, auto-detecting the
* `mixamorig:` bone-name prefix. Falls back to the unprefixed names.
* @param bodyMesh The rigged Mixamo body mesh.
* @returns An {@link XRBodyMeshRigMapping} whose bone names include the
* detected prefix (if any).
* @internal
*/
export function _ResolveMixamoRigMapping(bodyMesh) {
let skeleton = bodyMesh.skeleton;
if (!skeleton) {
for (const child of bodyMesh.getChildMeshes()) {
if (child.skeleton) {
skeleton = child.skeleton;
break;
}
}
}
let prefix = "";
if (skeleton) {
// Look for the Hips bone to determine whether bones use the mixamorig: prefix.
if (skeleton.getBoneIndexByName("mixamorig:Hips") !== -1) {
prefix = "mixamorig:";
}
else if (skeleton.getBoneIndexByName("Hips") === -1) {
// Neither prefixed nor unprefixed Hips was found — try to auto-detect
// the prefix by scanning bone names.
for (const b of skeleton.bones) {
const match = /^(.*?)Hips$/.exec(b.name);
if (match) {
prefix = match[1];
break;
}
}
}
}
if (!prefix) {
return MixamoRigMapping;
}
const resolved = {};
for (const key of Object.keys(MixamoRigMapping)) {
resolved[key] = prefix + MixamoRigMapping[key];
}
return resolved;
}
// ────────────────────────────────────────────────────────────────────────────
// WebXRTrackedBody — runtime representation of a tracked body
// ────────────────────────────────────────────────────────────────────────────
/**
* Represents a tracked body during a WebXR session.
*
* This class manages the bridge between the WebXR body pose data and the
* Babylon.js scene graph. It creates a set of {@link TransformNode}s — one per
* body joint — whose transforms are updated every frame from the XR runtime.
* When a rigged body mesh is attached, its skeleton bones are linked to these
* transform nodes, causing the mesh to follow the user's body automatically.
*
* Coordinate-system handling:
* - WebXR delivers poses in a right-handed system.
* - By default, Babylon.js uses a left-handed system.
* - The class converts the data in-place (negating the Z components of every
* 4 × 4 joint matrix) before decomposing into Babylon transforms.
* - If the mesh was authored in a right-handed tool (the common case for glTF),
* the bone transforms are un-flipped so the skeleton interprets them correctly.
*/
export class WebXRTrackedBody {
/**
* Get the current body mesh (if any).
*/
get bodyMesh() {
return this._bodyMesh;
}
/**
* Get or set the scale factor for local joint offsets.
* @see {@link IWebXRBodyTrackingOptions.jointScaleFactor}
*/
get jointScaleFactor() {
return this._jointScaleFactor;
}
set jointScaleFactor(value) {
this._jointScaleFactor = value;
}
/**
* Returns the array of transform nodes representing each body joint.
* The order matches {@link WebXRBodyTracking.AllBodyJoints}; use
* {@link getJointTransform} or {@link getBodyPartTransforms} for
* name-based lookup.
*
* Note: when a body mesh is attached, these transform nodes are also
* used as the skeleton's link targets for mapped joints. In that case
* the values held by mapped-joint nodes are skeleton-local (parent bone's
* frame), not XR world-space. Unmapped-joint nodes always hold world-space
* pose. If you need world-space poses for every joint regardless of
* mapping, sample the bone matrices directly via the attached skeleton.
*/
get jointTransforms() {
return this._jointTransforms;
}
/**
* Get the transform node for a specific body joint.
* @param jointName The name of the body joint (from {@link WebXRBodyJoint}).
* @returns The transform node corresponding to that joint, or `undefined` if not found.
*/
getJointTransform(jointName) {
const idx = BodyJointNameToIndex.get(jointName);
return idx !== undefined ? this._jointTransforms[idx] : undefined;
}
/**
* Get all joint transform nodes that belong to a given body part.
* @param part The body part to query.
* @param result Optional pre-allocated array to fill (avoids per-call allocation).
* The array is cleared and populated with the results.
* @returns An array of TransformNodes for that body part.
*/
getBodyPartTransforms(part, result) {
const joints = BodyPartsDefinition[part];
if (!result) {
result = new Array(joints.length);
}
else {
result.length = joints.length;
}
for (let i = 0; i < joints.length; i++) {
result[i] = this._jointTransforms[BodyJointNameToIndex.get(joints[i])];
}
return result;
}
/**
* Construct a new tracked body instance.
* @param scene The Babylon.js scene.
* @param bodyMesh Optional rigged body mesh to attach immediately.
* @param rigMapping Optional mapping from WebXR joint names to skeleton bone names.
* @param jointScaleFactor Scale factor for local joint offsets (default 1.0).
* @param preserveBindPoseBonePositions Whether mapped bones should keep bind-pose local translations.
* @param useBoneOrientationOffsets Whether mapped bones should correct XR joint rotations using bind-space offsets.
* @param aimChildOverrides Per–XR-joint override for the aim child used with `useBoneOrientationOffsets`.
* @param jointLocalRotationOffset Optional rotation re-basing each XR joint's local frame (e.g. Z-along-bone → Y-along-bone).
*/
constructor(scene, bodyMesh, rigMapping, jointScaleFactor = 1.0, preserveBindPoseBonePositions = false, useBoneOrientationOffsets = false, aimChildOverrides, jointLocalRotationOffset) {
/**
* Fired when the body mesh is changed via {@link setBodyMesh}.
*/
this.onBodyMeshSetObservable = new Observable();
/**
* One {@link TransformNode} per joint. These receive the WebXR matrix data
* every frame and serve as link targets for skeleton bones.
*/
this._jointTransforms = new Array(BODY_JOINT_COUNT);
/**
* Flat Float32Array that receives transform matrices directly from the
* WebXR API (via `fillPoses`). 16 floats per joint × 83 joints = 1 328 floats.
*/
this._jointTransformMatrices = new Float32Array(BODY_JOINT_COUNT * 16);
/**
* Copy of the raw RHS XR matrices (before LHS conversion).
* Used to compute bone-local transforms for glTF skeletons that
* operate in RHS space.
*/
this._jointTransformMatricesRHS = new Float32Array(BODY_JOINT_COUNT * 16);
/**
* Cached array of XRBodySpace objects extracted from the XRBody, kept in the
* same order as {@link BodyJointReferenceArray}.
*/
this._jointSpaces = new Array(BODY_JOINT_COUNT);
/** Temporary matrix: this joint's XR world-space matrix. */
this._tempJointMatrix = new Matrix();
/** Temporary matrix: parent joint's XR world-space matrix. */
this._tempParentMatrix = new Matrix();
/** Temporary matrix: computed bone-local matrix. */
this._tempLocalMatrix = new Matrix();
/** Temporary vector for scale extracted from decompose. */
this._tempScaleVector = new Vector3();
/** Temporary quaternion for decompose. */
this._tempRotQuat = new Quaternion();
/** Temporary position vector for decompose. */
this._tempPosVec = new Vector3();
/** Temporary quaternion for alternate rotation calculations. */
this._tempRotQuat2 = new Quaternion();
/** Temporary vector for desired child direction. */
this._tempDirection = new Vector3();
/** Temporary vector for joint-local child direction. */
this._tempLocalDirection = new Vector3();
/** Cached desired final positions for mapped joints. */
this._desiredFinalPositions = Array.from({ length: BODY_JOINT_COUNT }, () => new Vector3());
/**
* For each joint index, the joint index of the nearest mapped SKELETON
* ancestor bone. -1 when the bone has no mapped ancestor (root level).
* Precomputed in {@link setBodyMesh} by walking the skeleton hierarchy.
*/
this._jointParentJointIdx = new Array(BODY_JOINT_COUNT).fill(-1);
/** Tracks which joint indices have a linked bone (for step 4b). */
this._jointHasBone = new Array(BODY_JOINT_COUNT).fill(false);
/** Bone → XR joint index lookup, built in {@link setBodyMesh}. */
this._boneToJointIdx = new Map();
/** Original bind-pose local matrices for mapped bones. */
this._mappedBoneBindLocals = new Map();
/** Nearest mapped child bone for each mapped bone. */
this._mappedChildBones = new Map();
/** Bind-space local child direction for each mapped bone. */
this._bindLocalAimDirections = new Map();
/** Bind-space local hand-plane normal used to correct wrist/hand twist from tracked finger positions. */
this._bindLocalTwistNormals = new Map();
/**
* XR joint index to aim each mapped bone at. This can be a mapped joint
* (same as `_boneToJointIdx.get(aimChildBone)`) or an **unmapped** XR
* joint whose tracked position is nonetheless useful for aim correction —
* e.g. `LEFT_HAND_MIDDLE_METACARPAL` for `mixamorig:LeftHand`, which has
* no mapped finger descendant but whose tracked position still defines
* "where the hand is pointing".
*/
this._boneAimTargetJointIdx = new Map();
/** Per-bone pair of tracked joints that define the hand plane used for twist correction. */
this._boneTwistReferenceJointIdx = new Map();
/**
* Per-mapped-bone bind-pose world rotation in mesh-local space
* (decomposed from `bone.getFinalMatrix()` at bind time). Used by the
* delta-from-bind retarget path (axis-convention-invariant).
*/
this._bindBoneWorldRotMeshLocal = new Map();
/**
* Bind-pose tracked joint rotation in mesh-local space per mapped joint.
* Captured on the first tracked frame (or on demand via
* {@link captureTrackedBind}). `null` until captured.
*/
this._trackedBindDesiredFinalRot = null;
/** Bind-pose tracked joint position (mesh-local), captured alongside rotation. */
this._trackedBindDesiredFinalPos = null;
/** True once a tracked-bind snapshot has been taken. */
this._hasTrackedBind = false;
/**
* When `true` (default), the first tracked frame after the feature
* attaches is used as the "rest" pose for delta-from-bind retargeting.
* Set to `false` to require an explicit {@link captureTrackedBind} call.
*/
this.autoCaptureBindOnFirstFrame = true;
/** Scratch quaternion for retarget delta composition. */
this._tempDeltaQuat = new Quaternion();
this._tempBoneWorldRot = new Quaternion();
this._tempParentNewWorldRotInv = new Quaternion();
this._tempTrackedCurRot = new Quaternion();
this._tempTrackedCurPos = new Vector3();
this._tempBindLocalScale = new Vector3();
this._tempBindLocalPos = new Vector3();
/**
* Per-bone cache of the current frame's computed world rotation.
* Entries are pooled across frames (values are reused via `copyFrom`)
* to avoid allocating a fresh Quaternion per mapped bone per frame.
* A bone is considered "populated this frame" iff it has been visited
* by the current retarget pass (tracked via `_computedBoneNewWorldRotFrameId`).
*/
this._computedBoneNewWorldRot = new Map();
/** Per-bone marker: frame id at which the pooled rotation above was last set. */
this._computedBoneNewWorldRotFrameId = new Map();
this._currentRetargetFrameId = 0;
/** Scratch quaternion reused for the parent-world accumulation loop. */
this._tempParentAccumRot = new Quaternion();
/** Scratch quaternion reused for the parent-world intermediate product. */
this._tempParentAccumTmp = new Quaternion();
/** Scratch vectors reused by hand twist correction. */
this._tempTwistFirst = new Vector3();
this._tempTwistSecond = new Vector3();
this._tempTwistNormal = new Vector3();
this._tempCurrentTwistNormal = new Vector3();
this._tempProjectedTwistNormal = new Vector3();
this._tempProjectedDesiredTwistNormal = new Vector3();
this._tempTwistAimAxis = new Vector3();
this._tempTwistCross = new Vector3();
/** The skeleton reference for iterating bones in parent-first order. */
this._skeleton = null;
/** Cached inverse of the skeleton mesh's world matrix. */
this._meshWorldMatrixInverse = new Matrix();
/** Cached inverse of the skeleton mesh pose matrix when initial skinning is used. */
this._initialSkinMatrixInverse = new Matrix();
/**
* Pre-allocated desiredFinal matrices (one per joint slot).
* `desiredFinal[i] = strip(xrWorld[i] × inv(meshWorld))` — the bone's
* target skeleton-space final matrix with parasitic scale removed.
*/
this._desiredFinals = [];
/**
* Standalone TransformNodes created for unmapped skinned bones.
* These TNs are initialized to the bone's bind-pose local and linked
* so that `prepare()` reads deterministic values rather than the
* original glTF scene-graph TNs (which we don't control).
*/
this._unmappedBoneNodes = [];
/** The mesh that owns the skeleton (used for world-matrix inverse). */
this._skeletonMesh = null;
/** The body mesh root (topmost parent), used to parent the mesh to the camera. */
this._bodyMeshRoot = null;
/** The rigged body mesh, if any. */
this._bodyMesh = null;
/**
* Runtime-mutable rotation applied in each tracked joint's local frame to
* re-base XR joint axes (e.g., "+Z-along-bone" → "+Y-along-bone").
* `null` = identity / disabled.
*/
this.jointLocalRotationOffset = null;
/** Cached 4×4 matrix form of {@link jointLocalRotationOffset} for the fast path. */
this._jointLocalRotationOffsetMatrix = new Matrix();
/** Temporary matrix used when applying {@link jointLocalRotationOffset}. */
this._tempOffsetAppliedMatrix = new Matrix();
/**
* When true, bypass skeleton.prepare() and write skin matrices directly.
* This is a diagnostic flag to help isolate rendering issues. When the
* standard pipeline (TN → bone → prepare → skin matrices) produces
* unexpected results, enabling this writes `absInvBind × final` directly
* into the skeleton's transform matrix buffer.
* @internal
*/
this._directSkinWrite = false;
/**
* Debug info string from the last `updateFromXRFrame` call.
* Useful for diagnosing tracking failures on-device.
* @internal
*/
this._lastDebugInfo = "TB:not called";
this._scene = scene;
this._jointScaleFactor = jointScaleFactor