Lavorare con le mani utilizzando ARCore per Jetpack XR

ARCore per Jetpack XR può fornire informazioni sulle mani rilevate dall'utente e sulle relative articolazioni. Questi dati sulle mani possono essere utilizzati per associare entità e modelli alle mani di un utente, ad esempio un menu strumenti:

Ottenere una sessione

Accedi alle informazioni sulle mani tramite un dispositivo Android XR Session. Consulta la sezione Comprendere il ciclo di vita di una sessione per ottenere un Session.

Configura la sessione

Il rilevamento delle mani non è attivo per impostazione predefinita nelle sessioni XR. Per ricevere i dati della mano, configura la sessione:

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! */)
}

Recuperare i dati della mano

I dati relativi alle mani sono disponibili separatamente per la mano sinistra e la mano destra. Usa state di ogni mano per accedere alle posizioni delle pose per ogni articolazione:

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)
}

Le mani hanno le seguenti proprietà:

  • isActive: indica se la mano è in fase di monitoraggio.
  • handJoints: una mappa delle articolazioni delle mani alle pose. Le pose delle articolazioni delle mani sono specificate dagli standard OpenXR.

Utilizzare i dati delle mani nell'app

Le posizioni delle articolazioni delle mani di un utente possono essere utilizzate per ancorare oggetti 3D alle mani dell'utente, ad esempio per attaccare un modello al palmo sinistro:

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))

In alternativa, per collegare un modello alla punta del dito indice della mano destra:

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))

Rileva i gesti di base delle mani

Utilizza le posizioni delle articolazioni della mano per rilevare i gesti di base delle mani. Consulta le Convenzioni delle articolazioni delle mani per determinare l'intervallo di pose in cui devono trovarsi le articolazioni per essere registrate come una determinata posa.

Ad esempio, per rilevare un pizzicotto con il pollice e l'indice, utilizza la distanza tra le due articolazioni delle punte:

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

Un esempio di gesto più complicato è il gesto "stop". In questo gesto, ogni dito deve essere allungato, ovvero ogni articolazione di ogni dito deve essere rivolta approssimativamente nella stessa direzione:

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)

Tieni presente i seguenti punti quando sviluppi il rilevamento personalizzato per i gesti delle mani:

  • Gli utenti potrebbero avere un'interpretazione diversa di un determinato gesto. Ad esempio, alcuni potrebbero considerare un gesto di "stop" con le dita aperte, mentre altri potrebbero trovare più intuitivo tenere le dita unite.
  • Alcuni gesti potrebbero essere scomodi da mantenere. Utilizza gesti intuitivi che non sollecitano le mani dell'utente.

Determinare la mano secondaria dell'utente

Il sistema Android posiziona la navigazione di sistema sulla mano principale dell'utente, come specificato dall'utente nelle preferenze di sistema. Utilizza la mano secondaria per i gesti personalizzati per evitare conflitti con i gesti di navigazione di sistema:

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)