using Unity.XR.CoreUtils; using UnityEngine.Assertions; namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets { /// /// A version of action-based continuous movement that automatically controls the frame of reference that /// determines the forward direction of movement based on user preference for each hand. /// For example, can configure to use head relative movement for the left hand and controller relative movement for the right hand. /// public class DynamicMoveProvider : ActionBasedContinuousMoveProvider { /// /// Defines which transform the XR Origin's movement direction is relative to. /// /// /// public enum MovementDirection { /// /// Use the forward direction of the head (camera) as the forward direction of the XR Origin's movement. /// HeadRelative, /// /// Use the forward direction of the hand (controller) as the forward direction of the XR Origin's movement. /// HandRelative, } [Space, Header("Movement Direction")] [SerializeField] [Tooltip("Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.")] Transform m_HeadTransform; /// /// Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera. /// public Transform headTransform { get => m_HeadTransform; set => m_HeadTransform = value; } [SerializeField] [Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the left hand.")] Transform m_LeftControllerTransform; /// /// Directs the XR Origin's movement when using the hand-relative mode with the left hand. /// public Transform leftControllerTransform { get => m_LeftControllerTransform; set => m_LeftControllerTransform = value; } [SerializeField] [Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the right hand.")] Transform m_RightControllerTransform; public Transform rightControllerTransform { get => m_RightControllerTransform; set => m_RightControllerTransform = value; } [SerializeField] [Tooltip("Whether to use the specified head transform or left controller transform to direct the XR Origin's movement for the left hand.")] MovementDirection m_LeftHandMovementDirection; /// /// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the left hand. /// /// public MovementDirection leftHandMovementDirection { get => m_LeftHandMovementDirection; set => m_LeftHandMovementDirection = value; } [SerializeField] [Tooltip("Whether to use the specified head transform or right controller transform to direct the XR Origin's movement for the right hand.")] MovementDirection m_RightHandMovementDirection; /// /// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the right hand. /// /// public MovementDirection rightHandMovementDirection { get => m_RightHandMovementDirection; set => m_RightHandMovementDirection = value; } Transform m_CombinedTransform; Pose m_LeftMovementPose = Pose.identity; Pose m_RightMovementPose = Pose.identity; /// protected override void Awake() { base.Awake(); m_CombinedTransform = new GameObject("[Dynamic Move Provider] Combined Forward Source").transform; m_CombinedTransform.SetParent(transform, false); m_CombinedTransform.localPosition = Vector3.zero; m_CombinedTransform.localRotation = Quaternion.identity; forwardSource = m_CombinedTransform; } /// protected override Vector3 ComputeDesiredMove(Vector2 input) { // Don't need to do anything if the total input is zero. // This is the same check as the base method. if (input == Vector2.zero) return Vector3.zero; // Initialize the Head Transform if necessary, getting the Camera from XR Origin if (m_HeadTransform == null) { var xrOrigin = system.xrOrigin; if (xrOrigin != null) { var xrCamera = xrOrigin.Camera; if (xrCamera != null) m_HeadTransform = xrCamera.transform; } } // Get the forward source for the left hand input switch (m_LeftHandMovementDirection) { case MovementDirection.HeadRelative: if (m_HeadTransform != null) m_LeftMovementPose = m_HeadTransform.GetWorldPose(); break; case MovementDirection.HandRelative: if (m_LeftControllerTransform != null) m_LeftMovementPose = m_LeftControllerTransform.GetWorldPose(); break; default: Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_LeftHandMovementDirection}"); break; } // Get the forward source for the right hand input switch (m_RightHandMovementDirection) { case MovementDirection.HeadRelative: if (m_HeadTransform != null) m_RightMovementPose = m_HeadTransform.GetWorldPose(); break; case MovementDirection.HandRelative: if (m_RightControllerTransform != null) m_RightMovementPose = m_RightControllerTransform.GetWorldPose(); break; default: Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_RightHandMovementDirection}"); break; } // Combine the two poses into the forward source based on the magnitude of input var leftHandValue = leftHandMoveAction.action?.ReadValue() ?? Vector2.zero; var rightHandValue = rightHandMoveAction.action?.ReadValue() ?? Vector2.zero; var totalSqrMagnitude = leftHandValue.sqrMagnitude + rightHandValue.sqrMagnitude; var leftHandBlend = 0.5f; if (totalSqrMagnitude > Mathf.Epsilon) leftHandBlend = leftHandValue.sqrMagnitude / totalSqrMagnitude; var combinedPosition = Vector3.Lerp(m_RightMovementPose.position, m_LeftMovementPose.position, leftHandBlend); var combinedRotation = Quaternion.Slerp(m_RightMovementPose.rotation, m_LeftMovementPose.rotation, leftHandBlend); m_CombinedTransform.SetPositionAndRotation(combinedPosition, combinedRotation); return base.ComputeDesiredMove(input); } } }