/// <summary> /// Generates a <see cref="NavMesh"/> given a collection of triangles and some settings. /// </summary> /// <param name="triangles">The triangles that form the level.</param> /// <param name="settings">The settings to generate with.</param> /// <returns>A <see cref="NavMesh"/>.</returns> public static NavMesh Generate(IEnumerable<Triangle3> triangles, NavMeshGenerationSettings settings) { BBox3 bounds = triangles.GetBoundingBox(settings.CellSize); var hf = new Heightfield(bounds, settings); hf.RasterizeTriangles(triangles); hf.FilterLedgeSpans(settings.VoxelAgentHeight, settings.VoxelMaxClimb); hf.FilterLowHangingWalkableObstacles(settings.VoxelMaxClimb); hf.FilterWalkableLowHeightSpans(settings.VoxelAgentHeight); var chf = new CompactHeightfield(hf, settings); chf.Erode(settings.VoxelAgentRadius); chf.BuildDistanceField(); chf.BuildRegions(2, settings.MinRegionSize, settings.MergedRegionSize); var cont = chf.BuildContourSet(settings); var polyMesh = new PolyMesh(cont, settings); var polyMeshDetail = new PolyMeshDetail(polyMesh, chf, settings); var buildData = new NavMeshBuilder(polyMesh, polyMeshDetail, new Pathfinding.OffMeshConnection[0], settings); var navMesh = new NavMesh(buildData); return navMesh; }
private void GenerateNavMesh() { Console.WriteLine("Generating NavMesh"); Stopwatch sw = new Stopwatch(); sw.Start(); long prevMs = 0; try { //level.SetBoundingBoxOffset(new SVector3(settings.CellSize * 0.5f, settings.CellHeight * 0.5f, settings.CellSize * 0.5f)); var levelTris = level.GetTriangles(); var triEnumerable = TriangleEnumerable.FromTriangle(levelTris, 0, levelTris.Length); BBox3 bounds = triEnumerable.GetBoundingBox(); heightfield = new Heightfield(bounds, settings); Console.WriteLine("Heightfield"); Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; /*Area[] areas = AreaGenerator.From(triEnumerable, Area.Default) .MarkAboveHeight(areaSettings.MaxLevelHeight, Area.Null) .MarkBelowHeight(areaSettings.MinLevelHeight, Area.Null) .MarkBelowSlope(areaSettings.MaxTriSlope, Area.Null) .ToArray(); heightfield.RasterizeTrianglesWithAreas(levelTris, areas);*/ heightfield.RasterizeTriangles(levelTris, Area.Default); Console.WriteLine(" + Rasterization\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); Console.WriteLine(" + Filtering"); prevMs = sw.ElapsedMilliseconds; heightfield.FilterLedgeSpans(settings.VoxelAgentHeight, settings.VoxelMaxClimb); Console.WriteLine(" + Ledge Spans\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; heightfield.FilterLowHangingWalkableObstacles(settings.VoxelMaxClimb); Console.WriteLine(" + Low Hanging Obstacles\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; heightfield.FilterWalkableLowHeightSpans(settings.VoxelAgentHeight); Console.WriteLine(" + Low Height Spans\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; compactHeightfield = new CompactHeightfield(heightfield, settings); Console.WriteLine("CompactHeightfield"); Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; compactHeightfield.Erode(settings.VoxelAgentRadius); Console.WriteLine(" + Erosion\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; compactHeightfield.BuildDistanceField(); Console.WriteLine(" + Distance Field\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; compactHeightfield.BuildRegions(0, settings.MinRegionSize, settings.MergedRegionSize); Console.WriteLine(" + Regions\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; Random r = new Random(); regionColors = new Color4[compactHeightfield.MaxRegions]; regionColors[0] = Color4.Black; for (int i = 1; i < regionColors.Length; i++) regionColors[i] = new Color4((byte)r.Next(0, 255), (byte)r.Next(0, 255), (byte)r.Next(0, 255), 255); Console.WriteLine(" + Colors\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; contourSet = compactHeightfield.BuildContourSet(settings); Console.WriteLine("ContourSet"); Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; polyMesh = new PolyMesh(contourSet, settings); Console.WriteLine("PolyMesh"); Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; polyMeshDetail = new PolyMeshDetail(polyMesh, compactHeightfield, settings); Console.WriteLine("PolyMeshDetail"); Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms"); prevMs = sw.ElapsedMilliseconds; hasGenerated = true; } catch (Exception e) { if (!interceptExceptions) throw; else Console.WriteLine("Navmesh generation failed with exception:" + Environment.NewLine + e.ToString()); } finally { sw.Stop(); } if (hasGenerated) { try { GeneratePathfinding(); //Pathfinding with multiple units GenerateCrowd(); } catch (Exception e) { Console.WriteLine("Pathfinding generation failed with exception" + Environment.NewLine + e.ToString()); hasGenerated = false; } Label l = (Label)statusBar.FindChildByName("GenTime"); l.Text = "Generation Time: " + sw.ElapsedMilliseconds + "ms"; Console.WriteLine("Navmesh generated successfully in " + sw.ElapsedMilliseconds + "ms."); Console.WriteLine("Rasterized " + level.GetTriangles().Length + " triangles."); Console.WriteLine("Generated " + contourSet.Count + " regions."); Console.WriteLine("PolyMesh contains " + polyMesh.VertCount + " vertices in " + polyMesh.PolyCount + " polys."); Console.WriteLine("PolyMeshDetail contains " + polyMeshDetail.VertCount + " vertices and " + polyMeshDetail.TrisCount + " tris in " + polyMeshDetail.MeshCount + " meshes."); } }
/// <summary> /// Initializes a new instance of the <see cref="NavMeshBuilder" /> class. /// Add all the PolyMesh and PolyMeshDetail attributes to the Navigation Mesh. /// Then, add Off-Mesh connection support. /// </summary> /// <param name="polyMesh">The PolyMesh</param> /// <param name="polyMeshDetail">The PolyMeshDetail</param> /// <param name="offMeshCons">Offmesh connection data</param> /// <param name="settings">The settings used to build.</param> public NavMeshBuilder(PolyMesh polyMesh, PolyMeshDetail polyMeshDetail, OffMeshConnection[] offMeshCons, NavMeshGenerationSettings settings) { if (settings.VertsPerPoly > PathfindingCommon.VERTS_PER_POLYGON) throw new InvalidOperationException("The number of vertices per polygon is above SharpNav's limit"); if (polyMesh.VertCount == 0) throw new InvalidOperationException("The provided PolyMesh has no vertices."); if (polyMesh.PolyCount == 0) throw new InvalidOperationException("The provided PolyMesh has not polys."); int nvp = settings.VertsPerPoly; //classify off-mesh connection points BoundarySide[] offMeshSides = new BoundarySide[offMeshCons.Length * 2]; int storedOffMeshConCount = 0; int offMeshConLinkCount = 0; if (offMeshCons.Length > 0) { //find height bounds float hmin = float.MaxValue; float hmax = -float.MaxValue; if (polyMeshDetail != null) { for (int i = 0; i < polyMeshDetail.VertCount; i++) { float h = polyMeshDetail.Verts[i].Y; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } else { for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; float h = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } hmin -= settings.MaxClimb; hmax += settings.MaxClimb; BBox3 bounds = polyMesh.Bounds; bounds.Min.Y = hmin; bounds.Max.Y = hmax; for (int i = 0; i < offMeshCons.Length; i++) { Vector3 p0 = offMeshCons[i].Pos0; Vector3 p1 = offMeshCons[i].Pos1; offMeshSides[i * 2 + 0] = BoundarySideExtensions.FromPoint(p0, bounds); offMeshSides[i * 2 + 1] = BoundarySideExtensions.FromPoint(p1, bounds); //off-mesh start position isn't touching mesh if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { if (p0.Y < bounds.Min.Y || p0.Y > bounds.Max.Y) offMeshSides[i * 2 + 0] = 0; } //count number of links to allocate if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) offMeshConLinkCount++; if (offMeshSides[i * 2 + 1] == BoundarySide.Internal) offMeshConLinkCount++; if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) storedOffMeshConCount++; } } //off-mesh connections stored as polygons, adjust values int totPolyCount = polyMesh.PolyCount + storedOffMeshConCount; int totVertCount = polyMesh.VertCount + storedOffMeshConCount * 2; //find portal edges int edgeCount = 0; int portalCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { PolyMesh.Polygon p = polyMesh.Polys[i]; for (int j = 0; j < nvp; j++) { if (p.Vertices[j] == PolyMesh.NullId) break; edgeCount++; if (PolyMesh.IsBoundaryEdge(p.NeighborEdges[j])) { int dir = p.NeighborEdges[j] % 16; if (dir != 15) portalCount++; } } } int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2; //find unique detail vertices int uniqueDetailVertCount = 0; int detailTriCount = 0; if (polyMeshDetail != null) { detailTriCount = polyMeshDetail.TrisCount; for (int i = 0; i < polyMesh.PolyCount; i++) { int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numDetailVerts - numPolyVerts; } } else { uniqueDetailVertCount = 0; detailTriCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numPolyVerts - 2; } } //allocate data header = new PathfindingCommon.NavMeshInfo(); navVerts = new Vector3[totVertCount]; navPolys = new Poly[totPolyCount]; navDMeshes = new PolyMeshDetail.MeshData[polyMesh.PolyCount]; navDVerts = new Vector3[uniqueDetailVertCount]; navDTris = new PolyMeshDetail.TriangleData[detailTriCount]; offMeshConnections = new OffMeshConnection[storedOffMeshConCount]; //store header //HACK TiledNavMesh should figure out the X/Y/layer instead of the user maybe? header.X = 0; header.Y = 0; header.Layer = 0; header.PolyCount = totPolyCount; header.VertCount = totVertCount; header.MaxLinkCount = maxLinkCount; header.Bounds = polyMesh.Bounds; header.DetailMeshCount = polyMesh.PolyCount; header.DetailVertCount = uniqueDetailVertCount; header.DetailTriCount = detailTriCount; header.OffMeshBase = polyMesh.PolyCount; header.WalkableHeight = settings.AgentHeight; header.WalkableRadius = settings.AgentRadius; header.WalkableClimb = settings.MaxClimb; header.OffMeshConCount = storedOffMeshConCount; header.BvNodeCount = settings.BuildBoundingVolumeTree ? polyMesh.PolyCount * 2 : 0; header.BvQuantFactor = 1f / settings.CellSize; int offMeshVertsBase = polyMesh.VertCount; int offMeshPolyBase = polyMesh.PolyCount; //store vertices for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; navVerts[i].X = polyMesh.Bounds.Min.X + iv.X * settings.CellSize; navVerts[i].Y = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; navVerts[i].Z = polyMesh.Bounds.Min.Z + iv.Z * settings.CellSize; } //off-mesh link vertices int n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navVerts[offMeshVertsBase + (n * 2 + 0)] = offMeshCons[i].Pos0; navVerts[offMeshVertsBase + (n * 2 + 1)] = offMeshCons[i].Pos1; n++; } } //store polygons for (int i = 0; i < polyMesh.PolyCount; i++) { navPolys[i] = new Poly(); navPolys[i].VertCount = 0; navPolys[i].Tag = polyMesh.Polys[i].Tag; navPolys[i].Area = polyMesh.Polys[i].Area; navPolys[i].PolyType = PolygonType.Ground; navPolys[i].Verts = new int[nvp]; navPolys[i].Neis = new int[nvp]; for (int j = 0; j < nvp; j++) { if (polyMesh.Polys[i].Vertices[j] == PolyMesh.NullId) break; navPolys[i].Verts[j] = polyMesh.Polys[i].Vertices[j]; if (PolyMesh.IsBoundaryEdge(polyMesh.Polys[i].NeighborEdges[j])) { //border or portal edge int dir = polyMesh.Polys[i].NeighborEdges[j] % 16; if (dir == 0xf) //border navPolys[i].Neis[j] = 0; else if (dir == 0) //portal x- navPolys[i].Neis[j] = Link.External | 4; else if (dir == 1) //portal z+ navPolys[i].Neis[j] = Link.External | 2; else if (dir == 2) //portal x+ navPolys[i].Neis[j] = Link.External | 0; else if (dir == 3) //portal z- navPolys[i].Neis[j] = Link.External | 6; } else { //normal connection navPolys[i].Neis[j] = polyMesh.Polys[i].NeighborEdges[j] + 1; } navPolys[i].VertCount++; } } //off-mesh connection vertices n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navPolys[offMeshPolyBase + n].VertCount = 2; navPolys[offMeshPolyBase + n].Verts = new int[nvp]; navPolys[offMeshPolyBase + n].Verts[0] = offMeshVertsBase + (n * 2 + 0); navPolys[offMeshPolyBase + n].Verts[1] = offMeshVertsBase + (n * 2 + 1); navPolys[offMeshPolyBase + n].Tag = offMeshCons[i].Flags; navPolys[offMeshPolyBase + n].Area = polyMesh.Polys[offMeshCons[i].Poly].Area; //HACK is this correct? navPolys[offMeshPolyBase + n].PolyType = PolygonType.OffMeshConnection; n++; } } //store detail meshes and vertices if (polyMeshDetail != null) { int vbase = 0; List<Vector3> storedDetailVerts = new List<Vector3>(); for (int i = 0; i < polyMesh.PolyCount; i++) { int vb = polyMeshDetail.Meshes[i].VertexIndex; int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = vbase; navDMeshes[i].VertexCount = numDetailVerts - numPolyVerts; navDMeshes[i].TriangleIndex = polyMeshDetail.Meshes[i].TriangleIndex; navDMeshes[i].TriangleCount = polyMeshDetail.Meshes[i].TriangleCount; //Copy detail vertices //first 'nv' verts are equal to nav poly verts //the rest are detail verts for (int j = 0; j < navDMeshes[i].VertexCount; j++) { storedDetailVerts.Add(polyMeshDetail.Verts[vb + numPolyVerts + j]); } vbase += numDetailVerts - numPolyVerts; } navDVerts = storedDetailVerts.ToArray(); //store triangles for (int j = 0; j < polyMeshDetail.TrisCount; j++) navDTris[j] = polyMeshDetail.Tris[j]; } else { //create dummy detail mesh by triangulating polys int tbase = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = 0; navDMeshes[i].VertexCount = 0; navDMeshes[i].TriangleIndex = tbase; navDMeshes[i].TriangleCount = numPolyVerts - 2; //triangulate polygon for (int j = 2; j < numPolyVerts; j++) { navDTris[tbase].VertexHash0 = 0; navDTris[tbase].VertexHash1 = j - 1; navDTris[tbase].VertexHash2 = j; //bit for each edge that belongs to the poly boundary navDTris[tbase].Flags = 1 << 2; if (j == 2) navDTris[tbase].Flags |= 1 << 0; if (j == numPolyVerts - 1) navDTris[tbase].Flags |= 1 << 4; tbase++; } } } //store and create BV tree if (settings.BuildBoundingVolumeTree) { //build tree navBvTree = new BVTree(polyMesh.Verts, polyMesh.Polys, nvp, settings.CellSize, settings.CellHeight); } //store off-mesh connections n = 0; for (int i = 0; i < offMeshConnections.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConnections[n].Poly = offMeshPolyBase + n; //copy connection end points offMeshConnections[n].Pos0 = offMeshCons[i].Pos0; offMeshConnections[n].Pos1 = offMeshCons[i].Pos1; offMeshConnections[n].Radius = offMeshCons[i].Radius; offMeshConnections[n].Flags = offMeshCons[i].Flags; offMeshConnections[n].Side = offMeshSides[i * 2 + 1]; offMeshConnections[n].Tag = offMeshCons[i].Tag; n++; } } }
/// <summary> /// Initializes a new instance of the <see cref="PolyMeshDetail"/> class. /// </summary> /// <remarks> /// <see cref="PolyMeshDetail"/> uses a <see cref="CompactHeightfield"/> to add in details to a /// <see cref="PolyMesh"/>. This detail is triangulated into a new mesh and can be used to approximate height in the walkable /// areas of a scene. /// </remarks> /// <param name="mesh">The <see cref="PolyMesh"/>.</param> /// <param name="compactField">The <see cref="CompactHeightfield"/> used to add height detail.</param> /// <param name="sampleDist">The sampling distance.</param> /// <param name="sampleMaxError">The maximum sampling error allowed.</param> public PolyMeshDetail(PolyMesh mesh, CompactHeightfield compactField, float sampleDist, float sampleMaxError) { if (mesh.VertCount == 0 || mesh.PolyCount == 0) return; Vector3 origin = mesh.Bounds.Min; int maxhw = 0, maxhh = 0; BBox2i[] bounds = new BBox2i[mesh.PolyCount]; Vector3[] poly = new Vector3[mesh.NumVertsPerPoly]; var storedVertices = new List<Vector3>(); var storedTriangles = new List<TriangleData>(); //find max size for polygon area for (int i = 0; i < mesh.PolyCount; i++) { var p = mesh.Polys[i]; int xmin = compactField.Width; int xmax = 0; int zmin = compactField.Length; int zmax = 0; for (int j = 0; j < mesh.NumVertsPerPoly; j++) { var pj = p.Vertices[j]; if (pj == PolyMesh.NullId) break; var v = mesh.Verts[pj]; xmin = Math.Min(xmin, v.X); xmax = Math.Max(xmax, v.X); zmin = Math.Min(zmin, v.Z); zmax = Math.Max(zmax, v.Z); } xmin = Math.Max(0, xmin - 1); xmax = Math.Min(compactField.Width, xmax + 1); zmin = Math.Max(0, zmin - 1); zmax = Math.Min(compactField.Length, zmax + 1); if (xmin >= xmax || zmin >= zmax) continue; maxhw = Math.Max(maxhw, xmax - xmin); maxhh = Math.Max(maxhh, zmax - zmin); bounds[i] = new BBox2i(xmin, zmin, xmax, zmax); } HeightPatch hp = new HeightPatch(0, 0, maxhw, maxhh); this.meshes = new MeshData[mesh.PolyCount]; for (int i = 0; i < mesh.PolyCount; i++) { var p = mesh.Polys[i]; //store polygon vertices for processing int npoly = 0; for (int j = 0; j < mesh.NumVertsPerPoly; j++) { int pvi = p.Vertices[j]; if (pvi == PolyMesh.NullId) break; PolyVertex pv = mesh.Verts[pvi]; Vector3 v = new Vector3(pv.X, pv.Y, pv.Z); v.X *= mesh.CellSize; v.Y *= mesh.CellHeight; v.Z *= mesh.CellSize; poly[j] = v; npoly++; } //get height data from area of polygon BBox2i bound = bounds[i]; hp.Resize(bound.Min.X, bound.Min.Y, bound.Max.X - bound.Min.X, bound.Max.Y - bound.Min.Y); GetHeightData(compactField, p, npoly, mesh.Verts, mesh.BorderSize, hp); List<Vector3> tempVerts = new List<Vector3>(); List<TriangleData> tempTris = new List<TriangleData>(128); List<EdgeInfo> edges = new List<EdgeInfo>(16); List<SamplingData> samples = new List<SamplingData>(128); BuildPolyDetail(poly, npoly, sampleDist, sampleMaxError, compactField, hp, tempVerts, tempTris, edges, samples); //more detail verts for (int j = 0; j < tempVerts.Count; j++) { Vector3 tv = tempVerts[j]; Vector3 v; v.X = tv.X + origin.X; v.Y = tv.Y + origin.Y + compactField.CellHeight; v.Z = tv.Z + origin.Z; tempVerts[j] = v; } for (int j = 0; j < npoly; j++) { Vector3 po = poly[j]; po.X += origin.X; po.Y += origin.Y; po.Z += origin.Z; poly[j] = po; } //save data this.meshes[i].VertexIndex = storedVertices.Count; this.meshes[i].VertexCount = tempVerts.Count; this.meshes[i].TriangleIndex = storedTriangles.Count; this.meshes[i].TriangleCount = tempTris.Count; //store vertices storedVertices.AddRange(tempVerts); //store triangles for (int j = 0; j < tempTris.Count; j++) { storedTriangles.Add(new TriangleData(tempTris[j], tempVerts, poly, npoly)); } } this.verts = storedVertices.ToArray(); this.tris = storedTriangles.ToArray(); }
/// <summary> /// Initializes a new instance of the <see cref="PolyMeshDetail"/> class. /// </summary> /// <param name="mesh">The <see cref="PolyMesh"/>.</param> /// <param name="compactField">The <see cref="CompactHeightfield"/> used to add height detail.</param> /// <param name="settings">The settings to build with.</param> public PolyMeshDetail(PolyMesh mesh, CompactHeightfield compactField, NavMeshGenerationSettings settings) : this(mesh, compactField, settings.SampleDistance, settings.MaxSampleError) { }
/// <summary> /// Floodfill heightfield to get 2D height data, starting at vertex locations /// </summary> /// <param name="compactField">Original heightfield data</param> /// <param name="poly">Polygon in PolyMesh</param> /// <param name="polyCount">Number of vertices per polygon</param> /// <param name="verts">PolyMesh Vertices</param> /// <param name="borderSize">Heightfield border size</param> /// <param name="hp">HeightPatch which extracts heightfield data</param> /// <param name="stack">Temporary stack of CompactSpanReferences</param> private void GetHeightDataSeedsFromVertices(CompactHeightfield compactField, PolyMesh.Polygon poly, int polyCount, PolyVertex[] verts, int borderSize, HeightPatch hp, List<CompactSpanReference> stack) { hp.SetAll(0); //use poly vertices as seed points for (int j = 0; j < polyCount; j++) { var csr = new CompactSpanReference(0, 0, -1); int dmin = int.MaxValue; var v = verts[poly.Vertices[j]]; for (int k = 0; k < 9; k++) { //get vertices and offset x and z coordinates depending on current drection int ax = v.X + VertexOffset[k * 2 + 0]; int ay = v.Y; int az = v.Z + VertexOffset[k * 2 + 1]; //skip if out of bounds if (ax < hp.X || ax >= hp.X + hp.Width || az < hp.Y || az >= hp.Y + hp.Length) continue; //get new cell CompactCell c = compactField.Cells[(az + borderSize) * compactField.Width + (ax + borderSize)]; //loop through all the spans for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { CompactSpan s = compactField.Spans[i]; //find minimum y-distance int d = Math.Abs(ay - s.Minimum); if (d < dmin) { csr = new CompactSpanReference(ax, az, i); dmin = d; } } } //only add if something new found if (csr.Index != -1) { stack.Add(csr); } } //find center of polygon using flood fill int pcx = 0, pcz = 0; for (int j = 0; j < polyCount; j++) { var v = verts[poly.Vertices[j]]; pcx += v.X; pcz += v.Z; } pcx /= polyCount; pcz /= polyCount; //stack groups 3 elements as one part foreach (var cell in stack) { int idx = (cell.Y - hp.Y) * hp.Width + (cell.X - hp.X); hp[idx] = 1; } //process the entire stack while (stack.Count > 0) { var cell = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); //check if close to center of polygon if (Math.Abs(cell.X - pcx) <= 1 && Math.Abs(cell.Y - pcz) <= 1) { //clear the stack and add a new group stack.Clear(); stack.Add(cell); break; } CompactSpan cs = compactField[cell]; //check all four directions for (var dir = Direction.West; dir <= Direction.South; dir++) { //skip if disconnected if (!cs.IsConnected(dir)) continue; //get neighbor int ax = cell.X + dir.GetHorizontalOffset(); int ay = cell.Y + dir.GetVerticalOffset(); //skip if out of bounds if (ax < hp.X || ax >= (hp.X + hp.Width) || ay < hp.Y || ay >= (hp.Y + hp.Length)) continue; if (hp[(ay - hp.Y) * hp.Width + (ax - hp.X)] != 0) continue; //get the new index int ai = compactField.Cells[(ay + borderSize) * compactField.Width + (ax + borderSize)].StartIndex + CompactSpan.GetConnection(ref cs, dir); //save data int idx = (ay - hp.Y) * hp.Width + (ax - hp.X); hp[idx] = 1; //push to stack stack.Add(new CompactSpanReference(ax, ay, ai)); } } //clear the heightpatch hp.Clear(); //mark start locations for (int i = 0; i < stack.Count; i++) { var c = stack[i]; //set new heightpatch data int idx = (c.Y - hp.Y) * hp.Width + (c.X - hp.X); CompactSpan cs = compactField.Spans[c.Index]; hp[idx] = cs.Minimum; stack[i] = new CompactSpanReference(c.X + borderSize, c.Y + borderSize, c.Index); } }
private void GetHeightData(CompactHeightfield compactField, PolyMesh.Polygon poly, int polyCount, PolyVertex[] verts, int borderSize, HeightPatch hp) { var stack = new List<CompactSpanReference>(); bool empty = true; hp.Clear(); for (int y = 0; y < hp.Length; y++) { int hy = hp.Y + y + borderSize; for (int x = 0; x < hp.Width; x++) { int hx = hp.X + x + borderSize; var cells = compactField.Cells[hy * compactField.Width + hx]; for (int i = cells.StartIndex, end = cells.StartIndex + cells.Count; i < end; i++) { var span = compactField.Spans[i]; if (span.Region == poly.RegionId) { hp[x, y] = span.Minimum; empty = false; bool border = false; for (var dir = Direction.West; dir <= Direction.South; dir++) { if (span.IsConnected(dir)) { int ax = hx + dir.GetHorizontalOffset(); int ay = hy + dir.GetVerticalOffset(); int ai = compactField.Cells[ay * compactField.Width + ax].StartIndex + CompactSpan.GetConnection(ref span, dir); if (compactField.Spans[ai].Region != poly.RegionId) { border = true; break; } } } if (border) stack.Add(new CompactSpanReference(hx, hy, i)); break; } } } } if (empty) GetHeightDataSeedsFromVertices(compactField, poly, polyCount, verts, borderSize, hp, stack); const int RetractSize = 256; int head = 0; while (head < stack.Count) { var cell = stack[head++]; var cs = compactField[cell]; if (head >= RetractSize) { head = 0; if (stack.Count > RetractSize) { for (int i = 0; i < stack.Count - RetractSize; i++) stack[i] = stack[i + RetractSize]; } int targetSize = stack.Count % RetractSize; while (stack.Count > targetSize) stack.RemoveAt(stack.Count - 1); } //loop in all four directions for (var dir = Direction.West; dir <= Direction.South; dir++) { //skip if (!cs.IsConnected(dir)) continue; int ax = cell.X + dir.GetHorizontalOffset(); int ay = cell.Y + dir.GetVerticalOffset(); int hx = ax - hp.X - borderSize; int hy = ay - hp.Y - borderSize; if (hx < 0 || hx >= hp.Width || hy < 0 || hy >= hp.Length) continue; //only continue if height is unset if (hp.IsSet(hy * hp.Width + hx)) continue; //get new span int ai = compactField.Cells[ay * compactField.Width + ax].StartIndex + CompactSpan.GetConnection(ref cs, dir); CompactSpan ds = compactField.Spans[ai]; hp[hx, hy] = ds.Minimum; stack.Add(new CompactSpanReference(ax, ay, ai)); } } }