// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using System.Linq; using UltimateXR.Core; using UltimateXR.Core.Settings; using UltimateXR.Haptics; using UnityEngine; using UnityEngine.XR; #if ULTIMATEXR_UNITY_XR_OCULUS using Unity.XR.Oculus; #endif namespace UltimateXR.Devices.Integrations { /// /// Generic base class for left-right input devices that can be handled through the new /// generic Unity XR input interface. Before, we had to manually support each SDK individually. /// public abstract partial class UxrUnityXRControllerInput : UxrControllerInput { #region Public Types & Data /// /// Gets list of controller names that the component can handle /// public abstract IEnumerable ControllerNames { get; } /// /// We use this when we are implementing new controllers that we don't know the name of, in order to /// show the controller names in the UxrDebugControllerPanel. /// Returning true will register the controllers in no /// matter which input device gets connected. Then using the UxrDebugControllerPanel we can see which /// devices got connected. /// This is mostly useful for untethered devices that cannot be tested directly in Unity. /// public virtual bool ForceUseAlways => false; #endregion #region Public Overrides UxrControllerInput /// public override string LeftControllerName => _deviceLeft.isValid ? _deviceLeft.name : string.Empty; /// public override string RightControllerName => _deviceRight.isValid ? _deviceRight.name : string.Empty; /// public override bool IsControllerEnabled(UxrHandSide handSide) { return GetInputDevice(handSide).isValid; } /// public override float GetInput1D(UxrHandSide handSide, UxrInput1D input1D, bool getIgnoredInput = false) { if (ShouldIgnoreInput(handSide, getIgnoredInput)) { return 0.0f; } InputDevice inputDevice = GetInputDevice(handSide); if (inputDevice.isValid) { float value; switch (input1D) { case UxrInput1D.Grip: if (inputDevice.TryGetFeatureValue(CommonUsages.grip, out value)) { return value; } break; case UxrInput1D.Trigger: if (inputDevice.TryGetFeatureValue(CommonUsages.trigger, out value)) { return value; } break; case UxrInput1D.Trigger2: break; } } return 0.0f; } /// public override Vector2 GetInput2D(UxrHandSide handSide, UxrInput2D input2D, bool getIgnoredInput = false) { if (ShouldIgnoreInput(handSide, getIgnoredInput)) { return Vector2.zero; } InputDevice inputDevice = GetInputDevice(handSide); if (inputDevice.isValid) { Vector2 value; switch (input2D) { case UxrInput2D.Joystick: if (inputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out value)) { return FilterTwoAxesDeadZone(value, JoystickDeadZone); } break; case UxrInput2D.Joystick2: if (inputDevice.TryGetFeatureValue(CommonUsages.secondary2DAxis, out value)) { return FilterTwoAxesDeadZone(value, JoystickDeadZone); } break; } } return Vector2.zero; } /// public override UxrControllerInputCapabilities GetControllerCapabilities(UxrHandSide handSide) { UxrControllerInputCapabilities capabilities = 0; InputDevice inputDevice = GetInputDevice(handSide); if (!inputDevice.isValid) { return capabilities; } if (inputDevice.TryGetHapticCapabilities(out HapticCapabilities hapticCapabilities)) { if (hapticCapabilities.supportsBuffer) { capabilities |= UxrControllerInputCapabilities.HapticClips; } if (hapticCapabilities.supportsImpulse) { capabilities |= UxrControllerInputCapabilities.HapticImpulses; } } return capabilities; } /// public override void SendHapticFeedback(UxrHandSide handSide, UxrHapticClip hapticClip) { InputDevice inputDevice = GetInputDevice(handSide); if (!inputDevice.isValid) { return; } if (hapticClip == null) { return; } if (!inputDevice.TryGetHapticCapabilities(out HapticCapabilities hapticCapabilities)) { return; } if (hapticClip.Clip == null || !hapticCapabilities.supportsBuffer) { SendHapticFeedback(handSide, hapticClip.FallbackClipType, hapticClip.FallbackAmplitude, hapticClip.FallbackDurationSeconds, hapticClip.HapticMode); return; } // Create haptics clip from audio byte[] hapticBuffer = CreateHapticBufferFromAudioClip(inputDevice, hapticClip.Clip); if (hapticBuffer == null) { SendHapticFeedback(handSide, hapticClip.FallbackClipType, hapticClip.FallbackAmplitude, hapticClip.FallbackDurationSeconds, hapticClip.HapticMode); return; } // Readjust amplitude? if (Mathf.Approximately(hapticClip.ClipAmplitude, 1.0f) == false) { for (int i = 0; i < hapticBuffer.Length; ++i) { hapticBuffer[i] = (byte)Mathf.Clamp(Mathf.RoundToInt(hapticBuffer[i] * hapticClip.ClipAmplitude), 0, 255); } } // Send using replace or mix uint channel = 0; if (hapticClip.HapticMode == UxrHapticMode.Mix && hapticCapabilities.numChannels > 0) { if (handSide == UxrHandSide.Left) { _leftHapticChannel = (_leftHapticChannel + 1) % hapticCapabilities.numChannels; channel = _leftHapticChannel; } else { _rightHapticChannel = (_rightHapticChannel + 1) % hapticCapabilities.numChannels; channel = _rightHapticChannel; } } else { inputDevice.StopHaptics(); _leftHapticChannel = 0; _rightHapticChannel = 0; } inputDevice.SendHapticBuffer(channel, hapticBuffer); } /// public override void SendHapticFeedback(UxrHandSide handSide, float frequency, float amplitude, float durationSeconds, UxrHapticMode hapticMode = UxrHapticMode.Mix) { InputDevice inputDevice = GetInputDevice(handSide); if (!inputDevice.isValid) { return; } if (!inputDevice.TryGetHapticCapabilities(out HapticCapabilities hapticCapabilities)) { return; } // Setup using replace or mix uint channel = 0; if (hapticMode == UxrHapticMode.Mix && hapticCapabilities.numChannels > 0) { if (handSide == UxrHandSide.Left) { _leftHapticChannel = (_leftHapticChannel + 1) % hapticCapabilities.numChannels; channel = _leftHapticChannel; } else { _rightHapticChannel = (_rightHapticChannel + 1) % hapticCapabilities.numChannels; channel = _rightHapticChannel; } } else { inputDevice.StopHaptics(); _leftHapticChannel = 0; _rightHapticChannel = 0; } // Send if (hapticCapabilities.supportsBuffer) { byte[] samples = new byte[(int)(hapticCapabilities.bufferFrequencyHz * durationSeconds)]; int steps = frequency > 0.0f ? Mathf.RoundToInt(hapticCapabilities.bufferFrequencyHz / frequency) : -1; byte sample = (byte)Mathf.Clamp(amplitude * 255.0f, 0, 255.0f); for (int i = 0; i < samples.Length; ++i) { if (steps < 2) { samples[i] = sample; } else { samples[i] = i % steps == 0 ? sample : (byte)0; } } inputDevice.SendHapticBuffer(channel, samples); } else if (hapticCapabilities.supportsImpulse) { inputDevice.SendHapticImpulse(channel, amplitude, durationSeconds); } } /// public override void StopHapticFeedback(UxrHandSide handSide) { InputDevice inputDevice = GetInputDevice(handSide); if (!inputDevice.isValid) { return; } inputDevice.StopHaptics(); } #endregion #region Unity /// /// Initializes variables and subscribes to events. /// If the controllers were already initialized, enables the component. Otherwise it begins disabled until devices are /// connected. /// protected override void Awake() { base.Awake(); _leftHapticChannel = 0; _rightHapticChannel = 0; if (enabled) { InputDevices.deviceConnected += InputDevices_DeviceConnected; InputDevices.deviceDisconnected += InputDevices_DeviceDisconnected; // Check if the device is already connected. This may happen if a new scene was loaded, because // the connection events were already triggered and processed. We should have them registered in // our static fields. _deviceLeft = s_activeInputDevices.FirstOrDefault(d => ControllerNames.Any(n => string.Equals(d.name, n)) && IsLeftController(d)); _deviceRight = s_activeInputDevices.FirstOrDefault(d => ControllerNames.Any(n => string.Equals(d.name, n)) && IsRightController(d)); List devices = new List(); InputDevices.GetDevices(devices); if (!_deviceLeft.isValid) { _deviceLeft = devices.FirstOrDefault(d => ControllerNames.Any(n => string.Equals(d.name, n)) && IsLeftController(d)); } if (!_deviceRight.isValid) { _deviceRight = devices.FirstOrDefault(d => ControllerNames.Any(n => string.Equals(d.name, n)) && IsRightController(d)); } enabled = _deviceLeft.isValid || _deviceRight.isValid; RaiseConnectOnStart = enabled; } } /// /// Unsubscribes from device events. /// protected override void OnDestroy() { base.OnDestroy(); InputDevices.deviceConnected -= InputDevices_DeviceConnected; InputDevices.deviceDisconnected -= InputDevices_DeviceDisconnected; } #endregion #region Event Handling Methods /// /// Event called when a device is connected. Check for compatible devices. /// /// The device that was connected private void InputDevices_DeviceConnected(InputDevice inputDevice) { // Check if device is compatible with component if (ForceUseAlways || ControllerNames.Any(n => string.Equals(n, inputDevice.name))) { // Found compatible device. Look for features. List listFeatures = new List(); bool isController = false; // Check for controllers and side if (IsLeftController(inputDevice)) { // Left controller if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} {InputClassName}::{nameof(InputDevices_DeviceConnected)}: Device name {inputDevice.name} was registered by {InputClassName} and is being processed as left controller. InputDevice.isValid={inputDevice.isValid}"); } _deviceLeft = inputDevice; isController = true; } else if (IsRightController(inputDevice)) { // Right controller if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} {InputClassName}::{nameof(InputDevices_DeviceConnected)}: Device name {inputDevice.name} was registered by {InputClassName} and is being processed as right controller. InputDevice.isValid={inputDevice.isValid}"); } _deviceRight = inputDevice; isController = true; } if (isController) { // Register active device s_activeInputDevices.Add(inputDevice); if (!enabled) { // Component is disabled. Enable it and send Connected event. enabled = true; OnDeviceConnected(new UxrDeviceConnectEventArgs(true)); } } } else { // Check for controllers and side if (IsLeftController(inputDevice)) { // Left controller if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} {InputClassName}::{nameof(InputDevices_DeviceConnected)}: Left device connected unknown: {inputDevice.name}. InputDevice.isValid={inputDevice.isValid}"); } } else if (IsRightController(inputDevice)) { // Right controller if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} {InputClassName}::{nameof(InputDevices_DeviceConnected)}: Right device connected unknown: {inputDevice.name}. InputDevice.isValid={inputDevice.isValid}"); } } } } /// /// Event called when a device is disconnected. We use it to update our internal lists. /// /// The device that was disconnected private void InputDevices_DeviceDisconnected(InputDevice inputDevice) { // Check if device is compatible with component if (ForceUseAlways || ControllerNames.Any(n => string.Equals(n, inputDevice.name))) { if (string.Equals(inputDevice.serialNumber, _deviceLeft.serialNumber) || string.Equals(inputDevice.serialNumber, _deviceRight.serialNumber)) { if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} {InputClassName}::{nameof(InputDevices_DeviceDisconnected)}: Device name {inputDevice.name} was registered by {InputClassName} and is being disconnected. InputDevice.isValid={inputDevice.isValid}"); } } // Unregister device s_activeInputDevices.RemoveAll(i => string.Equals(i.name, inputDevice.name)); // If last device was disconnected, disable component. Component will be re-enabled using connection event. if (enabled && !_deviceLeft.isValid && !_deviceRight.isValid) { enabled = false; OnDeviceConnected(new UxrDeviceConnectEventArgs(false)); } } } #endregion #region Protected Overrides UxrControllerInput /// /// Updates the input state. This should not be called by the user since it is called by the framework already. /// protected override void UpdateInput() { base.UpdateInput(); bool buttonPressTriggerLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Trigger, ButtonContact.Press); bool buttonPressTriggerRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Trigger, ButtonContact.Press); bool buttonPressTrigger2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Trigger2, ButtonContact.Press); bool buttonPressTrigger2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Trigger2, ButtonContact.Press); bool buttonPressJoystickLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Joystick, ButtonContact.Press); bool buttonPressJoystickRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Joystick, ButtonContact.Press); bool buttonPressJoystick2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Joystick2, ButtonContact.Press); bool buttonPressJoystick2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Joystick2, ButtonContact.Press); bool buttonPressButton1Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button1, ButtonContact.Press); bool buttonPressButton1Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button1, ButtonContact.Press); bool buttonPressButton2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button2, ButtonContact.Press); bool buttonPressButton2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button2, ButtonContact.Press); bool buttonPressButton3Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button3, ButtonContact.Press); bool buttonPressButton3Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button3, ButtonContact.Press); bool buttonPressButton4Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button4, ButtonContact.Press); bool buttonPressButton4Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button4, ButtonContact.Press); bool buttonPressBumperLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Bumper, ButtonContact.Press); bool buttonPressBumperRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Bumper, ButtonContact.Press); bool buttonPressBumper2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Bumper2, ButtonContact.Press); bool buttonPressBumper2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Bumper2, ButtonContact.Press); bool buttonPressMenuLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Menu, ButtonContact.Press); bool buttonPressMenuRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Menu, ButtonContact.Press); bool buttonPressGripLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Grip, ButtonContact.Press); bool buttonPressGripRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Grip, ButtonContact.Press); bool buttonPressThumbCapSenseLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.ThumbCapSense, ButtonContact.Press); bool buttonPressThumbCapSenseRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.ThumbCapSense, ButtonContact.Press); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Trigger, buttonPressTriggerLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Trigger, buttonPressTriggerRight); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Trigger2, buttonPressTrigger2Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Trigger2, buttonPressTrigger2Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Joystick, buttonPressJoystickLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Joystick, buttonPressJoystickRight); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Joystick2, buttonPressJoystick2Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Joystick2, buttonPressJoystick2Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Button1, buttonPressButton1Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Button1, buttonPressButton1Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Button2, buttonPressButton2Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Button2, buttonPressButton2Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Button3, buttonPressButton3Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Button3, buttonPressButton3Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Button4, buttonPressButton4Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Button4, buttonPressButton4Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Bumper, buttonPressBumperLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Bumper, buttonPressBumperRight); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Bumper2, buttonPressBumper2Left); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Bumper2, buttonPressBumper2Right); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Menu, buttonPressMenuLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Menu, buttonPressMenuRight); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.Grip, buttonPressGripLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.Grip, buttonPressGripRight); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.ThumbCapSense, buttonPressThumbCapSenseLeft); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.ThumbCapSense, buttonPressThumbCapSenseRight); bool buttonTouchTriggerLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Trigger, ButtonContact.Touch); bool buttonTouchTriggerRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Trigger, ButtonContact.Touch); bool buttonTouchTrigger2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Trigger2, ButtonContact.Touch); bool buttonTouchTrigger2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Trigger2, ButtonContact.Touch); bool buttonTouchJoystickLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Joystick, ButtonContact.Touch); bool buttonTouchJoystickRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Joystick, ButtonContact.Touch); bool buttonTouchJoystick2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Joystick2, ButtonContact.Touch); bool buttonTouchJoystick2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Joystick2, ButtonContact.Touch); bool buttonTouchButton1Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button1, ButtonContact.Touch); bool buttonTouchButton1Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button1, ButtonContact.Touch); bool buttonTouchButton2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button2, ButtonContact.Touch); bool buttonTouchButton2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button2, ButtonContact.Touch); bool buttonTouchButton3Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button3, ButtonContact.Touch); bool buttonTouchButton3Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button3, ButtonContact.Touch); bool buttonTouchButton4Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Button4, ButtonContact.Touch); bool buttonTouchButton4Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Button4, ButtonContact.Touch); bool buttonTouchBumperLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Bumper, ButtonContact.Touch); bool buttonTouchBumperRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Bumper, ButtonContact.Touch); bool buttonTouchBumper2Left = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Bumper2, ButtonContact.Touch); bool buttonTouchBumper2Right = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Bumper2, ButtonContact.Touch); bool buttonTouchMenuLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Menu, ButtonContact.Touch); bool buttonTouchMenuRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Menu, ButtonContact.Touch); bool buttonTouchGripLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.Grip, ButtonContact.Touch); bool buttonTouchGripRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.Grip, ButtonContact.Touch); bool buttonTouchThumbCapSenseLeft = HasButtonContact(UxrHandSide.Left, UxrInputButtons.ThumbCapSense, ButtonContact.Touch); bool buttonTouchThumbCapSenseRight = HasButtonContact(UxrHandSide.Right, UxrInputButtons.ThumbCapSense, ButtonContact.Touch); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Trigger, buttonTouchTriggerLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Trigger, buttonTouchTriggerRight); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Trigger2, buttonTouchTrigger2Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Trigger2, buttonTouchTrigger2Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Joystick, buttonTouchJoystickLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Joystick, buttonTouchJoystickRight); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Joystick2, buttonTouchJoystick2Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Joystick2, buttonTouchJoystick2Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Button1, buttonTouchButton1Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Button1, buttonTouchButton1Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Button2, buttonTouchButton2Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Button2, buttonTouchButton2Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Button3, buttonTouchButton3Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Button3, buttonTouchButton3Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Button4, buttonTouchButton4Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Button4, buttonTouchButton4Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Bumper, buttonTouchBumperLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Bumper, buttonTouchBumperRight); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Bumper2, buttonTouchBumper2Left); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Bumper2, buttonTouchBumper2Right); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Menu, buttonTouchMenuLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Menu, buttonTouchMenuRight); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.Grip, buttonTouchGripLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.Grip, buttonTouchGripRight); SetButtonFlags(ButtonFlags.TouchFlagsLeft, UxrInputButtons.ThumbCapSense, buttonTouchThumbCapSenseLeft); SetButtonFlags(ButtonFlags.TouchFlagsRight, UxrInputButtons.ThumbCapSense, buttonTouchThumbCapSenseRight); Vector2 leftJoystick = GetInput2D(UxrHandSide.Left, UxrInput2D.Joystick, true); Vector2 leftDPad = leftJoystick; // Mapped to joystick by default if (leftJoystick != Vector2.zero && leftJoystick.magnitude > AnalogAsDPadThreshold) { float joystickAngle = Input2DToAngle(leftJoystick); bool touchPadFix = !MainJoystickIsTouchpad || buttonPressJoystickLeft; SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickLeft, touchPadFix && IsInput2dDPadLeft(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickRight, touchPadFix && IsInput2dDPadRight(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickUp, touchPadFix && IsInput2dDPadUp(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickDown, touchPadFix && IsInput2dDPadDown(joystickAngle)); } else { SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickLeft, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickRight, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickUp, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.JoystickDown, false); } if (leftDPad != Vector2.zero && leftDPad.magnitude > AnalogAsDPadThreshold) { float dPadAngle = Input2DToAngle(leftDPad); bool touchPadFix = !MainJoystickIsTouchpad || buttonPressJoystickLeft; SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadLeft, touchPadFix && IsInput2dDPadLeft(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadRight, touchPadFix && IsInput2dDPadRight(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadUp, touchPadFix && IsInput2dDPadUp(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadDown, touchPadFix && IsInput2dDPadDown(dPadAngle)); } else { SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadLeft, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadRight, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadUp, false); SetButtonFlags(ButtonFlags.PressFlagsLeft, UxrInputButtons.DPadDown, false); } Vector2 rightJoystick = GetInput2D(UxrHandSide.Right, UxrInput2D.Joystick, true); Vector2 rightDPad = rightJoystick; // Mapped to joystick by default if (rightJoystick != Vector2.zero && rightJoystick.magnitude > AnalogAsDPadThreshold) { float joystickAngle = Input2DToAngle(rightJoystick); bool touchPadFix = !MainJoystickIsTouchpad || buttonPressJoystickRight; SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickLeft, touchPadFix && IsInput2dDPadLeft(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickRight, touchPadFix && IsInput2dDPadRight(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickUp, touchPadFix && IsInput2dDPadUp(joystickAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickDown, touchPadFix && IsInput2dDPadDown(joystickAngle)); } else { SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickLeft, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickRight, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickUp, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.JoystickDown, false); } if (rightDPad != Vector2.zero && rightDPad.magnitude > AnalogAsDPadThreshold) { float dPadAngle = Input2DToAngle(rightDPad); bool touchPadFix = !MainJoystickIsTouchpad || buttonPressJoystickRight; SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadLeft, touchPadFix && IsInput2dDPadLeft(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadRight, touchPadFix && IsInput2dDPadRight(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadUp, touchPadFix && IsInput2dDPadUp(dPadAngle)); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadDown, touchPadFix && IsInput2dDPadDown(dPadAngle)); } else { SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadLeft, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadRight, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadUp, false); SetButtonFlags(ButtonFlags.PressFlagsRight, UxrInputButtons.DPadDown, false); } } #endregion #region Protected Methods /// /// Checks whether a non-standard button in a controller is currently being touched or pressed. /// /// Which controller side to check /// Button to check /// Type of contact to check for (touch or press) /// Boolean telling whether the specified button has contact protected virtual bool HasButtonContactOther(UxrHandSide handSide, UxrInputButtons button, ButtonContact buttonContact) { return false; } /// /// Gets the input device interface in Unity's input system for a given hand. /// Usually if it is a left+right setup it will give a list with a single entry but the system is very generic /// so it is prepared to handle different setups. /// Normally we get the list and just use the first entry if available. /// /// Hand to get the input devices for /// representing the input device protected InputDevice GetInputDevice(UxrHandSide handSide) { return handSide == UxrHandSide.Left ? _deviceLeft : _deviceRight; } /// /// Using an audio file, creates a haptic samples buffer that can be sent for feedback. /// This code is based on the Oculus SDK (OVRHaptics.cs). /// /// Unity input device that will be the feedback target /// Audio clip whose audio sample will be used to create haptics /// Buffer that can be sent to the device as haptic feedback protected byte[] CreateHapticBufferFromAudioClip(InputDevice inputDevice, AudioClip audioClip) { float[] audioData = new float[audioClip.samples * audioClip.channels]; audioClip.GetData(audioData, 0); if (!inputDevice.TryGetHapticCapabilities(out HapticCapabilities hapticCapabilities) || !hapticCapabilities.supportsBuffer) { return null; } if (hapticCapabilities.bufferFrequencyHz == 0) { return null; } double stepSizePrecise = (audioClip.frequency + 1e-6) / hapticCapabilities.bufferFrequencyHz; if (stepSizePrecise < 1.0) { return null; } int stepSize = (int)stepSizePrecise; double stepSizeError = stepSizePrecise - stepSize; int length = audioData.Length; double accumStepSizeError = 0.0f; byte[] samples = new byte[length]; int i = 0; int s = 0; while (i < length) { byte sample = (byte)(Mathf.Clamp01(Mathf.Abs(audioData[i])) * byte.MaxValue); if (s < samples.Length) { samples[s] = sample; s++; } else { break; } i += stepSize * audioClip.channels; accumStepSizeError += stepSizeError; if ((int)accumStepSizeError > 0) { i += (int)accumStepSizeError * audioClip.channels; accumStepSizeError = accumStepSizeError - (int)accumStepSizeError; } } return samples; } #endregion #region Private Methods /// /// Gets if the given input device is a left side VR controller /// /// Device to check /// Whether the given input device is a left side VR controller private static bool IsLeftController(InputDevice inputDevice) { return inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Left); } /// /// Gets if the given input device is a right side VR controller /// /// Device to check /// Whether the given input device is a right side VR controller private static bool IsRightController(InputDevice inputDevice) { return inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Right); } /// /// Checks whether the given button in a controller is currently being touched or pressed. /// /// Which controller side to check /// Button to check /// Type of contact to check for (touch or press) /// Boolean telling whether the specified button has contact private bool HasButtonContact(UxrHandSide handSide, UxrInputButtons button, ButtonContact buttonContact) { InputDevice inputDevice = GetInputDevice(handSide); if (!inputDevice.isValid) { return false; } if (button == UxrInputButtons.Joystick) { var featureUsage = buttonContact == ButtonContact.Press ? CommonUsages.primary2DAxisClick : CommonUsages.primary2DAxisTouch; if (inputDevice.TryGetFeatureValue(featureUsage, out bool value)) { return value; } } else if (button == UxrInputButtons.Joystick2) { return false; } else if (button == UxrInputButtons.Trigger) { if (buttonContact == ButtonContact.Touch) { #if ULTIMATEXR_UNITY_XR_OCULUS if (inputDevice.TryGetFeatureValue(OculusUsages.indexTouch, out bool valueTouch)) { return valueTouch; } #endif } if (inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float valueFloat)) { // We try getting the float value first because in analog buttons like the oculus it will trigger too early with the bool version. return valueFloat > AnalogAsDPadThreshold; } if (inputDevice.TryGetFeatureValue(CommonUsages.triggerButton, out bool value)) { return value; } } else if (button == UxrInputButtons.Trigger2) { return false; } else if (button == UxrInputButtons.Grip) { if (inputDevice.TryGetFeatureValue(CommonUsages.gripButton, out bool value)) { return value; } } else if (button == UxrInputButtons.Button1) { var featureUsage = buttonContact == ButtonContact.Press ? CommonUsages.primaryButton : CommonUsages.primaryTouch; if (inputDevice.TryGetFeatureValue(featureUsage, out bool value)) { return value; } } else if (button == UxrInputButtons.Button2) { var featureUsage = buttonContact == ButtonContact.Press ? CommonUsages.secondaryButton : CommonUsages.secondaryTouch; if (inputDevice.TryGetFeatureValue(featureUsage, out bool value)) { return value; } } else if (button == UxrInputButtons.Menu) { if (inputDevice.TryGetFeatureValue(CommonUsages.menuButton, out bool value)) { return value; } } else if (button == UxrInputButtons.ThumbCapSense) { if (buttonContact == ButtonContact.Press) { #if ULTIMATEXR_UNITY_XR_OCULUS if (inputDevice.TryGetFeatureValue(OculusUsages.thumbrest, out bool value)) { return value; } #endif } if (buttonContact == ButtonContact.Touch) { #if ULTIMATEXR_UNITY_XR_OCULUS if (inputDevice.TryGetFeatureValue(OculusUsages.thumbTouch, out bool value)) { return value; } #endif } } else if (button == UxrInputButtons.IndexCapSense) { } else if (button == UxrInputButtons.MiddleCapSense) { } else if (button == UxrInputButtons.RingCapSense) { } else if (button == UxrInputButtons.LittleCapSense) { } return false; } #endregion #region Private Types & Data private string InputClassName => GetType().Name; private static readonly List s_activeInputDevices = new List(); private InputDevice _deviceLeft; private InputDevice _deviceRight; private uint _leftHapticChannel; private uint _rightHapticChannel; #endregion } }