void BuildTiles(Queue <Int2> tileQueue, List <RasterizationMesh>[] meshBuckets, ManualResetEvent doneEvent, int threadIndex) { try { // Create the voxelizer and set all settings var vox = new Voxelize(CellHeight, cellSize, walkableClimb, walkableHeight, maxSlope, maxEdgeLength); while (true) { Int2 tile; lock (tileQueue) { if (tileQueue.Count == 0) { return; } tile = tileQueue.Dequeue(); } vox.inputMeshes = meshBuckets[tile.x + tile.y * tileXCount]; tiles[tile.x + tile.y * tileXCount] = BuildTileMesh(vox, tile.x, tile.y, threadIndex); } } catch (System.Exception e) { Debug.LogException(e); } finally { if (doneEvent != null) { doneEvent.Set(); } } }
void IUpdatableGraph.UpdateAreaInit(GraphUpdateObject o) { if (!o.updatePhysics) { return; } AstarProfiler.Reset(); AstarProfiler.StartProfile("UpdateAreaInit"); AstarProfiler.StartProfile("CollectMeshes"); RelevantGraphSurface.UpdateAllPositions(); // Calculate world bounds of all affected tiles IntRect touchingTiles = GetTouchingTiles(o.bounds); Bounds tileBounds = GetTileBounds(touchingTiles); // Expand TileBorderSizeInWorldUnits voxels in all directions tileBounds.Expand(new Vector3(1, 0, 1) * TileBorderSizeInWorldUnits * 2); var meshes = CollectMeshes(tileBounds); if (globalVox == null) { // Create the voxelizer and set all settings globalVox = new Voxelize(CellHeight, cellSize, walkableClimb, walkableHeight, maxSlope, maxEdgeLength); } globalVox.inputMeshes = meshes; AstarProfiler.EndProfile("CollectMeshes"); AstarProfiler.EndProfile("UpdateAreaInit"); }
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); } }
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; }
protected NavmeshTile BuildTileMesh(Voxelize vox, int x, int z, int threadIndex = 0) { vox.borderSize = this.TileBorderSizeInVoxels; vox.forcedBounds = this.CalculateTileBoundsWithBorder(x, z); vox.width = this.tileSizeX + vox.borderSize * 2; vox.depth = this.tileSizeZ + vox.borderSize * 2; if (!this.useTiles && this.relevantGraphSurfaceMode == RecastGraph.RelevantGraphSurfaceMode.OnlyForCompletelyInsideTile) { vox.relevantGraphSurfaceMode = RecastGraph.RelevantGraphSurfaceMode.RequireForAll; } else { vox.relevantGraphSurfaceMode = this.relevantGraphSurfaceMode; } vox.minRegionSize = Mathf.RoundToInt(this.minRegionSize / (this.cellSize * this.cellSize)); vox.Init(); vox.VoxelizeInput(this.transform, this.CalculateTileBoundsWithBorder(x, z)); vox.FilterLedges(vox.voxelWalkableHeight, vox.voxelWalkableClimb, vox.cellSize, vox.cellHeight); vox.FilterLowHeightSpans(vox.voxelWalkableHeight, vox.cellSize, vox.cellHeight); vox.BuildCompactField(); vox.BuildVoxelConnections(); vox.ErodeWalkableArea(this.CharacterRadiusInVoxels); vox.BuildDistanceField(); vox.BuildRegions(); VoxelContourSet cset = new VoxelContourSet(); vox.BuildContours(this.contourMaxError, 1, cset, 5); VoxelMesh mesh; vox.BuildPolyMesh(cset, 3, out mesh); for (int i = 0; i < mesh.verts.Length; i++) { mesh.verts[i] *= 1000; } vox.transformVoxel2Graph.Transform(mesh.verts); return(this.CreateTile(vox, mesh, x, z, threadIndex)); }
/** 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 IEnumerable <Progress> ScanAllTiles() { transform = CalculateTransform(); InitializeTileInfo(); // If this is true, just fill the graph with empty tiles if (scanEmptyGraph) { FillWithEmptyTiles(); yield break; } // 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); yield return(new Progress(0, "Finding Meshes")); var bounds = transform.Transform(new Bounds(forcedBoundsSize * 0.5f, forcedBoundsSize)); var meshes = CollectMeshes(bounds); var buckets = PutMeshesIntoTileBuckets(meshes); Queue <Int2> tileQueue = new Queue <Int2>(); // Put all tiles in the queue for (int z = 0; z < tileZCount; z++) { for (int x = 0; x < tileXCount; x++) { tileQueue.Enqueue(new Int2(x, z)); } } var workQueue = new ParallelWorkQueue <Int2>(tileQueue); // Create the voxelizers and set all settings (one for each thread) var voxelizers = new Voxelize[workQueue.threadCount]; for (int i = 0; i < voxelizers.Length; i++) { voxelizers[i] = new Voxelize(CellHeight, cellSize, walkableClimb, walkableHeight, maxSlope, maxEdgeLength); } workQueue.action = (tile, threadIndex) => { voxelizers[threadIndex].inputMeshes = buckets[tile.x + tile.y * tileXCount]; tiles[tile.x + tile.y * tileXCount] = BuildTileMesh(voxelizers[threadIndex], tile.x, tile.y, threadIndex); }; // Prioritize responsiveness while playing // but when not playing prioritize throughput // (the Unity progress bar is also pretty slow to update) int timeoutMillis = Application.isPlaying ? 1 : 200; // Scan all tiles in parallel foreach (var done in workQueue.Run(timeoutMillis)) { yield return(new Progress(Mathf.Lerp(0.1f, 0.9f, done / (float)tiles.Length), "Calculated Tiles: " + done + "/" + tiles.Length)); } yield return(new Progress(0.9f, "Assigning Graph Indices")); // Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(this); GetNodes(node => node.GraphIndex = graphIndex); // First connect all tiles with an EVEN coordinate sum // This would be the white squares on a chess board. // Then connect all tiles with an ODD coordinate sum (which would be all black squares on a chess board). // This will prevent the different threads that do all // this in parallel from conflicting with each other. // The directions are also done separately // first they are connected along the X direction and then along the Z direction. // Looping over 0 and then 1 for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) { for (int direction = 0; direction <= 1; direction++) { for (int i = 0; i < tiles.Length; i++) { if ((tiles[i].x + tiles[i].z) % 2 == coordinateSum) { tileQueue.Enqueue(new Int2(tiles[i].x, tiles[i].z)); } } workQueue = new ParallelWorkQueue <Int2>(tileQueue); workQueue.action = (tile, threadIndex) => { // Connect with tile at (x+1,z) and (x,z+1) if (direction == 0 && tile.x < tileXCount - 1) { ConnectTiles(tiles[tile.x + tile.y * tileXCount], tiles[tile.x + 1 + tile.y * tileXCount]); } if (direction == 1 && tile.y < tileZCount - 1) { ConnectTiles(tiles[tile.x + tile.y * tileXCount], tiles[tile.x + (tile.y + 1) * tileXCount]); } }; var numTilesInQueue = tileQueue.Count; // Connect all tiles in parallel foreach (var done in workQueue.Run(timeoutMillis)) { yield return(new Progress(0.95f, "Connected Tiles " + (numTilesInQueue - done) + "/" + numTilesInQueue + " (Phase " + (direction + 1 + 2 * coordinateSum) + " of 4)")); } } } for (int i = 0; i < meshes.Count; i++) { meshes[i].Pool(); } ListPool <RasterizationMesh> .Release(ref meshes); // 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[]); } }
protected IEnumerable <Progress> ScanAllTiles() { this.transform = this.CalculateTransform(); this.InitializeTileInfo(); if (this.scanEmptyGraph) { base.FillWithEmptyTiles(); yield break; } this.walkableClimb = Mathf.Min(this.walkableClimb, this.walkableHeight); yield return(new Progress(0f, "Finding Meshes")); Bounds bounds = this.transform.Transform(new Bounds(this.forcedBoundsSize * 0.5f, this.forcedBoundsSize)); List <RasterizationMesh> meshes = this.CollectMeshes(bounds); List <RasterizationMesh>[] buckets = this.PutMeshesIntoTileBuckets(meshes); Queue <Int2> tileQueue = new Queue <Int2>(); for (int i = 0; i < this.tileZCount; i++) { for (int j = 0; j < this.tileXCount; j++) { tileQueue.Enqueue(new Int2(j, i)); } } ParallelWorkQueue <Int2> workQueue = new ParallelWorkQueue <Int2>(tileQueue); Voxelize[] voxelizers = new Voxelize[workQueue.threadCount]; for (int k = 0; k < voxelizers.Length; k++) { voxelizers[k] = new Voxelize(this.CellHeight, this.cellSize, this.walkableClimb, this.walkableHeight, this.maxSlope, this.maxEdgeLength); } workQueue.action = delegate(Int2 tile, int threadIndex) { voxelizers[threadIndex].inputMeshes = buckets[tile.x + tile.y * this.$this.tileXCount]; this.$this.tiles[tile.x + tile.y * this.$this.tileXCount] = this.$this.BuildTileMesh(voxelizers[threadIndex], tile.x, tile.y, threadIndex); }; int timeoutMillis = (!Application.isPlaying) ? 200 : 1; foreach (int done in workQueue.Run(timeoutMillis)) { yield return(new Progress(Mathf.Lerp(0.1f, 0.9f, (float)done / (float)this.tiles.Length), string.Concat(new object[] { "Calculated Tiles: ", done, "/", this.tiles.Length }))); } yield return(new Progress(0.9f, "Assigning Graph Indices")); uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(this); this.GetNodes(delegate(GraphNode node) { node.GraphIndex = graphIndex; }); for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) { int direction; for (direction = 0; direction <= 1; direction++) { for (int l = 0; l < this.tiles.Length; l++) { if ((this.tiles[l].x + this.tiles[l].z) % 2 == coordinateSum) { tileQueue.Enqueue(new Int2(this.tiles[l].x, this.tiles[l].z)); } } workQueue = new ParallelWorkQueue <Int2>(tileQueue); workQueue.action = delegate(Int2 tile, int threadIndex) { if (direction == 0 && tile.x < this.$this.tileXCount - 1) { this.$this.ConnectTiles(this.$this.tiles[tile.x + tile.y * this.$this.tileXCount], this.$this.tiles[tile.x + 1 + tile.y * this.$this.tileXCount]); } if (direction == 1 && tile.y < this.$this.tileZCount - 1) { this.$this.ConnectTiles(this.$this.tiles[tile.x + tile.y * this.$this.tileXCount], this.$this.tiles[tile.x + (tile.y + 1) * this.$this.tileXCount]); } }; int numTilesInQueue = tileQueue.Count; foreach (int done2 in workQueue.Run(timeoutMillis)) { yield return(new Progress(0.95f, string.Concat(new object[] { "Connected Tiles ", numTilesInQueue - done2, "/", numTilesInQueue, " (Phase ", direction + 1 + 2 * coordinateSum, " of 4)" }))); } } } for (int m = 0; m < meshes.Count; m++) { meshes[m].Pool(); } ListPool <RasterizationMesh> .Release(meshes); if (this.OnRecalculatedTiles != null) { this.OnRecalculatedTiles(this.tiles.Clone() as NavmeshTile[]); } yield break; }
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(); }
public override void Scan () { AstarProfiler.Reset (); //AstarProfiler.StartProfile ("Base Scan"); //base.Scan (); //AstarProfiler.EndProfile ("Base Scan"); if (useCRecast) { ScanCRecast (); } else { #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Collecting Meshes"); #endif AstarProfiler.StartProfile ("Collecting Meshes"); AstarProfiler.StartProfile ("Collecting Meshes"); MeshFilter[] filters; ExtraMesh[] extraMeshes; if (!CollectMeshes (out filters, out extraMeshes)) { nodes = new Node[0]; return; } AstarProfiler.EndProfile ("Collecting Meshes"); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Creating Voxel Base"); #endif Voxelize vox = new Voxelize (cellHeight, cellSize, walkableClimb, walkableHeight, maxSlope); vox.maxEdgeLength = maxEdgeLength; vox.forcedBounds = forcedBounds; vox.includeOutOfBounds = includeOutOfBounds; #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Voxelizing"); #endif AstarProfiler.EndProfile ("Collecting Meshes"); //g.GetComponent<Voxelize>(); vox.VoxelizeMesh (filters, extraMeshes); /*bool[,] open = new bool[width,depth]; int[,] visited = new int[width+1,depth+1]; for (int z=0;z<depth;z++) { for (int x = 0;x < width;x++) { open[x,z] = graphNodes[z*width+x].walkable; } }*/ /*for (int i=0;i<depth*width;i++) { open[i] = graphNodes[i].walkable; } int wd = width*depth; List<int> boundary = new List<int>(); int p = 0; for (int i=0;i<wd;i++) { if (!open[i]) { boundary.Add (i); p = i; int backtrack = i-1; }*/ #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Eroding"); #endif vox.ErodeWalkableArea (Mathf.CeilToInt (2*characterRadius/cellSize)); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Building Distance Field"); #endif vox.BuildDistanceField (); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Building Regions"); #endif vox.BuildRegions (); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Building Contours"); #endif VoxelContourSet cset = new VoxelContourSet (); vox.BuildContours (contourMaxError,1,cset,Voxelize.RC_CONTOUR_TESS_WALL_EDGES); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Building Poly Mesh"); #endif VoxelMesh mesh; vox.BuildPolyMesh (cset,3,out mesh); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Building Nodes"); #endif Vector3[] vertices = new Vector3[mesh.verts.Length]; AstarProfiler.StartProfile ("Build Nodes"); for (int i=0;i<vertices.Length;i++) { vertices[i] = (Vector3)mesh.verts[i]; } matrix = Matrix4x4.TRS (vox.voxelOffset,Quaternion.identity,Int3.Precision*Voxelize.CellScale); //Int3.Precision*Voxelize.CellScale+(Int3)vox.voxelOffset //GenerateNodes (this,vectorVertices,triangles, out originalVertices, out _vertices); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Generating Nodes"); #endif NavMeshGraph.GenerateNodes (this,vertices,mesh.tris, out _vectorVertices, out _vertices); AstarProfiler.EndProfile ("Build Nodes"); AstarProfiler.PrintResults (); #if ASTARDEBUG Console.WriteLine ("Recast Graph -- Done"); #endif } }
public void ScanCRecast() { //#if (!UNITY_EDITOR && !UNITY_STANDALONE_OSX && !UNITY_STANDALONE_WIN) || UNITY_WEBPLAYER || UNITY_IPHONE || UNITY_ANDROID || UNITY_DASHBOARD_WIDGET || UNITY_XBOX360 || UNITY_PS3 #if (UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN) System.Diagnostics.Process myProcess = new System.Diagnostics.Process(); myProcess.StartInfo.FileName = GetRecastPath(); //"/Users/arong/Recast/build/Debug/Recast"; //System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo (); //startInfo.UseShellExecute = true; myProcess.StartInfo.UseShellExecute = false; myProcess.StartInfo.RedirectStandardInput = true; myProcess.StartInfo.RedirectStandardOutput = true; myProcess.StartInfo.Arguments = ""; MeshFilter[] filters; ExtraMesh[] extraMeshes; //Get all meshes which should be used CollectMeshes(out filters, out extraMeshes); Vector3[] inputVerts; int[] inputTris; //Get polygon soup from meshes Voxelize.CollectMeshes(filters, extraMeshes, forcedBounds, out inputVerts, out inputTris); //Bild .obj file System.Text.StringBuilder arguments = new System.Text.StringBuilder(); arguments.Append("o recastMesh.obj\n"); for (int i = 0; i < inputVerts.Length; i++) { arguments.Append("v " + inputVerts[i].x.ToString("0.000") + " ").Append(inputVerts[i].y.ToString("0.000") + " ").Append(inputVerts[i].z.ToString("0.000")); arguments.Append("\n"); } //Build .obj file tris for (int i = 0; i < inputTris.Length - 2; i += 3) { arguments.Append("f " + (inputTris[i] + 1) + "//0 ").Append((inputTris[i + 1] + 1) + "//0 ").Append((inputTris[i + 2] + 1) + "//0"); //Debug.DrawLine (inputVerts[inputTris[i]],inputVerts[inputTris[i+1]],Color.red); //Debug.DrawLine (inputVerts[inputTris[i+1]],inputVerts[inputTris[i+2]],Color.red); //Debug.DrawLine (inputVerts[inputTris[i+2]],inputVerts[inputTris[i]],Color.red); arguments.Append("\n"); } string tmpPath = System.IO.Path.GetTempPath(); tmpPath += "recastMesh.obj"; try { using (System.IO.StreamWriter outfile = new System.IO.StreamWriter(tmpPath)) { outfile.Write(arguments.ToString()); } myProcess.StartInfo.Arguments = tmpPath + "\n" + cellSize + "\n" + cellHeight + "\n" + walkableHeight + "\n" + walkableClimb + "\n" + maxSlope + "\n" + maxEdgeLength + "\n" + contourMaxError + "\n" + regionMinSize + "\n" + characterRadius; /* public int erosionRadius = 2; /< Voxels to erode away from the edges of the mesh / * public float contourMaxError = 2F; /< Max distance from simplified edge to real edge / * * public float cellSize = 0.5F; /< Voxel sample size (x,z) / * public float cellHeight = 0.4F; /< Voxel sample size (y) / * public float walkableHeight = 2F; /< Character height/ * public float walkableClimb = 0.5F; /< Height the character can climb / * public float maxSlope = 30; /< Max slope in degrees the character can traverse / * public float maxEdgeLength = 20; /< Longer edges will be subdivided. Reducing this value can im * public bool useCRecast = true; * public bool includeOutOfBounds = false;*/ myProcess.Start(); System.IO.StreamReader sOut = myProcess.StandardOutput; //string result = sOut.ReadToEnd (); //Debug.Log (result); //return; bool failed = false; bool startedVerts = false; int readVerts = 0; bool startedTris = false; int vCount = -1; int readTris = 0; int trisCount = 0; Vector3[] verts = null; int[] tris = null; int internalVertCount = 0; Vector3 bmin = Vector3.zero; float cs = 1F; float ch = 1F; while (sOut.Peek() >= 0) { string line = sOut.ReadLine(); int resultInt; if (line == "") { continue; } if (!int.TryParse(line, out resultInt)) { //Debug.Log ("Syntax Error at '"+line+"'"); failed = true; break; } if (!startedVerts) { verts = new Vector3[resultInt]; if (resultInt == 0) { failed = true; break; } bmin.x = float.Parse(sOut.ReadLine()); bmin.y = float.Parse(sOut.ReadLine()); bmin.z = float.Parse(sOut.ReadLine()); cs = float.Parse(sOut.ReadLine()); ch = float.Parse(sOut.ReadLine()); startedVerts = true; //Debug.Log ("Starting Reading "+resultInt+" verts "+bmin.ToString ()+" - "+cs+" * "+ch); } else if (readVerts < verts.Length) { resultInt *= 1; if (internalVertCount == 0) { verts[readVerts].x = resultInt * cs + bmin.x; } else if (internalVertCount == 1) { verts[readVerts].y = resultInt * ch + bmin.y; } else { verts[readVerts].z = resultInt * cs + bmin.z; } internalVertCount++; if (internalVertCount == 3) { internalVertCount = 0; readVerts++; } } else if (!startedTris) { trisCount = resultInt; startedTris = true; } else if (vCount == -1) { vCount = resultInt; tris = new int[trisCount * vCount]; //Debug.Log ("Starting Reading "+trisCount+" - "+tris.Length+" tris at vertsCount "+readVerts); //Debug.Log ("Max vertices per polygon: "+vCount); } else if (readTris < tris.Length) { tris[readTris] = resultInt; readTris++; } } if (!myProcess.HasExited) { myProcess.Kill(); } sOut.Close(); myProcess.Close(); if (failed) { return; } matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one); NavMeshGraph.GenerateNodes(this, verts, tris, out _vectorVertices, out _vertices); } finally { //Debug.Log (tmpPath); System.IO.File.Delete(tmpPath); } #else Debug.LogError("The C++ version of recast can only be used in editor or standalone mode, I'm sure it cannot be used in the webplayer, but other platforms are not tested yet\n" + "If you are in the Unity Editor, try switching Platform to Standalone just when scanning, scanned graphs can be cached to enable them to be used in a webplayer"); _vectorVertices = new Vector3[0]; _vertices = new Int3[0]; nodes = new Node[0]; #endif }
public override void Scan() { AstarProfiler.Reset(); //AstarProfiler.StartProfile ("Base Scan"); //base.Scan (); //AstarProfiler.EndProfile ("Base Scan"); if (useCRecast) { ScanCRecast(); } else { MeshFilter[] filters; ExtraMesh[] extraMeshes; if (!CollectMeshes(out filters, out extraMeshes)) { nodes = new Node[0]; return; } Voxelize vox = new Voxelize(cellHeight, cellSize, walkableClimb, walkableHeight, maxSlope); vox.maxEdgeLength = maxEdgeLength; vox.forcedBounds = forcedBounds; vox.includeOutOfBounds = includeOutOfBounds; //g.GetComponent<Voxelize>(); vox.VoxelizeMesh(filters, extraMeshes); /*bool[,] open = new bool[width,depth]; * int[,] visited = new int[width+1,depth+1]; * * for (int z=0;z<depth;z++) { * for (int x = 0;x < width;x++) { * open[x,z] = graphNodes[z*width+x].walkable; * } * }*/ /*for (int i=0;i<depth*width;i++) { * open[i] = graphNodes[i].walkable; * } * * * int wd = width*depth; * * List<int> boundary = new List<int>(); * * int p = 0; * * for (int i=0;i<wd;i++) { * if (!open[i]) { * boundary.Add (i); * * p = i; * * int backtrack = i-1; * * * }*/ vox.ErodeWalkableArea(Mathf.CeilToInt(2 * characterRadius / cellSize)); vox.BuildDistanceField(); vox.BuildRegions(); VoxelContourSet cset = new VoxelContourSet(); vox.BuildContours(contourMaxError, 1, cset, Voxelize.RC_CONTOUR_TESS_WALL_EDGES); VoxelMesh mesh; vox.BuildPolyMesh(cset, 3, out mesh); Vector3[] vertices = new Vector3[mesh.verts.Length]; AstarProfiler.StartProfile("Build Nodes"); for (int i = 0; i < vertices.Length; i++) { vertices[i] = (Vector3)mesh.verts[i]; } matrix = Matrix4x4.TRS(vox.voxelOffset, Quaternion.identity, Int3.Precision * Voxelize.CellScale); //Int3.Precision*Voxelize.CellScale+(Int3)vox.voxelOffset //GenerateNodes (this,vectorVertices,triangles, out originalVertices, out _vertices); NavMeshGraph.GenerateNodes(this, vertices, mesh.tris, out _vectorVertices, out _vertices); AstarProfiler.EndProfile("Build Nodes"); AstarProfiler.PrintResults(); } }