public void BuildRegions_Success() { //Build a 3x3 heightfield Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), (float)(1.0f / 3.0f), 0.02f); for (int i = 0; i < 9; i++) { hf[i].AddSpan(new Span(10, 20, Area.Default)); hf[i].AddSpan(new Span(25, 30, Area.Default)); } CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); chf.BuildDistanceField(); chf.BuildRegions(1, 2, 3); //Most spans do not have a region id because those spans are part of the border //Most region ids won't be assigned to any span //Total number of regions right now Assert.AreEqual(chf.MaxRegions, 7); //Center spans should have region id Assert.AreEqual((int)chf.Spans[4 * 2 + 0].Region, 5); Assert.AreEqual((int)chf.Spans[4 * 2 + 1].Region, 6); //Check that the rest of the region ids are not assigned to a span for (int i = 0; i < chf.Spans.Length; i++) { for (int j = 0; j <= 4; j++) { Assert.AreNotEqual((int)chf.Spans[i].Region, j); } } }
/// <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); }
/// <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; }
public void ConvertSpans_OneCell() { Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); hf[0].AddSpan(new Span(10, 20, Area.Default)); hf[0].AddSpan(new Span(25, 30, Area.Default)); CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); Assert.AreEqual(chf.Spans.Length, 2); Assert.AreEqual(chf.Spans[0].Minimum, 20); Assert.AreEqual(chf.Spans[0].Height, 5); Assert.AreEqual(chf.Spans[1].Minimum, 30); Assert.AreEqual(chf.Spans[1].Height, int.MaxValue); }
public void ConvertSpans_TwoCells() { Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); hf[0].AddSpan(new Span(10, 20, Area.Default)); hf[0].AddSpan(new Span(25, 30, Area.Default)); hf[1].AddSpan(new Span(5, 15, Area.Default)); hf[1].AddSpan(new Span(25, 30, Area.Default)); hf[1].AddSpan(new Span(40, 55, Area.Default)); CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); Assert.AreEqual(chf.Cells.Length, 4); Assert.AreEqual(chf.Cells[0].StartIndex, 0); Assert.AreEqual(chf.Cells[0].Count, 2); Assert.AreEqual(chf.Cells[1].StartIndex, 2); Assert.AreEqual(chf.Cells[1].Count, 3); }
public void SetConnection_TwoCells() { Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); hf[0].AddSpan(new Span(10, 20, Area.Default)); hf[0].AddSpan(new Span(25, 30, Area.Default)); hf[1].AddSpan(new Span(10, 21, Area.Default)); hf[1].AddSpan(new Span(25, 30, Area.Default)); CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); Assert.IsTrue(chf.Spans[0].IsConnected(Direction.East)); Assert.IsTrue(chf.Spans[1].IsConnected(Direction.East)); Assert.IsTrue(chf.Spans[2].IsConnected(Direction.West)); Assert.IsTrue(chf.Spans[3].IsConnected(Direction.West)); Assert.AreEqual(chf.Spans[0].ConnectionEast, 0); Assert.AreEqual(chf.Spans[1].ConnectionEast, 1); Assert.AreEqual(chf.Spans[2].ConnectionWest, 0); Assert.AreEqual(chf.Spans[3].ConnectionWest, 1); }
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="CompactHeightfield"/> class. /// </summary> /// <param name="field">A <see cref="Heightfield"/> to build from.</param> /// <param name="walkableHeight">The maximum difference in height to filter.</param> /// <param name="walkableClimb">The maximum difference in slope to filter.</param> public CompactHeightfield(Heightfield field, int walkableHeight, int walkableClimb) { this.bounds = field.Bounds; this.width = field.Width; this.height = field.Height; this.length = field.Length; this.cellSize = field.CellSizeXZ; this.cellHeight = field.CellHeight; int spanCount = field.SpanCount; cells = new CompactCell[width * length]; spans = new CompactSpan[spanCount]; areas = new Area[spanCount]; //iterate over the Heightfield's cells int spanIndex = 0; for (int i = 0; i < cells.Length; i++) { //get the heightfield span list, skip if empty var fs = field[i].Spans; if (fs.Count == 0) continue; CompactCell c = new CompactCell(spanIndex, 0); //convert the closed spans to open spans int lastInd = fs.Count - 1; for (int j = 0; j < lastInd; j++) { var s = fs[j]; if (s.Area.IsWalkable) { CompactSpan.FromMinMax(s.Maximum, fs[j + 1].Minimum, out spans[spanIndex]); areas[spanIndex] = s.Area; spanIndex++; c.Count++; } } //the last closed span that has an "infinite" height var lastS = fs[lastInd]; if (lastS.Area.IsWalkable) { spans[spanIndex] = new CompactSpan(fs[lastInd].Maximum, int.MaxValue); areas[spanIndex] = lastS.Area; spanIndex++; c.Count++; } cells[i] = c; } //set neighbor connections for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { CompactCell c = cells[z * width + x]; for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { CompactSpan s = spans[i]; for (var dir = Direction.West; dir <= Direction.South; dir++) { int dx = x + dir.GetHorizontalOffset(); int dz = z + dir.GetVerticalOffset(); if (dx < 0 || dz < 0 || dx >= width || dz >= length) continue; CompactCell dc = cells[dz * width + dx]; for (int j = dc.StartIndex, cellEnd = dc.StartIndex + dc.Count; j < cellEnd; j++) { CompactSpan ds = spans[j]; int overlapBottom, overlapTop; CompactSpan.OverlapMin(ref s, ref ds, out overlapBottom); CompactSpan.OverlapMax(ref s, ref ds, out overlapTop); //Make sure that the agent can walk to the next span and that the span isn't a huge drop or climb if ((overlapTop - overlapBottom) >= walkableHeight && Math.Abs(ds.Minimum - s.Minimum) <= walkableClimb) { int con = j - dc.StartIndex; CompactSpan.SetConnection(dir, con, ref spans[i]); break; } } } } } } }
/// <summary> /// Initializes a new instance of the <see cref="CompactHeightfield"/> class. /// </summary> /// <param name="field">A <see cref="Heightfield"/> to build from.</param> /// <param name="settings">The settings to build with.</param> public CompactHeightfield(Heightfield field, NavMeshGenerationSettings settings) : this(field, settings.VoxelAgentHeight, settings.VoxelMaxClimb) { }
public void Filter_LowHangingWalkable_Success() { var hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); var span = new Span(10, 15, Area.Default); var span2 = new Span(16, 20, Area.Null); hf[0].AddSpan(span); hf[0].AddSpan(span2); hf.FilterLowHangingWalkableObstacles(20); Assert.AreEqual(hf[0].Spans[0].Area, hf[0].Spans[1].Area); }
public void Indexer_CellOutOfRange_Throws() { var hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.5f); Assert.Throws<ArgumentOutOfRangeException>(() => { var c = hf[5]; }); }
public void Indexer_Valid_ReturnsCell() { var hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.5f); Assert.IsNotNull(hf[0, 1]); }
public void Filter_WalkableLowHeight_Success() { var hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); var span = new Span(10, 20, Area.Default); var span2 = new Span(25, 30, Area.Default); hf[0].AddSpan(span); hf[0].AddSpan(span2); //too low to walk through. there is only a gap of 5 units to walk through, //but at least 15 units is needed hf.FilterWalkableLowHeightSpans(15); //so one span is unwalkable and the other is fine Assert.AreEqual(hf[0].Spans[0].Area, Area.Null); Assert.AreEqual(hf[0].Spans[1].Area, Area.Default); }
public void Filter_LowHangingWalkable_Fail() { var hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f); var span = new Span(1, 2, Area.Default); var span2 = new Span(10, 20, Area.Null); hf[2].AddSpan(span); hf[2].AddSpan(span2); //walkable step cannot cover the gap (difference between span2 maximum and span 1 maximum) so fail hf.FilterLowHangingWalkableObstacles(10); Assert.AreNotEqual(hf[0, 1].Spans[0].Area, hf[0, 1].Spans[1].Area); }
public void DistanceField_Simple_Success() { //Build a 3x3 heightfield Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), (float)(1.0f/3.0f), 0.02f); for (int i = 0; i < 9; i++) { hf[i].AddSpan(new Span(10, 20, Area.Default)); hf[i].AddSpan(new Span(25, 30, Area.Default)); } CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); //make sure connections are set Assert.AreEqual(chf.Spans[0 * 2].ConnectionCount, 2); //corner Assert.AreEqual(chf.Spans[1 * 2].ConnectionCount, 3); //edge Assert.AreEqual(chf.Spans[2 * 2].ConnectionCount, 2); //corner Assert.AreEqual(chf.Spans[3 * 2].ConnectionCount, 3); //edge Assert.AreEqual(chf.Spans[4 * 2].ConnectionCount, 4); //center chf.BuildDistanceField(); //check distance field values Assert.AreEqual(chf.MaxDistance, 2); //1st row Assert.AreEqual(chf.Distances[0 * 2], 0); //boundary Assert.AreEqual(chf.Distances[1 * 2], 0); //boundary Assert.AreEqual(chf.Distances[2 * 2], 0); //boundary //2nd row Assert.AreEqual(chf.Distances[3 * 2], 0); //boundary Assert.AreEqual(chf.Distances[4 * 2], 2); //center span Assert.AreEqual(chf.Distances[5 * 2], 0); //boundary }
public void DistanceField_Medium_Success() { //Build a 5x5 heightfield Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.2f, 0.02f); for (int i = 0; i < 25; i++) { hf[i].AddSpan(new Span(10, 20, Area.Default)); hf[i].AddSpan(new Span(25, 30, Area.Default)); } CompactHeightfield chf = new CompactHeightfield(hf, 2, 1); chf.BuildDistanceField(); //Before box blur, MaxDistance is 4 //After box blur, MaxDistance is 2 Assert.AreEqual(chf.MaxDistance, 2); //1st row Assert.AreEqual(chf.Distances[0 * 2], 0); //boundary Assert.AreEqual(chf.Distances[4 * 2], 0); //boundary //2nd row Assert.AreEqual(chf.Distances[5 * 2], 0); //boundary Assert.AreEqual(chf.Distances[6 * 2], 2); //near boundary Assert.AreEqual(chf.Distances[7 * 2], 2); //near boundary Assert.AreEqual(chf.Distances[8 * 2], 2); //near boundary Assert.AreEqual(chf.Distances[9 * 2], 0); //boundary //3rd row Assert.AreEqual(chf.Distances[10 * 2], 0); //boundary Assert.AreEqual(chf.Distances[11 * 2], 2); //near boundary Assert.AreEqual(chf.Distances[12 * 2], 2); //center (box blurred distance is (2*8 + 5)/9) Assert.AreEqual(chf.Distances[13 * 2], 2); //near boundary Assert.AreEqual(chf.Distances[14 * 2], 0); //boundary }