From 26440b0faff230ff89dde87a37b61e3f130caa15 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 5 Nov 2020 16:55:18 +0300 Subject: [PATCH] Add SRTM rendering example (wip) --- examples/srtm.cpp | 1198 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1198 insertions(+) create mode 100644 examples/srtm.cpp diff --git a/examples/srtm.cpp b/examples/srtm.cpp new file mode 100644 index 00000000..ce077291 --- /dev/null +++ b/examples/srtm.cpp @@ -0,0 +1,1198 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace psemek::cg +{ + + template + struct box; + + template + box(geom::box) -> box; + + template + struct box + { + box() = default; + box(geom::box const & b); + + std::array, 8> vertices; + }; + + template + box::box(geom::box const & b) + { + for (std::size_t z = 0; z < 2; ++z) + { + for (std::size_t y = 0; y < 2; ++y) + { + for (std::size_t x = 0; x < 2; ++x) + { + std::size_t i = z * 4 + y * 2 + x; + + vertices[i][0] = (x == 0) ? b[0].min : b[0].max; + vertices[i][1] = (y == 0) ? b[1].min : b[1].max; + vertices[i][2] = (z == 0) ? b[2].min : b[2].max; + } + } + } + } + + template + auto const & vertices(box const & b) + { + return b.vertices; + } + + namespace detail + { + + inline auto const & cubiod_edges() + { + static const std::array, 12> result = + {{ + { 0b000, 0b001 }, + { 0b010, 0b011 }, + { 0b100, 0b101 }, + { 0b110, 0b111 }, + + { 0b000, 0b010 }, + { 0b001, 0b011 }, + { 0b100, 0b110 }, + { 0b101, 0b111 }, + + { 0b000, 0b100 }, + { 0b001, 0b101 }, + { 0b010, 0b110 }, + { 0b011, 0b111 }, + }}; + + return result; + } + + inline auto const & cubiod_faces() + { + static const std::array, 6> result = + {{ + {{ 0b000, 0b100, 0b110, 0b010 }}, + {{ 0b001, 0b011, 0b111, 0b101 }}, + {{ 0b000, 0b001, 0b101, 0b100 }}, + {{ 0b010, 0b110, 0b111, 0b011 }}, + {{ 0b000, 0b010, 0b011, 0b001 }}, + {{ 0b100, 0b101, 0b111, 0b110 }}, + }}; + + return result; + } + } + + template + auto const & edges(box const &) + { + return detail::cubiod_edges(); + } + + template + auto const & faces(box const &) + { + return detail::cubiod_faces(); + } + + template + auto const & face_normals(box const &) + { + static const std::array, 6> result = + {{ + {-1, 0, 0}, + { 1, 0, 0}, + { 0, -1, 0}, + { 0, 1, 0}, + { 0, 0, -1}, + { 0, 0, 1}, + }}; + + return result; + } + + template + auto const & edge_directions(box const &) + { + static const std::array, 3> result = + {{ + { 1, 0, 0}, + { 0, 1, 0}, + { 0, 0, 1}, + }}; + + return result; + } + + template + struct frustum; + + template + frustum(geom::matrix) -> frustum; + + template + struct frustum + { + frustum(geom::matrix const & m); + + std::array, 8> vertices; + std::array, 6> face_normals; + }; + + template + frustum::frustum(geom::matrix const & m) + { + bool flip = (geom::det(m) < 0); + + for (std::size_t z = 0; z < 2; ++z) + { + for (std::size_t y = 0; y < 2; ++y) + { + for (std::size_t x = 0; x < 2; ++x) + { + std::size_t i = z * 4 + y * 2 + (flip ? 1 - x : x); + + geom::vector p; + p[0] = (x == 0) ? -1 : 1; + p[1] = (y == 0) ? -1 : 1; + p[2] = (z == 0) ? -1 : 1; + p[3] = 1; + + geom::gauss(m, p); + + vertices[i] = geom::as_point(p); + } + } + } + } + + template + auto const & vertices(frustum const & f) + { + return f.vertices; + } + + template + auto const & edges(frustum const &) + { + return detail::cubiod_edges(); + } + + template + auto const & faces(frustum const &) + { + return detail::cubiod_faces(); + } + + template + struct triangular_prism + { + triangular_prism() = default; + triangular_prism(geom::triangle> const & t, geom::vector const & d); + + std::array, 6> vertices; + std::array, 4> edge_directions; + }; + + template + triangular_prism::triangular_prism(geom::triangle> const & t, geom::vector const & d) + { + for (std::size_t i = 0; i < 3; ++i) + { + vertices[i] = t[i]; + vertices[i + 3] = t[i] + d; + } + + if (geom::dot(geom::normal(vertices[0], vertices[1], vertices[2]), d) < 0) + { + std::swap(vertices[1], vertices[2]); + std::swap(vertices[4], vertices[5]); + } + + for (std::size_t i = 0; i < 3; ++i) + { + edge_directions[i] = geom::normalized(vertices[(i + 1) % 3] - vertices[i]); + } + edge_directions[3] = geom::normalized(d); + } + + template + auto const & vertices(triangular_prism const & p) + { + return p.vertices; + } + + template + auto const & edges(triangular_prism const &) + { + static const std::array, 9> result = + {{ + { 0, 1 }, + { 1, 2 }, + { 2, 0 }, + + { 3, 4 }, + { 4, 5 }, + { 5, 3 }, + + { 0, 3 }, + { 1, 4 }, + { 2, 5 }, + }}; + + return result; + } + + template + auto const & faces(triangular_prism const &) + { + static std::array, 5> result = + {{ + { 0, 2, 1 }, + { 3, 4, 5 }, + { 0, 1, 4, 3 }, + { 1, 2, 5, 4 }, + { 2, 0, 3, 5 }, + }}; + return result; + } + + template + auto const & edge_directions(triangular_prism const & p) + { + return p.edge_directions; + } + + namespace detail + { + + template + struct has_static_size + : std::false_type + {}; + + template + struct has_static_size> + : std::true_type + {}; + + template + constexpr bool has_static_size_v = has_static_size::value; + + template + struct static_size; + + template + struct static_size> + { + static constexpr std::size_t value = N; + }; + + template + constexpr std::size_t static_size_v = static_size::value; + + } + + template + auto triangles(Body const & b) + { + auto const & fs = faces(b); + + using faces_type = std::remove_cvref_t; + using face_type = std::remove_cvref_t; + using index_type = std::remove_cvref_t; + + auto impl = [&fs](auto out) + { + for (auto const & f : fs) + { + auto it0 = std::begin(f); + + for (auto it = std::next(it0), jt = std::next(it); jt < std::end(f); it = jt++) + { + *out++ = {*it0, *it, *jt}; + } + } + }; + + if constexpr (detail::has_static_size_v && detail::has_static_size_v) + { + constexpr std::size_t faces_count = detail::static_size_v; + constexpr std::size_t face_size = detail::static_size_v; + std::array, faces_count * (face_size - 2)> result; + impl(result.begin()); + return result; + } + else + { + std::vector> result; + if constexpr (detail::has_static_size_v) + { + constexpr std::size_t face_size = detail::static_size_v; + result.reserve(fs.size() * (face_size - 2)); + } + else + { + result.reserve(fs.size()); + } + impl(std::back_inserter(result)); + return result; + } + } + + template + auto edge_directions(Body const & b) + { + auto const & vs = vertices(b); + auto const & es = edges(b); + + using edges_type = std::remove_cvref_t; + using vector_type = std::remove_cvref_t; + + auto impl = [&es, &vs](auto out) + { + for (auto const & e : es) + { + *out++ = geom::normalized(vs[e[1]] - vs[e[0]]); + } + }; + + if constexpr (detail::has_static_size_v) + { + constexpr std::size_t edges_count = detail::static_size_v; + std::array result; + impl(result.begin()); + return result; + } + else + { + std::vector result; + result.reserve(es.size()); + impl(std::back_inserter(result)); + return result; + } + } + + template + auto faces(Body const & b) + { + return triangles(b); + } + + template + auto face_normals(Body const & b) + { + auto const & vs = vertices(b); + auto const & fs = faces(b); + + using faces_type = std::remove_cvref_t; + using vector_type = std::remove_cvref_t; + + auto impl = [&fs, &vs](auto out) + { + for (auto const & f : fs) + { + *out++ = geom::normal(vs[f[0]], vs[f[1]], vs[f[2]]); + } + }; + + if constexpr (detail::has_static_size_v) + { + constexpr std::size_t faces_count = detail::static_size_v; + std::array result; + impl(result.begin()); + return result; + } + else + { + std::vector result; + result.reserve(fs.size()); + impl(std::back_inserter(result)); + return result; + } + } + + template + bool inside(P const & p, Body const & body) + { + auto const & vs = vertices(body); + auto const & fs = faces(body); + + for (auto const & f : fs) + { + if (geom::orientation(vs[f[0]], vs[f[1]], vs[f[2]], p) == geom::sign_t::negative) + return false; + } + return true; + } + + // returns pair(normalized vector from 1 to 2, signed distance) + // distance <= 0 means intersection + template + auto separation(Body1 const & b1, Body2 const & b2) + { + auto const & vs1 = vertices(b1); + auto const & vs2 = vertices(b2); + + auto const & fs1 = faces(b1); + auto const & fs2 = faces(b2); + + auto const & eds1 = edge_directions(b1); + auto const & eds2 = edge_directions(b2); + + using vector_type = std::remove_cvref_t; + using scalar_type = std::remove_cvref_t; + + vector_type res_n = vector_type::zero(); + auto res_d = -std::numeric_limits::infinity(); + + auto process_faces = [](auto const & vs1, auto const & fs1, auto const & vs2) + { + vector_type res_n = vector_type::zero(); + scalar_type res_d = -std::numeric_limits::infinity(); + + for (auto const & f : fs1) + { + auto const face_n = geom::normal(vs1[f[0]], vs1[f[1]], vs1[f[2]]); + scalar_type face_d = std::numeric_limits::infinity(); + + auto const face_p = vs1[f[0]]; + + for (auto const & v : vs2) + { + auto const d = geom::dot(face_n, v - face_p); + face_d = std::min(d, face_d); + } + + if (face_d == std::numeric_limits::infinity()) + { + throw 42; + } + + if (face_d > res_d) + { + res_d = face_d; + res_n = face_n; + } + } + + return std::make_pair(res_n, res_d); + }; + + auto process_edges = [](auto const & vs1, auto const & eds1, auto const & vs2, auto const & eds2) + { + vector_type res_n = vector_type::zero(); + scalar_type res_d = -std::numeric_limits::infinity(); + + for (auto const & ed1 : eds1) + { + for (auto const & ed2 : eds2) + { + auto edge_n = geom::cross(ed1, ed2); + auto l = geom::length(edge_n); + if (l == 0) continue; + edge_n /= l; + + geom::interval i1, i2; + + for (auto const & v : vs1) + { + i1 |= geom::dot(geom::homogeneous(v), geom::homogeneous(edge_n)); + } + + for (auto const & v : vs2) + { + i2 |= geom::dot(geom::homogeneous(v), geom::homogeneous(edge_n)); + } + + auto edge_d12 = i2.min - i1.max; + auto edge_d21 = i1.min - i2.max; + + scalar_type edge_d; + + if (edge_d12 > edge_d21) + { + edge_d = edge_d12; + } + else + { + edge_d = edge_d21; + edge_n = -edge_n; + } + + if (edge_d > res_d) + { + res_d = edge_d; + res_n = edge_n; + } + } + } + + return std::make_pair(res_n, res_d); + }; + + { + auto res12 = process_faces(vs1, fs1, vs2); + if (res12.second > res_d) + { + res_d = res12.second; + res_n = res12.first; + } + } + + { + auto res21 = process_faces(vs2, fs2, vs1); + if (res21.second > res_d) + { + res_d = res21.second; + res_n = -res21.first; + } + } + + { + auto rese = process_edges(vs1, eds1, vs2, eds2); + if (rese.second > res_d) + { + res_d = rese.second; + res_n = rese.first; + } + } + + return std::make_pair(res_n, res_d); + } + +} + +using namespace psemek; + +template +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 const float icosa_a = 0.850650808; // phi / sqrt(1 + phi^2) +static const float icosa_b = 0.525731112; // 1 / sqrt(1 + phi^2) + +static const float icosa_side = 1.05146222; // 2 / sqrt(phi * sqrt(5)) + +static const geom::vector icosa_vertices[12] = +{ + {-icosa_a, -icosa_b, 0.0}, // 0 + {-icosa_a, icosa_b, 0.0}, // 1 + { icosa_a, -icosa_b, 0.0}, // 2 + { icosa_a, icosa_b, 0.0}, // 3 + + {0.0, -icosa_a, -icosa_b}, // 4 + {0.0, -icosa_a, icosa_b}, // 5 + {0.0, icosa_a, -icosa_b}, // 6 + {0.0, icosa_a, icosa_b}, // 7 + + {-icosa_b, 0.0, -icosa_a}, // 8 + { icosa_b, 0.0, -icosa_a}, // 9 + {-icosa_b, 0.0, icosa_a}, // 10 + { icosa_b, 0.0, icosa_a}, // 11 +}; + +static const std::size_t icosa_faces[20][3] = +{ + {0, 1, 8,}, + {0, 10, 1,}, + {2, 9, 3,}, + {2, 3, 11,}, + {4, 5, 0,}, + {4, 2, 5,}, + {6, 1, 7,}, + {6, 7, 3,}, + {8, 9, 4,}, + {8, 6, 9,}, + {10, 5, 11,}, + {10, 11, 7,}, + {0, 8, 4,}, + {0, 5, 10,}, + {1, 6, 8,}, + {1, 10, 7,}, + {2, 4, 9,}, + {2, 11, 5,}, + {3, 9, 6,}, + {3, 7, 11}, +}; + +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 camera_azimuthal_angle_updater{camera.azimuthal_angle, 20.f}; + smooth_updater 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 body; + gfx::mesh mesh; + int x, y; + }; + + int const test_object_count = 21; + std::vector test_objects; + + util::clock> frame_clock; + util::moving_average 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 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 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>(); + 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::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 +std::ostream & operator << (std::ostream & os, std::vector 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() +{ + std::vector 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::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::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(tile_size); + + info.push_back(util::to_string("Camera pos: ", camera_pos)); + + std::size_t rendered_tiles = 0; + std::vector id; + auto visit = util::recursive([&](auto & self, geom::vector const (&v)[3], int level = 0) -> void + { + auto const o = geom::point::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> t{o + v[0], o + v[1], o + v[2]}; + cg::triangular_prism 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 + { + 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::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 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 v[3]; + v[0] = icosa_vertices[icosa_faces[f][0]]; + v[1] = icosa_vertices[icosa_faces[f][1]]; + v[2] = icosa_vertices[icosa_faces[f][2]]; + 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(); +}