/// <summary>
        /// Clamps X-Z coordinates to a navigation mesh tile
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="boundingBox"></param>
        /// <param name="tileCoord"></param>
        /// <returns></returns>
        public static BoundingBox ClampBoundingBoxToTile(NavigationMeshBuildSettings settings, BoundingBox boundingBox, Point tileCoord)
        {
            float   tcs     = settings.TileSize * settings.CellSize;
            Vector2 tileMin = new Vector2(tileCoord.X * tcs, tileCoord.Y * tcs);
            Vector2 tileMax = tileMin + new Vector2(tcs);

            boundingBox.Minimum.X = tileMin.X;
            boundingBox.Minimum.Z = tileMin.Y;
            boundingBox.Maximum.X = tileMax.X;
            boundingBox.Maximum.Z = tileMax.Y;

            // Snap Y to tile height to avoid height differences between tiles
            boundingBox.Minimum.Y = (float)Math.Floor(boundingBox.Minimum.Y / settings.CellHeight) * settings.CellHeight;
            boundingBox.Maximum.Y = (float)Math.Ceiling(boundingBox.Maximum.Y / settings.CellHeight) * settings.CellHeight;

            return(boundingBox);
        }
        /// <summary>
        /// Check which tiles overlap a given bounding box
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="boundingBox"></param>
        /// <returns></returns>
        public static List <Point> GetOverlappingTiles(NavigationMeshBuildSettings settings, BoundingBox boundingBox)
        {
            List <Point> ret       = new List <Point>();
            float        tcs       = settings.TileSize * settings.CellSize;
            Vector2      start     = boundingBox.Minimum.XZ() / tcs;
            Vector2      end       = boundingBox.Maximum.XZ() / tcs;
            Point        startTile = new Point(
                (int)Math.Floor(start.X),
                (int)Math.Floor(start.Y));
            Point endTile = new Point(
                (int)Math.Ceiling(end.X),
                (int)Math.Ceiling(end.Y));

            for (int y = startTile.Y; y < endTile.Y; y++)
            {
                for (int x = startTile.X; x < endTile.X; x++)
                {
                    ret.Add(new Point(x, y));
                }
            }
            return(ret);
        }
            protected override Task <ResultStatus> DoCommandOverride(ICommandContext commandContext)
            {
                var intermediateDataId = ComputeAssetIntermediateDataId();

                // Build cache items to build incrementally
                currentBuild = new NavigationMeshCachedBuild();
                oldBuild     = LoadIntermediateData(intermediateDataId);

                // The output object of the compilation
                NavigationMesh generatedNavigationMesh = new NavigationMesh();

                // No scene specified, result in failure
                if (asset.Scene == null)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                if (asset.AutoGenerateBoundingBox)
                {
                    generateBoundingBox = true;
                    globalBoundingBox   = BoundingBox.Empty;
                }
                else
                {
                    generateBoundingBox = false;
                    globalBoundingBox   = asset.BoundingBox;
                }

                // Copy build settings so we can modify them
                buildSettings = asset.BuildSettings;

                // Check for tile size
                if (buildSettings.TileSize <= 0)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                // Clone scene, obtain hash and load collider shape assets
                EnsureClonedSceneAndHash();
                if (clonedSceneAsset == null)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                // This collects all the input geometry, calculates the modified areas and calculates the scene bounds
                CollectInputGeometry();
                List <BoundingBox> removedAreas = oldBuild?.GetRemovedAreas(sceneEntities);

                BoundingBox boundingBox = globalBoundingBox;

                // Can't generate when no bounding box or and invalid bounding box is specified
                // this means that either the user specified bounding box is wrong or the scene does not contain any colliders
                if (boundingBox.Extent.X > 0 && boundingBox.Extent.Y > 0 && boundingBox.Extent.Z > 0)
                {
                    // Turn generated data into arrays
                    Vector3[] meshVertices = sceneNavigationMeshInputBuilder.Points.ToArray();
                    int[]     meshIndices  = sceneNavigationMeshInputBuilder.Indices.ToArray();

                    // NOTE: Reversed winding order as input to recast
                    int numSrcTriangles = meshIndices.Length / 3;
                    for (int i = 0; i < numSrcTriangles; i++)
                    {
                        int j = meshIndices[i * 3 + 1];
                        meshIndices[i * 3 + 1] = meshIndices[i * 3 + 2];
                        meshIndices[i * 3 + 2] = j;
                    }

                    // Check if settings changed to trigger a full rebuild
                    int currentSettingsHash = asset.GetHashCode();
                    currentBuild.SettingsHash = currentSettingsHash;
                    if (oldBuild != null && oldBuild.SettingsHash != currentBuild.SettingsHash)
                    {
                        fullRebuild = true;
                    }

                    if (oldBuild != null && !fullRebuild)
                    {
                        // Perform incremental build on old navigation mesh
                        generatedNavigationMesh = oldBuild.NavigationMesh;
                    }

                    // Initialize navigation mesh for building
                    generatedNavigationMesh.Initialize(buildSettings, asset.NavigationMeshAgentSettings.ToArray());

                    // Set the new navigation mesh in the current build
                    currentBuild.NavigationMesh = generatedNavigationMesh;

                    // Generate all the layers corresponding to the various agent settings
                    for (int layer = 0; layer < asset.NavigationMeshAgentSettings.Count; layer++)
                    {
                        Stopwatch layerBuildTimer = new Stopwatch();
                        layerBuildTimer.Start();
                        var agentSetting = asset.NavigationMeshAgentSettings[layer];

                        // Flag tiles to build for this specific layer
                        HashSet <Point> tilesToBuild = new HashSet <Point>();
                        if (fullRebuild)
                        {
                            // For full rebuild just take the root bounding box for selecting tiles to build
                            List <Point> newTileList = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, boundingBox);
                            foreach (Point p in newTileList)
                            {
                                tilesToBuild.Add(p);
                            }
                        }
                        else
                        {
                            // Apply an offset so their neighbouring tiles which are affected by the agent radius also get rebuild
                            Vector3 agentOffset = new Vector3(agentSetting.Radius, 0, agentSetting.Radius);
                            if (removedAreas != null)
                            {
                                updatedAreas.AddRange(removedAreas);
                            }
                            foreach (var update in updatedAreas)
                            {
                                BoundingBox agentSpecificBoundingBox = new BoundingBox
                                {
                                    Minimum = update.Minimum - agentOffset,
                                    Maximum = update.Maximum + agentOffset,
                                };
                                List <Point> newTileList = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, agentSpecificBoundingBox);
                                foreach (Point p in newTileList)
                                {
                                    tilesToBuild.Add(p);
                                }
                            }
                        }

                        // Build tiles
                        foreach (var tileToBuild in tilesToBuild)
                        {
                            BoundingBox tileBoundingBox = NavigationMeshBuildUtils.ClampBoundingBoxToTile(buildSettings, boundingBox, tileToBuild);
                            // Check if tile bounding box is contained withing the navigation mesh bounding box
                            if (boundingBox.Contains(ref tileBoundingBox) == ContainmentType.Disjoint)
                            {
                                // Remove this tile
                                generatedNavigationMesh.Layers[layer].RemoveLayerTile(tileToBuild);
                                continue;
                            }

                            // Build the tile for the current layer being processed
                            generatedNavigationMesh.Layers[layer].BuildTile(meshVertices.ToArray(), meshIndices.ToArray(), tileBoundingBox, tileToBuild);
                        }
                    }
                }

                // Store used bounding box in navigation mesh
                generatedNavigationMesh.BoundingBox = boundingBox;

                contentManager.Save(assetUrl, generatedNavigationMesh);
                SaveIntermediateData(intermediateDataId, currentBuild);

                return(Task.FromResult(ResultStatus.Successful));
            }