// Controller Haptics|Interactions|30030
namespace VRTK
{
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
///
/// A collection of static methods for calling haptic functions on a given controller.
///
///
/// **Script Usage:**
/// > There is no requirement to add this script to a GameObject as all of the public methods are static and can be called directly e.g. `VRTK_ControllerHaptics.TriggerHapticPulse(ref, 1f)`.
///
public class VRTK_ControllerHaptics : MonoBehaviour
{
protected static VRTK_ControllerHaptics instance;
protected Dictionary hapticLoopCoroutines = new Dictionary();
///
/// The TriggerHapticPulse/2 method calls a single haptic pulse call on the controller for a single tick.
///
/// The reference to the controller to activate the haptic feedback on.
/// The intensity of the rumble of the controller motor. `0` to `1`.
public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength)
{
SetupInstance();
if (instance != null)
{
instance.InternalTriggerHapticPulse(controllerReference, strength);
}
}
///
/// The TriggerHapticPulse/4 method calls a haptic pulse for a specified amount of time rather than just a single tick. Each pulse can be separated by providing a `pulseInterval` to pause between each haptic pulse.
///
/// The reference to the controller to activate the haptic feedback on.
/// The intensity of the rumble of the controller motor. `0` to `1`.
/// The length of time the rumble should continue for.
/// The interval to wait between each haptic pulse.
public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength, float duration, float pulseInterval)
{
SetupInstance();
if (instance != null)
{
instance.InternalTriggerHapticPulse(controllerReference, strength, duration, pulseInterval);
}
}
///
/// The TriggerHapticPulse/2 method calls a haptic pulse based on a given audio clip.
///
/// The reference to the controller to activate the haptic feedback on.
/// The audio clip to use for the haptic pattern.
public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, AudioClip clip)
{
SetupInstance();
if (instance != null)
{
instance.InternalTriggerHapticPulse(controllerReference, clip);
}
}
///
/// The CancelHapticPulse method cancels the existing running haptic pulse on the given controller index.
///
/// The reference to the controller to cancel the haptic feedback on.
public static void CancelHapticPulse(VRTK_ControllerReference controllerReference)
{
SetupInstance();
if (instance != null)
{
instance.InternalCancelHapticPulse(controllerReference);
}
}
protected virtual void OnDisable()
{
StopAllCoroutines();
hapticLoopCoroutines.Clear();
}
protected static void SetupInstance()
{
if (instance == null && VRTK_SDKManager.ValidInstance())
{
instance = VRTK_SDKManager.instance.gameObject.AddComponent();
}
}
protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength)
{
InternalCancelHapticPulse(controllerReference);
float hapticPulseStrength = Mathf.Clamp(strength, 0f, 1f);
VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticPulseStrength);
}
protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength, float duration, float pulseInterval)
{
InternalCancelHapticPulse(controllerReference);
float hapticPulseStrength = Mathf.Clamp(strength, 0f, 1f);
SDK_ControllerHapticModifiers hapticModifiers = VRTK_SDK_Bridge.GetHapticModifiers();
Coroutine hapticLoop = StartCoroutine(SimpleHapticPulseRoutine(controllerReference, duration * hapticModifiers.durationModifier, hapticPulseStrength, pulseInterval * hapticModifiers.intervalModifier));
VRTK_SharedMethods.AddDictionaryValue(hapticLoopCoroutines, controllerReference, hapticLoop);
}
protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, AudioClip clip)
{
InternalCancelHapticPulse(controllerReference);
if (!VRTK_SDK_Bridge.HapticPulse(controllerReference, clip))
{
//If the SDK Bridge doesn't support audio clips then defer to a local version
Coroutine hapticLoop = StartCoroutine(AudioClipHapticsRoutine(controllerReference, clip));
VRTK_SharedMethods.AddDictionaryValue(hapticLoopCoroutines, controllerReference, hapticLoop);
}
}
protected virtual void InternalCancelHapticPulse(VRTK_ControllerReference controllerReference)
{
Coroutine currentHapticLoopRoutine = VRTK_SharedMethods.GetDictionaryValue(hapticLoopCoroutines, controllerReference);
if (currentHapticLoopRoutine != null)
{
StopCoroutine(currentHapticLoopRoutine);
hapticLoopCoroutines.Remove(controllerReference);
}
}
protected virtual IEnumerator SimpleHapticPulseRoutine(VRTK_ControllerReference controllerReference, float duration, float hapticPulseStrength, float pulseInterval)
{
if (pulseInterval <= 0)
{
yield break;
}
while (duration > 0)
{
VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticPulseStrength);
yield return new WaitForSeconds(pulseInterval);
duration -= pulseInterval;
}
}
protected virtual IEnumerator AudioClipHapticsRoutine(VRTK_ControllerReference controllerReference, AudioClip clip)
{
SDK_ControllerHapticModifiers hapticModifiers = VRTK_SDK_Bridge.GetHapticModifiers();
float hapticScalar = hapticModifiers.maxHapticVibration;
float[] audioData = new float[hapticModifiers.hapticsBufferSize];
int sampleOffset = -hapticModifiers.hapticsBufferSize;
float startTime = Time.time;
float length = clip.length / 1;
float endTime = startTime + length;
float sampleRate = clip.samples;
while (Time.time <= endTime)
{
float lerpVal = (Time.time - startTime) / length;
int sampleIndex = (int)(sampleRate * lerpVal);
if (sampleIndex >= sampleOffset + hapticModifiers.hapticsBufferSize)
{
clip.GetData(audioData, sampleIndex);
sampleOffset = sampleIndex;
}
float currentSample = Mathf.Abs(audioData[sampleIndex - sampleOffset]);
ushort hapticStrength = (ushort)(hapticScalar * currentSample);
VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticStrength);
yield return null;
}
}
}
}