void InitializeTileInfo() { // Voxel grid size int totalVoxelWidth = (int)(forcedBoundsSize.x / cellSize + 0.5f); int totalVoxelDepth = (int)(forcedBoundsSize.z / cellSize + 0.5f); if (!useTiles) { tileSizeX = totalVoxelWidth; tileSizeZ = totalVoxelDepth; } else { tileSizeX = editorTileSize; tileSizeZ = editorTileSize; } // Number of tiles tileXCount = (totalVoxelWidth + tileSizeX - 1) / tileSizeX; tileZCount = (totalVoxelDepth + tileSizeZ - 1) / tileSizeZ; if (tileXCount * tileZCount > TileIndexMask + 1) { throw new System.Exception("Too many tiles (" + (tileXCount * tileZCount) + ") maximum is " + (TileIndexMask + 1) + "\nTry disabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* inspector."); } tiles = new NavmeshTile[tileXCount * tileZCount]; }
void IUpdatableGraph.UpdateArea(GraphUpdateObject guo) { // Figure out which tiles are affected // Expand TileBorderSizeInWorldUnits voxels in all directions to make sure // all tiles that could be affected by the update are recalculated. var affectedTiles = GetTouchingTiles(guo.bounds, TileBorderSizeInWorldUnits); if (!guo.updatePhysics) { for (int z = affectedTiles.ymin; z <= affectedTiles.ymax; z++) { for (int x = affectedTiles.xmin; x <= affectedTiles.xmax; x++) { NavmeshTile tile = tiles[z * tileXCount + x]; NavMeshGraph.UpdateArea(guo, tile); } } return; } Voxelize vox = globalVox; if (vox == null) { throw new System.InvalidOperationException("No Voxelizer object. UpdateAreaInit should have been called before this function."); } AstarProfiler.StartProfile("Build Tiles"); // Build the new tiles for (int x = affectedTiles.xmin; x <= affectedTiles.xmax; x++) { for (int z = affectedTiles.ymin; z <= affectedTiles.ymax; z++) { stagingTiles.Add(BuildTileMesh(vox, x, z)); } } uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(this); // Set the correct graph index for (int i = 0; i < stagingTiles.Count; i++) { NavmeshTile tile = stagingTiles[i]; GraphNode[] nodes = tile.nodes; for (int j = 0; j < nodes.Length; j++) { nodes[j].GraphIndex = graphIndex; } } for (int i = 0; i < vox.inputMeshes.Count; i++) { vox.inputMeshes[i].Pool(); } ListPool <RasterizationMesh> .Release(ref vox.inputMeshes); AstarProfiler.EndProfile("Build Tiles"); }
private NavmeshTile CreateTile(Voxelize vox, VoxelMesh mesh, int x, int z, int threadIndex) { if (mesh.tris == null) { throw new ArgumentNullException("mesh.tris"); } if (mesh.verts == null) { throw new ArgumentNullException("mesh.verts"); } if (mesh.tris.Length % 3 != 0) { throw new ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)"); } if (mesh.verts.Length >= 4095) { if (this.tileXCount * this.tileZCount == 1) { throw new ArgumentException("Too many vertices per tile (more than " + 4095 + ").\n<b>Try enabling tiling in the recast graph settings.</b>\n"); } throw new ArgumentException("Too many vertices per tile (more than " + 4095 + ").\n<b>Try reducing tile size or enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector</b>"); } else { NavmeshTile navmeshTile = new NavmeshTile { x = x, z = z, w = 1, d = 1, tris = mesh.tris, bbTree = new BBTree() }; navmeshTile.vertsInGraphSpace = Utility.RemoveDuplicateVertices(mesh.verts, navmeshTile.tris); navmeshTile.verts = (Int3[])navmeshTile.vertsInGraphSpace.Clone(); this.transform.Transform(navmeshTile.verts); uint num = (uint)(this.active.data.graphs.Length + threadIndex); if (num > 255u) { throw new Exception("Graph limit reached. Multithreaded recast calculations cannot be done because a few scratch graph indices are required."); } int num2 = x + z * this.tileXCount; num2 <<= 12; TriangleMeshNode.SetNavmeshHolder((int)num, navmeshTile); object active = this.active; lock (active) { navmeshTile.nodes = base.CreateNodes(navmeshTile.tris, num2, num); } navmeshTile.bbTree.RebuildFrom(navmeshTile.nodes); NavmeshBase.CreateNodeConnections(navmeshTile.nodes); TriangleMeshNode.SetNavmeshHolder((int)num, null); return(navmeshTile); } }
protected void GenerateTileFromInputMesh(AStarPathfindingWalkableArea walkArea) { if (null == walkArea) { EB.Debug.LogWarning("GenerateTileFromInputMesh: walkArea is empty"); return; } Mesh inputMesh = walkArea.gameObject.GetComponent <MeshFilter>().sharedMesh; if (null == inputMesh) { EB.Debug.LogWarning("GenerateTileFromInputMesh: inputMesh is empty"); return; } List <Vector3> allPoints = new List <Vector3>(inputMesh.vertices); Bounds bounds = GameUtils.CalculateBounds(allPoints); forcedBoundsCenter = walkArea.transform.TransformPoint(bounds.center); forcedBoundsSize = bounds.size; // this section changes the size of the bounds and tile, so that we always get one single tile SetUpNavMeshToFitOnOneTile(); CalculateNumberOfTiles(ref tileXCount, ref tileZCount); Debug.Assert(tileXCount == 1 && tileZCount == 1, "Their should be only one tile when using a prebuilt nav mesh"); tiles = new NavmeshTile[tileXCount * tileZCount]; // only one tile // ignore this setting scanEmptyGraph = false; // the Vector3 vertices in the mesh need to be converted to the APP Int3 format Int3[] Int3Verts = new Int3[inputMesh.vertices.Length]; for (int i = 0; i < Int3Verts.Length; ++i) { Vector3 tempVert = inputMesh.vertices[i]; tempVert = walkArea.transform.TransformPoint(tempVert); // get the world space position, rather than local space Int3Verts[i] = (Int3)tempVert; } tiles[0] = CreateTile(inputMesh.triangles, Int3Verts, 0, 0); // our single tile //Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.astarData.GetGraphIndex(this); GraphNodeDelegateCancelable del = delegate(GraphNode n) { n.GraphIndex = graphIndex; return(true); }; GetNodes(del); }
protected override IEnumerable <Progress> ScanInternal() { cachedSourceMeshBoundsMin = sourceMesh != null ? sourceMesh.bounds.min : Vector3.zero; transform = CalculateTransform(); tileZCount = tileXCount = 1; tiles = new NavmeshTile[tileZCount * tileXCount]; TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this); if (sourceMesh == null) { FillWithEmptyTiles(); yield break; } yield return(new Progress(0.0f, "Transforming Vertices")); forcedBoundsSize = sourceMesh.bounds.size * scale; Vector3[] vectorVertices = sourceMesh.vertices; var intVertices = ListPool <Int3> .Claim(vectorVertices.Length); var matrix = Matrix4x4.TRS(-sourceMesh.bounds.min * scale, Quaternion.identity, Vector3.one * scale); // Convert the vertices to integer coordinates and also position them in graph space // so that the minimum of the bounding box of the mesh is at the origin // (the vertices will later be transformed to world space) for (int i = 0; i < vectorVertices.Length; i++) { intVertices.Add((Int3)matrix.MultiplyPoint3x4(vectorVertices[i])); } yield return(new Progress(0.1f, "Compressing Vertices")); // Remove duplicate vertices Int3[] compressedVertices = null; int[] compressedTriangles = null; Polygon.CompressMesh(intVertices, new List <int>(sourceMesh.triangles), out compressedVertices, out compressedTriangles); ListPool <Int3> .Release(ref intVertices); yield return(new Progress(0.2f, "Building Nodes")); ReplaceTile(0, 0, compressedVertices, compressedTriangles); // Signal that tiles have been recalculated to the navmesh cutting system. navmeshUpdateData.OnRecalculatedTiles(tiles); if (OnRecalculatedTiles != null) { OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]); } }
public override IEnumerable <Progress> ScanInternal() { transform = CalculateTransform(); tileZCount = tileXCount = 1; tiles = new NavmeshTile[tileZCount * tileXCount]; TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this); if (sourceMesh == null) { FillWithEmptyTiles(); yield break; } yield return(new Progress(0.0f, "Transforming Vertices")); forcedBoundsSize = sourceMesh.bounds.size * scale; Vector3[] vectorVertices = sourceMesh.vertices; var intVertices = ListPool <Int3> .Claim(vectorVertices.Length); var matrix = Matrix4x4.TRS(-sourceMesh.bounds.min * scale, Quaternion.identity, Vector3.one * scale); // Convert the vertices to integer coordinates and also position them in graph space // so that the minimum of the bounding box of the mesh is at the origin // (the vertices will later be transformed to world space) for (int i = 0; i < vectorVertices.Length; i++) { intVertices.Add((Int3)matrix.MultiplyPoint3x4(vectorVertices[i])); } yield return(new Progress(0.1f, "Compressing Vertices")); // Remove duplicate vertices Int3[] compressedVertices = null; int[] compressedTriangles = null; Polygon.CompressMesh(intVertices, new List <int>(sourceMesh.triangles), out compressedVertices, out compressedTriangles); ListPool <Int3> .Release(intVertices); yield return(new Progress(0.2f, "Building Nodes")); ReplaceTile(0, 0, compressedVertices, compressedTriangles); // This may be used by the TileHandlerHelper script to update the tiles // while taking NavmeshCuts into account after the graph has been completely recalculated. if (OnRecalculatedTiles != null) { OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]); } }
void IUpdatableGraph.UpdateArea(GraphUpdateObject guo) { IntRect touchingTiles = base.GetTouchingTiles(guo.bounds); if (!guo.updatePhysics) { for (int i = touchingTiles.ymin; i <= touchingTiles.ymax; i++) { for (int j = touchingTiles.xmin; j <= touchingTiles.xmax; j++) { NavmeshTile graph = this.tiles[i * this.tileXCount + j]; NavMeshGraph.UpdateArea(guo, graph); } } return; } Voxelize voxelize = this.globalVox; if (voxelize == null) { throw new InvalidOperationException("No Voxelizer object. UpdateAreaInit should have been called before this function."); } for (int k = touchingTiles.xmin; k <= touchingTiles.xmax; k++) { for (int l = touchingTiles.ymin; l <= touchingTiles.ymax; l++) { this.stagingTiles.Add(this.BuildTileMesh(voxelize, k, l, 0)); } } uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(this); for (int m = 0; m < this.stagingTiles.Count; m++) { NavmeshTile navmeshTile = this.stagingTiles[m]; GraphNode[] nodes = navmeshTile.nodes; for (int n = 0; n < nodes.Length; n++) { nodes[n].GraphIndex = graphIndex; } } for (int num = 0; num < voxelize.inputMeshes.Count; num++) { voxelize.inputMeshes[num].Pool(); } ListPool <RasterizationMesh> .Release(voxelize.inputMeshes); voxelize.inputMeshes = null; }
/** Creates an outline of the navmesh for use in OnDrawGizmos in the editor */ public static void CreateNavmeshOutlineVisualization(NavmeshTile tile, GraphGizmoHelper helper) { var sharedEdges = new bool[3]; for (int j = 0; j < tile.nodes.Length; j++) { sharedEdges[0] = sharedEdges[1] = sharedEdges[2] = false; var node = tile.nodes[j]; for (int c = 0; c < node.connections.Length; c++) { var other = node.connections[c].node as TriangleMeshNode; // Loop through neighbours to figure out which edges are shared if (other != null && other.GraphIndex == node.GraphIndex) { for (int v = 0; v < 3; v++) { for (int v2 = 0; v2 < 3; v2++) { if (node.GetVertexIndex(v) == other.GetVertexIndex((v2 + 1) % 3) && node.GetVertexIndex((v + 1) % 3) == other.GetVertexIndex(v2)) { // Found a shared edge with the other node sharedEdges[v] = true; v = 3; break; } } } } } var color = helper.NodeColor(node); for (int v = 0; v < 3; v++) { if (!sharedEdges[v]) { helper.builder.DrawLine((Vector3)node.GetVertex(v), (Vector3)node.GetVertex((v + 1) % 3), color); } } } }
// Token: 0x0600262B RID: 9771 RVA: 0x001A7DD0 File Offset: 0x001A5FD0 void IUpdatableGraph.UpdateAreaPost(GraphUpdateObject guo) { for (int i = 0; i < this.stagingTiles.Count; i++) { NavmeshTile navmeshTile = this.stagingTiles[i]; int num = navmeshTile.x + navmeshTile.z * this.tileXCount; NavmeshTile navmeshTile2 = this.tiles[num]; for (int j = 0; j < navmeshTile2.nodes.Length; j++) { navmeshTile2.nodes[j].Destroy(); } this.tiles[num] = navmeshTile; } for (int k = 0; k < this.stagingTiles.Count; k++) { NavmeshTile tile = this.stagingTiles[k]; base.ConnectTileWithNeighbours(tile, false); } if (this.OnRecalculatedTiles != null) { this.OnRecalculatedTiles(this.stagingTiles.ToArray()); } this.stagingTiles.Clear(); }
protected NavmeshTile CreateTile(int[] tris, Int3[] verts, int x, int z) { #if BNICKSON_UPDATED if (tris == null) { throw new System.ArgumentNullException("The mesh must be valid. tris is null."); } if (verts == null) { throw new System.ArgumentNullException("The mesh must be valid. verts is null."); } //Create a new navmesh tile and assign its settings var tile = new NavmeshTile(); tile.x = x; tile.z = z; tile.w = 1; tile.d = 1; tile.tris = tris; tile.verts = verts; tile.bbTree = new BBTree(); #endif if (tile.tris.Length % 3 != 0) { throw new System.ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)"); } if (tile.verts.Length >= VertexIndexMask) { throw new System.ArgumentException("Too many vertices per tile (more than " + VertexIndexMask + ")." + "\nTry enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector"); } //Dictionary<Int3, int> firstVerts = new Dictionary<Int3, int> (); Dictionary <Int3, int> firstVerts = cachedInt3_int_dict; firstVerts.Clear(); var compressedPointers = new int[tile.verts.Length]; int count = 0; for (int i = 0; i < tile.verts.Length; i++) { try { firstVerts.Add(tile.verts[i], count); compressedPointers[i] = count; tile.verts[count] = tile.verts[i]; count++; } catch { //There are some cases, rare but still there, that vertices are identical compressedPointers[i] = firstVerts[tile.verts[i]]; } } for (int i = 0; i < tile.tris.Length; i++) { tile.tris[i] = compressedPointers[tile.tris[i]]; } var compressed = new Int3[count]; for (int i = 0; i < count; i++) { compressed[i] = tile.verts[i]; } tile.verts = compressed; var nodes = new TriangleMeshNode[tile.tris.Length / 3]; tile.nodes = nodes; //Here we are faking a new graph //The tile is not added to any graphs yet, but to get the position querys from the nodes //to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph //and direct the position queries directly to the tile int graphIndex = AstarPath.active.astarData.graphs.Length; TriangleMeshNode.SetNavmeshHolder(graphIndex, tile); //This index will be ORed to the triangle indices int tileIndex = x + z * tileXCount; tileIndex <<= TileIndexOffset; //Create nodes and assign triangle indices for (int i = 0; i < nodes.Length; i++) { var node = new TriangleMeshNode(active); nodes[i] = node; node.GraphIndex = (uint)graphIndex; node.v0 = tile.tris[i * 3 + 0] | tileIndex; node.v1 = tile.tris[i * 3 + 1] | tileIndex; node.v2 = tile.tris[i * 3 + 2] | tileIndex; //Degenerate triangles might occur, but they will not cause any large troubles anymore //if (Polygon.IsColinear (node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { // Debug.Log ("COLINEAR!!!!!!"); //} //Make sure the triangle is clockwise if (!VectorMath.IsClockwiseXZ(node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } node.Walkable = true; node.Penalty = initialPenalty; node.UpdatePositionFromVertices(); tile.bbTree.Insert(node); } CreateNodeConnections(tile.nodes); //Remove the fake graph TriangleMeshNode.SetNavmeshHolder(graphIndex, null); return(tile); }
/** Create a tile at tile index \a x, \a z from the mesh. * \version Since version 3.7.6 the implementation is thread safe */ NavmeshTile CreateTile(Voxelize vox, VoxelMesh mesh, int x, int z, int threadIndex) { if (mesh.tris == null) { throw new System.ArgumentNullException("mesh.tris"); } if (mesh.verts == null) { throw new System.ArgumentNullException("mesh.verts"); } if (mesh.tris.Length % 3 != 0) { throw new System.ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)"); } if (mesh.verts.Length >= VertexIndexMask) { if (tileXCount * tileZCount == 1) { throw new System.ArgumentException("Too many vertices per tile (more than " + VertexIndexMask + ")." + "\n<b>Try enabling tiling in the recast graph settings.</b>\n"); } else { throw new System.ArgumentException("Too many vertices per tile (more than " + VertexIndexMask + ")." + "\n<b>Try reducing tile size or enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector</b>"); } } // Create a new navmesh tile and assign its settings var tile = new NavmeshTile { x = x, z = z, w = 1, d = 1, tris = mesh.tris, bbTree = new BBTree(), graph = this, }; tile.vertsInGraphSpace = Utility.RemoveDuplicateVertices(mesh.verts, tile.tris); tile.verts = (Int3[])tile.vertsInGraphSpace.Clone(); transform.Transform(tile.verts); // Here we are faking a new graph // The tile is not added to any graphs yet, but to get the position queries from the nodes // to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph // and direct the position queries directly to the tile // The thread index is added to make sure that if multiple threads are calculating tiles at the same time // they will not use the same temporary graph index uint temporaryGraphIndex = (uint)(active.data.graphs.Length + threadIndex); if (temporaryGraphIndex > GraphNode.MaxGraphIndex) { // Multithreaded tile calculations use fake graph indices, see above. throw new System.Exception("Graph limit reached. Multithreaded recast calculations cannot be done because a few scratch graph indices are required."); } TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, tile); // We need to lock here because creating nodes is not thread safe // and we may be doing this from multiple threads at the same time tile.nodes = new TriangleMeshNode[tile.tris.Length / 3]; lock (active) { CreateNodes(tile.nodes, tile.tris, x + z * tileXCount, temporaryGraphIndex); } tile.bbTree.RebuildFrom(tile.nodes); CreateNodeConnections(tile.nodes); // Remove the fake graph TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, null); return(tile); }
protected NavmeshTile BuildTileMesh(Voxelize vox, int x, int z, int threadIndex = 0) { AstarProfiler.StartProfile("Build Tile"); AstarProfiler.StartProfile("Init"); vox.borderSize = TileBorderSizeInVoxels; vox.forcedBounds = CalculateTileBoundsWithBorder(x, z); vox.width = tileSizeX + vox.borderSize * 2; vox.depth = tileSizeZ + vox.borderSize * 2; if (!useTiles && relevantGraphSurfaceMode == RelevantGraphSurfaceMode.OnlyForCompletelyInsideTile) { // This best reflects what the user would actually want vox.relevantGraphSurfaceMode = RelevantGraphSurfaceMode.RequireForAll; } else { vox.relevantGraphSurfaceMode = relevantGraphSurfaceMode; } vox.minRegionSize = Mathf.RoundToInt(minRegionSize / (cellSize * cellSize)); AstarProfiler.EndProfile("Init"); // Init voxelizer vox.Init(); vox.VoxelizeInput(transform, CalculateTileBoundsWithBorder(x, z)); AstarProfiler.StartProfile("Filter Ledges"); vox.FilterLedges(vox.voxelWalkableHeight, vox.voxelWalkableClimb, vox.cellSize, vox.cellHeight); AstarProfiler.EndProfile("Filter Ledges"); AstarProfiler.StartProfile("Filter Low Height Spans"); vox.FilterLowHeightSpans(vox.voxelWalkableHeight, vox.cellSize, vox.cellHeight); AstarProfiler.EndProfile("Filter Low Height Spans"); vox.BuildCompactField(); vox.BuildVoxelConnections(); vox.ErodeWalkableArea(CharacterRadiusInVoxels); vox.BuildDistanceField(); vox.BuildRegions(); var cset = new VoxelContourSet(); vox.BuildContours(contourMaxError, 1, cset, Voxelize.RC_CONTOUR_TESS_WALL_EDGES | Voxelize.RC_CONTOUR_TESS_TILE_EDGES); VoxelMesh mesh; vox.BuildPolyMesh(cset, 3, out mesh); AstarProfiler.StartProfile("Build Nodes"); // Position the vertices correctly in graph space (all tiles are laid out on the xz plane with the (0,0) tile at the origin) for (int i = 0; i < mesh.verts.Length; i++) { mesh.verts[i] *= Int3.Precision; } vox.transformVoxel2Graph.Transform(mesh.verts); NavmeshTile tile = CreateTile(vox, mesh, x, z, threadIndex); AstarProfiler.EndProfile("Build Nodes"); AstarProfiler.EndProfile("Build Tile"); return(tile); }
protected void ScanAllTiles(OnScanStatus statusCallback) { #if ASTARDEBUG System.Console.WriteLine("Recast Graph -- Collecting Meshes"); #endif #if BNICKSON_UPDATED editorTileSize = (int)(EditorVars.GridSize / cellSize); #endif //---- //Voxel grid size int gw = (int)(forcedBounds.size.x / cellSize + 0.5f); int gd = (int)(forcedBounds.size.z / cellSize + 0.5f); if (!useTiles) { tileSizeX = gw; tileSizeZ = gd; } else { tileSizeX = editorTileSize; tileSizeZ = editorTileSize; } //Number of tiles int tw = (gw + tileSizeX - 1) / tileSizeX; int td = (gd + tileSizeZ - 1) / tileSizeZ; tileXCount = tw; tileZCount = td; if (tileXCount * tileZCount > TileIndexMask + 1) { throw new System.Exception("Too many tiles (" + (tileXCount * tileZCount) + ") maximum is " + (TileIndexMask + 1) + "\nTry disabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* inspector."); } tiles = new NavmeshTile[tileXCount * tileZCount]; #if ASTARDEBUG System.Console.WriteLine("Recast Graph -- Creating Voxel Base"); #endif // If this is true, just fill the graph with empty tiles if (scanEmptyGraph) { for (int z = 0; z < td; z++) { for (int x = 0; x < tw; x++) { tiles[z * tileXCount + x] = NewEmptyTile(x, z); } } return; } AstarProfiler.StartProfile("Finding Meshes"); List <ExtraMesh> extraMeshes; #if !NETFX_CORE || UNITY_EDITOR System.Console.WriteLine("Collecting Meshes"); #endif CollectMeshes(out extraMeshes, forcedBounds); AstarProfiler.EndProfile("Finding Meshes"); // A walkableClimb higher than walkableHeight can cause issues when generating the navmesh since then it can in some cases // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with navmesh without links) // The editor scripts also enforce this but we enforce it here too just to be sure walkableClimb = Mathf.Min(walkableClimb, walkableHeight); //Create the voxelizer and set all settings var vox = new Voxelize(cellHeight, cellSize, walkableClimb, walkableHeight, maxSlope); vox.inputExtraMeshes = extraMeshes; vox.maxEdgeLength = maxEdgeLength; int lastInfoCallback = -1; var watch = System.Diagnostics.Stopwatch.StartNew(); //Generate all tiles for (int z = 0; z < td; z++) { for (int x = 0; x < tw; x++) { int tileNum = z * tileXCount + x; #if !NETFX_CORE || UNITY_EDITOR System.Console.WriteLine("Generating Tile #" + (tileNum) + " of " + td * tw); #endif //Call statusCallback only 10 times since it is very slow in the editor if (statusCallback != null && (tileNum * 10 / tiles.Length > lastInfoCallback || watch.ElapsedMilliseconds > 2000)) { lastInfoCallback = tileNum * 10 / tiles.Length; watch.Reset(); watch.Start(); statusCallback(new Progress(Mathf.Lerp(0.1f, 0.9f, tileNum / (float)tiles.Length), "Building Tile " + tileNum + "/" + tiles.Length)); } BuildTileMesh(vox, x, z); } } #if !NETFX_CORE System.Console.WriteLine("Assigning Graph Indices"); #endif if (statusCallback != null) { statusCallback(new Progress(0.9f, "Connecting tiles")); } //Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.astarData.GetGraphIndex(this); GraphNodeDelegateCancelable del = delegate(GraphNode n) { n.GraphIndex = graphIndex; return(true); }; GetNodes(del); #if BNICKSON_UPDATED #if DEBUG if (useCenterTileOnly && (3 != tileXCount || 3 != tileZCount)) { EB.Debug.LogError("RecastGenerator.ScanAllTiles() : Incorrect amount of tiles generated if ceneter tile is all that is required"); } #endif int centerXTile = (tileXCount / 2); int centerZTile = (tileZCount / 2); #endif for (int z = 0; z < td; z++) { for (int x = 0; x < tw; x++) { #if BNICKSON_UPDATED // if we're only using the center tile, and this is not the center tile if (useCenterTileOnly && !(centerZTile == z && centerXTile == x)) { continue; } #endif #if !NETFX_CORE System.Console.WriteLine("Connecing Tile #" + (z * tileXCount + x) + " of " + td * tw); #endif if (x < tw - 1) { ConnectTiles(tiles[x + z * tileXCount], tiles[x + 1 + z * tileXCount]); } if (z < td - 1) { ConnectTiles(tiles[x + z * tileXCount], tiles[x + (z + 1) * tileXCount]); } } } AstarProfiler.PrintResults(); }
/// <summary>Exports the INavmesh graph to a .obj file</summary> public static void ExportToFile(RecastGraph target) { //INavmesh graph = (INavmesh)target; if (target == null) { return; } NavmeshTile[] tiles = target.GetTiles(); if (tiles == null) { if (EditorUtility.DisplayDialog("Scan graph before exporting?", "The graph does not contain any mesh data. Do you want to scan it?", "Ok", "Cancel")) { AstarPathEditor.MenuScan(); tiles = target.GetTiles(); if (tiles == null) { return; } } else { return; } } string path = EditorUtility.SaveFilePanel("Export .obj", "", "navmesh.obj", "obj"); if (path == "") { return; } //Generate .obj var sb = new System.Text.StringBuilder(); string name = System.IO.Path.GetFileNameWithoutExtension(path); sb.Append("g ").Append(name).AppendLine(); //Vertices start from 1 int vCount = 1; //Define single texture coordinate to zero sb.Append("vt 0 0\n"); for (int t = 0; t < tiles.Length; t++) { NavmeshTile tile = tiles[t]; if (tile == null) { continue; } Int3[] vertices = tile.verts; //Write vertices for (int i = 0; i < vertices.Length; i++) { var v = (Vector3)vertices[i]; sb.Append(string.Format("v {0} {1} {2}\n", -v.x, v.y, v.z)); } //Write triangles TriangleMeshNode[] nodes = tile.nodes; for (int i = 0; i < nodes.Length; i++) { TriangleMeshNode node = nodes[i]; if (node == null) { Debug.LogError("Node was null or no TriangleMeshNode. Critical error. Graph type " + target.GetType().Name); return; } if (node.GetVertexArrayIndex(0) < 0 || node.GetVertexArrayIndex(0) >= vertices.Length) { throw new System.Exception("ERR"); } sb.Append(string.Format("f {0}/1 {1}/1 {2}/1\n", (node.GetVertexArrayIndex(0) + vCount), (node.GetVertexArrayIndex(1) + vCount), (node.GetVertexArrayIndex(2) + vCount))); } vCount += vertices.Length; } string obj = sb.ToString(); using (var sw = new System.IO.StreamWriter(path)) { sw.Write(obj); } }
/** Generate connections between the two tiles. * The tiles must be adjacent. */ protected void ConnectTiles(NavmeshTile tile1, NavmeshTile tile2) { if (tile1 == null) { return; //throw new System.ArgumentNullException ("tile1"); } if (tile2 == null) { return; //throw new System.ArgumentNullException ("tile2"); } if (tile1.nodes == null) { throw new System.ArgumentException("tile1 does not contain any nodes"); } if (tile2.nodes == null) { throw new System.ArgumentException("tile2 does not contain any nodes"); } int t1x = Mathf.Clamp(tile2.x, tile1.x, tile1.x + tile1.w - 1); int t2x = Mathf.Clamp(tile1.x, tile2.x, tile2.x + tile2.w - 1); int t1z = Mathf.Clamp(tile2.z, tile1.z, tile1.z + tile1.d - 1); int t2z = Mathf.Clamp(tile1.z, tile2.z, tile2.z + tile2.d - 1); int coord, altcoord; int t1coord, t2coord; float tcs; if (t1x == t2x) { coord = 2; altcoord = 0; t1coord = t1z; t2coord = t2z; tcs = tileSizeZ * cellSize; } else if (t1z == t2z) { coord = 0; altcoord = 2; t1coord = t1x; t2coord = t2x; tcs = tileSizeX * cellSize; } else { throw new System.ArgumentException("Tiles are not adjacent (neither x or z coordinates match)"); } if (Math.Abs(t1coord - t2coord) != 1) { EB.Debug.Log("{0} {1} {2} {3}\n{5} {6} {7} {8}\n{9} {10} {11} {12}", tile1.x, tile1.z, tile1.w, tile1.d, tile2.x, tile2.z, tile2.w, tile2.d, t1x, t1z, t2x, t2z); throw new System.ArgumentException("Tiles are not adjacent (tile coordinates must differ by exactly 1. Got '" + t1coord + "' and '" + t2coord + "')"); } //Midpoint between the two tiles int midpoint = (int)Math.Round((Math.Max(t1coord, t2coord) * tcs + forcedBounds.min[coord]) * Int3.Precision); #if ASTARDEBUG Vector3 v1 = new Vector3(-100, 0, -100); Vector3 v2 = new Vector3(100, 0, 100); v1[coord] = midpoint * Int3.PrecisionFactor; v2[coord] = midpoint * Int3.PrecisionFactor; Debug.DrawLine(v1, v2, Color.magenta); #endif #if BNICKSON_UPDATED // different triangle link height tolerance based on whether we're linking tiles generated for random levels or not float heightToleranceSquared = generateFromInputMesh ? GameUtils.Square(heightZoneLinkTolerance) : GameUtils.Square(walkableClimb); #endif TriangleMeshNode[] nodes1 = tile1.nodes; TriangleMeshNode[] nodes2 = tile2.nodes; //Find adjacent nodes on the border between the tiles for (int i = 0; i < nodes1.Length; i++) { TriangleMeshNode node = nodes1[i]; int av = node.GetVertexCount(); for (int a = 0; a < av; a++) { Int3 ap1 = node.GetVertex(a); Int3 ap2 = node.GetVertex((a + 1) % av); #if BNICKSON_UPDATED if (ap1[coord] == midpoint && ap2[coord] == midpoint) // this could be given a little bit of tolerance #else if (Math.Abs(ap1[coord] - midpoint) < 2 && Math.Abs(ap2[coord] - midpoint) < 2) #endif { #if ASTARDEBUG Debug.DrawLine((Vector3)ap1, (Vector3)ap2, Color.red); #endif int minalt = Math.Min(ap1[altcoord], ap2[altcoord]); int maxalt = Math.Max(ap1[altcoord], ap2[altcoord]); //Degenerate edge if (minalt == maxalt) { continue; } for (int j = 0; j < nodes2.Length; j++) { TriangleMeshNode other = nodes2[j]; int bv = other.GetVertexCount(); for (int b = 0; b < bv; b++) { Int3 bp1 = other.GetVertex(b); Int3 bp2 = other.GetVertex((b + 1) % av); #if BNICKSON_UPDATED if (bp1[coord] == midpoint && bp2[coord] == midpoint) // this could be given a little bit of tolerance #else if (Math.Abs(bp1[coord] - midpoint) < 2 && Math.Abs(bp2[coord] - midpoint) < 2) #endif { int minalt2 = Math.Min(bp1[altcoord], bp2[altcoord]); int maxalt2 = Math.Max(bp1[altcoord], bp2[altcoord]); //Degenerate edge if (minalt2 == maxalt2) { continue; } if (maxalt > minalt2 && minalt < maxalt2) { //Adjacent //Test shortest distance between the segments (first test if they are equal since that is much faster) if ((ap1 == bp1 && ap2 == bp2) || (ap1 == bp2 && ap2 == bp1) || #if BNICKSON_UPDATED VectorMath.SqrDistanceSegmentSegment((Vector3)ap1, (Vector3)ap2, (Vector3)bp1, (Vector3)bp2) < heightToleranceSquared) // different height tolerances based on generating from input mesh or not #else VectorMath.SqrDistanceSegmentSegment((Vector3)ap1, (Vector3)ap2, (Vector3)bp1, (Vector3)bp2) < walkableClimb * walkableClimb) #endif { uint cost = (uint)(node.position - other.position).costMagnitude; node.AddConnection(other, cost); other.AddConnection(node, cost); } } } } } } } } }
// generate a nav mesh with one tile per zone protected void GenerateTilesForZones(AStarPathfindingWalkableArea[] walkAreas) { if (null == walkAreas || 0 == walkAreas.Length) { EB.Debug.LogWarning("GenerateTilesForZones: walkAreas is empty"); return; } LevelHelper levelHelper = GameObject.FindObjectOfType(typeof(LevelHelper)) as LevelHelper; if (null == levelHelper) { EB.Debug.LogWarning("GenerateTilesForZones: LevelHelper not found"); return; } Vector3 NavMeshBoundingBoxMin; Vector3 NavMeshBoundingBoxMax; levelHelper.CalculateAllZonesMinMax(out NavMeshBoundingBoxMin, out NavMeshBoundingBoxMax); // set the entire bounds and center of the recast graph forcedBoundsSize = NavMeshBoundingBoxMax - NavMeshBoundingBoxMin; forcedBoundsCenter = NavMeshBoundingBoxMin + (forcedBoundsSize * 0.5f); tileSizeX = tileSizeZ = (int)(EditorVars.GridSize / cellSize); // this line sets the tile size so that each tile will hold the size of a zone CalculateNumberOfTiles(ref tileXCount, ref tileZCount); #if DEBUG int correctTilesXCount = (Mathf.CeilToInt(forcedBoundsSize.x / EditorVars.GridSize)); // this would fail if GridSize is less than 1f int correctTilesZCount = (Mathf.CeilToInt(forcedBoundsSize.z / EditorVars.GridSize)); // this would fail if GridSize is less than 1f if (correctTilesXCount != tileXCount || correctTilesZCount != tileZCount) { EB.Debug.LogError("Incorrect number of tiles generated"); } #endif tiles = new NavmeshTile[tileXCount * tileZCount]; // ignore this setting scanEmptyGraph = false; Vector3 tempZoneMin = new Vector3(); Vector3 tempZoneMax = new Vector3(); // go over all the walkable areas and put there mesh into a tile foreach (AStarPathfindingWalkableArea walk in walkAreas) { ZoneDescriptor zoneDescriptor = (ZoneDescriptor)GameUtils.FindFirstComponentUpwards <ZoneDescriptor>(walk.transform); if (zoneDescriptor != null) { ZoneDescriptor.CalculateZoneMinAndMax(ref tempZoneMin, ref tempZoneMax, zoneDescriptor.gameObject.transform); // 1f is added to avoid floating point inacuracy (avoids 63.999/64 = 0, 64.99/64=1 which is correct) int z = (int)(((tempZoneMin.z + 1f) - NavMeshBoundingBoxMin.z) / EditorVars.GridSize); int x = (int)(((tempZoneMin.x + 1f) - NavMeshBoundingBoxMin.x) / EditorVars.GridSize); MeshFilter meshFilter = walk.gameObject.GetComponent <MeshFilter>(); if (null != meshFilter.sharedMesh) { // the Vector3 vertices in the mesh need to be converted to the APP Int3 format Int3[] Int3Verts = new Int3[meshFilter.sharedMesh.vertices.Length]; for (int i = 0; i < Int3Verts.Length; ++i) { Vector3 tempVert = new Vector3(meshFilter.sharedMesh.vertices[i].x, meshFilter.sharedMesh.vertices[i].y, meshFilter.sharedMesh.vertices[i].z); tempVert = walk.transform.TransformPoint(tempVert); // get the world space position, rather than local space // clamp the verts to the edges of the zone boundaries if they are close, this is so that the different tiles are linked together accurately const float Tol = 0.01f; tempVert.x = (tempVert.x <= tempZoneMin.x + Tol) ? tempZoneMin.x : tempVert.x; tempVert.x = (tempVert.x >= tempZoneMax.x - Tol) ? tempZoneMax.x : tempVert.x; tempVert.z = (tempVert.z <= tempZoneMin.z + Tol) ? tempZoneMin.z : tempVert.z; tempVert.z = (tempVert.z >= tempZoneMax.z - Tol) ? tempZoneMax.z : tempVert.z; Int3Verts[i] = (Int3)tempVert; } NavmeshTile tile = CreateTile(meshFilter.sharedMesh.triangles, Int3Verts, x, z); tiles[Convert2DArrayCoordTo1DArrayCoord(x, z, tileXCount)] = tile; } } } //Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.astarData.GetGraphIndex(this); GraphNodeDelegateCancelable del = delegate(GraphNode n) { n.GraphIndex = graphIndex; return(true); }; GetNodes(del); // connect each tile to one and other for (int z = 0; z < tileZCount; z++) { for (int x = 0; x < tileXCount; x++) { // make sure all the tiles which might be considered, have tiles created CreateAndAddEmptyTileIfNonExists(x, z); CreateAndAddEmptyTileIfNonExists(x + 1, z); CreateAndAddEmptyTileIfNonExists(x, z + 1); if (x < tileXCount - 1) { ConnectTiles(tiles[Convert2DArrayCoordTo1DArrayCoord(x, z, tileXCount)], tiles[Convert2DArrayCoordTo1DArrayCoord(x + 1, z, tileXCount)]); } if (z < tileZCount - 1) { ConnectTiles(tiles[Convert2DArrayCoordTo1DArrayCoord(x, z, tileXCount)], tiles[Convert2DArrayCoordTo1DArrayCoord(x, z + 1, tileXCount)]); } } } }
/** Creates a mesh of the surfaces of the navmesh for use in OnDrawGizmos in the editor */ public static void CreateNavmeshSurfaceVisualization(this NavmeshBase navmeshBase, NavmeshTile tile, GraphGizmoHelper helper) { // Vertex array might be a bit larger than necessary, but that's ok var vertices = PF.ArrayPool <Vector3> .Claim(tile.nodes.Length *3); var colors = PF.ArrayPool <Color> .Claim(tile.nodes.Length *3); for (int j = 0; j < tile.nodes.Length; j++) { var node = tile.nodes[j]; Int3 v0, v1, v2; node.GetVertices(out v0, out v1, out v2); vertices[j * 3 + 0] = (Vector3)v0; vertices[j * 3 + 1] = (Vector3)v1; vertices[j * 3 + 2] = (Vector3)v2; var color = helper.NodeColor(node); colors[j * 3 + 0] = colors[j * 3 + 1] = colors[j * 3 + 2] = color; } if (navmeshBase.showMeshSurface) { helper.DrawTriangles(vertices, colors, tile.nodes.Length); } if (navmeshBase.showMeshOutline) { helper.DrawWireTriangles(vertices, colors, tile.nodes.Length); } // Return lists to the pool PF.ArrayPool <Vector3> .Release(ref vertices); PF.ArrayPool <Color> .Release(ref colors); }