#if !PICO_OPENXR_SDK /******************************************************************************* Copyright © 2015-2022 PICO Technology Co., Ltd.All rights reserved. NOTICE:All information contained herein is, and remains the property of PICO Technology Co., Ltd. The intellectual and technical concepts contained herein are proprietary to PICO Technology Co., Ltd. and may be covered by patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from PICO Technology Co., Ltd. *******************************************************************************/ using Unity.Collections; using UnityEngine; using UnityEngine.Scripting; using System.Runtime.CompilerServices; using UnityEngine.XR.Management; using UnityEngine.InputSystem; using UnityEngine.XR; using System.Collections.Generic; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.XR; #if XR_HANDS using UnityEngine.XR.Hands; using UnityEngine.XR.Hands.ProviderImplementation; namespace Unity.XR.PXR { [Preserve] /// /// Implement Unity XRHandSubSystem /// Reference: https://docs.unity3d.com/Packages/com.unity.xr.hands@1.1/manual/implement-a-provider.html /// public class PXR_HandSubSystem : XRHandSubsystem { XRHandProviderUtility.SubsystemUpdater m_Updater; // This method registers the subsystem descriptor with the SubsystemManager [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void RegisterDescriptor() { var handsSubsystemCinfo = new XRHandSubsystemDescriptor.Cinfo { id = "PICO Hands", providerType = typeof(PXRHandSubsystemProvider), subsystemTypeOverride = typeof(PXR_HandSubSystem) }; XRHandSubsystemDescriptor.Register(handsSubsystemCinfo); } protected override void OnCreate() { base.OnCreate(); m_Updater = new XRHandProviderUtility.SubsystemUpdater(this); } protected override void OnStart() { Debug.Log("PXR_HandSubSystem Start"); m_Updater.Start(); base.OnStart(); } protected override void OnStop() { m_Updater.Stop(); base.OnStop(); } protected override void OnDestroy() { m_Updater.Destroy(); m_Updater = null; base.OnDestroy(); } class PXRHandSubsystemProvider : XRHandSubsystemProvider { HandJointLocations jointLocations = new HandJointLocations(); readonly HandLocationStatus AllStatus = HandLocationStatus.PositionTracked | HandLocationStatus.PositionValid | HandLocationStatus.OrientationTracked | HandLocationStatus.OrientationValid; bool isValid = false; public override void Start() { CreateHands(); } public override void Stop() { DestroyHands(); } public override void Destroy() { } /// /// Mapping the PICO Joint Index To Unity Joint Index /// static int[] pxrJointIndexToUnityJointIndexMapping; static void Initialize() { if (pxrJointIndexToUnityJointIndexMapping == null) { pxrJointIndexToUnityJointIndexMapping = new int[(int)HandJoint.JointMax]; pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointPalm] = XRHandJointID.Palm.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointWrist] = XRHandJointID.Wrist.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointThumbMetacarpal] = XRHandJointID.ThumbMetacarpal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointThumbProximal] = XRHandJointID.ThumbProximal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointThumbDistal] = XRHandJointID.ThumbDistal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointThumbTip] = XRHandJointID.ThumbTip.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointIndexMetacarpal] = XRHandJointID.IndexMetacarpal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointIndexProximal] = XRHandJointID.IndexProximal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointIndexIntermediate] = XRHandJointID.IndexIntermediate.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointIndexDistal] = XRHandJointID.IndexDistal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointIndexTip] = XRHandJointID.IndexTip.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointMiddleMetacarpal] = XRHandJointID.MiddleMetacarpal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointMiddleProximal] = XRHandJointID.MiddleProximal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointMiddleIntermediate] = XRHandJointID.MiddleIntermediate.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointMiddleDistal] = XRHandJointID.MiddleDistal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointMiddleTip] = XRHandJointID.MiddleTip.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointRingMetacarpal] = XRHandJointID.RingMetacarpal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointRingProximal] = XRHandJointID.RingProximal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointRingIntermediate] = XRHandJointID.RingIntermediate.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointRingDistal] = XRHandJointID.RingDistal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointRingTip] = XRHandJointID.RingTip.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointLittleMetacarpal] = XRHandJointID.LittleMetacarpal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointLittleProximal] = XRHandJointID.LittleProximal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointLittleIntermediate] = XRHandJointID.LittleIntermediate.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointLittleDistal] = XRHandJointID.LittleDistal.ToIndex(); pxrJointIndexToUnityJointIndexMapping[(int)HandJoint.JointLittleTip] = XRHandJointID.LittleTip.ToIndex(); } } /// /// 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, /// so 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. /// /// /// 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 override void GetHandLayout(NativeArray handJointsInLayout) { Initialize(); handJointsInLayout[XRHandJointID.Palm.ToIndex()] = true; handJointsInLayout[XRHandJointID.Wrist.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.ThumbTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.IndexTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.MiddleTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.RingTip.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleMetacarpal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleProximal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleIntermediate.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleDistal.ToIndex()] = true; handJointsInLayout[XRHandJointID.LittleTip.ToIndex()] = true; isValid = true; } /// /// Attempts to retrieve current hand-tracking data from the provider. /// public override UpdateSuccessFlags TryUpdateHands( UpdateType updateType, ref Pose leftHandRootPose, NativeArray leftHandJoints, ref Pose rightHandRootPose, NativeArray rightHandJoints) { if (!isValid) return UpdateSuccessFlags.None; UpdateSuccessFlags ret = UpdateSuccessFlags.None; const int handRootIndex = (int)HandJoint.JointWrist; if (PXR_HandTracking.GetJointLocations(HandType.HandLeft, ref jointLocations)) { if (jointLocations.isActive != 0U) { for (int index = 0, jointCount = (int)jointLocations.jointCount; index < jointCount; ++index) { ref HandJointLocation joint = ref jointLocations.jointLocations[index]; int unityHandJointIndex = pxrJointIndexToUnityJointIndexMapping[index]; leftHandJoints[unityHandJointIndex] = CreateXRHandJoint(Handedness.Left, unityHandJointIndex, joint); if (index == handRootIndex) { leftHandRootPose = PXRPosefToUnityPose(joint.pose); ret |= UpdateSuccessFlags.LeftHandRootPose; } } #if UNITY_EDITOR ret |= UpdateSuccessFlags.LeftHandJoints; #else if (PicoAimHand.left.UpdateHand(HandType.HandLeft, (ret & UpdateSuccessFlags.LeftHandRootPose) != 0)) { ret |= UpdateSuccessFlags.LeftHandJoints; } #endif } } if (PXR_HandTracking.GetJointLocations(HandType.HandRight, ref jointLocations)) { if (jointLocations.isActive != 0U) { for (int index = 0, jointCount = (int)jointLocations.jointCount; index < jointCount; ++index) { ref HandJointLocation joint = ref jointLocations.jointLocations[index]; int unityHandJointIndex = pxrJointIndexToUnityJointIndexMapping[index]; rightHandJoints[unityHandJointIndex] = CreateXRHandJoint(Handedness.Right, unityHandJointIndex, joint); if (index == handRootIndex) { rightHandRootPose = PXRPosefToUnityPose(joint.pose); ret |= UpdateSuccessFlags.RightHandRootPose; } } #if UNITY_EDITOR ret |= UpdateSuccessFlags.RightHandJoints; #else if (PicoAimHand.right.UpdateHand(HandType.HandRight, (ret & UpdateSuccessFlags.RightHandRootPose) != 0)) { ret |= UpdateSuccessFlags.RightHandJoints; } #endif } } return ret; } void CreateHands() { if (PicoAimHand.left == null) PicoAimHand.left = PicoAimHand.CreateHand(InputDeviceCharacteristics.Left); if (PicoAimHand.right == null) PicoAimHand.right = PicoAimHand.CreateHand(InputDeviceCharacteristics.Right); } void DestroyHands() { if (PicoAimHand.left != null) { InputSystem.RemoveDevice(PicoAimHand.left); PicoAimHand.left = null; } if (PicoAimHand.right != null) { InputSystem.RemoveDevice(PicoAimHand.right); PicoAimHand.right = null; } } /// /// Create Unity XRHandJoint From PXR HandJointLocation /// /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] XRHandJoint CreateXRHandJoint(Handedness handedness, int unityHandJointIndex, in HandJointLocation joint) { Pose pose = Pose.identity; XRHandJointTrackingState state = XRHandJointTrackingState.None; if ((joint.locationStatus & AllStatus) == AllStatus) { state = (XRHandJointTrackingState.Pose | XRHandJointTrackingState.Radius); pose = PXRPosefToUnityPose(joint.pose); } return XRHandProviderUtility.CreateJoint(handedness, state, XRHandJointIDUtility.FromIndex(unityHandJointIndex), pose, joint.radius ); } /// /// PXR's Posef to Unity'Pose /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] Pose PXRPosefToUnityPose(in Posef pxrPose) { Vector3 position = pxrPose.Position.ToVector3(); Quaternion orientation = pxrPose.Orientation.ToQuat(); return new Pose(position, orientation); } } } /// /// The and /// inherited from /// represent the aim pose. You can use these values to discover the target for pinch gestures, /// when appropriate. /// /// Use the [XROrigin](xref:Unity.XR.CoreUtils.XROrigin) in the scene to position and orient /// the device properly. If you are using this data to set the Transform of a GameObject in /// the scene hierarchy, you can set the local position and rotation of the Transform and make /// it a child of the CameraOffset object below the XROrigin. Otherwise, you can use the /// Transform of the CameraOffset to transform the data into world space. /// #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif [Preserve, InputControlLayout(displayName = "Pico Aim Hand", commonUsages = new[] { "LeftHand", "RightHand" })] public partial class PicoAimHand : TrackedDevice { /// /// The left-hand that contains /// s that surface data in the Pico Hand /// Tracking Aim extension. /// /// /// It is recommended that you treat this as read-only, and do not set /// it yourself. It will be set for you if hand-tracking has been /// enabled and if you are running with either the OpenXR or Oculus /// plug-in. /// public static PicoAimHand left { get; set; } /// /// The right-hand that contains /// s that surface data in the Pico Hand /// Tracking Aim extension. /// /// /// It is recommended that you treat this as read-only, and do not set /// it yourself. It will be set for you if hand-tracking has been /// enabled and if you are running with either the OpenXR or Oculus /// plug-in. /// public static PicoAimHand right { get; set; } /// /// The pinch amount required to register as being pressed for the /// purposes of , , /// , and . /// public const float pressThreshold = 0.8f; /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) /// that represents whether the pinch between the index finger and /// the thumb is mostly pressed (greater than a threshold of 0.8 /// contained in ). /// [Preserve, InputControl(offset = 0)] public ButtonControl indexPressed { get; private set; } /// /// Cast the result of reading this to to examine the value. /// [Preserve, InputControl] public IntegerControl aimFlags { get; private set; } /// /// An [AxisControl](xref:UnityEngine.InputSystem.Controls.AxisControl) /// that represents the pinch strength between the index finger and /// the thumb. /// /// /// A value of 0 denotes no pinch at all, while a value of /// 1 denotes a full pinch. /// [Preserve, InputControl] public AxisControl pinchStrengthIndex { get; private set; } /// /// Perform final initialization tasks after the control hierarchy has been put into place. /// protected override void FinishSetup() { base.FinishSetup(); indexPressed = GetChildControl(nameof(indexPressed)); aimFlags = GetChildControl(nameof(aimFlags)); pinchStrengthIndex = GetChildControl(nameof(pinchStrengthIndex)); var deviceDescriptor = XRDeviceDescriptor.FromJson(description.capabilities); if (deviceDescriptor != null) { if ((deviceDescriptor.characteristics & InputDeviceCharacteristics.Left) != 0) InputSystem.SetDeviceUsage(this, UnityEngine.InputSystem.CommonUsages.LeftHand); else if ((deviceDescriptor.characteristics & InputDeviceCharacteristics.Right) != 0) InputSystem.SetDeviceUsage(this, UnityEngine.InputSystem.CommonUsages.RightHand); } PXR_Plugin.System.FocusStateAcquired += OnFocusStateAcquired; } private void OnFocusStateAcquired() { m_WasTracked = false; } protected override void OnRemoved() { PXR_Plugin.System.FocusStateAcquired -= OnFocusStateAcquired; base.OnRemoved(); } /// /// Creates a and adds it to the Input System. /// /// /// Additional characteristics to build the hand device with besides /// and . /// /// /// A retrieved from /// . /// /// /// It is recommended that you do not call this yourself. It will be /// called for you at the appropriate time if hand-tracking has been /// enabled and if you are running with either the OpenXR or Oculus /// plug-in. /// public static PicoAimHand CreateHand(InputDeviceCharacteristics extraCharacteristics) { var desc = new InputDeviceDescription { product = k_PicoAimHandDeviceProductName, capabilities = new XRDeviceDescriptor { characteristics = InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.TrackedDevice | extraCharacteristics, inputFeatures = new List { new XRFeatureDescriptor { name = "index_pressed", featureType = FeatureType.Binary }, new XRFeatureDescriptor { name = "aim_flags", featureType = FeatureType.DiscreteStates }, new XRFeatureDescriptor { name = "aim_pose_position", featureType = FeatureType.Axis3D }, new XRFeatureDescriptor { name = "aim_pose_rotation", featureType = FeatureType.Rotation }, new XRFeatureDescriptor { name = "pinch_strength_index", featureType = FeatureType.Axis1D } } }.ToJson() }; return InputSystem.AddDevice(desc) as PicoAimHand; } /// /// Queues update events in the Input System based on the supplied hand. /// It is not recommended that you call this directly. This will be called /// for you when appropriate. /// /// /// Whether the hand root pose is valid. /// /// /// The aim flags to update in the Input System. /// /// /// The aim pose to update in the Input System. Used if the hand root is tracked. /// /// /// The pinch strength for the index finger to update in the Input System. /// public void UpdateHand(bool isHandRootTracked, HandAimStatus aimFlags, Posef aimPose, float pinchIndex) { if (aimFlags != m_PreviousFlags) { InputSystem.QueueDeltaStateEvent(this.aimFlags, (int)aimFlags); m_PreviousFlags = aimFlags; } bool isIndexPressed = pinchIndex > pressThreshold; if (isIndexPressed != m_WasIndexPressed) { InputSystem.QueueDeltaStateEvent(indexPressed, isIndexPressed); m_WasIndexPressed = isIndexPressed; } InputSystem.QueueDeltaStateEvent(pinchStrengthIndex, pinchIndex); if ((aimFlags & HandAimStatus.AimComputed) == 0) { if (m_WasTracked) { InputSystem.QueueDeltaStateEvent(isTracked, false); InputSystem.QueueDeltaStateEvent(trackingState, InputTrackingState.None); m_WasTracked = false; } return; } if (isHandRootTracked) { InputSystem.QueueDeltaStateEvent(devicePosition, aimPose.Position.ToVector3()); InputSystem.QueueDeltaStateEvent(deviceRotation, aimPose.Orientation.ToQuat()); if (!m_WasTracked) { InputSystem.QueueDeltaStateEvent(trackingState, InputTrackingState.Position | InputTrackingState.Rotation); InputSystem.QueueDeltaStateEvent(isTracked, true); } m_WasTracked = true; } else if (m_WasTracked) { InputSystem.QueueDeltaStateEvent(trackingState, InputTrackingState.None); InputSystem.QueueDeltaStateEvent(isTracked, false); m_WasTracked = false; } } internal bool UpdateHand(HandType handType, bool isHandRootTracked) { HandAimState handAimState = new HandAimState(); PXR_HandTracking.GetAimState(handType, ref handAimState); UpdateHand( isHandRootTracked, handAimState.aimStatus, handAimState.aimRayPose, handAimState.touchStrengthRay); return (handAimState.aimStatus&HandAimStatus.AimComputed) != 0; } #if UNITY_EDITOR static PicoAimHand() => RegisterLayout(); #endif [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void RegisterLayout() { InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithProduct(k_PicoAimHandDeviceProductName)); } const string k_PicoAimHandDeviceProductName = "Pico Aim Hand Tracking"; HandAimStatus m_PreviousFlags; bool m_WasTracked; bool m_WasIndexPressed; } } #endif //XR_HANDS #endif