UNPKG

@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.

975 lines (974 loc) 44.8 kB
/** This file must only contain pure code and pure imports */ import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js"; import { type WebXRSessionManager } from "../webXRSessionManager.js"; import { type AbstractMesh } from "../../Meshes/abstractMesh.pure.js"; import { Quaternion } from "../../Maths/math.vector.pure.js"; import { type Nullable } from "../../types.js"; import { type IDisposable, type Scene } from "../../scene.pure.js"; import { Observable } from "../../Misc/observable.pure.js"; import { TransformNode } from "../../Meshes/transformNode.pure.js"; import { type Node } from "../../node.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 declare enum WebXRBodyJoint { /** The center of the hips / pelvis */ HIPS = "hips", /** Lower spine (lumbar) */ SPINE_LOWER = "spine-lower", /** Middle spine (thoracic) */ SPINE_MIDDLE = "spine-middle", /** Upper spine */ SPINE_UPPER = "spine-upper", /** Chest */ CHEST = "chest", /** Neck */ NECK = "neck", /** Head */ HEAD = "head", /** Left shoulder */ LEFT_SHOULDER = "left-shoulder", /** Left scapula */ LEFT_SCAPULA = "left-scapula", /** Left upper arm */ LEFT_ARM_UPPER = "left-arm-upper", /** Left forearm (lower arm) */ LEFT_ARM_LOWER = "left-arm-lower", /** Left hand wrist twist (forearm twist) */ LEFT_HAND_WRIST_TWIST = "left-hand-wrist-twist", /** Right shoulder */ RIGHT_SHOULDER = "right-shoulder", /** Right scapula */ RIGHT_SCAPULA = "right-scapula", /** Right upper arm */ RIGHT_ARM_UPPER = "right-arm-upper", /** Right forearm (lower arm) */ RIGHT_ARM_LOWER = "right-arm-lower", /** Right hand wrist twist (forearm twist) */ RIGHT_HAND_WRIST_TWIST = "right-hand-wrist-twist", /** Left palm center */ LEFT_HAND_PALM = "left-hand-palm", /** Left wrist */ LEFT_HAND_WRIST = "left-hand-wrist", /** Left thumb metacarpal */ LEFT_HAND_THUMB_METACARPAL = "left-hand-thumb-metacarpal", /** Left thumb proximal phalanx */ LEFT_HAND_THUMB_PHALANX_PROXIMAL = "left-hand-thumb-phalanx-proximal", /** Left thumb distal phalanx */ LEFT_HAND_THUMB_PHALANX_DISTAL = "left-hand-thumb-phalanx-distal", /** Left thumb tip */ LEFT_HAND_THUMB_TIP = "left-hand-thumb-tip", /** Left index finger metacarpal */ LEFT_HAND_INDEX_METACARPAL = "left-hand-index-metacarpal", /** Left index finger proximal phalanx */ LEFT_HAND_INDEX_PHALANX_PROXIMAL = "left-hand-index-phalanx-proximal", /** Left index finger intermediate phalanx */ LEFT_HAND_INDEX_PHALANX_INTERMEDIATE = "left-hand-index-phalanx-intermediate", /** Left index finger distal phalanx */ LEFT_HAND_INDEX_PHALANX_DISTAL = "left-hand-index-phalanx-distal", /** Left index finger tip */ LEFT_HAND_INDEX_TIP = "left-hand-index-tip", /** Left middle finger metacarpal */ LEFT_HAND_MIDDLE_METACARPAL = "left-hand-middle-metacarpal", /** Left middle finger proximal phalanx */ LEFT_HAND_MIDDLE_PHALANX_PROXIMAL = "left-hand-middle-phalanx-proximal", /** Left middle finger intermediate phalanx */ LEFT_HAND_MIDDLE_PHALANX_INTERMEDIATE = "left-hand-middle-phalanx-intermediate", /** Left middle finger distal phalanx */ LEFT_HAND_MIDDLE_PHALANX_DISTAL = "left-hand-middle-phalanx-distal", /** Left middle finger tip */ LEFT_HAND_MIDDLE_TIP = "left-hand-middle-tip", /** Left ring finger metacarpal */ LEFT_HAND_RING_METACARPAL = "left-hand-ring-metacarpal", /** Left ring finger proximal phalanx */ LEFT_HAND_RING_PHALANX_PROXIMAL = "left-hand-ring-phalanx-proximal", /** Left ring finger intermediate phalanx */ LEFT_HAND_RING_PHALANX_INTERMEDIATE = "left-hand-ring-phalanx-intermediate", /** Left ring finger distal phalanx */ LEFT_HAND_RING_PHALANX_DISTAL = "left-hand-ring-phalanx-distal", /** Left ring finger tip */ LEFT_HAND_RING_TIP = "left-hand-ring-tip", /** Left little finger metacarpal */ LEFT_HAND_LITTLE_METACARPAL = "left-hand-little-metacarpal", /** Left little finger proximal phalanx */ LEFT_HAND_LITTLE_PHALANX_PROXIMAL = "left-hand-little-phalanx-proximal", /** Left little finger intermediate phalanx */ LEFT_HAND_LITTLE_PHALANX_INTERMEDIATE = "left-hand-little-phalanx-intermediate", /** Left little finger distal phalanx */ LEFT_HAND_LITTLE_PHALANX_DISTAL = "left-hand-little-phalanx-distal", /** Left little finger tip */ LEFT_HAND_LITTLE_TIP = "left-hand-little-tip", /** Right palm center */ RIGHT_HAND_PALM = "right-hand-palm", /** Right wrist */ RIGHT_HAND_WRIST = "right-hand-wrist", /** Right thumb metacarpal */ RIGHT_HAND_THUMB_METACARPAL = "right-hand-thumb-metacarpal", /** Right thumb proximal phalanx */ RIGHT_HAND_THUMB_PHALANX_PROXIMAL = "right-hand-thumb-phalanx-proximal", /** Right thumb distal phalanx */ RIGHT_HAND_THUMB_PHALANX_DISTAL = "right-hand-thumb-phalanx-distal", /** Right thumb tip */ RIGHT_HAND_THUMB_TIP = "right-hand-thumb-tip", /** Right index finger metacarpal */ RIGHT_HAND_INDEX_METACARPAL = "right-hand-index-metacarpal", /** Right index finger proximal phalanx */ RIGHT_HAND_INDEX_PHALANX_PROXIMAL = "right-hand-index-phalanx-proximal", /** Right index finger intermediate phalanx */ RIGHT_HAND_INDEX_PHALANX_INTERMEDIATE = "right-hand-index-phalanx-intermediate", /** Right index finger distal phalanx */ RIGHT_HAND_INDEX_PHALANX_DISTAL = "right-hand-index-phalanx-distal", /** Right index finger tip */ RIGHT_HAND_INDEX_TIP = "right-hand-index-tip", /** Right middle finger metacarpal */ RIGHT_HAND_MIDDLE_METACARPAL = "right-hand-middle-metacarpal", /** Right middle finger proximal phalanx */ RIGHT_HAND_MIDDLE_PHALANX_PROXIMAL = "right-hand-middle-phalanx-proximal", /** Right middle finger intermediate phalanx */ RIGHT_HAND_MIDDLE_PHALANX_INTERMEDIATE = "right-hand-middle-phalanx-intermediate", /** Right middle finger distal phalanx */ RIGHT_HAND_MIDDLE_PHALANX_DISTAL = "right-hand-middle-phalanx-distal", /** Right middle finger tip */ RIGHT_HAND_MIDDLE_TIP = "right-hand-middle-tip", /** Right ring finger metacarpal */ RIGHT_HAND_RING_METACARPAL = "right-hand-ring-metacarpal", /** Right ring finger proximal phalanx */ RIGHT_HAND_RING_PHALANX_PROXIMAL = "right-hand-ring-phalanx-proximal", /** Right ring finger intermediate phalanx */ RIGHT_HAND_RING_PHALANX_INTERMEDIATE = "right-hand-ring-phalanx-intermediate", /** Right ring finger distal phalanx */ RIGHT_HAND_RING_PHALANX_DISTAL = "right-hand-ring-phalanx-distal", /** Right ring finger tip */ RIGHT_HAND_RING_TIP = "right-hand-ring-tip", /** Right little finger metacarpal */ RIGHT_HAND_LITTLE_METACARPAL = "right-hand-little-metacarpal", /** Right little finger proximal phalanx */ RIGHT_HAND_LITTLE_PHALANX_PROXIMAL = "right-hand-little-phalanx-proximal", /** Right little finger intermediate phalanx */ RIGHT_HAND_LITTLE_PHALANX_INTERMEDIATE = "right-hand-little-phalanx-intermediate", /** Right little finger distal phalanx */ RIGHT_HAND_LITTLE_PHALANX_DISTAL = "right-hand-little-phalanx-distal", /** Right little finger tip */ RIGHT_HAND_LITTLE_TIP = "right-hand-little-tip", /** Left upper leg (thigh) */ LEFT_UPPER_LEG = "left-upper-leg", /** Left lower leg (shin) */ LEFT_LOWER_LEG = "left-lower-leg", /** Left foot ankle twist */ LEFT_FOOT_ANKLE_TWIST = "left-foot-ankle-twist", /** Left foot ankle */ LEFT_FOOT_ANKLE = "left-foot-ankle", /** Left foot subtalar */ LEFT_FOOT_SUBTALAR = "left-foot-subtalar", /** Left foot transverse */ LEFT_FOOT_TRANSVERSE = "left-foot-transverse", /** Left foot ball */ LEFT_FOOT_BALL = "left-foot-ball", /** Right upper leg (thigh) */ RIGHT_UPPER_LEG = "right-upper-leg", /** Right lower leg (shin) */ RIGHT_LOWER_LEG = "right-lower-leg", /** Right foot ankle twist */ RIGHT_FOOT_ANKLE_TWIST = "right-foot-ankle-twist", /** Right foot ankle */ RIGHT_FOOT_ANKLE = "right-foot-ankle", /** Right foot subtalar */ RIGHT_FOOT_SUBTALAR = "right-foot-subtalar", /** Right foot transverse */ RIGHT_FOOT_TRANSVERSE = "right-foot-transverse", /** Right foot ball */ RIGHT_FOOT_BALL = "right-foot-ball" } /** * 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. */ export declare const BodyJointParentIndex: number[]; /** * Logical body parts for convenient grouping of joints. */ export declare enum BodyPart { /** Torso / spine (hips through head) */ TORSO = "torso", /** Left arm (shoulder through wrist twist) */ LEFT_ARM = "left-arm", /** Right arm (shoulder through wrist twist) */ RIGHT_ARM = "right-arm", /** Left hand (palm through finger tips) */ LEFT_HAND = "left-hand", /** Right hand (palm through finger tips) */ RIGHT_HAND = "right-hand", /** Left leg (upper leg through foot ball) */ LEFT_LEG = "left-leg", /** Right leg (upper leg through foot ball) */ RIGHT_LEG = "right-leg" } /** * A dictionary mapping each {@link WebXRBodyJoint} to a bone name in a rigged body mesh. * * When you supply a rigged mesh to {@link WebXRTrackedBody.setBodyMesh}, provide * a mapping so the feature knows which skeleton bone to drive for each joint. * * @example * ```typescript * const rigMapping: XRBodyMeshRigMapping = { * "hips": "Bip01_Pelvis", * "spine-lower": "Bip01_Spine", * "head": "Bip01_Head", * // …remaining joints… * }; * ``` */ export type XRBodyMeshRigMapping = { [jointName in WebXRBodyJoint]?: string; }; /** * Represents the XRBodySpace native interface as defined by the spec. * An XRBodySpace is an XRSpace that additionally exposes a jointName. * @see https://immersive-web.github.io/body-tracking/#xrjointspace-interface */ interface XRBodySpace extends XRSpace { readonly jointName: string; } /** * Represents the native XRBody interface as defined by the spec. * An XRBody is an iterable map of XRBodyJoint → XRBodySpace. * @see https://immersive-web.github.io/body-tracking/#xrbody-interface */ interface XRBody { readonly size: number; get(key: string): XRBodySpace | undefined; forEach(callbackfn: (value: XRBodySpace, key: string, map: XRBody) => void): void; [Symbol.iterator](): IterableIterator<[string, XRBodySpace]>; entries(): IterableIterator<[string, XRBodySpace]>; keys(): IterableIterator<string>; values(): IterableIterator<XRBodySpace>; } declare global { interface XRFrame { body?: XRBody; } } /** * Configuration options for the WebXR body tracking feature. */ export interface IWebXRBodyTrackingOptions { /** * A pre-existing rigged body mesh to drive with tracked joint poses. * If provided, skeleton bones will be linked to tracked joints automatically. * The mesh should contain a skeleton whose bones can be mapped via `rigMapping`. */ bodyMesh?: AbstractMesh; /** * A mapping from {@link WebXRBodyJoint} names to skeleton bone names. * Required when the skeleton's bone names do not match the WebXR joint names. * If omitted and a body mesh is provided, the feature assumes bones are * named identically to the WebXR joint names (e.g. `"hips"`, `"left-arm-upper"`, etc.). */ rigMapping?: XRBodyMeshRigMapping; /** * Scale factor applied to the local-space position of every joint. * * This uniformly scales the distances between parent and child joints, * allowing you to fit XR body tracking data to meshes that are larger or * smaller than the tracked user. * * - `1.0` (default): no scaling — real-world proportions. * - `> 1.0`: stretches the skeleton (makes it taller / wider). * - `< 1.0`: compresses the skeleton. * * Only affects local joint offsets, not the root (hips) position. */ jointScaleFactor?: number; /** * Preserve bind-pose local translations for mapped bones and only retarget rotations. * * When `false` (default), mapped bones are translated directly to the tracked joint * positions. This reproduces the tracked skeleton exactly, but can distort avatars whose * proportions differ from the tracked user. * * When `true`, mapped child bones keep their bind-pose local translation offsets while * still using the tracked joint rotations. This is the typical retargeting mode for * driving arbitrary skinned characters without forcing them to the tracked skeleton's * segment lengths. */ preserveBindPoseBonePositions?: boolean; /** * Apply a per-bone orientation offset so the avatar bone basis matches the XR joint basis. * * When enabled, WebXR joint rotations are corrected using the bone's bind-space child axis. * This is useful for rigs whose bone local axes do not match the anatomical axes used by * the WebXR body-tracking joint orientations. */ useBoneOrientationOffsets?: boolean; /** * Rotation applied in each tracked joint's local frame to re-base the * XR joint axes. Some runtimes (e.g., some Meta Quest builds) emit body * joint poses whose +Z axis points along the bone (parent → child), while * most avatar rigs expect +Y along the bone. Setting this to * `Quaternion.RotationAxis(Vector3.Right(), -Math.PI / 2)` converts * "+Z-along-bone" joint data to "+Y-along-bone" before retargeting. * * Applied as a pre-multiply on each joint's world matrix: * `M' = R × M`, which, under Babylon's row-vector convention * (`v_world = v_local × M`), effectively re-bases the joint-local axes. * * Default `undefined` = identity (no re-basing). */ jointLocalRotationOffset?: Quaternion; /** * Per–XR-joint override for the "aim child" joint used when * {@link useBoneOrientationOffsets} is enabled. * * By default the aim direction for a mapped bone is computed against its * nearest mapped descendant in the skeleton. For rigs whose XR-mapped * spine chain has very short segments (e.g. WebXR's `hips`→`spine-lower` * is typically only ~1 cm), the aim direction becomes noise-dominated and * can produce a large incorrect rotation for the parent bone. * * Use this map to redirect a specific XR joint's aim target to a farther * XR joint whose relative position is stable. Both the source and target * joint must be present in {@link rigMapping} (i.e. mapped to a bone on the * skeleton). * * Example for a Mixamo-style rig where hips, spine, spine1, spine2 and * neck are all mapped: * ```ts * aimChildOverrides: { * [WebXRBodyJoint.HIPS]: WebXRBodyJoint.SPINE_UPPER, // skip spine-lower/middle * [WebXRBodyJoint.SPINE_LOWER]: WebXRBodyJoint.NECK, // long stable segment * [WebXRBodyJoint.SPINE_MIDDLE]: WebXRBodyJoint.NECK, * } * ``` */ aimChildOverrides?: Partial<Record<WebXRBodyJoint, WebXRBodyJoint>>; /** * Convenience flag for Mixamo-rigged characters. * * When set to `true`, the feature automatically applies: * - {@link MixamoRigMapping} as the `rigMapping` (skipping any explicit * `rigMapping` you provide). * - {@link MixamoAimChildOverrides} as `aimChildOverrides` (skipping any * explicit `aimChildOverrides` you provide). * - Turns on {@link useBoneOrientationOffsets} by default (you can still * override to `false`). * * The Mixamo `mixamorig:` bone-name prefix is detected automatically by * inspecting the skeleton, so the same mapping works regardless of whether * the bones have been renamed to strip the prefix (common when re-exporting). */ isMixamoModel?: boolean; } /** * 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 declare const MixamoRigMapping: XRBodyMeshRigMapping; /** * 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 declare const MixamoAimChildOverrides: Partial<Record<WebXRBodyJoint, WebXRBodyJoint>>; /** * 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 declare function _ResolveMixamoRigMapping(bodyMesh: AbstractMesh): XRBodyMeshRigMapping; /** * 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 declare class WebXRTrackedBody implements IDisposable { /** * Fired when the body mesh is changed via {@link setBodyMesh}. */ readonly onBodyMeshSetObservable: Observable<WebXRTrackedBody>; private _scene; /** * One {@link TransformNode} per joint. These receive the WebXR matrix data * every frame and serve as link targets for skeleton bones. */ private _jointTransforms; /** * Flat Float32Array that receives transform matrices directly from the * WebXR API (via `fillPoses`). 16 floats per joint × 83 joints = 1 328 floats. */ private _jointTransformMatrices; /** * Copy of the raw RHS XR matrices (before LHS conversion). * Used to compute bone-local transforms for glTF skeletons that * operate in RHS space. */ private _jointTransformMatricesRHS; /** * Cached array of XRBodySpace objects extracted from the XRBody, kept in the * same order as {@link BodyJointReferenceArray}. */ private _jointSpaces; /** Temporary matrix: this joint's XR world-space matrix. */ private _tempJointMatrix; /** Temporary matrix: parent joint's XR world-space matrix. */ private _tempParentMatrix; /** Temporary matrix: computed bone-local matrix. */ private _tempLocalMatrix; /** Temporary vector for scale extracted from decompose. */ private _tempScaleVector; /** Temporary quaternion for decompose. */ private _tempRotQuat; /** Temporary position vector for decompose. */ private _tempPosVec; /** Temporary quaternion for alternate rotation calculations. */ private _tempRotQuat2; /** Temporary vector for desired child direction. */ private _tempDirection; /** Temporary vector for joint-local child direction. */ private _tempLocalDirection; /** Cached desired final positions for mapped joints. */ private _desiredFinalPositions; /** * 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. */ private _jointParentJointIdx; /** Tracks which joint indices have a linked bone (for step 4b). */ private _jointHasBone; /** Bone → XR joint index lookup, built in {@link setBodyMesh}. */ private _boneToJointIdx; /** Original bind-pose local matrices for mapped bones. */ private _mappedBoneBindLocals; /** Nearest mapped child bone for each mapped bone. */ private _mappedChildBones; /** Bind-space local child direction for each mapped bone. */ private _bindLocalAimDirections; /** Bind-space local hand-plane normal used to correct wrist/hand twist from tracked finger positions. */ private _bindLocalTwistNormals; /** * 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". */ private _boneAimTargetJointIdx; /** Per-bone pair of tracked joints that define the hand plane used for twist correction. */ private _boneTwistReferenceJointIdx; /** * 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). */ private _bindBoneWorldRotMeshLocal; /** * 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. */ private _trackedBindDesiredFinalRot; /** Bind-pose tracked joint position (mesh-local), captured alongside rotation. */ private _trackedBindDesiredFinalPos; /** True once a tracked-bind snapshot has been taken. */ private _hasTrackedBind; /** * 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. */ autoCaptureBindOnFirstFrame: boolean; /** Scratch quaternion for retarget delta composition. */ private _tempDeltaQuat; private _tempBoneWorldRot; private _tempParentNewWorldRotInv; private _tempTrackedCurRot; private _tempTrackedCurPos; private _tempBindLocalScale; private _tempBindLocalPos; /** * 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`). */ private _computedBoneNewWorldRot; /** Per-bone marker: frame id at which the pooled rotation above was last set. */ private _computedBoneNewWorldRotFrameId; private _currentRetargetFrameId; /** Scratch quaternion reused for the parent-world accumulation loop. */ private _tempParentAccumRot; /** Scratch quaternion reused for the parent-world intermediate product. */ private _tempParentAccumTmp; /** Scratch vectors reused by hand twist correction. */ private _tempTwistFirst; private _tempTwistSecond; private _tempTwistNormal; private _tempCurrentTwistNormal; private _tempProjectedTwistNormal; private _tempProjectedDesiredTwistNormal; private _tempTwistAimAxis; private _tempTwistCross; /** The skeleton reference for iterating bones in parent-first order. */ private _skeleton; /** Cached inverse of the skeleton mesh's world matrix. */ private _meshWorldMatrixInverse; /** Cached inverse of the skeleton mesh pose matrix when initial skinning is used. */ private _initialSkinMatrixInverse; /** * 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. */ private _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). */ private _unmappedBoneNodes; /** The mesh that owns the skeleton (used for world-matrix inverse). */ private _skeletonMesh; /** The body mesh root (topmost parent), used to parent the mesh to the camera. */ private _bodyMeshRoot; /** The rigged body mesh, if any. */ private _bodyMesh; /** Scale factor for local joint offsets. */ private _jointScaleFactor; /** Whether mapped bones should keep their bind-pose local translations. */ private readonly _preserveBindPoseBonePositions; /** Whether mapped bones should correct WebXR joint rotations using bind-space orientation offsets. */ private readonly _useBoneOrientationOffsets; /** Per–XR-joint override mapping for aim children (used with _useBoneOrientationOffsets). */ private _aimChildOverrides; /** * 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. */ jointLocalRotationOffset: Nullable<Quaternion>; /** Cached 4×4 matrix form of {@link jointLocalRotationOffset} for the fast path. */ private _jointLocalRotationOffsetMatrix; /** Temporary matrix used when applying {@link jointLocalRotationOffset}. */ private _tempOffsetAppliedMatrix; /** * 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 */ _directSkinWrite: boolean; /** * Debug info string from the last `updateFromXRFrame` call. * Useful for diagnosing tracking failures on-device. * @internal */ _lastDebugInfo: string; /** * Get the current body mesh (if any). */ get bodyMesh(): Nullable<AbstractMesh>; /** * Get or set the scale factor for local joint offsets. * @see {@link IWebXRBodyTrackingOptions.jointScaleFactor} */ get jointScaleFactor(): number; set jointScaleFactor(value: number); /** * 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(): readonly TransformNode[]; /** * 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: WebXRBodyJoint): TransformNode | 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: BodyPart, result?: TransformNode[]): TransformNode[]; /** * 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: Scene, bodyMesh?: AbstractMesh, rigMapping?: XRBodyMeshRigMapping, jointScaleFactor?: number, preserveBindPoseBonePositions?: boolean, useBoneOrientationOffsets?: boolean, aimChildOverrides?: Partial<Record<WebXRBodyJoint, WebXRBodyJoint>>, jointLocalRotationOffset?: Quaternion); /** * Attach (or replace) a rigged body mesh. * * The mesh's skeleton bones are linked to the internal transform nodes * that receive WebXR tracking data each frame. If the mesh has a skeleton, * the `rigMapping` (or a direct name match) is used to bind each bone. * * @param bodyMesh The rigged mesh to drive. * @param rigMapping An optional mapping from {@link WebXRBodyJoint} to bone name. * If omitted, bones are expected to be named after the WebXR joint names. */ setBodyMesh(bodyMesh: AbstractMesh, rigMapping?: XRBodyMeshRigMapping): void; /** * Update joint transforms from the current XR frame. * * This method is called once per frame by the feature class. Internally it: * 1. Extracts all XRBodySpaces from the XRBody. * 2. Fills the transform-matrix buffer via `fillPoses()` (or per-joint fallback). * 3. Converts from WebXR right-handed to Babylon left-handed coordinates. * 4. Decomposes each matrix into the corresponding TransformNode. * 5. Parents the body mesh root to the XR camera so it tracks correctly. * * @param xrFrame The current XRFrame. * @param referenceSpace The XRReferenceSpace to resolve poses against. * @param xrCameraParent The parent node of the XR camera (used for parenting). * @returns `true` if valid tracking data was processed, `false` otherwise. */ updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, xrCameraParent: Nullable<Node>): boolean; /** * Replay a pre-captured joint matrix set through the retargeting * pipeline as if it had just been delivered by an XR frame. * * Useful for headset-less testing: call with a snapshot captured via * {@link snapshotFrame} (the `jointMatricesRHS` array, or `jointMatricesLHS` * already flipped). The matrices are assumed to be RHS unless * `isAlreadyLhs=true`, in which case the RHS→LHS flip step is skipped. * * @param rawMatrices Float32Array of BODY_JOINT_COUNT × 16 (= 1328) floats. * @param isAlreadyLhs Set to `true` if matrices are already LHS-converted. */ replayRawJointMatrices(rawMatrices: Float32Array | number[], isAlreadyLhs?: boolean): void; /** * Run steps 2.5 → 5 of the retargeting pipeline using whatever is * currently in `_jointTransformMatrices` (as if the WebXR API just * filled it for the current frame). * @param xrCameraParent Parent node of the XR camera (used to parent the body mesh root during live XR). * @param skipRhsToLhs If true, skip the RHS→LHS flip (matrices are already in the scene's handedness). */ private _processTrackedJointMatrices; /** * Capture the current tracked-joint desired-final rotations and positions * as the "rest pose" for delta-from-bind retargeting. * * Delta-from-bind is the production retarget path: every subsequent * frame is interpreted as a rotation delta from this snapshot, which * makes retargeting invariant to the XR-joint axis convention and to * any skeletal-proportion differences between the tracked user and the * avatar. * * Call this after the user assumes a known rest pose (e.g. T-pose, * A-pose, arms-at-sides). By default the feature auto-captures on the * first tracked frame — disable via {@link autoCaptureBindOnFirstFrame}. */ captureTrackedBind(): void; /** * Clear any captured bind, reverting subsequent frames to the fallback * direct-retarget path (or re-triggering auto-capture on the next frame). */ clearTrackedBind(): void; private _computeNormalFromJointPositions; private _projectOnPlaneToRef; private _storeBindLocalTwistNormalFromPositions; /** Internal: copy current desiredFinals into the tracked-bind slots. */ private _captureTrackedBindFromDesiredFinals; /** * Delta-from-bind retarget: axis-convention-invariant. * * For each mapped bone in skeleton order (parents first): * let j = joint mapped to this bone * deltaMeshLocalRot = bindTracked[j]⁻¹ × currentTracked[j] (right-side in row-vector) * newBoneWorldRot = bindBoneWorldRot × deltaMeshLocalRot * newBoneLocalRot = newBoneWorldRot × parentNewBoneWorldRot⁻¹ * * Positions: root bone receives tracked world delta (so the avatar * translates with the user). All other mapped bones keep their rig's * bind-pose local translation to preserve segment lengths. * @param scaleFactor Additional scale applied to joint local positions. */ private _retargetDeltaFromBind; /** * Legacy direct-retarget path (pre-bind-capture). Kept as fallback. * @param useInitialSkinMatrix Skeleton needs initial-skin-matrix chaining for the root. * @param useBoneOrientationOffsets Apply aim-direction correction per mapped bone. * @param scaleFactor Additional scale applied to joint local positions. * @param computedWorldRotations Optional map used by the aim-correction path for parent lookups. */ private _retargetDirect; /** * Capture a snapshot of the current frame's raw XR joint matrices and * skeleton metadata. Returns a JSON string that can be used offline to * replay / debug bone-local computation without a headset. * * The snapshot includes: * - `jointMatricesRHS` – 83 × 16 raw RHS matrices (before LHS conversion) * - `jointMatricesLHS` – 83 × 16 LHS-converted matrices (after step 3) * - `meshWorldMatrix` – 16 floats, the skeleton mesh's world matrix (if any) * - `jointHasBone` – boolean[83], which joints are mapped to bones * - `jointParentJointIdx` – number[83], mapped ancestor for each joint * - `useRightHandedSystem` – scene handedness setting * - `jointNames` – the 83 joint names in order * * @returns A JSON string with the snapshot data. */ snapshotFrame(): string; /** * Capture a snapshot and copy it to the system clipboard. * Logs to the console on success or failure. * @returns A promise that resolves when the copy completes. */ snapshotFrameToClipboardAsync(): Promise<void>; /** * Dispose of this tracked body and its resources. * @param disposeMesh If `true`, the body mesh and its skeleton are disposed as well. */ dispose(disposeMesh?: boolean): void; } /** * WebXR Body Tracking feature. * * This feature tracks the user's full-body pose using the * [WebXR Body Tracking Module](https://immersive-web.github.io/body-tracking/), * which exposes 83 articulated joints covering the torso, arms, hands, legs and feet. * * ## Quick Start * * ```typescript * // Enable body tracking when creating the default XR experience: * const xr = await scene.createDefaultXRExperienceAsync(); * const bodyTracking = xr.baseExperience.featuresManager.enableFeature( * WebXRFeatureName.BODY_TRACKING, * "latest", * { * bodyMesh: myRiggedBodyMesh, * rigMapping: { * "hips": "Bip01_Pelvis", * "spine-lower": "Bip01_Spine", * // … one entry per joint you want to drive … * }, * } as IWebXRBodyTrackingOptions, * ); * * // React to tracking changes: * bodyTracking.onBodyTrackingStartedObservable.add((trackedBody) => { * console.log("Body tracking started"); * }); * bodyTracking.onBodyTrackingFrameUpdateObservable.add((trackedBody) => { * // The tracked body's joint transforms are already up-to-date. * }); * ``` * * ## How It Works * * 1. The feature requests the `"body-tracking"` native WebXR feature at session start. * 2. Each frame, if `XRFrame.body` is available, joint poses are filled into a * flat Float32Array via the batch `fillPoses()` API (with a per-joint fallback). * 3. The 4 × 4 matrices are converted from WebXR right-handed coordinates to * Babylon.js left-handed coordinates in-place (unless the scene is RHS). * 4. Each matrix is decomposed and written to a TransformNode; skeleton bones * linked to those nodes animate the rigged mesh automatically. * * ## Coordinate System * * WebXR data arrives in a **right-handed** coordinate system. Babylon.js * defaults to **left-handed**. The conversion is handled automatically: * - Joint matrices are flipped in-place (Z-negation of specific matrix elements). * - For meshes authored in a right-handed tool (glTF, Blender, etc.), the bone * data is un-flipped so the skeleton interprets poses correctly. * - If you use `scene.useRightHandedSystem = true`, no conversion is applied. * * @see https://immersive-web.github.io/body-tracking/ */ export declare class WebXRBodyTracking extends WebXRAbstractFeature { /** Configuration options for the body tracking feature. */ readonly options: IWebXRBodyTrackingOptions; /** * The module's name, used when enabling the feature on the features manager. * Value: `"xr-body-tracking"`. */ static readonly Name: "xr-body-tracking"; /** * The (Babylon) version of this module. * This is an integer representing the implementation version. * This number does not correspond to the WebXR specs version. */ static readonly Version = 1; /** * Observable fired when body tracking starts (i.e. the first frame where * `XRFrame.body` returns valid data). */ readonly onBodyTrackingStartedObservable: Observable<WebXRTrackedBody>; /** * Observable fired when body tracking is lost (i.e. `XRFrame.body` becomes * `null` or returns no valid poses after previously tracking). */ readonly onBodyTrackingEndedObservable: Observable<void>; /** * Observable fired every frame that has valid body tracking data. * At the point of notification, all joint transforms are up-to-date. */ readonly onBodyTrackingFrameUpdateObservable: Observable<WebXRTrackedBody>; /** * Observable fired when the body mesh has been set via {@link setBodyMesh} * or during initial configuration. */ readonly onBodyMeshSetObservable: Observable<WebXRTrackedBody>; /** The current tracked body, or null when not tracking. */ private _trackedBody; /** True while we have an active body tracking session. */ private _isTracking; /** Observer for world scale changes, so the body mesh can be rescaled. */ private _worldScaleObserver; /** * Debug info from the feature-level frame loop. * Shows why `_onXRFrame` did or did not call `updateFromXRFrame`. * @internal */ _lastFrameDebugInfo: string; /** * Get the currently tracked body, if any. */ get trackedBody(): Nullable<WebXRTrackedBody>; /** * Returns `true` while body tracking data is actively being received. */ get isTracking(): boolean; /** * Construct a new WebXRBodyTracking feature. * @param _xrSessionManager The XR session manager. * @param options Configuration options. */ constructor(_xrSessionManager: WebXRSessionManager, /** Configuration options for the body tracking feature. */ options?: IWebXRBodyTrackingOptions); /** * Attach a rigged body mesh (or replace the current one) at any time. * * This is a convenience method that forwards to the underlying * {@link WebXRTrackedBody.setBodyMesh}. The body does not need to be * already tracking for this to work — the mesh will be applied once * tracking begins. * * @param bodyMesh The rigged mesh to drive. * @param rigMapping Optional mapping from {@link WebXRBodyJoint} names to bone names. */ setBodyMesh(bodyMesh: AbstractMesh, rigMapping?: XRBodyMeshRigMapping): void; /** * Attach the feature. * Called by the features manager when the XR session initialises. * * Body tracking is a draft WebXR spec. Some UAs (e.g. Meta Quest) provide * body data on `XRFrame.body` but do not list `"body-tracking"` in * `session.enabledFeatures`. To handle this, we temporarily clear * {@link xrNativeFeatureName} before calling the base `attach()` so the * enabled-features check is skipped, then restore it afterwards. * @returns `true` if attachment succeeded. */ attach(): boolean; /** * Detach the feature. * Called by the features manager when the XR session ends. * @returns `true` if detachment succeeded. */ detach(): boolean; /** * Dispose this feature and all resources. */ dispose(): void; /** * Called every XR frame by the base class. * Reads body joint data from the XR runtime and updates transforms. * @param xrFrame The current XRFrame. */ protected _onXRFrame(xrFrame: XRFrame): void; /** * Returns the complete ordered list of body joint names tracked by this feature. * Useful for iterating over all joints or building UI. */ static get AllBodyJoints(): readonly WebXRBodyJoint[]; /** * Capture a single-frame snapshot of all 83 joints and copy it to the * clipboard. Call this from a playground button or the console while * wearing the headset: * * ```typescript * bodyTracking.snapshotFrameToClipboard(); * ``` * * The JSON can later be loaded offline to replay the bone-local * computation without a headset. * @returns A promise that resolves when the copy completes, or rejects * if no body is currently tracked. */ snapshotFrameToClipboardAsync(): Promise<void>; } /** * Register side effects for webXRBodyTracking. * Safe to call multiple times; only the first call has an effect. */ export declare function RegisterWebXRBodyTracking(): void; export {};