636 lines
16 KiB
C++
636 lines
16 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/gfx/framebuffer.hpp>
|
|
#include <psemek/gfx/renderbuffer.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/random/device.hpp>
|
|
#include <psemek/random/generator.hpp>
|
|
#include <psemek/random/uniform_sphere.hpp>
|
|
#include <psemek/random/uniform_ball.hpp>
|
|
#include <psemek/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);
|
|
color = texture(u_texture, in_t);
|
|
}
|
|
)";
|
|
|
|
static char const grass_fs[] =
|
|
R"(#version 330
|
|
|
|
in vec4 color;
|
|
|
|
out vec4 out_color;
|
|
|
|
void main()
|
|
{
|
|
out_color = color;
|
|
}
|
|
)";
|
|
|
|
static char const grass_slice_vs[] =
|
|
R"(#version 330
|
|
|
|
uniform mat4 u_transform;
|
|
uniform mat4 u_tile_transform;
|
|
|
|
uniform float u_density00;
|
|
uniform float u_density01;
|
|
uniform float u_density10;
|
|
uniform float u_density11;
|
|
|
|
layout (location = 0) in vec4 in_position;
|
|
layout (location = 1) in vec2 in_texcoord;
|
|
|
|
out vec3 texcoord;
|
|
|
|
void main()
|
|
{
|
|
vec2 p = (u_tile_transform * vec4(in_position.xy, 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 level = mix(mix(u_density00, u_density01, d.x), mix(u_density10, u_density11, d.x), d.y);
|
|
|
|
gl_Position = u_transform * vec4(p, in_position.z, 1.0);
|
|
texcoord = vec3(in_texcoord, level);
|
|
}
|
|
)";
|
|
|
|
static char const grass_slice_fs[] =
|
|
R"(#version 330
|
|
|
|
uniform sampler2DArray u_texture;
|
|
|
|
in vec3 texcoord;
|
|
|
|
out vec4 out_color;
|
|
|
|
void main()
|
|
{
|
|
float l0 = floor(texcoord.z);
|
|
float l1 = l0 + 1;
|
|
float t = texcoord.z - l0;
|
|
vec4 c0 = texture(u_texture, vec3(texcoord.xy, l0));
|
|
vec4 c1 = texture(u_texture, vec3(texcoord.xy, l1));
|
|
vec4 c = mix(c0, c1, t);
|
|
// vec4 c = c0;
|
|
out_color = vec4(c.rgb / c.a, c.a);
|
|
}
|
|
)";
|
|
|
|
struct grass_app
|
|
: app::app
|
|
{
|
|
geom::free_camera camera;
|
|
|
|
int size = 64;
|
|
|
|
int const density_level_count = 8;
|
|
|
|
pcg::perlin<float, 2> density;
|
|
|
|
gfx::program ground_program{ground_vs, ground_fs};
|
|
gfx::mesh ground_mesh;
|
|
|
|
gfx::program grass_program{grass_vs, grass_fs};
|
|
gfx::mesh grass_mesh;
|
|
gfx::texture_1d grass_texture;
|
|
|
|
gfx::program grass_slice_program{grass_slice_vs, grass_slice_fs};
|
|
gfx::texture_2d_array grass_slice_z_texture;
|
|
gfx::mesh grass_slice_z_mesh;
|
|
|
|
gfx::framebuffer grass_slice_framebuffer;
|
|
gfx::renderbuffer grass_slice_renderbuffer;
|
|
|
|
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.1f;
|
|
camera.far_clip = 1000.f;
|
|
camera.pos = {0.5f, 0.5f, 1.5f};
|
|
camera.rotateYZ(geom::rad(-90.f));
|
|
|
|
init_ground();
|
|
init_grass();
|
|
init_grass_slices();
|
|
|
|
random::generator rng;
|
|
random::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 = random::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()));
|
|
}
|
|
|
|
grass_texture.load(pm);
|
|
grass_texture.linear_filter();
|
|
grass_texture.generate_mipmap();
|
|
}
|
|
|
|
random::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::uint32_t>> triangles;
|
|
random::uniform_box_point_distribution<float, 2> d_origin({{{0.f, 1.f}, {0.f, 1.f}}});
|
|
random::uniform_sphere_vector_distribution<float, 2> d_orientation;
|
|
random::uniform_real_distribution<float> d_width{1.f / 64.f, 1.f / 128.f};
|
|
random::uniform_real_distribution<float> d_height{0.25f, 1.f};
|
|
random::uniform_real_distribution<float> d_density{0.f, 1.f};
|
|
|
|
for (int blade = 0; blade < 4096; ++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;
|
|
|
|
auto r = d_orientation(rng);
|
|
auto s = d_width(rng) * 0.5f;
|
|
auto h = d_height(rng);
|
|
|
|
auto n = geom::ort(r);
|
|
|
|
auto color = random::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 init_grass_slices()
|
|
{
|
|
struct vertex
|
|
{
|
|
geom::point<float, 3> position;
|
|
geom::vector<float, 2> texcoord;
|
|
};
|
|
|
|
int const slice_count = 4;
|
|
float slice_width = 1.f / slice_count;
|
|
|
|
std::vector<vertex> vertices;
|
|
vertices.push_back({{0.f, 0.f, slice_width}, {0.f, 0.f}});
|
|
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
|
|
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
|
|
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
|
|
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
|
|
vertices.push_back({{1.f, 1.f, slice_width}, {1.f, 1.f}});
|
|
|
|
grass_slice_z_mesh.setup<geom::point<float, 3>, geom::vector<float, 2>>();
|
|
grass_slice_z_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
|
|
|
|
std::size_t slice_resolution = 256;
|
|
|
|
grass_slice_z_texture.load<gfx::color_rgba>({slice_resolution, slice_resolution, density_level_count});
|
|
grass_slice_renderbuffer.storage(gl::DEPTH24_STENCIL8, {slice_resolution, slice_resolution});
|
|
for (int d = 0; d < density_level_count; ++d)
|
|
{
|
|
grass_slice_framebuffer.color(grass_slice_z_texture, d);
|
|
grass_slice_framebuffer.depth(grass_slice_renderbuffer);
|
|
grass_slice_framebuffer.assert_complete();
|
|
gl::Viewport(0, 0, slice_resolution, slice_resolution);
|
|
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
|
gl::Enable(gl::DEPTH_TEST);
|
|
gl::DepthFunc(gl::LEQUAL);
|
|
grass_program.bind();
|
|
grass_program["u_tile_transform"] = geom::matrix<float, 4, 4>::identity();
|
|
grass_program["u_height00"] = 1.f;
|
|
grass_program["u_height01"] = 1.f;
|
|
grass_program["u_height10"] = 1.f;
|
|
grass_program["u_height11"] = 1.f;
|
|
grass_program["u_texture"] = 0;
|
|
grass_texture.bind();
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
grass_program["u_transform"] = geom::translation<float, 3>({-1.f + 2.f * x, -1.f + 2.f * y, 0.f}).homogeneous_matrix() * geom::scale<float, 3>({2.f, 2.f, 1.f}).homogeneous_matrix();
|
|
grass_mesh.draw(0, (grass_mesh.index_count() * (d + 1)) / density_level_count);
|
|
}
|
|
}
|
|
}
|
|
gfx::framebuffer::null().bind();
|
|
|
|
grass_slice_z_texture.linear_filter();
|
|
grass_slice_z_texture.anisotropy();
|
|
grass_slice_z_texture.generate_mipmap();
|
|
grass_slice_z_texture.clamp();
|
|
// grass_slice_z_texture.bind();
|
|
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
|
|
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
|
|
}
|
|
|
|
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.rotateZX(0.01f * dx);
|
|
camera.rotateYZ(0.01f * dy);
|
|
}
|
|
}
|
|
|
|
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 present() 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::Enable(gl::BLEND);
|
|
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
|
|
|
auto camera_transform = camera.transform();
|
|
|
|
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)
|
|
{
|
|
if (x < size / 2) continue;
|
|
|
|
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 i = std::floor(d * density_level_count);
|
|
if (i > density_level_count - 1) i = density_level_count - 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()) / density_level_count);
|
|
|
|
triangles += (((i + 1) * grass_mesh.index_count()) / density_level_count) / 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
grass_slice_program.bind();;
|
|
grass_slice_program["u_transform"] = camera_transform;
|
|
grass_slice_program["u_texture"] = 0;
|
|
grass_slice_z_texture.bind();
|
|
|
|
for (int x = 0; x < size; ++x)
|
|
{
|
|
for (int y = 0; y < size; ++y)
|
|
{
|
|
if (x >= size / 2) continue;
|
|
|
|
grass_slice_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
|
|
* random_transform[random_transform_index(x, y)];
|
|
|
|
float d00 = density({(x + 0.f) / size, (y + 0.f) / size}) * density_level_count;
|
|
float d01 = density({(x + 1.f) / size, (y + 0.f) / size}) * density_level_count;
|
|
float d10 = density({(x + 0.f) / size, (y + 1.f) / size}) * density_level_count;
|
|
float d11 = density({(x + 1.f) / size, (y + 1.f) / size}) * density_level_count;
|
|
|
|
grass_slice_program["u_density00"] = d00;
|
|
grass_slice_program["u_density01"] = d01;
|
|
grass_slice_program["u_density10"] = d10;
|
|
grass_slice_program["u_density11"] = d11;
|
|
|
|
grass_slice_z_mesh.draw();
|
|
}
|
|
}
|
|
|
|
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>();
|
|
}
|