OVRHaptics.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. using UnityEngine;
  2. using System;
  3. using System.IO;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Runtime.InteropServices;
  7. /// <summary>
  8. /// Plays tactile effects on a tracked VR controller.
  9. /// </summary>
  10. public static class OVRHaptics
  11. {
  12. public readonly static OVRHapticsChannel[] Channels;
  13. public readonly static OVRHapticsChannel LeftChannel;
  14. public readonly static OVRHapticsChannel RightChannel;
  15. private readonly static OVRHapticsOutput[] m_outputs;
  16. static OVRHaptics()
  17. {
  18. Config.Load();
  19. m_outputs = new OVRHapticsOutput[]
  20. {
  21. new OVRHapticsOutput((uint)OVRPlugin.Controller.LTouch),
  22. new OVRHapticsOutput((uint)OVRPlugin.Controller.RTouch),
  23. };
  24. Channels = new OVRHapticsChannel[]
  25. {
  26. LeftChannel = new OVRHapticsChannel(0),
  27. RightChannel = new OVRHapticsChannel(1),
  28. };
  29. }
  30. /// <summary>
  31. /// Determines the target format for haptics data on a specific device.
  32. /// </summary>
  33. public static class Config
  34. {
  35. public static int SampleRateHz { get; private set; }
  36. public static int SampleSizeInBytes { get; private set; }
  37. public static int MinimumSafeSamplesQueued { get; private set; }
  38. public static int MinimumBufferSamplesCount { get; private set; }
  39. public static int OptimalBufferSamplesCount { get; private set; }
  40. public static int MaximumBufferSamplesCount { get; private set; }
  41. static Config()
  42. {
  43. Load();
  44. }
  45. public static void Load()
  46. {
  47. OVRPlugin.HapticsDesc desc = OVRPlugin.GetControllerHapticsDesc((uint)OVRPlugin.Controller.RTouch);
  48. SampleRateHz = desc.SampleRateHz;
  49. SampleSizeInBytes = desc.SampleSizeInBytes;
  50. MinimumSafeSamplesQueued = desc.MinimumSafeSamplesQueued;
  51. MinimumBufferSamplesCount = desc.MinimumBufferSamplesCount;
  52. OptimalBufferSamplesCount = desc.OptimalBufferSamplesCount;
  53. MaximumBufferSamplesCount = desc.MaximumBufferSamplesCount;
  54. }
  55. }
  56. /// <summary>
  57. /// A track of haptics data that can be mixed or sequenced with another track.
  58. /// </summary>
  59. public class OVRHapticsChannel
  60. {
  61. private OVRHapticsOutput m_output;
  62. /// <summary>
  63. /// Constructs a channel targeting the specified output.
  64. /// </summary>
  65. public OVRHapticsChannel(uint outputIndex)
  66. {
  67. m_output = m_outputs[outputIndex];
  68. }
  69. /// <summary>
  70. /// Cancels any currently-playing clips and immediatly plays the specified clip instead.
  71. /// </summary>
  72. public void Preempt(OVRHapticsClip clip)
  73. {
  74. m_output.Preempt(clip);
  75. }
  76. /// <summary>
  77. /// Enqueues the specified clip to play after any currently-playing clips finish.
  78. /// </summary>
  79. public void Queue(OVRHapticsClip clip)
  80. {
  81. m_output.Queue(clip);
  82. }
  83. /// <summary>
  84. /// Adds the specified clip to play simultaneously to the currently-playing clip(s).
  85. /// </summary>
  86. public void Mix(OVRHapticsClip clip)
  87. {
  88. m_output.Mix(clip);
  89. }
  90. /// <summary>
  91. /// Cancels any currently-playing clips.
  92. /// </summary>
  93. public void Clear()
  94. {
  95. m_output.Clear();
  96. }
  97. }
  98. private class OVRHapticsOutput
  99. {
  100. private class ClipPlaybackTracker
  101. {
  102. public int ReadCount { get; set; }
  103. public OVRHapticsClip Clip { get; set; }
  104. public ClipPlaybackTracker(OVRHapticsClip clip)
  105. {
  106. Clip = clip;
  107. }
  108. }
  109. private bool m_lowLatencyMode = true;
  110. private int m_prevSamplesQueued = 0;
  111. private float m_prevSamplesQueuedTime = 0;
  112. private int m_numPredictionHits = 0;
  113. private int m_numPredictionMisses = 0;
  114. private int m_numUnderruns = 0;
  115. private List<ClipPlaybackTracker> m_pendingClips = new List<ClipPlaybackTracker>();
  116. private uint m_controller = 0;
  117. private OVRNativeBuffer m_nativeBuffer = new OVRNativeBuffer(OVRHaptics.Config.MaximumBufferSamplesCount * OVRHaptics.Config.SampleSizeInBytes);
  118. private OVRHapticsClip m_paddingClip = new OVRHapticsClip();
  119. public OVRHapticsOutput(uint controller)
  120. {
  121. m_controller = controller;
  122. }
  123. /// <summary>
  124. /// The system calls this each frame to update haptics playback.
  125. /// </summary>
  126. public void Process()
  127. {
  128. var hapticsState = OVRPlugin.GetControllerHapticsState(m_controller);
  129. float elapsedTime = Time.realtimeSinceStartup - m_prevSamplesQueuedTime;
  130. if (m_prevSamplesQueued > 0)
  131. {
  132. int expectedSamples = m_prevSamplesQueued - (int)(elapsedTime * OVRHaptics.Config.SampleRateHz + 0.5f);
  133. if (expectedSamples < 0)
  134. expectedSamples = 0;
  135. if ((hapticsState.SamplesQueued - expectedSamples) == 0)
  136. m_numPredictionHits++;
  137. else
  138. m_numPredictionMisses++;
  139. //Debug.Log(hapticsState.SamplesAvailable + "a " + hapticsState.SamplesQueued + "q " + expectedSamples + "e "
  140. //+ "Prediction Accuracy: " + m_numPredictionHits / (float)(m_numPredictionMisses + m_numPredictionHits));
  141. if ((expectedSamples > 0) && (hapticsState.SamplesQueued == 0))
  142. {
  143. m_numUnderruns++;
  144. //Debug.LogError("Samples Underrun (" + m_controller + " #" + m_numUnderruns + ") -"
  145. // + " Expected: " + expectedSamples
  146. // + " Actual: " + hapticsState.SamplesQueued);
  147. }
  148. m_prevSamplesQueued = hapticsState.SamplesQueued;
  149. m_prevSamplesQueuedTime = Time.realtimeSinceStartup;
  150. }
  151. int desiredSamplesCount = OVRHaptics.Config.OptimalBufferSamplesCount;
  152. if (m_lowLatencyMode)
  153. {
  154. float sampleRateMs = 1000.0f / (float)OVRHaptics.Config.SampleRateHz;
  155. float elapsedMs = elapsedTime * 1000.0f;
  156. int samplesNeededPerFrame = (int)Mathf.Ceil(elapsedMs / sampleRateMs);
  157. int lowLatencySamplesCount = OVRHaptics.Config.MinimumSafeSamplesQueued + samplesNeededPerFrame;
  158. if (lowLatencySamplesCount < desiredSamplesCount)
  159. desiredSamplesCount = lowLatencySamplesCount;
  160. }
  161. if (hapticsState.SamplesQueued > desiredSamplesCount)
  162. return;
  163. if (desiredSamplesCount > OVRHaptics.Config.MaximumBufferSamplesCount)
  164. desiredSamplesCount = OVRHaptics.Config.MaximumBufferSamplesCount;
  165. if (desiredSamplesCount > hapticsState.SamplesAvailable)
  166. desiredSamplesCount = hapticsState.SamplesAvailable;
  167. int acquiredSamplesCount = 0;
  168. int clipIndex = 0;
  169. while(acquiredSamplesCount < desiredSamplesCount && clipIndex < m_pendingClips.Count)
  170. {
  171. int numSamplesToCopy = desiredSamplesCount - acquiredSamplesCount;
  172. int remainingSamplesInClip = m_pendingClips[clipIndex].Clip.Count - m_pendingClips[clipIndex].ReadCount;
  173. if (numSamplesToCopy > remainingSamplesInClip)
  174. numSamplesToCopy = remainingSamplesInClip;
  175. if (numSamplesToCopy > 0)
  176. {
  177. int numBytes = numSamplesToCopy * OVRHaptics.Config.SampleSizeInBytes;
  178. int dstOffset = acquiredSamplesCount * OVRHaptics.Config.SampleSizeInBytes;
  179. int srcOffset = m_pendingClips[clipIndex].ReadCount * OVRHaptics.Config.SampleSizeInBytes;
  180. Marshal.Copy(m_pendingClips[clipIndex].Clip.Samples, srcOffset, m_nativeBuffer.GetPointer(dstOffset), numBytes);
  181. m_pendingClips[clipIndex].ReadCount += numSamplesToCopy;
  182. acquiredSamplesCount += numSamplesToCopy;
  183. }
  184. clipIndex++;
  185. }
  186. for (int i = m_pendingClips.Count - 1; i >= 0 && m_pendingClips.Count > 0; i--)
  187. {
  188. if (m_pendingClips[i].ReadCount >= m_pendingClips[i].Clip.Count)
  189. m_pendingClips.RemoveAt(i);
  190. }
  191. int desiredPadding = desiredSamplesCount - (hapticsState.SamplesQueued + acquiredSamplesCount);
  192. if (desiredPadding < (OVRHaptics.Config.MinimumBufferSamplesCount - acquiredSamplesCount))
  193. desiredPadding = (OVRHaptics.Config.MinimumBufferSamplesCount - acquiredSamplesCount);
  194. if (desiredPadding > hapticsState.SamplesAvailable)
  195. desiredPadding = hapticsState.SamplesAvailable;
  196. if (desiredPadding > 0)
  197. {
  198. int numBytes = desiredPadding * OVRHaptics.Config.SampleSizeInBytes;
  199. int dstOffset = acquiredSamplesCount * OVRHaptics.Config.SampleSizeInBytes;
  200. int srcOffset = 0;
  201. Marshal.Copy(m_paddingClip.Samples, srcOffset, m_nativeBuffer.GetPointer(dstOffset), numBytes);
  202. acquiredSamplesCount += desiredPadding;
  203. }
  204. if (acquiredSamplesCount > 0)
  205. {
  206. OVRPlugin.HapticsBuffer hapticsBuffer;
  207. hapticsBuffer.Samples = m_nativeBuffer.GetPointer();
  208. hapticsBuffer.SamplesCount = acquiredSamplesCount;
  209. OVRPlugin.SetControllerHaptics(m_controller, hapticsBuffer);
  210. hapticsState = OVRPlugin.GetControllerHapticsState(m_controller);
  211. m_prevSamplesQueued = hapticsState.SamplesQueued;
  212. m_prevSamplesQueuedTime = Time.realtimeSinceStartup;
  213. }
  214. }
  215. /// <summary>
  216. /// Immediately plays the specified clip without waiting for any currently-playing clip to finish.
  217. /// </summary>
  218. public void Preempt(OVRHapticsClip clip)
  219. {
  220. m_pendingClips.Clear();
  221. m_pendingClips.Add(new ClipPlaybackTracker(clip));
  222. }
  223. /// <summary>
  224. /// Enqueues the specified clip to play after any currently-playing clip finishes.
  225. /// </summary>
  226. public void Queue(OVRHapticsClip clip)
  227. {
  228. m_pendingClips.Add(new ClipPlaybackTracker(clip));
  229. }
  230. /// <summary>
  231. /// Adds the samples from the specified clip to the ones in the currently-playing clip(s).
  232. /// </summary>
  233. public void Mix(OVRHapticsClip clip)
  234. {
  235. int numClipsToMix = 0;
  236. int numSamplesToMix = 0;
  237. int numSamplesRemaining = clip.Count;
  238. while (numSamplesRemaining > 0 && numClipsToMix < m_pendingClips.Count)
  239. {
  240. int numSamplesRemainingInClip = m_pendingClips[numClipsToMix].Clip.Count - m_pendingClips[numClipsToMix].ReadCount;
  241. numSamplesRemaining -= numSamplesRemainingInClip;
  242. numSamplesToMix += numSamplesRemainingInClip;
  243. numClipsToMix++;
  244. }
  245. if (numSamplesRemaining > 0)
  246. {
  247. numSamplesToMix += numSamplesRemaining;
  248. numSamplesRemaining = 0;
  249. }
  250. if (numClipsToMix > 0)
  251. {
  252. OVRHapticsClip mixClip = new OVRHapticsClip(numSamplesToMix);
  253. OVRHapticsClip a = clip;
  254. int aReadCount = 0;
  255. for (int i = 0; i < numClipsToMix; i++)
  256. {
  257. OVRHapticsClip b = m_pendingClips[i].Clip;
  258. for(int bReadCount = m_pendingClips[i].ReadCount; bReadCount < b.Count; bReadCount++)
  259. {
  260. if (OVRHaptics.Config.SampleSizeInBytes == 1)
  261. {
  262. byte sample = 0; // TODO support multi-byte samples
  263. if ((aReadCount < a.Count) && (bReadCount < b.Count))
  264. {
  265. sample = (byte)(Mathf.Clamp(a.Samples[aReadCount] + b.Samples[bReadCount], 0, System.Byte.MaxValue)); // TODO support multi-byte samples
  266. aReadCount++;
  267. }
  268. else if (bReadCount < b.Count)
  269. {
  270. sample = b.Samples[bReadCount]; // TODO support multi-byte samples
  271. }
  272. mixClip.WriteSample(sample); // TODO support multi-byte samples
  273. }
  274. }
  275. }
  276. while (aReadCount < a.Count)
  277. {
  278. if (OVRHaptics.Config.SampleSizeInBytes == 1)
  279. {
  280. mixClip.WriteSample(a.Samples[aReadCount]); // TODO support multi-byte samples
  281. }
  282. aReadCount++;
  283. }
  284. m_pendingClips[0] = new ClipPlaybackTracker(mixClip);
  285. for (int i = 1; i < numClipsToMix; i++)
  286. {
  287. m_pendingClips.RemoveAt(1);
  288. }
  289. }
  290. else
  291. {
  292. m_pendingClips.Add(new ClipPlaybackTracker(clip));
  293. }
  294. }
  295. public void Clear()
  296. {
  297. m_pendingClips.Clear();
  298. }
  299. }
  300. /// <summary>
  301. /// The system calls this each frame to update haptics playback.
  302. /// </summary>
  303. public static void Process()
  304. {
  305. Config.Load();
  306. for (int i = 0; i < m_outputs.Length; i++)
  307. {
  308. m_outputs[i].Process();
  309. }
  310. }
  311. }