ZEDOffsetController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
  2. using UnityEngine;
  3. using System.IO;
  4. #if UNITY_EDITOR
  5. using UnityEditor;
  6. #endif
  7. /// <summary>
  8. /// Saves and loads the pose of this object relative to its parent.
  9. /// Used primarily when mounting the ZED to a tracked object (like a VR controller) for 3rd person mixed reality.
  10. /// This way, you can calibrate its position/rotation to line up the real/virtual worlds, and hit Save.
  11. /// It will automatically load a calibration in the future if it's found.
  12. /// Note that if you used our beta tool for SteamVR calibration, this script will load that calibration automatically.
  13. /// </summary>
  14. public class ZEDOffsetController : MonoBehaviour
  15. {
  16. /// <summary>
  17. /// ZED offset file name.
  18. /// </summary>
  19. [SerializeField]
  20. public static string ZEDOffsetFile = "ZED_Position_Offset.conf";
  21. /// <summary>
  22. /// Where to save the ZED offset file.
  23. /// </summary>
  24. private string path = @"Stereolabs\steamvr";
  25. /// <summary>
  26. /// The ZEDControllerTracker object in the scene from which we're offset.
  27. /// This script checks this object, its parents and its children (in that order) for such a component.
  28. /// </summary>
  29. public ZEDControllerTracker controllerTracker;
  30. /// <summary>
  31. /// If the object is instantiated and ready to save/load an offset file.
  32. /// Used by the custom Inspector editor to know if the Save/Load buttons should be pressable.
  33. /// </summary>
  34. public bool isReady = false;
  35. /// <summary>
  36. /// Save the local position/rotation of the ZED into an offset file.
  37. /// </summary>
  38. public void SaveZEDPos()
  39. {
  40. using (System.IO.StreamWriter file = new System.IO.StreamWriter(path))
  41. {
  42. string tx = "x=" + transform.localPosition.x.ToString() + " //Translation x";
  43. string ty = "y=" + transform.localPosition.y.ToString() + " //Translation y";
  44. string tz = "z=" + transform.localPosition.z.ToString() + " //Translation z";
  45. string rx = "rx=" + transform.localRotation.eulerAngles.x.ToString() + " //Rotation x";
  46. string ry = "ry=" + transform.localRotation.eulerAngles.y.ToString() + " //Rotation y";
  47. string rz = "rz=" + transform.localRotation.eulerAngles.z.ToString() + " //Rotation z";
  48. //Write those values into the file.
  49. file.WriteLine(tx);
  50. file.WriteLine(ty);
  51. file.WriteLine(tz);
  52. file.WriteLine(rx);
  53. file.WriteLine(ry);
  54. file.WriteLine(rz);
  55. #if ZED_STEAM_VR
  56. if (TrackerComponentExist())
  57. {
  58. //If using SteamVR, get the serial number of the tracked device, or write "NONE" to indicate we checked but couldn't find it.
  59. string result = "indexController = ";
  60. if (controllerTracker.index > 0)
  61. {
  62. var snerror = Valve.VR.ETrackedPropertyError.TrackedProp_Success;
  63. var snresult = new System.Text.StringBuilder((int)64);
  64. result += Valve.VR.OpenVR.System.GetStringTrackedDeviceProperty((uint)controllerTracker.index, Valve.VR.ETrackedDeviceProperty.Prop_SerialNumber_String, snresult, 64, ref snerror);
  65. //OpenVR.System.GetStringTrackedDeviceProperty((uint)index, ETrackedDeviceProperty.Prop_SerialNumber_String, snresult, 64, ref snerror);
  66. }
  67. else
  68. {
  69. result += "NONE";
  70. }
  71. file.WriteLine(result);
  72. }
  73. #endif
  74. file.Close(); //Finalize the new file.
  75. }
  76. }
  77. /// <summary>
  78. /// Whether there is a referenced ZEDControllerTracker object in this object, a parent, or a child.
  79. /// </summary>
  80. /// <returns>True if such a component exists and is used to handle the offset.</returns>
  81. public bool TrackerComponentExist()
  82. {
  83. if (controllerTracker != null)
  84. return true;
  85. else
  86. return false;
  87. }
  88. private void OnEnable()
  89. {
  90. LoadTrackerComponent();
  91. }
  92. /// <summary>
  93. /// Searched for a ZEDControllerTracker component in this object, its parents, and its children.
  94. /// Sets the controllerTracker value to the first one it finds.
  95. /// </summary>
  96. private void LoadTrackerComponent()
  97. {
  98. ZEDControllerTracker zct = GetComponent<ZEDControllerTracker>();
  99. if (zct == null)
  100. zct = GetComponentInParent<ZEDControllerTracker>();
  101. if (zct == null)
  102. zct = GetComponentInChildren<ZEDControllerTracker>();
  103. if (zct != null)
  104. controllerTracker = zct;
  105. }
  106. /// <summary>
  107. /// Tries to find the relevant ZEDControllerTracker object, and loads the existing
  108. /// offset file if there is one.
  109. /// </summary>
  110. void Awake()
  111. {
  112. LoadTrackerComponent();
  113. string folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
  114. string specificFolder = Path.Combine(folder, @"Stereolabs\steamvr");
  115. path = Path.Combine(specificFolder, ZEDOffsetFile);
  116. // Check if folder exists and if not, create it
  117. if (!Directory.Exists(specificFolder))
  118. Directory.CreateDirectory(specificFolder);
  119. LoadZEDPos();
  120. CreateFileWatcher(specificFolder);
  121. isReady = true;
  122. }
  123. private void Update()
  124. {
  125. if (isReady)
  126. LoadZEDPos();
  127. }
  128. /// <summary>
  129. /// Loads the offset file and sets the local position/rotation to the loaded values.
  130. /// </summary>
  131. public void LoadZEDPos()
  132. {
  133. if (!System.IO.File.Exists(path)) return;
  134. string[] lines = null;
  135. try
  136. {
  137. lines = System.IO.File.ReadAllLines(path);
  138. }
  139. catch (System.Exception)
  140. {
  141. controllerTracker.SNHolder = "NONE";
  142. }
  143. if (lines == null)
  144. {
  145. controllerTracker.SNHolder = "NONE";
  146. return;
  147. }
  148. if (lines == null) return;
  149. Vector3 position = new Vector3(0, 0, 0);
  150. Vector3 eulerRotation = new Vector3(0, 0, 0);
  151. foreach (string line in lines)
  152. {
  153. string[] splittedLine = line.Split('=');
  154. if (splittedLine != null && splittedLine.Length >= 2)
  155. {
  156. string key = splittedLine[0];
  157. string field = splittedLine[1].Split(' ')[0];
  158. if (key == "x")
  159. {
  160. position.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  161. }
  162. else if (key == "y")
  163. {
  164. position.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  165. }
  166. else if (key == "z")
  167. {
  168. position.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  169. }
  170. else if (key == "rx")
  171. {
  172. eulerRotation.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  173. }
  174. else if (key == "ry")
  175. {
  176. eulerRotation.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  177. }
  178. else if (key == "rz")
  179. {
  180. eulerRotation.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  181. }
  182. else if (key == "indexController")
  183. {
  184. LoadTrackerComponent();
  185. if (TrackerComponentExist())
  186. {
  187. controllerTracker.SNHolder = field;
  188. }
  189. }
  190. }
  191. }
  192. transform.localPosition = position;
  193. transform.localRotation = Quaternion.Euler(eulerRotation.x, eulerRotation.y, eulerRotation.z);
  194. }
  195. /// <summary>
  196. /// Creates a FileSystemWatcher that keeps track of the offset file, in case it
  197. /// changes or moves.
  198. /// </summary>
  199. /// <param name="path"></param>
  200. public void CreateFileWatcher(string path)
  201. {
  202. // Create a new FileSystemWatcher and set its properties.
  203. FileSystemWatcher watcher = new FileSystemWatcher();
  204. watcher.Path = path;
  205. /* Watch for changes in LastAccess and LastWrite times, and
  206. the renaming of files or directories. */
  207. watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
  208. | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  209. // Only watch text files.
  210. watcher.Filter = ZEDOffsetFile;
  211. // Add event handlers.
  212. watcher.Changed += new FileSystemEventHandler(OnChanged);
  213. // Begin watching.
  214. watcher.EnableRaisingEvents = true;
  215. }
  216. /// <summary>
  217. /// Event handler for when the offset file changes or moves.
  218. /// Called by the FileSystemWatcher created in CreateFileWatcher().
  219. /// </summary>
  220. /// <param name="source"></param>
  221. /// <param name="e"></param>
  222. private void OnChanged(object source, FileSystemEventArgs e)
  223. {
  224. if (TrackerComponentExist())
  225. {
  226. LoadZEDPos();
  227. }
  228. }
  229. }
  230. #if UNITY_EDITOR
  231. /// <summary>
  232. /// Custom editor for ZEDOffsetController, to define its Inspector layout.
  233. /// Specifically, it doesn't draw public fields like normal but instead places Save/Load buttons
  234. /// for the offset file that are only pressable during runtime.
  235. /// </summary>
  236. [CustomEditor(typeof(ZEDOffsetController))]
  237. public class ZEDPositionEditor : Editor
  238. {
  239. private ZEDOffsetController positionManager;
  240. public void OnEnable()
  241. {
  242. positionManager = (ZEDOffsetController)target;
  243. }
  244. public override void OnInspectorGUI() //Called when the Inspector GUI becomes visible, or changes at all.
  245. {
  246. GUILayout.Space(5);
  247. EditorGUILayout.BeginHorizontal();
  248. EditorGUILayout.EndHorizontal();
  249. EditorGUILayout.BeginHorizontal();
  250. GUI.enabled = positionManager.isReady;
  251. GUIContent savecontent = new GUIContent("Save Offset", "Saves the object's local position/rotation to a text file to be loaded anytime in the future.");
  252. if (GUILayout.Button(savecontent))
  253. {
  254. positionManager.SaveZEDPos();
  255. }
  256. GUIContent loadcontent = new GUIContent("Load Offset", "Loads local position/rotation from an offset file previously saved, or created by the beta ZED calibration tool.");
  257. if (GUILayout.Button(loadcontent))
  258. {
  259. positionManager.LoadZEDPos();
  260. }
  261. EditorGUILayout.EndHorizontal();
  262. }
  263. }
  264. #endif