Add water 2d example
This commit is contained in:
parent
3940766f7d
commit
7c15c1bb0d
1 changed files with 485 additions and 0 deletions
485
examples/water_2d.cpp
Normal file
485
examples/water_2d.cpp
Normal file
|
|
@ -0,0 +1,485 @@
|
||||||
|
#include <psemek/app/application_base.hpp>
|
||||||
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
|
#include <psemek/gfx/painter.hpp>
|
||||||
|
#include <psemek/math/orthographic.hpp>
|
||||||
|
#include <psemek/log/log.hpp>
|
||||||
|
#include <psemek/util/array.hpp>
|
||||||
|
#include <psemek/random/device.hpp>
|
||||||
|
#include <psemek/random/generator.hpp>
|
||||||
|
#include <psemek/random/uniform.hpp>
|
||||||
|
#include <psemek/random/uniform_sphere.hpp>
|
||||||
|
#include <psemek/pcg/perlin.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
|
||||||
|
struct water_2d_app
|
||||||
|
: app::application_base
|
||||||
|
{
|
||||||
|
water_2d_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;
|
||||||
|
|
||||||
|
math::box<float, 2> simulation_area;
|
||||||
|
math::box<float, 2> inner_area;
|
||||||
|
float aspect_ratio;
|
||||||
|
|
||||||
|
random::generator rng{random::device{}};
|
||||||
|
|
||||||
|
int const N = 128;
|
||||||
|
|
||||||
|
float time = 0.f;
|
||||||
|
|
||||||
|
util::array<float, 2> bed;
|
||||||
|
util::array<float, 2> water;
|
||||||
|
util::array<float, 2> flowx;
|
||||||
|
util::array<float, 2> flowy;
|
||||||
|
util::array<math::vector<float, 2>, 2> velocity;
|
||||||
|
util::array<float, 2> sediment;
|
||||||
|
util::array<float, 2> new_sediment;
|
||||||
|
|
||||||
|
gfx::painter painter;
|
||||||
|
|
||||||
|
bool paused = true;
|
||||||
|
bool show_water = true;
|
||||||
|
bool show_velocity = false;
|
||||||
|
bool erosion_on = false;
|
||||||
|
bool rain_on = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
ctx.vsync(true);
|
||||||
|
|
||||||
|
simulation_area[0] = {-1.f, 1.f};
|
||||||
|
simulation_area[1] = {-1.f, 1.f};
|
||||||
|
|
||||||
|
inner_area = math::shrink(simulation_area, simulation_area.dimensions() / (2.f * N));
|
||||||
|
|
||||||
|
bed.assign({N, N}, 0.f);
|
||||||
|
water.assign({N, N}, 0.f);
|
||||||
|
flowx.assign({N + 1, N}, 0.f);
|
||||||
|
flowy.assign({N, N + 1}, 0.f);
|
||||||
|
velocity.assign({N, N}, math::vector<float, 2>::zero());
|
||||||
|
sediment.assign({N, N}, 0.f);
|
||||||
|
new_sediment.assign({N, N}, 0.f);
|
||||||
|
|
||||||
|
// water(N / 2, N / 2) = 100.f;
|
||||||
|
|
||||||
|
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||||
|
util::array<math::vector<float, 2>, 2> grads({9, 9});
|
||||||
|
for (auto & v : grads)
|
||||||
|
v = d(rng);
|
||||||
|
|
||||||
|
pcg::perlin<float, 2> noise(std::move(grads), pcg::seamless);
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
// if (x == N / 2 && (y != N / 4 && y != 3 * N / 4))
|
||||||
|
// bed(x, y) = 100.f;
|
||||||
|
|
||||||
|
float tx = (x + 0.5f) / N;
|
||||||
|
float ty = (y + 0.5f) / N;
|
||||||
|
|
||||||
|
float n = 0.f;
|
||||||
|
|
||||||
|
// Archipelago
|
||||||
|
n = 0.5f * noise(tx, ty) + 0.5f * noise(math::frac(2.f * tx), math::frac(2.f * ty));
|
||||||
|
n -= 0.5f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f});
|
||||||
|
n = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, n), {0.f, 1.f}));
|
||||||
|
|
||||||
|
// Island
|
||||||
|
// n = 4.f * std::max(0.f, 1.f - 3.f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f}) + (2.f * noise(tx, ty) - 1.f) * 0.25f);
|
||||||
|
|
||||||
|
// Delta
|
||||||
|
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * (1.f - tx);
|
||||||
|
|
||||||
|
// Double delta
|
||||||
|
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * std::pow(4.f * tx * (1.f - tx), 2.f);
|
||||||
|
|
||||||
|
// River
|
||||||
|
n = std::min(4.f, math::lerp(10.f, 20.f, noise(tx, ty)) * std::abs(ty - math::lerp({0.4f, 0.6f}, noise(tx, 0.f))));
|
||||||
|
|
||||||
|
// Canyon
|
||||||
|
// n = 10.f * math::clamp(10.f * math::lerp(-1.f, 1.f, noise(tx, ty)), {0.f, 1.f});
|
||||||
|
|
||||||
|
// Canyon v2
|
||||||
|
// n = math::clamp(20.f * std::abs(2.f * noise(tx, ty) - 1.f) - 2.f, {0.f, 5.f});
|
||||||
|
|
||||||
|
// Shore
|
||||||
|
// n = math::clamp(20.f * (ty - math::lerp(0.4f, 0.6f, noise(0.f, tx))), {-4.f, 4.f}) + 4.f;
|
||||||
|
|
||||||
|
// Circles
|
||||||
|
// n = 20.f * std::max(0.f, 1.f - 25.f * math::length(math::pointwise_mult({1.f, 0.1f}, math::point{tx, std::fmod(10.f * ty, 1.f)} - math::point{0.5f, 0.5f})));
|
||||||
|
|
||||||
|
bed(x, y) = n;
|
||||||
|
|
||||||
|
// water(x, y) = std::max(0.f, 4.0f - bed(x, y));
|
||||||
|
|
||||||
|
// water(x, y) = 10.f - bed(x, y);
|
||||||
|
|
||||||
|
// if (x == 0)
|
||||||
|
// water(x, y) = 100.f - bed(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// water(N / 2, N / 2) = 10000.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float const dt = 0.002f;
|
||||||
|
|
||||||
|
void water_2d_app::update()
|
||||||
|
{
|
||||||
|
if (paused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float const dx = simulation_area[0].length() / N;
|
||||||
|
float const dy = simulation_area[1].length() / N;
|
||||||
|
float const g = 10.f;
|
||||||
|
float const friction = std::pow(0.5f, dt);
|
||||||
|
float const viscosity = 0.f;
|
||||||
|
float const sediment_capacity = 0.1f;
|
||||||
|
float const erosion = 1.f;
|
||||||
|
float const deposition = 10.f;
|
||||||
|
|
||||||
|
// Init boundary flows
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
// flowy(x, 0) = -1.f / N;
|
||||||
|
// flowy(x, N) = 1.f / N;
|
||||||
|
|
||||||
|
// if (x >= 3 * N / 4) flowy(x, 0) = 3.f / N;
|
||||||
|
// if (x < N / 4) flowy(x, N) = -3.f / N;
|
||||||
|
|
||||||
|
// flowy(x, 0) = 5.f * std::sin(10.f * time) / N;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
// flowx(0, y) = 5.f * std::sin(10.f * time) / N;
|
||||||
|
|
||||||
|
// flowx(0, y) = 1.f / N;
|
||||||
|
// flowx(N, y) = 1.f / N;
|
||||||
|
|
||||||
|
// flowx(0, y) = 10.f * ((y + 0.5f) / N) / N;
|
||||||
|
// flowx(N, y) = - (10.f / N - flowx(0, y));
|
||||||
|
|
||||||
|
// if (y < N / 4) flowx(0, y) = 3.f / N;
|
||||||
|
// if (y >= 3 * N / 4) flowx(N, y) = -3.f / N;
|
||||||
|
|
||||||
|
if (bed(0, y) < 0.75f)
|
||||||
|
flowx(0, y) = 4.f / N;
|
||||||
|
// flowx(0, y) = 5.f * math::sqr(std::max(0.f, std::sin(15.f * time))) / N;
|
||||||
|
|
||||||
|
if (bed(N - 1, y) < 0.75f)
|
||||||
|
flowx(N, y) = 5.f / N;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rain :)
|
||||||
|
if (rain_on)
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
// if (random::uniform<float>(rng) < 0.1f)
|
||||||
|
water(x, y) += random::uniform<float>(rng) * dt;
|
||||||
|
// water(x, y) += dt;
|
||||||
|
|
||||||
|
// water(N/2, N/2) += 1000.f * dt;
|
||||||
|
|
||||||
|
// Update X flows
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
for (int x = 1; x < N; ++x)
|
||||||
|
flowx(x, y) = friction * flowx(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)
|
||||||
|
flowy(x, y) = friction * flowy(x, y) + g * dt * (water(x, y - 1) + bed(x, y - 1) - water(x, y) - bed(x, y));
|
||||||
|
|
||||||
|
// Apply viscosity to X flows
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 1; x < N; ++x)
|
||||||
|
{
|
||||||
|
float h = (flowx(x, y) > 0.f) ? water(x - 1, y) : water(x, y);
|
||||||
|
h *= h;
|
||||||
|
|
||||||
|
if (h > 0.f)
|
||||||
|
flowx(x, y) *= h / (h + 3.f * dt * viscosity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply viscosity to Y flows
|
||||||
|
for (int y = 1; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
float h = (flowy(x, y) > 0.f) ? water(x, y - 1) : water(x, y);
|
||||||
|
h *= h;
|
||||||
|
|
||||||
|
if (h > 0.f)
|
||||||
|
flowy(x, y) *= h / (h + 3.f * dt * viscosity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale flows
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
float outflow = 0.f;
|
||||||
|
outflow += std::max(0.f, - flowx(x , y ));
|
||||||
|
outflow += std::max(0.f, flowx(x + 1, y ));
|
||||||
|
outflow += std::max(0.f, - flowy(x , y ));
|
||||||
|
outflow += std::max(0.f, flowy(x , y + 1));
|
||||||
|
|
||||||
|
float max_outflow = water(x, y) * dx * dy / dt;
|
||||||
|
|
||||||
|
if (outflow > 0.f)
|
||||||
|
{
|
||||||
|
float scale = std::min(1.f, max_outflow / outflow);
|
||||||
|
|
||||||
|
if (flowx(x, y) < 0.f) flowx(x, y) *= scale;
|
||||||
|
if (flowx(x + 1, y) > 0.f) flowx(x + 1, y) *= scale;
|
||||||
|
|
||||||
|
if (flowy(x, y) < 0.f) flowy(x, y) *= scale;
|
||||||
|
if (flowy(x, y + 1) > 0.f) flowy(x, y + 1) *= scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update water and compute velocity
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
float w_old = water(x, y);
|
||||||
|
|
||||||
|
water(x, y) += dt / dx / dy * (flowx(x, y) + flowy(x, y) - flowx(x + 1, y) - flowy(x, y + 1));
|
||||||
|
|
||||||
|
float w_avg = (w_old + water(x, y)) / 2.f;
|
||||||
|
|
||||||
|
math::vector v{0.f, 0.f};
|
||||||
|
|
||||||
|
if (w_avg > 0.f)
|
||||||
|
{
|
||||||
|
v[0] = (flowx(x, y) + flowx(x + 1, y)) / 2.f / dx / w_avg;
|
||||||
|
v[1] = (flowy(x, y) + flowy(x, y + 1)) / 2.f / dy / w_avg;
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity(x, y) = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erosion_on)
|
||||||
|
{
|
||||||
|
// Erosion-deposition
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
float c = sediment_capacity * water(x, y) * math::length(velocity(x, y));
|
||||||
|
|
||||||
|
if (c >= sediment(x, y))
|
||||||
|
{
|
||||||
|
float delta = std::min(bed(x, y), dt * erosion * (c - sediment(x, y)));
|
||||||
|
bed(x, y) -= delta;
|
||||||
|
sediment(x, y) += delta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float delta = std::min(sediment(x, y), dt * deposition * (sediment(x, y) - c));
|
||||||
|
bed(x, y) += delta;
|
||||||
|
sediment(x, y) -= delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sediment transport
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
math::point p = math::lerp(simulation_area, {(x + 0.5f) / N, (y + 0.5f) / N});
|
||||||
|
p -= velocity(x, y) * dt;
|
||||||
|
|
||||||
|
p = math::clamp(p, inner_area);
|
||||||
|
|
||||||
|
auto q = math::unlerp(inner_area, p) * (N - 1.f);
|
||||||
|
|
||||||
|
int ix = std::min<int>(N - 2, std::floor(q[0]));
|
||||||
|
int iy = std::min<int>(N - 2, std::floor(q[1]));
|
||||||
|
|
||||||
|
float tx = q[0] - ix;
|
||||||
|
float ty = q[1] - iy;
|
||||||
|
|
||||||
|
new_sediment(x, y) = 0.f
|
||||||
|
+ sediment(ix, iy) * (1.f - tx) * (1.f - ty)
|
||||||
|
+ sediment(ix + 1, iy) * tx * (1.f - ty)
|
||||||
|
+ sediment(ix, iy + 1) * (1.f - tx) * ty
|
||||||
|
+ sediment(ix + 1, iy + 1) * tx * ty
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::swap(sediment, new_sediment);
|
||||||
|
}
|
||||||
|
|
||||||
|
time += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_app::present()
|
||||||
|
{
|
||||||
|
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.8f);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
math::box<float, 3> view_area;
|
||||||
|
{
|
||||||
|
view_area[0] = simulation_area[0];
|
||||||
|
view_area[1] = simulation_area[1];
|
||||||
|
view_area[2] = {-1.f, 1.f};
|
||||||
|
|
||||||
|
float extra_y = view_area[1].length() * 0.f;
|
||||||
|
view_area[1].min -= extra_y;
|
||||||
|
view_area[1].max += extra_y;
|
||||||
|
float extra_x = view_area[1].length() * aspect_ratio - view_area[0].length();
|
||||||
|
view_area[0].min -= extra_x / 2.f;
|
||||||
|
view_area[0].max += extra_x / 2.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
math::matrix<float, 4, 4> const transform = math::orthographic{view_area}.homogeneous_matrix();
|
||||||
|
|
||||||
|
painter.rect(simulation_area, {0, 0, 0, 255});
|
||||||
|
|
||||||
|
float invN = 1.f / N;
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
auto min = simulation_area.corner(x * invN, y * invN);
|
||||||
|
auto max = simulation_area.corner((x + 1) * invN, (y + 1) * invN);
|
||||||
|
|
||||||
|
float b = 1.f - std::exp(- 1.f * bed(x, y));
|
||||||
|
float w = 1.f - std::exp(- 1.f * water(x, y));
|
||||||
|
float s = 1.f - std::exp(- 1.f * sediment(x, y));
|
||||||
|
|
||||||
|
if (water(x, y) > 1e-3f)
|
||||||
|
w = 0.5f + 0.5f * w;
|
||||||
|
|
||||||
|
auto bed_color = gfx::to_coloru8(gfx::color_4f{0.96f, 0.72f, 0.53f, b});
|
||||||
|
// auto bed_color = gfx::to_coloru8(gfx::color_4f{0.3f, 0.5f, 0.1f, b});
|
||||||
|
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.25f, 1.f, w});
|
||||||
|
auto sediment_color = gfx::to_coloru8(gfx::color_4f{1.f, 0.25f, 0.f, s * w});
|
||||||
|
|
||||||
|
auto box = math::box<float, 2>::singleton(min) | math::box<float, 2>::singleton(max);
|
||||||
|
|
||||||
|
painter.rect(box, bed_color);
|
||||||
|
if (show_water)
|
||||||
|
{
|
||||||
|
painter.rect(box, color);
|
||||||
|
painter.rect(box, sediment_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_velocity)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
auto center = simulation_area.corner((x + 0.5f) * invN, (y + 0.5f) * invN);
|
||||||
|
|
||||||
|
auto v = water(x, y) * velocity(x, y) / 100.f;
|
||||||
|
|
||||||
|
float max_length = 0.02f;
|
||||||
|
auto l = math::length(v);
|
||||||
|
|
||||||
|
auto color = gfx::to_coloru8(gfx::color_4f{1.f, std::exp(- 10.f * l), 0.f, 1.f});
|
||||||
|
|
||||||
|
float s = std::min(1.f, l / max_length);
|
||||||
|
|
||||||
|
painter.line(center, center + math::normalized(v) * max_length * s, s * 0.004f, s * 0.002f, color, color, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mouse = math::cast<float>(state().mouse);
|
||||||
|
|
||||||
|
mouse[0] = math::lerp(view_area[0], mouse[0] / state().size[0]);
|
||||||
|
mouse[1] = math::lerp(view_area[1], 1.f - mouse[1] / state().size[1]);
|
||||||
|
|
||||||
|
int mx = std::floor(N * math::unlerp(simulation_area[0], mouse[0]));
|
||||||
|
int my = std::floor(N * math::unlerp(simulation_area[1], mouse[1]));
|
||||||
|
|
||||||
|
if (mx >= 0 && mx < N && my >= 0 && my < N)
|
||||||
|
{
|
||||||
|
gfx::painter::text_options opts
|
||||||
|
{
|
||||||
|
.scale = {0.004f, -0.004f},
|
||||||
|
.c = {255, 0, 255, 255},
|
||||||
|
};
|
||||||
|
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.03f)}, std::format("b {:.3f} + w {:.3f} = h {:.3f}", bed(mx, my), water(mx, my), bed(mx, my) + water(mx, my)), opts);
|
||||||
|
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.05f)}, std::format("s {:.3f}", sediment(mx, my)), opts);
|
||||||
|
|
||||||
|
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||||
|
{
|
||||||
|
for (int dy = -2; dy <= 2; ++dy)
|
||||||
|
{
|
||||||
|
for (int dx = -2; dx <= 2; ++dx)
|
||||||
|
{
|
||||||
|
int x = mx + dx;
|
||||||
|
int y = my + dy;
|
||||||
|
|
||||||
|
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||||
|
water(x, y) += 1000.f * dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
painter.render(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_app::on_event(app::resize_event const & event)
|
||||||
|
{
|
||||||
|
app::application_base::on_event(event);
|
||||||
|
|
||||||
|
aspect_ratio = (event.size[0] * 1.f) / event.size[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_app::on_event(app::key_event const & event)
|
||||||
|
{
|
||||||
|
if (event.down && event.key == app::keycode::SPACE)
|
||||||
|
paused ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::W)
|
||||||
|
show_water ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::V)
|
||||||
|
show_velocity ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::E)
|
||||||
|
erosion_on ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::R)
|
||||||
|
rain_on ^= true;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace psemek::app
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<application::factory> make_application_factory()
|
||||||
|
{
|
||||||
|
return default_application_factory<water_2d_app>({.name = "Water 2D example", .multisampling = 4});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue