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