ZEDSpatialMapping.cs 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653
  1. //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.Threading;
  5. using System.Text;
  6. using System;
  7. using System.Globalization;
  8. /// <summary>
  9. /// Processes the mesh taken from the ZED's Spatial Mapping feature so it can be used within Unity.
  10. /// Handles the real-time updates as well as the final processing.
  11. /// Note that ZEDSpatialMappingManager is more user-friendly/high-level, designed to hide the complexities of this class.
  12. /// </summary>
  13. public class ZEDSpatialMapping
  14. {
  15. /// <summary>
  16. /// Submesh created by ZEDSpatialMapping. The scan is made of multiple chunks.
  17. /// </summary>
  18. public struct Chunk
  19. {
  20. /// <summary>
  21. /// Reference to the GameObject that holds the MeshFilter.
  22. /// </summary>
  23. public GameObject o;
  24. /// <summary>
  25. /// Dynamic mesh data that will change throughout the spatial mapping.
  26. /// </summary>
  27. public ProceduralMesh proceduralMesh;
  28. /// <summary>
  29. /// Final mesh, assigned to once the spatial mapping is over and done processing.
  30. /// </summary>
  31. public Mesh mesh;
  32. }
  33. /// <summary>
  34. /// Structure to contain a temporary buffer that holds triangles and vertices.
  35. /// </summary>
  36. public struct ProceduralMesh
  37. {
  38. /// <summary>
  39. /// List of vertex indexes that make up triangles.
  40. /// </summary>
  41. public int[] triangles;
  42. /// <summary>
  43. /// List of vertices in the mesh.
  44. /// </summary>
  45. public Vector3[] vertices;
  46. /// <summary>
  47. /// MeshFilter of a GameObject that holds the chunk this ProceduralMesh represents.
  48. /// </summary>
  49. public MeshFilter mesh;
  50. };
  51. /// <summary>
  52. /// Spatial mapping depth resolution presets.
  53. /// </summary>
  54. public enum RESOLUTION
  55. {
  56. /// <summary>
  57. /// Create detailed geometry. Requires lots of memory.
  58. /// </summary>
  59. HIGH,
  60. /// <summary>
  61. /// Small variations in the geometry will disappear. Useful for large objects.
  62. /// </summary>
  63. ///
  64. MEDIUM,
  65. /// <summary>
  66. /// Keeps only large variations of the geometry. Useful for outdoors.
  67. /// </summary>
  68. LOW
  69. }
  70. /// <summary>
  71. /// Spatial mapping depth range presets.
  72. /// </summary>
  73. public enum RANGE
  74. {
  75. /// <summary>
  76. /// Geometry within 3.5 meters of the camera will be mapped.
  77. /// </summary>
  78. NEAR,
  79. /// <summary>
  80. /// Geometry within 5 meters of the camera will be mapped.
  81. /// </summary>
  82. MEDIUM,
  83. /// <summary>
  84. /// Objects as far as 10 meters away are mapped. Useful for outdoors.
  85. /// </summary>
  86. FAR
  87. }
  88. /// <summary>
  89. /// Current instance of the ZED Camera.
  90. /// </summary>
  91. private sl.ZEDCamera zedCamera;
  92. /// <summary>
  93. /// Instance of an internal helper class for low-level mesh processing.
  94. /// </summary>
  95. private ZEDSpatialMappingHelper spatialMappingHelper;
  96. /// <summary>
  97. /// Amount of filtering to apply to the mesh. Higher values result in lower face counts/memory usage, but also lower precision.
  98. /// </summary>
  99. public sl.FILTER filterParameters = sl.FILTER.MEDIUM;
  100. /// <summary>
  101. /// True when RequestSaveMesh has been called, so that ongoing threads know to stop and save the mesh
  102. /// when everything is finished processing.
  103. /// </summary>
  104. private bool saveRequested = false;
  105. /// <summary>
  106. /// Where the new mesh will be saved. Should end in .obj.
  107. /// If textured, a .mtl (material) file and .png file will appear in the same folder with the same base filename.
  108. /// </summary>
  109. private string savePath = "Assets/ZEDMesh.obj";
  110. #if UNITY_EDITOR
  111. /// <summary>
  112. /// Color of the wireframe mesh to be drawn in Unity's Scene window.
  113. /// </summary>
  114. private Color colorMesh = new Color(0.35f, 0.65f, 0.95f);
  115. #endif
  116. /// <summary>
  117. /// Offset for the triangles buffer, so that new triangles are copied into the dynamic mesh starting at the correct index.
  118. /// </summary>
  119. private int trianglesOffsetLastFrame;
  120. /// <summary>
  121. /// Offset for the vertices buffer, so that new vertices are copied into the dynamic mesh starting at the correct index.
  122. /// </summary>
  123. private int verticesOffsetLastFrame;
  124. /// <summary>
  125. /// Offset for the UVs buffer, so that new UV coordinates are copied into the dynamic mesh starting at the correct index.
  126. /// </summary>
  127. private int uvsOffsetLastFrame;
  128. /// <summary>
  129. /// Index of the mesh that was updated last frame.
  130. /// </summary>
  131. private int indexLastFrame;
  132. /// <summary>
  133. /// Flag set to true if there were meshes what weren't completely updated last frame due to lack of time.
  134. /// </summary>
  135. private bool remainMeshes = false;
  136. /// <summary>
  137. /// The user has requested to stop spatial mapping.
  138. /// </summary>
  139. private bool stopWanted = false;
  140. /// <summary>
  141. /// Whether the mesh is in the filtering stage of processing.
  142. /// </summary>
  143. private bool isFiltering = false;
  144. /// <summary>
  145. /// Whether the filtering stage of the mesh's processing has started and finished.
  146. /// </summary>
  147. private bool isFilteringOver = false;
  148. /// <summary>
  149. /// Whether the update thread will stop running.
  150. /// </summary>
  151. private bool stopRunning = false;
  152. /// <summary>
  153. /// Whether any part of spatial mapping is running. Set to true when scanning has started
  154. /// and set to false after the scanned mesh has finished bring filtered, textured, etc.
  155. /// </summary>
  156. private bool running = false;
  157. /// <summary>
  158. /// Flag that causes spatial mapping to pause when true. Use SwitchPauseState() to change.
  159. /// </summary>
  160. private bool pause = false;
  161. /// <summary>
  162. /// Returns true if spatial mapping has been paused. This can be set to true even if spatial mapping isn't running.
  163. /// </summary>
  164. public bool IsPaused
  165. {
  166. get { return pause; }
  167. }
  168. /// <summary>
  169. /// Whether scanned meshes are visible or not.
  170. /// </summary>
  171. public bool display = false;
  172. /// <summary>
  173. /// State of the scanning during its initialization. Used to know if it has started successfully.
  174. /// </summary>
  175. private sl.ERROR_CODE scanningInitState;
  176. /// <summary>
  177. /// Delegate for the OnMeshUpdate event, which is called every time a new chunk/submesh is processed.
  178. /// </summary>
  179. public delegate void OnNewMesh();
  180. /// <summary>
  181. /// Events called every time a new chunk/submesh has been processed. It's called many times during the scan.
  182. /// </summary>
  183. public event OnNewMesh OnMeshUpdate;
  184. /// <summary>
  185. /// Delegate for OnMeshReady, which is called when spatial mapping has finished.
  186. /// </summary>
  187. public delegate void OnSpatialMappingEnded();
  188. /// <summary>
  189. /// Event called when spatial mapping has finished.
  190. /// </summary>
  191. public event OnSpatialMappingEnded OnMeshReady;
  192. /// <summary>
  193. /// Delegate for OnMeshStarted, which is called when spatial mapping has started.
  194. /// </summary>
  195. public delegate void OnSpatialMappingStarted();
  196. /// <summary>
  197. /// Event called when spatial mapping has started.
  198. /// </summary>
  199. public event OnSpatialMappingStarted OnMeshStarted;
  200. /// <summary>
  201. /// GameObject to which every chunk of the mesh is parented. Represents the scanned mesh in Unity's Hierarchy.
  202. /// </summary>
  203. private GameObject holder = null;
  204. /**** Threading Variables ****/
  205. /// <summary>
  206. /// True if the mesh has been updated, and needs to be processed.
  207. /// </summary>
  208. private bool meshUpdated = false;
  209. /// <summary>
  210. /// True if the mesh update thread is running.
  211. /// </summary>
  212. private bool updateThreadRunning = false;
  213. /// <summary>
  214. /// Public accessor for whether the mesh update thread is running.
  215. /// </summary>
  216. public bool IsUpdateThreadRunning
  217. {
  218. get { return updateThreadRunning; }
  219. }
  220. /// <summary>
  221. /// True if the user has requested that spatial mapping start.
  222. /// </summary>
  223. private bool spatialMappingRequested = false;
  224. /// <summary>
  225. /// True if the real-world texture needs to be updated.
  226. /// This only happens after scanning is finished and if Texturing (isTextured) is enabled.
  227. /// </summary>
  228. private bool updateTexture = false;
  229. /// <summary>
  230. /// True if the real-world texture has been updated.
  231. /// </summary>
  232. private bool updatedTexture = false;
  233. /// <summary>
  234. /// Thread that retrieves the size of the submeshes.
  235. /// </summary>
  236. private Thread scanningThread;
  237. /// <summary>
  238. /// Thread that filters the mesh once scanning has finished.
  239. /// </summary>
  240. private Thread filterThread;
  241. /// <summary>
  242. /// Mutex for threaded spatial mapping.
  243. /// </summary>
  244. private object lockScanning = new object();
  245. /// <summary>
  246. /// Maximum time in milliseconds that can be spent processing retrieved meshes each frame. If time is exceeded, remaining meshes will be processed next frame.
  247. /// </summary>
  248. private const int MAX_TIME = 5;
  249. /// <summary>
  250. /// True if the thread that updates the real-world texture is running.
  251. /// </summary>
  252. private bool texturingRunning = false;
  253. /// <summary>
  254. /// Gravity direction vector relative to ZEDManager's orientation. Estimated after spatial mapping is finished.
  255. /// Note that this will always be empty if using the ZED Mini as gravity is determined from its IMU at start.
  256. /// </summary>
  257. public Vector3 gravityEstimation;
  258. /// <summary>
  259. /// Public accessor for texturingRunning, which is whether the thread that updates the real-world texture is running.
  260. /// </summary>
  261. public bool IsTexturingRunning
  262. {
  263. get
  264. {
  265. return texturingRunning;
  266. }
  267. }
  268. /// <summary>
  269. /// If true, the script will add MeshColliders to all scanned chunks to allow physics collisions.
  270. /// </summary>
  271. private bool hasColliders = true;
  272. /// <summary>
  273. /// True if texture from the real world should be applied to the mesh. If true, texture will be applied after scanning is finished.
  274. /// </summary>
  275. private bool isTextured = false;
  276. /// <summary>
  277. /// Flag to check if we have attached ZEDMeshRenderer components to the ZED rig camera objects.
  278. /// This is done in Update() if it hasn't been done yet.
  279. /// </summary>
  280. private bool setMeshRenderer = false;
  281. /// <summary>
  282. /// References to the ZEDMeshRenderer components attached to the ZED rig camera objects.
  283. /// [0] is the one attached to the left camera. [1] is the right camera, if it exists.
  284. /// </summary>
  285. private ZEDMeshRenderer[] meshRenderer = new ZEDMeshRenderer[2];
  286. /// <summary>
  287. /// The scene's ZEDManager component, usually attached to the ZED rig GameObject (ZED_Rig_Mono or ZED_Rig_Stereo).
  288. /// </summary>
  289. private ZEDManager zedManager;
  290. /// <summary>
  291. /// All chunks/submeshes with their indices. Only used while spatial mapping is running, as meshes are consolidated from
  292. /// many small meshes into fewer, larger meshes when finished. See ChunkList for final submeshes.
  293. /// </summary>
  294. public Dictionary<int, ZEDSpatialMapping.Chunk> Chunks
  295. {
  296. get { return spatialMappingHelper.chunks; }
  297. }
  298. /// <summary>
  299. /// List of the final mesh chunks created after scanning is finished. This is not filled beforehand because we use
  300. /// many small chunks during scanning, and consolidate them afterward. See Chunks for runtime submeshes.
  301. /// </summary>
  302. public List<ZEDSpatialMapping.Chunk> ChunkList = new List<ZEDSpatialMapping.Chunk>();
  303. /// <summary>
  304. /// Constructor. Spawns the holder GameObject to hold scanned chunks and the ZEDSpatialMappingHelper to handle low-level mesh processing.
  305. /// </summary>
  306. /// <param name="transform">Transform of the scene's ZEDSpatialMappingManager.</param>
  307. /// <param name="zedCamera">Reference to the ZEDCamera instance.</param>
  308. /// <param name="zedManager">The scene's ZEDManager component.</param>
  309. public ZEDSpatialMapping(Transform transform, ZEDManager zedManager)
  310. {
  311. //Instantiate the low-level mesh processing helper.
  312. spatialMappingHelper = new ZEDSpatialMappingHelper(zedManager.zedCamera, Resources.Load("Materials/SpatialMapping/Mat_ZED_Texture") as Material, Resources.Load("Materials/SpatialMapping/Mat_ZED_Geometry_Wireframe") as Material);
  313. //Assign basic values.
  314. this.zedCamera = zedManager.zedCamera;
  315. this.zedManager = zedManager;
  316. scanningInitState = sl.ERROR_CODE.FAILURE;
  317. }
  318. /// <summary>
  319. /// Begins the spatial mapping process. This is called when you press the "Start Spatial Mapping" button in the Inspector.
  320. /// </summary>
  321. /// <param name="resolutionPreset">Resolution setting - how detailed the mesh should be at scan time.</param>
  322. /// <param name="rangePreset">Range setting - how close geometry must be to be scanned.</param>
  323. /// <param name="isTextured">Whether to scan texture, or only the geometry.</param>
  324. public void StartStatialMapping(sl.SPATIAL_MAP_TYPE type, RESOLUTION resolutionPreset, RANGE rangePreset, bool isTextured)
  325. {
  326. //Create the Holder object, to which all scanned chunks will be parented.
  327. holder = new GameObject();
  328. holder.name = "[ZED Mesh Holder (" + zedManager.name + ")]";
  329. holder.transform.position = Vector3.zero;
  330. holder.transform.rotation = Quaternion.identity;
  331. StaticBatchingUtility.Combine(holder);
  332. holder.transform.position = Vector3.zero;
  333. holder.transform.rotation = Quaternion.identity;
  334. spatialMappingRequested = true;
  335. if (spatialMappingRequested && scanningInitState != sl.ERROR_CODE.SUCCESS)
  336. {
  337. scanningInitState = EnableSpatialMapping(type, resolutionPreset, rangePreset, isTextured);
  338. }
  339. zedManager.gravityRotation = Quaternion.identity;
  340. pause = false; //Make sure the scanning doesn't start paused because it was left paused at the last scan.
  341. }
  342. /// <summary>
  343. /// Initializes flags used during scan, tells ZEDSpatialMappingHelper to activate the ZED SDK's scanning, and
  344. /// starts the thread that updates the in-game chunks with data from the ZED SDK.
  345. /// </summary>
  346. /// <param name="resolutionPreset">Resolution setting - how detailed the mesh should be at scan time.</param>
  347. /// <param name="rangePreset">Range setting - how close geometry must be to be scanned.</param>
  348. /// <param name="isTextured">Whether to scan texture, or only the geometry.</param>
  349. /// <returns></returns>
  350. private sl.ERROR_CODE EnableSpatialMapping(sl.SPATIAL_MAP_TYPE type,RESOLUTION resolutionPreset, RANGE rangePreset, bool isTextured)
  351. {
  352. sl.ERROR_CODE error;
  353. this.isTextured = isTextured;
  354. //Tell the helper to start scanning. This call gets passed directly to the wrapper call in ZEDCamera.
  355. error = spatialMappingHelper.EnableSpatialMapping(type,ZEDSpatialMappingHelper.ConvertResolutionPreset(resolutionPreset), ZEDSpatialMappingHelper.ConvertRangePreset(rangePreset), isTextured);
  356. if (meshRenderer[0]) meshRenderer[0].isTextured = isTextured;
  357. if (meshRenderer[1]) meshRenderer[1].isTextured = isTextured;
  358. stopWanted = false;
  359. running = true;
  360. if (error == sl.ERROR_CODE.SUCCESS) //If the scan was started successfully.
  361. {
  362. //Set default flag settings.
  363. display = true;
  364. meshUpdated = false;
  365. spatialMappingRequested = false;
  366. updateTexture = false;
  367. updatedTexture = false;
  368. //Clear all previous meshes.
  369. ClearMeshes();
  370. //Request the first mesh update. Later, this will get called continuously after each update is applied.
  371. zedCamera.RequestMesh();
  372. //Launch the thread to retrieve the chunks and their sizes from the ZED SDK.
  373. scanningThread = new Thread(UpdateMesh);
  374. updateThreadRunning = true;
  375. if (OnMeshStarted != null)
  376. {
  377. OnMeshStarted(); //Invoke the event for other scripts, like ZEDMeshRenderer.
  378. }
  379. scanningThread.Start();
  380. }
  381. return error;
  382. }
  383. /// <summary>
  384. /// Attach a new ZEDMeshRenderer to the ZED rig cameras. This is necessary to see the mesh.
  385. /// </summary>
  386. public void SetMeshRenderer()
  387. {
  388. if (!setMeshRenderer) //Make sure we haven't do this yet.
  389. {
  390. if (zedManager != null)
  391. {
  392. Transform left = zedManager.GetLeftCameraTransform(); //Find the left camera. This exists in both ZED_Rig_Mono and ZED_Rig_Stereo.
  393. if (left != null)
  394. {
  395. meshRenderer[0] = left.gameObject.GetComponent<ZEDMeshRenderer>();
  396. if (!meshRenderer[0])
  397. {
  398. meshRenderer[0] = left.gameObject.AddComponent<ZEDMeshRenderer>();
  399. }
  400. meshRenderer[0].Create();
  401. }
  402. Transform right = zedManager.GetRightCameraTransform(); //Find the right camera. This only exists in ZED_Rig_Stereo or a similar stereo rig.
  403. if (right != null)
  404. {
  405. meshRenderer[1] = right.gameObject.GetComponent<ZEDMeshRenderer>();
  406. if (!meshRenderer[1])
  407. {
  408. meshRenderer[1] = right.gameObject.AddComponent<ZEDMeshRenderer>();
  409. }
  410. meshRenderer[1].Create();
  411. }
  412. setMeshRenderer = true;
  413. }
  414. }
  415. }
  416. /// <summary>
  417. /// Updates the current mesh, if scanning, and manages the start and stop states.
  418. /// </summary>
  419. public void Update()
  420. {
  421. SetMeshRenderer(); //Make sure we have ZEDMeshRenderers on the cameras, so we can see the mesh.
  422. if (meshUpdated || remainMeshes)
  423. {
  424. UpdateMeshMainthread();
  425. meshUpdated = false;
  426. }
  427. if (stopWanted && !remainMeshes)
  428. {
  429. stopRunning = true;
  430. stopWanted = false;
  431. Stop();
  432. }
  433. //If it's time to stop the scan, disable the spatial mapping and store the gravity estimation in ZEDManager.
  434. if (stopRunning && !isFiltering && isFilteringOver)
  435. {
  436. isFilteringOver = false;
  437. UpdateMeshMainthread(false);
  438. Thread disabling = new Thread(DisableSpatialMapping);
  439. disabling.Start();
  440. if (hasColliders)
  441. {
  442. if (!zedManager.IsStereoRig && gravityEstimation != Vector3.zero && zedManager.transform.parent != null)
  443. {
  444. Quaternion rotationToApplyForGravity = Quaternion.Inverse(Quaternion.FromToRotation(Vector3.up, -gravityEstimation.normalized));
  445. holder.transform.localRotation = rotationToApplyForGravity;
  446. zedManager.gravityRotation = rotationToApplyForGravity;
  447. }
  448. UpdateMeshCollider();
  449. }
  450. else
  451. {
  452. running = false;
  453. }
  454. stopRunning = false;
  455. }
  456. }
  457. /// <summary>
  458. /// Gets the mesh data from the ZED SDK and stores it for later update in the Unity mesh.
  459. /// </summary>
  460. private void UpdateMesh()
  461. {
  462. while (updateThreadRunning)
  463. {
  464. if (!remainMeshes) //If we don't have leftover meshes to apply from the last update.
  465. {
  466. lock (lockScanning)
  467. {
  468. if (meshUpdated == false && updateTexture) //If we need to update the texture, prioritize that.
  469. {
  470. //Get the last size of the mesh and get the texture size.
  471. spatialMappingHelper.ApplyTexture();
  472. meshUpdated = true;
  473. updateTexture = false;
  474. updatedTexture = true;
  475. updateThreadRunning = false;
  476. }
  477. else if (zedCamera.GetMeshRequestStatus() == sl.ERROR_CODE.SUCCESS && !pause && meshUpdated == false)
  478. {
  479. spatialMappingHelper.UpdateMesh(); //Tells the ZED SDK to update its internal mesh.
  480. spatialMappingHelper.RetrieveMesh(); //Applies the ZED SDK's internal mesh to values inside spatialMappingHelper.
  481. meshUpdated = true;
  482. }
  483. }
  484. //Time to process all the meshes spread on multiple frames.
  485. Thread.Sleep(5);
  486. }
  487. else //If there are meshes that were collected but not processed yet. Happens if the last update took too long to process.
  488. {
  489. //Check every 5ms if the meshes are done being processed.
  490. Thread.Sleep(5);
  491. }
  492. }
  493. }
  494. /// <summary>
  495. /// Destroys all submeshes.
  496. /// </summary>
  497. private void ClearMeshes()
  498. {
  499. if (holder != null)
  500. {
  501. foreach (Transform child in holder.transform)
  502. {
  503. GameObject.Destroy(child.gameObject);
  504. }
  505. spatialMappingHelper.Clear();
  506. }
  507. }
  508. /// <summary>
  509. /// Measures time since the provided start time. Used in UpdateMeshMainthread() to check if computational time for mesh updates
  510. /// has exceeded the MAX_TIME time limit (usually 5ms), so that it can hold off processing remaining meshes until the next frame.
  511. /// </summary>
  512. /// <param name="startTimeMS">Time.realtimeSinceStartup value when the process began.</param>
  513. /// <returns><c>True</c> if more than MAX_TIME has elapsed since startTimeMS.</returns>
  514. private bool GoneOverTimeBudget(int startTimeMS)
  515. {
  516. return (Time.realtimeSinceStartup * 1000) - startTimeMS > MAX_TIME;
  517. }
  518. /// <summary>
  519. /// Update the Unity mesh with the last data retrieved from the ZED, creating a new submesh if needed.
  520. /// Also launches the OnMeshUpdate event when the update is finished.
  521. /// <param name="spreadUpdateOverTime">If <c>true</c>, caps time spent on updating meshes each frame, leaving 'leftover' meshes for the next frame.</param>
  522. /// </summary>
  523. private void UpdateMeshMainthread(bool spreadUpdateOverTime = true)
  524. {
  525. //Cache the start time so we can measure how long this function is taking.
  526. //We'll check when updating the submeshes so that if it takes too long, we'll stop updating until the next frame.
  527. int startTimeMS = (int)(Time.realtimeSinceStartup * 1000);
  528. int indexUpdate = 0;
  529. lock (lockScanning) //Don't update if another thread is accessing.
  530. {
  531. if (updatedTexture)
  532. {
  533. spreadUpdateOverTime = false;
  534. }
  535. //Set the offset of the buffers to the offset of the last frame.
  536. int verticesOffset = 0, trianglesOffset = 0, uvsOffset = 0;
  537. if (remainMeshes && spreadUpdateOverTime)
  538. {
  539. verticesOffset = verticesOffsetLastFrame;
  540. trianglesOffset = trianglesOffsetLastFrame;
  541. uvsOffset = uvsOffsetLastFrame;
  542. indexUpdate = indexLastFrame;
  543. }
  544. //Clear all existing meshes and process the last ones.
  545. if (updatedTexture)
  546. {
  547. ClearMeshes();
  548. spatialMappingHelper.SetMeshAndTexture();
  549. if (meshRenderer[0]) meshRenderer[0].isTextured = isTextured;
  550. if (meshRenderer[1]) meshRenderer[1].isTextured = isTextured;
  551. }
  552. //Process the last meshes.
  553. for (; indexUpdate < spatialMappingHelper.NumberUpdatedSubMesh; indexUpdate++)
  554. {
  555. spatialMappingHelper.SetMesh(indexUpdate, ref verticesOffset, ref trianglesOffset, ref uvsOffset, holder.transform, updatedTexture);
  556. if (spreadUpdateOverTime && GoneOverTimeBudget(startTimeMS)) //Check if it's taken too long this frame.
  557. {
  558. remainMeshes = true; //It has. Set this flag so we know to pick up where we left off next frame.
  559. break;
  560. }
  561. }
  562. if (spreadUpdateOverTime)
  563. {
  564. indexLastFrame = indexUpdate;
  565. }
  566. else
  567. {
  568. indexLastFrame = 0;
  569. }
  570. //If all the meshes have been updated, reset values used to process 'leftover' meshes and get more data from the ZED.
  571. if ((indexUpdate == spatialMappingHelper.NumberUpdatedSubMesh) || spatialMappingHelper.NumberUpdatedSubMesh == 0)
  572. {
  573. verticesOffsetLastFrame = 0;
  574. trianglesOffsetLastFrame = 0;
  575. uvsOffsetLastFrame = 0;
  576. indexLastFrame = 0;
  577. remainMeshes = false;
  578. meshUpdated = false;
  579. zedCamera.RequestMesh();
  580. }
  581. //If some meshes still need updating, we'll save the offsets so we know where to start next frame.
  582. else if (indexUpdate != spatialMappingHelper.NumberUpdatedSubMesh)
  583. {
  584. remainMeshes = true;
  585. indexLastFrame = indexUpdate + 1;
  586. verticesOffsetLastFrame = verticesOffset;
  587. trianglesOffsetLastFrame = trianglesOffset;
  588. uvsOffsetLastFrame = uvsOffset;
  589. }
  590. //Save the mesh here if we requested it to be saved, as we just updated the meshes, including textures, if applicable.
  591. if (saveRequested && remainMeshes == false)
  592. {
  593. if (!isTextured || updatedTexture)
  594. {
  595. SaveMeshNow(savePath);
  596. saveRequested = false;
  597. }
  598. }
  599. }
  600. if (OnMeshUpdate != null)
  601. {
  602. OnMeshUpdate(); //Call the event if it has at least one listener.
  603. }
  604. //The texture update is done in one pass, so this is only called once after the mesh has stopped scanning.
  605. if (updatedTexture)
  606. {
  607. DisableSpatialMapping();
  608. updatedTexture = false;
  609. running = false;
  610. texturingRunning = false;
  611. if (hasColliders)
  612. {
  613. UpdateMeshCollider();
  614. }
  615. else
  616. {
  617. running = false;
  618. }
  619. }
  620. }
  621. public void ClearAllMeshes()
  622. {
  623. GameObject[] gos = GameObject.FindObjectsOfType<GameObject>() as GameObject[];
  624. spatialMappingHelper.Clear();
  625. for (int i = 0; i < gos.Length; i++)
  626. {
  627. string targetName = "[ZED Mesh Holder (" + zedManager.name + ")]";
  628. if (gos[i] != null && gos[i].name.Contains(targetName))
  629. {
  630. GameObject.Destroy(gos[i]);
  631. }
  632. }
  633. }
  634. /// <summary>
  635. /// Changes the visibility state of the meshes.
  636. /// This is what's called when the Hide/Display Mesh button is clicked in the Inspector.
  637. /// </summary>
  638. /// <param name="newDisplayState"> If true, the mesh will be displayed, else it will be hide. </param>
  639. public void SwitchDisplayMeshState(bool newDisplayState)
  640. {
  641. display = newDisplayState;
  642. }
  643. /// <summary>
  644. /// Pauses or resumes spatial mapping. If the spatial mapping is not enabled, nothing will happen.
  645. /// </summary>
  646. /// <param name="newPauseState"> If true, the spatial mapping will be paused, else it will be resumed. </param>
  647. public void SwitchPauseState(bool newPauseState)
  648. {
  649. pause = newPauseState;
  650. zedCamera.PauseSpatialMapping(newPauseState);
  651. }
  652. /// <summary>
  653. /// Update the mesh collider with the current mesh so it can handle physics.
  654. /// Calling it is slow, so it's only called after a scan is finished (or loaded).
  655. /// </summary>
  656. public void UpdateMeshCollider(bool timeSlicing = false)
  657. {
  658. ChunkList.Clear();
  659. foreach (var submesh in Chunks)
  660. {
  661. ChunkList.Add(submesh.Value);
  662. }
  663. lock (lockScanning)
  664. {
  665. spatialMappingHelper.UpdateMeshCollider(ChunkList);
  666. }
  667. if (OnMeshReady != null)
  668. {
  669. OnMeshReady();
  670. }
  671. running = false;
  672. }
  673. /// <summary>
  674. /// Properly clears existing scan data when the application is closed.
  675. /// Called by OnApplicationQuit() when the application closes.
  676. /// </summary>
  677. public void Dispose()
  678. {
  679. if (scanningThread != null)
  680. {
  681. updateThreadRunning = false;
  682. scanningThread.Join();
  683. }
  684. ClearMeshes();
  685. GameObject.Destroy(holder);
  686. DisableSpatialMapping();
  687. }
  688. /// <summary>
  689. /// Disable the ZED's spatial mapping. The mesh will no longer be updated, but it is not deleted.
  690. /// This gets called in Update() if the user requested a stop, and will execute once the scanning thread is free.
  691. /// </summary>
  692. private void DisableSpatialMapping()
  693. {
  694. lock (lockScanning)
  695. {
  696. updateThreadRunning = false;
  697. spatialMappingHelper.DisableSpatialMapping();
  698. scanningInitState = sl.ERROR_CODE.FAILURE;
  699. spatialMappingRequested = false;
  700. }
  701. }
  702. /// <summary>
  703. /// Save the mesh as an .obj file, and the area database as an .area file.
  704. /// This can be quite time-comsuming if you mapped a large area.
  705. /// </summary>
  706. public void RequestSaveMesh(string meshFilePath = "Assets/ZEDMesh.obj")
  707. {
  708. saveRequested = true;
  709. savePath = meshFilePath;
  710. if (updateThreadRunning)
  711. {
  712. StopStatialMapping(); //Stop the mapping if it hasn't stopped already.
  713. }
  714. }
  715. /// <summary>
  716. /// Loads the mesh and the corresponding area file if it exists. It can be quite time-comsuming if you mapped a large area.
  717. /// Note that if there are no .area files found in the same folder, the mesh will not be loaded either.
  718. /// Loading a mesh this way also loads relevant data into buffers, so it's as if a scan was just finished
  719. /// rather than a mesh asset being dropped into Unity.
  720. /// <returns><c>True</c> if loaded successfully, otherwise <c>flase</c>.</returns>
  721. /// </summary>
  722. public bool LoadMesh(string meshFilePath = "ZEDMesh.obj")
  723. {
  724. if (holder == null)
  725. {
  726. holder = new GameObject();
  727. holder.name = "[ZED Mesh Holder (" + zedManager.name + ")]";
  728. holder.transform.position = Vector3.zero;
  729. holder.transform.rotation = Quaternion.identity;
  730. StaticBatchingUtility.Combine(holder);
  731. }
  732. if (OnMeshStarted != null)
  733. {
  734. OnMeshStarted();
  735. }
  736. //If spatial mapping has started, disable it.
  737. DisableSpatialMapping();
  738. //Find and load the area
  739. string basePath = meshFilePath.Substring(0, meshFilePath.LastIndexOf("."));
  740. if (!System.IO.File.Exists(basePath + ".area"))
  741. {
  742. Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.TRACKING_BASE_AREA_NOT_FOUND));
  743. }
  744. zedCamera.DisableTracking();
  745. Quaternion quat = Quaternion.identity; Vector3 tr = Vector3.zero;
  746. if (zedCamera.EnableTracking(ref quat, ref tr, true, false, false, false, true, System.IO.File.Exists(basePath + ".area") ? basePath + ".area" : "") != sl.ERROR_CODE.SUCCESS)
  747. {
  748. Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.TRACKING_NOT_INITIALIZED));
  749. }
  750. updateTexture = false;
  751. updatedTexture = false;
  752. bool meshUpdatedLoad = false;
  753. lock (lockScanning)
  754. {
  755. ClearMeshes();
  756. meshUpdatedLoad = spatialMappingHelper.LoadMesh(meshFilePath);
  757. if (meshUpdatedLoad)
  758. {
  759. //Checks if a texture exists.
  760. if (spatialMappingHelper.GetWidthTexture() != -1)
  761. {
  762. updateTexture = true;
  763. updatedTexture = true;
  764. }
  765. //Retrieves the mesh sizes to be updated in the Unity's buffer later.
  766. if (!updateTexture)
  767. {
  768. spatialMappingHelper.RetrieveMesh();
  769. }
  770. }
  771. }
  772. if (meshUpdatedLoad)
  773. {
  774. //Update the buffer on Unity's side.
  775. UpdateMeshMainthread(false);
  776. //Add colliders and scan for gravity.
  777. if (hasColliders)
  778. {
  779. if (!zedManager.IsStereoRig && gravityEstimation != Vector3.zero)
  780. {
  781. Quaternion rotationToApplyForGravity = Quaternion.Inverse(Quaternion.FromToRotation(Vector3.up, -gravityEstimation.normalized));
  782. holder.transform.rotation = rotationToApplyForGravity;
  783. zedManager.gravityRotation = rotationToApplyForGravity;
  784. }
  785. UpdateMeshCollider();
  786. foreach (Chunk c in ChunkList)
  787. {
  788. c.o.transform.localRotation = Quaternion.identity;
  789. }
  790. }
  791. if (OnMeshReady != null)
  792. {
  793. OnMeshReady(); //Call the event if it has at least one listener.
  794. }
  795. if (meshRenderer[0]) meshRenderer[0].UpdateRenderingPlane(true);
  796. if (meshRenderer[1]) meshRenderer[1].UpdateRenderingPlane(true);
  797. return true;
  798. }
  799. return false;
  800. }
  801. /// <summary>
  802. /// Filters the mesh with the current filtering parameters.
  803. /// This reduces the total number of faces. More filtering means fewer faces.
  804. /// </summary>
  805. public void FilterMesh()
  806. {
  807. lock (lockScanning) //Wait for the thread to be available.
  808. {
  809. spatialMappingHelper.FilterMesh(filterParameters);
  810. spatialMappingHelper.ResizeMesh();
  811. spatialMappingHelper.RetrieveMesh();
  812. meshUpdated = true;
  813. }
  814. }
  815. /// <summary>
  816. /// Begin mesh filtering, and consolidate chunks into a reasonably low number when finished.
  817. /// </summary>
  818. /// <param name="filter"></param>
  819. void PostProcessMesh(bool filter = true)
  820. {
  821. isFiltering = true;
  822. if (filter)
  823. {
  824. FilterMesh();
  825. }
  826. MergeChunks();
  827. }
  828. /// <summary>
  829. /// Consolidates meshes to get fewer chunks - one for every MAX_SUBMESH vertices. Then applies to
  830. /// actual meshes in Unity.
  831. /// </summary>
  832. public void MergeChunks()
  833. {
  834. lock (lockScanning)
  835. {
  836. spatialMappingHelper.MergeChunks();
  837. spatialMappingHelper.ResizeMesh();
  838. spatialMappingHelper.RetrieveMesh();
  839. meshUpdated = true;
  840. }
  841. isFiltering = false;
  842. isFilteringOver = true;
  843. }
  844. /// <summary>
  845. /// Multi-threaded component of ApplyTexture(). Filters, then updates the mesh once, but as
  846. /// updateTexture is set to true when this is called, UpdateMesh() will also handle applying the texture.
  847. /// </summary>
  848. void ApplyTextureThreaded()
  849. {
  850. FilterMesh();
  851. UpdateMesh();
  852. }
  853. /// <summary>
  854. /// Stops the spatial mapping and begins the final processing, including adding texture.
  855. /// </summary>
  856. public bool ApplyTexture()
  857. {
  858. updateTexture = true;
  859. if (updateThreadRunning)
  860. {
  861. updateThreadRunning = false;
  862. scanningThread.Join();
  863. }
  864. scanningThread = new Thread(ApplyTextureThreaded);
  865. updateThreadRunning = true;
  866. scanningThread.Start();
  867. texturingRunning = true;
  868. return true;
  869. }
  870. /// <summary>
  871. /// Stop the spatial mapping and calls appropriate functions to process the final mesh.
  872. /// </summary>
  873. private void Stop()
  874. {
  875. gravityEstimation = zedCamera.GetGravityEstimate();
  876. if (isTextured)
  877. {
  878. ApplyTexture();
  879. }
  880. else
  881. {
  882. stopRunning = false;
  883. if (updateThreadRunning)
  884. {
  885. updateThreadRunning = false;
  886. scanningThread.Join();
  887. }
  888. ClearMeshes();
  889. PostProcessMesh(true);
  890. //filterThread = new Thread(() => PostProcessMesh(true));
  891. //filterThread.Start();
  892. stopRunning = true;
  893. }
  894. SwitchDisplayMeshState(true); //Make it default to visible.
  895. }
  896. /// <summary>
  897. /// Returns true from the moment a scan has started until the post-process is finished.
  898. /// </summary>
  899. /// <returns></returns>
  900. public bool IsRunning()
  901. {
  902. return running;
  903. }
  904. /// <summary>
  905. /// Sets a flag that will cause spatial mapping to stop at the next Update() call after all meshes already retrieved from the ZED are applied.
  906. /// </summary>
  907. public void StopStatialMapping()
  908. {
  909. stopWanted = true;
  910. }
  911. /// <summary>
  912. /// Combines the meshes from all the current chunks and saves them into a single mesh. If textured,
  913. /// will also save a .mtl file and .png file.
  914. /// This must only be called once all the chunks are completely finalized, or else they won't be filtered
  915. /// or have their UVs set.
  916. /// Called after RequestSaveMesh has been called after the main thread has the chance to stop the scan
  917. /// and finalize everything.
  918. /// </summary>
  919. /// <param name="meshFilePath">Where the mesh, material, and texture files will be saved.</param>
  920. private void SaveMeshNow(string meshFilePath = "Assets/ZEDMesh.obj")
  921. {
  922. // Make sure we are in invariant culture to get . notation for decimals.
  923. CultureInfo oldCulture = Thread.CurrentThread.CurrentCulture; // Save the old culture to set it back once we are done
  924. Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
  925. //Make sure the destination file ends in .obj - only .obj file format is supported.
  926. string extension = meshFilePath.Substring(meshFilePath.Length - 4);
  927. if (extension.ToLower() != ".obj")
  928. {
  929. Debug.LogError("Couldn't save to " + meshFilePath + ": Must save as .obj.");
  930. }
  931. lock (lockScanning)
  932. {
  933. Debug.Log("Saving mesh to " + meshFilePath);
  934. //Count how many vertices and triangles are in all the chunk meshes so we know how large of an array to allocate.
  935. int vertcount = 0;
  936. int tricount = 0;
  937. foreach (Chunk chunk in Chunks.Values)
  938. {
  939. vertcount += chunk.mesh.vertices.Length;
  940. tricount += chunk.mesh.triangles.Length;
  941. }
  942. if (vertcount == 0) return;
  943. Vector3[] vertices = new Vector3[vertcount];
  944. Vector2[] uvs = new Vector2[vertcount];
  945. Vector3[] normals = new Vector3[vertcount];
  946. int[] triangles = new int[tricount];
  947. int vertssofar = 0; //We keep an ongoing tally of how many verts/tris we've put so far so as to increment
  948. int trissofar = 0; //where we copy to in the arrays, and also to increment the vertex indices in the triangle array.
  949. for (int i = 0; i < Chunks.Keys.Count; i++)
  950. {
  951. Mesh chunkmesh = Chunks[i].mesh; //Shorthand.
  952. Array.Copy(chunkmesh.vertices, 0, vertices, vertssofar, chunkmesh.vertices.Length);
  953. Array.Copy(chunkmesh.uv, 0, uvs, vertssofar, chunkmesh.uv.Length);
  954. chunkmesh.RecalculateNormals();
  955. Array.Copy(chunkmesh.normals, 0, normals, vertssofar, chunkmesh.normals.Length);
  956. Array.Copy(chunkmesh.triangles, 0, triangles, trissofar, chunkmesh.triangles.Length);
  957. for (int t = trissofar; t < trissofar + chunkmesh.triangles.Length; t++)
  958. {
  959. triangles[t] += vertssofar;
  960. }
  961. vertssofar += chunkmesh.vertices.Length;
  962. trissofar += chunkmesh.triangles.Length;
  963. }
  964. Material savemat = Chunks[0].o.GetComponent<MeshRenderer>().material; //All chunks share the same material.
  965. //We'll need to know the base file name for this and the .mtl file. We'll extract it.
  966. //Since both forward and backslashes are valid for the file pack, determine which they used last.
  967. int forwardindex = meshFilePath.LastIndexOf('/');
  968. int backindex = meshFilePath.LastIndexOf('\\');
  969. int slashindex = forwardindex > backindex ? forwardindex : backindex;
  970. string basefilename = meshFilePath.Substring(slashindex + 1, meshFilePath.LastIndexOf(".") - slashindex - 1);
  971. //Create the string file.
  972. //Importantly, we flip the X value (and reverse the triangles) since the scanning module uses a different handedness than Unity.
  973. StringBuilder objstring = new StringBuilder();
  974. objstring.Append("# Generated by the ZED SDK.\n");
  975. if (isTextured)
  976. objstring.Append("mtllib " + basefilename + ".mtl"+ "\n");
  977. foreach (Vector3 vec in vertices)
  978. {
  979. //X is flipped because of Unity's handedness.
  980. objstring.Append(string.Format("v {0} {1} {2}\n", -vec.x, vec.y, vec.z));
  981. }
  982. objstring.Append("\n");
  983. foreach (Vector2 uv in uvs)
  984. {
  985. objstring.Append(string.Format("vt {0} {1}\n", uv.x, uv.y));
  986. }
  987. objstring.Append("\n");
  988. foreach (Vector3 norm in normals)
  989. {
  990. objstring.Append(string.Format("vn {0} {1} {2}\n", -norm.x, norm.y, norm.z));
  991. }
  992. //objstring.Append("\n");
  993. if (isTextured)
  994. {
  995. objstring.Append("usemtl ").Append("material0000").Append("\n");
  996. //objstring.Append("usemap ").Append(basefilename).Append("\n");
  997. }
  998. for (int i = 0; i < triangles.Length; i += 3)
  999. {
  1000. //Triangles are reversed so that surface normals face the right way after the X vertex flip.
  1001. objstring.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
  1002. triangles[i + 2] + 1, triangles[i + 1] + 1, triangles[i + 0] + 1));
  1003. }
  1004. System.IO.StreamWriter swriter = new System.IO.StreamWriter(meshFilePath);
  1005. swriter.Write(objstring.ToString());
  1006. swriter.Close();
  1007. //Create a texture and .mtl file for your scan, if textured.
  1008. if (isTextured)
  1009. {
  1010. //First, the texture.
  1011. //You can't save a Texture2D directly to a file since it's stored on the GPU.
  1012. //So we use a RenderTexture as a buffer, which we can read into a new Texture2D on the CPU-side.
  1013. Texture textosave = savemat.mainTexture;
  1014. RenderTexture buffertex = new RenderTexture(textosave.width, textosave.height, 0);
  1015. Graphics.Blit(textosave, buffertex, savemat);
  1016. RenderTexture oldactivert = RenderTexture.active;
  1017. RenderTexture.active = buffertex;
  1018. Texture2D texcopy = new Texture2D(textosave.width, textosave.height);
  1019. texcopy.ReadPixels(new Rect(0, 0, buffertex.width, buffertex.height), 0, 0);
  1020. texcopy.Apply(); //It's now on the CPU!
  1021. byte[] imagebytes = texcopy.EncodeToPNG();
  1022. string imagepath = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + "_material0000_map_Kd.png";
  1023. System.IO.File.WriteAllBytes(imagepath, imagebytes);
  1024. RenderTexture.active = oldactivert;
  1025. //Now the material file.
  1026. StringBuilder mtlstring = new StringBuilder();
  1027. mtlstring.Append("newmtl " + "material0000" + "\n");
  1028. mtlstring.Append("Ka 1.000000 1.000000 1.000000\n");
  1029. mtlstring.Append("Kd 1.000000 1.000000 1.000000\n");
  1030. mtlstring.Append("Ks 0.000000 0.000000 0.000000\n");
  1031. mtlstring.Append("Tr 1.000000\n");
  1032. mtlstring.Append("illum 1\n");
  1033. mtlstring.Append("Ns 1.000000\n");
  1034. mtlstring.Append("map_Kd " + basefilename + "_material0000_map_Kd.png");
  1035. mtlstring.AppendFormat("\n");
  1036. string mtlpath = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + ".mtl";
  1037. swriter = new System.IO.StreamWriter(mtlpath);
  1038. swriter.Write(mtlstring.ToString());
  1039. swriter.Close();
  1040. }
  1041. }
  1042. //Save the .area file for spatial memory.
  1043. string areaName = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + ".area";
  1044. zedCamera.SaveCurrentArea(areaName);
  1045. Thread.CurrentThread.CurrentCulture = oldCulture;
  1046. }
  1047. /// <summary>
  1048. /// Used by Unity to draw the meshes in the editor with a double pass shader.
  1049. /// </summary>
  1050. #if UNITY_EDITOR
  1051. private void OnDrawGizmos()
  1052. {
  1053. Gizmos.color = colorMesh;
  1054. if (!IsRunning() && spatialMappingHelper != null && spatialMappingHelper.chunks.Count != 0 && display)
  1055. {
  1056. foreach (var submesh in spatialMappingHelper.chunks)
  1057. {
  1058. if (submesh.Value.proceduralMesh.mesh != null)
  1059. {
  1060. Gizmos.DrawWireMesh(submesh.Value.proceduralMesh.mesh.sharedMesh, submesh.Value.o.transform.position, submesh.Value.o.transform.rotation);
  1061. }
  1062. }
  1063. }
  1064. }
  1065. #endif
  1066. /// <summary>
  1067. /// Low-level spatial mapping class. Calls SDK wrapper functions to get mesh data and applies it to Unity meshes.
  1068. /// Functions are usually called from ZEDSpatialMapping, but buffer data is held within.
  1069. /// Note that some values are updated directly from the ZED wrapper dll, so such assignments aren't visible in the plugin.
  1070. /// </summary>
  1071. private class ZEDSpatialMappingHelper
  1072. {
  1073. /// <summary>
  1074. /// Reference to the ZEDCamera instance. Used to call SDK functions.
  1075. /// </summary>
  1076. private sl.ZEDCamera zedCamera;
  1077. /// <summary>
  1078. /// Maximum number of chunks. It's best to get relatively few chunks and to update them quickly.
  1079. /// </summary>
  1080. private const int MAX_SUBMESH = 1000;
  1081. /*** Number of vertices/triangles/indices per chunk***/
  1082. /// <summary>
  1083. /// Total vertices in each chunk/submesh.
  1084. /// </summary>
  1085. private int[] numVerticesInSubmesh = new int[MAX_SUBMESH];
  1086. /// <summary>
  1087. /// Total triangles in each chunk/submesh.
  1088. /// </summary>
  1089. private int[] numTrianglesInSubmesh = new int[MAX_SUBMESH];
  1090. /// <summary>
  1091. /// Total indices per chunk/submesh.
  1092. /// </summary>
  1093. private int[] UpdatedIndices = new int[MAX_SUBMESH];
  1094. /*** Number of vertices/uvs/indices at the moment**/
  1095. /// <summary>
  1096. /// Vertex count in current submesh.
  1097. /// </summary>
  1098. private int numVertices = 0;
  1099. /// <summary>
  1100. /// Triangle point counds in current submesh. (Every three values are the indexes of the three vertexes that make up one triangle)
  1101. /// </summary>
  1102. private int numTriangles = 0;
  1103. /// <summary>
  1104. /// How many submeshes were updated.
  1105. /// </summary>
  1106. private int numUpdatedSubmesh = 0;
  1107. /*** The current data in the current submesh***/
  1108. /// <summary>
  1109. /// Vertices of the current submesh.
  1110. /// </summary>
  1111. private Vector3[] vertices;
  1112. /// <summary>
  1113. /// UVs of the current submesh.
  1114. /// </summary>
  1115. private Vector2[] uvs;
  1116. /// <summary>
  1117. /// Triangles of the current submesh. (Each int refers to the index of a vertex)
  1118. /// </summary>
  1119. private int[] triangles;
  1120. /// <summary>
  1121. /// Width and height of the mesh texture, if any.
  1122. /// </summary>
  1123. private int[] texturesSize = new int[2];
  1124. /// <summary>
  1125. /// Dictionary of all existing chunks.
  1126. /// </summary>
  1127. public Dictionary<int, ZEDSpatialMapping.Chunk> chunks = new Dictionary<int, ZEDSpatialMapping.Chunk>(MAX_SUBMESH);
  1128. /// <summary>
  1129. /// Material with real-world texture, applied to the mesh when Texturing (isTextured) is enabled.
  1130. /// </summary>
  1131. private Material materialTexture;
  1132. /// <summary>
  1133. /// Material used to draw the mesh. Applied to chunks during the scan, and replaced with materialTexture
  1134. /// only if Texturing (isTextured) is enabled.
  1135. /// </summary>
  1136. private Material materialMesh;
  1137. /// <summary>
  1138. /// Public accessor for the number of chunks that have been updated.
  1139. /// </summary>
  1140. public int NumberUpdatedSubMesh
  1141. {
  1142. get { return numUpdatedSubmesh; }
  1143. }
  1144. /// <summary>
  1145. /// Gets the material used to draw spatial mapping meshes without real-world textures.
  1146. /// </summary>
  1147. /// <returns></returns>
  1148. public Material GetMaterialSpatialMapping()
  1149. {
  1150. return materialMesh;
  1151. }
  1152. /// <summary>
  1153. /// Constructor. Gets the ZEDCamera instance and sets materials used on the meshes.
  1154. /// </summary>
  1155. /// <param name="materialTexture"></param>
  1156. /// <param name="materialMesh"></param>
  1157. public ZEDSpatialMappingHelper(sl.ZEDCamera camera, Material materialTexture, Material materialMesh)
  1158. {
  1159. zedCamera = camera;
  1160. this.materialTexture = materialTexture;
  1161. this.materialMesh = materialMesh;
  1162. }
  1163. /// <summary>
  1164. /// Updates the range to match the specified preset.
  1165. /// </summary>
  1166. static public float ConvertRangePreset(RANGE rangePreset)
  1167. {
  1168. if (rangePreset == RANGE.NEAR)
  1169. {
  1170. return 3.5f;
  1171. }
  1172. else if (rangePreset == RANGE.MEDIUM)
  1173. {
  1174. return 5.0f;
  1175. }
  1176. if (rangePreset == RANGE.FAR)
  1177. {
  1178. return 10.0f;
  1179. }
  1180. return 5.0f;
  1181. }
  1182. /// <summary>
  1183. /// Updates the resolution to match the specified preset.
  1184. /// </summary>
  1185. static public float ConvertResolutionPreset(RESOLUTION resolutionPreset)
  1186. {
  1187. if (resolutionPreset == RESOLUTION.HIGH)
  1188. {
  1189. return 0.05f;
  1190. }
  1191. else if (resolutionPreset == RESOLUTION.MEDIUM)
  1192. {
  1193. return 0.10f;
  1194. }
  1195. if (resolutionPreset == RESOLUTION.LOW)
  1196. {
  1197. return 0.15f;
  1198. }
  1199. return 0.10f;
  1200. }
  1201. /// <summary>
  1202. /// Tells the ZED SDK to begin spatial mapping.
  1203. /// </summary>
  1204. /// <returns></returns>
  1205. public sl.ERROR_CODE EnableSpatialMapping(sl.SPATIAL_MAP_TYPE type,float resolutionMeter, float maxRangeMeter, bool saveTexture)
  1206. {
  1207. return zedCamera.EnableSpatialMapping(type,resolutionMeter, maxRangeMeter, saveTexture);
  1208. }
  1209. /// <summary>
  1210. /// Tells the ZED SDK to stop spatial mapping.
  1211. /// </summary>
  1212. public void DisableSpatialMapping()
  1213. {
  1214. zedCamera.DisableSpatialMapping();
  1215. }
  1216. /// <summary>
  1217. /// Create a new submesh to contain the data retrieved from the ZED.
  1218. /// </summary>
  1219. public ZEDSpatialMapping.Chunk CreateNewMesh(int i, Material meshMat, Transform holder)
  1220. {
  1221. //Initialize the chunk and create a GameObject for it.
  1222. ZEDSpatialMapping.Chunk chunk = new ZEDSpatialMapping.Chunk();
  1223. chunk.o = GameObject.CreatePrimitive(PrimitiveType.Quad);
  1224. chunk.o.layer = zedCamera.TagInvisibleToZED;
  1225. chunk.o.GetComponent<MeshCollider>().sharedMesh = null;
  1226. chunk.o.name = "Chunk" + chunks.Count;
  1227. chunk.o.transform.localPosition = Vector3.zero;
  1228. chunk.o.transform.localRotation = Quaternion.identity;
  1229. Mesh m = new Mesh();
  1230. m.MarkDynamic(); //Allows it to be updated regularly without performance issues.
  1231. chunk.mesh = m;
  1232. //Set graphics settings to not treat the chunk like a physical object (no shadows, no reflections, no lights, etc.).
  1233. MeshRenderer meshRenderer = chunk.o.GetComponent<MeshRenderer>();
  1234. meshRenderer.material = meshMat;
  1235. meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  1236. meshRenderer.receiveShadows = false;
  1237. meshRenderer.enabled = true;
  1238. meshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
  1239. meshRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
  1240. //Sets the position and parent of the chunk.
  1241. chunk.o.transform.parent = holder;
  1242. chunk.o.layer = zedCamera.TagInvisibleToZED;
  1243. //Add the chunk to the dictionary.
  1244. chunk.proceduralMesh.mesh = chunk.o.GetComponent<MeshFilter>();
  1245. chunks.Add(i, chunk);
  1246. return chunk;
  1247. }
  1248. /// <summary>
  1249. /// Adds a MeshCollider to each chunk for physics. This is time-consuming, so it's only called
  1250. /// once scanning is finished and the final mesh is being processed.
  1251. /// </summary>
  1252. public void UpdateMeshCollider(List<ZEDSpatialMapping.Chunk> listMeshes, int startIndex = 0)
  1253. {
  1254. List<int> idsToDestroy = new List<int>(); //List of meshes that are too small for colliders and will be destroyed.
  1255. //Update each mesh with a collider.
  1256. for (int i = startIndex; i < listMeshes.Count; ++i)
  1257. {
  1258. var submesh = listMeshes[i];
  1259. MeshCollider m = submesh.o.GetComponent<MeshCollider>();
  1260. if (m == null)
  1261. {
  1262. m = submesh.o.AddComponent<MeshCollider>();
  1263. }
  1264. //If a mesh has 2 or fewer vertices, it's useless, so queue it up to be destroyed.
  1265. Mesh tempMesh = submesh.o.GetComponent<MeshFilter>().sharedMesh;
  1266. if (tempMesh.vertexCount < 3)
  1267. {
  1268. idsToDestroy.Add(i);
  1269. continue;
  1270. }
  1271. m.sharedMesh = tempMesh;
  1272. m.sharedMesh.RecalculateNormals();
  1273. m.sharedMesh.RecalculateBounds();
  1274. }
  1275. //Destroy all useless meshes now that we've iterated through all the meshes.
  1276. for (int i = 0; i < idsToDestroy.Count; ++i)
  1277. {
  1278. GameObject.Destroy(chunks[idsToDestroy[i]].o);
  1279. chunks.Remove(idsToDestroy[i]);
  1280. }
  1281. Clear(); //Clear the buffer data now that we have Unity meshes.
  1282. }
  1283. /// <summary>
  1284. /// Tells the ZED SDK to calculate the size of the texture and the UVs.
  1285. /// </summary>
  1286. public void ApplyTexture()
  1287. {
  1288. zedCamera.ApplyTexture(numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, texturesSize, MAX_SUBMESH);
  1289. }
  1290. /// <summary>
  1291. /// Tells the ZED SDK to update its internal mesh from spatial mapping. The resulting mesh will later be retrieved with RetrieveMesh().
  1292. /// </summary>
  1293. public void UpdateMesh()
  1294. {
  1295. zedCamera.UpdateMesh(numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
  1296. ResizeMesh();
  1297. }
  1298. /// <summary>
  1299. /// Retrieves the mesh vertices and triangles from the ZED SDK. This must be called after UpdateMesh() has been called.
  1300. /// Note that the actual assignment to vertices and triangles happens from within the wrapper .dll via pointers, not a C# script.
  1301. /// </summary>
  1302. public void RetrieveMesh()
  1303. {
  1304. zedCamera.RetrieveMesh(vertices, triangles, MAX_SUBMESH, null, System.IntPtr.Zero);
  1305. }
  1306. /// <summary>
  1307. /// Clear the current buffer data.
  1308. /// </summary>
  1309. public void Clear()
  1310. {
  1311. chunks.Clear();
  1312. vertices = new Vector3[0];
  1313. triangles = new int[0];
  1314. uvs = new Vector2[0];
  1315. System.Array.Clear(vertices, 0, vertices.Length);
  1316. System.Array.Clear(triangles, 0, triangles.Length);
  1317. }
  1318. /// <summary>
  1319. /// Process data from a submesh retrieved from the ZED SDK into a chunk, which includes a GameObject and visible mesh.
  1320. /// </summary>
  1321. /// <param name="indexUpdate">Index of the submesh/chunk to be updated.</param>
  1322. /// <param name="verticesOffset">Starting index in the vertices stack.</param>
  1323. /// <param name="trianglesOffset">Starting index in the triangles stack.</param>
  1324. /// <param name="uvsOffset">Starting index in the UVs stack.</param>
  1325. /// <param name="transform">Transform of the holder object to which all chunks are parented.</param>
  1326. /// <param name="updatedTex"><c>True</c> if the world texture has been updated so we know to update UVs.</param>
  1327. public void SetMesh(int indexUpdate, ref int verticesOffset, ref int trianglesOffset, ref int uvsOffset, Transform holder, bool updatedTex)
  1328. {
  1329. ZEDSpatialMapping.Chunk subMesh;
  1330. int updatedIndex = UpdatedIndices[indexUpdate];
  1331. if (!chunks.TryGetValue(updatedIndex, out subMesh)) //Use the existing chunk/submesh if already in the dictionary. Otherwise, make a new one.
  1332. {
  1333. subMesh = CreateNewMesh(updatedIndex, materialMesh, holder);
  1334. }
  1335. Mesh currentMesh = subMesh.mesh;
  1336. ZEDSpatialMapping.ProceduralMesh dynamicMesh = subMesh.proceduralMesh;
  1337. //If the dynamicMesh's triangle and vertex arrays are unassigned or are the wrong size, redo the array.
  1338. if (dynamicMesh.triangles == null || dynamicMesh.triangles.Length != 3 * numTrianglesInSubmesh[indexUpdate])
  1339. {
  1340. dynamicMesh.triangles = new int[3 * numTrianglesInSubmesh[indexUpdate]];
  1341. }
  1342. if (dynamicMesh.vertices == null || dynamicMesh.vertices.Length != numVerticesInSubmesh[indexUpdate])
  1343. {
  1344. dynamicMesh.vertices = new Vector3[numVerticesInSubmesh[indexUpdate]];
  1345. }
  1346. //Clear the old mesh data.
  1347. currentMesh.Clear();
  1348. //Copy data retrieved from the ZED SDK into the ProceduralMesh buffer in the current chunk.
  1349. System.Array.Copy(vertices, verticesOffset, dynamicMesh.vertices, 0, numVerticesInSubmesh[indexUpdate]);
  1350. verticesOffset += numVerticesInSubmesh[indexUpdate];
  1351. System.Buffer.BlockCopy(triangles, trianglesOffset * sizeof(int), dynamicMesh.triangles, 0, 3 * numTrianglesInSubmesh[indexUpdate] * sizeof(int)); //Block copy has better performance than Array.
  1352. trianglesOffset += 3 * numTrianglesInSubmesh[indexUpdate];
  1353. currentMesh.vertices = dynamicMesh.vertices;
  1354. currentMesh.SetTriangles(dynamicMesh.triangles, 0, false);
  1355. dynamicMesh.mesh.sharedMesh = currentMesh;
  1356. //If textured, add UVs.
  1357. if (updatedTex)
  1358. {
  1359. Vector2[] localUvs = new Vector2[numVerticesInSubmesh[indexUpdate]];
  1360. subMesh.o.GetComponent<MeshRenderer>().sharedMaterial = materialTexture;
  1361. System.Array.Copy(uvs, uvsOffset, localUvs, 0, numVerticesInSubmesh[indexUpdate]);
  1362. uvsOffset += numVerticesInSubmesh[indexUpdate];
  1363. currentMesh.uv = localUvs;
  1364. }
  1365. }
  1366. /// <summary>
  1367. /// Retrieves the entire mesh and texture (vertices, triangles, and uvs) from the ZED SDK.
  1368. /// Differs for normal retrieval as the UVs and texture are retrieved.
  1369. /// This is only called after scanning has been stopped, and only if Texturing is enabled.
  1370. /// </summary>
  1371. public void SetMeshAndTexture()
  1372. {
  1373. //If the texture is too large, it's impossible to add the texture to the mesh.
  1374. if (texturesSize[0] > 8192) return;
  1375. Texture2D textureMesh = new Texture2D(texturesSize[0], texturesSize[1], TextureFormat.ARGB32, false);
  1376. if (textureMesh != null)
  1377. {
  1378. materialTexture.SetTexture("_MainTex", textureMesh);
  1379. vertices = new Vector3[numVertices];
  1380. uvs = new Vector2[numVertices];
  1381. triangles = new int[3 * numTriangles];
  1382. System.IntPtr texture = textureMesh.GetNativeTexturePtr();
  1383. zedCamera.RetrieveMesh(vertices, triangles, MAX_SUBMESH, uvs, texture);
  1384. }
  1385. }
  1386. /// <summary>
  1387. /// Loads a mesh from a file path and allocates the buffers accordingly.
  1388. /// </summary>
  1389. /// <param name="meshFilePath">Path to the mesh file.</param>
  1390. /// <returns></returns>
  1391. public bool LoadMesh(string meshFilePath)
  1392. {
  1393. bool r = zedCamera.LoadMesh(meshFilePath, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH, texturesSize);
  1394. if (!r) Debug.LogWarning("[ZED] Failed to load mesh: "+ meshFilePath);
  1395. vertices = new Vector3[numVertices];
  1396. uvs = new Vector2[numVertices];
  1397. triangles = new int[3 * numTriangles];
  1398. return r;
  1399. }
  1400. /// <summary>
  1401. /// Gets the width of the scanned texture file. Note that if this is over 8k, the texture will not be taken.
  1402. /// </summary>
  1403. /// <returns>Texture width in pixels.</returns>
  1404. public int GetWidthTexture()
  1405. {
  1406. return texturesSize[0];
  1407. }
  1408. public int GetHeightTexture()
  1409. {
  1410. return texturesSize[1];
  1411. }
  1412. /// <summary>
  1413. /// Resize the mesh buffer according to how many vertices are needed by the current submesh/chunk.
  1414. /// </summary>
  1415. public void ResizeMesh()
  1416. {
  1417. if (vertices.Length < numVertices)
  1418. {
  1419. vertices = new Vector3[numVertices * 2]; //Allocation is faster than resizing.
  1420. }
  1421. if (triangles.Length < 3 * numTriangles)
  1422. {
  1423. triangles = new int[3 * numTriangles * 2];
  1424. }
  1425. }
  1426. /// <summary>
  1427. /// Filters the mesh with predefined parameters.
  1428. /// </summary>
  1429. /// <param name="filterParameters">Filter setting. A higher setting results in fewer faces.</param>
  1430. public void FilterMesh(sl.FILTER filterParameters)
  1431. {
  1432. zedCamera.FilterMesh(filterParameters, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
  1433. }
  1434. /// <summary>
  1435. /// Tells the ZED SDK to consolidate the chunks into a smaller number of large chunks.
  1436. /// Useful because having many small chunks is more performant for scanning, but fewer large chunks are otherwise easier to work with.
  1437. /// </summary>
  1438. public void MergeChunks()
  1439. {
  1440. zedCamera.MergeChunks(MAX_SUBMESH, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
  1441. }
  1442. }
  1443. }