diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 72a4b4c48..f3137dd60 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -241,6 +241,15 @@ Number of block inventory slots. Default - 0 (no inventory). 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 allow you to write more data unique to a specified voxel than the user bits allow. diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index b2528c364..db407e92a 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -67,7 +67,7 @@ block.get_picking_item(id: int) -> int block.get_variant(x: int, y: int, z: int) -> int -- 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 block.has_tag(id: int, tag: str) -> bool diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 97e8fedc6..50bd7f023 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -247,6 +247,14 @@ Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`. +### Поведение приземленности - *grounding-behaviour* + +Определяет поведение расширенного приземленного блока. Доступные варианты поведения: + +- "partial" - как минимум один сегмент блока должен быть приземленным (по-умолчанию) +- "complete" - все сегменты блока должны быть приземленными +- "origin" - главный сегмент должен быть приземленным + ## Поля блока Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты. diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md index a82d2ff66..b7af3e462 100644 --- a/doc/ru/scripting/builtins/libblock.md +++ b/doc/ru/scripting/builtins/libblock.md @@ -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.set_variant(x: int, y: int, z: int, index: int) -> int +block.set_variant(x: int, y: int, z: int, index: int) ``` > [!WARNING] diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 993e7360c..3e0776747 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -1,6 +1,19 @@ #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& Assets::getAnimations() { return animations; diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index ea0753aec..b9faa877f 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -12,6 +12,7 @@ #include #include "util/stringutil.hpp" +#include "util/ObjectsKeeper.hpp" #include "graphics/core/TextureAnimation.hpp" class Assets; @@ -64,13 +65,14 @@ namespace assetload { } class Assets { + util::ObjectsKeeper* vault; std::vector animations; using assets_map = std::unordered_map>; std::unordered_map assets; std::vector setupFuncs; public: - Assets() = default; + Assets(util::ObjectsKeeper* vault); Assets(const Assets&) = delete; ~Assets(); @@ -79,12 +81,20 @@ public: template void store(std::unique_ptr 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 void store(std::shared_ptr 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 diff --git a/src/constants.hpp b/src/constants.hpp index 501c5f62e..3ec66c259 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -33,6 +33,8 @@ inline constexpr int CHUNK_W = 16; inline constexpr int CHUNK_H = 256; 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_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 87c10fa67..f44d3a9ca 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -12,6 +12,7 @@ #include "presets/ParticlesPreset.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" +#include "constants.hpp" using namespace data; @@ -120,8 +121,8 @@ template<> void ContentUnitLoader::loadUnit( } def.variants = std::make_unique(); - def.variants->offset = 0; - def.variants->mask = 0xF; + def.variants->offset = offset; + def.variants->mask = (1ULL << bitsCount) - 1; def.variants->variants.push_back(def.defaults); for (int i = 0; i < variants.size(); i++) { Variant variant = def.defaults; @@ -184,7 +185,11 @@ template<> void ContentUnitLoader::loadUnit( if (auto found = root.at("emission")) { const auto& emissionarr = *found; for (size_t i = 0; i < 3; i++) { - def.emission[i] = glm::clamp(emissionarr[i].asInteger(), static_cast(0), static_cast(15)); + def.emission[i] = glm::clamp( + emissionarr[i].asInteger(), + static_cast(0), + static_cast(15) + ); } } @@ -199,13 +204,31 @@ template<> void ContentUnitLoader::loadUnit( "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? if (def.defaults.model.type == BlockModelType::BLOCK && (def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { def.defaults.model.type = BlockModelType::AABB; 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 diff --git a/src/engine/AssetsManagement.cpp b/src/engine/AssetsManagement.cpp index 578a0e45b..1ca1935c9 100644 --- a/src/engine/AssetsManagement.cpp +++ b/src/engine/AssetsManagement.cpp @@ -51,7 +51,9 @@ void AssetsManagement::loadAssets(Content* content) { logger.info() << "loading assets"; Shader::preprocessor->setPaths(&paths.resPaths); - auto new_assets = std::make_unique(); + auto new_assets = std::make_unique( + settings.system.preserveAssetsDuringFrame.get() ? &assetsVault : nullptr + ); AssetsLoader loader(engine, *new_assets, paths.resPaths); AssetsLoader::addDefaults(loader, content); @@ -78,6 +80,7 @@ void AssetsManagement::loadAssets(Content* content) { } void AssetsManagement::update() { + assetsVault.clearKeepedObjects(); if (backgroundLoaderTask) { backgroundLoaderTask->update(); } diff --git a/src/engine/AssetsManagement.hpp b/src/engine/AssetsManagement.hpp index 4e14c9305..81c33bef8 100644 --- a/src/engine/AssetsManagement.hpp +++ b/src/engine/AssetsManagement.hpp @@ -1,6 +1,7 @@ #pragma once #include "assets/Assets.hpp" +#include "util/ObjectsKeeper.hpp" #include @@ -30,4 +31,5 @@ private: std::unique_ptr assets; std::unique_ptr backgroundLoader; std::shared_ptr backgroundLoaderTask; + util::ObjectsKeeper assetsVault; }; diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 20b7cdaa7..b8ce69a5f 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -70,7 +70,7 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { refreshVariant(def, def.defaults, 0, atlas); if (def.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); } def.variants->variants.at(0) = def.defaults; diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index acb3edf70..13618d33a 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -114,7 +114,7 @@ LevelScreen::~LevelScreen() { } scripting::on_frontend_close(); input.getBindings().enableAll(); - playerController->getPlayer()->chunks->saveAndClear(); + playerController->getPlayer().chunks->saveAndClear(); controller->onWorldQuit(); } @@ -168,12 +168,12 @@ void LevelScreen::saveDecorations() { void LevelScreen::saveWorldPreview() { try { logger.info() << "saving world preview"; - auto player = playerController->getPlayer(); - auto& settings = engine.getSettings(); + const Player& player = playerController->getPlayer(); + const auto& settings = engine.getSettings(); int previewSize = settings.ui.worldPreviewSize.get(); // camera special copy for world preview - Camera camera = *player->fpCamera; + Camera& camera = *player.fpCamera; camera.setFov(glm::radians(70.0f)); DrawContext pctx(nullptr, engine.getWindow(), batch.get()); @@ -215,18 +215,18 @@ void LevelScreen::updateHotkeys() { } void LevelScreen::updateAudio() { - auto player = playerController->getPlayer(); - auto camera = player->currentCamera; + Player& player = playerController->getPlayer(); + Camera& camera = *player.currentCamera; bool paused = hud->isPause(); audio::get_channel("regular")->setPaused(paused); audio::get_channel("ambient")->setPaused(paused); glm::vec3 velocity {}; - if (auto hitbox = player->getHitbox()) { + if (auto hitbox = player.getHitbox()) { velocity = hitbox->velocity; } 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); - const auto& weather = renderer->getWeather(); - const auto& player = *playerController->getPlayer(); - const auto& camera = *player.currentCamera; + const Weather& weather = renderer->getWeather(); + const Player& player = playerController->getPlayer(); + const Camera& camera = *player.currentCamera; decorator->update(paused ? 0.0f : delta, camera, weather); } 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()) { scripting::on_entities_render(engine.getTime().getDelta()); } - renderer->update(*camera, delta * !hud->isPause()); - renderer->renderFrame(ctx, *camera, hudVisible, *postProcessing); + renderer->update(camera, delta * !hud->isPause()); + renderer->renderFrame(ctx, camera, hudVisible, *postProcessing); if (!hud->isPause()) { scripting::on_frontend_render(); } diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index b4f423480..da82680bb 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -92,6 +92,12 @@ void ModelsGenerator::prepareModel( void ModelsGenerator::prepare(Content& content, Assets& assets) { for (auto& [name, def] : content.blocks.getDefs()) { 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()) { assets.store( diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index af4547a14..c66b5c451 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -100,6 +100,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.addSection("system"); 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 { diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp index 2de34442f..3c0d0412e 100644 --- a/src/logic/BlocksController.cpp +++ b/src/logic/BlocksController.cpp @@ -111,10 +111,17 @@ void BlocksController::updateBlock(int x, int y, int z) { if (vox == nullptr) return; const auto& def = level.content.getIndices()->blocks.require(vox->id); if (def.grounded) { - const auto& vec = get_ground_direction(def, vox->state.rotation); - if (!blocks_agent::is_solid_at(chunks, x + vec.x, y + vec.y, z + vec.z)) { - breakBlock(nullptr, def, x, y, z); - return; + if (def.rt.extended) { + auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); + if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, origin)) { + 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) { diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 17c4fe23f..e707d1637 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -22,6 +22,7 @@ #include "scripting/scripting.hpp" #include "settings.hpp" #include "voxels/Block.hpp" +#include "voxels/blocks_agent.hpp" #include "voxels/Chunks.hpp" #include "voxels/voxel.hpp" #include "window/Camera.hpp" @@ -252,18 +253,18 @@ void PlayerController::onFootstep(const Hitbox& hitbox) { } void PlayerController::updateFootsteps(float delta) { - const float pi = glm::pi(); + constexpr float GLM_PI = glm::pi(); auto hitbox = player.getHitbox(); if (hitbox && hitbox->grounded) { const glm::vec3& vel = hitbox->velocity; float f = glm::length(glm::vec2(vel.x, vel.z)); stepsTimer += delta * f * STEPS_SPEED; - if (stepsTimer >= pi) { - stepsTimer = fmod(stepsTimer, pi); + if (stepsTimer >= GLM_PI) { + stepsTimer = fmod(stepsTimer, GLM_PI); onFootstep(*hitbox); } } else { - stepsTimer = pi; + stepsTimer = GLM_PI; } } @@ -448,10 +449,7 @@ void PlayerController::processRightClick( return; } if (def.grounded) { - const auto& vec = get_ground_direction(def, state.rotation); - if (!chunks.isSolidBlock( - coord.x + vec.x, coord.y + vec.y, coord.z + vec.z - )) { + if (!blocks_agent::check_grounding(chunks, def, state.rotation, coord)) { return; } } @@ -556,6 +554,6 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta) } } -Player* PlayerController::getPlayer() { - return &player; +Player& PlayerController::getPlayer() { + return player; } diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index 7f895ee3c..e730ff37c 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -89,5 +89,5 @@ public: void postUpdate( float delta, int windowHeight, const Input* inputEvents, bool pause ); - Player* getPlayer(); + Player& getPlayer(); }; diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 6a84b4113..2fa2b1281 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -246,11 +246,12 @@ static int l_get_user_bits(lua::State* L) { } static int l_get_variant(lua::State* L) { + auto& chunks = *level->chunks; auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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) { return lua::pushinteger(L, 0); } @@ -258,6 +259,13 @@ static int l_get_variant(lua::State* L) { if (def.variants == nullptr) { 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( 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) { return 0; } + int ocx = floordiv(origin.x); + int ocz = floordiv(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; chunk->setModifiedAndUnsaved(); @@ -318,8 +334,8 @@ static int l_set_variant(lua::State* L) { } auto offset = def.variants->offset; - auto mask = def.variants->mask; - auto value = (lua::tointeger(L, 4) << offset) & mask; + auto mask = def.variants->mask << offset; + auto value = (lua::tointeger(L, 4) << offset); if (def.rt.extended) { 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) { return 0; } + int ocx = floordiv(origin.x); + int ocz = floordiv(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; chunk->setModifiedAndUnsaved(); diff --git a/src/settings.hpp b/src/settings.hpp index 331ff3ee8..782330f14 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -122,7 +122,10 @@ struct NetworkSettings { }; struct SystemSettings { + /// @brief Max background assets loaders IntegerSetting maxBgAssetLoaders {3, -4, 16}; + /// @brief Preserve assets during current frame from being destroyed + FlagSetting preserveAssetsDuringFrame {true}; }; struct EngineSettings { diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index e1a68c28f..5ba3bb267 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -140,6 +140,22 @@ VC_ENUM_METADATA(CullingMode) {"disabled", CullingMode::DISABLED}, 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 struct BlockMaterial : Serializable { std::string name; @@ -239,6 +255,9 @@ public: /// @brief Explicitly overriding 'solid' property if true assigned bool explictlySolid = false; + /// @brief Grounding behaviour + GroundingBehaviour groundingBehaviour = GroundingBehaviour::PARTIAL; + /// @brief Set of block physical hitboxes std::vector hitboxes {AABB()}; diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 11470fe44..a86b2e25b 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -95,7 +95,6 @@ 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 @@ -103,8 +102,10 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) { /// @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) { +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; } @@ -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 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) { +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; } @@ -455,12 +458,18 @@ voxel* raycast( 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 -inline std::optional is_obstacle_at(const Storage& chunks, float x, float y, float z, const AABB& aabb) { +inline std::optional is_obstacle_at( + const Storage& chunks, float x, float y, float z, const AABB& aabb +) { int ix = std::floor(x); int iy = std::floor(y); int iz = std::floor(z); @@ -497,4 +506,63 @@ inline std::optional 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})); } +/// @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 +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