diff --git a/Runtime/Tracking/Scripts/Utils/OVRSkeletonBoneIdExtensions.cs b/Runtime/Tracking/Scripts/Utils/OVRSkeletonBoneIdExtensions.cs new file mode 100644 index 0000000..9a7d7d9 --- /dev/null +++ b/Runtime/Tracking/Scripts/Utils/OVRSkeletonBoneIdExtensions.cs @@ -0,0 +1,139 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +namespace Oculus.Movement.Utils +{ + /// + /// Bone ID helpers for hand skeletons. + /// Additive workaround for oculus-samples/Unity-Movement#120: inside + /// , the legacy OVR hand (Hand_*) + /// and the OpenXR hand (XRHand_*) members fully overlap + /// numerically (for example, Hand_IndexTip and + /// XRHand_RingTip are both 20). OVRSkeleton.BoneLabelFromBoneId + /// dispatches on numeric value only, so a legacy Hand_* ID passed + /// while using SkeletonType.XRHandLeft/XRHandRight resolves to the wrong + /// joint (for example, Hand_IndexTip matches + /// XRHand_RingTip and returns the ring fingertip instead of the + /// index fingertip). + /// + public static class OVRSkeletonBoneIdExtensions + { + /// + /// Converts a legacy OVR hand (Hand_*) bone ID into the + /// equivalent OpenXR hand (XRHand_*) bone ID, so that lookups + /// written against SkeletonType.HandLeft/HandRight also resolve to + /// the correct joint on SkeletonType.XRHandLeft/XRHandRight. + /// The mapping follows the phalange annotations of + /// (for example, Hand_Index1 is the + /// index *proximal* phalange and maps to XRHand_IndexProximal, while + /// Hand_Pinky0 is the pinky *metacarpal* and maps to + /// XRHand_LittleMetacarpal; Hand_ForearmStub maps to XRHand_Wrist + /// because OpenXR hand skeletons have no forearm joint). + /// Contract: the input is intended to be a legacy Hand_* ID. + /// Do not pass native XRHand_* values through this method: + /// because the two ID families overlap numerically (for example, + /// XRHand_IndexTip == Hand_Middle2 == 10), a native ID would be + /// translated incorrectly. IDs outside the Hand_* set are + /// returned unchanged. + /// + /// Legacy OVR hand (Hand_*) bone ID. + /// Equivalent OpenXR hand (XRHand_*) bone ID. + public static OVRSkeleton.BoneId ToXRHandBoneId(this OVRSkeleton.BoneId handBoneId) + { + switch (handBoneId) + { + case OVRSkeleton.BoneId.Hand_WristRoot: + return OVRSkeleton.BoneId.XRHand_Wrist; + case OVRSkeleton.BoneId.Hand_ForearmStub: + return OVRSkeleton.BoneId.XRHand_Wrist; + case OVRSkeleton.BoneId.Hand_Thumb0: + return OVRSkeleton.BoneId.XRHand_ThumbMetacarpal; + case OVRSkeleton.BoneId.Hand_Thumb1: + return OVRSkeleton.BoneId.XRHand_ThumbProximal; + case OVRSkeleton.BoneId.Hand_Thumb2: + return OVRSkeleton.BoneId.XRHand_ThumbDistal; + case OVRSkeleton.BoneId.Hand_Thumb3: + return OVRSkeleton.BoneId.XRHand_ThumbTip; + case OVRSkeleton.BoneId.Hand_Index1: + return OVRSkeleton.BoneId.XRHand_IndexProximal; + case OVRSkeleton.BoneId.Hand_Index2: + return OVRSkeleton.BoneId.XRHand_IndexIntermediate; + case OVRSkeleton.BoneId.Hand_Index3: + return OVRSkeleton.BoneId.XRHand_IndexDistal; + case OVRSkeleton.BoneId.Hand_IndexTip: + return OVRSkeleton.BoneId.XRHand_IndexTip; + case OVRSkeleton.BoneId.Hand_Middle1: + return OVRSkeleton.BoneId.XRHand_MiddleProximal; + case OVRSkeleton.BoneId.Hand_Middle2: + return OVRSkeleton.BoneId.XRHand_MiddleIntermediate; + case OVRSkeleton.BoneId.Hand_Middle3: + return OVRSkeleton.BoneId.XRHand_MiddleDistal; + case OVRSkeleton.BoneId.Hand_MiddleTip: + return OVRSkeleton.BoneId.XRHand_MiddleTip; + case OVRSkeleton.BoneId.Hand_Ring1: + return OVRSkeleton.BoneId.XRHand_RingProximal; + case OVRSkeleton.BoneId.Hand_Ring2: + return OVRSkeleton.BoneId.XRHand_RingIntermediate; + case OVRSkeleton.BoneId.Hand_Ring3: + return OVRSkeleton.BoneId.XRHand_RingDistal; + case OVRSkeleton.BoneId.Hand_RingTip: + return OVRSkeleton.BoneId.XRHand_RingTip; + case OVRSkeleton.BoneId.Hand_Pinky0: + return OVRSkeleton.BoneId.XRHand_LittleMetacarpal; + case OVRSkeleton.BoneId.Hand_Pinky1: + return OVRSkeleton.BoneId.XRHand_LittleProximal; + case OVRSkeleton.BoneId.Hand_Pinky2: + return OVRSkeleton.BoneId.XRHand_LittleIntermediate; + case OVRSkeleton.BoneId.Hand_Pinky3: + return OVRSkeleton.BoneId.XRHand_LittleDistal; + case OVRSkeleton.BoneId.Hand_ThumbTip: + return OVRSkeleton.BoneId.XRHand_ThumbTip; + case OVRSkeleton.BoneId.Hand_PinkyTip: + return OVRSkeleton.BoneId.XRHand_LittleTip; + default: + return handBoneId; + } + } + + /// + /// Returns the finger-tip bone ID that matches the given skeleton + /// type: an XRHand_*Tip ID for + /// SkeletonType.XRHandLeft/XRHandRight, or a Hand_*Tip ID for + /// the legacy OVR hand skeleton types. This mirrors the + /// per-skeleton-type tip selection that the Meta XR Core SDK itself + /// performs in OVRVirtualKeyboard. Note that Hand_PinkyTip maps to + /// XRHand_LittleTip because OpenXR names the little finger "little". + /// + /// Skeleton type of the OVRSkeleton being queried. + /// Finger whose tip bone ID is requested. + /// The finger-tip bone ID matching the skeleton type. + public static OVRSkeleton.BoneId GetFingerTipBoneId(OVRSkeleton.SkeletonType skeletonType, + OVRHand.HandFinger finger) + { + OVRSkeleton.BoneId legacyTipId; + switch (finger) + { + case OVRHand.HandFinger.Thumb: + legacyTipId = OVRSkeleton.BoneId.Hand_ThumbTip; + break; + case OVRHand.HandFinger.Index: + legacyTipId = OVRSkeleton.BoneId.Hand_IndexTip; + break; + case OVRHand.HandFinger.Middle: + legacyTipId = OVRSkeleton.BoneId.Hand_MiddleTip; + break; + case OVRHand.HandFinger.Ring: + legacyTipId = OVRSkeleton.BoneId.Hand_RingTip; + break; + case OVRHand.HandFinger.Pinky: + legacyTipId = OVRSkeleton.BoneId.Hand_PinkyTip; + break; + default: + legacyTipId = OVRSkeleton.BoneId.Invalid; + break; + } + bool isOpenXRHand = skeletonType == OVRSkeleton.SkeletonType.XRHandLeft || + skeletonType == OVRSkeleton.SkeletonType.XRHandRight; + return isOpenXRHand ? legacyTipId.ToXRHandBoneId() : legacyTipId; + } + } +}