diff --git a/examples/biomes.png b/examples/biomes.png index 36b3c9bb..d6a16c38 100644 Binary files a/examples/biomes.png and b/examples/biomes.png differ diff --git a/examples/weather_v2.cpp b/examples/weather_v2.cpp index 8ec8bddc..6deba88c 100644 --- a/examples/weather_v2.cpp +++ b/examples/weather_v2.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -350,6 +352,22 @@ struct solver } }; +struct river_vertex +{ + // Empty for river deltas + std::optional> parent; + std::vector> children = {}; + float flow = 0.f; +}; + +struct river_net +{ + util::ndarray, 2> vertices; + util::ndarray water; + + std::vector> queue; +}; + struct weather_app : app::application_base { @@ -365,6 +383,7 @@ struct weather_app bool show_temperature = false; bool show_humidity = false; bool show_biomes = true; + bool show_rivers = true; gfx::pixmap_rgba biomes_map; util::ndarray terrain; @@ -387,6 +406,8 @@ struct weather_app std::optional solver; + river_net rivers; + float display_season = 0.f; util::clock<> frame_clock; @@ -396,7 +417,7 @@ struct weather_app simulation_box = {{{0.f, N}, {0.f, N}}}; terrain.resize({N, N}, 0.f); - auto heightmap = gfx::read_image(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_1.png"}); + auto heightmap = gfx::read_image(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_3.png"}); for (int y = 0; y < N; ++y) for (int x = 0; x < N; ++x) terrain(x, y) = ((heightmap(x, y) / 255.f) * 2048.f - 512.f) / 1024.f; @@ -424,6 +445,9 @@ struct weather_app if (event.down && event.key == app::keycode::B) show_biomes ^= true; + + if (event.down && event.key == app::keycode::R) + show_rivers ^= true; } void update() override @@ -443,6 +467,7 @@ struct weather_app if (season == 4) { compute_average(); + init_river_deltas(); need_update_display_snapshot = true; } } @@ -450,8 +475,30 @@ struct weather_app if (!solver && season < 4) solver.emplace(terrain, rng, season, day_night, snapshots[season][day_night]); - if (!paused && solver) - solver->step(); + if (!paused) + { + if (solver) + { + for (int i = 0; i < 16; ++i) + { + solver->step(); + if (solver->finished) + break; + } + } + else if (season == 4 && !rivers.queue.empty()) + { + for (int i = 0; i < 16; ++i) + { + propagate_rivers(); + if (rivers.queue.empty()) + { + compute_river_flow(); + break; + } + } + } + } if (state().key_down.contains(app::keycode::LEFT)) { @@ -490,6 +537,120 @@ struct weather_app scale(average.humidity, 1.f / 8.f); } + void init_river_deltas() + { + rivers.vertices.resize({N, N}); + rivers.water.resize({N, N}, false); + + for (int y = 1; y + 1 < N; ++y) + { + for (int x = 1; x + 1 < N; ++x) + { + int land = 0; + land += (terrain(x - 1, y - 1) >= 0.f ? 1 : 0); + land += (terrain(x + 0, y - 1) >= 0.f ? 1 : 0); + land += (terrain(x - 1, y + 0) >= 0.f ? 1 : 0); + land += (terrain(x + 0, y + 0) >= 0.f ? 1 : 0); + + if (land > 0 && land < 4) + { + rivers.queue.push_back({x, y}); + rivers.vertices(x, y).emplace(); + } + + if (land < 4) + rivers.water(x, y) = true; + } + } + } + + void propagate_rivers() + { + auto i = random::uniform(rng, 0, rivers.queue.size() - 1); + if (i + 1 < rivers.queue.size()) + std::swap(rivers.queue[i], rivers.queue.back()); + + auto p = rivers.queue.back(); + + math::vector const neighbours[4] + { + {-1, 0}, + { 1, 0}, + { 0, -1}, + { 0, 1}, + }; + + float height = sample_bilinear(terrain, math::cast(p)); + + std::vector> candidates; + for (auto n : neighbours) + { + auto q = p + n; + if (q[0] == 0 || q[0] == N || q[1] == 0 || q[1] == N) + continue; + + if (rivers.water(q[0], q[1])) + continue; + + if (rivers.vertices(q[0], q[1])) + continue; + + float nheight = sample_bilinear(terrain, math::cast(q)); + + if (nheight + 0.02f < height) + continue; + + candidates.push_back(q); + } + + if (candidates.size() <= 1) + rivers.queue.pop_back(); + + if (candidates.empty()) + { + // TODO: remove if no parent and not going anywhere? + return; + } + + auto next = random::uniform_from(rng, candidates); + + rivers.vertices(p[0], p[1])->children.push_back(next); + rivers.vertices(next[0], next[1]) = river_vertex{.parent = p}; + rivers.queue.push_back(next); + } + + void compute_river_flow() + { + for (int y = 0; y < N; ++y) + { + for (int x = 0; x < N; ++x) + { + if (auto & v = rivers.vertices(x, y); v && !v->parent) + { + compute_river_flow_at({x, y}); + } + } + } + } + + float compute_river_flow_at(math::point const & p) + { + auto & v = rivers.vertices(p[0], p[1]); + if (!v) return 0.f; + + if (v->children.empty()) + { + v->flow = std::max(0.f, sample_bilinear(average.humidity, math::cast(p)) - 0.05f); + } + else + { + for (auto c : v->children) + v->flow += compute_river_flow_at(c); + } + + return v->flow; + } + void update_display_snapshot() { display_snapshot.wind_velocity.resize({N, N}, math::vector{0.f, 0.f}); @@ -646,6 +807,30 @@ struct weather_app } } + if (show_rivers) + { + for (auto const & p : rivers.queue) + { + painter.circle(math::cast(p), 3.f * pixel_size, {0, 255, 255, 255}, 12); + } + + if (!rivers.vertices.empty()) + for (int y = 0; y < N; ++y) + { + for (int x = 0; x < N; ++x) + { + auto & v = rivers.vertices(x, y); + if (!v || !v->parent) continue; + + float width = 2.f; + if (rivers.queue.empty()) + width = 2.f * std::sqrt(v->flow); + + painter.line({x, y}, math::cast(*v->parent), width * pixel_size, {0, 255, 255, 255}, false); + } + } + } + int row = 0; auto push_text = [&](std::string const & text) mutable {