namespace VRTK { using UnityEngine; #if VRTK_DEFINE_SDK_WINDOWSMR && UNITY_2017_2_OR_NEWER using System.Collections; using UnityEngine.XR.WSA.Input; using VRTK.WindowsMixedReality.Utilities; #endif #if VRTK_DEFINE_WINDOWSMR_CONTROLLER_VISUALIZATION using VRTK.WindowsMixedReality; #endif public class WindowsMR_TrackedObject : MonoBehaviour { #if VRTK_DEFINE_SDK_WINDOWSMR && UNITY_2017_2_OR_NEWER private struct ButtonState { // // Summary: // /// // Normalized amount ([0, 1]) representing how much select is pressed. // /// public float SelectPressedAmount { get; set; } // // Summary: // /// // Depending on the InteractionSourceType of the interaction source, this returning // true could represent a number of equivalent things: main button on a blicker, // air-tap on a hand, and the trigger on a motion controller. // /// public bool SelectPressed { get; set; } // // Summary: // /// // Whether or not the menu button is pressed. // /// public bool MenuPressed { get; set; } // // Summary: // /// // Whether the controller is grasped. // /// public bool Grasped { get; set; } // // Summary: // /// // Whether or not the touchpad is touched. // /// public bool TouchpadTouched { get; set; } // // Summary: // /// // Whether or not the touchpad is pressed, as if a button. // /// public bool TouchpadPressed { get; set; } // // Summary: // /// // Normalized coordinates for the position of a touchpad interaction. // /// public Vector2 TouchpadPosition { get; set; } // // Summary: // /// // Normalized coordinates for the position of a thumbstick. // /// public Vector2 ThumbstickPosition { get; set; } // // Summary: // /// // Whether or not the thumbstick is pressed. // /// public bool ThumbstickPressed { get; set; } } [SerializeField] [Tooltip("Defines the controllers hand.")] private InteractionSourceHandedness handedness; public Vector3 AngularVelocity { get { return angularVelocity; } } public InteractionSourceHandedness Handedness { get { return handedness; } } public uint Index { get { return index; } } private uint index = uint.MaxValue; private ButtonState currentButtonState; private ButtonState prevButtonState; private Vector3 angularVelocity; private float hairTriggerDelta = 0.1f; // amount trigger must be pulled or released to change state private float hairTriggerLimit; private bool hairTriggerState; private bool hairTriggerPrevState; private bool isDetected; public float GetPressAmount(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Select: return currentButtonState.SelectPressedAmount; } return 0; } public bool GetPress(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Select: return currentButtonState.SelectPressed; case InteractionSourcePressType.Grasp: return currentButtonState.Grasped; case InteractionSourcePressType.Menu: return currentButtonState.MenuPressed; case InteractionSourcePressType.Touchpad: return currentButtonState.TouchpadPressed; case InteractionSourcePressType.Thumbstick: return currentButtonState.ThumbstickPressed; } return false; } public bool GetPressDown(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Select: return (prevButtonState.SelectPressed == false && currentButtonState.SelectPressed == true); case InteractionSourcePressType.Grasp: return (prevButtonState.Grasped == false && currentButtonState.Grasped == true); case InteractionSourcePressType.Menu: return (prevButtonState.MenuPressed == false && currentButtonState.MenuPressed == true); case InteractionSourcePressType.Touchpad: return (prevButtonState.TouchpadPressed == false && currentButtonState.TouchpadPressed == true); case InteractionSourcePressType.Thumbstick: return (prevButtonState.ThumbstickPressed == false && currentButtonState.ThumbstickPressed == true); } return false; } public bool GetPressUp(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Select: return (prevButtonState.SelectPressed == true && currentButtonState.SelectPressed == false); case InteractionSourcePressType.Grasp: return (prevButtonState.Grasped == true && currentButtonState.Grasped == false); case InteractionSourcePressType.Menu: return (prevButtonState.MenuPressed == true && currentButtonState.MenuPressed == false); case InteractionSourcePressType.Touchpad: return (prevButtonState.TouchpadPressed == true && currentButtonState.TouchpadPressed == false); case InteractionSourcePressType.Thumbstick: return (prevButtonState.ThumbstickPressed == true && currentButtonState.ThumbstickPressed == false); } return false; } public bool GetTouch(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Touchpad: return currentButtonState.TouchpadTouched; } return false; } public bool GetTouchDown(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Touchpad: return (prevButtonState.TouchpadTouched == false && currentButtonState.TouchpadTouched == true); } return false; } public bool GetTouchUp(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Touchpad: return (prevButtonState.TouchpadTouched == true && currentButtonState.TouchpadTouched == false); } return false; } public Vector2 GetAxis(InteractionSourcePressType button) { switch (button) { case InteractionSourcePressType.Select: return new Vector2(currentButtonState.SelectPressedAmount, 0f); case InteractionSourcePressType.Touchpad: return currentButtonState.TouchpadPosition; case InteractionSourcePressType.Thumbstick: return currentButtonState.ThumbstickPosition; } return Vector2.zero; } public bool GetHairTrigger() { return hairTriggerState; } public bool GetHairTriggerDown() { return (hairTriggerState && !hairTriggerPrevState); } public bool GetHairTriggerUp() { return !hairTriggerState && hairTriggerPrevState; } public void StartHaptics(float intensity = 0.5f, float duration = 0.4f) { InteractionSourceState[] states = InteractionManager.GetCurrentReading(); foreach (InteractionSourceState state in states) { if (state.source.kind == InteractionSourceKind.Controller && state.source.handedness == handedness) { state.source.StartHaptics(intensity, duration); } } } protected virtual void OnEnable() { switch (handedness) { case InteractionSourceHandedness.Left: index = 1; break; case InteractionSourceHandedness.Right: index = 2; break; case InteractionSourceHandedness.Unknown: Debug.LogError("Handedness of " + gameObject.name + " is not set."); break; } InitController(); } protected virtual void OnDisable() { InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected; InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost; InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated; InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed; InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased; } protected virtual void Update() { if (isDetected) { InteractionSourceState[] states = InteractionManager.GetCurrentReading(); foreach (InteractionSourceState state in states) { if (state.source.kind == InteractionSourceKind.Controller && state.source.handedness == handedness) { // Necessary to update Select Button State in Update Loop since it causes issues with PressDown and PressUp // Will be changed in a future iteration (probably VRTK 4) UpdateSelectButton(state); UpdateTouchpadTouch(state); } } } } protected virtual void InitController() { InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected; InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost; InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated; InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed; InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased; #if VRTK_DEFINE_WINDOWSMR_CONTROLLER_VISUALIZATION if (MotionControllerVisualizer.Instance != null) { MotionControllerVisualizer.Instance.OnControllerModelLoaded += AttachControllerModel; } #endif } protected virtual void SetupController(InteractionSource source) { index = source.id; currentButtonState = new ButtonState(); prevButtonState = new ButtonState(); isDetected = true; } #if VRTK_DEFINE_WINDOWSMR_CONTROLLER_VISUALIZATION protected virtual void AttachControllerModel(MotionControllerInfo controllerInfo) { if (controllerInfo.Handedness == Handedness) { Transform controllerTransform = controllerInfo.ControllerParent.transform; controllerTransform.SetParent(transform); controllerTransform.localPosition = Vector3.zero; controllerTransform.localRotation = Quaternion.identity; controllerTransform.localScale = Vector3.one; } } #endif protected virtual void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args) { InteractionSourceState state = args.state; InteractionSource source = state.source; if (source.kind == InteractionSourceKind.Controller && source.handedness == handedness) { SetupController(source); } } protected virtual void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs args) { InteractionSourceState state = args.state; InteractionSource source = state.source; if (source.kind == InteractionSourceKind.Controller && source.handedness == handedness) { index = uint.MaxValue; currentButtonState = new ButtonState(); isDetected = false; } } protected virtual void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs args) { InteractionSourceState state = args.state; InteractionSource source = state.source; if (source.kind == InteractionSourceKind.Controller && source.handedness == handedness) { if (!isDetected) { SetupController(source); } UpdateAxis(state); UpdatePose(state); } } protected virtual void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) { InteractionSourceState state = args.state; InteractionSource source = state.source; if (source.kind == InteractionSourceKind.Controller && source.handedness == handedness) { UpdateButtonState(args.pressType, state); } } protected virtual void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs args) { InteractionSourceState state = args.state; InteractionSource source = state.source; if (source.kind == InteractionSourceKind.Controller && source.handedness == handedness) { UpdateButtonState(args.pressType, state); } } protected virtual void UpdatePose(InteractionSourceState state) { UpdateAngularVelocity(state.sourcePose); UpdateControllerPose(state.sourcePose); } // Workaround for Select Button // Issue: Pressed and Released event only recognize Select once and Select is pressed when selectPressedAmount==1, // so on press Select State is not always true and therefore not 'pressed'. // Updating SelectPressed in UpdateEvent of WSA.XR causes issues because the event and Unity Update are not synched // and therefore VRTK's polling of GetPressDown and GetPressUp might already been overwritten. protected virtual void UpdateSelectButton(InteractionSourceState state) { prevButtonState.SelectPressed = currentButtonState.SelectPressed; currentButtonState.SelectPressed = state.selectPressed; } protected virtual void UpdateTouchpadTouch(InteractionSourceState state) { if (state.source.supportsTouchpad) { prevButtonState.TouchpadTouched = currentButtonState.TouchpadTouched; currentButtonState.TouchpadTouched = state.touchpadTouched; } } protected virtual void UpdateButtonState(InteractionSourcePressType button, InteractionSourceState state) { switch (button) { case InteractionSourcePressType.Grasp: prevButtonState.Grasped = currentButtonState.Grasped; currentButtonState.Grasped = state.grasped; break; case InteractionSourcePressType.Menu: prevButtonState.MenuPressed = currentButtonState.MenuPressed; currentButtonState.MenuPressed = state.menuPressed; break; case InteractionSourcePressType.Touchpad: prevButtonState.TouchpadPressed = currentButtonState.TouchpadPressed; currentButtonState.TouchpadPressed = state.touchpadPressed; break; case InteractionSourcePressType.Thumbstick: prevButtonState.ThumbstickPressed = currentButtonState.ThumbstickPressed; currentButtonState.ThumbstickPressed = state.thumbstickPressed; break; } StartCoroutine(UpdateButtonStateAfterNextFrame(button)); } protected virtual IEnumerator UpdateButtonStateAfterNextFrame(InteractionSourcePressType button) { yield return new WaitForEndOfFrame(); switch (button) { case InteractionSourcePressType.Grasp: prevButtonState.Grasped = currentButtonState.Grasped; break; case InteractionSourcePressType.Menu: prevButtonState.MenuPressed = currentButtonState.MenuPressed; break; case InteractionSourcePressType.Touchpad: prevButtonState.TouchpadPressed = currentButtonState.TouchpadPressed; break; case InteractionSourcePressType.Thumbstick: prevButtonState.ThumbstickPressed = currentButtonState.ThumbstickPressed; break; } } protected virtual void UpdateControllerPose(InteractionSourcePose pose) { Quaternion newRotation; if (pose.TryGetRotation(out newRotation, InteractionSourceNode.Grip)) { transform.localRotation = newRotation; } Vector3 newPosition; if (pose.TryGetPosition(out newPosition, InteractionSourceNode.Grip)) { transform.localPosition = newPosition; } } protected virtual void UpdateAxis(InteractionSourceState state) { InteractionSource source = state.source; prevButtonState.SelectPressedAmount = currentButtonState.SelectPressedAmount; currentButtonState.SelectPressedAmount = state.selectPressedAmount; UpdateHairTrigger(); if (source.supportsTouchpad) { currentButtonState.TouchpadPosition = state.touchpadPosition; } if (source.supportsThumbstick) { currentButtonState.ThumbstickPosition = state.thumbstickPosition; } } protected virtual void UpdateAngularVelocity(InteractionSourcePose pose) { Vector3 newAngularVelocity; if (pose.TryGetAngularVelocity(out newAngularVelocity)) { angularVelocity = newAngularVelocity; } } protected virtual void UpdateHairTrigger() { hairTriggerPrevState = hairTriggerState; float value = currentButtonState.SelectPressedAmount; if (hairTriggerState) { if (value < hairTriggerLimit - hairTriggerDelta || value <= 0.0f) hairTriggerState = false; } else { if (value > hairTriggerLimit + hairTriggerDelta || value >= 1.0f) hairTriggerState = true; } hairTriggerLimit = hairTriggerState ? Mathf.Max(hairTriggerLimit, value) : Mathf.Min(hairTriggerLimit, value); } #endif } }