API

Class WebXRBodyTracking

WebXR Body Tracking feature.

This feature tracks the user's full-body pose using the WebXR Body Tracking Module, which exposes 83 articulated joints covering the torso, arms, hands, legs and feet.

// 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.
});
  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.

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.

Hierarchy (View Summary)

Index
dependsOn?: string[]

The dependencies of this feature, if any

disableAutoAttach: boolean = false

Should auto-attach be disabled?

isDisposed: boolean = false

Is this feature disposed?

onBodyMeshSetObservable: Observable<WebXRTrackedBody> = ...

Observable fired when the body mesh has been set via setBodyMesh or during initial configuration.

onBodyTrackingEndedObservable: Observable<void> = ...

Observable fired when body tracking is lost (i.e. XRFrame.body becomes null or returns no valid poses after previously tracking).

onBodyTrackingFrameUpdateObservable: Observable<WebXRTrackedBody> = ...

Observable fired every frame that has valid body tracking data. At the point of notification, all joint transforms are up-to-date.

onBodyTrackingStartedObservable: Observable<WebXRTrackedBody> = ...

Observable fired when body tracking starts (i.e. the first frame where XRFrame.body returns valid data).

onFeatureAttachObservable: Observable<IWebXRFeature> = ...

Observers registered here will be executed when the feature is attached

onFeatureDetachObservable: Observable<IWebXRFeature> = ...

Observers registered here will be executed when the feature is detached

Configuration options for the body tracking feature.

Name: "xr-body-tracking" = WebXRFeatureName.BODY_TRACKING

The module's name, used when enabling the feature on the features manager. Value: "xr-body-tracking".

Version: 1

The (Babylon) version of this module. This is an integer representing the implementation version. This number does not correspond to the WebXR specs version.

  • 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 xrNativeFeatureName before calling the base attach() so the enabled-features check is skipped, then restore it afterwards.

    Returns boolean

    true if attachment succeeded.

  • 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:

    bodyTracking.snapshotFrameToClipboard();
    

    The JSON can later be loaded offline to replay the bone-local computation without a headset.

    Returns Promise<void>

    A promise that resolves when the copy completes, or rejects if no body is currently tracked.