العمل باستخدام اليدين باستخدام ARCore لـ Jetpack XR

يمكن أن يوفّر ARCore لـ Jetpack XR معلومات عن أيدي المستخدم التي تم رصدها، ويقدّم معلومات عن وضعية الأيدي والمفاصل المرتبطة بها. يمكن استخدام بيانات اليد هذه لإرفاق عناصر ونماذج بأيدي المستخدم، على سبيل المثال، قائمة الأدوات:

الحصول على جلسة

الوصول إلى معلومات اليد من خلال نظارات Android XR Session اطّلِع على مقالة فهم رحلة جلسة للحصول على Session.

ضبط الجلسة

لا تكون ميزة "تتبُّع اليد" مفعَّلة تلقائيًا في جلسات الواقع المعزّز. لتلقّي بيانات اليد، عليك ضبط الجلسة:

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

استرداد بيانات اليد

تتوفّر بيانات اليد اليمنى واليسرى بشكل منفصل. استخدِم رمز state في كل يد للوصول إلى أوضاع الوضع لكل مفصل:

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

تمتلك الأيدي السمات التالية:

  • isActive: ما إذا كان يتم تتبُّع اليد أم لا
  • handJoints: خريطة لمفاصل اليد إلى الوضعيات يتم تحديد أوضاع مفاصل اليد من خلال معايير OpenXR.

استخدام بيانات اليد في تطبيقك

يمكن استخدام مواضع مفاصل يد المستخدم للقيام بتثبيت أجسام ثلاثية الأبعاد بأيدي المستخدم، على سبيل المثال، لربط نموذج براحة اليد اليسرى:

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

أو لإرفاق نموذج بطرف إصبع السبابة الأيمن:

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

رصد إيماءات اليد الأساسية

استخدِم أوضاع المفاصل في اليد لرصد إيماءات اليد الأساسية. راجِع المصطلحات المتعلّقة بمفاصل اليد لتحديد نطاق الوضعيات التي يجب أن تكون فيها المفاصل لتسجيلها كوضعية معيّنة.

على سبيل المثال، لرصد الضغط باستخدام الإبهام والسبابة، استخدِم المسافة بين مفاصل طرفَي الإصبعَين:

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

من الأمثلة على الإيماءات الأكثر تعقيدًا إيماءة "إيقاف". في هذه الإيماءة، يجب مدّ كل إصبع، أي أن يكون كل مفصل في كل إصبع موجهًا تقريبًا في الاتجاه نفسه:

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)

يُرجى مراعاة النقاط التالية عند تطوير ميزة رصد مخصّصة لصنّاع محتوى المحتوى اليدوي:

  • قد يفسّر المستخدمون أي إيماءة معيّنة بشكل مختلف. على سبيل المثال، قد يعتقد البعض أنّ إيماءة "التوقف" هي ضم الأصابع معًا، بينما قد يجد البعض الآخر أنّه من البديهي تقريب الأصابع من بعضها.
  • قد لا تكون بعض الإيماءات مريحة. استخدِم إيماءات سهلة متعارفة لا تُجهد يدي المستخدم.

تحديد اليد الثانوية للمستخدم

يضع نظام Android عناصر التنقّل في النظام على اليد الأساسية للمستخدم، على النحو الذي يحدّده المستخدم في الإعدادات المفضّلة للنظام. استخدِم اليد الثانوية للإيماءات المخصّصة لتجنّب التعارض مع إيماءات التنقّل في النظام:

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)