Initial working implementation of flow field.

Debug methods to visualize the flow field
Player updates in game update on tile change
Map does the actual looping and setting of values
This commit is contained in:
2026-07-31 07:11:08 +02:00
parent e0c53290d7
commit 09c3e34c6b
4 changed files with 193 additions and 70 deletions
+110 -3
View File
@@ -36,17 +36,21 @@ tile_defs: [Tile_Type]Tile_Def = {
},
}
Grid_Pos :: [2]int
Game_Map :: struct {
tile_size: int,
width: int,
height: int,
width_px: f32,
height_px: f32,
data: [400]int
data: [400]int,
// Flow Field props
cost_field: [400]int,
flow_field: [400]rl.Vector2,
player_grid_pos: Grid_Pos
}
Grid_Pos :: [2]int
map_init :: proc() -> Game_Map {
// @TODO: store maps and load them later
// NOTE: flat, row-major — data[row * width + col]
@@ -213,3 +217,106 @@ draw_map :: proc(current_map: Game_Map) {
}
}
}
reset_cost_array :: proc(cost_arr: ^[400]int) {
for val, idx in cost_arr {
cost_arr[idx] = -1
}
}
compute_flow_field :: proc(game_map: ^Game_Map, player_pos: rl.Vector2) {
reset_cost_array(&game_map.cost_field)
player_grid_pos := world_to_grid(player_pos)
player_idx := grid_to_index(player_grid_pos.x, player_grid_pos.y, game_map.width)
game_map.cost_field[player_idx] = 0
flow_field_queue: [dynamic]Grid_Pos
defer delete(flow_field_queue)
append_elem(&flow_field_queue, player_grid_pos)
for len(flow_field_queue) != 0 {
current := pop_front(&flow_field_queue)
current_idx := grid_to_index(current.x, current.y, game_map.width)
current_cost := game_map.cost_field[current_idx]
// Check all 8 neighbors (3x3 grid minus center):
// (-1,-1) (0,-1) (1,-1)
// (-1, 0) X (1, 0)
// (-1, 1) (0, 1) (1, 1)
for loop_y in -1..=1 {
for loop_x in -1..=1 {
if loop_x == 0 && loop_y == 0 do continue
nx := current.x + loop_x
ny := current.y + loop_y
get_neighbor_cost({nx, ny}, &flow_field_queue, game_map, current_cost)
}
}
}
}
get_neighbor_cost :: proc(grid_pos: Grid_Pos, queue: ^[dynamic]Grid_Pos, game_map: ^Game_Map, current_cost: int) {
if grid_pos.x >= 0 && grid_pos.x < game_map.width && grid_pos.y >= 0 && grid_pos.y < game_map.height {
grid_pos_index := grid_to_index(grid_pos.x, grid_pos.y, game_map.width)
if game_map.cost_field[grid_pos_index] == -1 {
tile_t := Tile_Type(game_map.data[grid_pos_index])
def_t := tile_defs[tile_t]
if !def_t.solid {
game_map.cost_field[grid_pos_index] = current_cost + 1
append_elem(queue, grid_pos)
}
}
}
}
compute_flow_directions :: proc(game_map: ^Game_Map) {
// walk every tile, look at neighbor costs in cost_field,
// write a direction vector into direction_field[index]
for value, index in game_map.data {
row := index / game_map.width
column := index % game_map.width
current_index := grid_to_index(column, row, game_map.width)
// skip unreachable tiles
if game_map.cost_field[current_index] == -1 do continue
current_cost := game_map.cost_field[current_index]
lowest_cost := current_cost
// x = column, y = row — matches grid_to_world / screen space
a := rl.Vector2{f32(column), f32(row)}
b := a
// Check all 8 neighbors (3x3 grid minus center):
// (-1,-1) (0,-1) (1,-1)
// (-1, 0) X (1, 0)
// (-1, 1) (0, 1) (1, 1)
for loop_y in -1..=1 {
for loop_x in -1..=1 {
if loop_x == 0 && loop_y == 0 do continue
ncolumn := column + loop_x
nrow := row + loop_y
if ncolumn >= 0 && ncolumn < game_map.width && nrow >= 0 && nrow < game_map.height {
cost_index := grid_to_index(ncolumn, nrow, game_map.width)
neighbor_cost := game_map.cost_field[cost_index]
if neighbor_cost != -1 && neighbor_cost < lowest_cost {
lowest_cost = neighbor_cost
b = rl.Vector2{f32(ncolumn), f32(nrow)}
}
}
}
}
// point from current tile toward the lower-cost neighbor
delta := b - a
if delta == {0, 0} {
game_map.flow_field[current_index] = {0, 0}
} else {
game_map.flow_field[current_index] = rl.Vector2Normalize(delta)
}
}
}