Bekerja dengan tangan menggunakan ARCore untuk Jetpack XR

ARCore untuk Jetpack XR dapat memberikan informasi tentang tangan pengguna yang terdeteksi, dan memberikan informasi pose untuk tangan dan sendi terkaitnya. Data tangan ini dapat digunakan untuk melampirkan entitas dan model ke tangan pengguna, misalnya, menu alat:

Mendapatkan sesi

Akses informasi tangan melalui Session Android XR. Lihat Memahami siklus proses Sesi untuk mendapatkan Session.

Mengonfigurasi sesi

Pelacakan tangan tidak diaktifkan secara default pada sesi XR. Untuk menerima data tangan, konfigurasi sesi:

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

Mengambil data tangan

Data tangan tersedia untuk tangan kiri dan kanan secara terpisah. Gunakan state setiap tangan untuk mengakses posisi pose untuk setiap sendi:

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

Tangan memiliki properti berikut:

  • isActive: apakah tangan sedang dilacak atau tidak.
  • handJoints: peta sendi tangan ke pose. Pose sendi tangan ditentukan oleh standar OpenXR.

Menggunakan data tangan di aplikasi Anda

Posisi sendi tangan pengguna dapat digunakan untuk mengikat objek 3D ke tangan pengguna, misalnya, untuk memasang model ke telapak tangan kiri:

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

Atau untuk memasang model ke ujung jari telunjuk tangan kanan Anda:

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

Mendeteksi gestur tangan dasar

Gunakan pose sendi di tangan untuk mendeteksi gestur tangan dasar. Lihat Konvensi sendi tangan untuk menentukan rentang pose yang harus dimiliki sendi agar terdaftar sebagai pose tertentu.

Misalnya, untuk mendeteksi cubit dengan ibu jari dan jari telunjuk, gunakan jarak antara dua sendi ujung:

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

Contoh gestur yang lebih rumit adalah gestur "berhenti". Dalam gestur ini, setiap jari harus direntangkan, yaitu, setiap sendi di setiap jari harus secara kasar mengarah ke arah yang sama:

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)

Perhatikan poin-poin berikut saat mengembangkan deteksi kustom untuk gestur tangan:

  • Pengguna mungkin memiliki interpretasi yang berbeda terhadap gestur tertentu. Misalnya, beberapa orang mungkin menganggap gestur "berhenti" dengan jari yang terentang, sedangkan orang lain mungkin merasa lebih intuitif jika jari-jarinya saling berdekatan.
  • Beberapa gestur mungkin tidak nyaman untuk dipertahankan. Gunakan gestur intuitif yang tidak membebani tangan pengguna.

Menentukan tangan sekunder pengguna

Sistem Android menempatkan navigasi sistem di tangan utama pengguna, seperti yang ditentukan oleh pengguna di preferensi sistem. Gunakan tangan sekunder untuk gestur kustom Anda guna menghindari konflik dengan gestur navigasi sistem:

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)