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.

388 lines
12 KiB

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