Mit ARCore für Jetpack XR mit Händen arbeiten

ARCore für Jetpack XR kann Informationen zu den erkannten Händen des Nutzers liefern und bietet Informationen zur Position von Händen und den zugehörigen Gelenken. Diese Handdaten können verwendet werden, um Entitäten und Modelle an den Händen eines Nutzers zu befestigen, z. B. ein Toolmenü:

Sitzung abrufen

Über ein Android XR-Session auf Handinformationen zugreifen Informationen zum Abrufen einer Session finden Sie unter Lebenszyklus einer Sitzung.

Sitzung konfigurieren

Handtracking ist in XR-Sitzungen nicht standardmäßig aktiviert. Wenn Sie Handdaten empfangen möchten, konfigurieren Sie die Sitzung und legen Sie den Modus HandTrackingMode.BOTH fest:

val newConfig = session.config.copy(
    handTracking = Config.HandTrackingMode.BOTH
)
when (val result = session.configure(newConfig)) {
    is SessionConfigureConfigurationNotSupported ->
        TODO(/* Some combinations of configurations are not valid. Handle this failure case. */)
    is SessionConfigureSuccess -> TODO(/* Success! */)
    else ->
        TODO(/* A different unhandled exception was thrown. */)
}

Handdaten abrufen

Handdaten sind für die linke und die rechte Hand separat verfügbar. Verwenden Sie die state jeder Hand, um auf die Posenpositionen für jedes Gelenk zuzugreifen:

Hand.left(session)?.state?.collect { handState -> // or Hand.right(session)
    // Hand state has been updated.
    // Use the state of hand joints to update an entity's position.
    renderPlanetAtHandPalm(handState)
}

Hände haben die folgenden Eigenschaften:

Handdaten in Ihrer App verwenden

Die Positionen der Handgelenke eines Nutzers können verwendet werden, um 3D-Objekte an den Händen eines Nutzers zu verankern, z. B. um ein Modell an der linken Handfläche zu befestigen:

val palmPose = leftHandState.handJoints[HandJointType.PALM] ?: return

// the down direction points in the same direction as the palm
val angle = Vector3.angleBetween(palmPose.rotation * Vector3.Down, Vector3.Up)
palmEntity.setEnabled(angle > Math.toRadians(40.0))

val transformedPose =
    session.scene.perceptionSpace.transformPoseTo(
        palmPose,
        session.scene.activitySpace,
    )
val newPosition = transformedPose.translation + transformedPose.down * 0.05f
palmEntity.setPose(Pose(newPosition, transformedPose.rotation))

So befestigen Sie ein Modell an der Spitze des Zeigefingers Ihrer rechten Hand:

val tipPose = rightHandState.handJoints[HandJointType.INDEX_TIP] ?: return

// the forward direction points towards the finger tip.
val angle = Vector3.angleBetween(tipPose.rotation * Vector3.Forward, Vector3.Up)
indexFingerEntity.setEnabled(angle > Math.toRadians(40.0))

val transformedPose =
    session.scene.perceptionSpace.transformPoseTo(
        tipPose,
        session.scene.activitySpace,
    )
val position = transformedPose.translation + transformedPose.forward * 0.03f
val rotation = Quaternion.fromLookTowards(transformedPose.up, Vector3.Up)
indexFingerEntity.setPose(Pose(position, rotation))

Einfache Handbewegungen erkennen

Die Positionen der Gelenke in der Hand werden verwendet, um grundlegende Handgesten zu erkennen. In den Konventionen für Handgelenke finden Sie Informationen dazu, in welchem Bereich sich die Gelenke befinden müssen, damit sie als bestimmte Pose registriert werden.

Wenn Sie beispielsweise ein Auseinander- und Zusammenziehen von Daumen und Zeigefinger erkennen möchten, verwenden Sie den Abstand zwischen den beiden Fingerkuppengelenken:

val thumbTip = handState.handJoints[HandJointType.THUMB_TIP] ?: return false
val thumbTipPose = session.scene.perceptionSpace.transformPoseTo(thumbTip, session.scene.activitySpace)
val indexTip = handState.handJoints[HandJointType.INDEX_TIP] ?: return false
val indexTipPose = session.scene.perceptionSpace.transformPoseTo(indexTip, session.scene.activitySpace)
return Vector3.distance(thumbTipPose.translation, indexTipPose.translation) < 0.05

Ein Beispiel für eine kompliziertere Geste ist die „Stopp“-Geste. Bei dieser Geste sollten alle Finger ausgestreckt sein. Das bedeutet, dass jedes Gelenk jedes Fingers ungefähr in dieselbe Richtung zeigen sollte:

val threshold = toRadians(angleInDegrees = 30f)
fun pointingInSameDirection(joint1: HandJointType, joint2: HandJointType): Boolean {
    val forward1 = handState.handJoints[joint1]?.forward ?: return false
    val forward2 = handState.handJoints[joint2]?.forward ?: return false
    return Vector3.angleBetween(forward1, forward2) < threshold
}
return pointingInSameDirection(HandJointType.INDEX_PROXIMAL, HandJointType.INDEX_TIP) &&
    pointingInSameDirection(HandJointType.MIDDLE_PROXIMAL, HandJointType.MIDDLE_TIP) &&
    pointingInSameDirection(HandJointType.RING_PROXIMAL, HandJointType.RING_TIP)

Beachten Sie beim Entwickeln benutzerdefinierter Erkennung für Handgesten die folgenden Punkte:

  • Nutzer interpretieren eine bestimmte Geste möglicherweise unterschiedlich. Einige halten es beispielsweise für richtig, die Finger bei der Geste „Stopp“ zu spreizen, während andere es intuitiver finden, die Finger zusammenzuhalten.
  • Einige Gesten sind möglicherweise unangenehm auszuführen. Verwenden Sie intuitive Gesten, die die Hände des Nutzers nicht überanstrengen.

Sekundäre Hand des Nutzers ermitteln

Das Android-System platziert die Systemnavigation auf der primären Hand des Nutzers, wie in den Systemeinstellungen angegeben. Verwenden Sie die sekundäre Hand für Ihre benutzerdefinierten Gesten, um Konflikte mit Systemnavigationsgesten zu vermeiden:

val handedness = Hand.getPrimaryHandSide(activity.contentResolver)
val secondaryHand = if (handedness == Hand.HandSide.LEFT) Hand.right(session) else Hand.left(session)
val handState = secondaryHand?.state ?: return
detectGesture(handState)


OpenXR™ und das OpenXR-Logo sind Marken von The Khronos Group Inc. und sind in China, der Europäischen Union, Japan und dem Vereinigten Königreich als Marke eingetragen.