using System; using Unity.Burst; using Unity.Mathematics; namespace UnityEngine.XR.Hands.Gestures { /// /// Defines hand orientation relative to the user and a target Transform. /// [Serializable] public class XRHandRelativeOrientation { /// /// A condition that can be used to check the orientation of a hand relative to the user's head /// or the tracking origin. /// #if BURST_PRESENT [BurstCompile] #endif [Serializable] public class UserCondition { /// /// The that is used to compare to the direction relative to the target. /// public XRHandAxis handAxis { get => m_HandAxis; set => m_HandAxis = value; } /// /// The used to evaluate the condition. /// public XRHandAlignmentCondition alignmentCondition { get => m_AlignmentCondition; set => m_AlignmentCondition = value; } /// /// The to compare to the for this condition. /// public XRHandUserRelativeDirection referenceDirection { get => m_ReferenceDirection; set => m_ReferenceDirection = value; } /// /// If the is , /// this is the maximum angle between the and the reference direction for the condition to be met. /// /// If the is , /// this is the maximum angle between the and the opposite of the reference direction for the condition to be met. /// /// If the is , /// this is the maximum angle difference from 90 degrees (perpendicular) between the and the reference direction for the condition to be met. /// public float angleTolerance { get => m_AngleTolerance; set => m_AngleTolerance = value; } /// /// If enabled, the y position will be ignored when referencing a direction relative to another position. /// public bool ignorePositionY { get => m_IgnorePositionY; set => m_IgnorePositionY = value; } internal bool CheckCondition( in float3 rootPosition, in quaternion rootRotation, in RigidTransform originTransform, in RigidTransform headTransform, float handednessMultiplier) { return CheckConditionBursted( m_HandAxis, m_AlignmentCondition, m_ReferenceDirection, m_AngleTolerance, rootPosition, rootRotation, originTransform, headTransform, m_IgnorePositionY, handednessMultiplier); } #if BURST_PRESENT [BurstCompile] #endif static bool CheckConditionBursted( XRHandAxis handAxis, XRHandAlignmentCondition alignmentCondition, XRHandUserRelativeDirection referenceDirection, float tolerance, in float3 rootPosition, in quaternion rootRotation, in RigidTransform originTransform, in RigidTransform headTransform, bool ignorePositionY, float handednessMultiplier) { XRHandOrientationUtility.GetHandAxisDirection( out var calculatedHandAxisDirection, handAxis, rootRotation, handednessMultiplier); GetReferenceDirection( out var calculatedReferenceDirection, rootPosition, referenceDirection, originTransform, headTransform, handednessMultiplier); return XRHandOrientationUtility.CheckDirectionAlignment( alignmentCondition, tolerance, ignorePositionY, calculatedHandAxisDirection, calculatedReferenceDirection); } #if BURST_PRESENT [BurstCompile] #endif static void GetReferenceDirection( out float3 result, in float3 handPosition, XRHandUserRelativeDirection direction, in RigidTransform handTrackingOrigin, in RigidTransform headTransform, float handednessMultiplier) { float3 worldDirection; switch (direction) { case XRHandUserRelativeDirection.OriginUp: worldDirection = math.rotate(handTrackingOrigin, new float3(0f, 1f, 0f)); break; case XRHandUserRelativeDirection.HandToHead: worldDirection = headTransform.pos - math.transform(handTrackingOrigin, handPosition); break; case XRHandUserRelativeDirection.NoseDirection: worldDirection = math.rotate(headTransform, new float3(0f, 0f, 1f)); break; case XRHandUserRelativeDirection.ChinDirection: worldDirection = math.rotate(headTransform, new float3(0f, -1f, 0f)); break; case XRHandUserRelativeDirection.EarDirection: worldDirection = math.rotate(headTransform, new float3(-handednessMultiplier, 0f, 0f)); break; default: worldDirection = new float3(0f, 1f, 0f); break; } result = math.mul(math.inverse(handTrackingOrigin).rot, worldDirection); } [SerializeField] [Tooltip("The axis of the hand using the OpenXR spec axis mapping for hands.")] XRHandAxis m_HandAxis = XRHandAxis.PalmDirection; [SerializeField] [Tooltip("The method used to compare two directions.")] XRHandAlignmentCondition m_AlignmentCondition = XRHandAlignmentCondition.AlignsWith; [SerializeField] [Tooltip("The axis to compare the hand's axis to.")] XRHandUserRelativeDirection m_ReferenceDirection = XRHandUserRelativeDirection.OriginUp; [SerializeField] [Tooltip("The condition will be true if the angle difference between the hand axis and the reference direction (in degrees) is less than this value.")] [Range(XRHandOrientationUtility.k_MinimumAngleTolerance, XRHandOrientationUtility.k_MaximumAngleTolerance)] float m_AngleTolerance; [SerializeField] [Tooltip("If enabled, the y position will be ignored when comparing to a direction relative to another position.")] bool m_IgnorePositionY; } /// /// A condition that can be used to check the orientation of a hand /// relative to some other target. The target is another transform /// referenced by the property. /// /// /// To check the orientation relative to the user's head or origin, use /// to avoid the need to set a specific /// target transform per condition. /// #if BURST_PRESENT [BurstCompile] #endif [Serializable] public class TargetCondition { /// /// The that is used to compare to the direction relative to the target. /// public XRHandAxis handAxis { get => m_HandAxis; set => m_HandAxis = value; } /// /// The used to evaluate the condition. /// public XRHandAlignmentCondition alignmentCondition { get => m_AlignmentCondition; set => m_AlignmentCondition = value; } /// /// The to compare to the for this condition. /// public XRHandTargetRelativeDirection referenceDirection { get => m_ReferenceDirection; set => m_ReferenceDirection = value; } /// /// If the is , /// this is the maximum angle between the and the reference direction for the condition to be met. /// /// If the is , /// this is the maximum angle between the and the opposite of the reference direction for the condition to be met. /// /// If the is , /// this is the maximum angle difference from 90 degrees (perpendicular) between the and the reference direction for the condition to be met. /// public float angleTolerance { get => m_AngleTolerance; set => m_AngleTolerance = value; } /// /// If enabled, the Y position will be ignored when referencing a direction relative to another position. /// public bool ignorePositionY { get => m_IgnorePositionY; set => m_IgnorePositionY = value; } internal bool CheckCondition( in float3 rootPosition, in quaternion rootRotation, in RigidTransform originTransform, in RigidTransform targetTransform, float handednessMultiplier) { return CheckConditionBursted( m_HandAxis, m_AlignmentCondition, m_ReferenceDirection, m_AngleTolerance, m_IgnorePositionY, rootPosition, rootRotation, originTransform, targetTransform, handednessMultiplier); } #if BURST_PRESENT [BurstCompile] #endif static bool CheckConditionBursted( XRHandAxis xrHandAxis, XRHandAlignmentCondition condition, XRHandTargetRelativeDirection direction, float threshold, bool ignorePositionY, in float3 rootPosition, in quaternion rootRotation, in RigidTransform originTransform, in RigidTransform targetTransform, float handednessMultiplier) { XRHandOrientationUtility.GetHandAxisDirection( out var currentHandAxisDirection, xrHandAxis, rootRotation, handednessMultiplier); GetReferenceDirection( out var currentReferenceDirection, rootPosition, direction, originTransform, targetTransform); return XRHandOrientationUtility.CheckDirectionAlignment( condition, threshold, ignorePositionY, currentHandAxisDirection, currentReferenceDirection); } #if BURST_PRESENT [BurstCompile] #endif static void GetReferenceDirection( out float3 result, in float3 rootPosition, in XRHandTargetRelativeDirection direction, in RigidTransform handTrackingOrigin, in RigidTransform targetTransform) { float3 worldDirection; switch (direction) { case XRHandTargetRelativeDirection.HandToTarget: worldDirection = targetTransform.pos - math.transform(handTrackingOrigin, rootPosition); break; case XRHandTargetRelativeDirection.TargetForward: worldDirection = math.rotate(targetTransform, new float3(0f, 0f, 1f)); break; case XRHandTargetRelativeDirection.TargetRight: worldDirection = math.rotate(targetTransform, new float3(1f, 0f, 0f)); break; case XRHandTargetRelativeDirection.TargetUp: worldDirection = math.rotate(targetTransform, new float3(0f, 1f, 0f)); break; default: worldDirection = new float3(0f, 1f, 0f); break; } result = math.mul(math.inverse(handTrackingOrigin).rot, worldDirection); } [SerializeField] [Tooltip("The axis of the hand using the OpenXR spec axis mapping for hands.")] XRHandAxis m_HandAxis = XRHandAxis.PalmDirection; [SerializeField] [Tooltip("The method used to compare two directions.")] XRHandAlignmentCondition m_AlignmentCondition = XRHandAlignmentCondition.AlignsWith; [SerializeField] [Tooltip("The axis to compare the hand's axis to.")] XRHandTargetRelativeDirection m_ReferenceDirection = XRHandTargetRelativeDirection.HandToTarget; [SerializeField] [Tooltip("The condition will be true if the angle difference between the hand axis and the reference direction (in degrees) is less than this value.")] [Range(XRHandOrientationUtility.k_MinimumAngleTolerance, XRHandOrientationUtility.k_MaximumAngleTolerance)] float m_AngleTolerance; [SerializeField] [Tooltip("If enabled, the y position will be ignored when comparing to a direction relative to another position.")] bool m_IgnorePositionY; } /// /// The list of conditions to check for the hand's orientation, relative to the user. /// public UserCondition[] userConditions { get => m_UserConditions; set => m_UserConditions = value; } [SerializeField] [Tooltip("A list of conditions to user to check a hand's orientation, relative to the user.")] UserCondition[] m_UserConditions; /// /// The list of conditions to check for the hand's orientation, relative to a target Transform. /// /// /// All of these conditions use . /// public TargetCondition[] targetConditions { get => m_TargetConditions; set => m_TargetConditions = value; } [SerializeField] [Tooltip("A list of the conditions to use to check a hand's orientation, relative to a target Transform.")] TargetCondition[] m_TargetConditions; /// /// The target transform that the are relative to. /// /// /// This is not used for . /// public Transform targetTransform { get => m_TargetTransform; set => m_TargetTransform = value; } Transform m_TargetTransform; /// /// Checks the hand orientation conditions to determine whether the hand is in the correct orientation. /// /// The root pose of the hand. /// The handedness of the hand. /// Whether all the conditions are met. public bool CheckConditions(Pose rootPose, Handedness handedness) { if (!XRHandOrientationUtility.TryGetOriginTransform(out var originTransform) || !XRHandOrientationUtility.TryGetHeadTransform(out var headTransform)) return false; var rootPosePosition = (float3)rootPose.position; var rootPoseRotation = (quaternion)rootPose.rotation; var originRigidTransform = new RigidTransform(originTransform.rotation, originTransform.position); var handednessMultiplier = handedness == Handedness.Right ? -1f : 1f; for (var conditionIndex = 0; conditionIndex < m_UserConditions.Length; ++conditionIndex) { if (!m_UserConditions[conditionIndex].CheckCondition( rootPosePosition, rootPoseRotation, originRigidTransform, new RigidTransform(headTransform.rotation, headTransform.position), handednessMultiplier)) return false; } if (m_TargetTransform == null) return true; var targetRigidTransform = new RigidTransform(m_TargetTransform.rotation, m_TargetTransform.position); for (var conditionIndex = 0; conditionIndex < m_TargetConditions.Length; ++conditionIndex) { if (!m_TargetConditions[conditionIndex].CheckCondition( rootPosePosition, rootPoseRotation, originRigidTransform, targetRigidTransform, handednessMultiplier)) return false; } return true; } } }