Start · Sprachen · JavaScript · Referenz · XRFrame.getJointPose()

XRFrame.getJointPose()

Methode

Gibt ein <code>XRJointPose</code>-Objekt mit Position, Ausrichtung und Radius eines Handgelenkpunkts (<code>XRJointSpace</code>) relativ zu einem Basisraum zurück – oder <code>null</code>.

seit JavaScript WebXR Hand Input Module Level 1 – experimentell (C Kategorie: webapi

Signatur

XRFrame.prototype.getJointPose(joint, baseSpace)

Beschreibung

Mit getJointPose() fragt man innerhalb eines XR-Frames die aktuelle Pose eines einzelnen Gelenks einer Hand ab. Die WebXR-Hand-Input-API beschreibt jede Hand als XRHand, eine Map mit 25 Gelenken (z. B. "wrist", "thumb-tip", "index-finger-tip"). Jedes dieser Gelenke ist ein XRJointSpace, den man an getJointPose() übergibt.

Das Ergebnis ist ein XRJointPose: Es erbt von XRPose die Eigenschaft transform (Position und Ausrichtung als XRRigidTransform) sowie emulatedPosition und hat zusätzlich radius – den ungefähren Radius des Gelenks in Metern. Damit kann man Hände rendern (z. B. eine Kugel pro Gelenk), Gesten erkennen oder mit virtuellen Objekten kollidieren lassen.

Die Methode ruft man typischerweise im Callback von XRSession.requestAnimationFrame() auf, und zwar für jede XRInputSource, deren Eigenschaft hand gesetzt ist. Als Basisraum dient meist ein XRReferenceSpace vom Typ "local" oder "local-floor". Wenn das Gerät die Pose gerade nicht bestimmen kann (Hand außerhalb des Sichtfelds, verdeckt), wird null zurückgegeben.

Wenn du alle Gelenke auf einmal brauchst, sind XRFrame.fillPoses() und XRFrame.fillJointRadii() effizienter, weil sie direkt in ein Float32Array schreiben und keine einzelnen Objekte pro Gelenk anlegen.

Parameter

Name Typ Default Beschreibung
$joint Pflicht XRJointSpace Das Gelenk, dessen Pose abgefragt wird – z. B. inputSource.hand.get("index-finger-tip").
$baseSpace Pflicht XRSpace Der Raum, relativ zu dem die Pose berechnet wird, meist ein XRReferenceSpace aus session.requestReferenceSpace().

Rückgabewert

Typ
XRJointPose | null
Beschreibung
Ein XRJointPose mit transform, emulatedPosition und radius (in Metern) oder null, wenn die Pose gerade nicht bestimmt werden kann.

Beispiele

Session mit Hand-Tracking starten und Gelenke rendern

const startXR = async () => {
  const session = await navigator.xr.requestSession('immersive-vr', {
    optionalFeatures: ['hand-tracking']
  });
  const refSpace = await session.requestReferenceSpace('local-floor');

  const onFrame = (time, frame) => {
    session.requestAnimationFrame(onFrame);

    for (const source of session.inputSources) {
      if (!source.hand) continue; // kein Hand-Eingabegerät

      for (const [name, jointSpace] of source.hand) {
        const pose = frame.getJointPose(jointSpace, refSpace);
        if (!pose) continue; // Gelenk aktuell nicht erfasst

        const { x, y, z } = pose.transform.position;
        // Hier z. B. eine Kugel mit pose.radius an (x, y, z) zeichnen
        drawSphere(x, y, z, pose.radius);
      }
    }
  };

  session.requestAnimationFrame(onFrame);
};

const drawSphere = (x, y, z, r) => { /* WebGL-Rendering */ };

document.querySelector('#vr-button').addEventListener('click', startXR);

Pinch-Geste erkennen (Daumen- und Zeigefingerspitze)

const PINCH_THRESHOLD = 0.015; // 1,5 cm Abstand zwischen den Oberflächen

const isPinching = (frame, hand, refSpace) => {
  const thumb = frame.getJointPose(hand.get('thumb-tip'), refSpace);
  const index = frame.getJointPose(hand.get('index-finger-tip'), refSpace);
  if (!thumb || !index) return false;

  const a = thumb.transform.position;
  const b = index.transform.position;
  const distance = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);

  // Radien abziehen, damit die Fingeroberflächen verglichen werden
  return distance - thumb.radius - index.radius < PINCH_THRESHOLD;
};

// Im Frame-Callback:
// for (const source of session.inputSources) {
//   if (source.hand && isPinching(frame, source.hand, refSpace)) {
//     console.log(`Pinch mit der ${source.handedness}-Hand`);
//   }
// }
Pinch mit der right-Hand

Handgelenk-Position protokollieren

const logWrist = (frame, session, refSpace) => {
  for (const source of session.inputSources) {
    const wristSpace = source.hand?.get('wrist');
    if (!wristSpace) continue;

    const pose = frame.getJointPose(wristSpace, refSpace);
    if (pose) {
      const { x, y, z } = pose.transform.position;
      console.log(source.handedness, x.toFixed(2), y.toFixed(2), z.toFixed(2));
    }
  }
};
left -0.21 1.12 -0.35 right 0.19 1.10 -0.33

// Wichtig · Fallstricke

  • Feature anfordern: Ohne "hand-tracking" in requiredFeatures oder optionalFeatures bei requestSession() ist inputSource.hand immer null. Wer das Feature nur optional anfordert, muss im Code damit rechnen, dass es fehlt.
  • Nur im aktiven Frame: Wird die Methode außerhalb des requestAnimationFrame()-Callbacks aufgerufen, also auf einem nicht mehr aktiven XRFrame, wirft sie eine InvalidStateError-DOMException. Frames nicht zwischenspeichern und später verwenden.
  • Mit null rechnen: Verdeckte oder nicht erfasste Gelenke liefern null. Den Rückgabewert daher immer prüfen, bevor du auf transform oder radius zugreifst.
  • Performance: 25 Gelenke × 2 Hände × 72–120 fps ergeben sehr viele Objekte. Für das Rendern ganzer Hände sind fillPoses() und fillJointRadii() besser geeignet.
  • Datenschutz: Handdaten sind sensible, biometrisch nutzbare Daten. Browser fragen deshalb beim Nutzer um Erlaubnis. Es wird ein sicherer Kontext (HTTPS) vorausgesetzt.
  • Kompatibilität: Experimentell. Unterstützt wird die Methode vor allem von Chromium-basierten XR-Browsern (z. B. Meta Quest Browser, Chrome mit WebXR) und von Safari auf der Apple Vision Pro. Firefox und Desktop-Safari unterstützen sie nicht. Vorher mit 'XRHand' in window prüfen.