/// <summary>
 /// <para>Constructor for conveniently filling out a SurfaceData struct.</para>
 /// </summary>
 /// <param name="_id">ID for the surface in question.</param>
 /// <param name="_outputMesh">MeshFilter to write Mesh data to.</param>
 /// <param name="_outputAnchor">WorldAnchor receiving the anchor point for the surface.</param>
 /// <param name="_outputCollider">MeshCollider to write baked physics data to (optional).</param>
 /// <param name="_trianglesPerCubicMeter">Requested resolution for the computed Mesh.  Actual resolution may be less than this value.</param>
 /// <param name="_bakeCollider">Set to true if collider baking is/has been requested.</param>
 public SurfaceData(SurfaceId _id, MeshFilter _outputMesh, WorldAnchor _outputAnchor, MeshCollider _outputCollider, float _trianglesPerCubicMeter, bool _bakeCollider)
 {
     this.id = _id;
     this.outputMesh = _outputMesh;
     this.outputAnchor = _outputAnchor;
     this.outputCollider = _outputCollider;
     this.trianglesPerCubicMeter = _trianglesPerCubicMeter;
     this.bakeCollider = _bakeCollider;
 }
 /// <summary>
 /// Called when a surface is going to be added, removed, or updated. 
 /// We only care about removal so we can remove our internal copy of the surface mesh.
 /// </summary>
 /// <param name="surfaceId">The surface ID that is being added/removed/updated</param>
 /// <param name="changeType">Added | Removed | Updated</param>
 /// <param name="bounds">The world volume the mesh is in.</param>
 /// <param name="updateTime">When the mesh was updated.</param>
 private void MappingObserver_SurfaceChanged(SurfaceId surfaceId, SurfaceChange changeType, Bounds bounds, DateTime updateTime)
 {
     // We only need to worry about removing meshes from our list.  Adding and updating is 
     // done when the mesh data is actually ready.
     if (changeType == SurfaceChange.Removed)
     {
         int meshIndex = FindMeshIndexInInputMeshList(surfaceId.handle);
         if(meshIndex >= 0)
         {
             inputMeshList.RemoveAt(meshIndex);
         }
     }
 }
    /// <summary>
    /// Handles when a surface is added or updated by either creating the needed components or finding them in either the RemovedMeshObjects collection or the SpatialMeshObjects collection
    /// 
    /// If a surface is contained in the RemovedMeshObjects collection, the enabled state for its MeshCollider or MeshRenderer is restored if appropriate. The GameObject will be moved into SpatialMeshObjects, and the RemovedMeshHolder will be removed from RemovedMeshObject
    /// If a surface is not found in either collection, a new GameObject will be created for it and it will be added to SpatialMeshObjects indexed by its id.
    /// After the GameObject is handled as appropriately, SurfaceObserver.RequestMeshAsync will be called for the appropriate settings.
    /// </summary>
    /// <param name="surfaceId">The id of the surface that was added or updated</param>
    /// <param name="updateTime">The time at which the surface was modified</param>
    /// <param name="bake">Whether or not this component should request to back a collider for the surface</param>
    protected virtual void HandleAdd(SurfaceId surfaceId, System.DateTime updateTime, bool bake)
    {
        if (RemovedMeshObjects.ContainsKey(surfaceId))
        {
            SpatialMeshObjects[surfaceId] = RemovedMeshObjects[surfaceId].gameObject;
            if (RemovedMeshObjects[surfaceId].wasMeshColliderEnabled)
            {
                MeshCollider collider = SpatialMeshObjects[surfaceId].GetComponent<MeshCollider>();
                collider.enabled = true;
            }
            if (RemovedMeshObjects[surfaceId].wasMeshRendererEnabled)
            {
                MeshRenderer mr = SpatialMeshObjects[surfaceId].GetComponent<MeshRenderer>();
                mr.enabled = true;
            }

            RemovedMeshObjects.Remove(surfaceId);
        }
        else if (!SpatialMeshObjects.ContainsKey(surfaceId))
        {
            SpatialMeshObjects[surfaceId] = new GameObject("spatial-mapping-" + surfaceId.handle);
            SpatialMeshObjects[surfaceId].transform.parent = this.transform;
        }
        GameObject target = SpatialMeshObjects[surfaceId];
        SurfaceData sd = new SurfaceData(
                //the surface id returned from the system
                surfaceId,
                //the mesh filter that is populated with the spatial mapping data for this mesh
                (target.GetComponent<MeshFilter>() == null) ? target.AddComponent<MeshFilter>() : target.GetComponent<MeshFilter>(),
                //the world anchor used to position the spatial mapping mesh in the world
                (target.GetComponent<WorldAnchor>() == null) ? target.AddComponent<WorldAnchor>() : target.GetComponent<WorldAnchor>(),
                //the mesh collider that is populated with collider data for this mesh, if true is passed to bakeMeshes below
                (target.GetComponent<MeshCollider>() == null) ? target.AddComponent<MeshCollider>() : target.GetComponent<MeshCollider>(),
                //triangles per cubic meter requested for this mesh
                TrianglesPerCubicMeter,
                //bakeMeshes - if true, the mesh collider is populated, if false, the mesh collider is empty.
                bake
                );
        surfaceObserver.RequestMeshAsync(sd, SurfaceObserver_OnDataReady);
    }
    /// <summary>
    /// Handles cleaning up a known mesh or moving it to the RemovedMeshObjects list
    /// 
    /// If NumUpdatesBeforeRemoval < 1, the mesh will immediately be destroyed upon removal
    /// Else we will create a new RemoveSurfaceHolder and add that to the RemovedMeshObjects list to cache the mesh until we believe it should actually be removed
    /// </summary>
    /// <param name="surfaceId"></param>
    protected virtual void HandleDelete(SurfaceId surfaceId)
    {
        GameObject obj = SpatialMeshObjects[surfaceId];
        SpatialMeshObjects.Remove(surfaceId);

        if (NumUpdatesBeforeRemoval < 1)
        {
            FinalDestroy(obj);
        }
        else if (obj != null)
        {
            bool shouldDisable = !ShouldBeActiveWhileRemoved(obj);

            MeshCollider collider = obj.GetComponent<MeshCollider>();
            bool collisionsEnabled = (collider != null && collider.enabled);
            if (collisionsEnabled && shouldDisable)
            {
                collider.enabled = false;
            }

            MeshRenderer mr = obj.GetComponent<MeshRenderer>();
            bool rendererEnabled = (mr != null && mr.enabled);
            if (rendererEnabled && shouldDisable)
            {
                mr.enabled = false;
            }
            RemovedSurfaceHolder holder = new RemovedSurfaceHolder(NumUpdatesBeforeRemoval + 1 /*+1 because we are going to process it now*/, obj, surfaceId, collisionsEnabled, rendererEnabled);
            RemovedMeshObjects.Add(surfaceId, holder);
        }
    }
 /// <summary>
 /// Handler for calling SurfaceObserver.Update which will then handle the changes
 /// 
 /// The actual changes will be handled via HandleAdd (for SurfaceChange.Added and SurfaceChange.Updated) and HandleDelete (for SurfaceChange.Removed)
 /// </summary>
 /// <param name="surfaceId">The identifier of the surface in question</param>
 /// <param name="changeType">What type of change this is (add, update, or remove)</param>
 /// <param name="bounds">The bounds of the mesh</param>
 /// <param name="updateTime">The time the update occurred</param>
 protected virtual void SurfaceObserver_OnSurfaceChanged(SurfaceId surfaceId, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
 {
     switch (changeType)
     {
         case SurfaceChange.Added:
         case SurfaceChange.Updated:
             HandleAdd(surfaceId, updateTime, bakeMeshes);
             break;
         case SurfaceChange.Removed:
             HandleDelete(surfaceId);
             break;
         default:
             break;
     }
 }
 /// <summary>
 /// Constructor for the struct taking all the parameters in
 /// </summary>
 /// <param name="updatesBeforeRemoval">The number of updates which will pass before this object is removed.</param>
 /// <param name="gameObject">The GameObject owner of all of the mesh components</param>
 /// <param name="id">The SurfaceId identifying this surface</param>
 /// <param name="wasMeshColliderEnabled">Whether or not a MeshCollider was present and enabled</param>
 /// <param name="wasMeshRendererEnabled">Whether or not a MeshRenderer was present and enabled</param>
 public RemovedSurfaceHolder(int updatesBeforeRemoval, GameObject gameObject, SurfaceId id, bool wasMeshColliderEnabled, bool wasMeshRendererEnabled)
 {
     this.updatesBeforeRemoval = updatesBeforeRemoval;
     this.gameObject = gameObject;
     this.id = id;
     this.wasMeshColliderEnabled = wasMeshColliderEnabled;
     this.wasMeshRendererEnabled = wasMeshRendererEnabled;
 }
        /// <summary>
        /// Handles the SurfaceObserver's OnSurfaceChanged event.
        /// </summary>
        /// <param name="id">The identifier assigned to the surface which has changed.</param>
        /// <param name="changeType">The type of change that occurred on the surface.</param>
        /// <param name="bounds">The bounds of the surface.</param>
        /// <param name="updateTime">The date and time at which the change occurred.</param>
        private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
        {
            // Verify that the client of the Surface Observer is expecting updates.
            if (ObserverState != ObserverStates.Running)
            {
                return;
            }

            GameObject surface;

            switch (changeType)
            {
                // Adding and updating are nearly identical.  The only difference is if a new GameObject to contain
                // the surface needs to be created.
                case SurfaceChange.Added:
                case SurfaceChange.Updated:
                    // Check to see if the surface is known to the observer.
                    if (!surfaces.TryGetValue(id.handle, out surface))
                    {
                        // If we are adding a new surface, construct a GameObject
                        // to represent its state and attach some Mesh-related
                        // components to it.
                        surface = AddSurfaceObject(null, string.Format("Surface-{0}", id.handle), transform);

                        surface.AddComponent<WorldAnchor>();

                        // Add the surface to our dictionary of known surfaces so
                        // we can interact with it later.
                        surfaces.Add(id.handle, surface);
                    }

                    // Add the request to create the mesh for this surface to our work queue.
                    QueueSurfaceDataRequest(id, surface);
                    break;

                case SurfaceChange.Removed:
                    // Always process surface removal events.
                    if (surfaces.TryGetValue(id.handle, out surface))
                    {
                        RemoveSurfaceObject(surface);
                        surfaces.Remove(id.handle);
                    }
                    break;
            }
        }
        /// <summary>
        /// Calls GetMeshAsync to update the SurfaceData and re-activate the surface object when ready.
        /// </summary>
        /// <param name="id">Identifier of the SurfaceData object to update.</param>
        /// <param name="surface">The SurfaceData object to update.</param>
        private void QueueSurfaceDataRequest(SurfaceId id, GameObject surface)
        {
            SurfaceData surfaceData = new SurfaceData(id,
                                                        surface.GetComponent<MeshFilter>(),
                                                        surface.GetComponent<WorldAnchor>(),
                                                        surface.GetComponent<MeshCollider>(),
                                                        TrianglesPerCubicMeter,
                                                        true);

            surfaceWorkQueue.Enqueue(surfaceData);
        }
        /// <summary>
        /// Handles the SurfaceObserver's OnSurfaceChanged event.
        /// </summary>
        /// <param name="id">The identifier assigned to the surface which has changed.</param>
        /// <param name="changeType">The type of change that occurred on the surface.</param>
        /// <param name="bounds">The bounds of the surface.</param>
        /// <param name="updateTime">The date and time at which the change occurred.</param>
        private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
        {
            // Verify that the client of the Surface Observer is expecting updates.
            if (ObserverState != ObserverStates.Running)
            {
                return;
            }

            GameObject surface;

            switch (changeType)
            {
                // Adding and updating are nearly identical.  The only difference is if a new gameobject to contain
                // the surface needs to be created.
                case SurfaceChange.Added:
                case SurfaceChange.Updated:
                    // Check to see if the surface is known to the observer.
                    // If so, we want to add it for cleanup after we get new meshes
                    // We do this because Unity doesn't properly cleanup baked collision data
                    if (surfaces.TryGetValue(id.handle, out surface))
                    {
                        pendingCleanup.Add(id.handle, surface);
                        surfaces.Remove(id.handle);
                    }

                    // Get an available surface object ready to be used
                    surface = GetSurfaceObject(id.handle, transform);

                    // Add the surface to our dictionary of known surfaces so
                    // we can interact with it later.
                    surfaces.Add(id.handle, surface);

                    // Add the request to create the mesh for this surface to our work queue.
                    QueueSurfaceDataRequest(id, surface);
                    break;

                case SurfaceChange.Removed:
                    // Always process surface removal events.
                    // This code can be made more thread safe
                    if (surfaces.TryGetValue(id.handle, out surface))
                    {
                        surfaces.Remove(id.handle);
                        CleanupSurface(surface);
                        RemoveSurfaceObject(surface, false);
                    }
                    break;
            }

            // Event
            if (SurfaceChanged != null)
            {
                SurfaceChanged(id, changeType, bounds, updateTime);
            }
        }