/// <summary>
            /// Saves intermediate data used for building a navigation mesh
            /// </summary>
            /// <param name="objectId">The unique Id for this data in the object database</param>
            /// <param name="build">The build data to save</param>
            private void SaveIntermediateData(ObjectId objectId, NavigationMeshCachedBuild build)
            {
                var objectDatabase = ContentManager.FileProvider.ObjectDatabase;

                using (var stream = objectDatabase.OpenStream(objectId, VirtualFileMode.Create, VirtualFileAccess.Write))
                {
                    var writer = new BinarySerializationWriter(stream);
                    writer.Serialize(ref build, ArchiveMode.Serialize);
                    writer.Flush();
                }
            }
 /// <summary>
 /// Loads intermediate data used for building a navigation mesh
 /// </summary>
 /// <param name="objectId">The unique Id for this data in the object database</param>
 /// <returns>The found cached build or null if there is no previous build</returns>
 private NavigationMeshCachedBuild LoadIntermediateData(ObjectId objectId)
 {
     try
     {
         var objectDatabase = ContentManager.FileProvider.ObjectDatabase;
         using (var stream = objectDatabase.OpenStream(objectId))
         {
             var reader = new BinarySerializationReader(stream);
             NavigationMeshCachedBuild result = new NavigationMeshCachedBuild();
             reader.Serialize(ref result, ArchiveMode.Deserialize);
             return(result);
         }
     }
     catch (Exception ex)
     {
         return(null);
     }
 }
            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));
            }