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 | |
| parent | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff) | |
Update to most recent version (old initial commit)
Diffstat (limited to 'src/client/world')
| -rw-r--r-- | src/client/world/block.cc | 117 | ||||
| -rw-r--r-- | src/client/world/block.hh | 53 | ||||
| -rw-r--r-- | src/client/world/chunk.cc | 255 | ||||
| -rw-r--r-- | src/client/world/chunk.hh | 82 | ||||
| -rw-r--r-- | src/client/world/world.dat | bin | 0 -> 20480 bytes |
5 files changed, 507 insertions, 0 deletions
diff --git a/src/client/world/block.cc b/src/client/world/block.cc new file mode 100644 index 0000000..df3ddcd --- /dev/null +++ b/src/client/world/block.cc @@ -0,0 +1,117 @@ +#include "client/world/block.hh" + +namespace client { +namespace world { + +struct glvert_args { + glm::vec3 translate; + float rotate_degrees; + glm::vec3 rotate_axis; + glm::vec3 texture_offset; +}; +static block::glface_t make_glface(const glvert_args& args) noexcept { + static constexpr block::glface_t glverts = { + block::glvert{{-0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + block::glvert{{0.5f, -0.5f, 0.0f}, {1.0f, 1.0f, 0.0f}}, + block::glvert{{0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + block::glvert{{0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + block::glvert{{-0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 0.0f}}, + block::glvert{{-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. + constexpr auto zero = glm::vec3{}; + const glm::mat4 mtranslate = + args.translate == zero + ? glm::mat4{1.0f} + : glm::translate(glm::mat4{1.0f}, args.translate); + const glm::mat4 mrotate = + args.rotate_axis == zero + ? glm::mat4{1.0f} + : glm::rotate(glm::mat4{1.0f}, glm::radians(args.rotate_degrees), + args.rotate_axis); + + block::glface_t ret; + std::ranges::transform(glverts, std::begin(ret), [&](auto f) { + f.vertice = + glm::vec3(mtranslate * mrotate * glm::vec4{f.vertice, 1.0f}); + f.texture += args.texture_offset; + return f; + }); + return ret; +} + +static const block::glfaces_t& get_shrub_faces() noexcept { + static block::glfaces_t faces{ + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}})}; + return faces; +} + +static const block::glfaces_t& get_cube_faces() noexcept { + static block::glfaces_t faces{ + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}}), + make_glface({.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}}), + }; + return faces; +} + +enum block::draw_type +block::get_draw_type(const enum block::type& type) noexcept { + using t = enum shared::world::block::type; + switch (type) { + case t::dead_shrub: + case t::shrub: + case t::snowy_shrub: + return draw_type::custom; + default: + return draw_type::block; + } +} + +const block::glfaces_t& +block::get_glfaces(const enum block::type& type) noexcept { + const auto draw_type = get_draw_type(type); + if (draw_type == block::draw_type::custom) { + return get_shrub_faces(); + } + return get_cube_faces(); +} + +} // namespace world +} // namespace client diff --git a/src/client/world/block.hh b/src/client/world/block.hh new file mode 100644 index 0000000..9dfe303 --- /dev/null +++ b/src/client/world/block.hh @@ -0,0 +1,53 @@ +#ifndef CLIENT_WORLD_BLOCK_HH_ +#define CLIENT_WORLD_BLOCK_HH_ + +#include <algorithm> +#include <array> +#include <vector> + +#include <glm/glm.hpp> +#include <glm/gtc/matrix_access.hpp> +#include <glm/gtc/matrix_transform.hpp> + +#include "shared/world/block.hh" + +namespace client { +namespace world { + +// Doesn't add any data, just information for rendering. +class block : public shared::world::block { +public: + struct glvert { + glm::vec3 vertice; + glm::vec3 texture; + }; + using glface_t = std::array<glvert, 6>; // array of verts (a face) + using glfaces_t = std::vector<glface_t>; // vector of faces (a block) + + // Render types refer to how the block should be culled when making the vbo. + enum class draw_type { + block, // face testing + custom, // no testing + }; + +public: + static enum draw_type get_draw_type(const enum block::type& type) noexcept; + static const glfaces_t& get_glfaces(const enum block::type& type) noexcept; + +public: + template <typename... Args> + block(Args&&... args) noexcept : block(std::forward<Args>(args)...) {} + +public: + enum draw_type get_draw_type() const noexcept { + return get_draw_type(this->type); + } + const glfaces_t& get_glfaces() const noexcept { + return get_glfaces(this->type); + } +}; + +} // namespace world +} // namespace client + +#endif 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 diff --git a/src/client/world/chunk.hh b/src/client/world/chunk.hh new file mode 100644 index 0000000..5436525 --- /dev/null +++ b/src/client/world/chunk.hh @@ -0,0 +1,82 @@ +#ifndef CLIENT_WORLD_CHUNK_HH_ +#define CLIENT_WORLD_CHUNK_HH_ + +#include <algorithm> +#include <optional> +#include <ranges> +#include <unordered_map> +#include <vector> + +#include "client/render/render.hh" +#include "client/render/texture.hh" +#include "client/world/block.hh" +#include "shared/entity/player.hh" +#include "shared/world/chunk.hh" + +namespace client { +namespace world { + +class chunk; +using chunks_t = std::unordered_map<shared::math::coords, + std::optional<client::world::chunk>, + decltype(&shared::world::chunk::hash), + decltype(&shared::world::chunk::equal)>; + +// client::world::chunk is a renderable shared::world::chunk. +class chunk : public shared::world::chunk { +public: + // Which part to draw when we call draw. + enum class pass { solid, water }; + +public: + bool should_regenerate_vbo; + +private: + struct gl_objects { + unsigned long solid_elements; + GLuint solid_vbo; + GLuint solid_vao; + unsigned long water_elements; + GLuint water_vbo; + GLuint water_vao; + + gl_objects(const unsigned long se, const GLuint svbo, const GLuint svao, + const unsigned long we, const GLuint wvbo, const GLuint wvao) + : solid_elements(se), solid_vbo(svbo), solid_vao(svao), + water_elements(we), water_vbo(wvbo), water_vao(wvao) {} + gl_objects(const gl_objects&) = delete; + gl_objects(gl_objects&&) = delete; + ~gl_objects() { + glDeleteBuffers(1, &solid_vbo); + glDeleteVertexArrays(1, &solid_vao); + glDeleteBuffers(1, &water_vbo); + glDeleteVertexArrays(1, &water_vao); + } + }; + std::optional<gl_objects> glo; + +private: + const chunk* get_neighbour(const chunks_t& chunks, + shared::math::coords offset) const noexcept; + + void render(const float x_offset, const float z_offset, + const pass& pass) noexcept; + bool maybe_regenerate_glo(const chunks_t& chunks) noexcept; + +public: + template <typename... Args> + chunk(Args&&... args) noexcept + : shared::world::chunk(std::forward<Args>(args)...) {} + virtual ~chunk() noexcept = default; + + // true if we regen'd, false otherwise + bool draw(const chunks_t& chunks, const shared::player& lp, + const pass& pass, const bool skip_regen = false) noexcept; + + bool can_draw() const noexcept { return this->glo.has_value(); } +}; + +} // namespace world +} // namespace client + +#endif diff --git a/src/client/world/world.dat b/src/client/world/world.dat Binary files differnew file mode 100644 index 0000000..e210d11 --- /dev/null +++ b/src/client/world/world.dat |
