diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index 1dc36f8855..755a9d63bb 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -313,8 +313,8 @@ public class Vars implements Loadable{ spawner = new WaveSpawner(); indexer = new BlockIndexer(); pathfinder = new Pathfinder(); - hpath = new HierarchyPathFinder(); controlPath = new ControlPathfinder(); + hpath = new HierarchyPathFinder(); fogControl = new FogControl(); bases = new BaseRegistry(); logicVars = new GlobalVars(); diff --git a/core/src/mindustry/ai/HierarchyPathFinder.java b/core/src/mindustry/ai/HierarchyPathFinder.java index 6a235cfb44..dcc1d2f762 100644 --- a/core/src/mindustry/ai/HierarchyPathFinder.java +++ b/core/src/mindustry/ai/HierarchyPathFinder.java @@ -6,13 +6,17 @@ import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; import arc.struct.*; -import mindustry.game.*; +import arc.util.*; +import mindustry.content.*; import mindustry.game.EventType.*; +import mindustry.game.*; import mindustry.graphics.*; import static mindustry.Vars.*; import static mindustry.ai.Pathfinder.*; +//https://webdocs.cs.ualberta.ca/~mmueller/ps/hpastar.pdf +//https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter23_Crowd_Pathfinding_and_Steering_Using_Flow_Field_Tiles.pdf public class HierarchyPathFinder{ static final boolean debug = true; @@ -80,7 +84,7 @@ public class HierarchyPathFinder{ Draw.color(Color.green); Lines.rect(cx * clusterSize * tilesize - tilesize/2f, cy * clusterSize * tilesize - tilesize/2f, clusterSize * tilesize, clusterSize * tilesize); - Draw.color(Color.blue); + Draw.color(Color.red); for(int d = 0; d < 4; d++){ IntSeq portals = cluster.portals[d]; @@ -102,6 +106,16 @@ public class HierarchyPathFinder{ } } } + + Draw.color(Color.magenta); + for(var con : cluster.cons){ + float + x1 = Point2.x(con.posFrom) * tilesize, y1 = Point2.y(con.posFrom) * tilesize, + x2 = Point2.x(con.posTo) * tilesize, y2 = Point2.y(con.posTo) * tilesize, + mx = (cx * clusterSize + clusterSize/2f) * tilesize, my = (cy * clusterSize + clusterSize/2f) * tilesize; + //Lines.curve(x1, y1, mx, my, mx, my, x2, y2, 20); + Lines.line(x1, y1, x2, y2); + } } } } @@ -124,6 +138,8 @@ public class HierarchyPathFinder{ cluster.innerEdges.clear(); } + //TODO: other cluster inner edges should be recomputed if changed. + //TODO look it up based on number. PathCost cost = ControlPathfinder.costGround; @@ -139,14 +155,12 @@ public class HierarchyPathFinder{ if(other == null){ //create new portals at direction - portals = cluster.portals[direction] = new IntSeq(); + portals = cluster.portals[direction] = new IntSeq(4); }else{ //share portals with the other cluster portals = cluster.portals[direction] = other.portals[(direction + 2) % 4]; } - //Point2 adder = Geometry.d4[(direction + 1) % 4]; - int addX = moveDirs[direction * 2], addY = moveDirs[direction * 2 + 1]; int baseX = cx * clusterSize + offsets[direction * 2] * (clusterSize - 1), @@ -185,6 +199,157 @@ public class HierarchyPathFinder{ portals.add(Point2.pack(previous, lastPortal)); } } + + connectInnerEdges(cx, cy, team, cost, cluster); + } + + static PathfindQueue frontier = new PathfindQueue(); + //node index -> total cost + static IntFloatMap costs = new IntFloatMap(); + + static IntSet usedEdges = new IntSet(); + + void connectInnerEdges(int cx, int cy, int team, PathCost cost, Cluster cluster){ + int minX = cx * clusterSize, minY = cy * clusterSize, maxX = Math.min(minX + clusterSize - 1, wwidth - 1), maxY = Math.min(minY + clusterSize - 1, wheight - 1); + + usedEdges.clear(); + cluster.cons.clear(); + + //TODO: how the hell to identify a vertex? + //cluster (i16) | direction (i2) | index (i14) + + for(int direction = 0; direction < 4; direction++){ + var portals = cluster.portals[direction]; + if(portals == null) continue; + + int addX = moveDirs[direction * 2], addY = moveDirs[direction * 2 + 1]; + + for(int i = 0; i < portals.size; i++){ + usedEdges.add(Point2.pack(direction, i)); + + int + portal = portals.items[i], + from = Point2.x(portal), to = Point2.y(portal), + average = (from + to) / 2, + x = (addX * average + cx * clusterSize + offsets[direction * 2] * (clusterSize - 1)), + y = (addY * average + cy * clusterSize + offsets[direction * 2 + 1] * (clusterSize - 1)); + + for(int otherDir = 0; otherDir < 4; otherDir++){ + var otherPortals = cluster.portals[otherDir]; + + for(int j = 0; j < otherPortals.size; j++){ + + //TODO redundant calculations? + if(!usedEdges.contains(Point2.pack(otherDir, j))){ + + int + other = otherPortals.items[j], + otherFrom = Point2.x(other), otherTo = Point2.y(other), + otherAverage = (otherFrom + otherTo) / 2, + ox = cx * clusterSize + offsets[otherDir * 2] * (clusterSize - 1), + oy = cy * clusterSize + offsets[otherDir * 2 + 1] * (clusterSize - 1), + otherX = (moveDirs[otherDir * 2] * otherAverage + ox), + otherY = (moveDirs[otherDir * 2 + 1] * otherAverage + oy); + + //HOW + if(Point2.pack(x, y) == Point2.pack(otherX, otherY)){ + if(true) continue; + + Log.infoList("self ", direction, " ", i, " | ", otherDir, " ", j); + System.exit(1); + } + + float connectionCost = astar( + team, cost, + minX, minY, maxX, maxY, + x + y * wwidth, + otherX + otherY * wwidth, + + (moveDirs[otherDir * 2] * otherFrom + ox), + (moveDirs[otherDir * 2 + 1] * otherFrom + oy), + (moveDirs[otherDir * 2] * otherTo + ox), + (moveDirs[otherDir * 2 + 1] * otherTo + oy) + + ); + + if(connectionCost != -1f){ + cluster.cons.add(new Con(Point2.pack(x, y), Point2.pack(otherX, otherY), connectionCost)); + + Fx.debugLine.at(x* tilesize, y * tilesize, 0f, Color.purple, + new Vec2[]{new Vec2(x, y).scl(tilesize), new Vec2(otherX, otherY).scl(tilesize)}); + } + } + } + } + } + } + } + + //distance heuristic: manhattan + private static float heuristic(int a, int b){ + int x = a % wwidth, x2 = b % wwidth, y = a / wwidth, y2 = b / wwidth; + return Math.abs(x - x2) + Math.abs(y - y2); + } + + private static int tcost(int team, PathCost cost, int tilePos){ + return cost.getCost(team, pathfinder.tiles[tilePos]); + } + + private static float tileCost(int team, PathCost type, int a, int b){ + //currently flat cost + return cost(team, type, b); + } + + /** @return -1 if no path was found */ + float astar(int team, PathCost cost, int minX, int minY, int maxX, int maxY, int startPos, int goalPos, int goalX1, int goalY1, int goalX2, int goalY2){ + frontier.clear(); + costs.clear(); + + costs.put(startPos, 0); + frontier.add(startPos, 0); + + if(debug && false){ + Fx.debugLine.at(Point2.x(startPos) * tilesize, Point2.y(startPos) * tilesize, 0f, Color.purple, + new Vec2[]{new Vec2(Point2.x(startPos), Point2.y(startPos)).scl(tilesize), new Vec2(Point2.x(goalPos), Point2.y(goalPos)).scl(tilesize)}); + } + + while(frontier.size > 0){ + int current = frontier.poll(); + + int cx = current % wwidth, cy = current / wwidth; + + //found the goal (it's in the portal rectangle) + //TODO portal rectangle approach does not work. + if((cx >= goalX1 && cy >= goalY1 && cx <= goalX2 && cy <= goalY2) || current == goalPos){ + return costs.get(current); + } + + for(Point2 point : Geometry.d4){ + int newx = cx + point.x, newy = cy + point.y; + int next = newx + wwidth * newy; + + if(newx > maxX || newy > maxY || newx < minX || newy < minY) continue; + + //TODO fallback mode for enemy walls or whatever + if(tcost(team, cost, next) == impassable) continue; + + float add = tileCost(team, cost, current, next); + float currentCost = costs.get(current); + + if(add < 0) continue; + + float newCost = currentCost + add; + + //a cost of 0 means "not set" + if(!costs.containsKey(next) || newCost < costs.get(next)){ + costs.put(next, newCost); + float priority = newCost + heuristic(next, goalPos); + frontier.add(next, priority); + } + } + } + + return -1f; } Cluster cluster(int pathCost, int cx, int cy){ @@ -213,11 +378,18 @@ public class HierarchyPathFinder{ static class Cluster{ IntSeq[] portals = new IntSeq[4]; IntSeq innerEdges = new IntSeq(); + Seq cons = new Seq<>(); + } - Cluster(){ + //TODO for debugging only + static class Con{ + int posFrom, posTo; + float cost; + public Con(int posFrom, int posTo, float cost){ + this.posFrom = posFrom; + this.posTo = posTo; + this.cost = cost; } - - } }