diff options
Diffstat (limited to 'src/client/world/chunk.cc')
| -rw-r--r-- | src/client/world/chunk.cc | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/src/client/world/chunk.cc b/src/client/world/chunk.cc new file mode 100644 index 0000000..99720cb --- /dev/null +++ b/src/client/world/chunk.cc @@ -0,0 +1,255 @@ +#include "client/world/chunk.hh" + +namespace client { +namespace world { + +void chunk::render(const float world_x, const float world_z, + const pass& pass) noexcept { + const auto make_matrix = [&]() -> glm::mat4 { + const auto& proj = client::render::camera::get_proj(); + const auto& view = client::render::camera::get_view(); + return glm::translate(proj * view, glm::vec3{world_x, 0, world_z}); + }; + static client::render::program program{"res/shaders/face.vs", + "res/shaders/face.fs"}; + static const GLint u_matrix = glGetUniformLocation(program, "_u_matrix"); + + const GLuint texture [[maybe_unused]] = client::render::get_texture_atlas(); + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glUseProgram(program); + // Our choice of vao depends on which pass we're doing. + const auto [vao, elements] = [&pass, this]() -> std::pair<GLuint, GLuint> { + if (pass == pass::solid) { + return {this->glo->solid_vao, this->glo->solid_elements}; + } + return {this->glo->water_vao, this->glo->water_elements}; + }(); + glBindVertexArray(vao); + + glUniformMatrix4fv(u_matrix, 1, GL_FALSE, glm::value_ptr(make_matrix())); + + glDrawArrays(GL_TRIANGLES, 0, elements); +} + +const chunk* chunk::get_neighbour(const chunks_t& chunks, + shared::math::coords offset) const noexcept { + const auto find_it = chunks.find(this->pos + offset); + if (find_it == std::end(chunks) || !find_it->second.has_value()) { + return nullptr; + } + return &((*find_it).second.value()); +} + +bool chunk::maybe_regenerate_glo(const chunks_t& chunks) noexcept { + // We need all surrounding chunks to make our vbo, so early out with false + // if we can't do that yet. + const auto chunk_forward = this->get_neighbour(chunks, {0, 1}); + const auto chunk_backward = this->get_neighbour(chunks, {0, -1}); + const auto chunk_right = this->get_neighbour(chunks, {1, 0}); + const auto chunk_left = this->get_neighbour(chunks, {-1, 0}); + if (!chunk_forward || !chunk_left || !chunk_backward || !chunk_right) { + return false; + } + + // Single-axis-outside-chunk-bounds-allowed block access. + const auto get_outside_block = [&](const int x, const int y, + const int z) -> shared::world::block { + if (y < 0 || y >= shared::world::chunk::HEIGHT) { + return shared::world::block::type::air; + } else if (x >= shared::world::chunk::WIDTH) { + return chunk_right->get_block({x - WIDTH, y, z}); + } else if (x < 0) { + return chunk_left->get_block({x + WIDTH, y, z}); + } else if (z >= shared::world::chunk::WIDTH) { + return chunk_forward->get_block({x, y, z - WIDTH}); + } else if (z < 0) { + return chunk_backward->get_block({x, y, z + WIDTH}); + } + return this->get_block({x, y, z}); + }; + + // We fill up two vbos, one for each possible rendering pass. + std::vector<block::glvert> solid_data; + std::vector<block::glvert> water_data; + + // For all blocks in the chunk, check if its neighbours are air. If they + // are, it's possible that we can see the block, so add it to vertices. + // We need to read into the neighbours chunk occasionally. + for (auto x = 0; x < WIDTH; ++x) { + for (auto y = 0; y < HEIGHT; ++y) { + for (auto z = 0; z < WIDTH; ++z) { + const auto& block = this->get_block({x, y, z}); + const auto bv = shared::world::block::get_visibility(block); + + if (bv == shared::world::block::visibility::invisible) { + continue; + } + + std::vector<block::glvert> glverts; + glverts.reserve(6 * 6); + + const auto draw_type = block::get_draw_type(block.type); + + const block::glfaces_t& faces = block::get_glfaces(block.type); + if (draw_type == block::draw_type::block) { + const std::array<shared::world::block, 6> around{ + get_outside_block(x, y, z + 1), + get_outside_block(x + 1, y, z), + get_outside_block(x, y, z - 1), + get_outside_block(x - 1, y, z), + get_outside_block(x, y + 1, z), + get_outside_block(x, y - 1, z), + }; + + for (auto i = 0ul; i < std::size(faces); ++i) { + const auto ov = block::get_visibility(around[i]); + if (bv == block::visibility::translucent && + ov == block::visibility::translucent) { + continue; + } + if (ov == block::visibility::solid) { + continue; + } + + std::ranges::copy(faces[i], + std::back_inserter(glverts)); + } + } else if (draw_type == block::draw_type::custom) { + for (const auto& face : faces) { + std::ranges::copy(face, std::back_inserter(glverts)); + } + } + + // Move the block pos verts to its intended position. + // Move the block texture verts to fit in the atlas. + const glm::vec3 offset_vec3{x, y, z}; + const float tex_yoff = static_cast<float>(block.type) - 1.0f; + const auto fix_face = [&](auto& face) { + face.vertice += offset_vec3 + 0.5f; // move to origin too + face.texture.z += tex_yoff * 6.0f; + return face; + }; + + auto& vbo_dest = block.type == shared::world::block::type::water + ? water_data + : solid_data; + std::ranges::transform(glverts, std::back_inserter(vbo_dest), + fix_face); + } + } + } + + const auto generate_vbo = [](const auto& data) -> GLuint { + GLuint vbo = 0; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, std::size(data) * sizeof(block::glvert), + std::data(data), GL_STATIC_DRAW); + return vbo; + }; + const auto generate_vao = []() -> GLuint { + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + // position + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, sizeof(glm::vec3) / sizeof(float), GL_FLOAT, + GL_FALSE, sizeof(block::glvert), nullptr); + // texture + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, sizeof(glm::vec3) / sizeof(float), GL_FLOAT, + GL_FALSE, sizeof(block::glvert), + reinterpret_cast<void*>(sizeof(glm::vec3))); + return vao; + }; + // If we were to emplace glo with these there is no guarantee that each + // function will be called in order (at least, for g++ it isn't). Therefore + // we need to call them in order first. + const auto solid_vbo = generate_vbo(solid_data); + const auto solid_vao = generate_vao(); + const auto water_vbo = generate_vbo(water_data); + const auto water_vao = generate_vao(); + this->glo.emplace(std::size(solid_data), solid_vbo, solid_vao, + std::size(water_data), water_vbo, water_vao); + return true; +} + +// http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-testing-boxes/ +static bool box_in_frustum(const std::array<glm::vec3, 8>& points) noexcept { + const auto& frustum = client::render::camera::get_frustum(); + + for (const auto& plane : frustum) { + bool inside = false; + bool outside = false; + + for (const auto& point : points) { + const float distance = plane.x * point.x + plane.y * point.y + + plane.z * point.z + plane.w; + if (distance < 0.0f) { + outside = true; + } else { + inside = true; + } + + if (inside && outside) { + break; + } + } + + if (!inside) { + return false; + } + } + + return true; +}; + +static bool is_chunk_visible(const float world_x, + const float world_z) noexcept { + const std::array<glm::vec3, 8> box_vertices = + [&world_x, &world_z]() -> std::array<glm::vec3, 8> { + const float max_world_x = world_x + shared::world::chunk::WIDTH; + const float max_world_z = world_z + shared::world::chunk::WIDTH; + + return {glm::vec3{world_x, 0.0f, world_z}, + {max_world_x, 0.0f, world_z}, + {world_x, 0.0f, max_world_z}, + {max_world_x, 0.0f, max_world_z}, + {world_x, shared::world::chunk::HEIGHT, world_z}, + {max_world_x, shared::world::chunk::HEIGHT, world_z}, + {world_x, shared::world::chunk::HEIGHT, max_world_z}, + {max_world_x, shared::world::chunk::HEIGHT, max_world_z}}; + }(); + + return box_in_frustum(box_vertices); +} + +bool chunk::draw(const chunks_t& chunks, const shared::player& lp, + const pass& pass, const bool skip_regen) noexcept { + bool did_regen = false; + if (!this->glo.has_value() || this->should_regenerate_vbo) { + if (skip_regen || !maybe_regenerate_glo(chunks)) { + return false; + } + this->should_regenerate_vbo = false; + did_regen = true; + } + + const auto [world_x, world_z] = [&lp, this]() -> std::pair<float, float> { + const float offset_x = + static_cast<float>(this->pos.x - lp.get_chunk_pos().x); + const float offset_z = + static_cast<float>(this->pos.z - lp.get_chunk_pos().z); + return {offset_x * chunk::WIDTH, offset_z * chunk::WIDTH}; + }(); + + if (is_chunk_visible(world_x, world_z)) { + render(world_x, world_z, pass); + } + + return did_regen; +} + +} // namespace world +} // namespace client |
