diff options
Diffstat (limited to 'src/client/render')
| -rw-r--r-- | src/client/render/draw.cc | 428 | ||||
| -rw-r--r-- | src/client/render/draw.hh | 30 | ||||
| -rw-r--r-- | src/client/render/render.cc | 147 | ||||
| -rw-r--r-- | src/client/render/render.hh | 24 | ||||
| -rw-r--r-- | src/client/render/struct.cc | 1 | ||||
| -rw-r--r-- | src/client/render/struct.hh | 29 |
6 files changed, 641 insertions, 18 deletions
diff --git a/src/client/render/draw.cc b/src/client/render/draw.cc new file mode 100644 index 0000000..d9ea9c3 --- /dev/null +++ b/src/client/render/draw.cc @@ -0,0 +1,428 @@ +#include "client/render/draw.hh" + +namespace client { +namespace render { + +static void draw_state_info(const shared::player& lp) noexcept { + + constexpr auto nstr = [](const auto d) -> std::string { + auto str = std::to_string(d); + str.erase(std::prev(std::end(str), 4), std::end(str)); + return str; + }; + + const auto make_position = [&]() -> std::string { + const auto chunk_x = lp.get_chunk_pos().x; + const auto chunk_z = lp.get_chunk_pos().z; + const auto x = static_cast<double>(lp.get_local_pos().x); + const auto y = static_cast<double>(lp.get_local_pos().y); + const auto z = static_cast<double>(lp.get_local_pos().z); + constexpr auto WIDTH = shared::world::chunk::WIDTH; + const auto abs_x = + static_cast<double>(lp.get_chunk_pos().x) * WIDTH + x; + const auto abs_z = + static_cast<double>(lp.get_chunk_pos().z) * WIDTH + z; + + constexpr auto nstr = [](const double d) -> std::string { + auto str = std::to_string(d); + str.erase(std::prev(std::end(str), 4), std::end(str)); + return str; + }; + const std::string chk_str = '[' + std::to_string(chunk_x) + ", " + + std::to_string(chunk_z) + ']'; + const std::string abs_str = + '(' + nstr(abs_x) + ", " + nstr(y) + ", " + nstr(abs_z) + ')'; + return "position: " + abs_str + chk_str; + }; + + const auto make_direction = [&]() -> std::string { + const float yaw = glm::degrees(lp.get_angles().yaw); + const float pitch = glm::degrees(lp.get_angles().pitch); + + const std::string bearing = [&]() -> std::string { + if (yaw > 135.0f || yaw < -135.0f) { + return "South"; + } else if (yaw > 45.0f) { + return "East"; + } else if (yaw > -45.0f) { + return "North"; + } else { + return "West"; + } + }(); + const std::string face_str = '(' + nstr(pitch) + ", " + nstr(yaw) + ')'; + // Yaw is between 0 and 360. + return "perspective: " + face_str + " -> " + bearing; + }; + + const auto make_address = []() -> std::string { + const std::string address = std::string(client::state::address); + const std::string port = std::string(client::state::port); + return "address: " + address + ":" + port; + }; + + const auto make_tickrate = [&]() -> std::string { + return "tickrate: " + std::to_string(state::tickrate); + }; + + const auto make_seed = []() -> std::string { + return "seed: " + std::to_string(client::state::seed); + }; + + const auto make_entities = []() -> std::string { + const auto index = std::to_string(*client::state::localplayer_index); + return "index: " + index; + }; + + const auto make_chunks = []() -> std::string { + return "chunks: " + + std::to_string(client::state::requested_chunk_count) + ", " + + std::to_string(client::state::networked_chunk_count) + " @ " + + std::to_string(client::state::draw_distance) + " draw distance"; + }; + + const auto make_speed = [&]() -> std::string { + return "speed: " + nstr(glm::length(lp.get_velocity())) + " b/s"; + }; + + const auto make_velocity = [&]() -> std::string { + return "velocity: (" + nstr(lp.get_velocity().x) + ", " + + nstr(lp.get_velocity().y) + ", " + nstr(lp.get_velocity().z) + + ")"; + }; + + const auto make_msg = [&]() -> std::string { + const auto& message = get_localplayer().get_message(); + return "message: " + (message.has_value() ? message->text : ""); + }; + + const auto make_biome = [&]() -> std::string { + const int x = static_cast<int>(lp.get_local_pos().x); + const int z = static_cast<int>(lp.get_local_pos().z); + const auto is_inside = [](const int val) { + return std::clamp(val, 0, shared::world::chunk::WIDTH) == val; + }; + if (!is_inside(x) || !is_inside(z)) { + return "?"; + } + + const auto find_it = client::state::chunks.find(lp.get_chunk_pos()); + if (find_it == std::end(client::state::chunks)) { + return "?"; + } + const auto& chunk = find_it->second; + if (!chunk.has_value()) { + return "?"; + } + return std::string{"biome: "} + chunk->get_biome(x, z); + }; + + // Draws all of our strings and works its way down the top of the screen. + text_args args{.pos = {.extent = {0.0f, 1.0f - 0.015f}}, + .extent_height = 0.0165f, + .colour = {1.0f, 1.0f, 1.0f, 1.0f}, + .has_backing = true}; + const auto draw_str_trail = [&args](const std::string& str) { + draw_text(str, args); + args.pos.extent.y -= 0.02f; + }; + draw_str_trail("blockgame_linux v0.22"); + draw_str_trail(make_address()); + draw_str_trail(make_tickrate()); + draw_str_trail(make_seed()); + draw_str_trail(make_entities()); + draw_str_trail(make_chunks()); + draw_str_trail(make_position()); + draw_str_trail(make_direction()); + draw_str_trail(make_speed()); + draw_str_trail(make_velocity()); + draw_str_trail(make_biome()); + draw_str_trail(make_msg()); +} + +static void draw_fps() noexcept { + const auto get_fps_colour = [](const int fps) -> glm::vec4 { + if (fps == 0) { + return {1.0f, 1.0f, 1.0f, 1.0f}; + } else if (fps < 30) { + return {1.0f, 0.0f, 0.0f, 1.0f}; + } else if (fps < 60) { + return {1.0f, 0.5f, 0.0f, 1.0f}; + } else if (fps < 120) { + return {0.0f, 1.0f, 0.0f, 1.0f}; + } + return {0.0f, 1.0f, 1.0f, 1.0f}; + }; + + const int fps = client::render::get_fps(); + const std::string fps_str = fps != 0 ? std::to_string(fps) : "~"; + draw_text("FPS: " + fps_str, {.pos = relative_arg{.offset = {0.0f, 2.0f}}, + .extent_height = 0.0165f, + .colour = get_fps_colour(fps), + .has_backing = true}); +} + +// Player position, version, fps etc +// Just don't read past this point, doing this is unbelievably ugly. +// I don't know why I write this like someone else will read it. +static void draw_overlay(const shared::player& lp) noexcept { + draw_state_info(lp); + draw_fps(); +} + +static void draw_main_pass(const client::player& localplayer, + client::entities_t& entities, + client::world::chunks_t& chunks) noexcept { + + { + // zero means no limit + const unsigned chunks_per_frame = + client::settings::get<unsigned>({"video", "chunks_per_frame"}, 0u); + unsigned chunks_regenerated = 0u; + for (auto& chunk : chunks) { + if (!chunk.second.has_value()) { + continue; + } + + const bool skip = chunks_per_frame == 0 + ? false + : chunks_regenerated >= chunks_per_frame; + chunks_regenerated += chunk.second->draw( + chunks, localplayer, world::chunk::pass::solid, skip); + } + } + + for (const auto& [index, entity_ptr] : entities) { + if (index == localplayer.get_index()) { + continue; + } + entity_ptr->draw(localplayer); + } +} + +static void draw_water_pass(const client::player& localplayer, + client::world::chunks_t& chunks) noexcept { + for (auto& chunk : chunks) { + if (!chunk.second.has_value()) { + continue; + } + chunk.second->draw(chunks, localplayer, + client::world::chunk::pass::water); + } +} + +static void draw_wts_text(const client::player& localplayer, + client::entities_t& entities) noexcept { + for (const auto& [index, entity_ptr] : entities) { + if (index == localplayer.get_index()) { + continue; + } + entity_ptr->draw_wts(localplayer); + } +} + +static void draw_hud(const client::player& localplayer, + const client::world::chunks_t& chunks) noexcept { + const auto should_draw_hud = !client::input::is_key_toggled(SDLK_F1); + if (client::input::is_key_toggled(SDLK_F3) && should_draw_hud) { + draw_overlay(localplayer); + } + + if (client::window::is_open()) { + return; + } + + if (!should_draw_hud) { + return; + } + + // crosshair + client::render::draw_rectangle( + {.pos = {.extent = {0.5f, 0.5f}, .offset = {-3.0f, -3.0f}}, + .size = {.offset = {6.0f, 6.0f}}, + .colour = {1.0f, 1.0f, 1.0f, 1.0f}}); + + // Draw the outline of the block we're aiming at. + if (const auto interact = client::movement::interact( + localplayer, movement::interact_mode::remove, chunks); + interact.has_value()) { + + const auto [world_x, world_z] = [&]() -> std::pair<float, float> { + const float offset_x = static_cast<float>( + interact->first.x - localplayer.get_chunk_pos().x); + const float offset_z = static_cast<float>( + interact->first.z - localplayer.get_chunk_pos().z); + return {offset_x * shared::world::chunk::WIDTH, + offset_z * shared::world::chunk::WIDTH}; + }(); + + const glm::vec3 render_pos = { + world_x + static_cast<float>(interact->second.x), + interact->second.y, + world_z + static_cast<float>(interact->second.z)}; + + client::render::render_cube_outline(render_pos + 0.5f, + {0.8f, 0.8f, 0.8f, 1.0f}); + } +} + +static void update_camera(const shared::player& localplayer) noexcept { + // approximately encompasses everything we could possibly see + + // if our zfar is too low, our fog breaks + // bit of maths for calculating the max distance we could can see + client::render::camera::get_zfar() = std::hypot( + static_cast<float>(world::chunk::HEIGHT), + float(client::state::draw_distance) * std::hypot(16.0f, 16.0f)); + client::render::camera::get_pos() = + localplayer.get_local_pos() + + glm::vec3{0.0f, shared::player::EYE_HEIGHT, 0.0f}; + client::render::camera::get_front() = localplayer.get_angles().to_dir(); + client::render::camera::update(); +} + +// We create framebuffer here to enable postprocessing and solve some +// rendering problems. +static void draw_buffers(const client::player& localplayer, + client::entities_t& entities, + client::world::chunks_t& chunks) noexcept { + const auto make_vbo = [](const auto& vertices) -> GLuint { + GLuint vbo = 0; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), std::data(vertices), + GL_STATIC_DRAW); + return vbo; + }; + const auto make_vao = []() -> GLuint { + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + // position + glVertexAttribPointer(0, sizeof(glm::vec2) / sizeof(float), GL_FLOAT, + GL_FALSE, sizeof(glm::vec2), nullptr); + glEnableVertexAttribArray(0); + return vao; + }; + const auto make_ebo = [](const auto& indices) -> GLuint { + GLuint ebo = 0; + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices, + GL_STATIC_DRAW); + return ebo; + }; + const auto make_fbo = []() -> GLuint { + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + return fbo; + }; + const auto make_texture = [](const auto& internal_format, + const auto& format, + const int texture_offset) -> GLuint { + GLuint texture; + glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE2 + texture_offset); + glBindTexture(GL_TEXTURE_2D, texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + + const glm::vec2& window = client::render::get_window_size(); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, window.x, window.y, 0, + internal_format, GL_UNSIGNED_BYTE, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, format, GL_TEXTURE_2D, texture, + 0); + return texture; + }; + + static constexpr std::array<glm::vec2, 4> vertices = { + glm::vec2{-1.0f, -1.0f}, glm::vec2{1.0f, -1.0f}, glm::vec2{1.0f, 1.0f}, + glm::vec2{-1.0f, 1.0f}}; + static constexpr std::array<unsigned, 6> indices = {0, 1, 2, 2, 3, 0}; + static const client::render::program framebuffer_program{ + "res/shaders/framebuffer.vs", "res/shaders/framebuffer.fs"}; + static const GLuint vbo [[maybe_unused]] = make_vbo(vertices); + static const GLuint vao = make_vao(); + static const GLuint ebo [[maybe_unused]] = make_ebo(indices); + // Main frame buffer object. + static const GLuint fbo_main = make_fbo(); + static const GLuint main_colour [[maybe_unused]] = + make_texture(GL_RGB, GL_COLOR_ATTACHMENT0, 0); + static const GLuint main_depth [[maybe_unused]] = + make_texture(GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT, 1); + // Water frame buffer object. + static const GLuint fbo_water = make_fbo(); + static const GLuint water_colour [[maybe_unused]] = + make_texture(GL_RGB, GL_COLOR_ATTACHMENT0, 2); + static const GLuint water_depth [[maybe_unused]] = + make_texture(GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT, 3); + static const GLint u_is_underwater = + glGetUniformLocation(framebuffer_program, "_u_is_underwater"); + + // We render our main scene on the main framebuffer. + glBindFramebuffer(GL_FRAMEBUFFER, fbo_main); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + draw_main_pass(localplayer, entities, chunks); + + // We render our water scene on the water framebuffer. + glBindFramebuffer(GL_FRAMEBUFFER, fbo_water); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + const bool is_underwater = [&]() -> bool { + const glm::ivec3 pos{client::render::camera::get_pos()}; + + const auto find_it = chunks.find(localplayer.get_chunk_pos()); + if (find_it == std::end(chunks) || !find_it->second.has_value()) { + return false; + } + if (shared::world::chunk::is_outside_chunk(pos)) { + return false; + } + + const auto block = find_it->second->get_block(pos); + return block.type == shared::world::block::type::water; + }(); + if (is_underwater) { + glFrontFace(GL_CW); + draw_water_pass(localplayer, chunks); + glFrontFace(GL_CCW); + } else { + draw_water_pass(localplayer, chunks); + } + + glBindFramebuffer(GL_FRAMEBUFFER, main_colour); + glDisable(GL_DEPTH_TEST); + glUseProgram(framebuffer_program); + glBindVertexArray(vao); + + glUniform1i(u_is_underwater, is_underwater); + + glDrawElements(GL_TRIANGLES, std::size(indices), GL_UNSIGNED_INT, nullptr); + + static client::render::program postprocess_program{ + "res/shaders/framebuffer.vs", "res/shaders/postprocess.fs"}; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glUseProgram(postprocess_program); + glDrawElements(GL_TRIANGLES, std::size(indices), GL_UNSIGNED_INT, nullptr); + + glEnable(GL_DEPTH_TEST); +} + +void draw(entities_t& entities, world::chunks_t& chunks) noexcept { + const auto& localplayer = get_localplayer(); + + update_camera(localplayer); + client::render::update_uniforms(); + + draw_buffers(localplayer, entities, chunks); + draw_hud(localplayer, chunks); + draw_wts_text(localplayer, entities); + + client::window::draw(); + + client::render::swap_window(); +} + +} // namespace render +} // namespace client diff --git a/src/client/render/draw.hh b/src/client/render/draw.hh new file mode 100644 index 0000000..bf43224 --- /dev/null +++ b/src/client/render/draw.hh @@ -0,0 +1,30 @@ +#ifndef CLIENT_RENDER_DRAW_HH_ +#define CLIENT_RENDER_DRAW_HH_ + +#include <string_view> + +#include <glm/glm.hpp> + +#include "client/entity/player.hh" +#include "client/input.hh" +#include "client/movement/movement.hh" +#include "client/render/camera.hh" +#include "client/render/program.hh" +#include "client/render/render.hh" +#include "client/render/struct.hh" +#include "client/settings.hh" +#include "client/shared.hh" +#include "client/window/hud_window.hh" +#include "client/window/window.hh" +#include "client/world/chunk.hh" + +namespace client { +namespace render { + +void draw(client::entities_t& players, + client::world::chunks_t& chunks) noexcept; + +} // namespace render +} // namespace client + +#endif diff --git a/src/client/render/render.cc b/src/client/render/render.cc index 18aa32a..941988d 100644 --- a/src/client/render/render.cc +++ b/src/client/render/render.cc @@ -27,7 +27,7 @@ SDL_Window* const& get_sdl_window() noexcept { std::bind(SDL_GL_SetAttribute, SDL_GL_CONTEXT_MINOR_VERSION, 2)); SDL_Window* const ret = - SDL_CreateWindow("blockgame_linux", 0, 0, 0, 0, + SDL_CreateWindow("blockgame_linux", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_BORDERLESS); check_sdl_pointer(ret); @@ -124,6 +124,71 @@ void swap_window() noexcept { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } +// Additional sanity checks for our atlas. +static void check_atlas(const client::render::texture& texture) { + if (texture.width % 6) { + throw std::runtime_error("invalid atlas; WIDTH is not divisible by 6"); + } + if (texture.height % (texture.width / 6)) { + throw std::runtime_error( + "invalid atlas, HEIGHT is not divisible by (WIDTH / 6)"); + } +} + +GLuint get_texture_atlas() noexcept { + static const auto atlas = []() -> GLuint { + GLuint texture = 0; + glActiveTexture(GL_TEXTURE1); + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); + + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_ANISOTROPY, 16.0f); + + const client::render::texture stbi{"res/textures/atlas.png"}; + check_atlas(stbi); + const int face_size = stbi.width / 6; + + // 2D texture array, where our depth is our block face. + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, face_size, face_size, + 6 * (stbi.height / face_size), 0, + stbi.channels == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + + // Fill the 2D texture array. + // Because our image has multiple images on the x-axis and opengl + // expects a single image per axis, we must fill it in row by row. + const auto get_pixel_xy = [&stbi](const int x, const int y) { + return stbi.image + 4 * (y * stbi.width + x); + }; + for (int x = 0; x < 6; ++x) { + const int x_pixel = x * face_size; + + for (int y = 0; y < stbi.height / face_size; ++y) { + const int y_pixel = y * face_size; + + for (auto row = 0; row < face_size; ++row) { + glTexSubImage3D( + GL_TEXTURE_2D_ARRAY, 0, 0, row, x + y * 6, face_size, 1, + 1, GL_RGBA, GL_UNSIGNED_BYTE, + get_pixel_xy(x_pixel, row + y_pixel)); // pixel + } + } + } + + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + + return texture; + }(); + return atlas; +} + void render_cube_outline(const glm::vec3& pos, const glm::vec4& colour) noexcept { const auto generate_vbo = [](const auto& vertices) -> GLuint { @@ -427,6 +492,8 @@ public: font_atlas(const unsigned size) noexcept : font_size(size), texture(make_texture_info(size)) {} ~font_atlas() noexcept { glDeleteTextures(1, &this->texture.texture); } + font_atlas(font_atlas&&) = delete; + font_atlas(const font_atlas&) = delete; GLint get_height() const noexcept { return this->texture.height; } GLint get_width() const noexcept { return this->texture.width; } @@ -578,25 +645,71 @@ void update_uniforms() noexcept { }; static const GLuint ubo = make_ubo(); - ubo_data upload = {.proj = camera::get_proj(), - .view = camera::get_view(), - .frustum = camera::get_frustum(), - .znear = camera::get_znear(), - .zfar = camera::get_zfar(), - .xfov = glm::radians(camera::get_xfov()), - .yfov = glm::radians(camera::get_yfov()), - .time = []() -> unsigned { - static const auto start = - std::chrono::steady_clock::now(); - const auto now = std::chrono::steady_clock::now(); - return static_cast<unsigned>( - (now - start) / std::chrono::milliseconds(1)); - }(), - .xwindow = static_cast<float>(get_window_size().x), - .ywindow = static_cast<float>(get_window_size().y)}; + const ubo_data upload = { + .proj = camera::get_proj(), + .view = camera::get_view(), + .frustum = camera::get_frustum(), + .znear = camera::get_znear(), + .zfar = camera::get_zfar(), + .xfov = glm::radians(camera::get_xfov()), + .yfov = glm::radians(camera::get_yfov()), + .time = []() -> unsigned { + static const auto start = std::chrono::steady_clock::now(); + const auto now = std::chrono::steady_clock::now(); + return static_cast<unsigned>((now - start) / + std::chrono::milliseconds(1)); + }(), + .xwindow = static_cast<float>(get_window_size().x), + .ywindow = static_cast<float>(get_window_size().y)}; glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(upload), &upload); } +void draw_rectangle(const rectangle_args& ra) noexcept { + const glm::vec2 pos = ra.pos.to_vec2(); + const glm::vec2 size = ra.size.to_vec2(); + + // We want to render from the bottom left corner. + render::render_rectangle(pos + (size / 2.0f), size, ra.colour); +} + +void draw_colour(const glm::vec4& colour) noexcept { + const rectangle_args args = { + .pos = {.extent = {0.0f, 0.0f}}, + .size = {.extent = {1.0f, 1.0f}}, + .colour = colour, + }; + draw_rectangle(args); +} + +void draw_text(const std::string_view text, const text_args& args) noexcept { + const glm::vec2& window = render::get_window_size(); + const float x = floorf(window.x * args.pos.extent.x + args.pos.offset.x); + const float y = floorf(window.y * args.pos.extent.y + args.pos.offset.y); + const unsigned height = static_cast<unsigned>( + args.extent_height * window.y + args.offset_height); + + const auto make_matrix = [&](const float xo, const float yo) -> glm::mat4 { + constexpr auto identity = glm::mat4(1.0f); + const auto proj = glm::ortho(0.f, window.x, 0.f, window.y); + const auto tran = + glm::translate(identity, glm::vec3{x + xo, y + yo, 0.0f}); + return proj * tran; + }; + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + if (args.has_backing) { + constexpr glm::vec4 black{0.0f, 0.0f, 0.0f, 1.0f}; + render_text(text, height, args.is_centered, args.is_vcentered, black, + make_matrix(2.0f, -2.0f)); + } + glEnable(GL_BLEND); + + render_text(text, height, args.is_centered, args.is_vcentered, args.colour, + make_matrix(0, 0)); + glEnable(GL_DEPTH_TEST); +} + } // namespace render } // namespace client diff --git a/src/client/render/render.hh b/src/client/render/render.hh index ca98e6f..c98043a 100644 --- a/src/client/render/render.hh +++ b/src/client/render/render.hh @@ -27,8 +27,10 @@ #include "client/render/camera.hh" #include "client/render/program.hh" +#include "client/render/struct.hh" +#include "client/render/texture.hh" #include "shared/shared.hh" -#include "shared/world.hh" +#include "shared/world/chunk.hh" namespace client { namespace render { @@ -40,6 +42,7 @@ void quit() noexcept; int get_fps() noexcept; SDL_Window* const& get_sdl_window() noexcept; const glm::ivec2& get_window_size() noexcept; +GLuint get_texture_atlas() noexcept; // Update void update_uniforms() noexcept; @@ -54,7 +57,26 @@ void render_text(const std::string_view text, const unsigned int size, void render_cube_outline(const glm::vec3& pos, const glm::vec4& colour) noexcept; +// Draw is similar to render but more abstracted. +void draw_colour(const glm::vec4& colour) noexcept; +struct rectangle_args { + relative_arg pos; + relative_arg size; + glm::vec4 colour; +}; +void draw_rectangle(const rectangle_args& args) noexcept; + +struct text_args { + relative_arg pos; + float extent_height; // we don't get width here, + float offset_height; + glm::vec4 colour; + bool has_backing; + bool is_centered; + bool is_vcentered; +}; +void draw_text(const std::string_view text, const text_args& args) noexcept; } // namespace render } // namespace client diff --git a/src/client/render/struct.cc b/src/client/render/struct.cc new file mode 100644 index 0000000..a7a1b5d --- /dev/null +++ b/src/client/render/struct.cc @@ -0,0 +1 @@ +#include "client/render/struct.hh" diff --git a/src/client/render/struct.hh b/src/client/render/struct.hh new file mode 100644 index 0000000..fd00ffd --- /dev/null +++ b/src/client/render/struct.hh @@ -0,0 +1,29 @@ +#ifndef CLIENT_RENDER_STRUCT_HH_ +#define CLIENT_RENDER_STRUCT_HH_ + +//#include <client/render/render.hh> +#include <glm/glm.hpp> + +namespace client { +namespace render { + +const glm::ivec2& get_window_size() noexcept; // forward declaration + +// Scale takes the range [0, 1] and represents an amount of the screen. +// Offset is any value that is added after scale. +struct relative_arg { + glm::vec2 extent; + glm::vec2 offset; + + glm::vec2 to_vec2() const noexcept { + const glm::vec2& window = render::get_window_size(); + const float x = this->extent.x * window.x + this->offset.x; + const float y = this->extent.y * window.y + this->offset.y; + return {x, y}; + } +}; + +} // namespace render +} // namespace client + +#endif |
