/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; public class OVRSkeleton : MonoBehaviour { public interface IOVRSkeletonDataProvider { SkeletonType GetSkeletonType(); SkeletonPoseData GetSkeletonPoseData(); bool enabled { get; } } public struct SkeletonPoseData { public OVRPlugin.Posef RootPose { get; set; } public float RootScale { get; set; } public OVRPlugin.Quatf[] BoneRotations { get; set; } public bool IsDataValid { get; set; } public bool IsDataHighConfidence { get; set; } public OVRPlugin.Vector3f[] BoneTranslations { get; set; } public int SkeletonChangedCount { get; set; } } public enum SkeletonType { None = OVRPlugin.SkeletonType.None, HandLeft = OVRPlugin.SkeletonType.HandLeft, HandRight = OVRPlugin.SkeletonType.HandRight, Body = OVRPlugin.SkeletonType.Body, } public enum BoneId { Invalid = OVRPlugin.BoneId.Invalid, // hand bones Hand_Start = OVRPlugin.BoneId.Hand_Start, Hand_WristRoot = OVRPlugin.BoneId.Hand_WristRoot, // root frame of the hand, where the wrist is located Hand_ForearmStub = OVRPlugin.BoneId.Hand_ForearmStub, // frame for user's forearm Hand_Thumb0 = OVRPlugin.BoneId.Hand_Thumb0, // thumb trapezium bone Hand_Thumb1 = OVRPlugin.BoneId.Hand_Thumb1, // thumb metacarpal bone Hand_Thumb2 = OVRPlugin.BoneId.Hand_Thumb2, // thumb proximal phalange bone Hand_Thumb3 = OVRPlugin.BoneId.Hand_Thumb3, // thumb distal phalange bone Hand_Index1 = OVRPlugin.BoneId.Hand_Index1, // index proximal phalange bone Hand_Index2 = OVRPlugin.BoneId.Hand_Index2, // index intermediate phalange bone Hand_Index3 = OVRPlugin.BoneId.Hand_Index3, // index distal phalange bone Hand_Middle1 = OVRPlugin.BoneId.Hand_Middle1, // middle proximal phalange bone Hand_Middle2 = OVRPlugin.BoneId.Hand_Middle2, // middle intermediate phalange bone Hand_Middle3 = OVRPlugin.BoneId.Hand_Middle3, // middle distal phalange bone Hand_Ring1 = OVRPlugin.BoneId.Hand_Ring1, // ring proximal phalange bone Hand_Ring2 = OVRPlugin.BoneId.Hand_Ring2, // ring intermediate phalange bone Hand_Ring3 = OVRPlugin.BoneId.Hand_Ring3, // ring distal phalange bone Hand_Pinky0 = OVRPlugin.BoneId.Hand_Pinky0, // pinky metacarpal bone Hand_Pinky1 = OVRPlugin.BoneId.Hand_Pinky1, // pinky proximal phalange bone Hand_Pinky2 = OVRPlugin.BoneId.Hand_Pinky2, // pinky intermediate phalange bone Hand_Pinky3 = OVRPlugin.BoneId.Hand_Pinky3, // pinky distal phalange bone Hand_MaxSkinnable = OVRPlugin.BoneId.Hand_MaxSkinnable, // Bone tips are position only. They are not used for skinning but are useful for hit-testing. // NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous Hand_ThumbTip = OVRPlugin.BoneId.Hand_ThumbTip, // tip of the thumb Hand_IndexTip = OVRPlugin.BoneId.Hand_IndexTip, // tip of the index finger Hand_MiddleTip = OVRPlugin.BoneId.Hand_MiddleTip, // tip of the middle finger Hand_RingTip = OVRPlugin.BoneId.Hand_RingTip, // tip of the ring finger Hand_PinkyTip = OVRPlugin.BoneId.Hand_PinkyTip, // tip of the pinky Hand_End = OVRPlugin.BoneId.Hand_End, // body bones Body_Start = OVRPlugin.BoneId.Body_Start, Body_Root = OVRPlugin.BoneId.Body_Root, Body_Hips = OVRPlugin.BoneId.Body_Hips, Body_SpineLower = OVRPlugin.BoneId.Body_SpineLower, Body_SpineMiddle = OVRPlugin.BoneId.Body_SpineMiddle, Body_SpineUpper = OVRPlugin.BoneId.Body_SpineUpper, Body_Chest = OVRPlugin.BoneId.Body_Chest, Body_Neck = OVRPlugin.BoneId.Body_Neck, Body_Head = OVRPlugin.BoneId.Body_Head, Body_LeftShoulder = OVRPlugin.BoneId.Body_LeftShoulder, Body_LeftScapula = OVRPlugin.BoneId.Body_LeftScapula, Body_LeftArmUpper = OVRPlugin.BoneId.Body_LeftArmUpper, Body_LeftArmLower = OVRPlugin.BoneId.Body_LeftArmLower, Body_LeftHandWristTwist = OVRPlugin.BoneId.Body_LeftHandWristTwist, Body_RightShoulder = OVRPlugin.BoneId.Body_RightShoulder, Body_RightScapula = OVRPlugin.BoneId.Body_RightScapula, Body_RightArmUpper = OVRPlugin.BoneId.Body_RightArmUpper, Body_RightArmLower = OVRPlugin.BoneId.Body_RightArmLower, Body_RightHandWristTwist = OVRPlugin.BoneId.Body_RightHandWristTwist, Body_LeftHandPalm = OVRPlugin.BoneId.Body_LeftHandPalm, Body_LeftHandWrist = OVRPlugin.BoneId.Body_LeftHandWrist, Body_LeftHandThumbMetacarpal = OVRPlugin.BoneId.Body_LeftHandThumbMetacarpal, Body_LeftHandThumbProximal = OVRPlugin.BoneId.Body_LeftHandThumbProximal, Body_LeftHandThumbDistal = OVRPlugin.BoneId.Body_LeftHandThumbDistal, Body_LeftHandThumbTip = OVRPlugin.BoneId.Body_LeftHandThumbTip, Body_LeftHandIndexMetacarpal = OVRPlugin.BoneId.Body_LeftHandIndexMetacarpal, Body_LeftHandIndexProximal = OVRPlugin.BoneId.Body_LeftHandIndexProximal, Body_LeftHandIndexIntermediate = OVRPlugin.BoneId.Body_LeftHandIndexIntermediate, Body_LeftHandIndexDistal = OVRPlugin.BoneId.Body_LeftHandIndexDistal, Body_LeftHandIndexTip = OVRPlugin.BoneId.Body_LeftHandIndexTip, Body_LeftHandMiddleMetacarpal = OVRPlugin.BoneId.Body_LeftHandMiddleMetacarpal, Body_LeftHandMiddleProximal = OVRPlugin.BoneId.Body_LeftHandMiddleProximal, Body_LeftHandMiddleIntermediate = OVRPlugin.BoneId.Body_LeftHandMiddleIntermediate, Body_LeftHandMiddleDistal = OVRPlugin.BoneId.Body_LeftHandMiddleDistal, Body_LeftHandMiddleTip = OVRPlugin.BoneId.Body_LeftHandMiddleTip, Body_LeftHandRingMetacarpal = OVRPlugin.BoneId.Body_LeftHandRingMetacarpal, Body_LeftHandRingProximal = OVRPlugin.BoneId.Body_LeftHandRingProximal, Body_LeftHandRingIntermediate = OVRPlugin.BoneId.Body_LeftHandRingIntermediate, Body_LeftHandRingDistal = OVRPlugin.BoneId.Body_LeftHandRingDistal, Body_LeftHandRingTip = OVRPlugin.BoneId.Body_LeftHandRingTip, Body_LeftHandLittleMetacarpal = OVRPlugin.BoneId.Body_LeftHandLittleMetacarpal, Body_LeftHandLittleProximal = OVRPlugin.BoneId.Body_LeftHandLittleProximal, Body_LeftHandLittleIntermediate = OVRPlugin.BoneId.Body_LeftHandLittleIntermediate, Body_LeftHandLittleDistal = OVRPlugin.BoneId.Body_LeftHandLittleDistal, Body_LeftHandLittleTip = OVRPlugin.BoneId.Body_LeftHandLittleTip, Body_RightHandPalm = OVRPlugin.BoneId.Body_RightHandPalm, Body_RightHandWrist = OVRPlugin.BoneId.Body_RightHandWrist, Body_RightHandThumbMetacarpal = OVRPlugin.BoneId.Body_RightHandThumbMetacarpal, Body_RightHandThumbProximal = OVRPlugin.BoneId.Body_RightHandThumbProximal, Body_RightHandThumbDistal = OVRPlugin.BoneId.Body_RightHandThumbDistal, Body_RightHandThumbTip = OVRPlugin.BoneId.Body_RightHandThumbTip, Body_RightHandIndexMetacarpal = OVRPlugin.BoneId.Body_RightHandIndexMetacarpal, Body_RightHandIndexProximal = OVRPlugin.BoneId.Body_RightHandIndexProximal, Body_RightHandIndexIntermediate = OVRPlugin.BoneId.Body_RightHandIndexIntermediate, Body_RightHandIndexDistal = OVRPlugin.BoneId.Body_RightHandIndexDistal, Body_RightHandIndexTip = OVRPlugin.BoneId.Body_RightHandIndexTip, Body_RightHandMiddleMetacarpal = OVRPlugin.BoneId.Body_RightHandMiddleMetacarpal, Body_RightHandMiddleProximal = OVRPlugin.BoneId.Body_RightHandMiddleProximal, Body_RightHandMiddleIntermediate = OVRPlugin.BoneId.Body_RightHandMiddleIntermediate, Body_RightHandMiddleDistal = OVRPlugin.BoneId.Body_RightHandMiddleDistal, Body_RightHandMiddleTip = OVRPlugin.BoneId.Body_RightHandMiddleTip, Body_RightHandRingMetacarpal = OVRPlugin.BoneId.Body_RightHandRingMetacarpal, Body_RightHandRingProximal = OVRPlugin.BoneId.Body_RightHandRingProximal, Body_RightHandRingIntermediate = OVRPlugin.BoneId.Body_RightHandRingIntermediate, Body_RightHandRingDistal = OVRPlugin.BoneId.Body_RightHandRingDistal, Body_RightHandRingTip = OVRPlugin.BoneId.Body_RightHandRingTip, Body_RightHandLittleMetacarpal = OVRPlugin.BoneId.Body_RightHandLittleMetacarpal, Body_RightHandLittleProximal = OVRPlugin.BoneId.Body_RightHandLittleProximal, Body_RightHandLittleIntermediate = OVRPlugin.BoneId.Body_RightHandLittleIntermediate, Body_RightHandLittleDistal = OVRPlugin.BoneId.Body_RightHandLittleDistal, Body_RightHandLittleTip = OVRPlugin.BoneId.Body_RightHandLittleTip, Body_End = OVRPlugin.BoneId.Body_End, // add new bones here Max = OVRPlugin.BoneId.Max, } [SerializeField] protected SkeletonType _skeletonType = SkeletonType.None; [SerializeField] private IOVRSkeletonDataProvider _dataProvider; [SerializeField] private bool _updateRootPose = false; [SerializeField] private bool _updateRootScale = false; [SerializeField] private bool _enablePhysicsCapsules = false; [SerializeField] private bool _applyBoneTranslations = true; private GameObject _bonesGO; private GameObject _bindPosesGO; private GameObject _capsulesGO; protected List _bones; private List _bindPoses; private List _capsules; protected OVRPlugin.Skeleton2 _skeleton = new OVRPlugin.Skeleton2(); private readonly Quaternion wristFixupRotation = new Quaternion(0.0f, 1.0f, 0.0f, 0.0f); public bool IsInitialized { get; private set; } public bool IsDataValid { get; private set; } public bool IsDataHighConfidence { get; private set; } public IList Bones { get; protected set; } public IList BindPoses { get; private set; } public IList Capsules { get; private set; } public SkeletonType GetSkeletonType() { return _skeletonType; } internal virtual void SetSkeletonType(SkeletonType type) { _skeletonType = type; } public bool IsValidBone(BoneId bone) { return OVRPlugin.IsValidBone((OVRPlugin.BoneId)bone, (OVRPlugin.SkeletonType)_skeletonType); } public int SkeletonChangedCount { get; private set; } protected virtual void Awake() { if (_dataProvider == null) { var foundDataProvider = SearchSkeletonDataProvider(); if (foundDataProvider != null) { _dataProvider = foundDataProvider; if (_dataProvider is MonoBehaviour mb) { Debug.Log($"Found IOVRSkeletonDataProvider reference in {mb.name} due to unassigned field."); } } } _bones = new List(); Bones = _bones.AsReadOnly(); _bindPoses = new List(); BindPoses = _bindPoses.AsReadOnly(); _capsules = new List(); Capsules = _capsules.AsReadOnly(); } internal IOVRSkeletonDataProvider SearchSkeletonDataProvider() { var dataProviders = gameObject.GetComponentsInParent(); foreach (var dataProvider in dataProviders) { if (dataProvider.GetSkeletonType() == _skeletonType) { return dataProvider; } } return null; } /// /// Start this instance. /// Initialize data structures. /// protected virtual void Start() { if (_dataProvider == null && _skeletonType == SkeletonType.Body) { Debug.LogWarning("OVRSkeleton and its subclasses requires OVRBody to function."); } if (ShouldInitialize()) { Initialize(); } } private bool ShouldInitialize() { if (IsInitialized) { return false; } if (_dataProvider != null && !_dataProvider.enabled) { return false; } if (_skeletonType == SkeletonType.None) { return false; } else if (IsHandSkeleton(_skeletonType)) { #if UNITY_EDITOR return OVRInput.IsControllerConnected(OVRInput.Controller.Hands); #else return true; #endif } else { return true; } } private void Initialize() { if (OVRPlugin.GetSkeleton2((OVRPlugin.SkeletonType)_skeletonType, ref _skeleton)) { InitializeBones(); InitializeBindPose(); InitializeCapsules(); IsInitialized = true; } } protected virtual Transform GetBoneTransform(BoneId boneId) => null; protected virtual void InitializeBones() { bool flipX = IsHandSkeleton(_skeletonType); if (!_bonesGO) { _bonesGO = new GameObject("Bones"); _bonesGO.transform.SetParent(transform, false); _bonesGO.transform.localPosition = Vector3.zero; _bonesGO.transform.localRotation = Quaternion.identity; } if (_bones == null || _bones.Count != _skeleton.NumBones) { _bones = new List(new OVRBone[_skeleton.NumBones]); Bones = _bones.AsReadOnly(); } bool newBonesCreated = false; // pre-populate bones list before attempting to apply bone hierarchy for (int i = 0; i < _bones.Count; ++i) { OVRBone bone = _bones[i] ?? (_bones[i] = new OVRBone()); bone.Id = (OVRSkeleton.BoneId)_skeleton.Bones[i].Id; bone.ParentBoneIndex = _skeleton.Bones[i].ParentBoneIndex; Assert.IsTrue((int)bone.Id >= 0 && bone.Id <= BoneId.Max); // don't create new bones each time; rely on // pre-existing bone transforms. if (bone.Transform == null) { newBonesCreated = true; bone.Transform = GetBoneTransform(bone.Id); if (bone.Transform == null) { bone.Transform = new GameObject(BoneLabelFromBoneId(_skeletonType, bone.Id)).transform; } } // if allocated bone here before, make sure the name is correct. if (GetBoneTransform(bone.Id) == null) { bone.Transform.name = BoneLabelFromBoneId(_skeletonType, bone.Id); } var pose = _skeleton.Bones[i].Pose; if (_applyBoneTranslations) { bone.Transform.localPosition = flipX ? pose.Position.FromFlippedXVector3f() : pose.Position.FromFlippedZVector3f(); } bone.Transform.localRotation = flipX ? pose.Orientation.FromFlippedXQuatf() : pose.Orientation.FromFlippedZQuatf(); } if (newBonesCreated) { for (int i = 0; i < _bones.Count; ++i) { if (!IsValidBone((BoneId)_bones[i].ParentBoneIndex) || IsBodySkeleton(_skeletonType)) // Body bones are always in tracking space { _bones[i].Transform.SetParent(_bonesGO.transform, false); } else { _bones[i].Transform.SetParent(_bones[_bones[i].ParentBoneIndex].Transform, false); } } } } private void InitializeBindPose() { if (!_bindPosesGO) { _bindPosesGO = new GameObject("BindPoses"); _bindPosesGO.transform.SetParent(transform, false); _bindPosesGO.transform.localPosition = Vector3.zero; _bindPosesGO.transform.localRotation = Quaternion.identity; } if (_bindPoses == null || _bindPoses.Count != _bones.Count) { _bindPoses = new List(new OVRBone[_bones.Count]); BindPoses = _bindPoses.AsReadOnly(); } // pre-populate bones list before attempting to apply bone hierarchy for (int i = 0; i < _bindPoses.Count; ++i) { OVRBone bone = _bones[i]; OVRBone bindPoseBone = _bindPoses[i] ?? (_bindPoses[i] = new OVRBone()); bindPoseBone.Id = bone.Id; bindPoseBone.ParentBoneIndex = bone.ParentBoneIndex; Transform trans = bindPoseBone.Transform ? bindPoseBone.Transform : (bindPoseBone.Transform = new GameObject(BoneLabelFromBoneId(_skeletonType, bindPoseBone.Id)).transform); trans.localPosition = bone.Transform.localPosition; trans.localRotation = bone.Transform.localRotation; } for (int i = 0; i < _bindPoses.Count; ++i) { if (!IsValidBone((BoneId)_bindPoses[i].ParentBoneIndex) || IsBodySkeleton(_skeletonType)) // Body bones are always in tracking space { _bindPoses[i].Transform.SetParent(_bindPosesGO.transform, false); } else { _bindPoses[i].Transform.SetParent(_bindPoses[_bindPoses[i].ParentBoneIndex].Transform, false); } } } private void InitializeCapsules() { bool flipX = IsHandSkeleton(_skeletonType); if (_enablePhysicsCapsules) { if (!_capsulesGO) { _capsulesGO = new GameObject("Capsules"); _capsulesGO.transform.SetParent(transform, false); _capsulesGO.transform.localPosition = Vector3.zero; _capsulesGO.transform.localRotation = Quaternion.identity; } if (_capsules == null || _capsules.Count != _skeleton.NumBoneCapsules) { _capsules = new List(new OVRBoneCapsule[_skeleton.NumBoneCapsules]); Capsules = _capsules.AsReadOnly(); } for (int i = 0; i < _capsules.Count; ++i) { OVRBone bone = _bones[_skeleton.BoneCapsules[i].BoneIndex]; OVRBoneCapsule capsule = _capsules[i] ?? (_capsules[i] = new OVRBoneCapsule()); capsule.BoneIndex = _skeleton.BoneCapsules[i].BoneIndex; if (capsule.CapsuleRigidbody == null) { capsule.CapsuleRigidbody = new GameObject(BoneLabelFromBoneId(_skeletonType, bone.Id) + "_CapsuleRigidbody") .AddComponent(); capsule.CapsuleRigidbody.mass = 1.0f; capsule.CapsuleRigidbody.isKinematic = true; capsule.CapsuleRigidbody.useGravity = false; capsule.CapsuleRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; } GameObject rbGO = capsule.CapsuleRigidbody.gameObject; rbGO.transform.SetParent(_capsulesGO.transform, false); rbGO.transform.position = bone.Transform.position; rbGO.transform.rotation = bone.Transform.rotation; if (capsule.CapsuleCollider == null) { capsule.CapsuleCollider = new GameObject(BoneLabelFromBoneId(_skeletonType, bone.Id) + "_CapsuleCollider") .AddComponent(); capsule.CapsuleCollider.isTrigger = false; } var p0 = flipX ? _skeleton.BoneCapsules[i].StartPoint.FromFlippedXVector3f() : _skeleton.BoneCapsules[i].StartPoint.FromFlippedZVector3f(); var p1 = flipX ? _skeleton.BoneCapsules[i].EndPoint.FromFlippedXVector3f() : _skeleton.BoneCapsules[i].EndPoint.FromFlippedZVector3f(); var delta = p1 - p0; var mag = delta.magnitude; var rot = Quaternion.FromToRotation(Vector3.right, delta); capsule.CapsuleCollider.radius = _skeleton.BoneCapsules[i].Radius; capsule.CapsuleCollider.height = mag + _skeleton.BoneCapsules[i].Radius * 2.0f; capsule.CapsuleCollider.direction = 0; capsule.CapsuleCollider.center = Vector3.right * mag * 0.5f; GameObject ccGO = capsule.CapsuleCollider.gameObject; ccGO.transform.SetParent(rbGO.transform, false); ccGO.transform.localPosition = p0; ccGO.transform.localRotation = rot; } } } protected virtual void Update() { UpdateSkeleton(); } protected void UpdateSkeleton() { if (ShouldInitialize()) { Initialize(); } if (!IsInitialized || _dataProvider == null) { IsDataValid = false; IsDataHighConfidence = false; return; } var data = _dataProvider.GetSkeletonPoseData(); IsDataValid = data.IsDataValid; if (!data.IsDataValid) { return; } if (SkeletonChangedCount != data.SkeletonChangedCount) { SkeletonChangedCount = data.SkeletonChangedCount; IsInitialized = false; Initialize(); } IsDataHighConfidence = data.IsDataHighConfidence; if (_updateRootPose) { transform.localPosition = data.RootPose.Position.FromFlippedZVector3f(); transform.localRotation = data.RootPose.Orientation.FromFlippedZQuatf(); } if (_updateRootScale) { transform.localScale = new Vector3(data.RootScale, data.RootScale, data.RootScale); } for (var i = 0; i < _bones.Count; ++i) { var boneTransform = _bones[i].Transform; if (boneTransform == null) continue; if (IsBodySkeleton(_skeletonType)) { boneTransform.localPosition = data.BoneTranslations[i].FromFlippedZVector3f(); boneTransform.localRotation = data.BoneRotations[i].FromFlippedZQuatf(); } else if (IsHandSkeleton(_skeletonType)) { boneTransform.localRotation = data.BoneRotations[i].FromFlippedXQuatf(); if (_bones[i].Id == BoneId.Hand_WristRoot) { boneTransform.localRotation *= wristFixupRotation; } } else { boneTransform.localRotation = data.BoneRotations[i].FromFlippedZQuatf(); } } } private void FixedUpdate() { if (!IsInitialized || _dataProvider == null) { IsDataValid = false; IsDataHighConfidence = false; return; } Update(); if (_enablePhysicsCapsules) { var data = _dataProvider.GetSkeletonPoseData(); IsDataValid = data.IsDataValid; IsDataHighConfidence = data.IsDataHighConfidence; for (int i = 0; i < _capsules.Count; ++i) { OVRBoneCapsule capsule = _capsules[i]; var capsuleGO = capsule.CapsuleRigidbody.gameObject; if (data.IsDataValid && data.IsDataHighConfidence) { Transform bone = _bones[(int)capsule.BoneIndex].Transform; if (capsuleGO.activeSelf) { capsule.CapsuleRigidbody.MovePosition(bone.position); capsule.CapsuleRigidbody.MoveRotation(bone.rotation); } else { capsuleGO.SetActive(true); capsule.CapsuleRigidbody.position = bone.position; capsule.CapsuleRigidbody.rotation = bone.rotation; } } else { if (capsuleGO.activeSelf) { capsuleGO.SetActive(false); } } } } } public BoneId GetCurrentStartBoneId() { switch (_skeletonType) { case SkeletonType.HandLeft: case SkeletonType.HandRight: return BoneId.Hand_Start; case SkeletonType.Body: return BoneId.Body_Start; case SkeletonType.None: default: return BoneId.Invalid; } } public BoneId GetCurrentEndBoneId() { switch (_skeletonType) { case SkeletonType.HandLeft: case SkeletonType.HandRight: return BoneId.Hand_End; case SkeletonType.Body: return BoneId.Body_End; case SkeletonType.None: default: return BoneId.Invalid; } } private BoneId GetCurrentMaxSkinnableBoneId() { switch (_skeletonType) { case SkeletonType.HandLeft: case SkeletonType.HandRight: return BoneId.Hand_MaxSkinnable; case SkeletonType.Body: return BoneId.Body_End; case SkeletonType.None: default: return BoneId.Invalid; } } public int GetCurrentNumBones() { switch (_skeletonType) { case SkeletonType.HandLeft: case SkeletonType.HandRight: case SkeletonType.Body: return GetCurrentEndBoneId() - GetCurrentStartBoneId(); case SkeletonType.None: default: return 0; } } public int GetCurrentNumSkinnableBones() { switch (_skeletonType) { case SkeletonType.HandLeft: case SkeletonType.HandRight: case SkeletonType.Body: return GetCurrentMaxSkinnableBoneId() - GetCurrentStartBoneId(); case SkeletonType.None: default: return 0; } } // force aliased enum values to the more appropriate value public static string BoneLabelFromBoneId(OVRSkeleton.SkeletonType skeletonType, BoneId boneId) { if (skeletonType == OVRSkeleton.SkeletonType.Body) { switch (boneId) { case BoneId.Body_Root: return "Body_Root"; case BoneId.Body_Hips: return "Body_Hips"; case BoneId.Body_SpineLower: return "Body_SpineLower"; case BoneId.Body_SpineMiddle: return "Body_SpineMiddle"; case BoneId.Body_SpineUpper: return "Body_SpineUpper"; case BoneId.Body_Chest: return "Body_Chest"; case BoneId.Body_Neck: return "Body_Neck"; case BoneId.Body_Head: return "Body_Head"; case BoneId.Body_LeftShoulder: return "Body_LeftShoulder"; case BoneId.Body_LeftScapula: return "Body_LeftScapula"; case BoneId.Body_LeftArmUpper: return "Body_LeftArmUpper"; case BoneId.Body_LeftArmLower: return "Body_LeftArmLower"; case BoneId.Body_LeftHandWristTwist: return "Body_LeftHandWristTwist"; case BoneId.Body_RightShoulder: return "Body_RightShoulder"; case BoneId.Body_RightScapula: return "Body_RightScapula"; case BoneId.Body_RightArmUpper: return "Body_RightArmUpper"; case BoneId.Body_RightArmLower: return "Body_RightArmLower"; case BoneId.Body_RightHandWristTwist: return "Body_RightHandWristTwist"; case BoneId.Body_LeftHandPalm: return "Body_LeftHandPalm"; case BoneId.Body_LeftHandWrist: return "Body_LeftHandWrist"; case BoneId.Body_LeftHandThumbMetacarpal: return "Body_LeftHandThumbMetacarpal"; case BoneId.Body_LeftHandThumbProximal: return "Body_LeftHandThumbProximal"; case BoneId.Body_LeftHandThumbDistal: return "Body_LeftHandThumbDistal"; case BoneId.Body_LeftHandThumbTip: return "Body_LeftHandThumbTip"; case BoneId.Body_LeftHandIndexMetacarpal: return "Body_LeftHandIndexMetacarpal"; case BoneId.Body_LeftHandIndexProximal: return "Body_LeftHandIndexProximal"; case BoneId.Body_LeftHandIndexIntermediate: return "Body_LeftHandIndexIntermediate"; case BoneId.Body_LeftHandIndexDistal: return "Body_LeftHandIndexDistal"; case BoneId.Body_LeftHandIndexTip: return "Body_LeftHandIndexTip"; case BoneId.Body_LeftHandMiddleMetacarpal: return "Body_LeftHandMiddleMetacarpal"; case BoneId.Body_LeftHandMiddleProximal: return "Body_LeftHandMiddleProximal"; case BoneId.Body_LeftHandMiddleIntermediate: return "Body_LeftHandMiddleIntermediate"; case BoneId.Body_LeftHandMiddleDistal: return "Body_LeftHandMiddleDistal"; case BoneId.Body_LeftHandMiddleTip: return "Body_LeftHandMiddleTip"; case BoneId.Body_LeftHandRingMetacarpal: return "Body_LeftHandRingMetacarpal"; case BoneId.Body_LeftHandRingProximal: return "Body_LeftHandRingProximal"; case BoneId.Body_LeftHandRingIntermediate: return "Body_LeftHandRingIntermediate"; case BoneId.Body_LeftHandRingDistal: return "Body_LeftHandRingDistal"; case BoneId.Body_LeftHandRingTip: return "Body_LeftHandRingTip"; case BoneId.Body_LeftHandLittleMetacarpal: return "Body_LeftHandLittleMetacarpal"; case BoneId.Body_LeftHandLittleProximal: return "Body_LeftHandLittleProximal"; case BoneId.Body_LeftHandLittleIntermediate: return "Body_LeftHandLittleIntermediate"; case BoneId.Body_LeftHandLittleDistal: return "Body_LeftHandLittleDistal"; case BoneId.Body_LeftHandLittleTip: return "Body_LeftHandLittleTip"; case BoneId.Body_RightHandPalm: return "Body_RightHandPalm"; case BoneId.Body_RightHandWrist: return "Body_RightHandWrist"; case BoneId.Body_RightHandThumbMetacarpal: return "Body_RightHandThumbMetacarpal"; case BoneId.Body_RightHandThumbProximal: return "Body_RightHandThumbProximal"; case BoneId.Body_RightHandThumbDistal: return "Body_RightHandThumbDistal"; case BoneId.Body_RightHandThumbTip: return "Body_RightHandThumbTip"; case BoneId.Body_RightHandIndexMetacarpal: return "Body_RightHandIndexMetacarpal"; case BoneId.Body_RightHandIndexProximal: return "Body_RightHandIndexProximal"; case BoneId.Body_RightHandIndexIntermediate: return "Body_RightHandIndexIntermediate"; case BoneId.Body_RightHandIndexDistal: return "Body_RightHandIndexDistal"; case BoneId.Body_RightHandIndexTip: return "Body_RightHandIndexTip"; case BoneId.Body_RightHandMiddleMetacarpal: return "Body_RightHandMiddleMetacarpal"; case BoneId.Body_RightHandMiddleProximal: return "Body_RightHandMiddleProximal"; case BoneId.Body_RightHandMiddleIntermediate: return "Body_RightHandMiddleIntermediate"; case BoneId.Body_RightHandMiddleDistal: return "Body_RightHandMiddleDistal"; case BoneId.Body_RightHandMiddleTip: return "Body_RightHandMiddleTip"; case BoneId.Body_RightHandRingMetacarpal: return "Body_RightHandRingMetacarpal"; case BoneId.Body_RightHandRingProximal: return "Body_RightHandRingProximal"; case BoneId.Body_RightHandRingIntermediate: return "Body_RightHandRingIntermediate"; case BoneId.Body_RightHandRingDistal: return "Body_RightHandRingDistal"; case BoneId.Body_RightHandRingTip: return "Body_RightHandRingTip"; case BoneId.Body_RightHandLittleMetacarpal: return "Body_RightHandLittleMetacarpal"; case BoneId.Body_RightHandLittleProximal: return "Body_RightHandLittleProximal"; case BoneId.Body_RightHandLittleIntermediate: return "Body_RightHandLittleIntermediate"; case BoneId.Body_RightHandLittleDistal: return "Body_RightHandLittleDistal"; case BoneId.Body_RightHandLittleTip: return "Body_RightHandLittleTip"; default: return "Body_Unknown"; } } else if (IsHandSkeleton(skeletonType)) { switch (boneId) { case OVRSkeleton.BoneId.Hand_WristRoot: return "Hand_WristRoot"; case OVRSkeleton.BoneId.Hand_ForearmStub: return "Hand_ForearmStub"; case OVRSkeleton.BoneId.Hand_Thumb0: return "Hand_Thumb0"; case OVRSkeleton.BoneId.Hand_Thumb1: return "Hand_Thumb1"; case OVRSkeleton.BoneId.Hand_Thumb2: return "Hand_Thumb2"; case OVRSkeleton.BoneId.Hand_Thumb3: return "Hand_Thumb3"; case OVRSkeleton.BoneId.Hand_Index1: return "Hand_Index1"; case OVRSkeleton.BoneId.Hand_Index2: return "Hand_Index2"; case OVRSkeleton.BoneId.Hand_Index3: return "Hand_Index3"; case OVRSkeleton.BoneId.Hand_Middle1: return "Hand_Middle1"; case OVRSkeleton.BoneId.Hand_Middle2: return "Hand_Middle2"; case OVRSkeleton.BoneId.Hand_Middle3: return "Hand_Middle3"; case OVRSkeleton.BoneId.Hand_Ring1: return "Hand_Ring1"; case OVRSkeleton.BoneId.Hand_Ring2: return "Hand_Ring2"; case OVRSkeleton.BoneId.Hand_Ring3: return "Hand_Ring3"; case OVRSkeleton.BoneId.Hand_Pinky0: return "Hand_Pinky0"; case OVRSkeleton.BoneId.Hand_Pinky1: return "Hand_Pinky1"; case OVRSkeleton.BoneId.Hand_Pinky2: return "Hand_Pinky2"; case OVRSkeleton.BoneId.Hand_Pinky3: return "Hand_Pinky3"; case OVRSkeleton.BoneId.Hand_ThumbTip: return "Hand_ThumbTip"; case OVRSkeleton.BoneId.Hand_IndexTip: return "Hand_IndexTip"; case OVRSkeleton.BoneId.Hand_MiddleTip: return "Hand_MiddleTip"; case OVRSkeleton.BoneId.Hand_RingTip: return "Hand_RingTip"; case OVRSkeleton.BoneId.Hand_PinkyTip: return "Hand_PinkyTip"; default: return "Hand_Unknown"; } } else { return "Skeleton_Unknown"; } } internal static bool IsBodySkeleton(SkeletonType type) => type == SkeletonType.Body; private static bool IsHandSkeleton(SkeletonType type) => type == SkeletonType.HandLeft || type == SkeletonType.HandRight; } public class OVRBone { public OVRSkeleton.BoneId Id { get; set; } public short ParentBoneIndex { get; set; } public Transform Transform { get; set; } public OVRBone() { } public OVRBone(OVRSkeleton.BoneId id, short parentBoneIndex, Transform trans) { Id = id; ParentBoneIndex = parentBoneIndex; Transform = trans; } } public class OVRBoneCapsule { public short BoneIndex { get; set; } public Rigidbody CapsuleRigidbody { get; set; } public CapsuleCollider CapsuleCollider { get; set; } public OVRBoneCapsule() { } public OVRBoneCapsule(short boneIndex, Rigidbody capsuleRigidBody, CapsuleCollider capsuleCollider) { BoneIndex = boneIndex; CapsuleRigidbody = capsuleRigidBody; CapsuleCollider = capsuleCollider; } }