349 lines
9.3 KiB
C++
349 lines
9.3 KiB
C++
#include <psemek/app/application_base.hpp>
|
|
#include <psemek/app/default_application_factory.hpp>
|
|
#include <psemek/gfx/gl.hpp>
|
|
#include <psemek/gfx/painter.hpp>
|
|
#include <psemek/math/box.hpp>
|
|
#include <psemek/math/camera.hpp>
|
|
#include <psemek/math/gauss.hpp>
|
|
#include <psemek/util/ndarray.hpp>
|
|
#include <psemek/random/generator.hpp>
|
|
#include <psemek/random/device.hpp>
|
|
#include <psemek/random/uniform_sphere.hpp>
|
|
#include <psemek/pcg/perlin.hpp>
|
|
#include <psemek/prof/profiler.hpp>
|
|
|
|
using namespace psemek;
|
|
|
|
static const math::vector x_axis {1.f, 0.f};
|
|
static const math::vector y_axis {0.5f, std::sqrt(0.75f)};
|
|
static const math::vector z_axis = y_axis - x_axis;
|
|
static const int N = 64;
|
|
|
|
static const float dt = 0.1f;
|
|
static const float dx = 1.f;
|
|
static float const g = 10.f;
|
|
static float const friction = std::pow(0.875f, dt);
|
|
|
|
math::point<float, 2> to_world(int x, int y)
|
|
{
|
|
return math::point{0.f, 0.f} + x_axis * (x - N / 2.f) + y_axis * (y - N / 2.f);
|
|
}
|
|
|
|
math::point<float, 2> to_grid(math::point<float, 2> const & p)
|
|
{
|
|
static auto matrix = *math::inverse(math::by_columns(x_axis, y_axis));
|
|
static auto zero = math::point{0.f, 0.f};
|
|
|
|
return zero + matrix * (p - zero) + math::vector{N / 2.f, N / 2.f};
|
|
}
|
|
|
|
struct water_2d_hex_app
|
|
: app::application_base
|
|
{
|
|
water_2d_hex_app(options const &, context const &);
|
|
|
|
void update() override;
|
|
void present() override;
|
|
|
|
void on_event(app::resize_event const & event) override;
|
|
void on_event(app::key_event const & event) override;
|
|
|
|
void stop() override;
|
|
|
|
private:
|
|
bool vsync_on_ = true;
|
|
std::function<void(bool)> set_vsync_;
|
|
|
|
random::generator rng_{random::device{}};
|
|
|
|
float aspect_ratio_ = 1.f;
|
|
math::vector<int, 2> screen_size_;
|
|
math::box<float, 2> view_area_;
|
|
gfx::painter painter_;
|
|
|
|
bool paused_ = false;
|
|
float time_ = 0.f;
|
|
|
|
bool show_velocity_ = false;
|
|
|
|
util::ndarray<float, 2> bed_;
|
|
util::ndarray<float, 2> water_;
|
|
util::ndarray<float, 2> flow_x_; // flow_x(x, y) is (x-1, y) => (x, y)
|
|
util::ndarray<float, 2> flow_y_; // flow_y(x, y) is (x, y-1) => (x, y)
|
|
util::ndarray<float, 2> flow_z_; // flow_z(x, y) is (x, y-1) => (x-1, y)
|
|
};
|
|
|
|
water_2d_hex_app::water_2d_hex_app(options const &, context const & ctx)
|
|
: set_vsync_(ctx.vsync)
|
|
{
|
|
set_vsync_(vsync_on_);
|
|
|
|
bed_.resize({N + 1, N + 1}, 0.f);
|
|
water_.resize({N + 1, N + 1}, 0.f);
|
|
flow_x_.resize({N + 2, N + 1}, 0.f);
|
|
flow_y_.resize({N + 1, N + 2}, 0.f);
|
|
flow_z_.resize({N + 2, N + 2}, 0.f);
|
|
|
|
random::uniform_sphere_vector_distribution<float, 2> d_grad;
|
|
|
|
util::ndarray<math::vector<float, 2>, 2> perlin_grad({17, 17});
|
|
for (auto & v : perlin_grad)
|
|
v = d_grad(rng_);
|
|
|
|
pcg::perlin<float, 2> noise(std::move(perlin_grad));
|
|
|
|
for (int y = 0; y <= N; ++y)
|
|
{
|
|
for (int x = 0; x <= N; ++x)
|
|
{
|
|
auto q = (to_world(x, y) - math::point{0.f, 0.f}) / (1.f * N);
|
|
auto p = q + math::vector{0.5f, 0.5f};
|
|
|
|
(void)p;
|
|
|
|
// Canyon
|
|
bed_(x, y) = std::max(0.f, 10.f * std::abs(2.f * noise(p) - 1.f) - 1.5f);
|
|
|
|
// Islands
|
|
// bed_(x, y) = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, noise(p) - 1.f * math::length(q)), {0.f, 1.f}));
|
|
|
|
water_(x, y) = 0.f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void water_2d_hex_app::update()
|
|
{
|
|
{
|
|
float y_extent = (N / 2) * y_axis[1];
|
|
view_area_[1] = {- y_extent, y_extent};
|
|
view_area_[0] = {- y_extent * aspect_ratio_, y_extent * aspect_ratio_};
|
|
}
|
|
|
|
if (state().mouse_button_down.contains(app::mouse_button::left))
|
|
{
|
|
auto m = math::lerp(view_area_, math::vector{state().mouse[0] * 1.f / screen_size_[0], 1.f - state().mouse[1] * 1.f / screen_size_[1]});
|
|
auto p = to_grid(m);
|
|
|
|
int const R = 4;
|
|
|
|
for (int dy = -R; dy <= R; ++dy)
|
|
{
|
|
for (int dx = -R; dx <= R; ++dx)
|
|
{
|
|
if (dx + dy < -R || dx + dy > R) continue;
|
|
|
|
int mx = std::round(p[0]) + dx;
|
|
int my = std::round(p[1]) + dy;
|
|
|
|
if (mx >= 0 && mx <= N && my >= 0 && my <= N)
|
|
{
|
|
auto c = to_world(mx, my);
|
|
water_(mx, my) += 10.f * std::exp(- 0.25f * math::distance_sqr(c, m)) * dt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (paused_)
|
|
return;
|
|
|
|
prof::profiler prof("update");
|
|
|
|
time_ += dt;
|
|
|
|
// Init boundary flows
|
|
for (int i = 0; i <= N / 2; ++i)
|
|
if (bed_(N / 2 - i, i) < 0.5f)
|
|
flow_x_(N / 2 - i, i) = 300.f / N;
|
|
// flow_x_(N / 2 - i, i) = 0*std::pow(std::sin(0.5f * time_) * std::sin(math::pi * (i * 4.f / N)), 5.f) * 300.f / N;
|
|
|
|
for (int i = 0; i <= N / 2; ++i)
|
|
if (bed_(N - i, N / 2 + i) < 0.5f)
|
|
flow_x_(N - i + 1, N / 2 + i) = 300.f / N;
|
|
|
|
// Update X flows
|
|
for (int y = 0; y <= N; ++y)
|
|
// for (int x = 1; x <= N; ++x)
|
|
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
|
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
|
flow_x_(x, y) = friction * flow_x_(x, y) + g * dt * (water_(x - 1, y) + bed_(x - 1, y) - water_(x, y) - bed_(x, y));
|
|
|
|
// Update Y flows
|
|
for (int y = 1; y <= N; ++y)
|
|
// for (int x = 0; x <= N; ++x)
|
|
for (int x = std::max(0, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
|
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
|
flow_y_(x, y) = friction * flow_y_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x, y) - bed_(x, y));
|
|
|
|
// Update Z flows
|
|
for (int y = 1; y <= N; ++y)
|
|
// for (int x = 1; x <= N; ++x)
|
|
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 + 1 - y); ++x)
|
|
// if (x + y - 1 >= N / 2 && x + y - 1 <= 3 * N / 2)
|
|
flow_z_(x, y) = friction * flow_z_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x - 1, y) - bed_(x - 1, y));
|
|
|
|
// Scale flows
|
|
for (int y = 0; y <= N; ++y)
|
|
{
|
|
// for (int x = 0; x <= N; ++x)
|
|
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
|
{
|
|
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
|
{
|
|
float outflow = 0.f;
|
|
|
|
float & fin1 = flow_x_(x , y );
|
|
float & fin2 = flow_y_(x , y );
|
|
float & fin3 = flow_z_(x + 1, y );
|
|
|
|
float & fout1 = flow_x_(x + 1, y );
|
|
float & fout2 = flow_y_(x , y + 1);
|
|
float & fout3 = flow_z_(x , y + 1);
|
|
|
|
outflow += std::max(0.f, -fin1);
|
|
outflow += std::max(0.f, -fin2);
|
|
outflow += std::max(0.f, -fin3);
|
|
outflow += std::max(0.f, fout1);
|
|
outflow += std::max(0.f, fout2);
|
|
outflow += std::max(0.f, fout3);
|
|
|
|
if (outflow > 0.f)
|
|
{
|
|
float max_outflow = water_(x, y) * dx * dx / dt;
|
|
|
|
float scale = std::min(1.f, max_outflow / outflow);
|
|
|
|
fin1 *= (fin1 < 0.f ? scale : 1.f);
|
|
fin2 *= (fin2 < 0.f ? scale : 1.f);
|
|
fin3 *= (fin3 < 0.f ? scale : 1.f);
|
|
|
|
fout1 *= (fout1 > 0.f ? scale : 1.f);
|
|
fout2 *= (fout2 > 0.f ? scale : 1.f);
|
|
fout3 *= (fout3 > 0.f ? scale : 1.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update water
|
|
for (int y = 0; y <= N; ++y)
|
|
// for (int x = 0; x <= N; ++x)
|
|
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
|
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
|
water_(x, y) += dt / dx / dx * (flow_x_(x, y) + flow_y_(x, y) + flow_z_(x + 1,y) - flow_x_(x + 1, y) - flow_y_(x, y + 1) - flow_z_(x, y + 1));
|
|
}
|
|
|
|
void water_2d_hex_app::present()
|
|
{
|
|
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
|
|
for (int y = 0; y < N; ++y)
|
|
{
|
|
for (int x = 0; x < N; ++x)
|
|
{
|
|
auto color = [this](int x, int y)
|
|
{
|
|
auto bed = gfx::color_4f{0.9f, 0.7f, 0.5f, - std::expm1(- bed_(x, y))};
|
|
auto water = gfx::color_4f{0.0f, 0.25f, 1.f, - std::expm1(- water_(x, y))};
|
|
|
|
auto color = gfx::color_4f{0.f, 0.f, 0.f, 0.f};
|
|
color = gfx::blend(color, bed);
|
|
color = gfx::blend(color, water);
|
|
return gfx::to_coloru8(color);
|
|
};
|
|
|
|
auto p00 = to_world(x, y);
|
|
auto p01 = to_world(x + 1, y);
|
|
auto p10 = to_world(x, y + 1);
|
|
auto p11 = to_world(x + 1, y + 1);
|
|
|
|
auto b00 = color(x, y);
|
|
auto b01 = color(x + 1, y);
|
|
auto b10 = color(x, y + 1);
|
|
auto b11 = color(x + 1, y + 1);
|
|
|
|
if (x + y >= N / 2 && x + y < (3 * N) / 2)
|
|
painter_.triangle(p00, p01, p10, b00, b01, b10);
|
|
|
|
if (x + y + 1 >= N / 2 && x + y + 1 < (3 * N) / 2)
|
|
painter_.triangle(p10, p01, p11, b10, b01, b11);
|
|
}
|
|
}
|
|
|
|
if (show_velocity_)
|
|
for (int y = 0; y <= N; ++y)
|
|
{
|
|
for (int x = 0; x <= N; ++x)
|
|
{
|
|
if (x + y >= N / 2 && x + y <= (3 * N) / 2)
|
|
{
|
|
auto p = to_world(x, y);
|
|
|
|
auto vx = (flow_x_(x, y) + flow_x_(x + 1, y)) * 0.5f * x_axis;
|
|
auto vy = (flow_y_(x, y) + flow_y_(x, y + 1)) * 0.5f * y_axis;
|
|
auto vz = (flow_z_(x + 1, y) + flow_z_(x, y + 1)) * 0.5f * z_axis;
|
|
|
|
auto v = (vx + vy + vz);
|
|
|
|
auto M = 1.f;
|
|
auto l = math::length(v);
|
|
float s = std::min(1.f, l / M);
|
|
float c = -std::expm1(- 0.1f * l);
|
|
|
|
auto color = gfx::to_coloru8(gfx::color_4f{1.f, 1.f - c, 1.f - c, 1.f});
|
|
|
|
painter_.line(p, p + v * (s / l), 0.25f * s, 0.f, color, color, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
painter_.render(math::orthographic_camera{view_area_}.transform());
|
|
}
|
|
|
|
void water_2d_hex_app::on_event(app::resize_event const & event)
|
|
{
|
|
gl::Viewport(0, 0, event.size[0], event.size[1]);
|
|
screen_size_ = event.size;
|
|
aspect_ratio_ = (event.size[0] * 1.f) / event.size[1];
|
|
}
|
|
|
|
void water_2d_hex_app::on_event(app::key_event const & event)
|
|
{
|
|
if (event.down)
|
|
{
|
|
switch (event.key)
|
|
{
|
|
case app::keycode::V:
|
|
vsync_on_ ^= true;
|
|
set_vsync_(vsync_on_);
|
|
break;
|
|
case app::keycode::C:
|
|
show_velocity_ ^= true;
|
|
break;
|
|
case app::keycode::SPACE:
|
|
paused_ ^= true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void water_2d_hex_app::stop()
|
|
{
|
|
app::application_base::stop();
|
|
prof::dump();
|
|
}
|
|
|
|
namespace psemek::app
|
|
{
|
|
|
|
std::unique_ptr<application::factory> make_application_factory()
|
|
{
|
|
return default_application_factory<water_2d_hex_app>({.name = "Water 2D hex example", .multisampling = 4});
|
|
}
|
|
|
|
}
|