From 6097ec6e0d62475ef8aa5e0c1c284dbdd53c2567 Mon Sep 17 00:00:00 2001 From: En Yi Date: Sat, 22 Jul 2023 15:52:55 +0800 Subject: [PATCH] Complete basic water flow algorithm Changelog: - Add in scanline filling process - Use signed integer for calculation - Generalise BFS to only depend on the current cell - During the search for the lowest reachable tile, allow a single tile equal or higher to the starting tile This is to allow moving upwards when no valid lower tile exists --- scenes/engine/EC/EC.h | 7 +-- scenes/water_flow.c | 119 ++++++++++++++++++++++++++++++------------ water_test.c | 26 ++------- 3 files changed, 94 insertions(+), 58 deletions(-) diff --git a/scenes/engine/EC/EC.h b/scenes/engine/EC/EC.h index bc6ec6c..b56181c 100644 --- a/scenes/engine/EC/EC.h +++ b/scenes/engine/EC/EC.h @@ -149,18 +149,19 @@ typedef enum _WaterRunnerState LOWEST_POINT_MOVEMENT, REACHABILITY_SEARCH, SCANLINE_FILL, + FILL_COMPLETE, }WaterRunerState_t; typedef struct _CWaterRunner { BFSTileMap_t bfs_tilemap; WaterRunerState_t state; struct sc_queue_32 bfs_queue; - int32_t start_height; + bool* visited; int32_t current_tile; int32_t target_tile; - bool* visited; + int32_t fill_idx; uint8_t movement_delay; - uint8_t counter; + int16_t counter; }CWaterRunner_t; // Credits to bedroomcoders.co.uk for this diff --git a/scenes/water_flow.c b/scenes/water_flow.c index 92343ad..4798587 100644 --- a/scenes/water_flow.c +++ b/scenes/water_flow.c @@ -27,7 +27,6 @@ Entity_t* create_water_runner(EntityManager_t* ent_manager, int32_t width, int32 p_crunner->visited = calloc(total, sizeof(bool)); p_crunner->current_tile = start_tile; - p_crunner->start_height = start_tile / width; p_crunner->target_tile = total; CTransform_t* p_ct = add_component(p_filler, CTRANSFORM_COMP_T); @@ -45,34 +44,42 @@ void free_water_runner(Entity_t* ent, EntityManager_t* ent_manager) remove_entity(ent_manager, ent->m_id); } -static void runner_BFS(const TileGrid_t* tilemap, CWaterRunner_t* p_crunner, int32_t* lowest_tile) +static void runner_BFS(const TileGrid_t* tilemap, CWaterRunner_t* p_crunner, int32_t* lowest_tile, int32_t start_tile) { + memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); + p_crunner->visited[start_tile] = true; + p_crunner->bfs_tilemap.tilemap[start_tile].reachable = true; + sc_queue_add_last(&p_crunner->bfs_queue, start_tile); + int init_height = start_tile / p_crunner->bfs_tilemap.width; + bool first_reached = false; while (!sc_queue_empty(&p_crunner->bfs_queue)) { - unsigned int curr_idx = sc_queue_peek_first(&p_crunner->bfs_queue); + const 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; + int curr_height = curr_idx / p_crunner->bfs_tilemap.width; + int curr_low = *lowest_tile / p_crunner->bfs_tilemap.width; // Possible optimisation to avoid repeated BFS, dunno how possible bool to_go[4] = {false, false, false, false}; Tile_t* curr_tile = tilemap->tiles + curr_idx; - unsigned int next = curr_idx + p_crunner->bfs_tilemap.width; + int next = curr_idx + p_crunner->bfs_tilemap.width; Tile_t* next_tile = tilemap->tiles + next; + if ( + ((curr_height <= init_height && !first_reached) || curr_height > curr_low) + && curr_tile->water_level < tilemap->max_water_level + ) + { + first_reached = true; + *lowest_tile = curr_idx; + } + + if (next < p_crunner->bfs_tilemap.len) { - if ( - curr_height > curr_low - && curr_tile->water_level < tilemap->max_water_level - ) - { - *lowest_tile = curr_idx; - } - to_go[0] = next_tile->solid != SOLID; if ( @@ -95,11 +102,12 @@ static void runner_BFS(const TileGrid_t* tilemap, CWaterRunner_t* p_crunner, int } } } - + if (curr_tile->water_level == tilemap->max_water_level) { + next = curr_idx - p_crunner->bfs_tilemap.width; - if (next >= 0 && next / p_crunner->bfs_tilemap.width >= p_crunner->start_height) + if (next >= 0) { next_tile = tilemap->tiles + next; to_go[3] = next_tile->solid != SOLID; @@ -170,29 +178,22 @@ void update_water_runner_system(Scene_t* scene) case BFS_RESET: for (size_t i = 0; i < p_crunner->bfs_tilemap.len; ++i) { + //p_crunner->bfs_tilemap.tilemap[i].to = -1; p_crunner->bfs_tilemap.tilemap[i].from = -1; p_crunner->bfs_tilemap.tilemap[i].reachable = false; } p_crunner->state = BFS_START; // Want the fallthough case BFS_START: - memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); - int32_t lowest_tile = p_crunner->current_tile; - p_crunner->visited[p_crunner->current_tile] = true; - p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].reachable = true; - sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile); p_crunner->state = LOWEST_POINT_SEARCH; // Want the fallthough case LOWEST_POINT_SEARCH: { - runner_BFS(&tilemap, p_crunner, &lowest_tile); - p_crunner->target_tile = lowest_tile; + int32_t lowest_tile = p_crunner->current_tile - p_crunner->bfs_tilemap.width; + if (lowest_tile <= 0) lowest_tile = p_crunner->current_tile; - if (p_crunner->target_tile == p_crunner->current_tile) - { - p_crunner->state = SCANLINE_FILL; - break; - } + runner_BFS(&tilemap, p_crunner, &lowest_tile, p_crunner->current_tile); + 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; @@ -202,6 +203,12 @@ void update_water_runner_system(Scene_t* scene) prev_idx = curr_idx; curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from; } + p_crunner->bfs_tilemap.tilemap[lowest_tile].to = -1; + if (prev_idx == lowest_tile) + { + p_crunner->state = REACHABILITY_SEARCH; + break; + } p_crunner->counter = p_crunner->movement_delay; p_crunner->state = LOWEST_POINT_MOVEMENT; } @@ -223,7 +230,12 @@ void update_water_runner_system(Scene_t* scene) break; case REACHABILITY_SEARCH: { - unsigned int start_tile = + if (tilemap.tiles[p_crunner->current_tile].water_level == tilemap.max_water_level) + { + p_crunner->state = FILL_COMPLETE; + break; + } + int start_tile = (p_crunner->current_tile / p_crunner->bfs_tilemap.width) * p_crunner->bfs_tilemap.width; for (size_t i = 0; i < p_crunner->bfs_tilemap.width; ++i) @@ -231,15 +243,56 @@ void update_water_runner_system(Scene_t* scene) p_crunner->bfs_tilemap.tilemap[start_tile + i].reachable = false; } - memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); - p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].reachable = true; - sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile); int32_t lowest_tile = p_crunner->current_tile; - runner_BFS(&tilemap, p_crunner, &lowest_tile); + runner_BFS(&tilemap, p_crunner, &lowest_tile, p_crunner->current_tile); + p_crunner->counter = 0; + for (int i = 0; i < p_crunner->bfs_tilemap.width; ++i) + { + Tile_t* curr_tile = tilemap.tiles + start_tile + i; + if ( + p_crunner->bfs_tilemap.tilemap[start_tile + i].reachable + && curr_tile->water_level < tilemap.max_water_level + ) + { + p_crunner->counter++; + } + } + p_crunner->fill_idx = 0; p_crunner->state = SCANLINE_FILL; } break; + case SCANLINE_FILL: + { + unsigned int start_tile = + (p_crunner->current_tile / p_crunner->bfs_tilemap.width) * p_crunner->bfs_tilemap.width; + //for (size_t i = 0; i < 10; ++i) + { + unsigned int curr_idx = start_tile + p_crunner->fill_idx; + Tile_t* curr_tile = tilemap.tiles + curr_idx; + if ( + p_crunner->bfs_tilemap.tilemap[curr_idx].reachable + && curr_tile->water_level < tilemap.max_water_level + ) + { + curr_tile->water_level++; + if (curr_tile->water_level == tilemap.max_water_level) + { + p_crunner->counter--; + } + } + + if (p_crunner->counter == 0) + { + p_crunner->state = BFS_RESET; + break; + } + + p_crunner->fill_idx++; + p_crunner->fill_idx %= p_crunner->bfs_tilemap.width; + } + } + break; default: break; } diff --git a/water_test.c b/water_test.c index 59036c7..8739f51 100644 --- a/water_test.c +++ b/water_test.c @@ -96,9 +96,10 @@ static void level_scene_render_func(Scene_t* scene) if (p_runner->state == LOWEST_POINT_MOVEMENT) { - 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) + int curr_idx = p_runner->current_tile; + int next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to; + while(curr_idx != p_runner->target_tile || curr_idx == next_idx) + while(next_idx >= 0) { 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; @@ -109,25 +110,6 @@ static void level_scene_render_func(Scene_t* scene) next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to; } } - else if (p_runner->state == SCANLINE_FILL) - { - unsigned int tile_y = ((p_runner->current_tile) / tilemap.width); - for (size_t i = 0; i < tilemap.width; ++i) - { - unsigned int tile_idx = i + tile_y * tilemap.width; - if ( - p_runner->bfs_tilemap.tilemap[tile_idx].reachable - && tilemap.tiles[tile_idx].water_level < tilemap.max_water_level - ) - { - DrawRectangle( - i * tilemap.tile_size, tile_y * tilemap.tile_size, - tilemap.tile_size, tilemap.tile_size, ColorAlpha(GREEN, 0.4) - ); - } - - } - } } char buffer[64] = {0};