using System.Collections.Generic; using HurricaneVR.Framework.ControllerInput; using HurricaneVR.Framework.Core; using HurricaneVR.Framework.Shared.Utilities; using UnityEngine; using UnityEngine.XR; namespace HurricaneVR.Framework.Shared { public abstract class HVRController : MonoBehaviour { public HVRHandSide Side { get; set; } public HVRButtonState GripButtonState; public HVRButtonState TriggerButtonState; public HVRButtonState PrimaryButtonState; public HVRButtonState SecondaryButtonState; public HVRButtonState MenuButtonState; public HVRButtonState PrimaryTouchButtonState; public HVRButtonState SecondaryTouchButtonState; public HVRButtonState JoystickButtonState; public HVRButtonState TrackpadButtonState; public HVRButtonState JoystickTouchState; public HVRButtonState TrackPadTouchState; public HVRButtonState TriggerTouchState; public HVRButtonState ThumbTouchState; public HVRButtonState TriggerNearTouchState; public HVRButtonState ThumbNearTouchState; public HVRButtonState TrackPadUp; public HVRButtonState TrackPadLeft; public HVRButtonState TrackPadRight; public HVRButtonState TrackPadDown; public Vector2 JoystickAxis; public Vector2 TrackpadAxis; public bool PrimaryButton; public bool SecondaryButton; public bool JoystickClicked; public bool TrackPadClicked; public bool MenuButton; public bool PrimaryTouch; public bool SecondaryTouch; public float Grip; public float GripForce; public float Trigger; public bool ThumbTouch; public bool TriggerTouch; public bool ThumbNearTouch; public bool TriggerNearTouch; /// /// Supplied by SteamVR, OpenXR, or OVR input bindings /// public bool GripButton; /// /// Suppplied by SteamVR, OpenXR, or OVR input bindings /// public bool TriggerButton; public bool JoystickTouch; public bool TrackPadTouch; public float[] FingerCurls; private float[] openxrFingerCurls; public float ThumbCurl; public float IndexCurl; public float MiddleCurl; public float RingCurl; public float PinkyCurl; public Vector3 Velocity; public Vector3 AngularVelocity; public bool IsActive { get; set; } public XRNode XRNode; private InputDevice _device; public InputDevice Device { get { if (_device.isValid) return _device; _device = InputDevices.GetDeviceAtXRNode(XRNode); return _device; } } public Vector2 ThumbstickDeadZone { get; set; } public HVRInputSettings InputMap { get; set; } public HVRFingerSettings FingerSettings { get; set; } public bool Knuckles => ControllerType == HVRControllerType.Knuckles; public bool WMR => ControllerType == HVRControllerType.WMR; public bool Vive => ControllerType == HVRControllerType.Vive; private static readonly Dictionary _leftButtonStates = new Dictionary(); private static readonly Dictionary _rightButtonStates = new Dictionary(); public static float[] LeftFingerCurls = new float[5]; public static float[] RightFingerCurls = new float[5]; public HVRControllerType ControllerType { get; set; } public float AngularVelocityMagnitude; public float VelocityMagnitude; public readonly CircularBuffer RecentVelocities = new CircularBuffer(200); protected virtual void Awake() { FingerCurls = new float[5]; openxrFingerCurls = new float[5]; } protected virtual void Start() { ResetTrackedVelocities(); } private void Update() { UpdateInput(); CorrectDeadzone(); CheckButtonState(HVRButtons.Grip, ref GripButtonState); CheckButtonState(HVRButtons.Trigger, ref TriggerButtonState); CheckButtonState(HVRButtons.JoystickButton, ref JoystickButtonState); CheckButtonState(HVRButtons.TrackPadButton, ref TrackpadButtonState); CheckButtonState(HVRButtons.Primary, ref PrimaryButtonState); CheckButtonState(HVRButtons.Secondary, ref SecondaryButtonState); CheckButtonState(HVRButtons.Menu, ref MenuButtonState); CheckButtonState(HVRButtons.PrimaryTouch, ref PrimaryTouchButtonState); CheckButtonState(HVRButtons.SecondaryTouch, ref SecondaryTouchButtonState); CheckButtonState(HVRButtons.JoystickTouch, ref JoystickTouchState); CheckButtonState(HVRButtons.TrackPadTouch, ref TrackPadTouchState); CheckButtonState(HVRButtons.TriggerTouch, ref TriggerTouchState); CheckButtonState(HVRButtons.ThumbTouch, ref ThumbTouchState); CheckButtonState(HVRButtons.TriggerNearTouch, ref TriggerNearTouchState); CheckButtonState(HVRButtons.ThumbNearTouch, ref ThumbNearTouchState); CheckButtonState(HVRButtons.TrackPadUp, ref TrackPadUp); CheckButtonState(HVRButtons.TrackPadLeft, ref TrackPadLeft); CheckButtonState(HVRButtons.TrackPadRight, ref TrackPadRight); CheckButtonState(HVRButtons.TrackPadDown, ref TrackPadDown); RecentVelocities.Enqueue(Velocity.magnitude); Device.TryGetFeatureValue(CommonUsages.deviceVelocity, out Velocity); Device.TryGetFeatureValue(CommonUsages.deviceAngularVelocity, out AngularVelocity); AngularVelocityMagnitude = AngularVelocity.magnitude; VelocityMagnitude = Velocity.magnitude; UpdateFingerCurls(); var curls = LeftFingerCurls; if (XRNode == XRNode.RightHand) { curls = RightFingerCurls; } for (int i = 0; i < 5; i++) { curls[i] = FingerCurls[i]; } AfterInputUpdate(); } protected virtual void AfterInputUpdate() { } protected virtual void UpdateFingerCurls() { if (FingerSettings) { FingerSettings.Evaluate( FingerCurls, Grip, Trigger, TriggerTouchState.Active, PrimaryTouchButtonState.Active, SecondaryTouchButtonState.Active, TrackPadTouchState.Active, JoystickTouchState.Active, Knuckles, HVRInputManager.Instance.IsOpenXR); } #if USING_XRHANDS if (Knuckles && HVRInputManager.Instance.IsOpenXR) { HVROpenXRFingerCurls.Update(); if (HVROpenXRFingerCurls.TryGetCurls(Side, openxrFingerCurls)) { FingerCurls[2] = openxrFingerCurls[2]; FingerCurls[3] = openxrFingerCurls[3]; FingerCurls[4] = openxrFingerCurls[4]; } } #endif } protected void ResetTrackedVelocities() { for (var i = 0; i < 200; i++) { RecentVelocities.Enqueue(0f); } } public float GetAverageVelocity(float seconds) { var frames = seconds / Time.fixedDeltaTime; var sum = 0f; for (var i = 0; i < frames; i++) { sum += RecentVelocities[i]; } if (frames == 0f) return 0f; return sum / frames; } private void CorrectDeadzone() { if (Mathf.Abs(JoystickAxis.x) < ThumbstickDeadZone.x) JoystickAxis.x = 0f; if (Mathf.Abs(JoystickAxis.y) < ThumbstickDeadZone.y) JoystickAxis.y = 0f; } protected abstract void UpdateInput(); protected virtual void CheckButtonState(HVRButtons button, ref HVRButtonState buttonState) { ResetButton(ref buttonState); switch (button) { case HVRButtons.Grip: buttonState.Value = Grip; SetButtonState(button, ref buttonState, GetIsGripPressed()); break; case HVRButtons.Trigger: buttonState.Value = Trigger; SetButtonState(button, ref buttonState, GetIsTriggerPressed()); break; case HVRButtons.Primary: SetButtonState(button, ref buttonState, PrimaryButton); break; case HVRButtons.PrimaryTouch: SetButtonState(button, ref buttonState, PrimaryTouch); break; case HVRButtons.Secondary: SetButtonState(button, ref buttonState, SecondaryButton); break; case HVRButtons.SecondaryTouch: SetButtonState(button, ref buttonState, SecondaryTouch); break; case HVRButtons.Menu: SetButtonState(button, ref buttonState, MenuButton); break; case HVRButtons.JoystickButton: SetButtonState(button, ref buttonState, JoystickClicked); break; case HVRButtons.TrackPadButton: SetButtonState(button, ref buttonState, TrackPadClicked); break; case HVRButtons.JoystickTouch: SetButtonState(button, ref buttonState, JoystickTouch); break; case HVRButtons.TrackPadTouch: SetButtonState(button, ref buttonState, TrackPadTouch); break; case HVRButtons.TriggerTouch: SetButtonState(button, ref buttonState, TriggerTouch); break; case HVRButtons.ThumbTouch: SetButtonState(button, ref buttonState, ThumbTouch); break; case HVRButtons.TriggerNearTouch: SetButtonState(button, ref buttonState, TriggerNearTouch); break; case HVRButtons.ThumbNearTouch: SetButtonState(button, ref buttonState, ThumbNearTouch); break; case HVRButtons.TrackPadLeft: SetButtonState(button, ref buttonState, TrackPadClicked && TrackpadAxis.x <= -InputMap.Axis2DLeftThreshold); break; case HVRButtons.TrackPadRight: SetButtonState(button, ref buttonState, TrackPadClicked && TrackpadAxis.x >= InputMap.Axis2DRighThreshold); break; case HVRButtons.TrackPadUp: SetButtonState(button, ref buttonState, TrackPadClicked && TrackpadAxis.y >= InputMap.Axis2DUpThreshold); break; case HVRButtons.TrackPadDown: SetButtonState(button, ref buttonState, TrackPadClicked && TrackpadAxis.y <= -InputMap.Axis2DDownThreshold); break; } } private bool _triggerLowerReset; private bool _triggerUpperReset; private bool _gripLowerReset; private bool _gripUpperReset; protected virtual bool GetIsTriggerPressed() { if (InputMap.TriggerUseAnalog) { if (InputMap.TriggerUseReleaseThreshold) { if (InputMap.TriggerThreshold > InputMap.TriggerReleaseThreshold) { if (TriggerButtonState.Active) return Trigger >= InputMap.TriggerReleaseThreshold; return Trigger >= InputMap.TriggerThreshold; } if (Trigger < InputMap.TriggerThreshold) _triggerLowerReset = true; if (TriggerButtonState.Active) { if (Trigger > InputMap.TriggerReleaseThreshold) _triggerUpperReset = true; if (Trigger < InputMap.TriggerReleaseThreshold && _triggerUpperReset) { _triggerUpperReset = false; return false; } if (Trigger < InputMap.TriggerThreshold) return false; return true; } if (Trigger > InputMap.TriggerReleaseThreshold && !_triggerUpperReset && !InputMap.TriggerRequireReset) return true; if (Trigger > InputMap.TriggerThreshold && _triggerLowerReset) { _triggerLowerReset = false; return true; } return false; } return Trigger >= InputMap.TriggerThreshold; } return TriggerButton; } protected virtual bool GetIsGripPressed() { if (InputMap.GripUseAnalog) { if (InputMap.GripUseReleaseThreshold) { if (InputMap.GripThreshold > InputMap.GripReleaseThreshold) { if (GripButtonState.Active) return Grip >= InputMap.GripReleaseThreshold; return Grip >= InputMap.GripThreshold; } if (Grip < InputMap.GripThreshold) _gripLowerReset = true; if (GripButtonState.Active) { if (Grip > InputMap.GripReleaseThreshold) _gripUpperReset = true; if (Grip < InputMap.GripReleaseThreshold && _gripUpperReset) { _gripUpperReset = false; return false; } if (Grip < InputMap.GripThreshold) return false; return true; } if (Grip > InputMap.GripReleaseThreshold && !_gripUpperReset && !InputMap.GripRequireReset) return true; if (Grip > InputMap.GripThreshold && _gripLowerReset) { _gripLowerReset = false; return true; } return false; } return Grip >= InputMap.GripThreshold; } return GripButton; } protected void SetButtonState(HVRButtons button, ref HVRButtonState buttonState, bool pressed) { if (pressed) { if (!buttonState.Active) { buttonState.JustActivated = true; buttonState.Active = true; } } else { if (buttonState.Active) { buttonState.Active = false; buttonState.JustDeactivated = true; } } SetButtonState(Side, button, buttonState); } protected void ResetButton(ref HVRButtonState buttonState) { buttonState.JustDeactivated = false; buttonState.JustActivated = false; buttonState.Value = 0f; } public static void SetButtonState(HVRHandSide side, HVRButtons button, HVRButtonState state) { var map = side == HVRHandSide.Right ? _rightButtonStates : _leftButtonStates; map[button] = state; } public static HVRButtonState GetButtonState(HVRHandSide side, HVRButtons button) { var map = side == HVRHandSide.Right ? _rightButtonStates : _leftButtonStates; map.TryGetValue(button, out var state); return state; } public virtual void Vibrate(HapticData haptics) { if (HVRSettings.Instance.DisableHaptics) return; if (haptics != null && haptics.Valid) { Vibrate(haptics.Amplitude, haptics.Duration, haptics.Frequency); } } /// /// Sends a haptic impulse to the controller. /// /// The normalized (0.0 to 1.0) amplitude value of the haptic impulse to play on the device. /// The duration in seconds that the haptic impulse will play. Only supported on Oculus. public virtual void Vibrate(float amplitude, float duration = 1f, float frequency = 1f) { if (HVRSettings.Instance.DisableHaptics) return; if (Device.isValid && Device.TryGetHapticCapabilities(out var hapticCapabilities) && hapticCapabilities.supportsImpulse) { amplitude = Mathf.Clamp(amplitude, 0f, 1f); Device.SendHapticImpulse(0, amplitude, duration); } } } public enum InputSDK { XRInput, Oculus, SteamVR, InputSystem, None } public enum HVRControllerType { None, Oculus, WMR, Vive, Knuckles, Cosmos, ReverbG2, Pico } }