Cómo trabajar con las manos con ARCore para Jetpack XR

ARCore para Jetpack XR puede proporcionar información sobre las manos detectadas del usuario y brinda información de la postura de las manos y sus articulaciones asociadas. Estos datos de la mano se pueden usar para unir entidades y modelos a las manos de un usuario, por ejemplo, un menú de herramientas:

Obtén una sesión

Accede a la información de las manos a través de un Session de Android XR. Consulta Cómo comprender el ciclo de vida de una sesión para obtener un Session.

Configura la sesión

El monitoreo de manos no está habilitado de forma predeterminada en las sesiones de XR. Para recibir datos de las manos, configura la sesión y establece el modo HandTrackingMode.BOTH:

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

Cómo recuperar datos de la mano

Los datos de las manos están disponibles para la mano izquierda y la derecha por separado. Usa el state de cada mano para acceder a las posiciones de la pose de cada articulación:

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

Las manos tienen las siguientes propiedades:

  • trackingState: Indica si se está haciendo un seguimiento de la mano.
  • handJoints: Es un mapa de articulaciones de la mano a poses. Las poses de las articulaciones de la mano se especifican según los estándares de OpenXR.

Cómo usar los datos de las manos en tu app

Las posiciones de las articulaciones de la mano de un usuario se pueden usar para anclar objetos 3D a las manos del usuario, por ejemplo, para unir un modelo a la palma izquierda:

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

O bien, para colocar un modelo en la punta del dedo índice de la mano derecha, haz lo siguiente:

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

Detecta gestos básicos con la mano

Usar las posiciones de las articulaciones de la mano para detectar gestos básicos con la mano Consulta las Convenciones de las articulaciones de la mano para determinar en qué rango de poses deben estar las articulaciones para registrarse como una pose determinada.

Por ejemplo, para detectar un pellizco con el pulgar y el índice, usa la distancia entre las dos articulaciones de la punta:

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 ejemplo de un gesto más complicado es el de "detenerse". En este gesto, cada dedo debe estar extendido, es decir, cada articulación de cada dedo debe apuntar aproximadamente en la misma dirección:

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)

Ten en cuenta los siguientes puntos cuando desarrolles una detección personalizada para los gestos con las manos:

  • Es posible que los usuarios tengan una interpretación diferente de cualquier gesto. Por ejemplo, algunas personas pueden considerar que el gesto de "detenerse" se realiza con los dedos separados, mientras que otras pueden encontrar más intuitivo tener los dedos juntos.
  • Es posible que algunas posturas sean incómodas de mantener. Usa gestos intuitivos que no cansen las manos del usuario.

Cómo determinar la mano secundaria del usuario

El sistema Android coloca la navegación del sistema en la mano principal del usuario, según lo especifica el usuario en las preferencias del sistema. Usa la mano secundaria para tus gestos personalizados y evitar conflictos con los gestos de navegación del sistema:

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™ y el logotipo de OpenXR son marcas comerciales propiedad de The Khronos Group Inc. y están registradas como marcas comerciales en China, la Unión Europea, Japón y el Reino Unido.