123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653 |
- //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
- using System.Collections.Generic;
- using UnityEngine;
- using System.Threading;
- using System.Text;
- using System;
- using System.Globalization;
- /// <summary>
- /// Processes the mesh taken from the ZED's Spatial Mapping feature so it can be used within Unity.
- /// Handles the real-time updates as well as the final processing.
- /// Note that ZEDSpatialMappingManager is more user-friendly/high-level, designed to hide the complexities of this class.
- /// </summary>
- public class ZEDSpatialMapping
- {
- /// <summary>
- /// Submesh created by ZEDSpatialMapping. The scan is made of multiple chunks.
- /// </summary>
- public struct Chunk
- {
- /// <summary>
- /// Reference to the GameObject that holds the MeshFilter.
- /// </summary>
- public GameObject o;
- /// <summary>
- /// Dynamic mesh data that will change throughout the spatial mapping.
- /// </summary>
- public ProceduralMesh proceduralMesh;
- /// <summary>
- /// Final mesh, assigned to once the spatial mapping is over and done processing.
- /// </summary>
- public Mesh mesh;
- }
- /// <summary>
- /// Structure to contain a temporary buffer that holds triangles and vertices.
- /// </summary>
- public struct ProceduralMesh
- {
- /// <summary>
- /// List of vertex indexes that make up triangles.
- /// </summary>
- public int[] triangles;
- /// <summary>
- /// List of vertices in the mesh.
- /// </summary>
- public Vector3[] vertices;
- /// <summary>
- /// MeshFilter of a GameObject that holds the chunk this ProceduralMesh represents.
- /// </summary>
- public MeshFilter mesh;
- };
- /// <summary>
- /// Spatial mapping depth resolution presets.
- /// </summary>
- public enum RESOLUTION
- {
- /// <summary>
- /// Create detailed geometry. Requires lots of memory.
- /// </summary>
- HIGH,
- /// <summary>
- /// Small variations in the geometry will disappear. Useful for large objects.
- /// </summary>
- ///
- MEDIUM,
- /// <summary>
- /// Keeps only large variations of the geometry. Useful for outdoors.
- /// </summary>
- LOW
- }
- /// <summary>
- /// Spatial mapping depth range presets.
- /// </summary>
- public enum RANGE
- {
- /// <summary>
- /// Geometry within 3.5 meters of the camera will be mapped.
- /// </summary>
- NEAR,
- /// <summary>
- /// Geometry within 5 meters of the camera will be mapped.
- /// </summary>
- MEDIUM,
- /// <summary>
- /// Objects as far as 10 meters away are mapped. Useful for outdoors.
- /// </summary>
- FAR
- }
- /// <summary>
- /// Current instance of the ZED Camera.
- /// </summary>
- private sl.ZEDCamera zedCamera;
- /// <summary>
- /// Instance of an internal helper class for low-level mesh processing.
- /// </summary>
- private ZEDSpatialMappingHelper spatialMappingHelper;
- /// <summary>
- /// Amount of filtering to apply to the mesh. Higher values result in lower face counts/memory usage, but also lower precision.
- /// </summary>
- public sl.FILTER filterParameters = sl.FILTER.MEDIUM;
- /// <summary>
- /// True when RequestSaveMesh has been called, so that ongoing threads know to stop and save the mesh
- /// when everything is finished processing.
- /// </summary>
- private bool saveRequested = false;
- /// <summary>
- /// Where the new mesh will be saved. Should end in .obj.
- /// If textured, a .mtl (material) file and .png file will appear in the same folder with the same base filename.
- /// </summary>
- private string savePath = "Assets/ZEDMesh.obj";
- #if UNITY_EDITOR
- /// <summary>
- /// Color of the wireframe mesh to be drawn in Unity's Scene window.
- /// </summary>
- private Color colorMesh = new Color(0.35f, 0.65f, 0.95f);
- #endif
- /// <summary>
- /// Offset for the triangles buffer, so that new triangles are copied into the dynamic mesh starting at the correct index.
- /// </summary>
- private int trianglesOffsetLastFrame;
- /// <summary>
- /// Offset for the vertices buffer, so that new vertices are copied into the dynamic mesh starting at the correct index.
- /// </summary>
- private int verticesOffsetLastFrame;
- /// <summary>
- /// Offset for the UVs buffer, so that new UV coordinates are copied into the dynamic mesh starting at the correct index.
- /// </summary>
- private int uvsOffsetLastFrame;
- /// <summary>
- /// Index of the mesh that was updated last frame.
- /// </summary>
- private int indexLastFrame;
- /// <summary>
- /// Flag set to true if there were meshes what weren't completely updated last frame due to lack of time.
- /// </summary>
- private bool remainMeshes = false;
- /// <summary>
- /// The user has requested to stop spatial mapping.
- /// </summary>
- private bool stopWanted = false;
- /// <summary>
- /// Whether the mesh is in the filtering stage of processing.
- /// </summary>
- private bool isFiltering = false;
- /// <summary>
- /// Whether the filtering stage of the mesh's processing has started and finished.
- /// </summary>
- private bool isFilteringOver = false;
- /// <summary>
- /// Whether the update thread will stop running.
- /// </summary>
- private bool stopRunning = false;
- /// <summary>
- /// Whether any part of spatial mapping is running. Set to true when scanning has started
- /// and set to false after the scanned mesh has finished bring filtered, textured, etc.
- /// </summary>
- private bool running = false;
- /// <summary>
- /// Flag that causes spatial mapping to pause when true. Use SwitchPauseState() to change.
- /// </summary>
- private bool pause = false;
- /// <summary>
- /// Returns true if spatial mapping has been paused. This can be set to true even if spatial mapping isn't running.
- /// </summary>
- public bool IsPaused
- {
- get { return pause; }
- }
- /// <summary>
- /// Whether scanned meshes are visible or not.
- /// </summary>
- public bool display = false;
- /// <summary>
- /// State of the scanning during its initialization. Used to know if it has started successfully.
- /// </summary>
- private sl.ERROR_CODE scanningInitState;
- /// <summary>
- /// Delegate for the OnMeshUpdate event, which is called every time a new chunk/submesh is processed.
- /// </summary>
- public delegate void OnNewMesh();
- /// <summary>
- /// Events called every time a new chunk/submesh has been processed. It's called many times during the scan.
- /// </summary>
- public event OnNewMesh OnMeshUpdate;
- /// <summary>
- /// Delegate for OnMeshReady, which is called when spatial mapping has finished.
- /// </summary>
- public delegate void OnSpatialMappingEnded();
- /// <summary>
- /// Event called when spatial mapping has finished.
- /// </summary>
- public event OnSpatialMappingEnded OnMeshReady;
- /// <summary>
- /// Delegate for OnMeshStarted, which is called when spatial mapping has started.
- /// </summary>
- public delegate void OnSpatialMappingStarted();
- /// <summary>
- /// Event called when spatial mapping has started.
- /// </summary>
- public event OnSpatialMappingStarted OnMeshStarted;
- /// <summary>
- /// GameObject to which every chunk of the mesh is parented. Represents the scanned mesh in Unity's Hierarchy.
- /// </summary>
- private GameObject holder = null;
- /**** Threading Variables ****/
- /// <summary>
- /// True if the mesh has been updated, and needs to be processed.
- /// </summary>
- private bool meshUpdated = false;
- /// <summary>
- /// True if the mesh update thread is running.
- /// </summary>
- private bool updateThreadRunning = false;
- /// <summary>
- /// Public accessor for whether the mesh update thread is running.
- /// </summary>
- public bool IsUpdateThreadRunning
- {
- get { return updateThreadRunning; }
- }
- /// <summary>
- /// True if the user has requested that spatial mapping start.
- /// </summary>
- private bool spatialMappingRequested = false;
- /// <summary>
- /// True if the real-world texture needs to be updated.
- /// This only happens after scanning is finished and if Texturing (isTextured) is enabled.
- /// </summary>
- private bool updateTexture = false;
- /// <summary>
- /// True if the real-world texture has been updated.
- /// </summary>
- private bool updatedTexture = false;
- /// <summary>
- /// Thread that retrieves the size of the submeshes.
- /// </summary>
- private Thread scanningThread;
- /// <summary>
- /// Thread that filters the mesh once scanning has finished.
- /// </summary>
- private Thread filterThread;
- /// <summary>
- /// Mutex for threaded spatial mapping.
- /// </summary>
- private object lockScanning = new object();
- /// <summary>
- /// Maximum time in milliseconds that can be spent processing retrieved meshes each frame. If time is exceeded, remaining meshes will be processed next frame.
- /// </summary>
- private const int MAX_TIME = 5;
- /// <summary>
- /// True if the thread that updates the real-world texture is running.
- /// </summary>
- private bool texturingRunning = false;
- /// <summary>
- /// Gravity direction vector relative to ZEDManager's orientation. Estimated after spatial mapping is finished.
- /// Note that this will always be empty if using the ZED Mini as gravity is determined from its IMU at start.
- /// </summary>
- public Vector3 gravityEstimation;
- /// <summary>
- /// Public accessor for texturingRunning, which is whether the thread that updates the real-world texture is running.
- /// </summary>
- public bool IsTexturingRunning
- {
- get
- {
- return texturingRunning;
- }
- }
- /// <summary>
- /// If true, the script will add MeshColliders to all scanned chunks to allow physics collisions.
- /// </summary>
- private bool hasColliders = true;
- /// <summary>
- /// True if texture from the real world should be applied to the mesh. If true, texture will be applied after scanning is finished.
- /// </summary>
- private bool isTextured = false;
- /// <summary>
- /// Flag to check if we have attached ZEDMeshRenderer components to the ZED rig camera objects.
- /// This is done in Update() if it hasn't been done yet.
- /// </summary>
- private bool setMeshRenderer = false;
- /// <summary>
- /// References to the ZEDMeshRenderer components attached to the ZED rig camera objects.
- /// [0] is the one attached to the left camera. [1] is the right camera, if it exists.
- /// </summary>
- private ZEDMeshRenderer[] meshRenderer = new ZEDMeshRenderer[2];
- /// <summary>
- /// The scene's ZEDManager component, usually attached to the ZED rig GameObject (ZED_Rig_Mono or ZED_Rig_Stereo).
- /// </summary>
- private ZEDManager zedManager;
- /// <summary>
- /// All chunks/submeshes with their indices. Only used while spatial mapping is running, as meshes are consolidated from
- /// many small meshes into fewer, larger meshes when finished. See ChunkList for final submeshes.
- /// </summary>
- public Dictionary<int, ZEDSpatialMapping.Chunk> Chunks
- {
- get { return spatialMappingHelper.chunks; }
- }
- /// <summary>
- /// List of the final mesh chunks created after scanning is finished. This is not filled beforehand because we use
- /// many small chunks during scanning, and consolidate them afterward. See Chunks for runtime submeshes.
- /// </summary>
- public List<ZEDSpatialMapping.Chunk> ChunkList = new List<ZEDSpatialMapping.Chunk>();
- /// <summary>
- /// Constructor. Spawns the holder GameObject to hold scanned chunks and the ZEDSpatialMappingHelper to handle low-level mesh processing.
- /// </summary>
- /// <param name="transform">Transform of the scene's ZEDSpatialMappingManager.</param>
- /// <param name="zedCamera">Reference to the ZEDCamera instance.</param>
- /// <param name="zedManager">The scene's ZEDManager component.</param>
- public ZEDSpatialMapping(Transform transform, ZEDManager zedManager)
- {
- //Instantiate the low-level mesh processing helper.
- spatialMappingHelper = new ZEDSpatialMappingHelper(zedManager.zedCamera, Resources.Load("Materials/SpatialMapping/Mat_ZED_Texture") as Material, Resources.Load("Materials/SpatialMapping/Mat_ZED_Geometry_Wireframe") as Material);
- //Assign basic values.
- this.zedCamera = zedManager.zedCamera;
- this.zedManager = zedManager;
- scanningInitState = sl.ERROR_CODE.FAILURE;
- }
- /// <summary>
- /// Begins the spatial mapping process. This is called when you press the "Start Spatial Mapping" button in the Inspector.
- /// </summary>
- /// <param name="resolutionPreset">Resolution setting - how detailed the mesh should be at scan time.</param>
- /// <param name="rangePreset">Range setting - how close geometry must be to be scanned.</param>
- /// <param name="isTextured">Whether to scan texture, or only the geometry.</param>
- public void StartStatialMapping(sl.SPATIAL_MAP_TYPE type, RESOLUTION resolutionPreset, RANGE rangePreset, bool isTextured)
- {
- //Create the Holder object, to which all scanned chunks will be parented.
- holder = new GameObject();
- holder.name = "[ZED Mesh Holder (" + zedManager.name + ")]";
- holder.transform.position = Vector3.zero;
- holder.transform.rotation = Quaternion.identity;
- StaticBatchingUtility.Combine(holder);
- holder.transform.position = Vector3.zero;
- holder.transform.rotation = Quaternion.identity;
- spatialMappingRequested = true;
- if (spatialMappingRequested && scanningInitState != sl.ERROR_CODE.SUCCESS)
- {
- scanningInitState = EnableSpatialMapping(type, resolutionPreset, rangePreset, isTextured);
- }
- zedManager.gravityRotation = Quaternion.identity;
- pause = false; //Make sure the scanning doesn't start paused because it was left paused at the last scan.
- }
- /// <summary>
- /// Initializes flags used during scan, tells ZEDSpatialMappingHelper to activate the ZED SDK's scanning, and
- /// starts the thread that updates the in-game chunks with data from the ZED SDK.
- /// </summary>
- /// <param name="resolutionPreset">Resolution setting - how detailed the mesh should be at scan time.</param>
- /// <param name="rangePreset">Range setting - how close geometry must be to be scanned.</param>
- /// <param name="isTextured">Whether to scan texture, or only the geometry.</param>
- /// <returns></returns>
- private sl.ERROR_CODE EnableSpatialMapping(sl.SPATIAL_MAP_TYPE type,RESOLUTION resolutionPreset, RANGE rangePreset, bool isTextured)
- {
- sl.ERROR_CODE error;
- this.isTextured = isTextured;
- //Tell the helper to start scanning. This call gets passed directly to the wrapper call in ZEDCamera.
- error = spatialMappingHelper.EnableSpatialMapping(type,ZEDSpatialMappingHelper.ConvertResolutionPreset(resolutionPreset), ZEDSpatialMappingHelper.ConvertRangePreset(rangePreset), isTextured);
- if (meshRenderer[0]) meshRenderer[0].isTextured = isTextured;
- if (meshRenderer[1]) meshRenderer[1].isTextured = isTextured;
- stopWanted = false;
- running = true;
- if (error == sl.ERROR_CODE.SUCCESS) //If the scan was started successfully.
- {
- //Set default flag settings.
- display = true;
- meshUpdated = false;
- spatialMappingRequested = false;
- updateTexture = false;
- updatedTexture = false;
- //Clear all previous meshes.
- ClearMeshes();
- //Request the first mesh update. Later, this will get called continuously after each update is applied.
- zedCamera.RequestMesh();
- //Launch the thread to retrieve the chunks and their sizes from the ZED SDK.
- scanningThread = new Thread(UpdateMesh);
- updateThreadRunning = true;
- if (OnMeshStarted != null)
- {
- OnMeshStarted(); //Invoke the event for other scripts, like ZEDMeshRenderer.
- }
- scanningThread.Start();
- }
- return error;
- }
- /// <summary>
- /// Attach a new ZEDMeshRenderer to the ZED rig cameras. This is necessary to see the mesh.
- /// </summary>
- public void SetMeshRenderer()
- {
- if (!setMeshRenderer) //Make sure we haven't do this yet.
- {
- if (zedManager != null)
- {
- Transform left = zedManager.GetLeftCameraTransform(); //Find the left camera. This exists in both ZED_Rig_Mono and ZED_Rig_Stereo.
- if (left != null)
- {
- meshRenderer[0] = left.gameObject.GetComponent<ZEDMeshRenderer>();
- if (!meshRenderer[0])
- {
- meshRenderer[0] = left.gameObject.AddComponent<ZEDMeshRenderer>();
- }
- meshRenderer[0].Create();
- }
- Transform right = zedManager.GetRightCameraTransform(); //Find the right camera. This only exists in ZED_Rig_Stereo or a similar stereo rig.
- if (right != null)
- {
- meshRenderer[1] = right.gameObject.GetComponent<ZEDMeshRenderer>();
- if (!meshRenderer[1])
- {
- meshRenderer[1] = right.gameObject.AddComponent<ZEDMeshRenderer>();
- }
- meshRenderer[1].Create();
- }
- setMeshRenderer = true;
- }
- }
- }
- /// <summary>
- /// Updates the current mesh, if scanning, and manages the start and stop states.
- /// </summary>
- public void Update()
- {
- SetMeshRenderer(); //Make sure we have ZEDMeshRenderers on the cameras, so we can see the mesh.
- if (meshUpdated || remainMeshes)
- {
- UpdateMeshMainthread();
- meshUpdated = false;
- }
- if (stopWanted && !remainMeshes)
- {
- stopRunning = true;
- stopWanted = false;
- Stop();
- }
- //If it's time to stop the scan, disable the spatial mapping and store the gravity estimation in ZEDManager.
- if (stopRunning && !isFiltering && isFilteringOver)
- {
- isFilteringOver = false;
- UpdateMeshMainthread(false);
- Thread disabling = new Thread(DisableSpatialMapping);
- disabling.Start();
- if (hasColliders)
- {
- if (!zedManager.IsStereoRig && gravityEstimation != Vector3.zero && zedManager.transform.parent != null)
- {
- Quaternion rotationToApplyForGravity = Quaternion.Inverse(Quaternion.FromToRotation(Vector3.up, -gravityEstimation.normalized));
- holder.transform.localRotation = rotationToApplyForGravity;
- zedManager.gravityRotation = rotationToApplyForGravity;
- }
- UpdateMeshCollider();
- }
- else
- {
- running = false;
- }
- stopRunning = false;
- }
- }
- /// <summary>
- /// Gets the mesh data from the ZED SDK and stores it for later update in the Unity mesh.
- /// </summary>
- private void UpdateMesh()
- {
- while (updateThreadRunning)
- {
- if (!remainMeshes) //If we don't have leftover meshes to apply from the last update.
- {
- lock (lockScanning)
- {
- if (meshUpdated == false && updateTexture) //If we need to update the texture, prioritize that.
- {
- //Get the last size of the mesh and get the texture size.
- spatialMappingHelper.ApplyTexture();
- meshUpdated = true;
- updateTexture = false;
- updatedTexture = true;
- updateThreadRunning = false;
- }
- else if (zedCamera.GetMeshRequestStatus() == sl.ERROR_CODE.SUCCESS && !pause && meshUpdated == false)
- {
- spatialMappingHelper.UpdateMesh(); //Tells the ZED SDK to update its internal mesh.
- spatialMappingHelper.RetrieveMesh(); //Applies the ZED SDK's internal mesh to values inside spatialMappingHelper.
- meshUpdated = true;
- }
- }
- //Time to process all the meshes spread on multiple frames.
- Thread.Sleep(5);
- }
- else //If there are meshes that were collected but not processed yet. Happens if the last update took too long to process.
- {
- //Check every 5ms if the meshes are done being processed.
- Thread.Sleep(5);
- }
- }
- }
- /// <summary>
- /// Destroys all submeshes.
- /// </summary>
- private void ClearMeshes()
- {
- if (holder != null)
- {
- foreach (Transform child in holder.transform)
- {
- GameObject.Destroy(child.gameObject);
- }
- spatialMappingHelper.Clear();
- }
- }
- /// <summary>
- /// Measures time since the provided start time. Used in UpdateMeshMainthread() to check if computational time for mesh updates
- /// has exceeded the MAX_TIME time limit (usually 5ms), so that it can hold off processing remaining meshes until the next frame.
- /// </summary>
- /// <param name="startTimeMS">Time.realtimeSinceStartup value when the process began.</param>
- /// <returns><c>True</c> if more than MAX_TIME has elapsed since startTimeMS.</returns>
- private bool GoneOverTimeBudget(int startTimeMS)
- {
- return (Time.realtimeSinceStartup * 1000) - startTimeMS > MAX_TIME;
- }
- /// <summary>
- /// Update the Unity mesh with the last data retrieved from the ZED, creating a new submesh if needed.
- /// Also launches the OnMeshUpdate event when the update is finished.
- /// <param name="spreadUpdateOverTime">If <c>true</c>, caps time spent on updating meshes each frame, leaving 'leftover' meshes for the next frame.</param>
- /// </summary>
- private void UpdateMeshMainthread(bool spreadUpdateOverTime = true)
- {
- //Cache the start time so we can measure how long this function is taking.
- //We'll check when updating the submeshes so that if it takes too long, we'll stop updating until the next frame.
- int startTimeMS = (int)(Time.realtimeSinceStartup * 1000);
- int indexUpdate = 0;
- lock (lockScanning) //Don't update if another thread is accessing.
- {
- if (updatedTexture)
- {
- spreadUpdateOverTime = false;
- }
- //Set the offset of the buffers to the offset of the last frame.
- int verticesOffset = 0, trianglesOffset = 0, uvsOffset = 0;
- if (remainMeshes && spreadUpdateOverTime)
- {
- verticesOffset = verticesOffsetLastFrame;
- trianglesOffset = trianglesOffsetLastFrame;
- uvsOffset = uvsOffsetLastFrame;
- indexUpdate = indexLastFrame;
- }
- //Clear all existing meshes and process the last ones.
- if (updatedTexture)
- {
- ClearMeshes();
- spatialMappingHelper.SetMeshAndTexture();
- if (meshRenderer[0]) meshRenderer[0].isTextured = isTextured;
- if (meshRenderer[1]) meshRenderer[1].isTextured = isTextured;
- }
- //Process the last meshes.
- for (; indexUpdate < spatialMappingHelper.NumberUpdatedSubMesh; indexUpdate++)
- {
- spatialMappingHelper.SetMesh(indexUpdate, ref verticesOffset, ref trianglesOffset, ref uvsOffset, holder.transform, updatedTexture);
- if (spreadUpdateOverTime && GoneOverTimeBudget(startTimeMS)) //Check if it's taken too long this frame.
- {
- remainMeshes = true; //It has. Set this flag so we know to pick up where we left off next frame.
- break;
- }
- }
- if (spreadUpdateOverTime)
- {
- indexLastFrame = indexUpdate;
- }
- else
- {
- indexLastFrame = 0;
- }
- //If all the meshes have been updated, reset values used to process 'leftover' meshes and get more data from the ZED.
- if ((indexUpdate == spatialMappingHelper.NumberUpdatedSubMesh) || spatialMappingHelper.NumberUpdatedSubMesh == 0)
- {
- verticesOffsetLastFrame = 0;
- trianglesOffsetLastFrame = 0;
- uvsOffsetLastFrame = 0;
- indexLastFrame = 0;
- remainMeshes = false;
- meshUpdated = false;
- zedCamera.RequestMesh();
- }
- //If some meshes still need updating, we'll save the offsets so we know where to start next frame.
- else if (indexUpdate != spatialMappingHelper.NumberUpdatedSubMesh)
- {
- remainMeshes = true;
- indexLastFrame = indexUpdate + 1;
- verticesOffsetLastFrame = verticesOffset;
- trianglesOffsetLastFrame = trianglesOffset;
- uvsOffsetLastFrame = uvsOffset;
- }
- //Save the mesh here if we requested it to be saved, as we just updated the meshes, including textures, if applicable.
- if (saveRequested && remainMeshes == false)
- {
- if (!isTextured || updatedTexture)
- {
- SaveMeshNow(savePath);
- saveRequested = false;
- }
- }
- }
- if (OnMeshUpdate != null)
- {
- OnMeshUpdate(); //Call the event if it has at least one listener.
- }
- //The texture update is done in one pass, so this is only called once after the mesh has stopped scanning.
- if (updatedTexture)
- {
- DisableSpatialMapping();
- updatedTexture = false;
- running = false;
- texturingRunning = false;
- if (hasColliders)
- {
- UpdateMeshCollider();
- }
- else
- {
- running = false;
- }
- }
- }
- public void ClearAllMeshes()
- {
- GameObject[] gos = GameObject.FindObjectsOfType<GameObject>() as GameObject[];
- spatialMappingHelper.Clear();
- for (int i = 0; i < gos.Length; i++)
- {
- string targetName = "[ZED Mesh Holder (" + zedManager.name + ")]";
- if (gos[i] != null && gos[i].name.Contains(targetName))
- {
- GameObject.Destroy(gos[i]);
- }
- }
- }
- /// <summary>
- /// Changes the visibility state of the meshes.
- /// This is what's called when the Hide/Display Mesh button is clicked in the Inspector.
- /// </summary>
- /// <param name="newDisplayState"> If true, the mesh will be displayed, else it will be hide. </param>
- public void SwitchDisplayMeshState(bool newDisplayState)
- {
- display = newDisplayState;
- }
- /// <summary>
- /// Pauses or resumes spatial mapping. If the spatial mapping is not enabled, nothing will happen.
- /// </summary>
- /// <param name="newPauseState"> If true, the spatial mapping will be paused, else it will be resumed. </param>
- public void SwitchPauseState(bool newPauseState)
- {
- pause = newPauseState;
- zedCamera.PauseSpatialMapping(newPauseState);
- }
- /// <summary>
- /// Update the mesh collider with the current mesh so it can handle physics.
- /// Calling it is slow, so it's only called after a scan is finished (or loaded).
- /// </summary>
- public void UpdateMeshCollider(bool timeSlicing = false)
- {
- ChunkList.Clear();
- foreach (var submesh in Chunks)
- {
- ChunkList.Add(submesh.Value);
- }
- lock (lockScanning)
- {
- spatialMappingHelper.UpdateMeshCollider(ChunkList);
- }
- if (OnMeshReady != null)
- {
- OnMeshReady();
- }
- running = false;
- }
- /// <summary>
- /// Properly clears existing scan data when the application is closed.
- /// Called by OnApplicationQuit() when the application closes.
- /// </summary>
- public void Dispose()
- {
- if (scanningThread != null)
- {
- updateThreadRunning = false;
- scanningThread.Join();
- }
- ClearMeshes();
- GameObject.Destroy(holder);
- DisableSpatialMapping();
- }
- /// <summary>
- /// Disable the ZED's spatial mapping. The mesh will no longer be updated, but it is not deleted.
- /// This gets called in Update() if the user requested a stop, and will execute once the scanning thread is free.
- /// </summary>
- private void DisableSpatialMapping()
- {
- lock (lockScanning)
- {
- updateThreadRunning = false;
- spatialMappingHelper.DisableSpatialMapping();
- scanningInitState = sl.ERROR_CODE.FAILURE;
- spatialMappingRequested = false;
- }
- }
- /// <summary>
- /// Save the mesh as an .obj file, and the area database as an .area file.
- /// This can be quite time-comsuming if you mapped a large area.
- /// </summary>
- public void RequestSaveMesh(string meshFilePath = "Assets/ZEDMesh.obj")
- {
- saveRequested = true;
- savePath = meshFilePath;
- if (updateThreadRunning)
- {
- StopStatialMapping(); //Stop the mapping if it hasn't stopped already.
- }
- }
- /// <summary>
- /// Loads the mesh and the corresponding area file if it exists. It can be quite time-comsuming if you mapped a large area.
- /// Note that if there are no .area files found in the same folder, the mesh will not be loaded either.
- /// Loading a mesh this way also loads relevant data into buffers, so it's as if a scan was just finished
- /// rather than a mesh asset being dropped into Unity.
- /// <returns><c>True</c> if loaded successfully, otherwise <c>flase</c>.</returns>
- /// </summary>
- public bool LoadMesh(string meshFilePath = "ZEDMesh.obj")
- {
- if (holder == null)
- {
- holder = new GameObject();
- holder.name = "[ZED Mesh Holder (" + zedManager.name + ")]";
- holder.transform.position = Vector3.zero;
- holder.transform.rotation = Quaternion.identity;
- StaticBatchingUtility.Combine(holder);
- }
- if (OnMeshStarted != null)
- {
- OnMeshStarted();
- }
- //If spatial mapping has started, disable it.
- DisableSpatialMapping();
- //Find and load the area
- string basePath = meshFilePath.Substring(0, meshFilePath.LastIndexOf("."));
- if (!System.IO.File.Exists(basePath + ".area"))
- {
- Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.TRACKING_BASE_AREA_NOT_FOUND));
- }
- zedCamera.DisableTracking();
- Quaternion quat = Quaternion.identity; Vector3 tr = Vector3.zero;
- if (zedCamera.EnableTracking(ref quat, ref tr, true, false, false, false, true, System.IO.File.Exists(basePath + ".area") ? basePath + ".area" : "") != sl.ERROR_CODE.SUCCESS)
- {
- Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.TRACKING_NOT_INITIALIZED));
- }
- updateTexture = false;
- updatedTexture = false;
- bool meshUpdatedLoad = false;
- lock (lockScanning)
- {
- ClearMeshes();
- meshUpdatedLoad = spatialMappingHelper.LoadMesh(meshFilePath);
- if (meshUpdatedLoad)
- {
- //Checks if a texture exists.
- if (spatialMappingHelper.GetWidthTexture() != -1)
- {
- updateTexture = true;
- updatedTexture = true;
- }
- //Retrieves the mesh sizes to be updated in the Unity's buffer later.
- if (!updateTexture)
- {
- spatialMappingHelper.RetrieveMesh();
- }
- }
- }
- if (meshUpdatedLoad)
- {
- //Update the buffer on Unity's side.
- UpdateMeshMainthread(false);
- //Add colliders and scan for gravity.
- if (hasColliders)
- {
- if (!zedManager.IsStereoRig && gravityEstimation != Vector3.zero)
- {
- Quaternion rotationToApplyForGravity = Quaternion.Inverse(Quaternion.FromToRotation(Vector3.up, -gravityEstimation.normalized));
- holder.transform.rotation = rotationToApplyForGravity;
- zedManager.gravityRotation = rotationToApplyForGravity;
- }
- UpdateMeshCollider();
- foreach (Chunk c in ChunkList)
- {
- c.o.transform.localRotation = Quaternion.identity;
- }
- }
- if (OnMeshReady != null)
- {
- OnMeshReady(); //Call the event if it has at least one listener.
- }
- if (meshRenderer[0]) meshRenderer[0].UpdateRenderingPlane(true);
- if (meshRenderer[1]) meshRenderer[1].UpdateRenderingPlane(true);
- return true;
- }
- return false;
- }
- /// <summary>
- /// Filters the mesh with the current filtering parameters.
- /// This reduces the total number of faces. More filtering means fewer faces.
- /// </summary>
- public void FilterMesh()
- {
- lock (lockScanning) //Wait for the thread to be available.
- {
- spatialMappingHelper.FilterMesh(filterParameters);
- spatialMappingHelper.ResizeMesh();
- spatialMappingHelper.RetrieveMesh();
- meshUpdated = true;
- }
- }
- /// <summary>
- /// Begin mesh filtering, and consolidate chunks into a reasonably low number when finished.
- /// </summary>
- /// <param name="filter"></param>
- void PostProcessMesh(bool filter = true)
- {
- isFiltering = true;
- if (filter)
- {
- FilterMesh();
- }
- MergeChunks();
- }
- /// <summary>
- /// Consolidates meshes to get fewer chunks - one for every MAX_SUBMESH vertices. Then applies to
- /// actual meshes in Unity.
- /// </summary>
- public void MergeChunks()
- {
- lock (lockScanning)
- {
- spatialMappingHelper.MergeChunks();
- spatialMappingHelper.ResizeMesh();
- spatialMappingHelper.RetrieveMesh();
- meshUpdated = true;
- }
- isFiltering = false;
- isFilteringOver = true;
- }
- /// <summary>
- /// Multi-threaded component of ApplyTexture(). Filters, then updates the mesh once, but as
- /// updateTexture is set to true when this is called, UpdateMesh() will also handle applying the texture.
- /// </summary>
- void ApplyTextureThreaded()
- {
- FilterMesh();
- UpdateMesh();
- }
- /// <summary>
- /// Stops the spatial mapping and begins the final processing, including adding texture.
- /// </summary>
- public bool ApplyTexture()
- {
- updateTexture = true;
- if (updateThreadRunning)
- {
- updateThreadRunning = false;
- scanningThread.Join();
- }
- scanningThread = new Thread(ApplyTextureThreaded);
- updateThreadRunning = true;
- scanningThread.Start();
- texturingRunning = true;
- return true;
- }
- /// <summary>
- /// Stop the spatial mapping and calls appropriate functions to process the final mesh.
- /// </summary>
- private void Stop()
- {
- gravityEstimation = zedCamera.GetGravityEstimate();
- if (isTextured)
- {
- ApplyTexture();
- }
- else
- {
- stopRunning = false;
- if (updateThreadRunning)
- {
- updateThreadRunning = false;
- scanningThread.Join();
- }
- ClearMeshes();
- PostProcessMesh(true);
- //filterThread = new Thread(() => PostProcessMesh(true));
- //filterThread.Start();
- stopRunning = true;
- }
- SwitchDisplayMeshState(true); //Make it default to visible.
- }
- /// <summary>
- /// Returns true from the moment a scan has started until the post-process is finished.
- /// </summary>
- /// <returns></returns>
- public bool IsRunning()
- {
- return running;
- }
- /// <summary>
- /// 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.
- /// </summary>
- public void StopStatialMapping()
- {
- stopWanted = true;
- }
- /// <summary>
- /// Combines the meshes from all the current chunks and saves them into a single mesh. If textured,
- /// will also save a .mtl file and .png file.
- /// This must only be called once all the chunks are completely finalized, or else they won't be filtered
- /// or have their UVs set.
- /// Called after RequestSaveMesh has been called after the main thread has the chance to stop the scan
- /// and finalize everything.
- /// </summary>
- /// <param name="meshFilePath">Where the mesh, material, and texture files will be saved.</param>
- private void SaveMeshNow(string meshFilePath = "Assets/ZEDMesh.obj")
- {
- // Make sure we are in invariant culture to get . notation for decimals.
- CultureInfo oldCulture = Thread.CurrentThread.CurrentCulture; // Save the old culture to set it back once we are done
- Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
- //Make sure the destination file ends in .obj - only .obj file format is supported.
- string extension = meshFilePath.Substring(meshFilePath.Length - 4);
- if (extension.ToLower() != ".obj")
- {
- Debug.LogError("Couldn't save to " + meshFilePath + ": Must save as .obj.");
- }
- lock (lockScanning)
- {
- Debug.Log("Saving mesh to " + meshFilePath);
- //Count how many vertices and triangles are in all the chunk meshes so we know how large of an array to allocate.
- int vertcount = 0;
- int tricount = 0;
- foreach (Chunk chunk in Chunks.Values)
- {
- vertcount += chunk.mesh.vertices.Length;
- tricount += chunk.mesh.triangles.Length;
- }
- if (vertcount == 0) return;
- Vector3[] vertices = new Vector3[vertcount];
- Vector2[] uvs = new Vector2[vertcount];
- Vector3[] normals = new Vector3[vertcount];
- int[] triangles = new int[tricount];
- int vertssofar = 0; //We keep an ongoing tally of how many verts/tris we've put so far so as to increment
- int trissofar = 0; //where we copy to in the arrays, and also to increment the vertex indices in the triangle array.
- for (int i = 0; i < Chunks.Keys.Count; i++)
- {
- Mesh chunkmesh = Chunks[i].mesh; //Shorthand.
- Array.Copy(chunkmesh.vertices, 0, vertices, vertssofar, chunkmesh.vertices.Length);
- Array.Copy(chunkmesh.uv, 0, uvs, vertssofar, chunkmesh.uv.Length);
- chunkmesh.RecalculateNormals();
- Array.Copy(chunkmesh.normals, 0, normals, vertssofar, chunkmesh.normals.Length);
- Array.Copy(chunkmesh.triangles, 0, triangles, trissofar, chunkmesh.triangles.Length);
- for (int t = trissofar; t < trissofar + chunkmesh.triangles.Length; t++)
- {
- triangles[t] += vertssofar;
- }
- vertssofar += chunkmesh.vertices.Length;
- trissofar += chunkmesh.triangles.Length;
- }
- Material savemat = Chunks[0].o.GetComponent<MeshRenderer>().material; //All chunks share the same material.
- //We'll need to know the base file name for this and the .mtl file. We'll extract it.
- //Since both forward and backslashes are valid for the file pack, determine which they used last.
- int forwardindex = meshFilePath.LastIndexOf('/');
- int backindex = meshFilePath.LastIndexOf('\\');
- int slashindex = forwardindex > backindex ? forwardindex : backindex;
- string basefilename = meshFilePath.Substring(slashindex + 1, meshFilePath.LastIndexOf(".") - slashindex - 1);
- //Create the string file.
- //Importantly, we flip the X value (and reverse the triangles) since the scanning module uses a different handedness than Unity.
- StringBuilder objstring = new StringBuilder();
- objstring.Append("# Generated by the ZED SDK.\n");
- if (isTextured)
- objstring.Append("mtllib " + basefilename + ".mtl"+ "\n");
- foreach (Vector3 vec in vertices)
- {
- //X is flipped because of Unity's handedness.
- objstring.Append(string.Format("v {0} {1} {2}\n", -vec.x, vec.y, vec.z));
- }
- objstring.Append("\n");
- foreach (Vector2 uv in uvs)
- {
- objstring.Append(string.Format("vt {0} {1}\n", uv.x, uv.y));
- }
- objstring.Append("\n");
- foreach (Vector3 norm in normals)
- {
- objstring.Append(string.Format("vn {0} {1} {2}\n", -norm.x, norm.y, norm.z));
- }
- //objstring.Append("\n");
- if (isTextured)
- {
- objstring.Append("usemtl ").Append("material0000").Append("\n");
- //objstring.Append("usemap ").Append(basefilename).Append("\n");
- }
- for (int i = 0; i < triangles.Length; i += 3)
- {
- //Triangles are reversed so that surface normals face the right way after the X vertex flip.
- objstring.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
- triangles[i + 2] + 1, triangles[i + 1] + 1, triangles[i + 0] + 1));
- }
- System.IO.StreamWriter swriter = new System.IO.StreamWriter(meshFilePath);
- swriter.Write(objstring.ToString());
- swriter.Close();
- //Create a texture and .mtl file for your scan, if textured.
- if (isTextured)
- {
- //First, the texture.
- //You can't save a Texture2D directly to a file since it's stored on the GPU.
- //So we use a RenderTexture as a buffer, which we can read into a new Texture2D on the CPU-side.
- Texture textosave = savemat.mainTexture;
- RenderTexture buffertex = new RenderTexture(textosave.width, textosave.height, 0);
- Graphics.Blit(textosave, buffertex, savemat);
- RenderTexture oldactivert = RenderTexture.active;
- RenderTexture.active = buffertex;
- Texture2D texcopy = new Texture2D(textosave.width, textosave.height);
- texcopy.ReadPixels(new Rect(0, 0, buffertex.width, buffertex.height), 0, 0);
- texcopy.Apply(); //It's now on the CPU!
- byte[] imagebytes = texcopy.EncodeToPNG();
- string imagepath = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + "_material0000_map_Kd.png";
- System.IO.File.WriteAllBytes(imagepath, imagebytes);
- RenderTexture.active = oldactivert;
- //Now the material file.
- StringBuilder mtlstring = new StringBuilder();
- mtlstring.Append("newmtl " + "material0000" + "\n");
- mtlstring.Append("Ka 1.000000 1.000000 1.000000\n");
- mtlstring.Append("Kd 1.000000 1.000000 1.000000\n");
- mtlstring.Append("Ks 0.000000 0.000000 0.000000\n");
- mtlstring.Append("Tr 1.000000\n");
- mtlstring.Append("illum 1\n");
- mtlstring.Append("Ns 1.000000\n");
- mtlstring.Append("map_Kd " + basefilename + "_material0000_map_Kd.png");
- mtlstring.AppendFormat("\n");
- string mtlpath = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + ".mtl";
- swriter = new System.IO.StreamWriter(mtlpath);
- swriter.Write(mtlstring.ToString());
- swriter.Close();
- }
- }
- //Save the .area file for spatial memory.
- string areaName = meshFilePath.Substring(0, meshFilePath.LastIndexOf(".")) + ".area";
- zedCamera.SaveCurrentArea(areaName);
- Thread.CurrentThread.CurrentCulture = oldCulture;
- }
- /// <summary>
- /// Used by Unity to draw the meshes in the editor with a double pass shader.
- /// </summary>
- #if UNITY_EDITOR
- private void OnDrawGizmos()
- {
- Gizmos.color = colorMesh;
- if (!IsRunning() && spatialMappingHelper != null && spatialMappingHelper.chunks.Count != 0 && display)
- {
- foreach (var submesh in spatialMappingHelper.chunks)
- {
- if (submesh.Value.proceduralMesh.mesh != null)
- {
- Gizmos.DrawWireMesh(submesh.Value.proceduralMesh.mesh.sharedMesh, submesh.Value.o.transform.position, submesh.Value.o.transform.rotation);
- }
- }
- }
- }
- #endif
- /// <summary>
- /// Low-level spatial mapping class. Calls SDK wrapper functions to get mesh data and applies it to Unity meshes.
- /// Functions are usually called from ZEDSpatialMapping, but buffer data is held within.
- /// Note that some values are updated directly from the ZED wrapper dll, so such assignments aren't visible in the plugin.
- /// </summary>
- private class ZEDSpatialMappingHelper
- {
- /// <summary>
- /// Reference to the ZEDCamera instance. Used to call SDK functions.
- /// </summary>
- private sl.ZEDCamera zedCamera;
- /// <summary>
- /// Maximum number of chunks. It's best to get relatively few chunks and to update them quickly.
- /// </summary>
- private const int MAX_SUBMESH = 1000;
- /*** Number of vertices/triangles/indices per chunk***/
- /// <summary>
- /// Total vertices in each chunk/submesh.
- /// </summary>
- private int[] numVerticesInSubmesh = new int[MAX_SUBMESH];
- /// <summary>
- /// Total triangles in each chunk/submesh.
- /// </summary>
- private int[] numTrianglesInSubmesh = new int[MAX_SUBMESH];
- /// <summary>
- /// Total indices per chunk/submesh.
- /// </summary>
- private int[] UpdatedIndices = new int[MAX_SUBMESH];
- /*** Number of vertices/uvs/indices at the moment**/
- /// <summary>
- /// Vertex count in current submesh.
- /// </summary>
- private int numVertices = 0;
- /// <summary>
- /// Triangle point counds in current submesh. (Every three values are the indexes of the three vertexes that make up one triangle)
- /// </summary>
- private int numTriangles = 0;
- /// <summary>
- /// How many submeshes were updated.
- /// </summary>
- private int numUpdatedSubmesh = 0;
- /*** The current data in the current submesh***/
- /// <summary>
- /// Vertices of the current submesh.
- /// </summary>
- private Vector3[] vertices;
- /// <summary>
- /// UVs of the current submesh.
- /// </summary>
- private Vector2[] uvs;
- /// <summary>
- /// Triangles of the current submesh. (Each int refers to the index of a vertex)
- /// </summary>
- private int[] triangles;
- /// <summary>
- /// Width and height of the mesh texture, if any.
- /// </summary>
- private int[] texturesSize = new int[2];
- /// <summary>
- /// Dictionary of all existing chunks.
- /// </summary>
- public Dictionary<int, ZEDSpatialMapping.Chunk> chunks = new Dictionary<int, ZEDSpatialMapping.Chunk>(MAX_SUBMESH);
- /// <summary>
- /// Material with real-world texture, applied to the mesh when Texturing (isTextured) is enabled.
- /// </summary>
- private Material materialTexture;
- /// <summary>
- /// Material used to draw the mesh. Applied to chunks during the scan, and replaced with materialTexture
- /// only if Texturing (isTextured) is enabled.
- /// </summary>
- private Material materialMesh;
- /// <summary>
- /// Public accessor for the number of chunks that have been updated.
- /// </summary>
- public int NumberUpdatedSubMesh
- {
- get { return numUpdatedSubmesh; }
- }
- /// <summary>
- /// Gets the material used to draw spatial mapping meshes without real-world textures.
- /// </summary>
- /// <returns></returns>
- public Material GetMaterialSpatialMapping()
- {
- return materialMesh;
- }
- /// <summary>
- /// Constructor. Gets the ZEDCamera instance and sets materials used on the meshes.
- /// </summary>
- /// <param name="materialTexture"></param>
- /// <param name="materialMesh"></param>
- public ZEDSpatialMappingHelper(sl.ZEDCamera camera, Material materialTexture, Material materialMesh)
- {
- zedCamera = camera;
- this.materialTexture = materialTexture;
- this.materialMesh = materialMesh;
- }
- /// <summary>
- /// Updates the range to match the specified preset.
- /// </summary>
- static public float ConvertRangePreset(RANGE rangePreset)
- {
- if (rangePreset == RANGE.NEAR)
- {
- return 3.5f;
- }
- else if (rangePreset == RANGE.MEDIUM)
- {
- return 5.0f;
- }
- if (rangePreset == RANGE.FAR)
- {
- return 10.0f;
- }
- return 5.0f;
- }
- /// <summary>
- /// Updates the resolution to match the specified preset.
- /// </summary>
- static public float ConvertResolutionPreset(RESOLUTION resolutionPreset)
- {
- if (resolutionPreset == RESOLUTION.HIGH)
- {
- return 0.05f;
- }
- else if (resolutionPreset == RESOLUTION.MEDIUM)
- {
- return 0.10f;
- }
- if (resolutionPreset == RESOLUTION.LOW)
- {
- return 0.15f;
- }
- return 0.10f;
- }
- /// <summary>
- /// Tells the ZED SDK to begin spatial mapping.
- /// </summary>
- /// <returns></returns>
- public sl.ERROR_CODE EnableSpatialMapping(sl.SPATIAL_MAP_TYPE type,float resolutionMeter, float maxRangeMeter, bool saveTexture)
- {
- return zedCamera.EnableSpatialMapping(type,resolutionMeter, maxRangeMeter, saveTexture);
- }
- /// <summary>
- /// Tells the ZED SDK to stop spatial mapping.
- /// </summary>
- public void DisableSpatialMapping()
- {
- zedCamera.DisableSpatialMapping();
- }
- /// <summary>
- /// Create a new submesh to contain the data retrieved from the ZED.
- /// </summary>
- public ZEDSpatialMapping.Chunk CreateNewMesh(int i, Material meshMat, Transform holder)
- {
- //Initialize the chunk and create a GameObject for it.
- ZEDSpatialMapping.Chunk chunk = new ZEDSpatialMapping.Chunk();
- chunk.o = GameObject.CreatePrimitive(PrimitiveType.Quad);
- chunk.o.layer = zedCamera.TagInvisibleToZED;
- chunk.o.GetComponent<MeshCollider>().sharedMesh = null;
- chunk.o.name = "Chunk" + chunks.Count;
- chunk.o.transform.localPosition = Vector3.zero;
- chunk.o.transform.localRotation = Quaternion.identity;
- Mesh m = new Mesh();
- m.MarkDynamic(); //Allows it to be updated regularly without performance issues.
- chunk.mesh = m;
- //Set graphics settings to not treat the chunk like a physical object (no shadows, no reflections, no lights, etc.).
- MeshRenderer meshRenderer = chunk.o.GetComponent<MeshRenderer>();
- meshRenderer.material = meshMat;
- meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- meshRenderer.receiveShadows = false;
- meshRenderer.enabled = true;
- meshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
- meshRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
- //Sets the position and parent of the chunk.
- chunk.o.transform.parent = holder;
- chunk.o.layer = zedCamera.TagInvisibleToZED;
- //Add the chunk to the dictionary.
- chunk.proceduralMesh.mesh = chunk.o.GetComponent<MeshFilter>();
- chunks.Add(i, chunk);
- return chunk;
- }
- /// <summary>
- /// Adds a MeshCollider to each chunk for physics. This is time-consuming, so it's only called
- /// once scanning is finished and the final mesh is being processed.
- /// </summary>
- public void UpdateMeshCollider(List<ZEDSpatialMapping.Chunk> listMeshes, int startIndex = 0)
- {
- List<int> idsToDestroy = new List<int>(); //List of meshes that are too small for colliders and will be destroyed.
- //Update each mesh with a collider.
- for (int i = startIndex; i < listMeshes.Count; ++i)
- {
- var submesh = listMeshes[i];
- MeshCollider m = submesh.o.GetComponent<MeshCollider>();
- if (m == null)
- {
- m = submesh.o.AddComponent<MeshCollider>();
- }
- //If a mesh has 2 or fewer vertices, it's useless, so queue it up to be destroyed.
- Mesh tempMesh = submesh.o.GetComponent<MeshFilter>().sharedMesh;
- if (tempMesh.vertexCount < 3)
- {
- idsToDestroy.Add(i);
- continue;
- }
- m.sharedMesh = tempMesh;
- m.sharedMesh.RecalculateNormals();
- m.sharedMesh.RecalculateBounds();
- }
- //Destroy all useless meshes now that we've iterated through all the meshes.
- for (int i = 0; i < idsToDestroy.Count; ++i)
- {
- GameObject.Destroy(chunks[idsToDestroy[i]].o);
- chunks.Remove(idsToDestroy[i]);
- }
- Clear(); //Clear the buffer data now that we have Unity meshes.
- }
- /// <summary>
- /// Tells the ZED SDK to calculate the size of the texture and the UVs.
- /// </summary>
- public void ApplyTexture()
- {
- zedCamera.ApplyTexture(numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, texturesSize, MAX_SUBMESH);
- }
- /// <summary>
- /// Tells the ZED SDK to update its internal mesh from spatial mapping. The resulting mesh will later be retrieved with RetrieveMesh().
- /// </summary>
- public void UpdateMesh()
- {
- zedCamera.UpdateMesh(numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
- ResizeMesh();
- }
- /// <summary>
- /// Retrieves the mesh vertices and triangles from the ZED SDK. This must be called after UpdateMesh() has been called.
- /// Note that the actual assignment to vertices and triangles happens from within the wrapper .dll via pointers, not a C# script.
- /// </summary>
- public void RetrieveMesh()
- {
- zedCamera.RetrieveMesh(vertices, triangles, MAX_SUBMESH, null, System.IntPtr.Zero);
- }
- /// <summary>
- /// Clear the current buffer data.
- /// </summary>
- public void Clear()
- {
- chunks.Clear();
- vertices = new Vector3[0];
- triangles = new int[0];
- uvs = new Vector2[0];
- System.Array.Clear(vertices, 0, vertices.Length);
- System.Array.Clear(triangles, 0, triangles.Length);
- }
- /// <summary>
- /// Process data from a submesh retrieved from the ZED SDK into a chunk, which includes a GameObject and visible mesh.
- /// </summary>
- /// <param name="indexUpdate">Index of the submesh/chunk to be updated.</param>
- /// <param name="verticesOffset">Starting index in the vertices stack.</param>
- /// <param name="trianglesOffset">Starting index in the triangles stack.</param>
- /// <param name="uvsOffset">Starting index in the UVs stack.</param>
- /// <param name="transform">Transform of the holder object to which all chunks are parented.</param>
- /// <param name="updatedTex"><c>True</c> if the world texture has been updated so we know to update UVs.</param>
- public void SetMesh(int indexUpdate, ref int verticesOffset, ref int trianglesOffset, ref int uvsOffset, Transform holder, bool updatedTex)
- {
- ZEDSpatialMapping.Chunk subMesh;
- int updatedIndex = UpdatedIndices[indexUpdate];
- if (!chunks.TryGetValue(updatedIndex, out subMesh)) //Use the existing chunk/submesh if already in the dictionary. Otherwise, make a new one.
- {
- subMesh = CreateNewMesh(updatedIndex, materialMesh, holder);
- }
- Mesh currentMesh = subMesh.mesh;
- ZEDSpatialMapping.ProceduralMesh dynamicMesh = subMesh.proceduralMesh;
- //If the dynamicMesh's triangle and vertex arrays are unassigned or are the wrong size, redo the array.
- if (dynamicMesh.triangles == null || dynamicMesh.triangles.Length != 3 * numTrianglesInSubmesh[indexUpdate])
- {
- dynamicMesh.triangles = new int[3 * numTrianglesInSubmesh[indexUpdate]];
- }
- if (dynamicMesh.vertices == null || dynamicMesh.vertices.Length != numVerticesInSubmesh[indexUpdate])
- {
- dynamicMesh.vertices = new Vector3[numVerticesInSubmesh[indexUpdate]];
- }
- //Clear the old mesh data.
- currentMesh.Clear();
- //Copy data retrieved from the ZED SDK into the ProceduralMesh buffer in the current chunk.
- System.Array.Copy(vertices, verticesOffset, dynamicMesh.vertices, 0, numVerticesInSubmesh[indexUpdate]);
- verticesOffset += numVerticesInSubmesh[indexUpdate];
- System.Buffer.BlockCopy(triangles, trianglesOffset * sizeof(int), dynamicMesh.triangles, 0, 3 * numTrianglesInSubmesh[indexUpdate] * sizeof(int)); //Block copy has better performance than Array.
- trianglesOffset += 3 * numTrianglesInSubmesh[indexUpdate];
- currentMesh.vertices = dynamicMesh.vertices;
- currentMesh.SetTriangles(dynamicMesh.triangles, 0, false);
- dynamicMesh.mesh.sharedMesh = currentMesh;
- //If textured, add UVs.
- if (updatedTex)
- {
- Vector2[] localUvs = new Vector2[numVerticesInSubmesh[indexUpdate]];
- subMesh.o.GetComponent<MeshRenderer>().sharedMaterial = materialTexture;
- System.Array.Copy(uvs, uvsOffset, localUvs, 0, numVerticesInSubmesh[indexUpdate]);
- uvsOffset += numVerticesInSubmesh[indexUpdate];
- currentMesh.uv = localUvs;
- }
- }
- /// <summary>
- /// Retrieves the entire mesh and texture (vertices, triangles, and uvs) from the ZED SDK.
- /// Differs for normal retrieval as the UVs and texture are retrieved.
- /// This is only called after scanning has been stopped, and only if Texturing is enabled.
- /// </summary>
- public void SetMeshAndTexture()
- {
- //If the texture is too large, it's impossible to add the texture to the mesh.
- if (texturesSize[0] > 8192) return;
- Texture2D textureMesh = new Texture2D(texturesSize[0], texturesSize[1], TextureFormat.ARGB32, false);
- if (textureMesh != null)
- {
- materialTexture.SetTexture("_MainTex", textureMesh);
- vertices = new Vector3[numVertices];
- uvs = new Vector2[numVertices];
- triangles = new int[3 * numTriangles];
- System.IntPtr texture = textureMesh.GetNativeTexturePtr();
- zedCamera.RetrieveMesh(vertices, triangles, MAX_SUBMESH, uvs, texture);
- }
- }
- /// <summary>
- /// Loads a mesh from a file path and allocates the buffers accordingly.
- /// </summary>
- /// <param name="meshFilePath">Path to the mesh file.</param>
- /// <returns></returns>
- public bool LoadMesh(string meshFilePath)
- {
- bool r = zedCamera.LoadMesh(meshFilePath, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH, texturesSize);
- if (!r) Debug.LogWarning("[ZED] Failed to load mesh: "+ meshFilePath);
- vertices = new Vector3[numVertices];
- uvs = new Vector2[numVertices];
- triangles = new int[3 * numTriangles];
- return r;
- }
- /// <summary>
- /// Gets the width of the scanned texture file. Note that if this is over 8k, the texture will not be taken.
- /// </summary>
- /// <returns>Texture width in pixels.</returns>
- public int GetWidthTexture()
- {
- return texturesSize[0];
- }
- public int GetHeightTexture()
- {
- return texturesSize[1];
- }
- /// <summary>
- /// Resize the mesh buffer according to how many vertices are needed by the current submesh/chunk.
- /// </summary>
- public void ResizeMesh()
- {
- if (vertices.Length < numVertices)
- {
- vertices = new Vector3[numVertices * 2]; //Allocation is faster than resizing.
- }
- if (triangles.Length < 3 * numTriangles)
- {
- triangles = new int[3 * numTriangles * 2];
- }
- }
- /// <summary>
- /// Filters the mesh with predefined parameters.
- /// </summary>
- /// <param name="filterParameters">Filter setting. A higher setting results in fewer faces.</param>
- public void FilterMesh(sl.FILTER filterParameters)
- {
- zedCamera.FilterMesh(filterParameters, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
- }
- /// <summary>
- /// Tells the ZED SDK to consolidate the chunks into a smaller number of large chunks.
- /// Useful because having many small chunks is more performant for scanning, but fewer large chunks are otherwise easier to work with.
- /// </summary>
- public void MergeChunks()
- {
- zedCamera.MergeChunks(MAX_SUBMESH, numVerticesInSubmesh, numTrianglesInSubmesh, ref numUpdatedSubmesh, UpdatedIndices, ref numVertices, ref numTriangles, MAX_SUBMESH);
- }
- }
- }
|