using System; using Unity.Collections; using UnityEngine.SubsystemsImplementation; using UnityEngine.XR.Hands.Gestures; namespace UnityEngine.XR.Hands.ProviderImplementation { /// /// Methods to implement by the implementing provider for an . /// public abstract class XRHandSubsystemProvider : SubsystemProvider { /// /// Gets the layout of hand joints for this provider, by having the /// provider mark each index corresponding to a /// get marked as if the provider attempts to track /// that joint. /// /// /// Called once on creation so that before the subsystem is even started, /// the user can immediately create a valid hierarchical structure as /// soon as they get a reference to the subsystem without even needing to /// start it. This is called before any call to /// . /// /// /// Each index corresponds to a . For each /// joint that the provider will attempt to track, mark that spot as /// by calling .ToIndex() on that ID. /// public abstract void GetHandLayout(NativeArray handJointsInLayout); /// /// Gets the on the current /// device for the given . /// /// /// Called once for each finger on creation so that the subsystem will /// always have valid configurations to base detection math off of. If /// the provider does not override this, defaults will be reported - /// this means that if the device is more constrained in reporting joint /// data than the defaults, gestures and poses may not be detected correctly. /// Called after for each finger, but before /// the subsystem is returned during a call to /// . /// /// /// Which finger to get the for. /// /// /// A populated representing /// range of motion for the given . /// /// /// Will throw an exception if is not a named /// value of . /// public virtual XRFingerShapeConfiguration GetFingerShapeConfiguration(XRHandFingerID fingerID) { switch (fingerID) { case XRHandFingerID.Thumb: return FingerConfigDefaults.k_Thumb; case XRHandFingerID.Index: return FingerConfigDefaults.k_Index; case XRHandFingerID.Middle: return FingerConfigDefaults.k_Middle; case XRHandFingerID.Ring: return FingerConfigDefaults.k_Ring; case XRHandFingerID.Little: return FingerConfigDefaults.k_Little; default: throw new ArgumentException("Invalid XRHandFingerID!"); } } /// /// Attempts to retrieve current hand-tracking data from the provider. /// /// /// Informs the provider which kind of timing the update is being /// requested under. /// /// /// Update this and include XRHandSubsystem.UpdateSuccesFlags.LeftHandRootPose /// in the return value to update the left hand's root pose. /// /// /// Array of hand joints to fill out for the left hand. These are /// initialized with a copy of the current joint data for the left hand, /// so if the last known tracking data for a particular joint is still /// fine, you don't need to fill out that data again. If you update /// these, include XRHandSubsystem.UpdateSuccesFlags.LeftHandJoints /// in the return value to have the changes reflected in the subsystem. /// /// /// Update this and include XRHandSubsystem.UpdateSuccesFlags.RightHandRootPose /// in the return value to update the right hand's root pose. /// /// /// Array of hand joints to fill out for the right hand. These are /// initialized with a copy of the current joint data for the right hand, /// so if the last known tracking data for a particular joint is still /// fine, you don't need to fill out that data again. If you update /// these, include XRHandSubsystem.UpdateSuccesFlags.RightHandJoints /// in the return value to have the changes reflected in the subsystem. /// /// /// Returns to describe which tracking /// data was successfully updated. /// public abstract XRHandSubsystem.UpdateSuccessFlags TryUpdateHands( XRHandSubsystem.UpdateType updateType, ref Pose leftHandRootPose, NativeArray leftHandJoints, ref Pose rightHandRootPose, NativeArray rightHandJoints); /// /// Whether the provider is currently able to surface data from any of , /// , , , /// , , or . /// public virtual bool canSurfaceCommonPoseData => false; /// /// Gets the aim pose. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The pose to update the aim pose to, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the aim pose was /// filled out, returns otherwise. /// public virtual bool TryGetAimPose(Handedness handedness, out Pose aimPose) { aimPose = Pose.identity; return false; } /// /// Gets the aim activate value. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The aim activate value, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the aim activate value was /// filled out, returns otherwise. /// public virtual bool TryGetAimActivateValue(Handedness handedness, out float aimActivateValue) { aimActivateValue = 0f; return false; } /// /// Gets the grasp value. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The grasp value, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the grasp value was /// filled out, returns otherwise. /// public virtual bool TryGetGraspValue(Handedness handedness, out float graspValue) { graspValue = 0f; return false; } /// /// Gets the grip pose. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The pose to update the aim pose to, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the grip pose was /// filled out, returns otherwise. /// public virtual bool TryGetGripPose(Handedness handedness, out Pose gripPose) { gripPose = Pose.identity; return false; } /// /// Gets the pinch pose. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The pose to update the pinch pose to, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the pinch pose was /// filled out, returns otherwise. /// public virtual bool TryGetPinchPose(Handedness handedness, out Pose pinchPose) { pinchPose = Pose.identity; return false; } /// /// Gets the pinch value. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The pinch value, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the grasp value was /// filled out, returns otherwise. /// public virtual bool TryGetPinchValue(Handedness handedness, out float pinchValue) { pinchValue = 0f; return false; } /// /// Gets the poke pose. Will only be called if /// is enabled. /// /// /// Which hand to retrieve data for. /// /// /// The pose to update the poke pose to, if available. Will not be used if /// is returned. /// /// /// Returns if successful and the poke pose was /// filled out, returns otherwise. /// public virtual bool TryGetPokePose(Handedness handedness, out Pose pokePose) { pokePose = Pose.identity; return false; } // these defaults were captured using a Meta Quest 2 static class FingerConfigDefaults { static internal readonly XRFingerShapeConfiguration k_Thumb = new XRFingerShapeConfiguration { minimumFullCurlDegrees1 = 132f, maximumFullCurlDegrees1 = 162f, minimumFullCurlDegrees2 = 129f, maximumFullCurlDegrees2 = 180f, minimumFullCurlDegrees3 = -1f, maximumFullCurlDegrees3 = -1f, minimumBaseCurlDegrees = 2f, maximumBaseCurlDegrees = 60f, minimumTipCurlDegrees1 = 119f, maximumTipCurlDegrees1 = 169f, minimumTipCurlDegrees2 = 129f, maximumTipCurlDegrees2 = 180f, minimumPinchDistance = -1f, maximumPinchDistance = -1f, minimumSpreadDegrees = 3f, maximumSpreadDegrees = 57f, }; static internal readonly XRFingerShapeConfiguration k_Index = new XRFingerShapeConfiguration { minimumFullCurlDegrees1 = 102f, maximumFullCurlDegrees1 = 180f, minimumFullCurlDegrees2 = 90f, maximumFullCurlDegrees2 = 174f, minimumFullCurlDegrees3 = 120f, maximumFullCurlDegrees3 = 180f, minimumBaseCurlDegrees = 102f, maximumBaseCurlDegrees = 180f, minimumTipCurlDegrees1 = 90f, maximumTipCurlDegrees1 = 174f, minimumTipCurlDegrees2 = 120f, maximumTipCurlDegrees2 = 180f, minimumPinchDistance = 0.01f, maximumPinchDistance = 0.045f, minimumSpreadDegrees = 3f, maximumSpreadDegrees = 18f, }; static internal readonly XRFingerShapeConfiguration k_Middle = new XRFingerShapeConfiguration { minimumFullCurlDegrees1 = 92f, maximumFullCurlDegrees1 = 180f, minimumFullCurlDegrees2 = 90f, maximumFullCurlDegrees2 = 174f, minimumFullCurlDegrees3 = 116f, maximumFullCurlDegrees3 = 180f, minimumBaseCurlDegrees = 92f, maximumBaseCurlDegrees = 180f, minimumTipCurlDegrees1 = 90f, maximumTipCurlDegrees1 = 174f, minimumTipCurlDegrees2 = 116f, maximumTipCurlDegrees2 = 180f, minimumPinchDistance = 0.01f, maximumPinchDistance = 0.045f, minimumSpreadDegrees = 3f, maximumSpreadDegrees = 20f, }; static internal readonly XRFingerShapeConfiguration k_Ring = new XRFingerShapeConfiguration { minimumFullCurlDegrees1 = 90f, maximumFullCurlDegrees1 = 180f, minimumFullCurlDegrees2 = 90f, maximumFullCurlDegrees2 = 174f, minimumFullCurlDegrees3 = 112f, maximumFullCurlDegrees3 = 180f, minimumBaseCurlDegrees = 95f, maximumBaseCurlDegrees = 180f, minimumTipCurlDegrees1 = 90f, maximumTipCurlDegrees1 = 174f, minimumTipCurlDegrees2 = 112f, maximumTipCurlDegrees2 = 180f, minimumPinchDistance = 0.01f, maximumPinchDistance = 0.045f, minimumSpreadDegrees = 3f, maximumSpreadDegrees = 20f, }; static internal readonly XRFingerShapeConfiguration k_Little = new XRFingerShapeConfiguration { minimumFullCurlDegrees1 = 90f, maximumFullCurlDegrees1 = 180f, minimumFullCurlDegrees2 = 90f, maximumFullCurlDegrees2 = 164f, minimumFullCurlDegrees3 = 116f, maximumFullCurlDegrees3 = 180f, minimumBaseCurlDegrees = 95f, maximumBaseCurlDegrees = 180f, minimumTipCurlDegrees1 = 90f, maximumTipCurlDegrees1 = 164f, minimumTipCurlDegrees2 = 116f, maximumTipCurlDegrees2 = 180f, minimumPinchDistance = 0.01f, maximumPinchDistance = 0.045f, minimumSpreadDegrees = -1f, maximumSpreadDegrees = -1f, }; } } }