VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/Inputs/Haptics/HapticsUtility.cs
2025-07-29 13:45:50 +03:00

179 lines
7.9 KiB
C#

// ENABLE_VR is not defined on Game Core but the assembly is available with limited features when the XR module is enabled.
// These are the guards that Input System uses in GenericXRDevice.cs to define the XRController and XRHMD classes.
#if ENABLE_VR || UNITY_GAMECORE
#define XR_INPUT_DEVICES_AVAILABLE
#endif
using UnityEngine.InputSystem;
namespace UnityEngine.XR.Interaction.Toolkit.Inputs.Haptics
{
/// <summary>
/// Haptics utilities.
/// Provides methods for a convenient way to send a haptic impulse to the left, right, or both controllers
/// using a <see langword="static"/> method call.
/// </summary>
/// <remarks>
/// If you need more control over how the haptic impulse is sent, you should use the <see cref="HapticImpulsePlayer"/>
/// with an object reference to a custom <see cref="IXRHapticImpulseProvider"/>.
/// </remarks>
public static class HapticsUtility
{
/// <summary>
/// Identify which controllers to send a haptic impulse to.
/// </summary>
public enum Controller
{
/// <summary>
/// Left controller.
/// </summary>
Left,
/// <summary>
/// Right controller.
/// </summary>
Right,
/// <summary>
/// Both left and right controllers.
/// </summary>
Both,
}
static HapticImpulseCommandChannelGroup s_LeftChannelGroup;
static HapticImpulseCommandChannelGroup s_RightChannelGroup;
static XRInputDeviceHapticImpulseChannelGroup s_LegacyLeftChannelGroup;
static XRInputDeviceHapticImpulseChannelGroup s_LegacyRightChannelGroup;
static InputDevice s_LegacyLeftDevice;
static InputDevice s_LegacyRightDevice;
static HapticControlActionManager s_HapticControlManager;
static InputAction s_LeftHapticAction;
static InputAction s_RightHapticAction;
/// <summary>
/// Sends a haptic impulse on the controller device if it supports sending a haptic impulse.
/// </summary>
/// <param name="amplitude">The desired motor amplitude that should be within a [0-1] range.</param>
/// <param name="duration">The desired duration of the impulse in seconds.</param>
/// <param name="controller">The controller to send the haptic impulse to.</param>
/// <param name="frequency">The desired frequency of the impulse in Hz. A value of 0 means to use the default frequency of the device.</param>
/// <param name="channel">The haptic channel of the device to send to, by index.</param>
/// <returns>Returns <see langword="true"/> if successful. Otherwise, returns <see langword="false"/>.</returns>
public static bool SendHapticImpulse(float amplitude, float duration, Controller controller, float frequency = 0f, int channel = 0)
{
var leftSuccess = false;
var rightSuccess = false;
if (controller == Controller.Left || controller == Controller.Both)
{
#if XR_INPUT_DEVICES_AVAILABLE
var device = InputSystem.XR.XRController.leftHand;
#else
InputSystem.InputDevice device = null;
#endif
if (device != null)
{
// Currently, the only code path that we can send a specific frequency is through OpenXR.
// However the only fixed HapticControl path common to all profiles is the "{Haptic}" usage, which is the default 0 channel.
// Quest Pro also has {HapticTrigger} (channel 1) and {HapticThumb} (channel 2), but these are not common to all profiles,
// so we can't make assumptions about the binding path to use for other channels.
if (frequency > 0f && channel == 0)
leftSuccess = SendHapticImpulseOpenXR(GetLeftHapticAction(), amplitude, duration, frequency);
if (!leftSuccess)
leftSuccess = SendHapticImpulse(ref s_LeftChannelGroup, device, channel, amplitude, duration, frequency);
}
else
{
leftSuccess = SendHapticImpulseLegacy(ref s_LegacyLeftChannelGroup, ref s_LegacyLeftDevice,
XRInputTrackingAggregator.Characteristics.leftController, channel, amplitude, duration, frequency);
}
}
if (controller == Controller.Right || controller == Controller.Both)
{
#if XR_INPUT_DEVICES_AVAILABLE
var device = InputSystem.XR.XRController.rightHand;
#else
InputSystem.InputDevice device = null;
#endif
if (device != null)
{
if (frequency > 0f && channel == 0)
rightSuccess = SendHapticImpulseOpenXR(GetRightHapticAction(), amplitude, duration, frequency);
if (!rightSuccess)
rightSuccess = SendHapticImpulse(ref s_RightChannelGroup, device, channel, amplitude, duration, frequency);
}
else
{
rightSuccess = SendHapticImpulseLegacy(ref s_LegacyRightChannelGroup, ref s_LegacyRightDevice,
XRInputTrackingAggregator.Characteristics.rightController, channel, amplitude, duration, frequency);
}
}
if (controller == Controller.Both)
return leftSuccess && rightSuccess;
if (controller == Controller.Left)
return leftSuccess;
if (controller == Controller.Right)
return rightSuccess;
return false;
}
static bool SendHapticImpulseOpenXR(InputAction hapticAction, float amplitude, float duration, float frequency)
{
s_HapticControlManager ??= new HapticControlActionManager();
return s_HapticControlManager.GetChannelGroup(hapticAction)?.GetChannel().SendHapticImpulse(amplitude, duration, frequency) ?? false;
}
static bool SendHapticImpulse(ref HapticImpulseCommandChannelGroup channelGroup, InputSystem.InputDevice device,
int channel, float amplitude, float duration, float frequency)
{
channelGroup ??= new HapticImpulseCommandChannelGroup();
channelGroup.Initialize(device);
return channelGroup.GetChannel(channel)?.SendHapticImpulse(amplitude, duration, frequency) ?? false;
}
static bool SendHapticImpulseLegacy(ref XRInputDeviceHapticImpulseChannelGroup channelGroup, ref InputDevice device,
InputDeviceCharacteristics characteristics, int channel, float amplitude, float duration, float frequency)
{
channelGroup ??= new XRInputDeviceHapticImpulseChannelGroup();
if (device.isValid || XRInputTrackingAggregator.TryGetDeviceWithExactCharacteristics(characteristics, out device))
{
channelGroup.Initialize(device);
return channelGroup.GetChannel(channel)?.SendHapticImpulse(amplitude, duration, frequency) ?? false;
}
return false;
}
static InputAction GetLeftHapticAction()
{
if (s_LeftHapticAction == null)
{
s_LeftHapticAction = new InputAction("Left Haptic", type: InputActionType.PassThrough, "<XRController>{LeftHand}/{Haptic}");
s_LeftHapticAction.Enable();
}
return s_LeftHapticAction;
}
static InputAction GetRightHapticAction()
{
if (s_RightHapticAction == null)
{
s_RightHapticAction = new InputAction("Right Haptic", type: InputActionType.PassThrough, "<XRController>{RightHand}/{Haptic}");
s_RightHapticAction.Enable();
}
return s_RightHapticAction;
}
}
}