Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
8.0 KiB

  1. // Controller Haptics|Interactions|30030
  2. namespace VRTK
  3. {
  4. using UnityEngine;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. /// <summary>
  8. /// A collection of static methods for calling haptic functions on a given controller.
  9. /// </summary>
  10. /// <remarks>
  11. /// **Script Usage:**
  12. /// > 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)`.
  13. /// </remarks>
  14. public class VRTK_ControllerHaptics : MonoBehaviour
  15. {
  16. protected static VRTK_ControllerHaptics instance;
  17. protected Dictionary<VRTK_ControllerReference, Coroutine> hapticLoopCoroutines = new Dictionary<VRTK_ControllerReference, Coroutine>();
  18. /// <summary>
  19. /// The TriggerHapticPulse/2 method calls a single haptic pulse call on the controller for a single tick.
  20. /// </summary>
  21. /// <param name="controllerReference">The reference to the controller to activate the haptic feedback on.</param>
  22. /// <param name="strength">The intensity of the rumble of the controller motor. `0` to `1`.</param>
  23. public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength)
  24. {
  25. SetupInstance();
  26. if (instance != null)
  27. {
  28. instance.InternalTriggerHapticPulse(controllerReference, strength);
  29. }
  30. }
  31. /// <summary>
  32. /// 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.
  33. /// </summary>
  34. /// <param name="controllerReference">The reference to the controller to activate the haptic feedback on.</param>
  35. /// <param name="strength">The intensity of the rumble of the controller motor. `0` to `1`.</param>
  36. /// <param name="duration">The length of time the rumble should continue for.</param>
  37. /// <param name="pulseInterval">The interval to wait between each haptic pulse.</param>
  38. public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength, float duration, float pulseInterval)
  39. {
  40. SetupInstance();
  41. if (instance != null)
  42. {
  43. instance.InternalTriggerHapticPulse(controllerReference, strength, duration, pulseInterval);
  44. }
  45. }
  46. /// <summary>
  47. /// The TriggerHapticPulse/2 method calls a haptic pulse based on a given audio clip.
  48. /// </summary>
  49. /// <param name="controllerReference">The reference to the controller to activate the haptic feedback on.</param>
  50. /// <param name="clip">The audio clip to use for the haptic pattern.</param>
  51. public static void TriggerHapticPulse(VRTK_ControllerReference controllerReference, AudioClip clip)
  52. {
  53. SetupInstance();
  54. if (instance != null)
  55. {
  56. instance.InternalTriggerHapticPulse(controllerReference, clip);
  57. }
  58. }
  59. /// <summary>
  60. /// The CancelHapticPulse method cancels the existing running haptic pulse on the given controller index.
  61. /// </summary>
  62. /// <param name="controllerReference">The reference to the controller to cancel the haptic feedback on.</param>
  63. public static void CancelHapticPulse(VRTK_ControllerReference controllerReference)
  64. {
  65. SetupInstance();
  66. if (instance != null)
  67. {
  68. instance.InternalCancelHapticPulse(controllerReference);
  69. }
  70. }
  71. protected virtual void OnDisable()
  72. {
  73. StopAllCoroutines();
  74. hapticLoopCoroutines.Clear();
  75. }
  76. protected static void SetupInstance()
  77. {
  78. if (instance == null && VRTK_SDKManager.ValidInstance())
  79. {
  80. instance = VRTK_SDKManager.instance.gameObject.AddComponent<VRTK_ControllerHaptics>();
  81. }
  82. }
  83. protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength)
  84. {
  85. InternalCancelHapticPulse(controllerReference);
  86. float hapticPulseStrength = Mathf.Clamp(strength, 0f, 1f);
  87. VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticPulseStrength);
  88. }
  89. protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, float strength, float duration, float pulseInterval)
  90. {
  91. InternalCancelHapticPulse(controllerReference);
  92. float hapticPulseStrength = Mathf.Clamp(strength, 0f, 1f);
  93. SDK_ControllerHapticModifiers hapticModifiers = VRTK_SDK_Bridge.GetHapticModifiers();
  94. Coroutine hapticLoop = StartCoroutine(SimpleHapticPulseRoutine(controllerReference, duration * hapticModifiers.durationModifier, hapticPulseStrength, pulseInterval * hapticModifiers.intervalModifier));
  95. VRTK_SharedMethods.AddDictionaryValue(hapticLoopCoroutines, controllerReference, hapticLoop);
  96. }
  97. protected virtual void InternalTriggerHapticPulse(VRTK_ControllerReference controllerReference, AudioClip clip)
  98. {
  99. InternalCancelHapticPulse(controllerReference);
  100. if (!VRTK_SDK_Bridge.HapticPulse(controllerReference, clip))
  101. {
  102. //If the SDK Bridge doesn't support audio clips then defer to a local version
  103. Coroutine hapticLoop = StartCoroutine(AudioClipHapticsRoutine(controllerReference, clip));
  104. VRTK_SharedMethods.AddDictionaryValue(hapticLoopCoroutines, controllerReference, hapticLoop);
  105. }
  106. }
  107. protected virtual void InternalCancelHapticPulse(VRTK_ControllerReference controllerReference)
  108. {
  109. Coroutine currentHapticLoopRoutine = VRTK_SharedMethods.GetDictionaryValue(hapticLoopCoroutines, controllerReference);
  110. if (currentHapticLoopRoutine != null)
  111. {
  112. StopCoroutine(currentHapticLoopRoutine);
  113. hapticLoopCoroutines.Remove(controllerReference);
  114. }
  115. }
  116. protected virtual IEnumerator SimpleHapticPulseRoutine(VRTK_ControllerReference controllerReference, float duration, float hapticPulseStrength, float pulseInterval)
  117. {
  118. if (pulseInterval <= 0)
  119. {
  120. yield break;
  121. }
  122. while (duration > 0)
  123. {
  124. VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticPulseStrength);
  125. yield return new WaitForSeconds(pulseInterval);
  126. duration -= pulseInterval;
  127. }
  128. }
  129. protected virtual IEnumerator AudioClipHapticsRoutine(VRTK_ControllerReference controllerReference, AudioClip clip)
  130. {
  131. SDK_ControllerHapticModifiers hapticModifiers = VRTK_SDK_Bridge.GetHapticModifiers();
  132. float hapticScalar = hapticModifiers.maxHapticVibration;
  133. float[] audioData = new float[hapticModifiers.hapticsBufferSize];
  134. int sampleOffset = -hapticModifiers.hapticsBufferSize;
  135. float startTime = Time.time;
  136. float length = clip.length / 1;
  137. float endTime = startTime + length;
  138. float sampleRate = clip.samples;
  139. while (Time.time <= endTime)
  140. {
  141. float lerpVal = (Time.time - startTime) / length;
  142. int sampleIndex = (int)(sampleRate * lerpVal);
  143. if (sampleIndex >= sampleOffset + hapticModifiers.hapticsBufferSize)
  144. {
  145. clip.GetData(audioData, sampleIndex);
  146. sampleOffset = sampleIndex;
  147. }
  148. float currentSample = Mathf.Abs(audioData[sampleIndex - sampleOffset]);
  149. ushort hapticStrength = (ushort)(hapticScalar * currentSample);
  150. VRTK_SDK_Bridge.HapticPulse(controllerReference, hapticStrength);
  151. yield return null;
  152. }
  153. }
  154. }
  155. }