diff options
| author | untodesu <kirill@untode.su> | 2025-12-11 15:14:26 +0500 |
|---|---|---|
| committer | untodesu <kirill@untode.su> | 2025-12-11 15:14:26 +0500 |
| commit | f40d09cb8f712e87691af4912f3630d92d692779 (patch) | |
| tree | 7ac3a4168ff722689372fd489c6f94d0a2546e8f /src/game/client/world/chunk_mesher.cc | |
| parent | 8bcbd2729388edc63c82d77d314b583af1447c49 (diff) | |
| download | voxelius-f40d09cb8f712e87691af4912f3630d92d692779.tar.bz2 voxelius-f40d09cb8f712e87691af4912f3630d92d692779.zip | |
Shuffle stuff around
- Use the new and improved hierarchy I figured out when making Prospero chat
- Re-add NSIS scripts, again from Prospero
- Update most build and utility scripts with their most recent versions
Diffstat (limited to 'src/game/client/world/chunk_mesher.cc')
| -rw-r--r-- | src/game/client/world/chunk_mesher.cc | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/game/client/world/chunk_mesher.cc b/src/game/client/world/chunk_mesher.cc new file mode 100644 index 0000000..5e58760 --- /dev/null +++ b/src/game/client/world/chunk_mesher.cc @@ -0,0 +1,450 @@ +#include "client/pch.hh" + +#include "client/world/chunk_mesher.hh" + +#include "core/math/crc64.hh" + +#include "core/threading.hh" + +#include "shared/world/chunk.hh" +#include "shared/world/dimension.hh" +#include "shared/world/voxel.hh" +#include "shared/world/voxel_registry.hh" + +#include "shared/coord.hh" + +#include "client/world/chunk_quad.hh" +#include "client/world/voxel_atlas.hh" + +#include "client/globals.hh" +#include "client/session.hh" + +using QuadBuilder = std::vector<world::ChunkQuad>; + +using CachedChunkCoord = unsigned short; +constexpr static CachedChunkCoord CPOS_ITSELF = 0x0000; +constexpr static CachedChunkCoord CPOS_NORTH = 0x0001; +constexpr static CachedChunkCoord CPOS_SOUTH = 0x0002; +constexpr static CachedChunkCoord CPOS_EAST = 0x0003; +constexpr static CachedChunkCoord CPOS_WEST = 0x0004; +constexpr static CachedChunkCoord CPOS_TOP = 0x0005; +constexpr static CachedChunkCoord CPOS_BOTTOM = 0x0006; +constexpr static const size_t NUM_CACHED_CPOS = 7; + +static const CachedChunkCoord get_cached_cpos(const chunk_pos& pivot, const chunk_pos& cpos) +{ + static const CachedChunkCoord nx[3] = { CPOS_WEST, 0, CPOS_EAST }; + static const CachedChunkCoord ny[3] = { CPOS_BOTTOM, 0, CPOS_TOP }; + static const CachedChunkCoord nz[3] = { CPOS_NORTH, 0, CPOS_SOUTH }; + + if(pivot != cpos) { + chunk_pos delta = pivot - cpos; + delta[0] = glm::clamp<chunk_pos::value_type>(delta[0], -1, 1); + delta[1] = glm::clamp<chunk_pos::value_type>(delta[1], -1, 1); + delta[2] = glm::clamp<chunk_pos::value_type>(delta[2], -1, 1); + + if(delta[0]) { + return nx[delta[0] + 1]; + } + else if(delta[1]) { + return ny[delta[1] + 1]; + } + else { + return nz[delta[2] + 1]; + } + } + + return CPOS_ITSELF; +} + +class GL_MeshingTask final : public Task { +public: + explicit GL_MeshingTask(entt::entity entity, const chunk_pos& cpos); + virtual ~GL_MeshingTask(void) = default; + virtual void process(void) override; + virtual void finalize(void) override; + +private: + bool vis_test(const world::Voxel* voxel, const local_pos& lpos) const; + void push_quad_a(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face); + void push_quad_v(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face, std::size_t entropy); + void make_cube(const world::Voxel* voxel, const local_pos& lpos, world::VoxelVisBits vis, std::size_t entropy); + void cache_chunk(const chunk_pos& cpos); + +private: + std::array<world::VoxelStorage, NUM_CACHED_CPOS> m_cache; + std::vector<QuadBuilder> m_quads_b; // blending + std::vector<QuadBuilder> m_quads_s; // solid + entt::entity m_entity; + chunk_pos m_cpos; +}; + +GL_MeshingTask::GL_MeshingTask(entt::entity entity, const chunk_pos& cpos) +{ + m_entity = entity; + m_cpos = cpos; + + cache_chunk(m_cpos); + cache_chunk(m_cpos + DIR_NORTH<chunk_pos::value_type>); + cache_chunk(m_cpos + DIR_SOUTH<chunk_pos::value_type>); + cache_chunk(m_cpos + DIR_EAST<chunk_pos::value_type>); + cache_chunk(m_cpos + DIR_WEST<chunk_pos::value_type>); + cache_chunk(m_cpos + DIR_DOWN<chunk_pos::value_type>); + cache_chunk(m_cpos + DIR_UP<chunk_pos::value_type>); +} + +void GL_MeshingTask::process(void) +{ + m_quads_b.resize(world::voxel_atlas::plane_count()); + m_quads_s.resize(world::voxel_atlas::plane_count()); + + const auto& voxels = m_cache.at(CPOS_ITSELF); + + for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { + if(m_status == task_status::CANCELLED) { + m_quads_b.clear(); + m_quads_s.clear(); + return; + } + + const auto lpos = coord::to_local(i); + const auto voxel = world::voxel_registry::find(voxels[i]); + + if(voxel == nullptr) { + // Either a NULL_VOXEL_ID or something went + // horribly wrong and we don't what this is + continue; + } + + unsigned int vis = 0U; + + if(vis_test(voxel, lpos + DIR_NORTH<local_pos::value_type>)) { + vis |= world::VVIS_NORTH; + } + + if(vis_test(voxel, lpos + DIR_SOUTH<local_pos::value_type>)) { + vis |= world::VVIS_SOUTH; + } + + if(vis_test(voxel, lpos + DIR_EAST<local_pos::value_type>)) { + vis |= world::VVIS_EAST; + } + + if(vis_test(voxel, lpos + DIR_WEST<local_pos::value_type>)) { + vis |= world::VVIS_WEST; + } + + if(vis_test(voxel, lpos + DIR_UP<local_pos::value_type>)) { + vis |= world::VVIS_UP; + } + + if(vis_test(voxel, lpos + DIR_DOWN<local_pos::value_type>)) { + vis |= world::VVIS_DOWN; + } + + const auto vpos = coord::to_voxel(m_cpos, lpos); + const auto entropy_src = vpos[0] * vpos[1] * vpos[2]; + const auto entropy = math::crc64(&entropy_src, sizeof(entropy_src)); + + // FIXME: handle different voxel types + make_cube(voxel, lpos, world::VoxelVisBits(vis), entropy); + } +} + +void GL_MeshingTask::finalize(void) +{ + if(!globals::dimension || !globals::dimension->chunks.valid(m_entity)) { + // We either disconnected or something + // else happened that invalidated the entity + return; + } + + auto& component = globals::dimension->chunks.emplace_or_replace<world::ChunkMesh>(m_entity); + + const std::size_t plane_count_nb = m_quads_s.size(); + const std::size_t plane_count_b = m_quads_b.size(); + + bool has_no_submeshes_b = true; + bool has_no_submeshes_nb = true; + + component.quad_nb.resize(plane_count_nb); + component.quad_b.resize(plane_count_b); + + for(std::size_t plane = 0; plane < plane_count_nb; ++plane) { + auto& builder = m_quads_s[plane]; + auto& buffer = component.quad_nb[plane]; + + if(builder.empty()) { + if(buffer.handle) { + glDeleteBuffers(1, &buffer.handle); + buffer.handle = 0; + buffer.size = 0; + } + } + else { + if(!buffer.handle) { + glGenBuffers(1, &buffer.handle); + } + + glBindBuffer(GL_ARRAY_BUFFER, buffer.handle); + glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW); + buffer.size = builder.size(); + has_no_submeshes_nb = false; + } + } + + for(std::size_t plane = 0; plane < plane_count_b; ++plane) { + auto& builder = m_quads_b[plane]; + auto& buffer = component.quad_b[plane]; + + if(builder.empty()) { + if(buffer.handle) { + glDeleteBuffers(1, &buffer.handle); + buffer.handle = 0; + buffer.size = 0; + } + } + else { + if(!buffer.handle) { + glGenBuffers(1, &buffer.handle); + } + + glBindBuffer(GL_ARRAY_BUFFER, buffer.handle); + glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW); + buffer.size = builder.size(); + has_no_submeshes_b = false; + } + } + + if(has_no_submeshes_b && has_no_submeshes_nb) { + globals::dimension->chunks.remove<world::ChunkMesh>(m_entity); + } +} + +bool GL_MeshingTask::vis_test(const world::Voxel* voxel, const local_pos& lpos) const +{ + const auto pvpos = coord::to_voxel(m_cpos, lpos); + const auto pcpos = coord::to_chunk(pvpos); + const auto plpos = coord::to_local(pvpos); + const auto index = coord::to_index(plpos); + + const auto cached_cpos = get_cached_cpos(m_cpos, pcpos); + const auto& voxels = m_cache.at(cached_cpos); + const auto neighbour = world::voxel_registry::find(voxels[index]); + + bool result; + + if(neighbour == nullptr) { + result = true; + } + else if(neighbour == voxel) { + result = false; + } + else if(neighbour->get_render_mode() != voxel->get_render_mode()) { + result = true; + } + else { + result = false; + } + + return result; +} + +void GL_MeshingTask::push_quad_a(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face) +{ + auto cached_offset = voxel->get_cached_face_offset(face); + auto cached_plane = voxel->get_cached_face_plane(face); + auto& textures = voxel->get_face_textures(face); + + switch(voxel->get_render_mode()) { + case world::VRENDER_OPAQUE: + m_quads_s[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset, textures.size())); + break; + + case world::VRENDER_BLEND: + m_quads_b[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset, textures.size())); + break; + } +} + +void GL_MeshingTask::push_quad_v(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face, + std::size_t entropy) +{ + auto cached_offset = voxel->get_cached_face_offset(face); + auto cached_plane = voxel->get_cached_face_plane(face); + auto& textures = voxel->get_face_textures(face); + auto index = entropy % textures.size(); + + switch(voxel->get_render_mode()) { + case world::VRENDER_OPAQUE: + m_quads_s[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset + index, 0)); + break; + + case world::VRENDER_BLEND: + m_quads_b[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset + index, 0)); + break; + } +} + +void GL_MeshingTask::make_cube(const world::Voxel* voxel, const local_pos& lpos, world::VoxelVisBits vis, std::size_t entropy) +{ + const glm::fvec3 fpos = glm::fvec3(lpos); + const glm::fvec2 fsize = glm::fvec2(1.0f, 1.0f); + + if(voxel->is_animated()) { + if(vis & world::VVIS_NORTH) { + push_quad_a(voxel, fpos, fsize, world::VFACE_NORTH); + } + + if(vis & world::VVIS_SOUTH) { + push_quad_a(voxel, fpos, fsize, world::VFACE_SOUTH); + } + + if(vis & world::VVIS_EAST) { + push_quad_a(voxel, fpos, fsize, world::VFACE_EAST); + } + + if(vis & world::VVIS_WEST) { + push_quad_a(voxel, fpos, fsize, world::VFACE_WEST); + } + + if(vis & world::VVIS_UP) { + push_quad_a(voxel, fpos, fsize, world::VFACE_TOP); + } + + if(vis & world::VVIS_DOWN) { + push_quad_a(voxel, fpos, fsize, world::VFACE_BOTTOM); + } + } + else { + if(vis & world::VVIS_NORTH) { + push_quad_v(voxel, fpos, fsize, world::VFACE_NORTH, entropy); + } + + if(vis & world::VVIS_SOUTH) { + push_quad_v(voxel, fpos, fsize, world::VFACE_SOUTH, entropy); + } + + if(vis & world::VVIS_EAST) { + push_quad_v(voxel, fpos, fsize, world::VFACE_EAST, entropy); + } + + if(vis & world::VVIS_WEST) { + push_quad_v(voxel, fpos, fsize, world::VFACE_WEST, entropy); + } + + if(vis & world::VVIS_UP) { + push_quad_v(voxel, fpos, fsize, world::VFACE_TOP, entropy); + } + + if(vis & world::VVIS_DOWN) { + push_quad_v(voxel, fpos, fsize, world::VFACE_BOTTOM, entropy); + } + } +} + +void GL_MeshingTask::cache_chunk(const chunk_pos& cpos) +{ + const auto index = get_cached_cpos(m_cpos, cpos); + + if(const auto chunk = globals::dimension->find_chunk(cpos)) { + m_cache[index] = chunk->get_voxels(); + return; + } +} + +// Bogus internal flag component +struct NeedsMeshingComponent final {}; + +static void on_chunk_create(const world::ChunkCreateEvent& event) +{ + const std::array<chunk_pos, 6> neighbours = { + event.cpos + DIR_NORTH<chunk_pos::value_type>, + event.cpos + DIR_SOUTH<chunk_pos::value_type>, + event.cpos + DIR_EAST<chunk_pos::value_type>, + event.cpos + DIR_WEST<chunk_pos::value_type>, + event.cpos + DIR_UP<chunk_pos::value_type>, + event.cpos + DIR_DOWN<chunk_pos::value_type>, + }; + + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); + + for(const chunk_pos& cpos : neighbours) { + if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); + continue; + } + } +} + +static void on_chunk_update(const world::ChunkUpdateEvent& event) +{ + const std::array<chunk_pos, 6> neighbours = { + event.cpos + DIR_NORTH<chunk_pos::value_type>, + event.cpos + DIR_SOUTH<chunk_pos::value_type>, + event.cpos + DIR_EAST<chunk_pos::value_type>, + event.cpos + DIR_WEST<chunk_pos::value_type>, + event.cpos + DIR_UP<chunk_pos::value_type>, + event.cpos + DIR_DOWN<chunk_pos::value_type>, + }; + + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); + + for(const chunk_pos& cpos : neighbours) { + if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); + continue; + } + } +} + +static void on_voxel_set(const world::VoxelSetEvent& event) +{ + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); + + std::vector<chunk_pos> neighbours; + + for(int dim = 0; dim < 3; dim += 1) { + chunk_pos offset = chunk_pos(0, 0, 0); + offset[dim] = 1; + + if(event.lpos[dim] == 0) { + neighbours.push_back(event.cpos - offset); + continue; + } + + if(event.lpos[dim] == (CHUNK_SIZE - 1)) { + neighbours.push_back(event.cpos + offset); + continue; + } + } + + for(const chunk_pos& cpos : neighbours) { + if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { + globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); + continue; + } + } +} + +void world::chunk_mesher::init(void) +{ + globals::dispatcher.sink<ChunkCreateEvent>().connect<&on_chunk_create>(); + globals::dispatcher.sink<ChunkUpdateEvent>().connect<&on_chunk_update>(); + globals::dispatcher.sink<VoxelSetEvent>().connect<&on_voxel_set>(); +} + +void world::chunk_mesher::shutdown(void) +{ +} + +void world::chunk_mesher::update(void) +{ + if(session::is_ingame()) { + const auto group = globals::dimension->chunks.group<NeedsMeshingComponent>(entt::get<ChunkComponent>); + for(const auto [entity, chunk] : group.each()) { + globals::dimension->chunks.remove<NeedsMeshingComponent>(entity); + threading::submit<GL_MeshingTask>(entity, chunk.cpos); + } + } +} |
