psemek/examples/grass.cpp

478 lines
12 KiB
C++

#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/easing.hpp>
#include <psemek/geom/gradient.hpp>
#include <psemek/geom/permutation.hpp>
#include <psemek/pcg/random/device.hpp>
#include <psemek/pcg/random/generator.hpp>
#include <psemek/pcg/random/uniform_sphere.hpp>
#include <psemek/pcg/random/uniform_ball.hpp>
#include <psemek/pcg/random/uniform_box.hpp>
#include <psemek/pcg/perlin.hpp>
#include <psemek/pcg/sample.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/assert.hpp>
#include <psemek/util/moving_average.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/pretty_print.hpp>
using namespace psemek;
static char const ground_vs[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec4 in_color;
out vec4 color;
void main()
{
gl_Position = u_transform * in_position;
color = in_color;
}
)";
static char const ground_fs[] =
R"(#version 330
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}
)";
static char const grass_vs[] =
R"(#version 330
uniform mat4 u_transform;
uniform mat4 u_tile_transform;
uniform float u_height00;
uniform float u_height01;
uniform float u_height10;
uniform float u_height11;
uniform sampler1D u_texture;
layout (location = 0) in vec4 in_position;
layout (location = 1) in float in_t;
out vec4 color;
void main()
{
vec2 p = (u_tile_transform * vec4(in_position.xy * 2.0 - vec2(0.5), 0.0, 1.0)).xy;
vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy;
o = vec2(floor(o.x), floor(o.y));
vec2 d = p - o;
float h = mix(mix(u_height00, u_height01, d.x), mix(u_height10, u_height11, d.x), d.y);
gl_Position = u_transform * vec4(p, in_position.z * h, 1.0);
color = mix(vec4(0.0, 0.0, 0.0, 1.0), texture(u_texture, in_t), in_position.z);
}
)";
static char const grass_fs[] =
R"(#version 330
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}
)";
struct grass_app
: app::app
{
geom::free_camera camera;
int size = 64;
pcg::perlin<float, 2> density;
gfx::program ground_program{ground_vs, ground_fs};
gfx::indexed_mesh ground_mesh;
gfx::program grass_program{grass_vs, grass_fs};
gfx::indexed_mesh grass_mesh;
gfx::texture_1d grass_texture;
geom::matrix<float, 4, 4> random_transform[8];
util::array<int, 2> random_transform_index;
util::clock<> frame_clock;
util::moving_average<double> frame_time{64};
util::clock<std::chrono::duration<float>> update_clock;
gfx::painter painter;
grass_app()
: app("Grass", 4)
{
vsync(false);
camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.01f;
camera.far_clip = 1000.f;
camera.pos = {0.5f, 0.5f, 1.5f};
camera.elevation_angle = geom::rad(15.f);
camera.azimuthal_angle = geom::rad(-90.f);
init_ground();
init_grass();
pcg::generator rng;
pcg::uniform_sphere_vector_distribution<float, 2> d;
util::array<geom::vector<float, 2>, 2> grad({size / 8 + 1, size / 8 + 1});
for (auto & v : grad) v = d(rng);
density = pcg::perlin<float, 2>(std::move(grad));
for (int i = 0; i < 8; ++i)
{
if (i < 4)
random_transform[i] = geom::matrix<float, 4, 4>::identity();
else
random_transform[i] = geom::swap<float, 3>(0, 1).homogeneous_matrix();
random_transform[i] = random_transform[i]
* geom::translation<float, 3>(geom::vector{0.5f, 0.5f, 0.f}).homogeneous_matrix()
* geom::plane_rotation<float, 3>(0, 1, i * geom::pi / 2.f).homogeneous_matrix()
* geom::translation<float, 3>(geom::vector{-0.5f, -0.5f, 0.f}).homogeneous_matrix();
}
random_transform_index.resize({size, size});
for (auto & i : random_transform_index)
i = pcg::uniform_int_distribution<int>{0, 7}(rng);
}
void init_ground()
{
ground_mesh.setup<geom::point<float, 3>, gfx::normalized<gfx::color_rgba>>();
struct vertex
{
geom::point<float, 3> pos;
gfx::color_rgba color;
};
std::vector<vertex> vertices;
std::vector<geom::triangle<std::uint32_t>> triangles;
gfx::color_rgba ground_color{47, 23, 11, 255};
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
std::uint32_t base = vertices.size();
float d = 0.0f;
vertices.push_back({{x + d, y + d, 0.f}, ground_color});
vertices.push_back({{x + 1 - d, y + d, 0.f}, ground_color});
vertices.push_back({{x + d, y + 1 - d, 0.f}, ground_color});
vertices.push_back({{x + 1 - d, y + 1 - d, 0.f}, ground_color});
triangles.push_back({base + 0, base + 1, base + 2});
triangles.push_back({base + 2, base + 1, base + 3});
}
}
ground_mesh.load(vertices, triangles, gl::STATIC_DRAW);
}
void init_grass()
{
frame_clock.restart();
struct vertex
{
geom::point<std::uint16_t, 3> pos;
std::uint8_t t;
std::uint8_t density;
vertex(geom::point<float, 3> const & p, float t, float d)
{
for (std::size_t i = 0; i < 2; ++i)
pos[i] = geom::clamp(std::round((p[i] + 0.5f) / 2.f * 65535.f), {0.f, 65535.f});
pos[2] = geom::clamp(std::round(p[2] * 65535.f), {0.f, 65535.f});
this->t = geom::clamp(std::round(t * 255.f), {0.f, 255.f});
this->density = geom::clamp(std::round(d * 255.f), {0.f, 255.f});
}
};
{
geom::gradient<float, gfx::color_4f> g
{
std::make_pair(-1.f, gfx::color_4f{0.f, 0.2f, 0.f, 1.f}),
geom::easing_type::linear,
std::pair{0.f, gfx::color_4f{0.4f, 0.65f, 0.35f, 1.f}},
geom::easing_type::linear,
std::pair{1.f, gfx::color_4f{0.7f, 0.75f, 0.2f, 1.f}}
};
util::array<gfx::color_rgba, 1> pm({256});
for (std::size_t i = 0; i < pm.width(); ++i)
{
pm(i) = gfx::to_coloru8(g((i + 0.5f) / pm.width()));
// pm(i) = gfx::to_coloru8(gfx::color_4f{(i + 0.5f) / pm.width(), 0.f, 0.f, 1.f});
}
grass_texture.load(pm);
grass_texture.linear_filter();
grass_texture.generate_mipmap();
}
pcg::generator rng;
std::size_t memory = 0;
grass_mesh.setup<gfx::normalized<geom::point<std::uint16_t, 3>>, gfx::normalized<std::uint8_t>, gfx::normalized<std::uint8_t>>();
std::vector<vertex> vertices;
std::vector<geom::triangle<std::uint16_t>> triangles;
pcg::uniform_ball_vector_distribution<float, 2> d_offset;
pcg::uniform_box_point_distribution<float, 2> d_origin({{{0.f, 1.f}, {0.f, 1.f}}});
pcg::uniform_sphere_vector_distribution<float, 2> d_orientation;
pcg::uniform_real_distribution<float> d_width{1.f / 64.f, 1.f / 128.f};
pcg::uniform_real_distribution<float> d_height{0.25f, 1.f};
pcg::uniform_real_distribution<float> d_density{0.f, 1.f};
for (int blade = 0; blade < 2048; ++blade)
{
int const segments = 8;
float const max_lean = 0.2f;
int tx = (blade % 64) % 8;
int ty = (blade % 64) / 8;
auto o = d_origin(rng);
o[0] = o[0] / 8.f + tx / 8.f;
o[1] = o[1] / 8.f + ty / 8.f;
// geom::point<float, 2> o{(blade % 256) / 256.f, std::fmod(blade * phi, 1.f)};
// o += d_offset(rng) * 0.f;
auto r = d_orientation(rng);
auto s = d_width(rng) * 0.5f;
auto h = d_height(rng);
auto n = geom::ort(r);
auto color = pcg::uniform_real_distribution<float>{0.f, 1.f}(rng);
float den = d_density(rng);
auto get = [&](float x, float z) -> vertex
{
assert(z >= 0.f);
assert(z <= 1.f);
float y = (1.f - std::sqrt(1.f - z * z)) * max_lean;
float w = 1.f - z / h;
return {geom::point{o[0] + r[0] * s * x * w + n[0] * y, o[1] + r[1] * s * x * w + n[1] * y, z}, color, den};
};
for (int s = 0; s <= segments; ++s)
{
float t = (s * 1.f) / segments;
t = geom::easing(geom::easing_type::quadratic_out, t);
float z = h * t;
std::uint32_t base = vertices.size();
if (s < segments)
{
vertices.push_back(get(-1.f, z));
vertices.push_back(get( 1.f, z));
if (s > 0)
{
triangles.push_back({base - 2, base - 1, base});
triangles.push_back({base, base - 1, base + 1});
}
}
else
{
vertices.push_back(get(0.f, z));
triangles.push_back({base - 2, base - 1, base});
}
}
}
grass_mesh.load(vertices, triangles, gl::STATIC_DRAW);
memory += (vertices.size() * sizeof(vertices[0]) + triangles.size() * sizeof(triangles[0]));
log::info() << memory << " bytes";
log::info() << vertices.size() << " vertices";
log::info() << frame_clock.count();
}
void on_resize(int width, int height) override
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void on_mouse_move(int x, int y, int dx, int dy) override
{
app::on_mouse_move(x, y, dx, dy);
if (is_middle_button_down())
{
camera.azimuthal_angle -= dx * 0.01f;
camera.elevation_angle += dy * 0.01f;
}
}
void on_mouse_wheel(int delta) override
{
app::on_mouse_wheel(delta);
}
void update() override
{
float dt = update_clock.restart().count();
auto d = camera.direction();
float s = 20.f;
if (is_key_down(SDLK_LSHIFT))
s = 2.5f;
if (is_key_down(SDLK_SPACE))
{
d[2] = 0.f;
d = geom::normalized(d);
}
auto n = geom::normalized(geom::cross(d, geom::vector{0.f, 0.f, 1.f}));
if (is_key_down(SDLK_w))
{
camera.pos += d * dt * s;
}
if (is_key_down(SDLK_s))
{
camera.pos -= d * dt * s;
}
if (is_key_down(SDLK_a))
{
camera.pos -= n * dt * s;
}
if (is_key_down(SDLK_d))
{
camera.pos += n * dt * s;
}
}
void render() override
{
gl::ClearColor(0.8f, 0.8f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Disable(gl::BLEND);
ground_program.bind();
ground_program["u_transform"] = camera.transform();
ground_mesh.draw();
grass_program.bind();
grass_program["u_texture"] = 0;
grass_texture.bind();
grass_program["u_transform"] = camera.transform();
std::size_t triangles = 0;
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
grass_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
* random_transform[random_transform_index(x, y)];
float d = density({(x + 0.5f) / size, (y + 0.5f) / size});
int const N = 8;
int i = std::floor(d * N);
if (i > N - 1) i = N - 1;
{
float h00 = density({(x + 0.f) / size, (y + 0.f) / size});
float h01 = density({(x + 1.f) / size, (y + 0.f) / size});
float h10 = density({(x + 0.f) / size, (y + 1.f) / size});
float h11 = density({(x + 1.f) / size, (y + 1.f) / size});
grass_program["u_height00"] = h00;
grass_program["u_height01"] = h01;
grass_program["u_height10"] = h10;
grass_program["u_height11"] = h11;
grass_mesh.draw(0, ((i + 1) * grass_mesh.index_count()) / N);
triangles += (((i + 1) * grass_mesh.index_count()) / N) / 3;
}
}
}
frame_time.push(frame_clock.restart().count());
{
gfx::painter::text_options opts;
opts.x = gfx::painter::x_align::left;
opts.y = gfx::painter::y_align::top;
opts.f = gfx::painter::font::font_9x12;
opts.scale = 2.f;
opts.c = {0, 0, 0, 255};
painter.text({0.f, 0.f}, util::to_string("FPS: ", std::round(1.0 / frame_time.average())), opts);
painter.text({0.f, 24.f}, util::to_string("Camera: ", camera.position()), opts);
painter.text({0.f, 48.f}, util::to_string("Triangles: ", triangles), opts);
}
gl::Disable(gl::DEPTH_TEST);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
geom::window_camera camera;
camera.width = width();
camera.height = height();
painter.render(camera.transform());
}
};
int main()
{
return app::main<grass_app>();
}