/// <summary> /// Returns a new mesh instance. This instance has its polygons auto-generated according to /// the given set "blocked" of impassable (int,int)-points. The largest possibly passable area will be /// a rectangle from (0,0) to (width,height). /// </summary> /// <param name="blocked">contains all integer points that are impassable</param> /// <param name="remainingPolygons">The polygons that still exist within area</param> /// <param name="coveredByPolygonBefore">the nodes that are covered by remainingPolygons</param> /// <param name="scale">the mesh scale</param> /// <param name="area">the area that should be filled with new polygons</param> /// <param name="createdNodes">the still existing nodes that must be integrated</param> /// <param name="unite">if everything in the area should be united at last</param> /// <returns>a new instance of Navigation Mesh that contains all freshly created polygons (but not remainingPolygons)</returns> public static NavigationMesh CreateInstanceFromBlockmap(PointSet<int> blocked, List<Polygon> remainingPolygons, PointSet<int> coveredByPolygonBefore, int scale, Rectangle area, List<MeshNode> createdNodes, bool unite = true) { GlobalValues globalValues = GlobalValues.GetInstance(); PointSet<int> coveredByPremarkedPolygon = new PointSet<int>(); if (globalValues.mShowMeshCreation) { CodeTest.mRects2ToDraw.Add(area); //CodeTest.mSetsToDraw.Add(coveredByPolygonBefore); // slows drawing down for big meshes //CodeTest.mSetsToDraw.Add(coveredByPremarkedPolygon); //CodeTest.mSetsToDraw.Add(blocked); } Debug.Assert(area.Width > 0 && area.Height > 0 && area.X >= 0 && area.Y >= 0); int areaWidth = (area.Width / scale); int areaHeight = (area.Height / scale); int areaOffsetX = (area.X / scale); int areaOffsetY = (area.Y / scale); PointSet<int> nodeToCreate = new PointSet<int>(); PointSet<int> nodeCreated = new PointSet<int>(); foreach (MeshNode node in createdNodes) { int x = ((int)(node.mVector.X)) / scale; int y = ((int)(node.mVector.Y)) / scale; nodeToCreate.Add(x, y); nodeCreated.Add(x, y); } #if DEBUG if (areaWidth < 20 && areaHeight < 20) { Debug.WriteLine("blocked (part):"); NavigationMesh.PrintPointSet(blocked, areaOffsetX, areaOffsetY, areaWidth + 1, areaHeight + 1); } #endif List<Rectangle> premarkedPolygons = new List<Rectangle>(); // a point(a,b) in coveredByPolygon indicates that there is already // a registered polygon which covers at least the square (a,b)-(a+1,b+1) for (int y = areaOffsetY; y < areaOffsetY + areaHeight; y++) { for (int x = areaOffsetX; x < areaOffsetX + areaWidth; x++) { // we wanna create a new rectangular polygon(x,y,*,*) // as big as possible. if (!coveredByPolygonBefore.Contains(x, y) && !coveredByPremarkedPolygon.Contains(x, y) && !blocked.Contains(x, y) && !blocked.Contains(x + 1, y) && !blocked.Contains(x, y + 1) && !blocked.Contains(x + 1, y + 1)) { int polyWidth = 1; int polyHeight = 1; while (x + polyWidth <= areaOffsetX + areaWidth && HorizontalFree(blocked, x, y, polyWidth, polyHeight) && HorizontalFree(coveredByPolygonBefore, x, y, polyWidth - 1, polyHeight - 1) && HorizontalFree(coveredByPremarkedPolygon, x, y, polyWidth - 1, polyHeight - 1) && y + polyHeight <= areaOffsetY + areaHeight && VerticalFree(blocked, x, y, polyWidth, polyHeight) && VerticalFree(coveredByPolygonBefore, x, y, polyWidth - 1, polyHeight - 1) && VerticalFree(coveredByPremarkedPolygon, x, y, polyWidth - 1, polyHeight - 1)) { if (globalValues.mShowMeshCreation) { Rectangle rectToDraw = new Rectangle(scale * x, scale * y, scale * polyWidth, scale * polyHeight); CodeTest.mRectsToDraw.Add(rectToDraw); CodeTest.DrawNow(); CodeTest.mRectsToDraw.Remove(rectToDraw); } polyWidth++; polyHeight++; } polyWidth--; polyHeight--; Debug.Assert(polyWidth > 0 && polyHeight > 0 && polyWidth == polyHeight); // We won't create the polygon now because there is a little // catch (see below), but we are keeping it in mind. // => fill premarkedPolygons now... if (globalValues.mShowMeshCreation) { Rectangle premarkedFullScaled = new Rectangle(scale * x, scale * y, scale * polyWidth, scale * polyHeight); CodeTest.mRectsToDraw.Add(premarkedFullScaled); } Rectangle premarked = new Rectangle(x, y, polyWidth, polyHeight); premarkedPolygons.Add(premarked); // ...and mark nodes as used: nodeToCreate.Add(x, y); nodeToCreate.Add(x + polyWidth, y); nodeToCreate.Add(x + polyWidth, y + polyHeight); nodeToCreate.Add(x, y + polyHeight); // register in coveredByPolygon: for (int i = x; i < x + polyWidth; i++) { for (int j = y; j < y + polyHeight; j++) { coveredByPremarkedPolygon.Add(i, j); } } } } } Debug.WriteLine("premarked polygons:" + premarkedPolygons.Count); if (globalValues.mShowMeshCreation) { for (int i = 0; i < premarkedPolygons.Count; i++) CodeTest.mRectsToDraw.RemoveAt(CodeTest.mRectsToDraw.Count - 1); } // Now that we have full knowledge about ALL nodes to create, we can // create the appropriate polygons. The catch is that sometimes it doesn't // suffice to create the polygon between its corners, but there are polygons // with more vertices in between. int mapWidth = GameLogic.GetInstance().GetMap().GetWidth(); int mapHeight = GameLogic.GetInstance().GetMap().GetHeight(); NavigationMesh result = new NavigationMesh(mapWidth, mapHeight); if (globalValues.mShowMeshCreation) CodeTest.mMeshesToDraw.Add(result); // Create each premarked polygon: foreach (Rectangle rectangle in premarkedPolygons) { List<Point> vertexList = GetVertexList(rectangle, nodeToCreate); Debug.Assert(vertexList.Count >= 4); List<MeshNode> nodes = new List<MeshNode>(); // check which nodes are already created and which need to be created: foreach (Point point in vertexList) { MeshNode node = RetrieveOrCreateNode(point, scale, nodeCreated, createdNodes, remainingPolygons, coveredByPolygonBefore); nodes.Add(node); } // now we can chain up our nodes: Polygon polygon = new Polygon(nodes); polygon.ImposeOwnership(); // add polygon to our mesh: // NOTE: WE MAY NOT ALLOW POLYGON CLEAN UP DURING MESH CONSTRUCTION!!!! // Damn error, I've found you at last... result.AddPolygon(polygon); if (globalValues.mShowMeshCreation) CodeTest.DrawNow(); } // Now unite and clean everything: (At least I think we are allowed now...) if (unite) result.WholeUnionRun(true, null); if (globalValues.mShowMeshCreation) { CodeTest.mMeshesToDraw.Remove(result); CodeTest.mRects2ToDraw.Remove(area); //CodeTest.mSetsToDraw.Remove(coveredByPolygonBefore); // again, taken out for being slow //CodeTest.mSetsToDraw.Remove(coveredByPremarkedPolygon); //CodeTest.mSetsToDraw.Remove(blocked); } return result; }
/// <summary> /// Unites two given polygons into one and returns it. The two polygons /// are deleted. Optionally applies a cleanup to the new polygon. /// </summary> /// <param name="unionEdges">the edges of the new united polygon</param> /// <param name="polygon1">the first of the polygons that are to be united</param> /// <param name="polygon2">the second of the polygons that are to be united</param> /// <param name="allowCleanUp">if the edges of the united polygon should /// be cleaned up</param> /// <returns>the new united polygon</returns> private Polygon ApplyPolygonUnion(List<Edge> unionEdges, Polygon polygon1, Polygon polygon2, bool allowCleanUp) { Debug.Assert(! polygon1.CanBeCleanedUp(), "Cannot unite with a dirty polygon!"); Debug.Assert(!polygon2.CanBeCleanedUp(), "Cannot unite with a dirty polygon!"); Polygon unionPolygon = new Polygon(unionEdges); unionPolygon.ImposeOwnership(); polygon1.Delete(true); RemovePolygon(polygon1); polygon2.Delete(true); RemovePolygon(polygon2); AddPolygon(unionPolygon); if (allowCleanUp) unionPolygon.CleanUp(true); return unionPolygon; }
/// <summary> /// For code testing purpose: /// Returns a new special-shaped mesh instance. /// </summary> public static NavigationMesh[] GetTestInstanceForCleanUp() { NavigationMesh[] result = new NavigationMesh[3]; for (int x = 0; x < 3; x++) { MeshNode a = new MeshNode(0, 0); MeshNode b = new MeshNode(0, 10); MeshNode c = new MeshNode(5, 10); MeshNode d = new MeshNode(5, 0); MeshNode e = new MeshNode(0, 15); MeshNode f = new MeshNode(10, 15); MeshNode g = new MeshNode(10, 10); MeshNode h = new MeshNode(15, 15); MeshNode i = new MeshNode(15, 0); MeshNode j = new MeshNode(10, 0); Edge ab = new Edge(a, b); ab.ImposeOwnership(); Edge bc = new Edge(b, c); bc.ImposeOwnership(); Edge cd = new Edge(c, d); cd.ImposeOwnership(); Edge da = new Edge(d, a); da.ImposeOwnership(); Edge be = new Edge(b, e); be.ImposeOwnership(); Edge ef = new Edge(e, f); ef.ImposeOwnership(); Edge fg = new Edge(f, g); fg.ImposeOwnership(); Edge gc = new Edge(g, c); gc.ImposeOwnership(); Edge cb = new Edge(c, b); cb.ImposeOwnership(); Edge fh = new Edge(f, h); fh.ImposeOwnership(); Edge hi = new Edge(h, i); hi.ImposeOwnership(); Edge ij = new Edge(i, j); ij.ImposeOwnership(); Edge jg = new Edge(j, g); jg.ImposeOwnership(); Edge gf = new Edge(g, f); gf.ImposeOwnership(); Polygon pA = new Polygon(new List<Edge> { ab, bc, cd, da }); pA.ImposeOwnership(); Polygon pB = new Polygon(new List<Edge> { be, ef, fg, gc, cb }); pB.ImposeOwnership(); Polygon pC = new Polygon(new List<Edge> { fh, hi, ij, jg, gf }); pC.ImposeOwnership(); result[x] = new NavigationMesh(15,15); result[x].AddPolygon(pA); result[x].AddPolygon(pB); result[x].AddPolygon(pC); } return result; }
/// <summary> /// For code testing purposes: /// Creates a raster of polygons where each one is rectangle-shaped and adjacent to each other. /// Note that you have to pass x as countX for x columns of squares, /// the same applies in y direction. The returned mesh then accepts points in the range of /// (0,0)-(cellWidth*countX, cellHeight*countY). /// </summary> /// <param name="cellWidth">the width of each polygon</param> /// <param name="cellHeight">the height of each polygon</param> /// <param name="countX">the number of polygons to create in horizontal direction</param> /// <param name="countY">the number of polygons to create in vertical direction</param> /// <param name="doUnite">if the polygons should be united where possible (reduces raster complexity)</param> /// <returns>the new mesh instance</returns> public static NavigationMesh[] GetTestInstance(int cellWidth, int cellHeight, int countX, int countY, bool doUnite) { Debug.Assert(countX >= 1 && countY >= 1 && cellWidth > 0 && cellHeight > 0); NavigationMesh[] testMesh = new NavigationMesh[3]; for (int i = 0; i < 3; i++) { testMesh[i] = new NavigationMesh(countX * cellWidth, countY * cellHeight); MeshNode[,] nodes = new MeshNode[countX + 1, countY + 1]; for (int x = 0; x <= countX; x++) { for (int y = 0; y <= countY; y++) { nodes[x, y] = new MeshNode(x * cellWidth, y * cellHeight); } } for (int x = 0; x < countX; x++) { for (int y = 0; y < countY; y++) { List<Edge> edges = new List<Edge> { new Edge(nodes[x, y], nodes[x + 1, y]), new Edge(nodes[x + 1, y], nodes[x + 1, y + 1]), new Edge(nodes[x + 1, y + 1], nodes[x, y + 1]), new Edge(nodes[x, y + 1], nodes[x, y]) }; foreach (Edge edge in edges) { edge.ImposeOwnership(); } Polygon polygon = new Polygon(edges); polygon.ImposeOwnership(); testMesh[i].AddPolygon(polygon); } } if (doUnite) { testMesh[i].WholeUnionRun(true, null); } } return testMesh; }
private static void PolygonTests() { double pi = Math.PI; List<MeshNode> nodes = new List<MeshNode> { new MeshNode(0, 0), new MeshNode(10, 0), new MeshNode(10, 10), new MeshNode(0, 10) }; Polygon polygon = new Polygon(nodes); polygon.ImposeOwnership(); List<Edge> edges = polygon.GetEdges(); Debug.Assert(HelperMethods.Circa( HelperMethods.GetAngleDifference(edges[0].ToVector(), edges[1].ToVector()), pi / 2)); Debug.Assert(Polygon.IsConcave(polygon.GetEdges()), "CodeTest.PolygonTests(): Polygon not concave!"); Debug.Assert(polygon.PointInPolygon(new Vector2(10, 5), false), "CodeTest.PolygonTests(): 10,5 not in Polygon!"); Random r = new Random(); Vector2 point1 = new Vector2((float)(10 * r.NextDouble()), (float)(10 * r.NextDouble())); Debug.Assert(polygon.PointInPolygon(point1, false), "CodeTest.PolygonTests(): Random point not in Polygon!"); Vector2 p1 = new Vector2(0, 0); Vector2 p2 = new Vector2(0, 10); Vector2 p3 = new Vector2(10, 0); Vector2 p4 = new Vector2(10, 10); Debug.Assert(polygon.PointInPolygon(p1, false), "PointInPolygon Test failed!"); Debug.Assert(polygon.PointInPolygon(p2, false), "PointInPolygon Test failed!"); Debug.Assert(polygon.PointInPolygon(p3, false), "PointInPolygon Test failed!"); Debug.Assert(polygon.PointInPolygon(p4, false), "PointInPolygon Test failed!"); List<MeshNode> nodes2 = new List<MeshNode> { nodes[1], new MeshNode(20, 0), new MeshNode(20, 10), nodes[2] }; Polygon polygon2 = new Polygon(nodes2); polygon2.ImposeOwnership(); List<Polygon> polygonNeighbours = polygon.GetAdjacentPolygons(); List<Polygon> polygon2Neighbours = polygon2.GetAdjacentPolygons(); Debug.Assert(polygonNeighbours.Count == 1 && polygonNeighbours.Contains(polygon2)); Debug.Assert(polygon2Neighbours.Count == 1 && polygon2Neighbours.Contains(polygon)); Edge commonEdge, commonEdge2; List<Edge> unitedEdges = polygon.GetUnion(polygon2, out commonEdge, out commonEdge2); Polygon unitedPolygon = new Polygon(unitedEdges); unitedPolygon.ImposeOwnership(); polygon.Delete(true); polygon2.Delete(true); unitedPolygon.CleanUp(true); Debug.Assert(commonEdge.mStartNode == null); Debug.Assert(commonEdge.mEndNode == null); Debug.Assert(commonEdge2.mStartNode == null); Debug.Assert(commonEdge2.mEndNode == null); Debug.Assert(unitedPolygon.EdgesHaveOneOwner()); Debug.Assert(unitedPolygon.NodesEdgesOwnerTest()); Debug.WriteLine("Use breakpoint to check the last union test."); Debug.WriteLine("Polygon tests done."); unitedPolygon.Delete(true); }
/// <summary> /// Tests for Navigation Mesh and Pathfinder, /// including performance test. /// </summary> private static void PathfinderTest() { Debug.WriteLine("Create testMesh..."); NavigationMesh[] testMesh = NavigationMeshFactory.GetTestInstanceForCleanUp(); Debug.WriteLine("testMesh created. Try 1. pathfinding..."); PathFinder.GetInstance().SetMesh(testMesh); Stopwatch watch = Stopwatch.StartNew(); Path path = PathFinder.GetInstance().CalculatePath(new Vector2(1, 1), new Vector2(4, 14), typeof(Minion)); Debug.Assert(path != null); watch.Stop(); Debug.WriteLine("1. Pathfinding done in " + watch.ElapsedMilliseconds + " ms. Use breakpoint to check the returned path."); GameLogic.GetInstance().GetGameStateManager().UnloadGame(); // ********************* second pathfinder test ************************** Debug.WriteLine("Try 2. pathfinding on another testMesh..."); watch = Stopwatch.StartNew(); int width = 5000; int height = 5000; Map map = Map.GetRandomInstance(20, width, height); Debug.Assert(map != null); GameLogic.GetInstance().SetMap(map); Debug.Assert(GameLogic.GetInstance().GetMap() != null); PathFinder.GetInstance().LoadMeshFromObstructions(width, height, 5); watch.Stop(); Debug.WriteLine("Mesh creation done in " + watch.ElapsedMilliseconds + " ms."); watch = Stopwatch.StartNew(); // ReSharper disable RedundantAssignment // I want to read this out via debugger path = PathFinder.GetInstance().CalculatePath(new Vector2(0, 0), new Vector2(width - 1, height - 1), typeof(Minion)); // ReSharper restore RedundantAssignment watch.Stop(); Debug.WriteLine("2. Pathfinding done in " + watch.ElapsedMilliseconds + " ms. Check path via breakpoint."); // ******************************* Extreme case: Check that a direct connection is NOT feasible ************************** List<Polygon> endPolygons = new List<Polygon>(); List<MeshNode> meshNodes = new List<MeshNode>(); for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { meshNodes.Add(new MeshNode(x, y)); } } List<Polygon> polygons = new List<Polygon>(); for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) { if (!(x == 1 && y == 1)) { Polygon polygon = new Polygon(new List<MeshNode> { meshNodes[y * 4 + x], meshNodes[y * 4 + (x + 1)], meshNodes[(y + 1) * 4 + (x + 1)], meshNodes[(y + 1) * 4 + x] }); polygon.ImposeOwnership(); polygons.Add(polygon); } } } int startNodeIndex = 9; int endNodeIndex = 6; endPolygons.Add(polygons[2]); Stack<Edge> firstCandidateEdges = new Stack<Edge>(meshNodes[startNodeIndex].GetPolyOwnersEdges()); Debug.Assert(!PathCleaner.IsCleanUpPossible(endPolygons, firstCandidateEdges, meshNodes[startNodeIndex].mVector, meshNodes[endNodeIndex].mVector)); GameLogic.GetInstance().GetGameStateManager().UnloadGame(); // ******************* Direct connection test ****************** int length = 3; int count = 3; testMesh = NavigationMeshFactory.GetTestInstance(length, length, count, count, false); PathFinder.GetInstance().SetMesh(testMesh); Debug.Assert(PathFinder.GetInstance().DirectConnection(new Vector2(2, 0), new Vector2(2, 3), typeof(Minion)), "Direct connection failed for (2,0)-(2,3)"); Debug.Assert(PathFinder.GetInstance().DirectConnection(new Vector2(8, 1), new Vector2(3, 1), typeof(Minion)), "Direct connection failed for (8,1)-(3,1)"); Debug.Assert(PathFinder.GetInstance().DirectConnection(new Vector2(8, 1), new Vector2(3, 3), typeof(Minion)), "Direct connection failed for (8,1)-(3,3)"); Debug.Assert(PathFinder.GetInstance().DirectConnection(new Vector2(4, 9), new Vector2(5, 4), typeof(Minion)), "Direct connection failed for (4,9)-(5,4)"); Random r = new Random(); Vector2 point1 = new Vector2(r.Next(length * count + 1), r.Next(length * count + 1)); Vector2 point2 = new Vector2(r.Next(length * count + 1), r.Next(length * count + 1)); Debug.Assert(PathFinder.GetInstance().DirectConnection(point1, point2, typeof(Minion)), "Direct connection test failed for (" + point1.X + "," + point1.Y + ")-(" + point2.X + "," + point2.Y + ")"); GameLogic.GetInstance().GetGameStateManager().UnloadGame(); }
private static void HelperMethodsTest() { // Angle: Debug.Assert(HelperMethods.Circa( HelperMethods.GetAngleDifference(new Vector2(1, 0), new Vector2(0, -1)), -Math.PI / 2)); // PointOnLine: Debug.Assert(HelperMethods.PointOnLine(new Vector2(1, 1), new Vector2(0, 0), new Vector2(2, 2))); Debug.Assert(HelperMethods.PointOnLine(new Vector2(1, 1), new Vector2(2, 2), new Vector2(0, 0))); Debug.Assert(HelperMethods.PointOnLine(new Vector2(1, 1), new Vector2(2, 0), new Vector2(0, 2))); Debug.Assert(!HelperMethods.PointOnLine(new Vector2(2, 0), new Vector2(1, 1), new Vector2(0, 2))); Debug.Assert(HelperMethods.PointOnLine(new Vector2(1, 1), new Vector2(1, 1), new Vector2(0, 0))); Debug.Assert(HelperMethods.PointOnLine(new Vector2(0, 2), new Vector2(0, 0), new Vector2(0, 2))); Debug.Assert(HelperMethods.PointOnLine(new Vector2(0, 0), new Vector2(0, 2), new Vector2(0, 0))); // PointInPolygon: // These points are all inside: Polygon polygon = new Polygon(new List<MeshNode> { new MeshNode(0, 0), new MeshNode(2, 0), new MeshNode(2, 2), new MeshNode(1, 2), new MeshNode(0, 2), new MeshNode(0, 1) }); polygon.ImposeOwnership(); foreach (MeshNode node in polygon.GetNodes()) { Debug.Assert(polygon.PointInPolygon(node.mVector, false)); } Debug.Assert(polygon.PointInPolygon(new Vector2(1, 0), false)); Debug.Assert(polygon.PointInPolygon(new Vector2(1, 1), false)); Debug.Assert(polygon.PointInPolygon(new Vector2(2, 1), false)); // Now for some points outside: Debug.Assert(!polygon.PointInPolygon(new Vector2(0, -1), false)); Debug.Assert(!polygon.PointInPolygon(new Vector2(1, -1), false)); Debug.Assert(!polygon.PointInPolygon(new Vector2(2, -1), false)); Debug.Assert(!polygon.PointInPolygon(new Vector2(0, 3), false)); Debug.Assert(!polygon.PointInPolygon(new Vector2(1, 3), false)); Debug.Assert(!polygon.PointInPolygon(new Vector2(2, 3), false)); polygon.Delete(true); }