From fa9c5efaf0a327dea9d5db124a96c374dc11a4cf Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 30 Jan 2025 12:40:32 +0300 Subject: [PATCH] Add 2D hex water example --- examples/water_2d_hex.cpp | 349 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 examples/water_2d_hex.cpp diff --git a/examples/water_2d_hex.cpp b/examples/water_2d_hex.cpp new file mode 100644 index 00000000..80badaf5 --- /dev/null +++ b/examples/water_2d_hex.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 to_grid(math::point 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 set_vsync_; + + random::generator rng_{random::device{}}; + + float aspect_ratio_ = 1.f; + math::vector screen_size_; + math::box view_area_; + gfx::painter painter_; + + bool paused_ = false; + float time_ = 0.f; + + bool show_velocity_ = false; + + util::array bed_; + util::array water_; + util::array flow_x_; // flow_x(x, y) is (x-1, y) => (x, y) + util::array flow_y_; // flow_y(x, y) is (x, y-1) => (x, y) + util::array 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 d_grad; + + util::array, 2> perlin_grad({17, 17}); + for (auto & v : perlin_grad) + v = d_grad(rng_); + + pcg::perlin 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 make_application_factory() + { + return default_application_factory({.name = "Water 2D hex example", .multisampling = 4}); + } + +}