Start · Sprachen · JavaScript · Referenz · XRJointSpace

XRJointSpace

Schnittstelle

Ein <code>XRSpace</code>, der Position und Ausrichtung eines einzelnen Handgelenks bzw. Fingergelenks einer <code>XRHand</code> beschreibt.

seit JavaScript WebXR Hand Input Module Level 1 – Chromium-Browser Kategorie: webapi

Signatur

interface XRJointSpace extends XRSpace

Beschreibung

XRJointSpace gehört zum WebXR Hand Input Module. Für jedes der 25 Gelenke einer getrackten Hand gibt es ein eigenes XRJointSpace-Objekt, etwa für das Handgelenk ("wrist"), die Fingerspitze des Zeigefingers ("index-finger-tip") oder das Daumengrundgelenk ("thumb-metacarpal"). Diese Objekte bekommst du über die XRHand-Map, z. B. mit inputSource.hand.get("index-finger-tip").

Weil XRJointSpace von XRSpace erbt, lässt es sich überall dort einsetzen, wo ein Raum erwartet wird. Die eigentliche Pose eines Gelenks fragst du pro Frame mit XRFrame.getJointPose(jointSpace, referenceSpace) ab. Das Ergebnis ist ein XRJointPose, der neben transform auch einen radius enthält, also den ungefähren Radius des Gelenks in Metern. Für viele Gelenke auf einmal sind XRFrame.fillPoses() und XRFrame.fillJointRadii() deutlich schneller.

Das Interface hat genau eine eigene Eigenschaft:

  • jointName (schreibgeschützt, XRHandJoint-String): der Name des Gelenks, z. B. "middle-finger-phalanx-distal".

Einen Konstruktor gibt es nicht. Der Browser erzeugt die Instanzen, sobald eine Sitzung mit dem Feature "hand-tracking" läuft und eine Eingabequelle eine Hand liefert.

Rückgabewert

Typ
XRJointSpace
Beschreibung
Wird nicht direkt erzeugt, sondern über XRHand.get(jointName) oder beim Iterieren über eine XRHand geliefert. Eigene Eigenschaft: jointName: XRHandJoint.

Beispiele

Hand-Tracking-Sitzung starten und Gelenk-Räume auflisten

const session = await navigator.xr.requestSession('immersive-vr', {
  optionalFeatures: ['hand-tracking']
});

session.addEventListener('inputsourceschange', () => {
  for (const source of session.inputSources) {
    if (!source.hand) continue; // Controller statt Hand
    // XRHand ist eine Map: jointName -> XRJointSpace
    for (const [name, jointSpace] of source.hand) {
      console.log(source.handedness, name, jointSpace instanceof XRJointSpace);
    }
  }
});
left wrist true left thumb-metacarpal true … (25 Gelenke pro Hand)

Position der Zeigefingerspitze pro Frame auslesen

const refSpace = await session.requestReferenceSpace('local-floor');

const onFrame = (time, frame) => {
  for (const source of frame.session.inputSources) {
    const tip = source.hand?.get('index-finger-tip');
    if (!tip) continue;
    const pose = frame.getJointPose(tip, refSpace);
    if (pose) {
      const { x, y, z } = pose.transform.position;
      console.log(`${tip.jointName}: ${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)} (r=${pose.radius.toFixed(3)} m)`);
    }
  }
  frame.session.requestAnimationFrame(onFrame);
};
session.requestAnimationFrame(onFrame);
index-finger-tip: 0.12, 1.21, -0.35 (r=0.008 m)

Pinch-Geste erkennen (Daumen- und Zeigefingerspitze)

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 dist = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
  return dist < 0.015; // unter 1,5 cm gilt als Pinch
};

Alle Gelenk-Posen gebündelt mit fillPoses()

const onFrame = (time, frame) => {
  for (const source of frame.session.inputSources) {
    if (!source.hand) continue;
    const joints = [...source.hand.values()]; // XRJointSpace[]
    const matrices = new Float32Array(joints.length * 16);
    const ok = frame.fillPoses(joints, refSpace, matrices);
    if (ok) {
      // matrices enthält 25 4×4-Matrizen, z. B. für Instanced Rendering
    }
  }
  frame.session.requestAnimationFrame(onFrame);
};

// Wichtig · Fallstricke

  • Feature anfordern: Ohne "hand-tracking" in requiredFeatures oder optionalFeatures bleibt inputSource.hand null, und es gibt keine XRJointSpace-Objekte.
  • Pose kann fehlen: getJointPose() liefert null, wenn ein Gelenk gerade nicht getrackt wird, z. B. weil es verdeckt ist oder außerhalb des Kamerasichtfelds liegt. Prüfe das deshalb in jedem Frame. fillPoses() gibt in diesem Fall false zurück.
  • Ausrichtung: Die Y-Achse eines Gelenk-Raums zeigt senkrecht zur Hautoberfläche, die −Z-Achse entlang des Knochens in Richtung Fingerspitze.
  • Performance: Wenn du alle 25 Gelenke brauchst, ist fillPoses() schneller als 25 einzelne getJointPose()-Aufrufe.
  • Datenschutz: Handdaten eignen sich zum Fingerprinting. Browser verlangen deshalb eine Nutzerzustimmung und runden die Werte teilweise.
  • Kompatibilität: Nur in sicheren Kontexten (HTTPS) und nur auf Geräten mit Hand-Tracking verfügbar, etwa Meta Quest oder Apple Vision Pro. Desktop-Browser ohne XR-Hardware kennen das Interface meist nicht. Prüfe also vorher mit 'XRJointSpace' in window.