Weather simulation wip

This commit is contained in:
Nikita Lisitsa 2025-12-12 15:35:31 +03:00
parent 02817be114
commit f88d158b35
2 changed files with 117 additions and 24 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -43,8 +43,8 @@ auto make_perlin(random::generator & rng, int min_octave, int max_octave, float
void make_force_field(random::generator & rng, util::ndarray<math::vector<float, 2>, 2> & result, float scale) void make_force_field(random::generator & rng, util::ndarray<math::vector<float, 2>, 2> & result, float scale)
{ {
auto noise_1 = make_perlin(rng, 2, 6, 2.f); auto noise_1 = make_perlin(rng, 3, 6, 2.f);
auto noise_2 = make_perlin(rng, 2, 6, 2.f); auto noise_2 = make_perlin(rng, 3, 6, 2.f);
for (int y = 0; y < result.height(); ++y) for (int y = 0; y < result.height(); ++y)
{ {
@ -61,7 +61,7 @@ struct weather_app
{ {
static constexpr int N = 128; static constexpr int N = 128;
const float dt = 4.f; const float dt = 20.f;
const float viscosity = 0.f; const float viscosity = 0.f;
const float advection_magnification = 1.f; const float advection_magnification = 1.f;
const float temperature_diffusion = 0.001f; const float temperature_diffusion = 0.001f;
@ -71,13 +71,13 @@ struct weather_app
const float coriolis = 0.f; const float coriolis = 0.f;
const float coriolis_bands = 2.f; const float coriolis_bands = 2.f;
const float friction = 0.f; const float friction = 0.f;
const float slope_force = 0.04f; const float slope_force = 4.f;
const float vorticity_confinement = 0.f; const float vorticity_confinement = 0.f;
const float evaporation = 0.1f; const float evaporation = 0.01f;
const float max_humidity_factor = 0.001f; const float max_humidity_factor = 100.f;
const float precipitation_factor = 0.0003f; const float precipitation_factor = 0.0003f;
const float force_field_amplitude = 0.00005f; const float force_field_amplitude = 0.00005f;
const float random_forces = 0.f; const float random_forces = 1.f;
const float force_field_switch_duration = 720.f * 7.5f; // 7.5 days const float force_field_switch_duration = 720.f * 7.5f; // 7.5 days
const int force_field_switch_frames = std::round(force_field_switch_duration / dt); const int force_field_switch_frames = std::round(force_field_switch_duration / dt);
// const float friction_factor = 1.f - std::exp(- friction * dt); // const float friction_factor = 1.f - std::exp(- friction * dt);
@ -89,7 +89,7 @@ struct weather_app
gfx::pixmap_rgba biomes_map; gfx::pixmap_rgba biomes_map;
float expected_temperature_at(int y) float expected_temperature_at(int y) const
{ {
// float latitude = (y - N * 0.5f) * 2.f / N; // float latitude = (y - N * 0.5f) * 2.f / N;
// return std::cos(latitude * float(math::pi)); // return std::cos(latitude * float(math::pi));
@ -97,7 +97,7 @@ struct weather_app
return temperature_income_at(y) * dt / (std::exp(cooling * dt) - 1.f); return temperature_income_at(y) * dt / (std::exp(cooling * dt) - 1.f);
} }
float temperature_income_at(int y) float temperature_income_at(int y) const
{ {
// float latitude = (y - N * 0.5f) * 2.f / N; // float latitude = (y - N * 0.5f) * 2.f / N;
float latitude = y * 1.f / N; float latitude = y * 1.f / N;
@ -105,6 +105,11 @@ struct weather_app
return heating * math::lerp(0.8f, 1.f, 1.f - std::abs(latitude)); return heating * math::lerp(0.8f, 1.f, 1.f - std::abs(latitude));
} }
int wrap(int i) const
{
return (i + N) % N;
}
weather_app(options const &, context const &) weather_app(options const &, context const &)
{ {
simulation_box_ = {{{0.f, N}, {0.f, N}}}; simulation_box_ = {{{0.f, N}, {0.f, N}}};
@ -141,6 +146,9 @@ struct weather_app
float latitude = (N * 0.5f - y) * 2.f / N; float latitude = (N * 0.5f - y) * 2.f / N;
velocity_(x, y) = random_velocity(rng) * 0.f + 0.f * math::vector{-std::cos(0.5f * float(math::pi) * latitude * coriolis_bands), 0.f}; velocity_(x, y) = random_velocity(rng) * 0.f + 0.f * math::vector{-std::cos(0.5f * float(math::pi) * latitude * coriolis_bands), 0.f};
temperature_(x, y) = expected_temperature_at(y); temperature_(x, y) = expected_temperature_at(y);
float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor;
humidity_(x, y) = max_humidity + dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f) * (1.f - precipitation_factor * dt) / precipitation_factor / dt;
} }
} }
@ -214,6 +222,9 @@ struct weather_app
} }
float const force_field_t = ((frame_ % force_field_switch_frames) + 0.5f) / force_field_switch_frames; float const force_field_t = ((frame_ % force_field_switch_frames) + 0.5f) / force_field_switch_frames;
int xmin = periodic_x ? 0 : 1;
int xmax = periodic_x ? N : N - 1; // exclusive
// Temperature source // Temperature source
for (int y = 0; y < N; ++y) for (int y = 0; y < N; ++y)
{ {
@ -234,22 +245,14 @@ struct weather_app
humidity_(x, y) += dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f); humidity_(x, y) += dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f);
// float discharge = std::min(humidity_(x, y), precipitation_factor * dt); // float discharge = std::min(humidity_(x, y), precipitation_factor * dt);
float discharge = humidity_(x, y) * precipitation_factor * dt; // float discharge = humidity_(x, y) * precipitation_factor * dt;
// float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor; float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor;
// float discharge = std::max(0.f, humidity_(x, y) - max_humidity) * precipitation_factor * dt; float discharge = std::max(0.f, humidity_(x, y) - max_humidity) * precipitation_factor * dt;
humidity_(x, y) -= discharge; humidity_(x, y) -= discharge;
precipitation_(x, y) = discharge; precipitation_(x, y) = discharge;
} }
} }
auto wrap = [](int i)
{
return (i + N) % N;
};
int xmin = periodic_x ? 0 : 1;
int xmax = periodic_x ? N : N - 1; // exclusive
// Velocity & temperature advection // Velocity & temperature advection
for (int i = 0; i < N; ++i) for (int i = 0; i < N; ++i)
{ {
@ -366,10 +369,14 @@ struct weather_app
(std::max(0.f, terrain_(x, y + 1)) - std::max(0.f, terrain_(x, y - 1))) / 2.f, (std::max(0.f, terrain_(x, y + 1)) - std::max(0.f, terrain_(x, y - 1))) / 2.f,
}; };
[[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * math::dot(math::normalized(velocity_(x, y)), terrain_gradient)); // [[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * math::dot(math::normalized(velocity_(x, y)), terrain_gradient));
velocity_(x, y) *= std::min(1.f, slope_factor); // velocity_(x, y) *= std::min(1.f, slope_factor);
// velocity_(x, y) -= terrain_gradient * slope_force * dt; // velocity_(x, y) -= terrain_gradient * slope_force * dt;
[[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * std::pow(math::length(terrain_gradient), 4.f));
// [[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * std::pow(std::max(0.f, terrain_(x, y)), 1.f));
velocity_(x, y) *= slope_factor;
// Directional external force // Directional external force
// velocity_(x, y)[1] += 0.001f * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands); // velocity_(x, y)[1] += 0.001f * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
// velocity_(x, y) += math::direction(frame_ * dt * 2.f * float(math::pi) / 10080.f) * 0.001f * dt; // velocity_(x, y) += math::direction(frame_ * dt * 2.f * float(math::pi) / 10080.f) * 0.001f * dt;
@ -528,6 +535,68 @@ struct weather_app
mouseover_cell = {x, y}; mouseover_cell = {x, y};
} }
if (mouseover_cell && (state().mouse_button_down.contains(app::mouse_button::left) || state().mouse_button_down.contains(app::mouse_button::right)))
{
float delta = state().mouse_button_down.contains(app::mouse_button::left) ? 1.f : -1.f;
int R = 4;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
auto d = math::vector<float, 2>{ix, iy} / 2.f;
terrain_(x, y) += delta * 0.05f * std::exp(- math::dot(d, d));
}
}
}
}
if (mouseover_cell && state().mouse_button_down.contains(app::mouse_button::middle))
{
int R = 4;
float average = 0.f;
int count = 0;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
average += terrain_(x, y);
count += 1;
}
}
}
average /= count;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
auto d = math::vector<float, 2>{ix, iy} / 2.f;
terrain_(x, y) += (average - terrain_(x, y)) * 0.05f * std::exp(- math::dot(d, d));
}
}
}
}
[[maybe_unused]] float const pixel_size = view_box[0].length() / state().size[0]; [[maybe_unused]] float const pixel_size = view_box[0].length() / state().size[0];
auto map_color = [](float value, gfx::color_4f const & negative, gfx::color_4f const & positive){ auto map_color = [](float value, gfx::color_4f const & negative, gfx::color_4f const & positive){
@ -604,10 +673,34 @@ struct weather_app
color = gfx::blend(color, map_color(10000.f * pressure_(x, y), {0.f, 0.f, 1.f, 0.75f}, {1.f, 0.f, 0.f, 0.75f})); color = gfx::blend(color, map_color(10000.f * pressure_(x, y), {0.f, 0.f, 1.f, 0.75f}, {1.f, 0.f, 0.f, 0.75f}));
if (show_water_vapor_) if (show_water_vapor_)
color = gfx::blend(color, map_color(humidity_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f})); color = gfx::blend(color, map_color(humidity_(x, y) * 0.001f, {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
if (show_precipitation_) if (show_precipitation_)
color = gfx::blend(color, map_color(precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f})); {
// color = gfx::blend(color, map_color(precipitation_(x, y), {1.f, 1.f, 1.f, -1.f}, {1.f, 1.f, 1.f, 1.f}));
float alpha = 2.f / (1.f + std::exp(- precipitation_(x, y))) - 1.f;
gfx::color_4f cloud_color{1.f, 1.f, 1.f, alpha * 0.875f};
if (y > 0 && y + 1 < N)
{
if (periodic_x || (x > 0 && x + 1 < N))
{
math::vector gradient
{
(precipitation_(wrap(x + 1), y) - precipitation_(wrap(x - 1), y)) / 2.f,
(precipitation_(x, y + 1) - precipitation_(x, y - 1)) / 2.f,
};
auto normal = math::normalized(math::vector{-gradient[0], -gradient[1], 2.f});
auto lightness = 0.5f + 0.5f * math::dot(normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
cloud_color = gfx::dark(cloud_color, 1.f - lightness);
}
}
color = gfx::blend(color, cloud_color);
}
if (show_average_precipitation_) if (show_average_precipitation_)
color = gfx::blend(color, map_color(average_precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f})); color = gfx::blend(color, map_color(average_precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));