Rework pathfinding to use Vector2, form formations

This commit is contained in:
2023-11-23 12:01:42 +01:00
parent 513168825c
commit 0dc8046d8f
6 changed files with 129 additions and 85 deletions

View File

@@ -285,6 +285,12 @@ void bzTileMapDestroy(BzTileMap *map) {
*map = BZ_TILEMAP_INVALID;
}
void bzTileMapPosToTile(BzTileMap *map, Vector2 pos, BzTile *x, BzTile *y) {
if (x) *x = (BzTile) (pos.x / (f32) map->tileWidth);
if (y) *y = (BzTile) (pos.y / (f32) map->tileHeight);
}
void bzTileMapOverrideLayer(BzTileMap *map, i32 slotID, BzTileLayerFunc func) {
BZ_ASSERT(slotID >= 0 && slotID < map->layerCount);
BzTileLayer *layer = map->layers + slotID;

View File

@@ -116,6 +116,8 @@ BzTileset *bzTileObjectGroupGetTileset(BzTileMap *map, BzTileObjectGroup *object
BzTileMap bzTileMapCreate(const BzTileMapDesc *desc);
void bzTileMapDestroy(BzTileMap *map);
void bzTileMapPosToTile(BzTileMap *map, Vector2 pos, BzTile *x, BzTile *y);
void bzTileMapOverrideLayer(BzTileMap *map, i32 slotID, BzTileLayerFunc func);
void bzTileMapOverrideObjectGroup(BzTileMap *map, i32 slotID, BzTileObjectsFunc func);

View File

@@ -192,58 +192,6 @@ void update(float dt, void *userData) {
updatePlayerInput(NULL);
}
static bool isUnitObstructed(f32 x, f32 y, BzTileMap *map) {
return bzTileMapHasCollision(map, x / map->tileWidth, y / map->tileHeight);
}
static bool canPlaceUnit(Vector2 pos, f32 space, BzTileMap *map) {
for (i32 y = -1; y <= 1; y++) {
for (i32 x = -1; x <= 1; x++) {
if (isUnitObstructed(pos.x + x * space, pos.y + y * space, map))
return false;
}
}
return true;
}
static void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces) {
BZ_UNUSED(outPlaces);
f32 angle = Vector2Angle(start, end);
Vector2 size = {Vector2Distance(start, end), 20.0f};
Rectangle rec = {start.x, start.y, size.x, size.y};
Color color = RED;
color.a = 80;
Vector2 pos = Vector2Zero();
pos.x = unitSpacing;
for (i32 i = 0; i < numUnits; i++) {
if (pos.x + unitSpacing * 2.0f > size.x) {
pos.x = unitSpacing;
pos.y += unitSpacing * 2.0f;
}
Vector2 unitPos = Vector2Add(start, Vector2Rotate(pos, angle));
Color color = ORANGE;
if (!canPlaceUnit(unitPos, 4.0f, map)) {
color = RED;
color.a = 80;
i--;
} else {
bzArrayPush(*outPlaces, unitPos);
}
DrawCircle(unitPos.x, unitPos.y, 2.0f, color);
pos.x += unitSpacing * 2.0f;
}
static char buf[128];
snprintf(buf, sizeof(buf), "Num units: %d", bzArraySize(*outPlaces));
DrawText(buf, 800, 200, 32, BLUE);
}
void render(float dt, void *userData) {
BZ_UNUSED(userData);
Game *game = ecs_singleton_get_mut(ECS, Game);

View File

@@ -14,11 +14,28 @@ static i32 dst(TilePosition a, TilePosition b) {
return 14 * dstX + 10 * (dstY - dstX);
}
static PathData *pushPathWaypoint(PathData *pathData, Position waypoint, BzObjectPool *pathPool) {
if (pathData->numWaypoints + 1 > PATH_DATA_SIZE) {
PathData *newPathData = bzObjectPool(pathPool);
BZ_ASSERT(newPathData);
newPathData->numWaypoints = 0;
newPathData->next = pathData;
pathData = newPathData;
}
pathData->waypoints[pathData->numWaypoints++] = waypoint;
return pathData;
}
bool findPath(const PathfindingDesc *desc) {
BZ_ASSERT(desc->map);
BzTileMap *map = desc->map;
BZ_ASSERT(desc->start.x >= 0 && desc->start.x < map->width);
BZ_ASSERT(desc->start.y >= 0 && desc->start.y < map->height);
TilePosition start = {0}, target = {0};
bzTileMapPosToTile(map, desc->start, &start.x, &start.y);
bzTileMapPosToTile(map, desc->target, &target.x, &target.y);
BZ_ASSERT(start.x >= 0 && start.x < map->width);
BZ_ASSERT(start.y >= 0 && start.y < map->height);
typedef struct Visited {
bool visited : 1;
i8 x : 3;
@@ -30,21 +47,22 @@ bool findPath(const PathfindingDesc *desc) {
if (!openSet) openSet = bzHeapCreate(PathNode, map->width * map->height);
else bzHeapClear(openSet);
i32 toTargetCost = dst(desc->start, desc->target);
i32 toTargetCost = dst(start, target);
bzHeapPush(openSet, (PathNode) {
.weight=toTargetCost,
.gCost=0,
.hCost=toTargetCost,
.visited=false,
.pos=desc->start
.pos=start
});
bool foundPath = false;
while (!bzHeapIsEmpty(openSet)) {
PathNode node = bzHeapPop(openSet);
if (node.pos.x == desc->target.x &&
node.pos.y == desc->target.y) {
if (node.pos.x == target.x &&
node.pos.y == target.y) {
// Found path
foundPath = true;
break;
@@ -69,7 +87,7 @@ bool findPath(const PathfindingDesc *desc) {
TilePosition curPos = {x, y};
i32 gCost = node.gCost + dst(node.pos, curPos);
//if (gCost >= node.gCost) continue;
toTargetCost = dst(curPos, desc->target);
toTargetCost = dst(curPos, target);
curClosed->x = (i8) (curPos.x - node.pos.x);
curClosed->y = (i8) (curPos.y - node.pos.y);
@@ -87,31 +105,25 @@ bool findPath(const PathfindingDesc *desc) {
i32 pathLen = 0;
if (foundPath && desc->outPath) {
TilePosition pos = desc->target;
TilePosition pos = target;
Path *out = desc->outPath;
out->curWaypoint = 0;
BZ_ASSERT(desc->pool);
PathData *pathData = bzObjectPool(desc->pool);
pathData->numWaypoints = 0;
pathData->next = NULL;
i32 numWaypoints = 0;
pathData = pushPathWaypoint(pathData, desc->target, desc->pool);
// Write path
// TODO: Write end pos
while (pos.x != desc->start.x || pos.y != desc->start.y) {
while (pos.x != start.x || pos.y != start.y) {
Position waypoint = {
pos.x * map->tileWidth + map->tileWidth * 0.5f,
pos.y * map->tileHeight + map->tileHeight * 0.5f
};
if (pathData->numWaypoints + 1 > PATH_DATA_SIZE) {
PathData *newPathData = bzObjectPool(desc->pool);
newPathData->numWaypoints = 0;
newPathData->next = pathData;
pathData = newPathData;
numWaypoints = 0;
}
pathData->waypoints[numWaypoints++] = waypoint;
pathData->numWaypoints = numWaypoints;
if (pathLen != 0)
pathData = pushPathWaypoint(pathData, waypoint, desc->pool);
Visited visit = closedSet[pos.y * map->width + pos.x];
BZ_ASSERT(visit.x != 0 || visit.y != 0);

View File

@@ -14,8 +14,8 @@ typedef struct PathNode {
} PathNode;
typedef struct PathfindingDesc {
TilePosition start;
TilePosition target;
Position start;
Position target;
BzObjectPool *pool;
BzTileMap *map;
PathNode *openSet; // heap

View File

@@ -8,6 +8,15 @@
void pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t **outEntities);
void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outEntities);
static void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces);
static bool wasInputDragged(InputState *input) {
return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT;
}
static bool wasInputClicked(InputState *input) {
return IsMouseButtonReleased(input->LMB);
}
void updatePlayerInput(ecs_iter_t *it) {
f32 dt = GetFrameTime();
ImGuiIO *io = igGetIO();
@@ -37,11 +46,11 @@ void updatePlayerInput(ecs_iter_t *it) {
}
Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);
int tileX = (int) worldPos.x / map->tileWidth;
int tileY = (int) worldPos.y / map->tileHeight;
BzTile tileX = 0, tileY = 0;
bzTileMapPosToTile(map, worldPos, &tileX, &tileY);
switch (input->state) {
case INPUT_NONE:
if (IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT) {
if (wasInputDragged(input)) {
// Dragging
Vector2 start = input->mouseDownWorld;
@@ -64,7 +73,7 @@ void updatePlayerInput(ecs_iter_t *it) {
break;
}
} else if (IsMouseButtonReleased(input->LMB)) {
} else if (wasInputClicked(input)) {
// Click
// 1. Entity
@@ -103,7 +112,37 @@ void updatePlayerInput(ecs_iter_t *it) {
input->state = INPUT_NONE;
break;
}
if (IsMouseButtonPressed(input->LMB)) {
if (bzArraySize(input->entities) > 1 && wasInputDragged(input)) {
// TODO: For click it should just move them
i32 numUnits = bzArraySize(input->entities);
f32 unitSpacing = 3.5f;
Vector2 *unitPositions = bzArrayCreate(Position, numUnits);
placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos,
map, &unitPositions);
BZ_ASSERT(bzArraySize(unitPositions) == numUnits);
bzArrayFor(unitPositions, i) {
Position target = bzArrayGet(unitPositions, i);
ecs_entity_t entity = bzArrayGet(input->entities, i);
const Position *start = ecs_get(ECS, entity, Position);
Path path = {NULL, 0};
findPath(&(PathfindingDesc) {
.start=*start,
.target=target,
.map=map,
.outPath=&path,
.pool=game->pools.pathData
});
if (!path.paths) continue;
ecs_set_ptr(ECS, entity, Path, &path);
input->state = INPUT_NONE;
}
bzArrayDestroy(unitPositions);
} else if (wasInputClicked(input)) {
bzArrayFor(input->entities, i) {
ecs_entity_t entity = bzArrayGet(input->entities, i);
ecs_remove(ECS, entity, Path);
@@ -111,12 +150,9 @@ void updatePlayerInput(ecs_iter_t *it) {
const Position *start = ecs_get(ECS, entity, Position);
Path path = {NULL, 0};
findPath(&(PathfindingDesc) {
.start=(TilePosition) {
start->x / game->map.tileWidth,
start->y / game->map.tileHeight,
},
.target=(TilePosition) {tileX, tileY},
.map=&game->map,
.start=*start,
.target=worldPos,
.map=map,
.outPath=&path,
.pool=game->pools.pathData
});
@@ -200,3 +236,43 @@ void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outE
bzArrayPush(*outEntities, entity);
}
}
static bool isUnitObstructed(f32 x, f32 y, BzTileMap *map) {
return bzTileMapHasCollision(map, x / map->tileWidth, y / map->tileHeight);
}
static bool canPlaceUnit(Vector2 pos, f32 space, BzTileMap *map) {
for (i32 y = -1; y <= 1; y++) {
for (i32 x = -1; x <= 1; x++) {
if (isUnitObstructed(pos.x + x * space, pos.y + y * space, map))
return false;
}
}
return true;
}
void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces) {
f32 angle = Vector2Angle(start, end);
f32 lineLength = Vector2Distance(start, end);
Vector2 pos = Vector2Zero();
pos.x = unitSpacing;
for (i32 i = 0; i < numUnits; i++) {
if (pos.x + unitSpacing * 2.0f > lineLength) {
pos.x = unitSpacing;
pos.y += unitSpacing * 2.0f;
}
Vector2 unitPos = Vector2Add(start, Vector2Rotate(pos, angle));
if (!canPlaceUnit(unitPos, 4.0f, map)) {
i--;
} else {
bzArrayPush(*outPlaces, unitPos);
}
pos.x += unitSpacing * 2.0f;
}
}