Flow field raycasting
This commit is contained in:
@@ -295,6 +295,8 @@ public class HierarchyPathFinder implements Runnable{
|
||||
}catch(Exception ignored){} //probably has some concurrency issues when iterating but I don't care, this is for debugging
|
||||
}
|
||||
});
|
||||
|
||||
Draw.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1055,14 +1057,16 @@ public class HierarchyPathFinder implements Runnable{
|
||||
int maxIterations = 30; //TODO higher/lower number? is this still too slow?
|
||||
int i = 0;
|
||||
boolean recalc = false;
|
||||
unit.hitboxTile(Tmp.r3);
|
||||
//tile rect size has tile size factored in, since the ray cannot have thickness
|
||||
float tileRectSize = tilesize + Tmp.r3.height;
|
||||
|
||||
//TODO last pos can change if the flowfield changes.
|
||||
if(initialTileOn.pos() != request.lastTile || request.lastTargetTile == null){
|
||||
//TODO tanks have weird behavior near edges of walls, as they try to avoid them
|
||||
boolean anyNearSolid = false;
|
||||
|
||||
//find the next tile until one near a solid block is discovered
|
||||
while(i ++ < maxIterations && !anyNearSolid){
|
||||
while(i ++ < maxIterations){
|
||||
int value = getCost(fieldCache, old, tileOn.x, tileOn.y);
|
||||
|
||||
Tile current = null;
|
||||
@@ -1083,16 +1087,16 @@ public class HierarchyPathFinder implements Runnable{
|
||||
}
|
||||
|
||||
//check for corner preventing movement
|
||||
if((checkCorner(unit, tileOn, other, dir - 1) || checkCorner(unit, tileOn, other, dir + 1)) &&
|
||||
(checkSolid(unit, tileOn, dir - 2) || checkSolid(unit, tileOn, dir + 2))){ //there must be a tile to the left or right to keep the unit from going back and forth forever
|
||||
//if((checkCorner(unit, tileOn, other, dir - 1) || checkCorner(unit, tileOn, other, dir + 1)) &&
|
||||
// (checkSolid(unit, tileOn, dir - 2) || checkSolid(unit, tileOn, dir + 2))){ //there must be a tile to the left or right to keep the unit from going back and forth forever
|
||||
|
||||
recalc = true;
|
||||
//recalc = true;
|
||||
//keep moving even if it's blocked
|
||||
any = true;
|
||||
continue;
|
||||
}
|
||||
//any = true;
|
||||
// continue;
|
||||
//}
|
||||
|
||||
if(otherCost < value && otherCost != impassable && (otherCost != 0 || packed == destPos) && (current == null || otherCost < minCost) && passable(unit.team.id, cost, packed) &&
|
||||
if((value == 0 || otherCost < value) && otherCost != impassable && (otherCost != 0 || packed == destPos) && (current == null || otherCost < minCost) && passable(unit.team.id, cost, packed) &&
|
||||
//diagonal corner trap
|
||||
!(
|
||||
(!passable(team, cost, world.packArray(tileOn.x + point.x, tileOn.y)) ||
|
||||
@@ -1104,13 +1108,28 @@ public class HierarchyPathFinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
//TODO raycast spam = extremely slow
|
||||
if(!(current == null || (costId == costGround && current.dangerous() && !tileOn.dangerous()))){
|
||||
tileOn = current;
|
||||
any = true;
|
||||
|
||||
if(current.array() == destPos){
|
||||
//when anyNearSolid is false, no solid tiles have been encountered anywhere so far, so raycasting is a waste of time
|
||||
if(anyNearSolid && !tileOn.dangerous() && raycastRect(unit.x, unit.y, current.x * tilesize, current.y * tilesize, team, cost, initialTileOn.x, initialTileOn.y, current.x, current.y, tileRectSize)){
|
||||
|
||||
//TODO this may be a mistake
|
||||
if(tileOn == initialTileOn){
|
||||
recalc = true;
|
||||
any = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}else{
|
||||
tileOn = current;
|
||||
any = true;
|
||||
|
||||
if(current.array() == destPos){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
@@ -1156,24 +1175,6 @@ public class HierarchyPathFinder implements Runnable{
|
||||
initializePathRequest(request, request.team, request.costId, request.unit.tileX(), request.unit.tileY(), request.destination % wwidth, request.destination / wwidth);
|
||||
}
|
||||
|
||||
private boolean checkSolid(Unit unit, Tile tile, int dir){
|
||||
var p = Geometry.d8[Mathf.mod(dir, 8)];
|
||||
return !unit.canPass(tile.x + p.x, tile.y + p.y);
|
||||
}
|
||||
|
||||
private boolean checkCorner(Unit unit, Tile tile, Tile next, int dir){
|
||||
Tile other = tile.nearby(Geometry.d8[Mathf.mod(dir, 8)]);
|
||||
if(other == null){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!unit.canPass(other.x, other.y)){
|
||||
return Geometry.raycastRect(unit.x, unit.y, next.worldx(), next.worldy(), Tmp.r1.setCentered(other.worldx(), other.worldy(), tilesize).grow(Math.min(unit.hitSize * 0.66f, 7.6f))) != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getCost(FieldCache cache, FieldCache old, int x, int y){
|
||||
//fall back to the old flowfield when possible - it's best not to use partial results from the base cache
|
||||
if(old != null){
|
||||
@@ -1244,6 +1245,47 @@ public class HierarchyPathFinder implements Runnable{
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean overlap(int team, PathCost type, int x, int y, float startX, float startY, float endX, float endY, float rectSize){
|
||||
if(x < 0 || y < 0 || x >= wwidth || y >= wheight) return false;
|
||||
if(!passable(team, type, x + y * wwidth)){
|
||||
return Intersector.intersectSegmentRectangleFast(startX, startY, endX, endY, x * tilesize - rectSize/2f, y * tilesize - rectSize/2f, rectSize, rectSize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean raycastRect(float startX, float startY, float endX, float endY, int team, PathCost type, int x1, int y1, int x2, int y2, float rectSize){
|
||||
int ww = wwidth, wh = wheight;
|
||||
int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1;
|
||||
int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1;
|
||||
int e2, err = dx - dy;
|
||||
|
||||
while(x >= 0 && y >= 0 && x < ww && y < wh){
|
||||
if(
|
||||
!passable(team, type, x + y * wwidth) ||
|
||||
overlap(team, type, x + 1, y, startX, startY, endX, endY, rectSize) ||
|
||||
overlap(team, type, x - 1, y, startX, startY, endX, endY, rectSize) ||
|
||||
overlap(team, type, x, y + 1, startX, startY, endX, endY, rectSize) ||
|
||||
overlap(team, type, x, y - 1, startX, startY, endX, endY, rectSize)
|
||||
) return true;
|
||||
|
||||
if(x == x2 && y == y2) return false;
|
||||
|
||||
//diagonal ver
|
||||
e2 = 2 * err;
|
||||
if(e2 > -dy){
|
||||
err -= dy;
|
||||
x += sx;
|
||||
}
|
||||
|
||||
if(e2 < dx){
|
||||
err += dx;
|
||||
y += sy;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean avoid(int team, PathCost type, int tilePos){
|
||||
int cost = cost(team, type, tilePos);
|
||||
return cost == impassable || cost >= 2;
|
||||
|
||||
@@ -205,6 +205,8 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
boolean alwaysArrive = false;
|
||||
|
||||
if(targetPos != null){
|
||||
boolean move = true, isFinalPoint = commandQueue.size == 0;
|
||||
vecOut.set(targetPos);
|
||||
@@ -251,6 +253,8 @@ public class CommandAI extends AIController{
|
||||
|
||||
//if you've spent 3 seconds stuck, something is wrong, move regardless
|
||||
move = hpath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
//rare case where unit must be perfectly aligned (happens with 1-tile gaps)
|
||||
alwaysArrive = vecOut.epsilonEquals(unit.tileX() * tilesize, unit.tileY() * tilesize);
|
||||
//we've reached the final point if the returned coordinate is equal to the supplied input
|
||||
isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f);
|
||||
|
||||
@@ -278,7 +282,7 @@ public class CommandAI extends AIController{
|
||||
attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram ? engageRange :
|
||||
unit.isGrounded() ? 0f :
|
||||
attackTarget != null && stance != UnitStance.ram ? engageRange :
|
||||
0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint);
|
||||
0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ public class AIController implements UnitController{
|
||||
vec.setLength(speed * length);
|
||||
}
|
||||
|
||||
//do not move when infinite vectors are used or if its zero.
|
||||
//ignore invalid movement values
|
||||
if(vec.isNaN() || vec.isInfinite() || vec.isZero()) return;
|
||||
|
||||
if(!unit.type.omniMovement && unit.type.rotateMoveFirst){
|
||||
|
||||
Reference in New Issue
Block a user