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/main-page.md b/doc/en/main-page.md index 57a5b25d8..5bb2d8f2d 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -21,5 +21,6 @@ Documentation for 0.31. - [Rigging](rigging.md) - [Scripting](scripting.md) - [Text styles](text-styles.md) +- [VCM models format](vcm.md) - [World generator engine](world-generator.md) - [XML UI building](xml-ui-layouts.md) 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/en/vcm.md b/doc/en/vcm.md new file mode 100644 index 000000000..d384bb7d4 --- /dev/null +++ b/doc/en/vcm.md @@ -0,0 +1,101 @@ +# VCM + +A text format for describing 3D models, replacing the deprecated `model-primitives` property. + +Syntax: + +``` +@primitive attribute (value1, value2,...) attribute2 "value3" { + @inner_primitive ... +} +``` + +Same structure in XML: + +```xml + + + +``` + +Unlike XML, the root can contain multiple elements. + +Currently, there are three types of primitives: `tri`, `box`, `rect` and `part` (describes a part of a primitive, such as the side of a cube). + +> [!NOTE] +> By default, texture coordinates are determined by the dimensions of the primitives. +> For sizes greater than 1.0, the result is undefined and may change in future updates. +> Use `region` or `region-scale` for manual adjustments. + +## Primitives + +### `tri` Properties + +This primitive describes a triangle. + +- `a` - Point A. Example: `a (0,0,0)` +- `b` - Point B. Example: `b (1,0,0)` +- `c` - Point C. Example: `c (1,1,0)` +- `uv` - Normalized texture coordinates of the points (6 numbers, 2 per point). Example: `uv (0,0,1,0,1)` +- `texture` - The texture to display. Default: `$0`. Example: `texture "blocks:sand"` +- `region` - UV region within the selected texture, defined by the positions of opposite corners of a square of normalized texture coordinates. Example: `region (0,0,1,1)` +- `region-scale` - multiplier vector for the UV region. Example: `region-scale (0.5,1)` +- `shading` - determines whether shading is enabled on the primitive. Example: `shading off` + +By default, texture coordinates are not affected by shape or size, unlike other primitives. + +### `rect` Properties + +This primitive describes a parallelogram using the right and up vectors. + +- `from` - the origin of the primitive. Example: `from (0,0.5,0.125)` +- `right` - the X vector, which also determines the width of the primitive. Example: `right (1,0,0)` +- `up` - the Y vector, which also determines the primitive's height. Example: `up (0,1,0)` +- `texture` - the texture to display. Default: `$0`. Example: `texture "blocks:sand"` +- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)` +- `region-scale` - the multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)` +- `shading` - determines whether shading is enabled on the primitive. Example: `shading off` +- `flip` - flips the UV region horizontally or vertically. Example: `flip v` + +### `box` Properties + +This primitive describes a rectangular parallelepiped. + +- `from` - the primitive's origin. Example: `from (0,0,0)` +- `to` - the point opposite the origin. Example: `to (1,1,1)` +- `origin` - the point relative to which the rotation will be applied. Default: the center of the primitive. Example: `origin (0.5,0.5,0.5)` +- `rotate` - rotation around the (x,y,z) axes in degrees, or a quaternion (x,y,z,w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)` +- `texture` - the default texture to display for all sides. +- `shading` determines whether shading is enabled on the primitive. Example: `shading off` +- `delete` deletes sides by name (top, bottom, east, west, north, south) + +### `part` properties (nested in `box`) + +This primitive describes the properties of the sides of a rectangular parallelepiped with the specified tags (top/bottom/north/south/east/west). + +- `tags` - determines which side the properties will be applied to. Example: `tags (top,bottom)` +- `texture` - the texture to display. Default: `$side_index`. +- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)` +- `region-scale` is a multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)` + +## Skeleton + +VCM can describe the [skeleton](rigging.md) of a model. The `@bone` element is used for this. + +There are two types of bones: +- unnamed - used only for transformations on inner primitives and not included in the final skeleton. +- named - will be included in the final skeleton. The name is specified via the `name` attribute. + +The main bone (`root`) is created automatically for the entire model. +If bones are explicitly specified, the model will be divided into several parts named as `model_name.bone_name`. +Since the model is an asset and has no prefix, the loaded skeleton receives the model name, also without a prefix. +The loaded skeleton can be used in the [entity](entity-properties.md#skeleton-name) via the `skeleton-name` property. + +In the `pack/preload.json` list, if `"squash": true` is specified, the vcm will be loaded as a single model, without a skeleton. + +### `bone` Properties + +- `name` - The bone name. If omitted, the bone will be "dissolved" and will not be included in the final skeleton, but transformations will be applied. +- `move` - Moves nested elements. +- `scale` - Scales nested elements. Applied before moving. +- `rotate` - Rotates nested elements around the (x, y, z) axes in degrees, or as a quaternion (x, y, z, w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)` 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/main-page.md b/doc/ru/main-page.md index c1773d5d1..517dcdfad 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -22,5 +22,5 @@ - [Свойства сущностей](entity-properties.md) - [Скриптинг](scripting.md) - [Стили текста](text-styles.md) -- [Частицы](particles.md) - [Формат моделей VCM](vcm.md) +- [Частицы](particles.md) 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/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index 8498091c9..57603d63e 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -203,7 +203,7 @@ const Mesh* ChunksRenderer::retrieveChunk( ); auto mesh = getOrRender( chunk, - distance < CHUNK_W * 1.5f * 10.0f, + distance < CHUNK_W * 1.5f, distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5 ); if (mesh == nullptr) { 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/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index fa0b51cdb..a9691038e 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -1,16 +1,17 @@ #include "api_lua.hpp" +#include "../usertypes/lua_type_canvas.hpp" #include "assets/Assets.hpp" #include "assets/AssetsLoader.hpp" +#include "coders/obj.hpp" #include "coders/png.hpp" #include "coders/vcm.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" #include "graphics/commons/Model.hpp" -#include "graphics/core/Texture.hpp" #include "graphics/core/Atlas.hpp" +#include "graphics/core/Texture.hpp" #include "util/Buffer.hpp" -#include "../usertypes/lua_type_canvas.hpp" using namespace scripting; @@ -76,6 +77,11 @@ static int l_parse_model(lua::State* L) { skeletonName = lua::require_string(L, 4); } + if (format == "obj") { + assets.store(obj::parse(name, string), name); + return 0; + } + if (format != "xml" && format != "vcm") { throw std::runtime_error( "unknown format " + util::quote(std::string(format)) 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 9ffac7f93..0742a7293 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -2,23 +2,23 @@ /// 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 "GlobalChunks.hpp" #include "maths/voxmaths.hpp" +#include "typedefs.hpp" +#include "voxel.hpp" +#include "VoxelsVolume.hpp" #include -#include #include -#include -#include #include +#include +#include +#include struct AABB; @@ -96,7 +96,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 @@ -104,8 +103,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; } @@ -119,8 +120,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; } @@ -456,12 +459,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 const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float z) { +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); @@ -494,4 +503,63 @@ inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float return nullptr; } +/// @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