436 lines
13 KiB
C++
436 lines
13 KiB
C++
#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 "GlobalChunks.hpp"
|
|
#include "constants.hpp"
|
|
#include "typedefs.hpp"
|
|
#include "content/Content.hpp"
|
|
#include "maths/voxmaths.hpp"
|
|
|
|
#include <set>
|
|
#include <stdint.h>
|
|
#include <stdexcept>
|
|
#include <glm/glm.hpp>
|
|
|
|
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<class Storage>
|
|
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<class Storage>
|
|
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<CHUNK_W>(x);
|
|
int cz = floordiv<CHUNK_D>(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<class Storage>
|
|
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<class Storage>
|
|
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<class Storage>
|
|
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<class Storage>
|
|
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<class Storage>
|
|
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.axisX * sx;
|
|
pos += rotation.axisY * sy;
|
|
pos += rotation.axisZ * 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 <class Storage>
|
|
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.axisX * sx;
|
|
pos += rotation.axisY * sy;
|
|
pos += rotation.axisZ * 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 <class Storage>
|
|
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.axisX;
|
|
if (segment & 2) pos -= rotation.axisY;
|
|
if (segment & 4) pos -= rotation.axisZ;
|
|
|
|
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 <class Storage>
|
|
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.axisX * sx;
|
|
pos += rotation.axisY * sy;
|
|
pos += rotation.axisZ * 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 <class Storage>
|
|
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<glm::ivec3> 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.axisX * sx;
|
|
pos += rotation.axisY * sy;
|
|
pos += rotation.axisZ * 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<CHUNK_W>(pos.x);
|
|
int cz = floordiv<CHUNK_D>(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.axisX * sx;
|
|
pos += prevRotation.axisY * sy;
|
|
pos += prevRotation.axisZ * 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 <class Storage>
|
|
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<CHUNK_W>(x);
|
|
int cz = floordiv<CHUNK_D>(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<blockid_t> 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<blockid_t> filter
|
|
);
|
|
|
|
} // blocks_agent
|