#include "Chunks.hpp" #include #include #include #include #include "data/StructLayout.hpp" #include "coders/byte_utils.hpp" #include "content/Content.hpp" #include "files/WorldFiles.hpp" #include "graphics/core/Mesh.hpp" #include "lighting/Lightmap.hpp" #include "maths/aabb.hpp" #include "maths/rays.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" #include "world/Level.hpp" #include "world/LevelEvents.hpp" #include "VoxelsVolume.hpp" #include "blocks_agent.hpp" Chunks::Chunks( int32_t w, int32_t d, int32_t ox, int32_t oz, LevelEvents* events, const ContentIndices* indices ) : events(events), indices(indices), areaMap(w, d) { areaMap.setCenter(ox-w/2, oz-d/2); areaMap.setOutCallback([this](int, int, const auto& chunk) { this->events->trigger(EVT_CHUNK_HIDDEN, chunk.get()); }); } void Chunks::configure(int32_t x, int32_t z, uint32_t radius) { setCenter(x, z); uint32_t diameter = radius * 2LL + 1; if (getWidth() != diameter) { resize(diameter, diameter); } } voxel* Chunks::get(int32_t x, int32_t y, int32_t z) const { return blocks_agent::get(*this, x, y, z); } voxel& Chunks::require(int32_t x, int32_t y, int32_t z) const { return blocks_agent::require(*this, x, y, z); } const AABB* Chunks::isObstacleAt(float x, float y, float z) const { int ix = std::floor(x); int iy = std::floor(y); int iz = std::floor(z); voxel* v = get(ix, iy, iz); if (v == nullptr) { if (iy >= CHUNK_H) { return nullptr; } else { static const AABB empty; return ∅ } } const auto& def = indices->blocks.require(v->id); if (def.obstacle) { glm::ivec3 offset {}; if (v->state.segment) { glm::ivec3 point(ix, iy, iz); offset = seekOrigin(point, def, v->state) - point; } const auto& boxes = def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; for (const auto& hitbox : boxes) { if (hitbox.contains( {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} )) { return &hitbox; } } } return nullptr; } bool Chunks::isSolidBlock(int32_t x, int32_t y, int32_t z) { return blocks_agent::is_solid_at(*this, x, y, z); } bool Chunks::isReplaceableBlock(int32_t x, int32_t y, int32_t z) { return blocks_agent::is_replaceable_at(*this, x, y, z); } bool Chunks::isObstacleBlock(int32_t x, int32_t y, int32_t z) { voxel* v = get(x, y, z); if (v == nullptr) return false; return indices->blocks.require(v->id).obstacle; } ubyte Chunks::getLight(int32_t x, int32_t y, int32_t z, int channel) const { if (y < 0 || y >= CHUNK_H) { return 0; } int cx = floordiv(x); int cz = floordiv(z); auto ptr = areaMap.getIf(cx, cz); if (ptr == nullptr) { return 0; } Chunk* chunk = ptr->get(); if (chunk == nullptr) { return 0; } int lx = x - cx * CHUNK_W; int lz = z - cz * CHUNK_D; return chunk->lightmap.get(lx, y, lz, channel); } light_t Chunks::getLight(int32_t x, int32_t y, int32_t z) const { if (y < 0 || y >= CHUNK_H) { return 0; } int cx = floordiv(x); int cz = floordiv(z); auto ptr = areaMap.getIf(cx, cz); if (ptr == nullptr) { return 0; } Chunk* chunk = ptr->get(); if (chunk == nullptr) { return 0; } int lx = x - cx * CHUNK_W; int lz = z - cz * CHUNK_D; return chunk->lightmap.get(lx, y, lz); } Chunk* Chunks::getChunkByVoxel(int32_t x, int32_t y, int32_t z) const { if (y < 0 || y >= CHUNK_H) { return nullptr; } int cx = floordiv(x); int cz = floordiv(z); if (auto ptr = areaMap.getIf(cx, cz)) { return ptr->get(); } return nullptr; } Chunk* Chunks::getChunk(int x, int z) const { if (auto ptr = areaMap.getIf(x, z)) { return ptr->get(); } return nullptr; } glm::ivec3 Chunks::seekOrigin( const glm::ivec3& srcpos, const Block& def, blockstate state ) const { return blocks_agent::seek_origin(*this, srcpos, def, state); } void Chunks::eraseSegments( const Block& def, blockstate state, int x, int y, int z ) { blocks_agent::erase_segments(*this, def, state, x, y, z); } void Chunks::repairSegments( const Block& def, blockstate state, int x, int y, int z ) { blocks_agent::repair_segments(*this, def, state, x, y, z); } bool Chunks::checkReplaceability( const Block& def, blockstate state, const glm::ivec3& origin, blockid_t ignore ) { return blocks_agent::check_replaceability(*this, def, state, origin, ignore); } void Chunks::setRotationExtended( const Block& def, blockstate state, const glm::ivec3& origin, uint8_t index ) { blocks_agent::set_rotation_extended(*this, def, state, origin, index); } void Chunks::setRotation(int32_t x, int32_t y, int32_t z, uint8_t index) { return blocks_agent::set_rotation(*this, x, y, z, index); } void Chunks::set( int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state ) { blocks_agent::set(*this, x, y, z, id, state); } voxel* Chunks::rayCast( const glm::vec3& start, const glm::vec3& dir, float maxDist, glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, std::set filter ) const { return blocks_agent::raycast( *this, start, dir, maxDist, end, norm, iend, std::move(filter) ); } glm::vec3 Chunks::rayCastToObstacle( const glm::vec3& start, const glm::vec3& dir, float maxDist ) const { const float px = start.x; const float py = start.y; const float pz = start.z; float dx = dir.x; float dy = dir.y; float dz = dir.z; float t = 0.0f; int ix = floor(px); int iy = floor(py); int iz = floor(pz); int stepx = (dx > 0.0f) ? 1 : -1; int stepy = (dy > 0.0f) ? 1 : -1; int stepz = (dz > 0.0f) ? 1 : -1; constexpr float infinity = std::numeric_limits::infinity(); constexpr float epsilon = 1e-6f; // 0.000001 float txDelta = (std::fabs(dx) < epsilon) ? infinity : std::fabs(1.0f / dx); float tyDelta = (std::fabs(dy) < epsilon) ? infinity : std::fabs(1.0f / dy); float tzDelta = (std::fabs(dz) < epsilon) ? infinity : std::fabs(1.0f / dz); float xdist = (stepx > 0) ? (ix + 1 - px) : (px - ix); float ydist = (stepy > 0) ? (iy + 1 - py) : (py - iy); float zdist = (stepz > 0) ? (iz + 1 - pz) : (pz - iz); float txMax = (txDelta < infinity) ? txDelta * xdist : infinity; float tyMax = (tyDelta < infinity) ? tyDelta * ydist : infinity; float tzMax = (tzDelta < infinity) ? tzDelta * zdist : infinity; while (t <= maxDist) { voxel* voxel = get(ix, iy, iz); if (voxel) { const auto& def = indices->blocks.require(voxel->id); if (def.obstacle) { if (!def.rt.solid) { const std::vector& hitboxes = def.rt.hitboxes[voxel->state.rotation]; scalar_t distance; glm::ivec3 norm; Ray ray(start, dir); glm::ivec3 offset {}; if (voxel->state.segment) { offset = seekOrigin({ix, iy, iz}, def, voxel->state) - glm::ivec3(ix, iy, iz); } for (const auto& box : hitboxes) { // norm is dummy now, can be inefficient if (ray.intersectAABB( glm::ivec3(ix, iy, iz) + offset, box, maxDist, norm, distance ) > RayRelation::None) { return start + (dir * glm::vec3(distance)); } } } else { return glm::vec3(px + t * dx, py + t * dy, pz + t * dz); } } } if (txMax < tyMax) { if (txMax < tzMax) { ix += stepx; t = txMax; txMax += txDelta; } else { iz += stepz; t = tzMax; tzMax += tzDelta; } } else { if (tyMax < tzMax) { iy += stepy; t = tyMax; tyMax += tyDelta; } else { iz += stepz; t = tzMax; tzMax += tzDelta; } } } return glm::vec3(px + maxDist * dx, py + maxDist * dy, pz + maxDist * dz); } void Chunks::setCenter(int32_t x, int32_t z) { areaMap.setCenter(floordiv(x, CHUNK_W), floordiv(z, CHUNK_D)); } void Chunks::resize(uint32_t newW, uint32_t newD) { areaMap.resize(newW, newD); } bool Chunks::putChunk(const std::shared_ptr& chunk) { if (areaMap.set(chunk->x, chunk->z, chunk)) { if (events) { events->trigger(LevelEventType::EVT_CHUNK_SHOWN, chunk.get()); } return true; } return false; } // reduce nesting on next modification // 25.06.2024: not now // 11.11.2024: not now void Chunks::getVoxels(VoxelsVolume* volume, bool backlight) const { voxel* voxels = volume->getVoxels(); light_t* lights = volume->getLights(); int x = volume->getX(); int y = volume->getY(); int z = volume->getZ(); int w = volume->getW(); int h = volume->getH(); int d = volume->getD(); int scx = floordiv(x); int scz = floordiv(z); int ecx = floordiv(x + w); int ecz = floordiv(z + d); int cw = ecx - scx + 1; int cd = ecz - scz + 1; // cw*cd chunks will be scanned for (int cz = scz; cz < scz + cd; cz++) { for (int cx = scx; cx < scx + cw; cx++) { const auto chunk = getChunk(cx, cz); if (chunk == nullptr) { // no chunk loaded -> filling with BLOCK_VOID for (int ly = y; ly < y + h; ly++) { for (int lz = std::max(z, cz * CHUNK_D); lz < std::min(z + d, (cz + 1) * CHUNK_D); lz++) { for (int lx = std::max(x, cx * CHUNK_W); lx < std::min(x + w, (cx + 1) * CHUNK_W); lx++) { uint idx = vox_index(lx - x, ly - y, lz - z, w, d); voxels[idx].id = BLOCK_VOID; lights[idx] = 0; } } } } else { const voxel* cvoxels = chunk->voxels; const light_t* clights = chunk->lightmap.getLights(); for (int ly = y; ly < y + h; ly++) { for (int lz = std::max(z, cz * CHUNK_D); lz < std::min(z + d, (cz + 1) * CHUNK_D); lz++) { for (int lx = std::max(x, cx * CHUNK_W); lx < std::min(x + w, (cx + 1) * CHUNK_W); lx++) { uint vidx = vox_index(lx - x, ly - y, lz - z, w, d); uint cidx = vox_index( lx - cx * CHUNK_W, ly, lz - cz * CHUNK_D, CHUNK_W, CHUNK_D ); voxels[vidx] = cvoxels[cidx]; light_t light = clights[cidx]; if (backlight) { const auto block = indices->blocks.get(voxels[vidx].id); if (block && block->lightPassing) { light = Lightmap::combine( std::min(15, Lightmap::extract(light, 0) + 1), std::min(15, Lightmap::extract(light, 1) + 1), std::min(15, Lightmap::extract(light, 2) + 1), std::min(15, static_cast(Lightmap::extract(light, 3))) ); } } lights[vidx] = light; } } } } } } } void Chunks::saveAndClear() { areaMap.clear(); }