diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
| commit | e4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch) | |
| tree | ed58c3c246e3af1af337697695d780aa31f6ad9a /src/client/world.cc | |
| parent | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff) | |
Update to most recent version (old initial commit)
Diffstat (limited to 'src/client/world.cc')
| -rw-r--r-- | src/client/world.cc | 429 |
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 |
