//======= 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);
        }

    }
}
