HandTracking.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.InputSystem;
  5. using System.Linq;
  6. using UnityEngine.InputSystem.Layouts;
  7. using System.IO;
  8. using Ximmerse.XR.Internal.Mathmatics;
  9. using System.Xml;
  10. using SXR;
  11. namespace Ximmerse.XR.InputSystems
  12. {
  13. /// <summary>
  14. /// Gesture type: open hand / fist
  15. /// </summary>
  16. public enum GestureType_Fist_OpenHand
  17. {
  18. None = 0,
  19. /// <summary>
  20. /// Five fingers opened.
  21. /// </summary>
  22. Opened = 1,
  23. /// <summary>
  24. /// Gesture of making hand as a fist
  25. /// </summary>
  26. Fist = 2,
  27. }
  28. /// <summary>
  29. /// Gesture type : grisp opened / closed
  30. /// </summary>
  31. public enum GestureType_Grisp
  32. {
  33. None = 0,
  34. /// <summary>
  35. /// Grisp open
  36. /// </summary>
  37. GrispOpen,
  38. /// <summary>
  39. /// Grisp close
  40. /// </summary>
  41. GraspClosed,
  42. }
  43. /// <summary>
  44. /// Hand tracking API expose interface for hand tracking functionality with RhinoX in XR platform.
  45. /// </summary>
  46. public static class HandTracking
  47. {
  48. static I_HandleTrackingModule currentHandTrackModule = null;
  49. static Vector3 rgbCameraAnchor = new Vector3(0.02631f, 0.05096f, 0.10121f);
  50. static Quaternion rgbCameraQ = Quaternion.Euler(0, 0, 0);
  51. /// <summary>
  52. /// The rgb camera anchor
  53. /// </summary>
  54. public static Transform rgbCamAnchor;
  55. /// <summary>
  56. /// The virtual XimXR gesture input device.
  57. /// </summary>
  58. public static XimmerseXRGesture sGestureInputDevice
  59. {
  60. get; private set;
  61. }
  62. static bool sIsGestureDeviceLayoutRegistered = false;
  63. static bool isDeviceCalibrated;
  64. static string pathDeviceCalibration = "/backup/slam/device_calibration.xml";
  65. static string pathRGB2VIO = "/backup/rgb_vio_camera_params.xml";
  66. /// <summary>
  67. /// Read RGB calibration data.
  68. /// </summary>
  69. static void ReadRGBCalibration()
  70. {
  71. try
  72. {
  73. //RGB to VIO R:
  74. XmlDocument xmlDoc_rgb = new XmlDocument(); // Create an XML document object
  75. xmlDoc_rgb.Load(pathRGB2VIO); // Load the XML document from the specified file
  76. XmlNode RotMat = xmlDoc_rgb.GetElementsByTagName("RotMat").Item(0);
  77. XmlNode TransVec = xmlDoc_rgb.GetElementsByTagName("TransVec").Item(0);
  78. ParseCalibrationMatrixFromText(RotMat["data"].InnerText, TransVec["data"].InnerText, out Matrix4x4 rgb2vioR);
  79. //VIO L to VIO R:
  80. XmlDocument xmlDoc_deviceCali = new XmlDocument(); // Create an XML document object
  81. xmlDoc_deviceCali.Load(pathDeviceCalibration); // Load the XML document from the specified file
  82. XmlNode camera_TrackingB = xmlDoc_deviceCali.GetElementsByTagName("Camera").Item(1);
  83. XmlNode rig = camera_TrackingB["Rig"];
  84. var rigAttri = rig.Attributes;
  85. ParseCalibrationMatrixFromText(rigAttri["rowMajorRotationMat"].InnerText, rigAttri["translation"].InnerText, out Matrix4x4 vioL2R);
  86. //VIO L to IMU:
  87. XmlNode SFConfig = xmlDoc_deviceCali.GetElementsByTagName("SFConfig").Item(0);
  88. XmlNode Stateinit = SFConfig["Stateinit"];
  89. var StateinitAttr = Stateinit.Attributes;
  90. ParseRotationVectorMatrixFromText(StateinitAttr["ombc"].InnerText, StateinitAttr["tbc"].InnerText, out Matrix4x4 vioL2Imu);
  91. Debug.LogFormat("Rgb to VIO L: {0}", rgb2vioR);
  92. Debug.LogFormat("VIO L to R: {0}", vioL2R);
  93. Debug.LogFormat("VIO L to IMU: {0}", vioL2Imu);
  94. Matrix4x4 rgb_to_IMU_space = vioL2Imu * vioL2R.inverse * rgb2vioR;
  95. Matrix4x4 righthand_rgb_2_imu = IMUSpaceToRightHandSpace(rgb_to_IMU_space);
  96. var eye2imu = ParamLoaderFloat16ToMatrix((int)ParamType.Design_Eye2IMU_TransMat_OpenGL_ARRAY16);
  97. Matrix4x4 righthand_rgb_2_eyecenter = eye2imu.inverse * righthand_rgb_2_imu; //imu2eye * rgb2imu
  98. Matrix4x4 unity_rgb_2_eyecenter = UnityRightHandSpaceToLeftHandSpace(righthand_rgb_2_eyecenter);
  99. Debug.LogFormat("(Unity space) RGB 2 EyeCenter : {0}, {1}", ((Vector3)unity_rgb_2_eyecenter.GetColumn(3)).ToString("F5"), unity_rgb_2_eyecenter.rotation.eulerAngles.ToString("F5"));
  100. rgbCameraAnchor = (Vector3)unity_rgb_2_eyecenter.GetColumn(3);
  101. rgbCameraQ = unity_rgb_2_eyecenter.rotation;
  102. isDeviceCalibrated = true;
  103. }
  104. catch (System.Exception e)
  105. {
  106. Debug.LogException(e);
  107. }
  108. }
  109. static Matrix4x4 ParamLoaderFloat16ToMatrix(int type)
  110. {
  111. float[] float16 = new float[16];
  112. ParamLoader.ParamLoaderGetFloatArray(type, float16, System.Runtime.InteropServices.Marshal.SizeOf<float>() * 16);
  113. return new Matrix4x4()
  114. {
  115. m00 = float16[0],
  116. m01 = float16[1],
  117. m02 = float16[2],
  118. m03 = float16[3],
  119. m10 = float16[4],
  120. m11 = float16[5],
  121. m12 = float16[6],
  122. m13 = float16[7],
  123. m20 = float16[8],
  124. m21 = float16[9],
  125. m22 = float16[10],
  126. m23 = float16[11],
  127. m30 = float16[12],
  128. m31 = float16[13],
  129. m32 = float16[14],
  130. m33 = float16[15],
  131. };
  132. }
  133. static Matrix4x4 UnityRightHandSpaceToLeftHandSpace(Matrix4x4 rightHandSpace)
  134. {
  135. var rightHandT = (Vector3)rightHandSpace.GetColumn(3);
  136. var rightHandQ = rightHandSpace.rotation.eulerAngles;
  137. return Matrix4x4.TRS(new Vector3(-rightHandT.x, -rightHandT.y, rightHandT.z), Quaternion.Euler(rightHandQ.x, -rightHandQ.y, -rightHandQ.z), Vector3.one);
  138. }
  139. static Matrix4x4 IMUSpaceToRightHandSpace(Matrix4x4 imuSpace)
  140. {
  141. // P: (-0.00092, -0.06631, -0.01755), (-0.37781, -169.53320, -89.64063)
  142. var imu_t = (Vector3)imuSpace.GetColumn(3);
  143. Vector3 rightHand_t = new Vector3(imu_t.y, imu_t.x, -imu_t.z);
  144. Quaternion righthand_q = Quaternion.Euler(0, 180, 90) * imuSpace.rotation * Quaternion.Euler(0, 0, 180);
  145. return Matrix4x4.TRS(rightHand_t, righthand_q, Vector3.one);
  146. }
  147. private static void ParseRotationVectorMatrixFromText(string RotationVector, string TextPosition, out Matrix4x4 translate)
  148. {
  149. translate = Matrix4x4.identity;
  150. int row = 0, col = 0;
  151. try
  152. {
  153. var textInArray = RotationVector.Trim().Split(' ');
  154. Vector3 rotationVector = Vector3.zero;
  155. for (int i = 0; i < textInArray.Length; i++)
  156. {
  157. string t = textInArray[i];
  158. if (float.TryParse(t, out float value))
  159. {
  160. rotationVector[col] = value;
  161. col++;
  162. }
  163. }
  164. Matrix3x3 qMatrix = RotationVectorToMatrix(rotationVector);
  165. textInArray = TextPosition.Trim().Split(' ');
  166. Vector4 column = new Vector4(0, 0, 0, 1);
  167. col = 0;
  168. for (int i = 0; i < textInArray.Length; i++)
  169. {
  170. string e = textInArray[i];
  171. if (string.IsNullOrEmpty(e.Trim()))
  172. {
  173. continue;
  174. }
  175. column[col++] = float.Parse(e.Trim());
  176. }
  177. translate = Matrix3x3.ToTRS(qMatrix, (Vector3)column);
  178. }
  179. catch (System.Exception exc)
  180. {
  181. Debug.LogException(exc);
  182. }
  183. }
  184. private static void ParseCalibrationMatrixFromText(string textQuaternion, string textPosition, out Matrix4x4 translate)
  185. {
  186. translate = Matrix4x4.identity;
  187. int row = 0, col = 0;
  188. try
  189. {
  190. var textInArray = textQuaternion.Trim().Split(' ');
  191. for (int i = 0; i < textInArray.Length; i++)
  192. {
  193. string t = textInArray[i];
  194. if (float.TryParse(t, out float value))
  195. {
  196. translate[row, col] = value;
  197. col++;
  198. if (col >= 3)
  199. {
  200. col = 0;
  201. row++;
  202. }
  203. }
  204. }
  205. textInArray = textPosition.Trim().Split(' ');
  206. Vector4 column = new Vector4(0, 0, 0, 1);
  207. col = 0;
  208. for (int i = 0; i < textInArray.Length; i++)
  209. {
  210. string e = textInArray[i];
  211. if (string.IsNullOrEmpty(e.Trim()))
  212. {
  213. continue;
  214. }
  215. column[col++] = float.Parse(e.Trim());
  216. }
  217. translate.SetColumn(3, column);
  218. }
  219. catch (System.Exception exc)
  220. {
  221. Debug.LogException(exc);
  222. }
  223. }
  224. /// <summary>
  225. /// Convert rotation vector in radians to a rotation matrix 3x3, using Rodrigues fomular.
  226. /// </summary>
  227. /// <param name="RotationVectorInRadians"></param>
  228. /// <returns>A 3x3 matrix represents a rotation quaternion.</returns>
  229. static Matrix3x3 RotationVectorToMatrix(Vector3 RotationVectorInRadians)
  230. {
  231. Vector3 r = RotationVectorInRadians.normalized;
  232. float theta = RotationVectorInRadians.magnitude;
  233. return Mathf.Cos(theta) * Matrix3x3.identity + (1 - Mathf.Cos(theta)) * new Matrix3x3(r, r) + Mathf.Sin(theta) * new Matrix3x3(0, -r.z, r.y, r.z, 0, -r.x, -r.y, r.x, 0);
  234. }
  235. /// <summary>
  236. /// Enable handle tracking
  237. /// </summary>
  238. public static void EnableHandTracking()
  239. {
  240. if(!isDeviceCalibrated)
  241. {
  242. ReadRGBCalibration();
  243. }
  244. if (!rgbCamAnchor)
  245. {
  246. rgbCamAnchor = new GameObject("RGB Camera Anchor").transform;
  247. rgbCamAnchor.localPosition = rgbCameraAnchor;
  248. rgbCamAnchor.localRotation = rgbCameraQ;
  249. rgbCamAnchor.SetParent(Camera.main.transform, false);
  250. }
  251. if (!sIsGestureDeviceLayoutRegistered)
  252. {
  253. RegisterXRGestureLayout();
  254. }
  255. if (currentHandTrackModule == null)
  256. {
  257. currentHandTrackModule = new HandTrackingT3D();
  258. if (currentHandTrackModule.EnableModule(new InitializeHandTrackingModuleParameter()
  259. {
  260. TrackingAnchor = rgbCamAnchor,
  261. }))
  262. {
  263. if (sGestureInputDevice == null)
  264. {
  265. //Adds a virtural input device for gesture input:
  266. XimmerseXRGesture gestureInputDevice = (XimmerseXRGesture)InputSystem.AddDevice(new InputDeviceDescription
  267. {
  268. interfaceName = "HandState",
  269. product = "GestureInputDevice",
  270. });
  271. sGestureInputDevice = gestureInputDevice;
  272. }
  273. }
  274. XRManager.OnPostTrackUpdate += currentHandTrackModule.Tick;
  275. }
  276. }
  277. private static void RegisterXRGestureLayout()
  278. {
  279. InputSystem.RegisterLayout<XimmerseXRGesture>(matches: new InputDeviceMatcher()
  280. .WithInterface("HandState"));
  281. sIsGestureDeviceLayoutRegistered = true;
  282. Debug.LogFormat("Gesture input device layout has been registered.");
  283. }
  284. /// <summary>
  285. ///
  286. /// </summary>
  287. public static I_HandleTrackingModule handTrackModule
  288. {
  289. get => currentHandTrackModule;
  290. }
  291. /// <summary>
  292. /// Disable hand tracking.
  293. /// </summary>
  294. public static void DisableHandTracking()
  295. {
  296. if (currentHandTrackModule != null)
  297. {
  298. currentHandTrackModule.DisableModule();
  299. XRManager.OnPostTrackUpdate -= currentHandTrackModule.Tick;
  300. currentHandTrackModule = null;
  301. if (sGestureInputDevice != null)
  302. {
  303. InputSystem.RemoveDevice(sGestureInputDevice);
  304. }
  305. }
  306. }
  307. /// <summary>
  308. /// Is hand tracking module currently enabled and running ?
  309. /// </summary>
  310. public static bool IsHandTrackingEnable
  311. {
  312. get => currentHandTrackModule != null && currentHandTrackModule.IsModuleEnabled;
  313. }
  314. /// <summary>
  315. /// If IsHandTrackingEnable is true, this is the hand track info.
  316. /// </summary>
  317. public static HandTrackingInfo HandTrackingInfo
  318. {
  319. get
  320. {
  321. return currentHandTrackModule != null ? currentHandTrackModule.HandleTrackInfo : default(HandTrackingInfo);
  322. }
  323. }
  324. #if UNITY_EDITOR
  325. //[UnityEditor.MenuItem("Ximmerse/Create Gesture Device")]
  326. private static void TestAddGestureDevice()
  327. {
  328. if (!sIsGestureDeviceLayoutRegistered)
  329. {
  330. InputSystem.RegisterLayout<XimmerseXRGesture>(matches: new InputDeviceMatcher()
  331. .WithInterface("HandState"));
  332. sIsGestureDeviceLayoutRegistered = true;
  333. }
  334. XimmerseXRGesture gestureID = (XimmerseXRGesture)InputSystem.AddDevice(new InputDeviceDescription
  335. {
  336. interfaceName = "HandState",
  337. product = "GestureInputDevice",
  338. });
  339. Debug.Log("Gesture device is added.");
  340. }
  341. //[UnityEditor.MenuItem("Ximmerse/Remove Gesture Device")]
  342. private static void RemoveDevice()
  343. {
  344. var gestureDevice = InputSystem.devices.FirstOrDefault(x => x is XimmerseXRGesture);
  345. if (gestureDevice != null)
  346. {
  347. InputSystem.RemoveDevice(gestureDevice);
  348. Debug.Log("Gesture device is removed.");
  349. }
  350. }
  351. #endif
  352. }
  353. }