progress
This commit is contained in:
@@ -65,6 +65,12 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
ObjectMap<Unit, PathRequest> unitRequests = new ObjectMap<>();
|
ObjectMap<Unit, PathRequest> unitRequests = new ObjectMap<>();
|
||||||
//maps position in world in (x + y * width format) to a cache of flow fields
|
//maps position in world in (x + y * width format) to a cache of flow fields
|
||||||
IntMap<FieldCache> requests = new IntMap<>();
|
IntMap<FieldCache> requests = new IntMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
//these are for inner edge A*
|
||||||
|
IntFloatMap innerCosts = new IntFloatMap();
|
||||||
|
PathfindQueue innerFrontier = new PathfindQueue();
|
||||||
|
|
||||||
/** Current pathfinding thread */
|
/** Current pathfinding thread */
|
||||||
@Nullable Thread thread;
|
@Nullable Thread thread;
|
||||||
|
|
||||||
@@ -80,10 +86,16 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
IntIntMap cameFrom = new IntIntMap();
|
IntIntMap cameFrom = new IntIntMap();
|
||||||
//frontier for A*
|
//frontier for A*
|
||||||
PathfindQueue frontier = new PathfindQueue();
|
PathfindQueue frontier = new PathfindQueue();
|
||||||
|
|
||||||
|
public PathRequest(int destination){
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class FieldCache{
|
static class FieldCache{
|
||||||
int destination;
|
PathCost cost;
|
||||||
|
int team;
|
||||||
|
int goalPos;
|
||||||
//frontier for flow fields
|
//frontier for flow fields
|
||||||
IntQueue frontier = new IntQueue();
|
IntQueue frontier = new IntQueue();
|
||||||
//maps cluster index to field weights; 0 means uninitialized
|
//maps cluster index to field weights; 0 means uninitialized
|
||||||
@@ -91,6 +103,13 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
|
|
||||||
//TODO: node map for merging
|
//TODO: node map for merging
|
||||||
//TODO: how to extend flowfields?
|
//TODO: how to extend flowfields?
|
||||||
|
|
||||||
|
|
||||||
|
public FieldCache(PathCost cost, int team, int goalPos){
|
||||||
|
this.cost = cost;
|
||||||
|
this.team = team;
|
||||||
|
this.goalPos = goalPos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HierarchyPathFinder(){
|
public HierarchyPathFinder(){
|
||||||
@@ -228,30 +247,6 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
queue.clear();
|
queue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(){
|
|
||||||
while(true){
|
|
||||||
if(net.client()) return;
|
|
||||||
try{
|
|
||||||
|
|
||||||
if(state.isPlaying()){
|
|
||||||
queue.run();
|
|
||||||
|
|
||||||
//TODO: update everything
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
Thread.sleep(updateInterval);
|
|
||||||
}catch(InterruptedException e){
|
|
||||||
//stop looping when interrupted externally
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}catch(Throwable e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 nodeToVec(int current, Vec2 out){
|
Vec2 nodeToVec(int current, Vec2 out){
|
||||||
portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), out);
|
portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), out);
|
||||||
return out;
|
return out;
|
||||||
@@ -451,6 +446,9 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
|
|
||||||
/** @return -1 if no path was found */
|
/** @return -1 if no path was found */
|
||||||
float innerAstar(int team, PathCost cost, int minX, int minY, int maxX, int maxY, int startPos, int goalPos, int goalX1, int goalY1, int goalX2, int goalY2){
|
float innerAstar(int team, PathCost cost, int minX, int minY, int maxX, int maxY, int startPos, int goalPos, int goalX1, int goalY1, int goalX2, int goalY2){
|
||||||
|
var frontier = innerFrontier;
|
||||||
|
var costs = innerCosts;
|
||||||
|
|
||||||
frontier.clear();
|
frontier.clear();
|
||||||
costs.clear();
|
costs.clear();
|
||||||
|
|
||||||
@@ -702,34 +700,103 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out, boolean[] noResultFound){
|
private void updateFields(FieldCache cache, long nsToRun){
|
||||||
|
var frontier = cache.frontier;
|
||||||
|
var fields = cache.fields;
|
||||||
|
var goalPos = cache.goalPos;
|
||||||
|
var pcost = cache.cost;
|
||||||
|
var team = cache.team;
|
||||||
|
|
||||||
|
long start = Time.nanos();
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
//actually do the flow field part
|
||||||
|
//TODO spread this out across many frames
|
||||||
|
while(frontier.size > 0){
|
||||||
|
int tile = frontier.removeLast();
|
||||||
|
int baseX = tile % wwidth, baseY = tile / wwidth;
|
||||||
|
int curWeightIndex = (baseX / clusterSize) + (baseY / clusterSize) * cwidth;
|
||||||
|
int[] curWeights = fields.get(curWeightIndex);
|
||||||
|
|
||||||
|
int cost = curWeights[baseX % clusterSize + ((baseY % clusterSize) * clusterSize)];
|
||||||
|
|
||||||
|
if(cost != impassable){
|
||||||
|
for(Point2 point : Geometry.d4){
|
||||||
|
|
||||||
|
int
|
||||||
|
dx = baseX + point.x, dy = baseY + point.y,
|
||||||
|
clx = dx / clusterSize, cly = dy / clusterSize;
|
||||||
|
|
||||||
|
if(clx < 0 || cly < 0 || dx >= wwidth || dy >= wheight) continue;
|
||||||
|
|
||||||
|
int nextWeightIndex = clx + cly * cwidth;
|
||||||
|
|
||||||
|
int[] weights = nextWeightIndex == curWeightIndex ? curWeights : fields.get(nextWeightIndex);
|
||||||
|
|
||||||
|
//out of bounds; not allowed to move this way because no weights were registered here
|
||||||
|
if(weights == null) continue;
|
||||||
|
|
||||||
|
int newPos = tile + point.x + point.y * wwidth;
|
||||||
|
|
||||||
|
//can't move back to the goal
|
||||||
|
if(newPos == goalPos) continue;
|
||||||
|
|
||||||
|
int newPosArray = (dx - clx * clusterSize) + (dy - cly * clusterSize) * clusterSize;
|
||||||
|
int otherCost = pcost.getCost(team, pathfinder.tiles[newPos]);
|
||||||
|
int oldCost = weights[newPosArray];
|
||||||
|
|
||||||
|
//a cost of 0 means uninitialized, OR it means we're at the goal position, but that's handled above
|
||||||
|
if((oldCost == 0 || oldCost > cost + otherCost) && otherCost != impassable){
|
||||||
|
frontier.addFirst(newPos);
|
||||||
|
weights[newPosArray] = cost + otherCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//every N iterations, check the time spent - this prevents extra calls to nano time, which itself is slow
|
||||||
|
if(nsToRun >= 0 && (counter++) >= 200){
|
||||||
|
counter = 0;
|
||||||
|
if(Time.timeSinceNanos(start) >= nsToRun){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createPathRequest(Unit unit, int goalX, int goalY){
|
||||||
int costId = 0;
|
int costId = 0;
|
||||||
|
|
||||||
int node = findClosestNode(unit.team.id, costId, unit.tileX(), unit.tileY());
|
|
||||||
int dest = findClosestNode(unit.team.id, costId, World.toTile(destination.x), World.toTile(destination.y));
|
|
||||||
|
|
||||||
fields = new IntMap<>();
|
|
||||||
|
|
||||||
PathCost pcost = ControlPathfinder.costGround;
|
PathCost pcost = ControlPathfinder.costGround;
|
||||||
int team = Team.sharded.id;
|
int team = unit.team.id;
|
||||||
int goalPos = (World.toTile(destination.x) + World.toTile(destination.y) * wwidth);
|
|
||||||
|
|
||||||
IntQueue frontier = new IntQueue();
|
int goalPos = (goalX + goalY * wwidth);
|
||||||
frontier.addFirst(goalPos);
|
|
||||||
|
|
||||||
Tile tileOn = unit.tileOn();
|
int node = findClosestNode(team, costId, unit.tileX(), unit.tileY());
|
||||||
|
int dest = findClosestNode(team, costId, goalX, goalY);
|
||||||
|
|
||||||
var result = clusterAstar(costId, node, dest);
|
//TODO: not new?
|
||||||
if(result != null && tileOn != null){
|
PathRequest request = new PathRequest(dest);
|
||||||
|
|
||||||
|
var nodePath = clusterAstar(request, costId, node, dest);
|
||||||
|
|
||||||
|
//TODO: how to reuse
|
||||||
|
FieldCache cache = this.requests.get(goalPos, () -> new FieldCache(pcost, team, goalPos));
|
||||||
|
|
||||||
|
if(cache.frontier.isEmpty()){
|
||||||
|
cache.frontier.addFirst(goalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodePath != null){
|
||||||
|
|
||||||
int fsize = clusterSize * clusterSize;
|
int fsize = clusterSize * clusterSize;
|
||||||
int cx = unit.tileX() / clusterSize, cy = unit.tileY() / clusterSize;
|
int cx = unit.tileX() / clusterSize, cy = unit.tileY() / clusterSize;
|
||||||
|
|
||||||
|
var fields = cache.fields;
|
||||||
|
|
||||||
fields.put(cx + cy * cwidth, new int[fsize]);
|
fields.put(cx + cy * cwidth, new int[fsize]);
|
||||||
|
|
||||||
for(int i = -1; i < result.size; i++){
|
for(int i = -1; i < nodePath.size; i++){
|
||||||
int
|
int
|
||||||
current = i == -1 ? node : result.items[i],
|
current = i == -1 ? node : nodePath.items[i],
|
||||||
cluster = NodeIndex.cluster(current),
|
cluster = NodeIndex.cluster(current),
|
||||||
dir = NodeIndex.dir(current),
|
dir = NodeIndex.dir(current),
|
||||||
dx = Geometry.d4[dir].x,
|
dx = Geometry.d4[dir].x,
|
||||||
@@ -766,60 +833,16 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(int i = -1; i < result.size - 1; i++){
|
}
|
||||||
int current = i == -1 ? node : result.items[i], next = result.items[i + 1];
|
|
||||||
|
|
||||||
portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), Tmp.v1);
|
public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out, boolean[] noResultFound){
|
||||||
portalToVec(0, NodeIndex.cluster(next), NodeIndex.dir(next), NodeIndex.portal(next), Tmp.v2);
|
int costId = 0;
|
||||||
line(Tmp.v1, Tmp.v2, Color.orange);
|
|
||||||
}
|
|
||||||
|
|
||||||
//actually do the flow field part
|
|
||||||
//TODO spread this out across many frames
|
|
||||||
while(frontier.size > 0){
|
|
||||||
int tile = frontier.removeLast();
|
|
||||||
int baseX = tile % wwidth, baseY = tile / wwidth;
|
|
||||||
int curWeightIndex = (baseX / clusterSize) + (baseY / clusterSize) * cwidth;
|
|
||||||
int[] curWeights = fields.get(curWeightIndex);
|
|
||||||
|
|
||||||
int cost = curWeights[baseX % clusterSize + ((baseY % clusterSize) * clusterSize)];
|
|
||||||
|
|
||||||
if(cost != impassable){
|
|
||||||
for(Point2 point : Geometry.d4){
|
|
||||||
|
|
||||||
int
|
|
||||||
dx = baseX + point.x, dy = baseY + point.y,
|
|
||||||
clx = dx / clusterSize, cly = dy / clusterSize;
|
|
||||||
|
|
||||||
if(clx < 0 || cly < 0 || dx >= wwidth || dy >= wheight) continue;
|
|
||||||
|
|
||||||
int nextWeightIndex = clx + cly * cwidth;
|
|
||||||
|
|
||||||
int[] weights = nextWeightIndex == curWeightIndex ? curWeights : fields.get(nextWeightIndex);
|
|
||||||
|
|
||||||
//out of bounds; not allowed to move this way because no weights were registered here
|
|
||||||
if(weights == null) continue;
|
|
||||||
|
|
||||||
int newPos = tile + point.x + point.y * wwidth;
|
|
||||||
|
|
||||||
//can't move back to the goal
|
|
||||||
if(newPos == goalPos) continue;
|
|
||||||
|
|
||||||
int newPosArray = (dx - clx * clusterSize) + (dy - cly * clusterSize) * clusterSize;
|
|
||||||
int otherCost = pcost.getCost(team, pathfinder.tiles[newPos]);
|
|
||||||
int oldCost = weights[newPosArray];
|
|
||||||
|
|
||||||
//a cost of 0 means uninitialized, OR it means we're at the goal position, but that's handled above
|
|
||||||
if((oldCost == 0 || oldCost > cost + otherCost) && otherCost != impassable){
|
|
||||||
frontier.addFirst(newPos);
|
|
||||||
weights[newPosArray] = cost + otherCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Tile tileOn = unit.tileOn();
|
||||||
|
|
||||||
|
if(tileOn != null){
|
||||||
int value = getCost(fields, tileOn.x, tileOn.y);
|
int value = getCost(fields, tileOn.x, tileOn.y);
|
||||||
|
|
||||||
Tile current = null;
|
Tile current = null;
|
||||||
@@ -887,6 +910,35 @@ public class HierarchyPathFinder implements Runnable{
|
|||||||
return cost.getCost(team, pathfinder.tiles[tilePos]);
|
return cost.getCost(team, pathfinder.tiles[tilePos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(){
|
||||||
|
while(true){
|
||||||
|
if(net.client()) return;
|
||||||
|
try{
|
||||||
|
|
||||||
|
if(state.isPlaying()){
|
||||||
|
queue.run();
|
||||||
|
|
||||||
|
//TODO: update everything else too
|
||||||
|
|
||||||
|
//each update time (not total!) no longer than maxUpdate
|
||||||
|
for(FieldCache cache : requests.values()){
|
||||||
|
updateFields(cache, maxUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Thread.sleep(updateInterval);
|
||||||
|
}catch(InterruptedException e){
|
||||||
|
//stop looping when interrupted externally
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}catch(Throwable e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class Cluster{
|
static class Cluster{
|
||||||
IntSeq[] portals = new IntSeq[4];
|
IntSeq[] portals = new IntSeq[4];
|
||||||
//maps rotation + index of portal to list of IntraEdge objects
|
//maps rotation + index of portal to list of IntraEdge objects
|
||||||
|
|||||||
Reference in New Issue
Block a user