ARCore für Jetpack XR kann Informationen zu den erkannten Händen des Nutzers sowie Informationen zur Haltung der Hände und der zugehörigen Gelenke liefern. Mit diesen Handdaten können Entitäten und Modelle an die Hände eines Nutzers angehängt werden, z. B. ein Tool-Menü:
Sitzung abrufen
Über ein Android XR-Gerät Session
auf Handinformationen zugreifen Weitere Informationen zum Abrufen einer Session
finden Sie unter Lebenszyklus einer Sitzung.
Sitzung konfigurieren
Das Hand-Tracking ist in XR-Sitzungen standardmäßig nicht aktiviert. So konfigurierst du die Sitzung, um Handdaten zu erhalten:
val newConfig = session.config.copy( handTracking = Config.HandTrackingMode.Enabled ) when (val result = session.configure(newConfig)) { is SessionConfigureConfigurationNotSupported -> TODO(/* Some combinations of configurations are not valid. Handle this failure case. */) is SessionConfigurePermissionsNotGranted -> TODO(/* The required permissions in result.permissions have not been granted. */) is SessionConfigureSuccess -> TODO(/* Success! */) }
Handdaten abrufen
Daten zu Handbewegungen sind für die linke und rechte Hand separat verfügbar. Verwenden Sie die state
der jeweiligen Hand, um auf die Pose-Positionen für jeden Gelenkpunkt 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 folgende Eigenschaften:
isActive
: ob die Hand erfasst wird.handJoints
: eine Zuordnung von Handgelenken zu Posen. Die Handgelenkspositionen werden gemäß den OpenXR-Standards angegeben.
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.setHidden(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.setHidden(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))
Grundlegende Handbewegungen erkennen
Anhand der Positionen der Gelenke in der Hand werden einfache Handgesten erkannt. Sehen Sie sich die Konventionen für Handgelenke an, um zu bestimmen, in welchem Bereich sich die Gelenke befinden sollten, damit sie als eine bestimmte Pose erkannt werden.
Wenn Sie beispielsweise ein Zusammenziehen mit dem Daumen und dem Zeigefinger erkennen möchten, verwenden Sie den Abstand zwischen den beiden Endgelenken:
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 komplexere Geste ist die Geste „Anhalten“. Bei dieser Geste sollten alle Finger ausgestreckt sein, d. h., alle Gelenke in jedem Finger sollten in etwa in dieselbe Richtung zeigen:
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 einer benutzerdefinierten Erkennung für Handgesten die folgenden Punkte:
- Nutzer können eine bestimmte Geste unterschiedlich interpretieren. Beispielsweise könnte für manche Nutzer eine Geste für „Stopp“ bedeuten, dass die Finger gespreizt sind, während andere es intuitiver finden, wenn die Finger nah beieinander sind.
- Einige Gesten sind möglicherweise unbequem. Verwenden Sie intuitive Gesten, die die Hände der Nutzer nicht belasten.
Sekundäre Hand des Nutzers ermitteln
Das Android-System platziert die Systemnavigation auf der dominanten Hand des Nutzers, wie vom Nutzer in den Systemeinstellungen angegeben. Verwenden Sie die sekundäre Hand für Ihre benutzerdefinierten Gesten, um Konflikte mit Systemnavigationsgesten zu vermeiden:
val handedness = Hand.getHandedness(activity.contentResolver) val secondaryHand = if (handedness == Hand.Handedness.LEFT) Hand.right(session) else Hand.left(session) val handState = secondaryHand?.state ?: return detectGesture(handState)