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/gfx/gl.hpp>
|
||||||
#include <psemek/math/camera.hpp>
|
#include <psemek/math/camera.hpp>
|
||||||
#include <psemek/math/gauss.hpp>
|
#include <psemek/math/gauss.hpp>
|
||||||
|
#include <psemek/math/intersection.hpp>
|
||||||
#include <psemek/random/generator.hpp>
|
#include <psemek/random/generator.hpp>
|
||||||
#include <psemek/random/device.hpp>
|
#include <psemek/random/device.hpp>
|
||||||
#include <psemek/random/uniform_ball.hpp>
|
#include <psemek/random/uniform_ball.hpp>
|
||||||
|
#include <psemek/random/uniform.hpp>
|
||||||
#include <psemek/pcg/perlin.hpp>
|
#include <psemek/pcg/perlin.hpp>
|
||||||
#include <psemek/pcg/fractal.hpp>
|
#include <psemek/pcg/fractal.hpp>
|
||||||
#include <psemek/util/ndarray.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
|
struct weather_app
|
||||||
: app::application_base
|
: app::application_base
|
||||||
{
|
{
|
||||||
|
|
@ -365,6 +383,7 @@ struct weather_app
|
||||||
bool show_temperature = false;
|
bool show_temperature = false;
|
||||||
bool show_humidity = false;
|
bool show_humidity = false;
|
||||||
bool show_biomes = true;
|
bool show_biomes = true;
|
||||||
|
bool show_rivers = true;
|
||||||
|
|
||||||
gfx::pixmap_rgba biomes_map;
|
gfx::pixmap_rgba biomes_map;
|
||||||
util::ndarray<float, 2> terrain;
|
util::ndarray<float, 2> terrain;
|
||||||
|
|
@ -387,6 +406,8 @@ struct weather_app
|
||||||
|
|
||||||
std::optional<struct solver> solver;
|
std::optional<struct solver> solver;
|
||||||
|
|
||||||
|
river_net rivers;
|
||||||
|
|
||||||
float display_season = 0.f;
|
float display_season = 0.f;
|
||||||
|
|
||||||
util::clock<> frame_clock;
|
util::clock<> frame_clock;
|
||||||
|
|
@ -396,7 +417,7 @@ struct weather_app
|
||||||
simulation_box = {{{0.f, N}, {0.f, N}}};
|
simulation_box = {{{0.f, N}, {0.f, N}}};
|
||||||
|
|
||||||
terrain.resize({N, N}, 0.f);
|
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 y = 0; y < N; ++y)
|
||||||
for (int x = 0; x < N; ++x)
|
for (int x = 0; x < N; ++x)
|
||||||
terrain(x, y) = ((heightmap(x, y) / 255.f) * 2048.f - 512.f) / 1024.f;
|
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)
|
if (event.down && event.key == app::keycode::B)
|
||||||
show_biomes ^= true;
|
show_biomes ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::R)
|
||||||
|
show_rivers ^= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update() override
|
void update() override
|
||||||
|
|
@ -443,6 +467,7 @@ struct weather_app
|
||||||
if (season == 4)
|
if (season == 4)
|
||||||
{
|
{
|
||||||
compute_average();
|
compute_average();
|
||||||
|
init_river_deltas();
|
||||||
need_update_display_snapshot = true;
|
need_update_display_snapshot = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -450,8 +475,30 @@ struct weather_app
|
||||||
if (!solver && season < 4)
|
if (!solver && season < 4)
|
||||||
solver.emplace(terrain, rng, season, day_night, snapshots[season][day_night]);
|
solver.emplace(terrain, rng, season, day_night, snapshots[season][day_night]);
|
||||||
|
|
||||||
if (!paused && solver)
|
if (!paused)
|
||||||
|
{
|
||||||
|
if (solver)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
solver->step();
|
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))
|
if (state().key_down.contains(app::keycode::LEFT))
|
||||||
{
|
{
|
||||||
|
|
@ -490,6 +537,120 @@ struct weather_app
|
||||||
scale(average.humidity, 1.f / 8.f);
|
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()
|
void update_display_snapshot()
|
||||||
{
|
{
|
||||||
display_snapshot.wind_velocity.resize({N, N}, math::vector{0.f, 0.f});
|
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;
|
int row = 0;
|
||||||
auto push_text = [&](std::string const & text) mutable
|
auto push_text = [&](std::string const & text) mutable
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue