public void UpdateAreaInit (GraphUpdateObject o) { if (!o.updatePhysics) { return; } if (!dynamic) { throw new System.Exception ("Recast graph must be marked as dynamic to enable graph updates"); } 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); int voxelCharacterRadius = Mathf.CeilToInt (characterRadius/cellSize); int borderSize = voxelCharacterRadius + 3; //Expand borderSize voxels on each side tileBounds.Expand (new Vector3 (borderSize,0,borderSize)*cellSize*2); List<ExtraMesh> extraMeshes; CollectMeshes (out extraMeshes, tileBounds); Voxelize vox = globalVox; if (vox == null) { //Create the voxelizer and set all settings vox = new Voxelize (cellHeight, cellSize, walkableClimb, walkableHeight, maxSlope); vox.maxEdgeLength = maxEdgeLength; if (dynamic) globalVox = vox; } vox.inputExtraMeshes = extraMeshes; AstarProfiler.EndProfile ("CollectMeshes"); AstarProfiler.EndProfile ("UpdateAreaInit"); }
protected void BuildTileMesh (Voxelize vox, int x, int z) { AstarProfiler.StartProfile ("Build Tile"); AstarProfiler.StartProfile ("Init"); //World size of tile float tcsx = tileSizeX*cellSize; float tcsz = tileSizeZ*cellSize; int voxelCharacterRadius = Mathf.CeilToInt (characterRadius/cellSize); Vector3 forcedBoundsMin = forcedBounds.min; Vector3 forcedBoundsMax = forcedBounds.max; var bounds = new Bounds (); bounds.SetMinMax(new Vector3 (x*tcsx, 0, z*tcsz) + forcedBoundsMin, new Vector3 ((x+1)*tcsx + forcedBoundsMin.x, forcedBoundsMax.y, (z+1)*tcsz + forcedBoundsMin.z) ); vox.borderSize = voxelCharacterRadius + 3; //Expand borderSize voxels on each side bounds.Expand (new Vector3 (vox.borderSize,0,vox.borderSize)*cellSize*2); vox.forcedBounds = bounds; 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)); #if ASTARDEBUG Debug.Log ("Building Tile " + x+","+z); System.Console.WriteLine ("Recast Graph -- Voxelizing"); #endif AstarProfiler.EndProfile ("Init"); //Init voxelizer vox.Init (); vox.CollectMeshes (); vox.VoxelizeInput (); AstarProfiler.StartProfile ("Filter Ledges"); #if ASTAR_RECAST_VOXEL_DEBUG if (importMode) { if (File.Exists(Application.dataPath+"/tile."+x+"."+z)) { FileStream fs = File.OpenRead (Application.dataPath+"/tile."+x+"."+z); byte[] bytes = new byte[fs.Length]; fs.Read (bytes,0,(int)fs.Length); VoxelArea tmpVox = new VoxelArea(vox.width,vox.depth); Pathfinding.Voxels.VoxelSerializeUtility.DeserializeVoxelAreaData (bytes,tmpVox); Pathfinding.Voxels.VoxelSerializeUtility.MergeVoxelAreaData(tmpVox,vox.voxelArea,vox.voxelWalkableClimb); } } if (exportMode) { FileStream fs = File.Create(Application.dataPath+"/tile."+x+"."+z); byte[] bytes = Pathfinding.Voxels.VoxelSerializeUtility.SerializeVoxelAreaData(vox.voxelArea); fs.Write(bytes,0,bytes.Length); fs.Close(); } #endif vox.FilterLedges (vox.voxelWalkableHeight, vox.voxelWalkableClimb, vox.cellSize, vox.cellHeight, vox.forcedBounds.min); AstarProfiler.EndProfile ("Filter Ledges"); AstarProfiler.StartProfile ("Filter Low Height Spans"); vox.FilterLowHeightSpans (vox.voxelWalkableHeight, vox.cellSize, vox.cellHeight, vox.forcedBounds.min); AstarProfiler.EndProfile ("Filter Low Height Spans"); vox.BuildCompactField (); vox.BuildVoxelConnections (); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Eroding"); #endif vox.ErodeWalkableArea (voxelCharacterRadius); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Building Distance Field"); #endif vox.BuildDistanceField (); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Building Regions"); #endif vox.BuildRegions (); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Building Contours"); #endif var cset = new VoxelContourSet (); vox.BuildContours (contourMaxError,1,cset,Voxelize.RC_CONTOUR_TESS_WALL_EDGES); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Building Poly Mesh"); #endif VoxelMesh mesh; vox.BuildPolyMesh (cset,3,out mesh); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Building Nodes"); #endif //Vector3[] vertices = new Vector3[mesh.verts.Length]; AstarProfiler.StartProfile ("Build Nodes"); // Debug code //matrix = Matrix4x4.TRS (vox.voxelOffset,Quaternion.identity,Int3.Precision*vox.cellScale); //Position the vertices correctly in the world for (int i=0;i<mesh.verts.Length;i++) { //Note the multiplication is Scalar multiplication of vectors mesh.verts[i] = ((mesh.verts[i]*Int3.Precision) * vox.cellScale) + (Int3)vox.voxelOffset; // Debug code //Debug.DrawRay (matrix.MultiplyPoint3x4(vertices[i]),Vector3.up,Color.red); } #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Generating Nodes"); #endif NavmeshTile tile = CreateTile (vox, mesh, x,z); tiles[tile.x + tile.z*tileXCount] = tile; AstarProfiler.EndProfile ("Build Nodes"); #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Done"); #endif AstarProfiler.EndProfile ("Build Tile"); }
/** Create a tile at tile index \a x , \a z from the mesh. * \warning This implementation is not thread safe. It uses cached variables to improve performance */ NavmeshTile CreateTile (Voxelize vox, VoxelMesh mesh, int x, int z) { if (mesh.tris == null) throw new System.ArgumentNullException ("mesh.tris"); if (mesh.verts == null) throw new System.ArgumentNullException ("mesh.verts"); //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 = mesh.tris; tile.verts = mesh.verts; tile.bbTree = new BBTree(); 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++) { if (!firstVerts.ContainsKey(tile.verts[i])) { firstVerts.Add (tile.verts[i], count); compressedPointers[i] = count; tile.verts[count] = tile.verts[i]; count++; } else { // 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 (!Polygon.IsClockwise (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.RebuildFrom(nodes); CreateNodeConnections (tile.nodes); //Remove the fake graph TriangleMeshNode.SetNavmeshHolder (graphIndex, null); return tile; }
protected void ScanAllTiles (OnScanStatus statusCallback) { #if ASTARDEBUG System.Console.WriteLine ("Recast Graph -- Collecting Meshes"); #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 (AstarMath.MapToRange (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); for (int z=0;z<td;z++) { for (int x=0;x<tw;x++) { #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 (); }