ZEDMixedRealityPlugin.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
  2. using UnityEngine;
  3. using System.Runtime.InteropServices;
  4. using UnityEngine.XR;
  5. using System.IO;
  6. using System.Collections.Generic;
  7. using UnityEngine.Rendering;
  8. /// <summary>
  9. /// In pass-through AR mode, handles the final output to the VR headset, positioning the final images
  10. /// to make the pass-through effect natural and comfortable. Also moves/rotates the images to
  11. /// compensate for the ZED image's latency using our Video Asynchronous Timewarp.
  12. /// ZEDManager attaches this component to a second stereo rig called "ZEDRigDisplayer" that it
  13. /// creates and hides in the editor at runtime; see ZEDManager.CreateZEDRigDisplayer() to see this process.
  14. ///
  15. /// The Timewarp effect is achieved by logging the pose of the headset each time it's available within the
  16. /// wrapper. Then, when a ZED image is available, the wrapper looks up the headset's position using the timestamp
  17. /// of the image, and moves the final viewing planes according to that position. In this way, the ZED's images
  18. /// line up with real-life, even after a ~60ms latency.
  19. /// </summary>
  20. public class ZEDMixedRealityPlugin : MonoBehaviour
  21. {
  22. #region DLL Calls
  23. const string nameDll = sl.ZEDCommon.NameDLL;
  24. [DllImport(nameDll, EntryPoint = "sl_compute_size_plane_with_gamma")]
  25. private static extern System.IntPtr dllz_compute_size_plane_with_gamma(int width, int height, float perceptionDistance, float eyeToZedDistance, float planeDistance, float HMDFocal, float zedFocal);
  26. [DllImport(nameDll, EntryPoint = "sl_compute_hmd_focal")]
  27. private static extern float dllz_compute_hmd_focal(int width, int height, float w, float h);
  28. /*****LATENCY CORRECTOR***/
  29. [DllImport(nameDll, EntryPoint = "sl_latency_corrector_add_key_pose")]
  30. private static extern void dllz_latency_corrector_add_key_pose(ref Vector3 translation, ref Quaternion rotation, ulong timeStamp);
  31. [DllImport(nameDll, EntryPoint = "sl_latency_corrector_get_transform")]
  32. private static extern int dllz_latency_corrector_get_transform(ulong timeStamp, bool useLatency,out Vector3 translation, out Quaternion rotation);
  33. [DllImport(nameDll, EntryPoint = "sl_latency_corrector_initialize")]
  34. private static extern void dllz_latency_corrector_initialize(int device);
  35. [DllImport(nameDll, EntryPoint = "sl_latency_corrector_shutdown")]
  36. private static extern void dllz_latency_corrector_shutdown();
  37. /****ANTI DRIFT ***/
  38. [DllImport(nameDll, EntryPoint = "sl_drift_corrector_initialize")]
  39. public static extern void dllz_drift_corrector_initialize();
  40. [DllImport(nameDll, EntryPoint = "sl_drift_corrector_shutdown")]
  41. public static extern void dllz_drift_corrector_shutdown();
  42. [DllImport(nameDll, EntryPoint = "sl_drift_corrector_get_tracking_data")]
  43. public static extern void dllz_drift_corrector_get_tracking_data(ref TrackingData trackingData, ref Pose HMDTransform, ref Pose latencyCorrectorTransform, int hasValidTrackingPosition,bool checkDrift);
  44. [DllImport(nameDll, EntryPoint = "sl_drift_corrector_set_calibration_transform")]
  45. public static extern void dllz_drift_corrector_set_calibration_transform(ref Pose pose);
  46. [DllImport(nameDll, EntryPoint = "sl_drift_corrector_set_calibration_const_offset_transform")]
  47. public static extern void dllz_drift_corrector_set_calibration_const_offset_transform(ref Pose pose);
  48. #endregion
  49. /// <summary>
  50. /// Container for storing historic pose information, used by the latency corrector.
  51. /// </summary>
  52. public struct KeyPose
  53. {
  54. public Quaternion Orientation;
  55. public Vector3 Translation;
  56. public ulong Timestamp;
  57. };
  58. /// <summary>
  59. /// Container for position and rotation. Used when timestamps are not needed or have already
  60. /// been processed, such as setting the initial camera offset or updating the stereo rig's
  61. /// transform from data pulled from the wrapper.
  62. /// </summary>
  63. [StructLayout(LayoutKind.Sequential)]
  64. public struct Pose
  65. {
  66. public Vector3 translation;
  67. public Quaternion rotation;
  68. public Pose(Vector3 t, Quaternion q)
  69. {
  70. translation = t;
  71. rotation = q;
  72. }
  73. }
  74. /// <summary>
  75. ///
  76. /// </summary>
  77. [StructLayout(LayoutKind.Sequential)]
  78. public struct TrackingData
  79. {
  80. public Pose zedPathTransform;
  81. public Pose zedWorldTransform;
  82. public Pose offsetZedWorldTransform;
  83. public int trackingState;
  84. }
  85. /// <summary>
  86. /// Gameobject holding the camera in the final ZEDRigDisplayer rig, which captures the final images sent to both HMD screens.
  87. /// </summary>
  88. public GameObject finalCameraCenter;
  89. /// <summary>
  90. /// 'Intermediate' left camera GameObject, which is the one on the regular, always-visible ZED stereo rig (ZED_Rig_Stereo),
  91. /// usually called 'Left_eye'.
  92. /// </summary>
  93. [Tooltip("'Intermediate' left camera GameObject, which is the one on the regular, always-visible ZED stereo rig (ZED_Rig_Stereo), "
  94. + "usually called 'Left_eye'. ")]
  95. public GameObject ZEDEyeLeft;
  96. /// <summary>
  97. /// 'Intermediate' right camera GameObject, which is the one on the regular, always-visible ZED stereo rig (ZED_Rig_Stereo),
  98. /// usually called 'Right_eye'.
  99. /// </summary>
  100. [Tooltip("'Intermediate' right camera GameObject, which is the one on the regular, always-visible ZED stereo rig (ZED_Rig_Stereo)," +
  101. "usually called 'Right_eye'. ")]
  102. public GameObject ZEDEyeRight;
  103. /// <summary>
  104. /// 'Intermediate' left screen/canvas object in the always-visible ZED stereo rig.
  105. /// </summary>
  106. [Tooltip("")]
  107. public ZEDRenderingPlane leftScreen;
  108. /// <summary>
  109. /// 'Intermediate' right screen/canvas object in the always-visible ZED stereo rig.
  110. /// </summary>
  111. [Tooltip("")]
  112. public ZEDRenderingPlane rightScreen;
  113. /// <summary>
  114. /// Final center viewing plane/canvas object in the final ZEDRigDisplayer rig. Displays the image from the center
  115. /// 'intermediate' cameras (ZEDEyeRight and ZEDEyeRight) and is offset for image comfort and moved each frame for the Timewarp effect.
  116. /// </summary>
  117. [Tooltip("")]
  118. public Transform quadCenter;
  119. /// <summary>
  120. /// Camera object in 'finalCameraCenter', which captures the final image output to the headset's left and right screens.
  121. /// </summary>
  122. // [Tooltip("")]
  123. public Camera finalCenterEye;
  124. /// <summary>
  125. /// Material from the final center plane. Usually a new instance of Mat_ZED_Unlit.
  126. /// </summary>
  127. [Tooltip("Material from the final right plane. Usually a new instance of Mat_ZED_Unlit. ")]
  128. public Material centerMaterial;
  129. /// <summary>
  130. /// Base, pre-Timewarp offset between each final plane and its corresponding camera.
  131. /// </summary>
  132. [Tooltip("Offset between each final plane and its corresponding camera.")]
  133. public Vector3 offset = new Vector3(0, 0, (float)sl.Constant.PLANE_DISTANCE);
  134. /// <summary>
  135. /// Distance to set each intermediate camera from the point between them. This is half of the post-calibration
  136. /// distance between the ZED cameras, so X is usually very close to 0.0315m (63mm / 2).
  137. /// </summary>
  138. [Tooltip("")]
  139. public Vector3 halfBaselineOffset;
  140. /// <summary>
  141. /// Reference to the ZEDCamera instance, which communicates with the SDK.
  142. /// </summary>
  143. [Tooltip("Reference to the ZEDCamera instance, which communicates with the SDK.")]
  144. public sl.ZEDCamera zedCamera;
  145. /// <summary>
  146. /// Reference to the scene's ZEDManager instance, usually contained in ZED_Rig_Stereo.
  147. /// </summary>
  148. [Tooltip("Reference to the scene's ZEDManager instance, usually contained in ZED_Rig_Stereo.")]
  149. public ZEDManager manager;
  150. /// <summary>
  151. /// Flag set to true when the target textures from the ZEDRenderingPlane overlays are ready.
  152. /// </summary>
  153. [Tooltip("Flag set to true when the target textures from the ZEDRenderingPlane overlays are ready.")]
  154. public bool ready = false;
  155. /// <summary>
  156. /// Flag set to true when a grab is ready, used to collect a pose from the latest time possible.
  157. /// </summary>
  158. [Tooltip("Flag set to true when a grab is ready, used to collect a pose from the latest time possible.")]
  159. public bool grabSucceeded = false;
  160. /// <summary>
  161. /// Flag set to true when the ZED is ready (after ZEDManager.OnZEDReady is invoked).
  162. /// </summary>
  163. [Tooltip("Flag set to true when the ZED is ready (after ZEDManager.OnZEDReady is invoked).")]
  164. public bool zedReady = false;
  165. /// <summary>
  166. /// If a VR device is still detected. Updated each frame. Used to know if certain updates should still happen.
  167. /// </summary>
  168. private bool hasVRDevice = false;
  169. public bool HasVRDevice
  170. {
  171. get { return hasVRDevice; }
  172. }
  173. /// <summary>
  174. /// The current latency pose - the pose the headset was at when the last ZED frame was captured (based on its timestamp).
  175. /// </summary>
  176. private Pose latencyPose;
  177. /// <summary>
  178. /// The physical offset of the HMD to the ZED. Represents the offset from the approximate center of the user's
  179. /// head to the ZED's left sensor.
  180. /// </summary>
  181. private Pose hmdtozedCalibration;
  182. /// <summary>
  183. /// Public accessor for the physical offset of the HMD to the ZED. Represents the offset from the
  184. /// approximate center of the user's head to the ZED's left sensor.
  185. /// </summary>
  186. public Pose HmdToZEDCalibration
  187. {
  188. get { return hmdtozedCalibration; }
  189. }
  190. /// <summary>
  191. /// Whether the latency correction is ready.
  192. /// </summary>
  193. private bool latencyCorrectionReady = false;
  194. /// <summary>
  195. /// Contains the last position computed by the anti-drift.
  196. /// </summary>
  197. public TrackingData trackingData = new TrackingData();
  198. /// <summary>
  199. /// Filename of the saved HMD to ZED calibration file loaded into hmdtozedCalibration.
  200. /// //If it doesn't exist, it's created with hard-coded values.
  201. /// </summary>
  202. [Tooltip("")]
  203. [SerializeField]
  204. private string calibrationFile = "CalibrationZEDHMD.ini";
  205. /// <summary>
  206. /// Path of the saved HMD to ZED calibration file loaded into hmdtozedCalibration.
  207. /// By default, corresponds to C:/ProgramData/Stereolabs/mr.
  208. /// </summary>
  209. private string calibrationFilePath = @"Stereolabs\mr";
  210. /// <summary>
  211. /// Delegate for the OnHMDCalibChanged event.
  212. /// </summary>
  213. public delegate void OnHmdCalibrationChanged();
  214. /// <summary>
  215. /// Event invoked if the calibration file that sets the physical ZED offset is changed at runtime.
  216. /// Causes ZEDManger.CalibrationHasChanged() to get called, which re-initialized the ZED's position
  217. /// with ZEDManager.AdjustZEDRigCameraPosition() at the next tracking update.
  218. /// </summary>
  219. public static event OnHmdCalibrationChanged OnHmdCalibChanged;
  220. /// <summary>
  221. /// Cached property id for _MainTex. use the mainTexID property instead.
  222. /// </summary>
  223. private int? _maintexLeftid;
  224. /// <summary>
  225. /// Property id for _MainTex, which is the main texture from the ZED.
  226. /// </summary>
  227. private int mainTexLeftID
  228. {
  229. get
  230. {
  231. if (_maintexLeftid == null) _maintexLeftid = Shader.PropertyToID("_MainTexLeft");
  232. return (int)_maintexLeftid;
  233. }
  234. }
  235. /// <summary>
  236. /// Cached property id for _MainTex. use the mainTexID property instead.
  237. /// </summary>
  238. private int? _maintexRightid;
  239. /// <summary>
  240. /// Property id for _MainTex, which is the main texture from the ZED.
  241. /// </summary>
  242. private int mainTexRightID
  243. {
  244. get
  245. {
  246. if (_maintexRightid == null) _maintexRightid = Shader.PropertyToID("_MainTexRight");
  247. return (int)_maintexRightid;
  248. }
  249. }
  250. List<XRNodeState> nodeStates = new List<XRNodeState>();
  251. private bool hasXRDevice()
  252. {
  253. #if UNITY_2020_1_OR_NEWER
  254. var xrDisplaySubsystems = new List<XRDisplaySubsystem>();
  255. SubsystemManager.GetInstances<XRDisplaySubsystem>(xrDisplaySubsystems);
  256. foreach (var xrDisplay in xrDisplaySubsystems)
  257. {
  258. if (xrDisplay.running)
  259. {
  260. return true;
  261. }
  262. }
  263. return false;
  264. #else
  265. return XRDevice.isPresent;
  266. #endif
  267. }
  268. private string getXRModelName()
  269. {
  270. #if UNITY_2019_1_OR_NEWER
  271. return XRSettings.loadedDeviceName;
  272. #else
  273. return XRDevice.model;
  274. #endif
  275. }
  276. private void Awake()
  277. {
  278. //Initialize the latency tracking only if a supported headset is detected.
  279. //You can force it to work for unsupported headsets by implementing your own logic for calling
  280. //dllz_latency_corrector_initialize.
  281. hasVRDevice = hasXRDevice();
  282. if (hasVRDevice)
  283. {
  284. if (getXRModelName().ToLower().Contains("vive")) //Vive or Vive Pro
  285. {
  286. dllz_latency_corrector_initialize(0);
  287. }
  288. else //Oculus Rift CV1, Rift S, Windows Mixed Reality, Valve Index, etc.
  289. {
  290. dllz_latency_corrector_initialize(1);
  291. }
  292. dllz_drift_corrector_initialize();
  293. }
  294. #if UNITY_2017_OR_NEWER
  295. nodeState.nodeType = VRNode.Head;
  296. nodes.Add(nodeState);
  297. #endif
  298. }
  299. /// <summary>
  300. /// Sets references not set in ZEDManager.CreateZEDRigDisplayer(), sets materials,
  301. /// adjusts final plane scale, loads the ZED calibration offset and other misc. values.
  302. /// </summary>
  303. void Start()
  304. {
  305. hasVRDevice = hasXRDevice();
  306. //iterate until we found the ZED Manager parent...
  307. Transform ObjParent = gameObject.transform;
  308. int tries = 0;
  309. while (manager == null && tries < 50) {
  310. if (ObjParent!=null)
  311. manager= ObjParent.GetComponent<ZEDManager> ();
  312. if (manager == null && ObjParent!=null)
  313. ObjParent = ObjParent.parent;
  314. tries++;
  315. }
  316. if (manager != null) {
  317. manager.OnZEDReady += ZEDReady;
  318. zedCamera = manager.zedCamera;
  319. } else
  320. return;
  321. leftScreen = ZEDEyeLeft.GetComponent<ZEDRenderingPlane>();
  322. rightScreen = ZEDEyeRight.GetComponent<ZEDRenderingPlane>();
  323. finalCenterEye = finalCameraCenter.GetComponent<Camera>();
  324. centerMaterial = quadCenter.GetComponent<Renderer>().material;
  325. finalCenterEye.SetReplacementShader(centerMaterial.shader, "");
  326. finalCenterEye.depth = 0;
  327. float plane_dist = (float)sl.Constant.PLANE_DISTANCE;
  328. scale(quadCenter.gameObject, new Vector2(1.78f * plane_dist, 1.0f * plane_dist));
  329. zedReady = false;
  330. #if ZED_HDRP || ZED_URP
  331. RenderPipelineManager.beginFrameRendering += SRPStartFrame;
  332. #else
  333. Camera.onPreRender += PreRender;
  334. #endif
  335. LoadHmdToZEDCalibration();
  336. }
  337. /// <summary>
  338. /// Computes the size of the final planes.
  339. /// </summary>
  340. /// <param name="resolution">ZED's current resolution. Usually 1280x720.</param>
  341. /// <param name="perceptionDistance">Typically 1.</param>
  342. /// <param name="eyeToZedDistance">Distance from your eye to the camera. Estimated at 0.1m.</param>
  343. /// <param name="planeDistance">Distance to final quad (quadLeft or quadRight). Arbitrary but set by offset.z.</param>
  344. /// <param name="HMDFocal">Focal length of the HMD, retrieved from the wrapper.</param>
  345. /// <param name="zedFocal">Focal length of the ZED, retrieved from the camera's rectified calibration parameters.</param>
  346. /// <returns></returns>
  347. public Vector2 ComputeSizePlaneWithGamma(sl.Resolution resolution, float perceptionDistance, float eyeToZedDistance, float planeDistance, float HMDFocal, float zedFocal)
  348. {
  349. System.IntPtr p = dllz_compute_size_plane_with_gamma((int)resolution.width, (int)resolution.height, perceptionDistance, eyeToZedDistance, planeDistance, HMDFocal, zedFocal);
  350. if (p == System.IntPtr.Zero)
  351. {
  352. return new Vector2();
  353. }
  354. Vector2 parameters = (Vector2)Marshal.PtrToStructure(p, typeof(Vector2));
  355. return parameters;
  356. }
  357. /// <summary>
  358. /// Compute the focal length of the HMD.
  359. /// </summary>
  360. /// <param name="targetSize">Resolution of the headset's eye textures.</param>
  361. /// <returns></returns>
  362. public float ComputeFocal(sl.Resolution targetSize)
  363. {
  364. float focal_hmd = dllz_compute_hmd_focal((int)targetSize.width, (int)targetSize.height, finalCenterEye.projectionMatrix.m00, finalCenterEye.projectionMatrix.m11);
  365. return focal_hmd;
  366. }
  367. /// <summary>
  368. /// Called once the ZED is finished initializing. Subscribed to ZEDManager.OnZEDReady in OnEnable.
  369. /// Uses the newly-available ZED parameters to scale the final planes (quadLeft and quadRight) to appear
  370. /// properly in the currently-connected headset.
  371. /// </summary>
  372. void ZEDReady()
  373. {
  374. Vector2 scaleFromZED;
  375. halfBaselineOffset.x = zedCamera.Baseline / 2.0f;
  376. float perception_distance = 1.0f;
  377. float zed2eye_distance = 0.1f; //Estimating 10cm between your eye and physical location of the ZED Mini.
  378. hasVRDevice = hasXRDevice();
  379. if (hasVRDevice) {
  380. sl.CalibrationParameters parameters = zedCamera.CalibrationParametersRectified;
  381. scaleFromZED = ComputeSizePlaneWithGamma (new sl.Resolution ((uint)zedCamera.ImageWidth, (uint)zedCamera.ImageHeight),
  382. perception_distance, zed2eye_distance, offset.z,
  383. ComputeFocal (new sl.Resolution ((uint)XRSettings.eyeTextureWidth, (uint)XRSettings.eyeTextureHeight)),
  384. parameters.leftCam.fx);
  385. scale(quadCenter.gameObject, scaleFromZED);
  386. }
  387. ready = false;
  388. // If using Vive, change ZED's settings to compensate for different screen.
  389. if (getXRModelName().ToLower().Contains("vive"))
  390. {
  391. zedCamera.SetCameraSettings(sl.CAMERA_SETTINGS.CONTRAST, 3);
  392. zedCamera.SetCameraSettings(sl.CAMERA_SETTINGS.SATURATION, 3);
  393. }
  394. //Set eye layers to respective eyes.
  395. finalCenterEye.stereoTargetEye = StereoTargetEyeMask.Both;
  396. /// AR Passtrough is recommended in 1280x720 at 60, due to FoV, FPS, etc.
  397. /// If not set to this resolution, warn the user.
  398. if (zedCamera.ImageWidth != 1280 && zedCamera.ImageHeight != 720)
  399. Debug.LogWarning("[ZED AR Passthrough] This resolution is not ideal for a proper AR passthrough experience. Recommended resolution is 1280x720.");
  400. zedReady = true;
  401. }
  402. public void OnEnable()
  403. {
  404. latencyCorrectionReady = false;
  405. if (manager != null)
  406. manager.OnZEDReady += ZEDReady;
  407. }
  408. public void OnDisable()
  409. {
  410. latencyCorrectionReady = false;
  411. if (manager != null)
  412. manager.OnZEDReady -= ZEDReady;
  413. }
  414. void OnGrab()
  415. {
  416. grabSucceeded = true;
  417. }
  418. /// <summary>
  419. /// Collects the position of the HMD with a timestamp, to be looked up later to correct for latency.
  420. /// </summary>
  421. public void CollectPose()
  422. {
  423. if (manager == null)
  424. return;
  425. KeyPose k = new KeyPose();
  426. InputTracking.GetNodeStates(nodeStates);
  427. XRNodeState nodeState = nodeStates.Find(node => node.nodeType == XRNode.Head);
  428. nodeState.TryGetRotation(out k.Orientation);
  429. nodeState.TryGetPosition(out k.Translation);
  430. if (manager.zedCamera.IsCameraReady)
  431. {
  432. k.Timestamp = manager.zedCamera.GetCurrentTimeStamp();
  433. if (k.Timestamp >= 0)
  434. {
  435. dllz_latency_corrector_add_key_pose(ref k.Translation, ref k.Orientation, k.Timestamp); //Poses are handled by the wrapper.
  436. }
  437. }
  438. }
  439. /// <summary>
  440. /// Returns a pose at a specific time.
  441. /// </summary>
  442. /// <param name="r">Rotation of the latency pose.</param>
  443. /// <param name="t">Translation/position of the latency pose.</param>
  444. /// <param name="cameraTimeStamp">Timestamp for looking up the pose.</param>
  445. /// <param name="useLatency">Whether to use latency.</param>
  446. public int LatencyCorrector(out Quaternion r, out Vector3 t, ulong cameraTimeStamp, bool useLatency)
  447. {
  448. return dllz_latency_corrector_get_transform(cameraTimeStamp, useLatency, out t, out r);
  449. }
  450. /// <summary>
  451. /// Sets the GameObject's 3D local scale based on a 2D resolution (Z scale is unchanged).
  452. /// Used for scaling quadLeft/quadRight.
  453. /// </summary>
  454. /// <param name="screen">Target GameObject to scale.</param>
  455. /// <param name="s">2D scale factor.</param>
  456. public void scale(GameObject screen, Vector2 s)
  457. {
  458. screen.transform.localScale = new Vector3(s.x, s.y, 1);
  459. }
  460. /// <summary>
  461. /// Set the planes/canvases to the proper position after accounting for latency.
  462. /// </summary>
  463. public void UpdateRenderPlane()
  464. {
  465. if (manager == null)
  466. return;
  467. if (!manager.IsStereoRig)
  468. return; //Make sure we're in pass-through AR mode.
  469. #if UNITY_2019_3_OR_NEWER
  470. List<InputDevice> eyes = new List<InputDevice>();
  471. InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, eyes);
  472. if (eyes.Count > 0) // if a headset is detected
  473. {
  474. var eye = eyes[0];
  475. eye.TryGetFeatureValue(CommonUsages.centerEyePosition, out Vector3 centerEyePosition);
  476. eye.TryGetFeatureValue(CommonUsages.centerEyeRotation, out Quaternion centerEyeRotation);
  477. finalCenterEye.transform.localPosition = centerEyePosition;
  478. finalCenterEye.transform.localRotation = centerEyeRotation;
  479. }
  480. #endif
  481. Quaternion r;
  482. //Modified code to ensure view in HMD does not play like a movie screen
  483. if (manager.inputType == sl.INPUT_TYPE.INPUT_TYPE_SVO || manager.inputType == sl.INPUT_TYPE.INPUT_TYPE_STREAM)
  484. {
  485. r = finalCameraCenter.transform.localRotation;
  486. }
  487. else
  488. {
  489. r = latencyPose.rotation;
  490. }
  491. // End of modified code
  492. //Plane's distance from the final camera never changes, but it's rotated around it based on the latency pose.
  493. quadCenter.localRotation = r;
  494. quadCenter.localPosition = finalCenterEye.transform.localPosition + r * (offset);
  495. }
  496. /// <summary>
  497. /// Initialize the ZED's tracking with the current HMD position and HMD-ZED calibration.
  498. /// This causes the ZED's internal tracking to start where the HMD is, despite being initialized later than the HMD.
  499. /// </summary>
  500. /// <returns>Initial offset for the ZED's tracking. </returns>
  501. public Pose InitTrackingAR()
  502. {
  503. if (manager == null)
  504. return new Pose();
  505. Transform tmpHMD = transform;
  506. InputTracking.GetNodeStates(nodeStates);
  507. XRNodeState nodeState = nodeStates.Find(node => node.nodeType == XRNode.Head);
  508. nodeState.TryGetRotation(out Quaternion rot);
  509. nodeState.TryGetPosition(out Vector3 pos);
  510. Pose hmdTransform = new Pose(pos, rot);
  511. Quaternion r = Quaternion.identity;
  512. Vector3 t = Vector3.zero;
  513. Pose const_offset = new Pose(t, r);
  514. dllz_drift_corrector_set_calibration_const_offset_transform(ref const_offset);
  515. zedCamera.ResetTrackingWithOffset(tmpHMD.rotation, tmpHMD.position, HmdToZEDCalibration.rotation, HmdToZEDCalibration.translation);
  516. return new Pose(tmpHMD.position, tmpHMD.rotation);
  517. }
  518. /// <summary>
  519. /// Sets latencyPose to the pose of the headset at a given timestamp and flags whether or not it's valid for use.
  520. /// </summary>
  521. /// <param name="cameraTimeStamp">Timestamp for looking up the pose.</param>
  522. public void ExtractLatencyPose(ulong cameraTimeStamp)
  523. {
  524. Quaternion latency_rot;
  525. Vector3 latency_pos;
  526. if (LatencyCorrector(out latency_rot, out latency_pos, cameraTimeStamp, true) == 1)
  527. {
  528. latencyPose = new Pose(latency_pos, latency_rot);
  529. latencyCorrectionReady = true;
  530. }
  531. else
  532. latencyCorrectionReady = false;
  533. }
  534. /// <summary>
  535. /// Returns the most recently retrieved latency pose.
  536. ///
  537. /// </summary>
  538. /// <returns>Last retrieved latency pose. </returns>
  539. public Pose LatencyPose()
  540. {
  541. return latencyPose;
  542. }
  543. /// <summary>
  544. /// Gets the proper position of the ZED virtual camera, factoring in HMD offset, latency, and anti-drift.
  545. /// Used by ZEDManager to set the pose of Camera_eyes in the 'intermediate' rig (ZED_Rig_Stereo).
  546. /// </summary>
  547. /// <param name="position">Current position as returned by the ZED's tracking.</param>
  548. /// <param name="orientation">Current rotation as returned by the ZED's tracking.</param>
  549. /// <param name="r">Final rotation.</param>
  550. /// <param name="t">Final translation/position.</param>
  551. public void AdjustTrackingAR(Vector3 position, Quaternion orientation, out Quaternion r, out Vector3 t, bool setimuprior)
  552. {
  553. hasVRDevice = hasXRDevice();
  554. InputTracking.GetNodeStates(nodeStates);
  555. XRNodeState nodeState = nodeStates.Find(node => node.nodeType == XRNode.Head);
  556. nodeState.TryGetRotation(out Quaternion rot);
  557. nodeState.TryGetPosition(out Vector3 pos);
  558. Pose hmdTransform = new Pose(pos, rot);
  559. trackingData.trackingState = (int)manager.ZEDTrackingState; //Whether the ZED's tracking is currently valid (not off or unable to localize).
  560. trackingData.zedPathTransform = new Pose(position, orientation);
  561. if (zedReady && latencyCorrectionReady && setimuprior == true)
  562. {
  563. zedCamera.SetIMUOrientationPrior(ref latencyPose.rotation);
  564. }
  565. dllz_drift_corrector_get_tracking_data(ref trackingData, ref hmdTransform, ref latencyPose, 0, true);
  566. r = trackingData.offsetZedWorldTransform.rotation;
  567. t = trackingData.offsetZedWorldTransform.translation;
  568. }
  569. /// <summary>
  570. /// Close related ZED processes when the application ends.
  571. /// </summary>
  572. private void OnApplicationQuit()
  573. {
  574. dllz_latency_corrector_shutdown();
  575. dllz_drift_corrector_shutdown();
  576. }
  577. /// <summary>
  578. /// Collects poses for latency correction, and updates the position of the rendering plane.
  579. /// Also assigns textures from 'intermediate' cameras to the final quads' materials if ready and not done yet.
  580. /// Called from ZEDManager.LateUpdate() so that it happens each frame after other tracking processes have finished.
  581. /// </summary>
  582. public void LateUpdateHmdRendering()
  583. {
  584. if (!ready) //Make sure intermediate cameras are rendering to the quad's materials.
  585. {
  586. if (leftScreen.target != null && leftScreen.target.IsCreated())
  587. {
  588. centerMaterial.SetTexture(mainTexLeftID, leftScreen.target);
  589. ready = true;
  590. }
  591. else ready = false;
  592. if (rightScreen.target != null && rightScreen.target.IsCreated())
  593. {
  594. centerMaterial.SetTexture(mainTexRightID, rightScreen.target);
  595. ready = true;
  596. }
  597. else ready = false;
  598. }
  599. if (hasVRDevice) //Do nothing if we no longer have a HMD connected.
  600. {
  601. CollectPose (); //File the current HMD pose into the latency poses to reference later.
  602. UpdateRenderPlane(); //Reposition the final quads based on the latency pose.
  603. }
  604. }
  605. #if ZED_HDRP || ZED_URP
  606. private void SRPStartFrame(ScriptableRenderContext context, Camera[] cams)
  607. {
  608. foreach(Camera cam in cams) {
  609. if (cam == finalCenterEye)
  610. {
  611. if ((!manager.IsZEDReady && manager.IsStereoRig))
  612. {
  613. System.Collections.Generic.List<XRNodeState> nodeStates = new System.Collections.Generic.List<XRNodeState>();
  614. InputTracking.GetNodeStates(nodeStates);
  615. XRNodeState nodeState = nodeStates.Find(node => node.nodeType == XRNode.Head);
  616. nodeState.TryGetRotation(out Quaternion rot);
  617. nodeState.TryGetPosition(out Vector3 pos);
  618. quadCenter.localRotation = rot;
  619. quadCenter.localPosition = pos + quadCenter.localRotation * offset;
  620. }
  621. }
  622. }
  623. }
  624. #else
  625. /// <summary>
  626. /// Before the ZED is ready, lock the quads in front of the cameras as latency correction isn't available yet.
  627. /// This allows us to see the loading messages (and other virtual objects if desired) while the ZED is still loading.
  628. /// Called by Camera.OnPreRender anytime any camera renders.
  629. /// </summary>
  630. /// <param name="cam">Cam.</param>
  631. public void PreRender(Camera cam)
  632. {
  633. if (cam == finalCenterEye)
  634. {
  635. if ((!manager.IsZEDReady && manager.IsStereoRig))
  636. {
  637. System.Collections.Generic.List<XRNodeState> nodeStates = new System.Collections.Generic.List<XRNodeState>();
  638. InputTracking.GetNodeStates(nodeStates);
  639. XRNodeState nodeState = nodeStates.Find(node => node.nodeType == XRNode.Head);
  640. nodeState.TryGetRotation(out Quaternion rot);
  641. nodeState.TryGetPosition(out Vector3 pos);
  642. quadCenter.localRotation = rot;
  643. quadCenter.localPosition = pos + quadCenter.localRotation * offset;
  644. }
  645. }
  646. }
  647. #endif
  648. /// <summary>
  649. /// Loads the HMD to ZED calibration file and applies it to the hmdtozedCalibration offset.
  650. /// Note that the file it loads is created using hard-coded values
  651. /// and the ZED plugin doesn't ever change it. See CreateDefaultCalibrationFile().
  652. /// </summary>
  653. public void LoadHmdToZEDCalibration()
  654. {
  655. if (hasVRDevice) {
  656. /// Default calibration (may be changed)
  657. hmdtozedCalibration.rotation = Quaternion.identity;
  658. hmdtozedCalibration.translation.x = 0.0315f;//-zedCamera.Baseline/2;
  659. hmdtozedCalibration.translation.y = 0.0f;
  660. hmdtozedCalibration.translation.z = 0.11f;
  661. //if a calibration exists then load it
  662. //should be in ProgramData/stereolabs/mr/calibration.ini
  663. string folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData);
  664. string specificFolder = Path.Combine(folder, @"Stereolabs\mr");
  665. calibrationFilePath = Path.Combine(specificFolder, calibrationFile);
  666. // Check if folder exists and if not, create it
  667. if (!Directory.Exists(specificFolder))
  668. {
  669. Directory.CreateDirectory(specificFolder);
  670. }
  671. // Check if file exist and if not, create a default one
  672. if (!ParseCalibrationFile(calibrationFilePath))
  673. CreateDefaultCalibrationFile(calibrationFilePath);
  674. // Set the calibration in mr processing
  675. dllz_drift_corrector_set_calibration_transform(ref hmdtozedCalibration);
  676. // Create a file system watcher for online modifications
  677. CreateFileWatcher(specificFolder);
  678. }
  679. }
  680. /// <summary>
  681. /// Creates a FileSystemEventHandler to watch the HMD-ZED calibration file and update settings if
  682. /// it changes during runtime. If it does, calls OnChanged to fix tracking.
  683. /// </summary>
  684. /// <param name="folder"></param>
  685. public void CreateFileWatcher(string folder)
  686. {
  687. // Create a new FileSystemWatcher and set its properties.
  688. FileSystemWatcher watcher = new FileSystemWatcher();
  689. watcher.Path = folder;
  690. /* Watch for changes in LastAccess and LastWrite times, and
  691. the renaming of files or directories. */
  692. watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
  693. | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  694. // Only watch text files.
  695. watcher.Filter = calibrationFile;
  696. // Add event handlers.
  697. watcher.Changed += new FileSystemEventHandler(OnChanged);
  698. // Begin watching.
  699. watcher.EnableRaisingEvents = true;
  700. }
  701. /// <summary>
  702. /// Reloads ZED-HMD offset calibration file and resets calibration accordintly.
  703. /// Also calls OnHmdCalibChanged() which ZEDManager uses to run additional reset logic.
  704. /// </summary>
  705. /// <param name="source"></param>
  706. /// <param name="e"></param>
  707. private void OnChanged(object source, FileSystemEventArgs e)
  708. {
  709. if (hasVRDevice)
  710. {
  711. ParseCalibrationFile(calibrationFilePath);
  712. dllz_drift_corrector_set_calibration_transform(ref hmdtozedCalibration);
  713. OnHmdCalibChanged();
  714. }
  715. }
  716. /// <summary>
  717. /// Creates and saves a text file with the default ZED-HMD offset calibration parameters, to be loaded anytime this class runs in the future.
  718. /// Values correspond to the distance from the center of the user's head to the ZED's left sensor.
  719. /// </summary>
  720. /// <param name="path">Path to save the file.</param>
  721. private void CreateDefaultCalibrationFile(string path)
  722. {
  723. //Default Calibration: DO NOT CHANGE.
  724. hmdtozedCalibration.rotation = Quaternion.identity;
  725. hmdtozedCalibration.translation.x = -0.0315f;
  726. hmdtozedCalibration.translation.y = 0.0f;
  727. hmdtozedCalibration.translation.z = 0.115f;
  728. //Write calibration file using default calibration.
  729. using (System.IO.StreamWriter file = new System.IO.StreamWriter(path))
  730. {
  731. string node = "[HMD]";
  732. string tx = "tx=" + hmdtozedCalibration.translation.x.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Translation x";
  733. string ty = "ty=" + hmdtozedCalibration.translation.y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Translation y";
  734. string tz = "tz=" + hmdtozedCalibration.translation.z.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Translation z";
  735. string rx = "rx=" + hmdtozedCalibration.rotation.x.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Quaternion x";
  736. string ry = "ry=" + hmdtozedCalibration.rotation.y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Quaternion y";
  737. string rz = "rz=" + hmdtozedCalibration.rotation.z.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Quaternion z";
  738. string rw = "rw=" + hmdtozedCalibration.rotation.w.ToString(System.Globalization.CultureInfo.InvariantCulture) + " //Quaternion w";
  739. file.WriteLine(node);
  740. file.WriteLine(tx);
  741. file.WriteLine(ty);
  742. file.WriteLine(tz);
  743. file.WriteLine(rx);
  744. file.WriteLine(ry);
  745. file.WriteLine(rz);
  746. file.WriteLine(rw);
  747. file.Close();
  748. }
  749. }
  750. /// <summary>
  751. /// Reads the ZED-HMD offset calibration file, if it exists, and loads calibration values to be applied to the final cameras.
  752. /// Values correspond to the distance from the center of the user's head to the ZED's left sensor.
  753. /// </summary>
  754. /// <param name="path">Path to save the file.</param>
  755. /// <returns>False if the file couldn't be loaded, whether empty, non-existant, etc.</returns>
  756. private bool ParseCalibrationFile(string path)
  757. {
  758. if (!System.IO.File.Exists(path)) return false;
  759. string[] lines = null;
  760. try
  761. {
  762. lines = System.IO.File.ReadAllLines(path);
  763. }
  764. catch (System.Exception)
  765. {
  766. return false;
  767. }
  768. if (lines.Length == 0)
  769. return false;
  770. //Default to these values (which are the same ones put in the calibration file by default).
  771. hmdtozedCalibration.rotation = Quaternion.identity;
  772. hmdtozedCalibration.translation.x = -0.0315f;
  773. hmdtozedCalibration.translation.y = 0.0f;
  774. hmdtozedCalibration.translation.z = 0.115f;
  775. foreach (string line in lines)
  776. {
  777. string[] splittedLine = line.Split('=');
  778. if (splittedLine != null && splittedLine.Length >= 2)
  779. {
  780. string key = splittedLine[0];
  781. string field = splittedLine[1].Split(' ')[0];
  782. if (key == "tx")
  783. {
  784. hmdtozedCalibration.translation.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  785. }
  786. else if (key == "ty")
  787. {
  788. hmdtozedCalibration.translation.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  789. }
  790. else if (key == "tz")
  791. {
  792. hmdtozedCalibration.translation.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  793. }
  794. else if (key == "rx")
  795. {
  796. hmdtozedCalibration.rotation.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  797. }
  798. else if (key == "ry")
  799. {
  800. hmdtozedCalibration.rotation.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  801. }
  802. else if (key == "rz")
  803. {
  804. hmdtozedCalibration.rotation.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  805. }
  806. else if (key == "rw")
  807. {
  808. hmdtozedCalibration.rotation.w = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture);
  809. }
  810. }
  811. }
  812. //Check if the calibration has values but they're all zeros.
  813. if (hmdtozedCalibration.translation.x == 0.0f && hmdtozedCalibration.translation.y == 0.0f && hmdtozedCalibration.translation.z == 0.0f)
  814. {
  815. CreateDefaultCalibrationFile(path);
  816. }
  817. return true;
  818. }
  819. }