psemek/examples/srtm.cpp

599 lines
14 KiB
C++

#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gauss.hpp>
#include <psemek/geom/orientation.hpp>
#include <psemek/geom/intersection.hpp>
#include <psemek/geom/distance.hpp>
#include <psemek/cg/body/icosahedron.hpp>
#include <psemek/cg/body/box.hpp>
#include <psemek/cg/body/frustum.hpp>
#include <psemek/cg/body/prism.hpp>
#include <psemek/cg/convex/inside.hpp>
#include <psemek/cg/convex/separation.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/moving_average.hpp>
#include <psemek/util/recursive.hpp>
using namespace psemek;
template <typename Value, typename T>
struct smooth_updater
{
smooth_updater(Value & value, T speed)
: value_{value}
, target_value_{value}
, speed_{speed}
{}
smooth_updater & operator = (Value const & value)
{
target_value_ = value;
return *this;
}
operator Value const & () const
{
return target_value_;
}
void update(T dt)
{
value_ += std::min(T{1}, dt * speed_) * (target_value_ - value_);
}
private:
Value & value_;
Value target_value_;
T speed_;
};
static char const tile_vs[] =
R"(#version 330
uniform mat4 u_transform;
uniform int u_icosa_face;
uniform int u_N;
uniform vec3 u_p0;
uniform vec3 u_p1;
uniform vec3 u_p2;
uniform vec4 u_color;
out vec3 color;
void main()
{
int i = int(floor(0.5 * (sqrt(1.0 + 8.0 * gl_VertexID) - 1.0)));
int j = gl_VertexID - (i * (i + 1)) / 2;
float t0 = 1.0 - float(i) / float(u_N);
float t1 = float(j) / float(u_N);
float t2 = 1.0 - t0 - t1;
vec3 p = normalize(u_p0 * t0 + u_p1 * t1 + u_p2 * t2);
gl_Position = u_transform * vec4(p, 1.0);
color = u_color.rgb;
})";
static char const tile_fs[] =
R"(#version 330
in vec3 color;
out vec4 out_color;
void main()
{
out_color = vec4(color, 1.0);
})";
static char const test_vs[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
out vec4 color;
void main()
{
gl_Position = u_transform * in_position;
color = vec4(in_position.xyz * 0.5 + vec3(0.5), 1.0);
})";
static char const test_fs[] =
R"(#version 330
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
})";
struct srtm_app
: app::app
{
srtm_app();
void on_resize(int width, int height) override;
void on_mouse_move(int x, int y, int dx, int dy) override;
void update() override;
void present() override;
// geom::spherical_camera camera;
geom::free_camera camera;
smooth_updater<float, float> camera_azimuthal_angle_updater{camera.azimuthal_angle, 20.f};
smooth_updater<float, float> camera_elevation_angle_updater{camera.elevation_angle, 20.f};
bool camera_forward = false;
static constexpr int tile_size_log2 = 8;
static constexpr int tile_size = (1 << tile_size_log2);
gfx::mesh tile_mesh[tile_size_log2 + 1];
gfx::program tile_program{tile_vs, tile_fs};
gfx::program test_program{test_vs, test_fs};
gfx::mesh test_mesh;
struct test_object
{
cg::box<float, 3> body;
gfx::mesh mesh;
int x, y;
};
int const test_object_count = 21;
std::vector<test_object> test_objects;
util::clock<std::chrono::duration<float>> frame_clock;
util::moving_average<float> frame_dt_average{32};
gfx::painter painter;
};
srtm_app::srtm_app()
: app("SRTM", 4)
{
vsync(false);
show_cursor(false);
camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.0001f;
camera.far_clip = 10.f;
// camera.target = {0.f, 0.f, 0.f};
camera.pos = {0.f, -10.f, 0.f};
camera.azimuthal_angle = 0.f;
camera.elevation_angle = 0.f;
// camera.distance = 5.f;
camera_azimuthal_angle_updater = camera.azimuthal_angle;
camera_elevation_angle_updater = camera.elevation_angle;
// camera_distance_updater = camera.distance;
for (std::size_t N = 0; N <= tile_size_log2; ++N)
{
std::vector<std::uint16_t> indices;
std::size_t step = tile_size / (1 << N);
auto idx = [step](std::size_t i, std::size_t j) -> std::uint16_t
{
i *= step;
j *= step;
return (i * (i + 1)) / 2 + j;
};
for (std::size_t i = 0; i < (1 << N); ++i)
{
for (std::size_t j = 0; j <= i; ++j)
{
indices.push_back(idx(i + 1, j));
indices.push_back(idx(i, j));
}
indices.push_back(idx(i + 1, i + 1));
indices.push_back(0xffffu);
}
tile_mesh[N].load_index(indices, gl::TRIANGLE_STRIP, gl::STATIC_DRAW);
}
for (int x = - test_object_count + 1; x <= test_object_count - 1; x += 2)
{
for (int y = - test_object_count + 1; y <= test_object_count - 1; y += 2)
{
auto & o = test_objects.emplace_back();
o.x = (x + test_object_count - 1) / 2;
o.y = (y + test_object_count - 1) / 2;
geom::box<float, 3> b {{
{x - 1.f, x + 1.f},
{y - 1.f, y + 1.f},
{0 - 1.f, 0 + 1.f}
}};
o.body = cg::box(b);
auto const & vertices = cg::vertices(o.body);
auto const & edges = cg::edges(o.body);
o.mesh.setup<geom::point<float, 3>>();
o.mesh.load(vertices.data(), vertices.size(), edges.data(), edges.size());
}
}
}
void srtm_app::on_resize(int width, int height)
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void srtm_app::on_mouse_move(int x, int y, int dx, int dy)
{
app::on_mouse_move(x, y, dx, dy);
camera_azimuthal_angle_updater = camera_azimuthal_angle_updater - 0.01f * dx;
camera_elevation_angle_updater = camera_elevation_angle_updater + 0.01f * dy;
}
void srtm_app::update()
{
float dt = frame_clock.restart().count();
frame_dt_average.push(dt);
if (is_key_down(SDLK_q))
{
}
if (is_key_down(SDLK_e))
{
}
camera_azimuthal_angle_updater.update(dt);
camera_elevation_angle_updater.update(dt);
float const camera_speed = std::min(5.f, geom::distance(camera.pos, geom::point<float, 3>::zero()) - 1.f);
auto const camera_forward = camera.direction();
auto const camera_up = camera.axis_y();
auto const camera_right = camera.axis_x();
if (is_key_down(SDLK_w))
{
camera.pos += camera_speed * dt * camera_forward;
}
if (is_key_down(SDLK_s))
{
camera.pos -= camera_speed * dt * camera_forward;
}
if (is_key_down(SDLK_d))
{
camera.pos += camera_speed * dt * camera_right;
}
if (is_key_down(SDLK_a))
{
camera.pos -= camera_speed * dt * camera_right;
}
if (is_key_down(SDLK_LSHIFT))
{
camera.pos += camera_speed * dt * camera_up;
}
if (is_key_down(SDLK_LCTRL))
{
camera.pos -= camera_speed * dt * camera_up;
}
}
namespace std
{
template <typename T>
std::ostream & operator << (std::ostream & os, std::vector<T> const & v)
{
os << "[";
bool first = true;
for (auto const & x : v)
{
if (first)
first = false;
else
os << ", ";
os << x;
}
return os << "]";
}
}
void srtm_app::present()
{
cg::icosahedron<float> icosahedron{geom::point<float, 3>::zero(), 1.f};
auto const & icosa_vertices = cg::vertices(icosahedron);
auto const & icosa_faces = cg::faces(icosahedron);
auto const icosa_side = geom::distance(icosa_vertices[icosa_faces[0][0]], icosa_vertices[icosa_faces[0][1]]);
std::vector<std::string> info;
gl::ClearColor(0.9f, 0.9f, 0.9f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::LineWidth(2.f);
gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
gl::PointSize(5.f);
gl::Enable(gl::CULL_FACE);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::PRIMITIVE_RESTART);
gl::PrimitiveRestartIndex(0xffffu);
{
auto d = geom::distance(camera.pos, geom::point<float, 3>::zero());
camera.far_clip = std::sqrt(d * d + 1.f);
camera.near_clip = (d > 2.f) ? d - 2.f : 0.0001f;
}
auto const camera_transform = camera.transform();
auto const camera_pos = camera.position();
auto const camera_direction = camera.direction();
info.push_back(util::to_string("Camera height: ", (geom::distance(camera_pos, geom::point<float, 3>::zero()) - 1.f) * 6400000.f, " m"));
auto const frustum = cg::frustum(camera_transform);
tile_program.bind();
tile_program["u_transform"] = camera_transform;
tile_program["u_N"] = static_cast<int>(tile_size);
info.push_back(util::to_string("Camera pos: ", camera_pos));
std::size_t rendered_tiles = 0;
std::vector<std::size_t> id;
auto visit = util::recursive([&](auto & self, geom::vector<float, 3> const (&v)[3], int level = 0) -> void
{
auto const o = geom::point<float, 3>::zero();
auto m = (v[0] + v[1] + v[2]) / 3.f;
m = geom::normalized(m) * (1.f + 1.f / 6400.f) - m;
{
bool culled = true;
for (std::size_t i = 0; i < 3; ++i)
{
if (geom::dot(v[i], geom::normalized(camera_pos - o)) >= 0.f)
{
culled = false;
break;
}
}
if (culled)
return;
(void)culled;
}
geom::triangle<geom::point<float, 3>> t{o + v[0], o + v[1], o + v[2]};
cg::triangular_prism<float> body{t, m};
if (cg::separation(body, frustum).second > 0.f)
return;
bool const selected = geom::intersect(geom::ray{camera_pos, camera_direction}, t);
float on_screen_unit;
{
auto edge = [](auto const & v0, auto const & v1, auto const & u) -> std::optional<float>
{
auto const n = geom::normalized(geom::cross(v0, v1));
auto v = geom::normalized(u - n * dot(u, n));
if (geom::dot(geom::cross(v0, v), geom::cross(v, v1)) >= 0.f)
return geom::length(v - u);
return std::nullopt;
};
float distance = std::numeric_limits<float>::infinity();
auto c = camera_pos - o;
if (geom::det(v[0], v[1], c) >= 0.f && geom::det(v[1], v[2], c) >= 0.f && geom::det(v[2], v[0], c) >= 0.f)
distance = std::min(distance, geom::length(c) - 1.f);
if (auto d = edge(v[0], v[1], c); d)
distance = std::min(distance, *d);
if (auto d = edge(v[1], v[2], c); d)
distance = std::min(distance, *d);
if (auto d = edge(v[2], v[0], c); d)
distance = std::min(distance, *d);
distance = std::min(distance, geom::length(c - v[0]));
distance = std::min(distance, geom::length(c - v[1]));
distance = std::min(distance, geom::length(c - v[2]));
on_screen_unit = width() / distance / std::tan(camera.fov_x / 2.f);
}
assert(on_screen_unit > 0.f);
float const max_triangle_size = 10.f; // pixels
float const side_length = icosa_side / (1 << (level * 4));
int tile_n = std::ceil(std::log2(on_screen_unit * side_length / max_triangle_size));
if (level < 3 && tile_n > tile_size_log2)
{
std::size_t const C = 16;
auto at = [&](std::size_t i, std::size_t j)
{
float t0 = 1.f - (1.f * i) / C;
float t1 = (1.f * j) / C;
float t2 = 1.f - t0 - t1;
return geom::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
};
std::size_t child_id = 0;
auto child = [&](std::size_t i0, std::size_t j0, std::size_t i1, std::size_t j1, std::size_t i2, std::size_t j2)
{
geom::vector<float, 3> v[3];
v[0] = at(i0, j0);
v[1] = at(i1, j1);
v[2] = at(i2, j2);
id.push_back(child_id++);
self(v, level + 1);
id.pop_back();
};
for (std::size_t i = 0; i < C; ++i)
{
for (std::size_t j = 0; j < i; ++j)
{
child(i + 1, j, i, j, i + 1, j + 1);
child(i + 1, j + 1, i, j, i, j + 1);
}
child(i + 1, i, i, i, i + 1, i + 1);
}
}
else
{
tile_n = geom::clamp(tile_n, {0, tile_size_log2});
++rendered_tiles;
static gfx::color_4f colors[4]
{
gfx::black,
gfx::dark(gfx::red).as_color_4f(),
gfx::dark(gfx::green).as_color_4f(),
gfx::blue
};
tile_program["u_p0"] = v[0];
tile_program["u_p1"] = v[1];
tile_program["u_p2"] = v[2];
tile_program["u_color"] = colors[tile_n % 4];
tile_mesh[tile_n].draw();
if (selected)
{
gl::Disable(gl::DEPTH_TEST);
tile_program["u_color"] = gfx::white.as_color_4f();
tile_mesh[0].draw();
gl::Enable(gl::DEPTH_TEST);
info.push_back(util::to_string("Selected id: ", id));
info.push_back(util::to_string("Selected separation: ", cg::separation(body, frustum).second));
}
}
});
// if(false)
// for (int f = 4; f < 5; ++f)
for (int f = 0; f < 20; ++f)
{
geom::vector<float, 3> v[3];
v[0] = icosa_vertices[icosa_faces[f][0]] - geom::point<float, 3>::zero();
v[1] = icosa_vertices[icosa_faces[f][1]] - geom::point<float, 3>::zero();
v[2] = icosa_vertices[icosa_faces[f][2]] - geom::point<float, 3>::zero();
id.push_back(f);
visit(v);
id.pop_back();
}
info.push_back(util::to_string("Tiles: ", rendered_tiles));
if (false)
{
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
gl::Disable(gl::CULL_FACE);
test_program.bind();
test_program["u_transform"] = camera_transform;
int visible_count = 0;
for (auto const & o : test_objects)
{
gfx::color_rgba color;
if (cg::separation(o.body, frustum).second < 0.f)
{
++visible_count;
o.mesh.draw();
color = gfx::red;
}
else
{
color = gfx::black;
}
float sx = width() - 10 * test_object_count + o.x * 10;
float sy = height() - 10 * test_object_count + (test_object_count - 1 - o.y) * 10;
painter.rect({{{sx, sx + 10.f}, {sy, sy + 10.f}}}, color);
}
info.push_back(util::to_string("Visible count: ", visible_count));
}
{
float s = 10.f;
painter.line({width() / 2.f - s, height() / 2.f}, {width() / 2.f + s, height() / 2.f}, 3.f, gfx::cyan, false);
painter.line({width() / 2.f, height() / 2.f - s}, {width() / 2.f, height() / 2.f + s}, 3.f, gfx::cyan, false);
}
{
info.insert(info.begin(), util::to_string("FPS: ", 1.f / frame_dt_average.average()));
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.c = gfx::cyan;
opts.scale = 2.f;
for (int l = 0; l < info.size(); ++l)
{
painter.text({10.f + 1.f, 10.f + 24.f * l + 1.f}, info[l], opts);
}
opts.c = gfx::black;
for (int l = 0; l < info.size(); ++l)
{
painter.text({10.f, 10.f + 24.f * l}, info[l], opts);
}
}
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::Disable(gl::DEPTH_TEST);
painter.render(geom::window_camera{width(), height()}.transform());
}
int main()
{
return app::main<srtm_app>();
}