aboutsummaryrefslogtreecommitdiff
path: root/src/client/world.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/world.cc')
-rw-r--r--src/client/world.cc429
1 files changed, 0 insertions, 429 deletions
diff --git a/src/client/world.cc b/src/client/world.cc
deleted file mode 100644
index 1ceb9fb..0000000
--- a/src/client/world.cc
+++ /dev/null
@@ -1,429 +0,0 @@
-#include "world.hh"
-
-namespace client {
-namespace world {
-
-// 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)");
- }
-}
-
-void chunk::render(const float world_x, const float world_z,
- const pass& pass) noexcept {
- const auto make_texture = []() -> 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;
- };
- 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 GLuint texture [[maybe_unused]] = make_texture();
- static const GLint u_matrix = glGetUniformLocation(program, "_u_matrix");
-
- 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);
-}
-
-// This function translates and rotates a set of vertices that describes a
-// neutral face and also adds a vector to the texture coords.
-std::array<chunk::glface, 6>
-chunk::make_glfaces(const glface_args& args) noexcept {
-
- static constexpr std::array<glface, 6> glfaces = {
- glface{{-0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}},
- glface{{0.5f, -0.5f, 0.0f}, {1.0f, 1.0f, 0.0f}},
- glface{{0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}},
- glface{{0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}},
- glface{{-0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 0.0f}},
- glface{{-0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}}};
-
- // We have to be careful here not to rotate/translate a zero vector.
- const glm::mat4 mtranslate =
- args.translate == glm::vec3{}
- ? glm::mat4{1.0f}
- : glm::translate(glm::mat4{1.0f}, args.translate);
- const glm::mat4 mrotate =
- args.rotate_axis == glm::vec3{}
- ? glm::mat4{1.0f}
- : glm::rotate(glm::mat4{1.0f}, glm::radians(args.rotate_degrees),
- args.rotate_axis);
-
- std::array<glface, 6> ret;
-
- std::ranges::transform(glfaces, std::begin(ret), [&](const auto f) {
- auto face = f; // unfortunate copy
- face.vertice =
- glm::vec3(mtranslate * mrotate * glm::vec4{face.vertice, 1.0f});
- face.texture += args.texture_offset;
- return face;
- });
-
- return ret;
-}
-
-const chunk* chunk::get_neighbour(const chunk::map& 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 chunk::map& chunks) noexcept {
- // We need all surrounding chunks to make our vbo, this is why it's called
- // "maybe" regenerate vbo.
- 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;
- }
-
- static const auto [atlas_width,
- atlas_height] = []() -> std::pair<int, int> {
- const client::render::texture texture{"res/textures/atlas.png"};
- check_atlas(texture);
- return {texture.width, texture.height};
- }();
-
- // 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<glface> solid_data;
- std::vector<glface> 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;
- }
-
- const auto& front{get_outside_block(x, y, z + 1)};
- const auto& back{get_outside_block(x, y, z - 1)};
- const auto& right{get_outside_block(x + 1, y, z)};
- const auto& left{get_outside_block(x - 1, y, z)};
- const auto& up{get_outside_block(x, y + 1, z)};
- const auto& down{get_outside_block(x, y - 1, z)};
-
- std::vector<glface> glfaces;
- glfaces.reserve(6 * 6);
-
- const auto should_draw_face = [&bv](const auto& other) -> bool {
- const auto ov = shared::world::block::get_visibility(other);
- if (bv == shared::world::block::visibility::translucent &&
- ov == shared::world::block::visibility::translucent) {
- return false;
- }
- return ov != shared::world::block::visibility::solid;
- };
- // Special shrub block case, ugly I know.
- if (block.type == shared::world::block::type::shrub ||
- block.type == shared::world::block::type::dead_shrub ||
- block.type == shared::world::block::type::snowy_shrub) {
- static const auto front_shrub =
- make_glfaces({.translate = {0.0f, 0.0f, 0.0f},
- .rotate_degrees = 45.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 0.0f}});
- static const auto right_shrub =
- make_glfaces({.translate = {0.0f, 0.0f, 0.0f},
- .rotate_degrees = 135.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 0.0f}});
- static const auto back_shrub =
- make_glfaces({.translate = {0.0f, 0.0f, 0.0f},
- .rotate_degrees = 225.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 0.0f}});
- static const auto left_shrub =
- make_glfaces({.translate = {0.0f, 0.0f, 0.0f},
- .rotate_degrees = 315.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 0.0f}});
-
- std::ranges::copy(front_shrub, std::back_inserter(glfaces));
- std::ranges::copy(right_shrub, std::back_inserter(glfaces));
- std::ranges::copy(back_shrub, std::back_inserter(glfaces));
- std::ranges::copy(left_shrub, std::back_inserter(glfaces));
-
- } else {
- if (should_draw_face(front)) {
- static const auto front_faces = make_glfaces(
- {.translate = {0.0f, 0.0f, 0.5f},
- .rotate_degrees = 0.0f,
- .rotate_axis = {0.0f, 0.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 0.0f}});
- std::ranges::copy(front_faces,
- std::back_inserter(glfaces));
- }
- if (should_draw_face(right)) {
- static const auto right_faces = make_glfaces(
- {.translate = {0.5f, 0.0f, 0.0f},
- .rotate_degrees = 90.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 1.0f}});
- std::ranges::copy(right_faces,
- std::back_inserter(glfaces));
- }
- if (should_draw_face(back)) {
- static const auto back_faces = make_glfaces(
- {.translate = {0.0f, 0.0f, -0.5f},
- .rotate_degrees = 180.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 2.0f}});
- std::ranges::copy(back_faces,
- std::back_inserter(glfaces));
- }
- if (should_draw_face(left)) {
- static const auto left_faces = make_glfaces(
- {.translate = {-0.5f, 0.0f, 0.0f},
- .rotate_degrees = 270.0f,
- .rotate_axis = {0.0f, 1.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 3.0f}});
- std::ranges::copy(left_faces,
- std::back_inserter(glfaces));
- }
- if (should_draw_face(up)) {
- static const auto up_faces = make_glfaces(
- {.translate = {0.0f, 0.5f, 0.0f},
- .rotate_degrees = -90.0f,
- .rotate_axis = {1.0f, 0.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 4.0f}});
- std::ranges::copy(up_faces,
- std::back_inserter(glfaces));
- }
- if (should_draw_face(down)) {
- static const auto down_faces = make_glfaces(
- {.translate = {0.0f, -0.5f, 0.0f},
- .rotate_degrees = 90.0f,
- .rotate_axis = {1.0f, 0.0f, 0.0f},
- .texture_offset = {0.0f, 0.0f, 5.0f}});
- std::ranges::copy(down_faces,
- std::back_inserter(glfaces));
- }
- }
-
- // 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 = [&, atlas_width = std::ref(atlas_width),
- atlas_height =
- std::ref(atlas_height)](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(glfaces, 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(glface),
- 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(glface), nullptr);
- // texture
- glEnableVertexAttribArray(1);
- glVertexAttribPointer(1, sizeof(glm::vec3) / sizeof(float), GL_FLOAT,
- GL_FALSE, sizeof(glface),
- 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);
-}
-
-void chunk::draw(const chunk::map& chunks, const shared::player& lp,
- const pass& pass) noexcept {
- if (!this->glo.has_value() || this->should_regenerate_vbo) {
- if (!maybe_regenerate_glo(chunks)) {
- return;
- }
- this->should_regenerate_vbo = false;
- }
-
- const auto [world_x, world_z] = [&lp, this]() -> std::pair<float, float> {
- const float offset_x = static_cast<float>(this->pos.x - lp.chunk_pos.x);
- const float offset_z = static_cast<float>(this->pos.z - lp.chunk_pos.z);
- return {offset_x * chunk::WIDTH, offset_z * chunk::WIDTH};
- }();
-
- if (!is_chunk_visible(world_x, world_z)) {
- return;
- }
-
- render(world_x, world_z, pass);
-}
-
-} // namespace world
-} // namespace client