Weather sim v2: river generation
This commit is contained in:
parent
1044443e9b
commit
c097420a50
2 changed files with 188 additions and 3 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 248 B |
|
|
@ -4,9 +4,11 @@
|
|||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/gauss.hpp>
|
||||
#include <psemek/math/intersection.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/uniform_ball.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/pcg/perlin.hpp>
|
||||
#include <psemek/pcg/fractal.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
|
|
@ -350,6 +352,22 @@ struct solver
|
|||
}
|
||||
};
|
||||
|
||||
struct river_vertex
|
||||
{
|
||||
// Empty for river deltas
|
||||
std::optional<math::point<int, 2>> parent;
|
||||
std::vector<math::point<int, 2>> children = {};
|
||||
float flow = 0.f;
|
||||
};
|
||||
|
||||
struct river_net
|
||||
{
|
||||
util::ndarray<std::optional<river_vertex>, 2> vertices;
|
||||
util::ndarray<bool, 2> water;
|
||||
|
||||
std::vector<math::point<int, 2>> 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<float, 2> terrain;
|
||||
|
|
@ -387,6 +406,8 @@ struct weather_app
|
|||
|
||||
std::optional<struct solver> 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<std::uint8_t>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_1.png"});
|
||||
auto heightmap = gfx::read_image<std::uint8_t>(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<int>(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<int, 2> const neighbours[4]
|
||||
{
|
||||
{-1, 0},
|
||||
{ 1, 0},
|
||||
{ 0, -1},
|
||||
{ 0, 1},
|
||||
};
|
||||
|
||||
float height = sample_bilinear(terrain, math::cast<float>(p));
|
||||
|
||||
std::vector<math::point<int, 2>> 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<float>(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<int, 2> 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<float>(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<float>(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<float>(*v->parent), width * pixel_size, {0, 255, 255, 255}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int row = 0;
|
||||
auto push_text = [&](std::string const & text) mutable
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue