fp: Make path finding consider non-traversables
This commit is contained in:
+65
-3
@@ -450,8 +450,37 @@ static bool FP_Game_EntityActionHasFailed(FP_GameEntityAction const *action)
|
||||
return result;
|
||||
}
|
||||
|
||||
static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena, TELY_Platform *platform, Dqn_V2I src_tile, Dqn_V2I dest_tile)
|
||||
static Dqn_V2I FP_Game_WorldPosToTilePos(FP_Game *game, Dqn_V2 world_pos)
|
||||
{
|
||||
Dqn_V2I result = Dqn_V2I_InitNx2(world_pos.x / game->tile_size, world_pos.y / game->tile_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game,
|
||||
Dqn_Arena *arena,
|
||||
TELY_Platform *platform,
|
||||
FP_GameEntityHandle entity,
|
||||
Dqn_V2I dest_tile)
|
||||
{
|
||||
|
||||
// NOTE: Enumerate the entities that are collidable ============================================
|
||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena);
|
||||
Dqn_List<FP_GameEntity const *> colliders = Dqn_List_Init<FP_GameEntity const *>(scratch.arena, 128);
|
||||
for (FP_GameEntityIterator it = {}; FP_Game_DFSPreOrderWalkEntityTree(game, &it, game->root_entity); ) {
|
||||
FP_GameEntity const *walk_entity = it.entity;
|
||||
if (entity == walk_entity->handle)
|
||||
continue;
|
||||
|
||||
if ((walk_entity->flags & FP_GameEntityFlag_NonTraversable) == 0)
|
||||
continue;
|
||||
|
||||
Dqn_List_Add(&colliders, walk_entity);
|
||||
}
|
||||
|
||||
// NOTE: Setup A* state ========================================================================
|
||||
Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos(game, entity);
|
||||
Dqn_V2I src_tile = FP_Game_WorldPosToTilePos(game, entity_world_pos);
|
||||
|
||||
Dqn_DSMap<FP_GameAStarNode> astar_info = Dqn_DSMap_Init<FP_GameAStarNode>(128);
|
||||
DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); };
|
||||
|
||||
@@ -465,6 +494,10 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
||||
uint64_t src_tile_u64 = (DQN_CAST(uint64_t)src_tile.y << 32) | (DQN_CAST(uint64_t)src_tile.x << 0);
|
||||
Dqn_DSMap_MakeKeyU64(&astar_info, src_tile_u64);
|
||||
|
||||
// NOTE: Do the A* process =====================================================================
|
||||
Dqn_usize last_successful_manhattan_dist = UINT64_MAX;
|
||||
Dqn_V2I last_successful_tile = src_tile;
|
||||
|
||||
while (frontier.size) {
|
||||
Dqn_V2I curr_tile = Dqn_FArray_PopFront(&frontier, 1);
|
||||
if (curr_tile == dest_tile)
|
||||
@@ -491,18 +524,47 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
||||
Dqn_usize const curr_cost = Dqn_DSMap_FindKeyU64(&astar_info, curr_tile_u64).value->cost;
|
||||
for (Dqn_V2I next_tile : neighbours) {
|
||||
|
||||
// NOTE: Calculate cost to move to this neighbouring tile.
|
||||
Dqn_usize new_cost = curr_cost + 1;
|
||||
uint64_t next_tile_u64 = (DQN_CAST(uint64_t)next_tile.y << 32) | (DQN_CAST(uint64_t)next_tile.x << 0);
|
||||
Dqn_DSMapResult<FP_GameAStarNode> next_cost_result = Dqn_DSMap_MakeKeyU64(&astar_info, next_tile_u64);
|
||||
|
||||
// NOTE: If we have already visited this node before, we only keep the cost if it's cheaper
|
||||
if (next_cost_result.found && new_cost >= next_cost_result.value->cost)
|
||||
continue;
|
||||
|
||||
// NOTE: Check if the neighbour is a non-traversable tile, if so we skip it
|
||||
bool tile_is_non_traversable = false;
|
||||
for (Dqn_ListIterator<FP_GameEntity const *> it = {}; Dqn_List_Iterate(&colliders, &it, 0); ) {
|
||||
FP_GameEntity const *collider = *it.data;
|
||||
Dqn_Rect bounding_box = FP_Game_CalcEntityWorldBoundingBox(game, collider->handle);
|
||||
Dqn_RectMinMax min_max = Dqn_Rect_MinMax(bounding_box);
|
||||
Dqn_V2I min_tile = FP_Game_WorldPosToTilePos(game, min_max.min);
|
||||
Dqn_V2I max_tile = FP_Game_WorldPosToTilePos(game, min_max.max);
|
||||
|
||||
if ((next_tile.x >= min_tile.x && next_tile.x <= max_tile.x) &&
|
||||
(next_tile.y >= min_tile.y && next_tile.y <= max_tile.y)) {
|
||||
tile_is_non_traversable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tile_is_non_traversable)
|
||||
continue;
|
||||
|
||||
// NOTE: Update the node cost value and the heuristic (estimated cost to the end)
|
||||
Dqn_usize manhattan_dist = DQN_ABS(dest_tile.x - next_tile.x) + DQN_ABS(dest_tile.y - next_tile.y);
|
||||
next_cost_result.value->cost = new_cost;
|
||||
next_cost_result.value->came_from = curr_tile;
|
||||
next_cost_result.value->heuristic = new_cost + manhattan_dist;
|
||||
|
||||
// NOTE: Store the last node we visited that had the best cost.
|
||||
// We may end up with a partial path find that could only get
|
||||
// part-way to the solution which this variable will track for us.
|
||||
if (manhattan_dist < last_successful_manhattan_dist) {
|
||||
last_successful_manhattan_dist = manhattan_dist;
|
||||
last_successful_tile = next_tile;
|
||||
}
|
||||
|
||||
// TODO(doyle): Find the insert location into the frontier
|
||||
bool inserted = false;
|
||||
DQN_FOR_UINDEX(index, frontier.size) {
|
||||
@@ -525,14 +587,14 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
||||
}
|
||||
|
||||
Dqn_usize slice_size = 0;
|
||||
for (Dqn_V2I it = dest_tile; it != src_tile; slice_size++) {
|
||||
for (Dqn_V2I it = last_successful_tile; it != src_tile; slice_size++) {
|
||||
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
||||
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
||||
}
|
||||
|
||||
Dqn_Slice<Dqn_V2I> result = Dqn_Slice_Alloc<Dqn_V2I>(arena, slice_size, Dqn_ZeroMem_No);
|
||||
slice_size = 0;
|
||||
for (Dqn_V2I it = dest_tile; it != src_tile; ) {
|
||||
for (Dqn_V2I it = last_successful_tile; it != src_tile; ) {
|
||||
result.data[slice_size++] = it;
|
||||
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
||||
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
||||
|
||||
Reference in New Issue
Block a user