using System; using Unity.XR.CoreUtils; using Unity.XR.CoreUtils.Datums; namespace UnityEngine.XR.Interaction.Toolkit.UI.BodyUI { /// /// Represents the reference axis relative to the tracking anchor used to compare up and camera facing direction. /// public enum FollowReferenceAxis { /// /// Represents the positive X axis. /// Right, /// /// Represents the positive Y axis. /// Up, /// /// Represents the positive Z axis. /// Forward, /// /// Represents the negative X axis. /// Left, /// /// Represents the negative Y axis. /// Down, /// /// Represents the negative Z axis. /// Back, } /// /// Class that defines the configuration of a following behaviour for a hand or object. /// It determines how an object should follow the hand and includes specifications about local position and rotation, /// angle constraints, gaze snapping, and smoothing settings. /// [Serializable] public class FollowPreset { /// /// Local space anchor position for the right hand. /// [Header("Local Space Anchor Transform")] [Tooltip("Local space anchor position for the right hand.")] public Vector3 rightHandLocalPosition; /// /// Local space anchor position for the left hand. /// [Tooltip("Local space anchor position for the left hand.")] public Vector3 leftHandLocalPosition; /// /// Local space anchor rotation for the right hand. /// [Tooltip("Local space anchor rotation for the right hand.")] public Vector3 rightHandLocalRotation; /// /// Local space anchor rotation for the left hand. /// [Tooltip("Local space anchor rotation for the left hand.")] public Vector3 leftHandLocalRotation; /// /// Reference axis equivalent used for comparisons with the user's gaze direction and the world up direction. /// [Header("Hand anchor angle constraints")] [Tooltip("Reference axis equivalent used for comparisons with the user's gaze direction and the world up direction.")] public FollowReferenceAxis palmReferenceAxis = FollowReferenceAxis.Down; /// /// Given that the default reference hand for menus is the left hand, it may be required to mirror the reference axis for the right hand. /// This is not necessary if using the up or down axis as a reference, which is the default for hand tracking. Controllers work best with the right and left axies. /// [Tooltip("Given that the default reference hand for menus is the left hand, it may be required to mirror the reference axis for the right hand.")] public bool invertAxisForRightHand; /// /// Check status of palm reference axis facing the user. /// [Tooltip("Whether or not check if the palm reference axis is facing the user.")] public bool requirePalmFacingUser; /// /// Angle threshold to check if the palm reference axis is facing the user. /// [Tooltip("The angle threshold in degrees to check if the palm reference axis is facing the user.")] public float palmFacingUserDegreeAngleThreshold; /// /// The dot product equivalent to the angle threshold used to check if the palm reference axis is facing the user. /// public float palmFacingUserDotThreshold => m_PalmFacingUserDotThreshold; float m_PalmFacingUserDotThreshold; /// /// Check status of palm reference axis facing up. /// [Tooltip("Whether or not check if the palm reference axis is facing up.")] public bool requirePalmFacingUp; /// /// Angle threshold to check if the palm reference axis is facing up. /// [Tooltip("The angle threshold in degrees to check if the palm reference axis is facing up.")] public float palmFacingUpDegreeAngleThreshold; /// /// The dot product equivalent to the angle threshold used to check if the palm reference axis is facing up. /// public float palmFacingUpDotThreshold => m_PalmFacingUpDotThreshold; float m_PalmFacingUpDotThreshold; /// /// Configures the snap to gaze option. /// [Header("Snap To gaze config")] [Tooltip("Whether to snap the following element to the gaze direction.")] public bool snapToGaze; /// /// The angle threshold in degrees to snap the following element to the gaze direction. /// [Tooltip("The angle threshold in degrees to snap the following element to the gaze direction.")] public float snapToGazeAngleThreshold; /// /// Dot product threshold for snap to gaze. /// public float snapToGazeDotThreshold => m_SnapToGazeDotThreshold; float m_SnapToGazeDotThreshold; /// /// The amount of time in seconds to wait before hiding the following element after the hand is no longer tracked. /// [Header("Hide delay config")] [Tooltip("The amount of time in seconds to wait before hiding the following element after the hand is no longer tracked.")] public float hideDelaySeconds = 0.25f; /// /// Whether to allow smoothing of the following element position and rotation. /// [Header("Smoothing Config")] [Tooltip("Whether to allow smoothing of the following element position and rotation.")] public bool allowSmoothing = true; /// /// The lower bound of smoothing to apply. /// [Tooltip("The lower bound of smoothing to apply.")] public float followLowerSmoothingValue = 10f; /// /// The upper bound of smoothing to apply. /// [Tooltip("The upper bound of smoothing to apply.")] public float followUpperSmoothingValue = 16f; /// /// Applies this preset to the specified tracking offsets for the left and right local positions and rotations. /// Also recomputes dot product thresholds. /// /// The Transform object that represents the left tracking offset. /// The Transform object that represents the right tracking offset. public void ApplyPreset(Transform leftTrackingOffset, Transform rightTrackingOffset) { leftTrackingOffset.SetLocalPose(new Pose(leftHandLocalPosition, Quaternion.Euler(leftHandLocalRotation))); rightTrackingOffset.SetLocalPose(new Pose(rightHandLocalPosition, Quaternion.Euler(rightHandLocalRotation))); ComputeDotProductThresholds(); } /// /// Update the dot product thresholds based on the current angle thresholds. /// public void ComputeDotProductThresholds() { m_PalmFacingUserDotThreshold = AngleToDot(palmFacingUserDegreeAngleThreshold); m_PalmFacingUpDotThreshold = AngleToDot(palmFacingUpDegreeAngleThreshold); m_SnapToGazeDotThreshold = AngleToDot(snapToGazeAngleThreshold); } static float AngleToDot(float angleDeg) { return Mathf.Cos(Mathf.Deg2Rad * angleDeg); } /// /// Gets the reference axis relative to the specified tracking root. /// Adjusts the return value depending on whether or not this is for the user's right hand. /// /// Tracking root Transform. /// Whether this is for the user's right hand or not. /// Returns a representing the reference axis relative to the specified tracking root. public Vector3 GetReferenceAxisForTrackingAnchor(Transform trackingRoot, bool isRightHand) { return trackingRoot.TransformDirection(GetLocalAxis(isRightHand)); } Vector3 GetLocalAxis(bool isRightHand) { Vector3 axis = Vector3.zero; bool invert = isRightHand && invertAxisForRightHand; switch (palmReferenceAxis) { case FollowReferenceAxis.Right: axis = invert ? Vector3.left : Vector3.right; break; case FollowReferenceAxis.Up: axis = invert ? Vector3.down : Vector3.up; break; case FollowReferenceAxis.Forward: axis = invert ? Vector3.back : Vector3.forward; break; case FollowReferenceAxis.Left: axis = invert ? Vector3.right : Vector3.left; break; case FollowReferenceAxis.Down: axis = invert ? Vector3.up : Vector3.down; break; case FollowReferenceAxis.Back: axis = invert ? Vector3.forward : Vector3.back; break; } return axis; } } /// /// Serializable container class that holds a value or container asset reference. /// /// [Serializable] public class FollowPresetDatumProperty : DatumProperty { /// public FollowPresetDatumProperty(FollowPreset value) : base(value) { } /// public FollowPresetDatumProperty(FollowPresetDatum datum) : base(datum) { } } /// /// container class that holds a float affordance theme value. /// [CreateAssetMenu(fileName = "Follow Preset Datum", menuName = "XR/Value Datums/Body UI Follow Preset Datum", order = 0)] [HelpURL(XRHelpURLConstants.k_FollowPresetDatum)] public class FollowPresetDatum : Datum { } }