Merge branch 'main' into physics-upgrade

This commit is contained in:
MihailRis 2026-03-02 21:52:17 +03:00
commit 6676db3525
21 changed files with 249 additions and 53 deletions

View file

@ -241,6 +241,15 @@ Number of block inventory slots. Default - 0 (no inventory).
Array of three integers. Default value is `[1, 1, 1]`. Array of three integers. Default value is `[1, 1, 1]`.
### *grounding-behaviour*
Defines the behavior of the extended grounded block. Available behavior options:
- "partial" - at least one segment must be grounded (default)
- "complete" - all segments must be grounded
- "origin" - origin segment must be grounded
## Block fields ## Block fields
Block fields allow you to write more data unique to a specified voxel than the user bits allow. Block fields allow you to write more data unique to a specified voxel than the user bits allow.

View file

@ -67,7 +67,7 @@ block.get_picking_item(id: int) -> int
block.get_variant(x: int, y: int, z: int) -> int block.get_variant(x: int, y: int, z: int) -> int
-- Sets the block variant by index -- Sets the block variant by index
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int)
-- Checks if an block has specified tag -- Checks if an block has specified tag
block.has_tag(id: int, tag: str) -> bool block.has_tag(id: int, tag: str) -> bool

View file

@ -247,6 +247,14 @@
Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`. Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`.
### Поведение приземленности - *grounding-behaviour*
Определяет поведение расширенного приземленного блока. Доступные варианты поведения:
- "partial" - как минимум один сегмент блока должен быть приземленным (по-умолчанию)
- "complete" - все сегменты блока должны быть приземленными
- "origin" - главный сегмент должен быть приземленным
## Поля блока ## Поля блока
Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты. Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты.

View file

@ -46,7 +46,7 @@ block.destruct(x: int, y: int, z: int, playerid: int)
block.get_variant(x: int, y: int, z: int) -> int block.get_variant(x: int, y: int, z: int) -> int
-- Устанавливает вариант блока по индексу -- Устанавливает вариант блока по индексу
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int)
``` ```
> [!WARNING] > [!WARNING]

View file

@ -1,6 +1,19 @@
#include "Assets.hpp" #include "Assets.hpp"
Assets::~Assets() = default; Assets::Assets(util::ObjectsKeeper* vault) : vault(vault) {
}
Assets::~Assets() {
if (vault == nullptr) {
return;
}
for (auto& [_, map] : assets) {
for (auto& [__, asset] : map) {
vault->keepAlive(std::move(asset));
}
map.clear();
}
}
const std::vector<TextureAnimation>& Assets::getAnimations() { const std::vector<TextureAnimation>& Assets::getAnimations() {
return animations; return animations;

View file

@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp" #include "graphics/core/TextureAnimation.hpp"
class Assets; class Assets;
@ -64,13 +65,14 @@ namespace assetload {
} }
class Assets { class Assets {
util::ObjectsKeeper* vault;
std::vector<TextureAnimation> animations; std::vector<TextureAnimation> animations;
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>; using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
std::unordered_map<std::type_index, assets_map> assets; std::unordered_map<std::type_index, assets_map> assets;
std::vector<assetload::setupfunc> setupFuncs; std::vector<assetload::setupfunc> setupFuncs;
public: public:
Assets() = default; Assets(util::ObjectsKeeper* vault);
Assets(const Assets&) = delete; Assets(const Assets&) = delete;
~Assets(); ~Assets();
@ -79,12 +81,20 @@ public:
template <class T> template <class T>
void store(std::unique_ptr<T> asset, const std::string& name) { void store(std::unique_ptr<T> asset, const std::string& name) {
assets[typeid(T)][name].reset(asset.release()); auto& dst = assets[typeid(T)][name];
if (vault != nullptr && dst != nullptr) {
vault->keepAlive(std::move(dst));
}
dst.reset(asset.release());
} }
template <class T> template <class T>
void store(std::shared_ptr<T> asset, const std::string& name) { void store(std::shared_ptr<T> asset, const std::string& name) {
assets[typeid(T)][name] = std::move(asset); auto& dst = assets[typeid(T)][name];
if (vault != nullptr && dst != nullptr) {
vault->keepAlive(std::move(dst));
}
dst = std::move(asset);
} }
template <class T> template <class T>

View file

@ -33,6 +33,8 @@ inline constexpr int CHUNK_W = 16;
inline constexpr int CHUNK_H = 256; inline constexpr int CHUNK_H = 256;
inline constexpr int CHUNK_D = 16; inline constexpr int CHUNK_D = 16;
inline constexpr int EXTENDED_BLOCK_LIMIT = CHUNK_W; // must not be greater than chunk width and depth
inline constexpr uint VOXEL_USER_BITS = 8; inline constexpr uint VOXEL_USER_BITS = 8;
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;

View file

@ -12,6 +12,7 @@
#include "presets/ParticlesPreset.hpp" #include "presets/ParticlesPreset.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "constants.hpp"
using namespace data; using namespace data;
@ -120,8 +121,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
} }
def.variants = std::make_unique<Variants>(); def.variants = std::make_unique<Variants>();
def.variants->offset = 0; def.variants->offset = offset;
def.variants->mask = 0xF; def.variants->mask = (1ULL << bitsCount) - 1;
def.variants->variants.push_back(def.defaults); def.variants->variants.push_back(def.defaults);
for (int i = 0; i < variants.size(); i++) { for (int i = 0; i < variants.size(); i++) {
Variant variant = def.defaults; Variant variant = def.defaults;
@ -184,7 +185,11 @@ template<> void ContentUnitLoader<Block>::loadUnit(
if (auto found = root.at("emission")) { if (auto found = root.at("emission")) {
const auto& emissionarr = *found; const auto& emissionarr = *found;
for (size_t i = 0; i < 3; i++) { for (size_t i = 0; i < 3; i++) {
def.emission[i] = glm::clamp(emissionarr[i].asInteger(), static_cast<integer_t>(0), static_cast<integer_t>(15)); def.emission[i] = glm::clamp(
emissionarr[i].asInteger(),
static_cast<integer_t>(0),
static_cast<integer_t>(15)
);
} }
} }
@ -199,13 +204,31 @@ template<> void ContentUnitLoader<Block>::loadUnit(
"block " + util::quote(def.name) + ": invalid block size" "block " + util::quote(def.name) + ": invalid block size"
); );
} }
if (def.size.x > EXTENDED_BLOCK_LIMIT ||
def.size.y > EXTENDED_BLOCK_LIMIT ||
def.size.z > EXTENDED_BLOCK_LIMIT) {
throw std::runtime_error(
"extended block " + util::quote(def.name) + " size limit " +
std::to_string(EXTENDED_BLOCK_LIMIT) + " exceeded"
);
}
// should variant modify hitbox? // should variant modify hitbox?
if (def.defaults.model.type == BlockModelType::BLOCK && if (def.defaults.model.type == BlockModelType::BLOCK &&
(def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { (def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) {
def.defaults.model.type = BlockModelType::AABB; def.defaults.model.type = BlockModelType::AABB;
def.hitboxes = {AABB(def.size)}; def.hitboxes = {AABB(def.size)};
} }
// grounding behaviour
if (root.has("grounding-behaviour")) {
std::string groundingBehaviourName = GroundingBehaviourMeta.getNameString(def.groundingBehaviour);
root.at("grounding-behaviour").get(groundingBehaviourName);
if (!GroundingBehaviourMeta.getItem(groundingBehaviourName, def.groundingBehaviour)) {
logger.error() << "unknown grounding behaviour: " << groundingBehaviourName;
}
}
} }
// primitive properties // primitive properties

View file

@ -51,7 +51,9 @@ void AssetsManagement::loadAssets(Content* content) {
logger.info() << "loading assets"; logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths); Shader::preprocessor->setPaths(&paths.resPaths);
auto new_assets = std::make_unique<Assets>(); auto new_assets = std::make_unique<Assets>(
settings.system.preserveAssetsDuringFrame.get() ? &assetsVault : nullptr
);
AssetsLoader loader(engine, *new_assets, paths.resPaths); AssetsLoader loader(engine, *new_assets, paths.resPaths);
AssetsLoader::addDefaults(loader, content); AssetsLoader::addDefaults(loader, content);
@ -78,6 +80,7 @@ void AssetsManagement::loadAssets(Content* content) {
} }
void AssetsManagement::update() { void AssetsManagement::update() {
assetsVault.clearKeepedObjects();
if (backgroundLoaderTask) { if (backgroundLoaderTask) {
backgroundLoaderTask->update(); backgroundLoaderTask->update();
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "util/ObjectsKeeper.hpp"
#include <memory> #include <memory>
@ -30,4 +31,5 @@ private:
std::unique_ptr<Assets> assets; std::unique_ptr<Assets> assets;
std::unique_ptr<AssetsLoader> backgroundLoader; std::unique_ptr<AssetsLoader> backgroundLoader;
std::shared_ptr<Task> backgroundLoaderTask; std::shared_ptr<Task> backgroundLoaderTask;
util::ObjectsKeeper assetsVault;
}; };

View file

@ -70,7 +70,7 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
refreshVariant(def, def.defaults, 0, atlas); refreshVariant(def, def.defaults, 0, atlas);
if (def.variants) { if (def.variants) {
const auto& variants = def.variants->variants; const auto& variants = def.variants->variants;
for (int i = 1; i < variants.size() - 1; i++) { for (int i = 1; i < variants.size(); i++) {
refreshVariant(def, variants[i], i, atlas); refreshVariant(def, variants[i], i, atlas);
} }
def.variants->variants.at(0) = def.defaults; def.variants->variants.at(0) = def.defaults;

View file

@ -114,7 +114,7 @@ LevelScreen::~LevelScreen() {
} }
scripting::on_frontend_close(); scripting::on_frontend_close();
input.getBindings().enableAll(); input.getBindings().enableAll();
playerController->getPlayer()->chunks->saveAndClear(); playerController->getPlayer().chunks->saveAndClear();
controller->onWorldQuit(); controller->onWorldQuit();
} }
@ -168,12 +168,12 @@ void LevelScreen::saveDecorations() {
void LevelScreen::saveWorldPreview() { void LevelScreen::saveWorldPreview() {
try { try {
logger.info() << "saving world preview"; logger.info() << "saving world preview";
auto player = playerController->getPlayer(); const Player& player = playerController->getPlayer();
auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
int previewSize = settings.ui.worldPreviewSize.get(); int previewSize = settings.ui.worldPreviewSize.get();
// camera special copy for world preview // camera special copy for world preview
Camera camera = *player->fpCamera; Camera& camera = *player.fpCamera;
camera.setFov(glm::radians(70.0f)); camera.setFov(glm::radians(70.0f));
DrawContext pctx(nullptr, engine.getWindow(), batch.get()); DrawContext pctx(nullptr, engine.getWindow(), batch.get());
@ -215,18 +215,18 @@ void LevelScreen::updateHotkeys() {
} }
void LevelScreen::updateAudio() { void LevelScreen::updateAudio() {
auto player = playerController->getPlayer(); Player& player = playerController->getPlayer();
auto camera = player->currentCamera; Camera& camera = *player.currentCamera;
bool paused = hud->isPause(); bool paused = hud->isPause();
audio::get_channel("regular")->setPaused(paused); audio::get_channel("regular")->setPaused(paused);
audio::get_channel("ambient")->setPaused(paused); audio::get_channel("ambient")->setPaused(paused);
glm::vec3 velocity {}; glm::vec3 velocity {};
if (auto hitbox = player->getHitbox()) { if (auto hitbox = player.getHitbox()) {
velocity = hitbox->velocity; velocity = hitbox->velocity;
} }
audio::set_listener( audio::set_listener(
camera->position, velocity, camera->dir, glm::vec3(0, 1, 0) camera.position, velocity, camera.dir, glm::vec3(0, 1, 0)
); );
} }
@ -257,22 +257,22 @@ void LevelScreen::update(float delta) {
hud->update(hudVisible); hud->update(hudVisible);
const auto& weather = renderer->getWeather(); const Weather& weather = renderer->getWeather();
const auto& player = *playerController->getPlayer(); const Player& player = playerController->getPlayer();
const auto& camera = *player.currentCamera; const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather); decorator->update(paused ? 0.0f : delta, camera, weather);
} }
void LevelScreen::draw(float delta) { void LevelScreen::draw(float delta) {
auto camera = playerController->getPlayer()->currentCamera; Camera& camera = *playerController->getPlayer().currentCamera;
DrawContext ctx(nullptr, engine.getWindow(), batch.get()); const DrawContext ctx(nullptr, engine.getWindow(), batch.get());
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
renderer->update(*camera, delta * !hud->isPause()); renderer->update(camera, delta * !hud->isPause());
renderer->renderFrame(ctx, *camera, hudVisible, *postProcessing); renderer->renderFrame(ctx, camera, hudVisible, *postProcessing);
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_frontend_render(); scripting::on_frontend_render();
} }

View file

@ -92,6 +92,12 @@ void ModelsGenerator::prepareModel(
void ModelsGenerator::prepare(Content& content, Assets& assets) { void ModelsGenerator::prepare(Content& content, Assets& assets) {
for (auto& [name, def] : content.blocks.getDefs()) { for (auto& [name, def] : content.blocks.getDefs()) {
prepareModel(assets, *def, def->defaults, 0); prepareModel(assets, *def, def->defaults, 0);
if (def->variants) {
auto& variants = def->variants->variants;
for (int i = 1; i < variants.size(); i++) {
prepareModel(assets, *def, variants[i], i);
}
}
} }
for (auto& [name, def] : content.items.getDefs()) { for (auto& [name, def] : content.items.getDefs()) {
assets.store( assets.store(

View file

@ -100,6 +100,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("system"); builder.addSection("system");
builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders); builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders);
builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame);
} }
dv::value SettingsHandler::getValue(const std::string& name) const { dv::value SettingsHandler::getValue(const std::string& name) const {

View file

@ -111,10 +111,17 @@ void BlocksController::updateBlock(int x, int y, int z) {
if (vox == nullptr) return; if (vox == nullptr) return;
const auto& def = level.content.getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.grounded) { if (def.grounded) {
const auto& vec = get_ground_direction(def, vox->state.rotation); if (def.rt.extended) {
if (!blocks_agent::is_solid_at(chunks, x + vec.x, y + vec.y, z + vec.z)) { auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
breakBlock(nullptr, def, x, y, z); if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, origin)) {
return; breakBlock(nullptr, def, origin.x, origin.y, origin.z);
return;
}
} else {
if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, {x, y, z})) {
breakBlock(nullptr, def, x, y, z);
return;
}
} }
} }
if (def.rt.funcsset.update) { if (def.rt.funcsset.update) {

View file

@ -22,6 +22,7 @@
#include "scripting/scripting.hpp" #include "scripting/scripting.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/blocks_agent.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/voxel.hpp" #include "voxels/voxel.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
@ -252,18 +253,18 @@ void PlayerController::onFootstep(const Hitbox& hitbox) {
} }
void PlayerController::updateFootsteps(float delta) { void PlayerController::updateFootsteps(float delta) {
const float pi = glm::pi<float>(); constexpr float GLM_PI = glm::pi<float>();
auto hitbox = player.getHitbox(); auto hitbox = player.getHitbox();
if (hitbox && hitbox->grounded) { if (hitbox && hitbox->grounded) {
const glm::vec3& vel = hitbox->velocity; const glm::vec3& vel = hitbox->velocity;
float f = glm::length(glm::vec2(vel.x, vel.z)); float f = glm::length(glm::vec2(vel.x, vel.z));
stepsTimer += delta * f * STEPS_SPEED; stepsTimer += delta * f * STEPS_SPEED;
if (stepsTimer >= pi) { if (stepsTimer >= GLM_PI) {
stepsTimer = fmod(stepsTimer, pi); stepsTimer = fmod(stepsTimer, GLM_PI);
onFootstep(*hitbox); onFootstep(*hitbox);
} }
} else { } else {
stepsTimer = pi; stepsTimer = GLM_PI;
} }
} }
@ -448,10 +449,7 @@ void PlayerController::processRightClick(
return; return;
} }
if (def.grounded) { if (def.grounded) {
const auto& vec = get_ground_direction(def, state.rotation); if (!blocks_agent::check_grounding(chunks, def, state.rotation, coord)) {
if (!chunks.isSolidBlock(
coord.x + vec.x, coord.y + vec.y, coord.z + vec.z
)) {
return; return;
} }
} }
@ -556,6 +554,6 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta)
} }
} }
Player* PlayerController::getPlayer() { Player& PlayerController::getPlayer() {
return &player; return player;
} }

View file

@ -89,5 +89,5 @@ public:
void postUpdate( void postUpdate(
float delta, int windowHeight, const Input* inputEvents, bool pause float delta, int windowHeight, const Input* inputEvents, bool pause
); );
Player* getPlayer(); Player& getPlayer();
}; };

View file

@ -246,11 +246,12 @@ static int l_get_user_bits(lua::State* L) {
} }
static int l_get_variant(lua::State* L) { static int l_get_variant(lua::State* L) {
auto& chunks = *level->chunks;
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(chunks, x, y, z);
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
@ -258,6 +259,13 @@ static int l_get_variant(lua::State* L) {
if (def.variants == nullptr) { if (def.variants == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z);
if (vox == nullptr) {
return lua::pushinteger(L, 0);
}
}
return lua::pushinteger( return lua::pushinteger(
L, (vox->state.userbits >> def.variants->offset) & def.variants->mask L, (vox->state.userbits >> def.variants->offset) & def.variants->mask
); );
@ -290,6 +298,14 @@ static int l_set_user_bits(lua::State* L) {
if (vox == nullptr) { if (vox == nullptr) {
return 0; return 0;
} }
int ocx = floordiv<CHUNK_W>(origin.x);
int ocz = floordiv<CHUNK_D>(origin.z);
if (cx != ocx || cz != ocz) {
chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
if (chunk == nullptr) {
return 0;
}
}
} }
vox->state.userbits = (vox->state.userbits & (~mask)) | value; vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved(); chunk->setModifiedAndUnsaved();
@ -318,8 +334,8 @@ static int l_set_variant(lua::State* L) {
} }
auto offset = def.variants->offset; auto offset = def.variants->offset;
auto mask = def.variants->mask; auto mask = def.variants->mask << offset;
auto value = (lua::tointeger(L, 4) << offset) & mask; auto value = (lua::tointeger(L, 4) << offset);
if (def.rt.extended) { if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
@ -327,6 +343,14 @@ static int l_set_variant(lua::State* L) {
if (vox == nullptr) { if (vox == nullptr) {
return 0; return 0;
} }
int ocx = floordiv<CHUNK_W>(origin.x);
int ocz = floordiv<CHUNK_D>(origin.z);
if (cx != ocx || cz != ocz) {
chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
if (chunk == nullptr) {
return 0;
}
}
} }
vox->state.userbits = (vox->state.userbits & (~mask)) | value; vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved(); chunk->setModifiedAndUnsaved();

View file

@ -122,7 +122,10 @@ struct NetworkSettings {
}; };
struct SystemSettings { struct SystemSettings {
/// @brief Max background assets loaders
IntegerSetting maxBgAssetLoaders {3, -4, 16}; IntegerSetting maxBgAssetLoaders {3, -4, 16};
/// @brief Preserve assets during current frame from being destroyed
FlagSetting preserveAssetsDuringFrame {true};
}; };
struct EngineSettings { struct EngineSettings {

View file

@ -140,6 +140,22 @@ VC_ENUM_METADATA(CullingMode)
{"disabled", CullingMode::DISABLED}, {"disabled", CullingMode::DISABLED},
VC_ENUM_END VC_ENUM_END
/// @brief Grounding behaviour for extended blocks
enum class GroundingBehaviour : uint8_t {
/// @brief at least one segment must be grounded
PARTIAL,
/// @brief all segments must be grounded
COMPLETE,
/// @brief origin segment must be grounded
ORIGIN
};
VC_ENUM_METADATA(GroundingBehaviour)
{"partial", GroundingBehaviour::PARTIAL},
{"complete", GroundingBehaviour::COMPLETE},
{"origin", GroundingBehaviour::ORIGIN},
VC_ENUM_END
/// @brief Common kit of block properties applied to groups of blocks /// @brief Common kit of block properties applied to groups of blocks
struct BlockMaterial : Serializable { struct BlockMaterial : Serializable {
std::string name; std::string name;
@ -239,6 +255,9 @@ public:
/// @brief Explicitly overriding 'solid' property if true assigned /// @brief Explicitly overriding 'solid' property if true assigned
bool explictlySolid = false; bool explictlySolid = false;
/// @brief Grounding behaviour
GroundingBehaviour groundingBehaviour = GroundingBehaviour::PARTIAL;
/// @brief Set of block physical hitboxes /// @brief Set of block physical hitboxes
std::vector<AABB> hitboxes {AABB()}; std::vector<AABB> hitboxes {AABB()};

View file

@ -95,7 +95,6 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
return chunks.getContentIndices().blocks.require(id); return chunks.getContentIndices().blocks.require(id);
} }
/// @brief Check if block at specified position is solid. /// @brief Check if block at specified position is solid.
/// @tparam Storage chunks storage class /// @tparam Storage chunks storage class
/// @param chunks chunks storage /// @param chunks chunks storage
@ -103,8 +102,10 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
/// @param y position Y /// @param y position Y
/// @param z position Z /// @param z position Z
/// @return true if block exists and solid /// @return true if block exists and solid
template<class Storage> template <class Storage>
inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { 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)) { if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).rt.solid; return get_block_def(chunks, vox->id).rt.solid;
} }
@ -118,8 +119,10 @@ inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z)
/// @param y position Y /// @param y position Y
/// @param z position Z /// @param z position Z
/// @return true if block exists and replaceable /// @return true if block exists and replaceable
template<class Storage> template <class Storage>
inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { 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)) { if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).replaceable; return get_block_def(chunks, vox->id).replaceable;
} }
@ -455,12 +458,18 @@ voxel* raycast(
bool includeNonSelectable bool includeNonSelectable
); );
void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(
const Chunks& chunks, VoxelsVolume* volume, bool backlight = false
);
void get_voxels(const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(
const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight = false
);
template <class Storage> template <class Storage>
inline std::optional<AABB> is_obstacle_at(const Storage& chunks, float x, float y, float z, const AABB& aabb) { inline std::optional<AABB> is_obstacle_at(
const Storage& chunks, float x, float y, float z, const AABB& aabb
) {
int ix = std::floor(x); int ix = std::floor(x);
int iy = std::floor(y); int iy = std::floor(y);
int iz = std::floor(z); int iz = std::floor(z);
@ -497,4 +506,63 @@ inline std::optional<AABB> is_obstacle_at(const Storage& chunks, float x, float
return is_obstacle_at(chunks, x, y, z, AABB({x, y, z}, {x + 1, y + 1, z + 1})); return is_obstacle_at(chunks, x, y, z, AABB({x, y, z}, {x + 1, y + 1, z + 1}));
} }
/// @brief Check block grounding
/// @tparam Storage chunks storage class
/// @param chunks chunks storage
/// @param def block definition
/// @param rotationIndex target block rotation index
/// @param origin target block origin
/// @return true if grounded
template <class Storage>
inline bool check_grounding(
const Storage& chunks,
const Block& def,
uint8_t rotationIndex,
const glm::ivec3& origin
) {
const auto& vec = get_ground_direction(def, rotationIndex);
if (!def.rt.extended) {
return blocks_agent::is_solid_at(
chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
);
}
const auto& rotation = def.rotations.variants[rotationIndex];
if (def.groundingBehaviour == GroundingBehaviour::PARTIAL) {
for (int sz = 0; sz < def.size.z; sz++) {
for (int sx = 0; sx < def.size.x; sx++) {
auto pos = origin;
pos += rotation.axes[0] * sx;
pos += rotation.axes[2] * sz;
if (blocks_agent::is_solid_at(
chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
)) {
return true;
}
}
}
return false;
} else if (def.groundingBehaviour == GroundingBehaviour::COMPLETE) {
for (int sz = 0; sz < def.size.z; sz++) {
for (int sx = 0; sx < def.size.x; sx++) {
auto pos = origin;
pos += rotation.axes[0] * sx;
pos += rotation.axes[2] * sz;
if (!blocks_agent::is_solid_at(
chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
)) {
return false;
}
}
}
return true;
} else {
return blocks_agent::is_solid_at(
chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
);
}
}
} // blocks_agent } // blocks_agent