NRHandsManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /****************************************************************************
  2. * Copyright 2019 Nreal Techonology Limited. All rights reserved.
  3. *
  4. * This file is part of NRSDK.
  5. *
  6. * https://www.nreal.ai/
  7. *
  8. *****************************************************************************/
  9. namespace NRKernal
  10. {
  11. using System;
  12. using System.Collections.Generic;
  13. using UnityEngine;
  14. /// <summary> A manager of hand states. </summary>
  15. public class NRHandsManager
  16. {
  17. private readonly Dictionary<HandEnum, NRHand> m_HandsDict;
  18. private readonly HandState[] m_HandStates; // index 0 represents right and index 1 represents left
  19. private readonly OneEuroFilter[] m_OneEuroFilters;
  20. private IHandStatesService m_HandStatesService;
  21. private bool m_Inited;
  22. public Action OnHandTrackingStarted;
  23. public Action OnHandStatesUpdated;
  24. public Action OnHandTrackingStopped;
  25. /// <summary>
  26. /// Returns true if the hand tracking is now running normally
  27. /// </summary>
  28. public bool IsRunning
  29. {
  30. get
  31. {
  32. if (m_HandStatesService != null)
  33. {
  34. return m_HandStatesService.IsRunning;
  35. }
  36. return false;
  37. }
  38. }
  39. public NRHandsManager()
  40. {
  41. m_HandsDict = new Dictionary<HandEnum, NRHand>();
  42. m_HandStates = new HandState[2] { new HandState(HandEnum.RightHand), new HandState(HandEnum.LeftHand) };
  43. m_OneEuroFilters = new OneEuroFilter[2] { new OneEuroFilter(), new OneEuroFilter() };
  44. }
  45. /// <summary>
  46. /// Regist the left or right NRHand. There would be at most one NRHand for each hand enum
  47. /// </summary>
  48. /// <param name="hand"></param>
  49. internal void RegistHand(NRHand hand)
  50. {
  51. if (hand == null || hand.HandEnum == HandEnum.None)
  52. return;
  53. var handEnum = hand.HandEnum;
  54. if (m_HandsDict.ContainsKey(handEnum))
  55. {
  56. m_HandsDict[handEnum] = hand;
  57. }
  58. else
  59. {
  60. m_HandsDict.Add(handEnum, hand);
  61. }
  62. }
  63. /// <summary>
  64. /// UnRegist the left or right NRHand
  65. /// </summary>
  66. /// <param name="hand"></param>
  67. internal void UnRegistHand(NRHand hand)
  68. {
  69. if (hand == null)
  70. return;
  71. m_HandsDict.Remove(hand.HandEnum);
  72. }
  73. /// <summary>
  74. /// Init hand tracking with a certain service
  75. /// </summary>
  76. /// <param name="handStatesService"></param>
  77. internal void Init(IHandStatesService handStatesService = null)
  78. {
  79. if (m_Inited)
  80. return;
  81. m_HandStatesService = handStatesService;
  82. if (m_HandStatesService == null)
  83. {
  84. #if UNITY_EDITOR
  85. m_HandStatesService = new NREmulatorHandStatesService();
  86. #else
  87. m_HandStatesService = new NRHandStatesService();
  88. #endif
  89. }
  90. NRInput.OnControllerStatesUpdated += UpdateHandTracking;
  91. m_Inited = true;
  92. NRDebugger.Info("[HandsManager] Hand Tracking Inited");
  93. }
  94. /// <summary>
  95. /// Returns true if start hand tracking success
  96. /// </summary>
  97. /// <returns></returns>
  98. internal bool StartHandTracking()
  99. {
  100. if (!m_Inited)
  101. {
  102. Init();
  103. }
  104. if (IsRunning)
  105. {
  106. NRDebugger.Info("[HandsManager] Hand Tracking Start: Success");
  107. return true;
  108. }
  109. if (m_HandStatesService != null && m_HandStatesService.RunService())
  110. {
  111. NRDebugger.Info("[HandsManager] Hand Tracking Start: Success");
  112. NRInput.SwitchControllerProvider(typeof(NRHandControllerProvider));
  113. OnHandTrackingStarted?.Invoke();
  114. return true;
  115. }
  116. NRDebugger.Info("[HandsManager] Hand Tracking Start: Failed");
  117. return false;
  118. }
  119. /// <summary>
  120. /// Returns true if stop hand tracking success
  121. /// </summary>
  122. /// <returns></returns>
  123. internal bool StopHandTracking()
  124. {
  125. if (!m_Inited)
  126. {
  127. NRDebugger.Info("[HandsManager] Hand Tracking Stop: Success");
  128. return true;
  129. }
  130. if (!IsRunning)
  131. {
  132. NRDebugger.Info("[HandsManager] Hand Tracking Stop: Success");
  133. return true;
  134. }
  135. if (m_HandStatesService != null && m_HandStatesService.StopService())
  136. {
  137. NRDebugger.Info("[HandsManager] Hand Tracking Stop: Success");
  138. NRInput.SwitchControllerProvider(ControllerProviderFactory.controllerProviderType);
  139. ResetHandStates();
  140. OnHandTrackingStopped?.Invoke();
  141. return true;
  142. }
  143. NRDebugger.Info("[HandsManager] Hand Tracking Stop: Failed");
  144. return false;
  145. }
  146. /// <summary>
  147. /// Get the current hand state of the left or right hand
  148. /// </summary>
  149. /// <param name="handEnum"></param>
  150. /// <returns></returns>
  151. public HandState GetHandState(HandEnum handEnum)
  152. {
  153. switch (handEnum)
  154. {
  155. case HandEnum.RightHand:
  156. return m_HandStates[0];
  157. case HandEnum.LeftHand:
  158. return m_HandStates[1];
  159. default:
  160. break;
  161. }
  162. return null;
  163. }
  164. /// <summary>
  165. /// Get the left or right NRHand if it has been registed.
  166. /// </summary>
  167. /// <param name="handEnum"></param>
  168. /// <returns></returns>
  169. public NRHand GetHand(HandEnum handEnum)
  170. {
  171. NRHand hand;
  172. if (m_HandsDict != null && m_HandsDict.TryGetValue(handEnum, out hand))
  173. {
  174. return hand;
  175. }
  176. return null;
  177. }
  178. /// <summary>
  179. /// Returns true if user is now performing the systemGesture
  180. /// </summary>
  181. /// <returns></returns>
  182. public bool IsPerformingSystemGesture()
  183. {
  184. return IsPerformingSystemGesture(HandEnum.LeftHand) || IsPerformingSystemGesture(HandEnum.RightHand);
  185. }
  186. /// <summary>
  187. /// Returns true if user is now performing the systemGesture
  188. /// </summary>
  189. /// <param name="handEnum"></param>
  190. /// <returns></returns>
  191. public bool IsPerformingSystemGesture(HandEnum handEnum)
  192. {
  193. return IsPerformingSystemGesture(GetHandState(handEnum));
  194. }
  195. private void ResetHandStates()
  196. {
  197. for (int i = 0; i < m_HandStates.Length; i++)
  198. {
  199. m_HandStates[i].Reset();
  200. }
  201. }
  202. private void UpdateHandTracking()
  203. {
  204. if (!IsRunning)
  205. return;
  206. m_HandStatesService.UpdateStates(m_HandStates);
  207. UpdateHandPointer();
  208. OnHandStatesUpdated?.Invoke();
  209. }
  210. private void UpdateHandPointer()
  211. {
  212. for (int i = 0; i < m_HandStates.Length; i++)
  213. {
  214. var handState = m_HandStates[i];
  215. if (handState == null)
  216. continue;
  217. CalculatePointerPose(handState);
  218. }
  219. }
  220. private void CalculatePointerPose(HandState handState)
  221. {
  222. if (handState.isTracked)
  223. {
  224. var wristPose = handState.GetJointPose(HandJointID.Wrist);
  225. var cameraTransform = NRInput.CameraCenter;
  226. handState.pointerPoseValid = Vector3.Angle(cameraTransform.forward, wristPose.forward) < 70f;
  227. if (handState.pointerPoseValid)
  228. {
  229. Vector3 middleToRing = (handState.GetJointPose(HandJointID.MiddleProximal).position
  230. - handState.GetJointPose(HandJointID.RingProximal).position).normalized;
  231. Vector3 middleToWrist = (handState.GetJointPose(HandJointID.MiddleProximal).position
  232. - handState.GetJointPose(HandJointID.Wrist).position).normalized;
  233. Vector3 middleToCenter = Vector3.Cross(middleToWrist, middleToRing).normalized;
  234. var pointerPosition = handState.GetJointPose(HandJointID.MiddleProximal).position
  235. + middleToWrist * 0.02f
  236. + middleToRing * 0.01f
  237. + middleToCenter * (handState.handEnum == HandEnum.RightHand ? 0.06f : -0.06f);
  238. var handDirection = pointerPosition - wristPose.position;
  239. var handRotation = Quaternion.LookRotation(handDirection);
  240. var handtoCamera = wristPose.position - (cameraTransform.position - 0.08f * cameraTransform.forward);
  241. float handtoCameraY = handtoCamera.y;
  242. handtoCamera.y = 0;
  243. var handtoCameraRot = Quaternion.LookRotation(handtoCamera);
  244. var pointerRotation = Quaternion.Lerp(handtoCameraRot, handRotation, 0.3f)
  245. * Quaternion.Euler(new Vector3(handtoCameraY * -150f - 30f, handState.handEnum == HandEnum.RightHand ? -15f : 15f, 0f));
  246. Vector3 pointerRotationToV3 = pointerRotation * Vector3.forward;
  247. pointerRotation = Quaternion.LookRotation(m_OneEuroFilters[(int)handState.handEnum].Step(Time.realtimeSinceStartup, pointerRotationToV3));
  248. handState.pointerPose = new Pose(pointerPosition, pointerRotation);
  249. }
  250. }
  251. else
  252. {
  253. handState.pointerPoseValid = false;
  254. }
  255. }
  256. private bool IsPerformingSystemGesture(HandState handState)
  257. {
  258. if (!IsRunning)
  259. {
  260. return false;
  261. }
  262. return handState.currentGesture == HandGesture.System;
  263. }
  264. public class OneEuroFilter
  265. {
  266. public float Beta = 0.01f;
  267. public float MinCutoff = 2.0f;
  268. const float DCutOff = 1.0f;
  269. (float t, Vector3 x, Vector3 dx) _prev;
  270. public Vector3 Step(float t, Vector3 x)
  271. {
  272. var t_e = t - _prev.t;
  273. if (t_e < 1e-5f)
  274. return _prev.x;
  275. var dx = (x - _prev.x) / t_e;
  276. var dx_res = Vector3.Lerp(_prev.dx, dx, Alpha(t_e, DCutOff));
  277. var cutoff = MinCutoff + Beta * dx_res.magnitude;
  278. var x_res = Vector3.Lerp(_prev.x, x, Alpha(t_e, cutoff));
  279. _prev = (t, x_res, dx_res);
  280. return x_res;
  281. }
  282. static float Alpha(float t_e, float cutoff)
  283. {
  284. var r = 2 * Mathf.PI * cutoff * t_e;
  285. return r / (r + 1);
  286. }
  287. }
  288. }
  289. }