Trabalhar com as mãos usando o ARCore para Jetpack XR

O ARCore para Jetpack XR pode fornecer informações sobre as mãos detectadas do usuário e informações de postura para as mãos e as articulações associadas. Esses dados podem ser usados para anexar entidades e modelos às mãos de um usuário, por exemplo, um menu de ferramentas:

Conseguir uma sessão

Acesse informações sobre as mãos usando um Session do Android XR. Consulte Entender o ciclo de vida de uma sessão para receber um Session.

Configurar a sessão

O rastreamento de mãos não está ativado por padrão nas sessões de XR. Para receber dados de mãos, configure a sessão e defina o 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. */)
}

Recuperar dados de mãos

Os dados das mãos estão disponíveis separadamente para a esquerda e a direita. Use o state de cada mão para acessar as posições de postura de cada articulação:

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

As mãos têm as seguintes propriedades:

  • trackingState: se a mão está sendo rastreada ou não.
  • handJoints: um mapa de articulações da mão para poses. As poses das articulações das mãos são especificadas pelos padrões OpenXR.

Usar dados de mãos no seu app

As posições das articulações das mãos de um usuário podem ser usadas para ancorar objetos 3D nas mãos de um usuário, por exemplo, para anexar um modelo à palma da mão esquerda:

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

Ou para anexar um modelo à ponta do dedo indicador da mão direita:

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

Detectar gestos básicos com as mãos

Usar as poses das articulações na mão para detectar gestos básicos. Consulte as Convenções de articulações das mãos para determinar em qual intervalo de poses as articulações precisam estar para serem registradas como uma determinada pose.

Por exemplo, para detectar um gesto de pinça com o polegar e o indicador, use a distância entre as duas articulações das pontas:

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

Um exemplo de gesto mais complicado é o de "parar". Nesse gesto, cada dedo precisa estar esticado, ou seja, cada articulação de cada dedo precisa estar apontando aproximadamente na mesma direção:

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)

Considere os seguintes pontos ao desenvolver uma detecção personalizada para gestos com as mãos:

  • Os usuários podem ter uma interpretação diferente de qualquer gesto. Por exemplo, algumas pessoas consideram que um gesto de "parar" tem os dedos abertos, enquanto outras acham mais intuitivo ter os dedos juntos.
  • Alguns gestos podem ser desconfortáveis de manter. Use gestos intuitivos que não exijam muito das mãos do usuário.

Determinar a mão secundária do usuário

O sistema Android coloca a navegação do sistema na mão principal do usuário, conforme especificado nas preferências do sistema. Use a mão secundária para seus gestos personalizados e evite conflitos com os gestos de navegação do 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™ e o logotipo OpenXR são marcas registradas de propriedade da Khronos Group Inc. e estão registradas como marcas comerciais na China, na União Europeia, no Japão e no Reino Unido.