diff --git a/scenes/engine/EC/EC.h b/scenes/engine/EC/EC.h index a6cdb83..112dc60 100644 --- a/scenes/engine/EC/EC.h +++ b/scenes/engine/EC/EC.h @@ -141,12 +141,19 @@ typedef struct _BFSTileMap { uint32_t len; }BFSTileMap_t; +typedef enum _WaterRunnerState +{ + LOWEST_POINT_SEARCH = 0, + LOWEST_POINT_MOVEMENT, +}WaterRunerState_t; + typedef struct _CWaterRunner { + BFSTileMap_t bfs_tilemap; int32_t current_tile; int32_t target_tile; struct sc_queue_32 bfs_queue; bool* visited; - BFSTileMap_t bfs_tilemap; + WaterRunerState_t state; }CWaterRunner_t; // Credits to bedroomcoders.co.uk for this diff --git a/scenes/water_flow.c b/scenes/water_flow.c index 249c883..0b2c03f 100644 --- a/scenes/water_flow.c +++ b/scenes/water_flow.c @@ -50,13 +50,13 @@ void update_water_runner_system(Scene_t* scene) // - Scanline fill // A runner is given an amount of movement cost // Within the movement cost, do the following logic - // Perform a modified DFS to find the lowest point: + // Perform a modified BFS to find the lowest point: // - Solid tiles are not reachable // - If bottom tile is non-solid, that is the only reachable tile, // - If bottom tile is filled with water fully, down+left+right are reachable // - If bottom tile is solid, left+right are reachable // - If bottom tile is OOB, terminate - // Use a LIFO to deal with this. + // Use a FIFO to deal with this. // On DFS completion, find the path to the lowest point. Keep track of this // The DFS should have figured out all reachable tiles, start scanline filling at the lowest point. // On completion, move up update tile reachability by DFS on the current level. (repeat first step) @@ -72,57 +72,95 @@ void update_water_runner_system(Scene_t* scene) { //Entity_t* ent = get_entity(&scene->ent_manager, ent_idx); sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile); - memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); - int32_t lowest_tile = p_crunner->current_tile; - while (!sc_queue_empty(&p_crunner->bfs_queue)) + + switch (p_crunner->state) { - unsigned int curr_idx = sc_queue_peek_first(&p_crunner->bfs_queue); - sc_queue_del_first(&p_crunner->bfs_queue); - if (p_crunner->visited[curr_idx]) continue; - - unsigned int curr_height = curr_idx / p_crunner->bfs_tilemap.width; - unsigned int curr_low = lowest_tile / p_crunner->bfs_tilemap.width; - if (curr_height > curr_low) + case LOWEST_POINT_SEARCH: { - lowest_tile = curr_idx; - } - - - p_crunner->visited[curr_idx] = true; - p_crunner->bfs_tilemap.tilemap[curr_idx].reachable = true; - // Possible optimisation to avoid repeated BFS, dunno how possible - - unsigned int next = curr_idx + p_crunner->bfs_tilemap.width; - if (next >= p_crunner->bfs_tilemap.len) continue; - - Tile_t* next_tile = tilemap.tiles + next; - if (next_tile->solid != SOLID && next_tile->water_level < next_tile->max_water_level) - { - sc_queue_add_last(&p_crunner->bfs_queue, next); - } - else - { - next = curr_idx - 1; - if (next % p_crunner->bfs_tilemap.width != 0) + for (size_t i = 0; i < p_crunner->bfs_tilemap.len; ++i) { - next_tile = tilemap.tiles + next; - if (next_tile->solid != SOLID && next_tile->water_level < next_tile->max_water_level) + p_crunner->bfs_tilemap.tilemap[i].from = -1; + } + memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); + int32_t lowest_tile = p_crunner->current_tile; + while (!sc_queue_empty(&p_crunner->bfs_queue)) + { + unsigned int curr_idx = sc_queue_peek_first(&p_crunner->bfs_queue); + sc_queue_del_first(&p_crunner->bfs_queue); + + unsigned int curr_height = curr_idx / p_crunner->bfs_tilemap.width; + unsigned int curr_low = lowest_tile / p_crunner->bfs_tilemap.width; + if (curr_height > curr_low) + { + lowest_tile = curr_idx; + } + + p_crunner->visited[curr_idx] = true; + p_crunner->bfs_tilemap.tilemap[curr_idx].reachable = true; + // Possible optimisation to avoid repeated BFS, dunno how possible + + unsigned int next = curr_idx + p_crunner->bfs_tilemap.width; + if (next >= p_crunner->bfs_tilemap.len) continue; + + Tile_t* next_tile = tilemap.tiles + next; + if ( + next_tile->solid != SOLID + && next_tile->water_level < next_tile->max_water_level + && !p_crunner->visited[next] + ) { sc_queue_add_last(&p_crunner->bfs_queue, next); + p_crunner->bfs_tilemap.tilemap[next].from = curr_idx; } - } - next = curr_idx + 1; - if (next % p_crunner->bfs_tilemap.width != 0) - { - next_tile = tilemap.tiles + next; - if (next_tile->solid != SOLID && next_tile->water_level < next_tile->max_water_level) + else { - sc_queue_add_last(&p_crunner->bfs_queue, next); + next = curr_idx - 1; + if (next % p_crunner->bfs_tilemap.width != 0) + { + next_tile = tilemap.tiles + next; + if ( + next_tile->solid != SOLID + && next_tile->water_level < next_tile->max_water_level + && !p_crunner->visited[next] + ) + { + sc_queue_add_last(&p_crunner->bfs_queue, next); + p_crunner->bfs_tilemap.tilemap[next].from = curr_idx; + } + } + next = curr_idx + 1; + if (next % p_crunner->bfs_tilemap.width != 0) + { + next_tile = tilemap.tiles + next; + if ( + next_tile->solid != SOLID + && next_tile->water_level < next_tile->max_water_level + && !p_crunner->visited[next] + ) + { + sc_queue_add_last(&p_crunner->bfs_queue, next); + p_crunner->bfs_tilemap.tilemap[next].from = curr_idx; + } + } } } + p_crunner->target_tile = lowest_tile; + + // Trace path from lowest_tile + unsigned int prev_idx = lowest_tile; + unsigned int curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from; + while (p_crunner->bfs_tilemap.tilemap[prev_idx].from >= 0) + { + p_crunner->bfs_tilemap.tilemap[curr_idx].to = prev_idx; + prev_idx = curr_idx; + curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from; + } + p_crunner->state = LOWEST_POINT_MOVEMENT; } + break; + default: + break; } - p_crunner->target_tile = lowest_tile; } } diff --git a/water_test.c b/water_test.c index 264e26f..fd57848 100644 --- a/water_test.c +++ b/water_test.c @@ -91,6 +91,22 @@ static void level_scene_render_func(Scene_t* scene) unsigned int y = ((p_runner->target_tile) / tilemap.width) * tilemap.tile_size; DrawCircle(x+16, y+16, 8, ColorAlpha(BLUE, 0.2)); } + + if (p_runner->state != LOWEST_POINT_SEARCH) + { + unsigned int curr_idx = p_runner->current_tile; + unsigned int next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to; + while(curr_idx != p_runner->target_tile) + { + unsigned int x1 = (curr_idx % tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2; + unsigned int y1 = (curr_idx / tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2; + unsigned int x2 = (next_idx % tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2; + unsigned int y2 = (next_idx / tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2; + DrawLine(x1, y1, x2, y2, BLACK); + curr_idx = next_idx; + next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to; + } + } } char buffer[64] = {0}; @@ -195,6 +211,11 @@ static void toggle_block_system(Scene_t* scene) if (new_type == SOLID_TILE) tilemap.tiles[tile_idx].water_level = 0; change_a_tile(&tilemap, tile_idx, new_type); last_tile_idx = tile_idx; + CWaterRunner_t* p_crunner; + sc_map_foreach_value(&scene->ent_manager.component_map[CWATERRUNNER_T], p_crunner) + { + p_crunner->state = LOWEST_POINT_SEARCH; + } } else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) {