#pragma once /// blocks_agent is set of templates but not a class to minimize OOP overhead. #include "voxel.hpp" #include "Block.hpp" #include "Chunk.hpp" #include "Chunks.hpp" #include "VoxelsVolume.hpp" #include "GlobalChunks.hpp" #include "constants.hpp" #include "typedefs.hpp" #include "content/Content.hpp" #include "maths/voxmaths.hpp" #include #include #include #include #include struct AABB; namespace blocks_agent { /// @brief Get specified chunk. /// @tparam Storage /// @param chunks /// @param cx chunk grid position X /// @param cz chunk grid position Z /// @return chunk or nullptr if does not exists template inline Chunk* get_chunk(const Storage& chunks, int cx, int cz) { return chunks.getChunk(cx, cz); } /// @brief Get voxel at specified position. /// Returns nullptr if voxel does not exists. /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param x position X /// @param y position Y /// @param z position Z /// @return voxel pointer or nullptr template inline voxel* get(const Storage& chunks, int32_t x, int32_t y, int32_t z) { if (y < 0 || y >= CHUNK_H) { return nullptr; } int cx = floordiv(x); int cz = floordiv(z); Chunk* chunk = get_chunk(chunks, cx, cz); if (chunk == nullptr) { return nullptr; } int lx = x - cx * CHUNK_W; int lz = z - cz * CHUNK_D; return &chunk->voxels[(y * CHUNK_D + lz) * CHUNK_W + lx]; } /// @brief Get voxel at specified position. /// @throws std::runtime_error if voxel does not exists /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param x position X /// @param y position Y /// @param z position Z /// @return voxel reference template inline voxel& require(const Storage& chunks, int32_t x, int32_t y, int32_t z) { auto vox = get(chunks, x, y, z); if (vox == nullptr) { throw std::runtime_error("voxel does not exist"); } return *vox; } template inline const Block& get_block_def(const Storage& chunks, blockid_t id) { return chunks.getContentIndices().blocks.require(id); } /// @brief Check if block at specified position is solid. /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param x position X /// @param y position Y /// @param z position Z /// @return true if block exists and solid template inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { if (auto vox = get(chunks, x, y, z)) { return get_block_def(chunks, vox->id).rt.solid; } return false; } /// @brief Check if block at specified position is replaceable. /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param x position X /// @param y position Y /// @param z position Z /// @return true if block exists and replaceable template inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { if (auto vox = get(chunks, x, y, z)) { return get_block_def(chunks, vox->id).replaceable; } return false; } /// @brief Set block at specified position if voxel exists. /// @param chunks chunks matrix /// @param x block position X /// @param y block position Y /// @param z block position Z /// @param id new block id /// @param state new block state void set( Chunks& chunks, int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state ); /// @brief Set block at specified position if voxel exists. /// @param chunks chunks storage /// @param x block position X /// @param y block position Y /// @param z block position Z /// @param id new block id /// @param state new block state void set( GlobalChunks& chunks, int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state ); /// @brief Erase extended block segments /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param def origin block definition /// @param state origin block state /// @param x origin position X /// @param y origin position Y /// @param z origin position Z template inline void erase_segments( Storage& chunks, const Block& def, blockstate state, int x, int y, int z ) { const auto& rotation = def.rotations.variants[state.rotation]; for (int sy = 0; sy < def.size.y; sy++) { for (int sz = 0; sz < def.size.z; sz++) { for (int sx = 0; sx < def.size.x; sx++) { if ((sx | sy | sz) == 0) { continue; } glm::ivec3 pos(x, y, z); pos += rotation.axes[0] * sx; pos += rotation.axes[1] * sy; pos += rotation.axes[2] * sz; set(chunks, pos.x, pos.y, pos.z, 0, {}); } } } } /// @brief Convert segment offset to segment bits /// @param sx segment offset X /// @param sy segment offset Y /// @param sz segment offset Z /// @return segment bits static constexpr inline uint8_t segment_to_int(int sx, int sy, int sz) { return ((sx > 0) | ((sy > 0) << 1) | ((sz > 0) << 2)); } /// @brief Restore invalid extended block segments /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param def origin block definition /// @param state origin block state /// @param x origin position X /// @param y origin position Y /// @param z origin position Z template inline void repair_segments( Storage& chunks, const Block& def, blockstate state, int x, int y, int z ) { const auto& rotation = def.rotations.variants[state.rotation]; const auto id = def.rt.id; const auto size = def.size; for (int sy = 0; sy < size.y; sy++) { for (int sz = 0; sz < size.z; sz++) { for (int sx = 0; sx < size.x; sx++) { if ((sx | sy | sz) == 0) { continue; } blockstate segState = state; segState.segment = segment_to_int(sx, sy, sz); glm::ivec3 pos(x, y, z); pos += rotation.axes[0] * sx; pos += rotation.axes[1] * sy; pos += rotation.axes[2] * sz; set(chunks, pos.x, pos.y, pos.z, id, segState); } } } } /// @brief Get origin position for specified extended block. Returns srcpos /// if block is not extended. /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param srcpos block segment position /// @param def definition of the block at srcpos /// @param state state of the block at srcpos /// @return origin block position template inline glm::ivec3 seek_origin( Storage& chunks, const glm::ivec3& srcpos, const Block& def, blockstate state ) { auto pos = srcpos; const auto& rotation = def.rotations.variants[state.rotation]; auto segment = state.segment; while (true) { if (!segment) { return pos; } if (segment & 1) pos -= rotation.axes[0]; if (segment & 2) pos -= rotation.axes[1]; if (segment & 4) pos -= rotation.axes[2]; if (auto* voxel = get(chunks, pos.x, pos.y, pos.z)) { segment = voxel->state.segment; } else { return pos; } } } /// @brief Check blocks replaceability with specified block /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param def block definition /// @param state target block state /// @param origin target block origin /// @param ignore ignored block id /// @return true if specified area may be replaced with the block/extended block template inline bool check_replaceability( const Storage& chunks, const Block& def, blockstate state, const glm::ivec3& origin, blockid_t ignore ) { const auto& blocks = chunks.getContentIndices().blocks; const auto& rotation = def.rotations.variants[state.rotation]; const auto size = def.size; for (int sy = 0; sy < size.y; sy++) { for (int sz = 0; sz < size.z; sz++) { for (int sx = 0; sx < size.x; sx++) { auto pos = origin; pos += rotation.axes[0] * sx; pos += rotation.axes[1] * sy; pos += rotation.axes[2] * sz; if (auto vox = get(chunks, pos.x, pos.y, pos.z)) { auto& target = blocks.require(vox->id); if (!target.replaceable && vox->id != ignore) { return false; } } else { return false; } } } } return true; } /// @brief Set rotation to an extended block /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param def block definition /// @param state current block state /// @param origin extended block origin /// @param index target rotation index template inline void set_rotation_extended( Storage& chunks, const Block& def, blockstate state, const glm::ivec3& origin, uint8_t index ) { auto newstate = state; newstate.rotation = index; // unable to rotate block (cause: obstacles) if (!check_replaceability(chunks, def, newstate, origin, def.rt.id)) { return; } const auto& rotation = def.rotations.variants[index]; const auto size = def.size; std::vector segmentBlocks; for (int sy = 0; sy < size.y; sy++) { for (int sz = 0; sz < size.z; sz++) { for (int sx = 0; sx < size.x; sx++) { auto pos = origin; pos += rotation.axes[0] * sx; pos += rotation.axes[1] * sy; pos += rotation.axes[2] * sz; blockstate segState = newstate; segState.segment = segment_to_int(sx, sy, sz); auto vox = get(chunks, pos.x, pos.y, pos.z); // checked for nullptr by checkReplaceability if (vox->id != def.rt.id) { set(chunks, pos.x, pos.y, pos.z, def.rt.id, segState); } else { vox->state = segState; int cx = floordiv(pos.x); int cz = floordiv(pos.z); auto chunk = get_chunk(chunks, cx, cz); assert(chunk != nullptr); chunk->setModifiedAndUnsaved(); segmentBlocks.emplace_back(pos); } } } } const auto& prevRotation = def.rotations.variants[state.rotation]; for (int sy = 0; sy < size.y; sy++) { for (int sz = 0; sz < size.z; sz++) { for (int sx = 0; sx < size.x; sx++) { auto pos = origin; pos += prevRotation.axes[0] * sx; pos += prevRotation.axes[1] * sy; pos += prevRotation.axes[2] * sz; if (std::find( segmentBlocks.begin(), segmentBlocks.end(), pos ) == segmentBlocks.end()) { set(chunks, pos.x, pos.y, pos.z, 0, {}); } } } } } /// @brief Set block rotation /// @tparam Storage chunks storage class /// @param chunks chunks storage /// @param x block X position /// @param y block Y position /// @param z block Z position /// @param index target rotation index template inline void set_rotation( Storage& chunks, int32_t x, int32_t y, int32_t z, uint8_t index ) { if (index >= BlockRotProfile::MAX_COUNT) { return; } auto vox = get(chunks, x, y, z); if (vox == nullptr) { return; } const auto& def = chunks.getContentIndices().blocks.require(vox->id); if (!def.rotatable || vox->state.rotation == index) { return; } if (def.rt.extended) { auto origin = seek_origin(chunks, {x, y, z}, def, vox->state); vox = get(chunks, origin.x, origin.y, origin.z); set_rotation_extended(chunks, def, vox->state, origin, index); } else { vox->state.rotation = index; int cx = floordiv(x); int cz = floordiv(z); auto chunk = get_chunk(chunks, cx, cz); assert(chunk != nullptr); chunk->setModifiedAndUnsaved(); } } /// @brief Cast ray to a selectable block with filter based on id. /// @param chunks chunks matrix /// @param start ray start position /// @param dir normalized ray direction vector /// @param maxDist maximum ray length /// @param end [out] ray end position /// @param norm [out] surface normal vector /// @param iend [out] ray end integer position (voxel position + normal) /// @param filter filtered ids /// @return voxel pointer or nullptr voxel* raycast( const Chunks& chunks, const glm::vec3& start, const glm::vec3& dir, float maxDist, glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, std::set filter ); /// @brief Cast ray to a selectable block with filter based on id. /// @param chunks chunks storage /// @param start ray start position /// @param dir normalized ray direction vector /// @param maxDist maximum ray length /// @param end [out] ray end position /// @param norm [out] surface normal vector /// @param iend [out] ray end integer position (voxel position + normal) /// @param filter filtered ids /// @return voxel pointer or nullptr voxel* raycast( const GlobalChunks& chunks, const glm::vec3& start, const glm::vec3& dir, float maxDist, glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, std::set filter ); void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight=false); template inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float z) { int ix = std::floor(x); int iy = std::floor(y); int iz = std::floor(z); voxel* v = get(chunks, ix, iy, iz); if (v == nullptr) { if (iy >= CHUNK_H) { return nullptr; } else { static const AABB empty; return ∅ } } const auto& def = chunks.getContentIndices().blocks.require(v->id); if (def.obstacle) { glm::ivec3 offset {}; if (v->state.segment) { glm::ivec3 point(ix, iy, iz); offset = seek_origin(chunks, 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; } } // blocks_agent