diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 0231e1df..69e687e4 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -30,6 +30,7 @@ AppDir: - libogg0 - libvorbis0a - libvorbisfile3 + - libluajit-5.1-2 exclude: - hicolor-icon-theme - sound-theme-freedesktop diff --git a/doc/en/audio.md b/doc/en/audio.md index 9322583f..cacd7107 100644 --- a/doc/en/audio.md +++ b/doc/en/audio.md @@ -60,7 +60,7 @@ Library **audio** contains available Audio API in Lua scripts. ```lua audio.play_stream( - -- audio file location + -- audio file location (without entry point, but with extension included) name: string, -- audio source world position x: number, y: number, z: number, @@ -79,7 +79,7 @@ Plays streaming audio from the specified file at the specified world position. R ```lua audio.play_stream_2d( - -- audio file location + -- audio file location (without entry point, but with extension included) name: string, -- audio gain (0.0 - 1.0) volume: number @@ -202,4 +202,4 @@ audio.count_speakers() -> integer -- get current number of playing streams audio.count_streams() -> integer -``` \ No newline at end of file +``` diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index fb874e0c..87df173b 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -35,6 +35,16 @@ Block model type from list: - "X" - grass model (two crossed sprites) - "aabb" - model based of block hitbox (complex hitbox will be combined into one). Examples: pipes, bulbs, panels. +### *model-name* + +In addition to built-in model types, you can use your own, loaded from file. + +The property specifies the model name without `entry_point:models/` nor extension. + +> [!WARNING] +> Textures (materials) used in the model must be in the `blocks` atlas and specified in the *atlas-texture* format: +> `blocks:texture_name` + ### *draw-group* Integer specifying number of block draw group (render order). Used for semi-transparent blocks. @@ -81,6 +91,13 @@ Turns off block model shading Determines the presence of the vertex AO effect. Turned-on by default. +### *culling* + +Face culling mode: +- **default** - normal face culling +- **optional** - face culling among blocks of the same rendering group can be disabled via the `graphics.dense-render` setting. +- **disabled** - face culling among blocks of the same rendering group disabled. + ## Physics ### *obstacle* diff --git a/doc/en/particles.md b/doc/en/particles.md index 8b7a37e7..47ab8ac3 100644 --- a/doc/en/particles.md +++ b/doc/en/particles.md @@ -16,6 +16,10 @@ Particles are a table, all fields of which are optional. | acceleration | Particles acceleration. | {0, -16, 0} | | explosion | Force of particles explosion on spawn. | {2, 2, 2} | | size | Size of particles. | {0.1, 0.1, 0.1} | +| size_spread | Maximum particle size spread over time. | 0.2 | +| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 | +| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 | +| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 | | spawn_shape | Shape of particle spawn area. (ball/sphere/box) | ball | | spawn_spread | Size of particle spawn area. | {0, 0, 0} | | random_sub_uv | Size of random texture subregion (1 - entire texture will be used). | 1.0 | diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md index cb46a3a5..9108fbdf 100644 --- a/doc/en/scripting/builtins/libentities.md +++ b/doc/en/scripting/builtins/libentities.md @@ -15,7 +15,8 @@ entities.get(uid: int) -> table -- prefix - component pack id -- name - component name -- component prefix and name are separated with two underscores -entities.spawn(name: str, pos: vec3, [optional] args: table) +-- Returns an entity object +entities.spawn(name: str, pos: vec3, [optional] args: table) -> table -- Checks the existence of an entity by a unique identifier. entities.exists(uid: int) -> bool diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index 2112bce2..40fd1b37 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -27,6 +27,7 @@ * [Small structures placement](#small-structures-placement) * [Wide structures placement](#wide-structures-placement) - [Structural air](#structural-air) +- [Generator 'Demo' (base:demo)](#generator-demo-basedemo) ## Basic concepts @@ -52,6 +53,7 @@ The main properties described in the configuration file: - **biomes-bpd** - number of blocks per point of the biome selection parameter map. Default: 4. - **heights-bpd** - number of blocks per point of the height map. Default: 4. - **wide-structs-chunks-radius** - maximum radius for placing 'wide' structures, measured in chunks. +- **heightmap-inputs** - an array of parameter map numbers that will be passed by the inputs table to the height map generation function. ## Global variables @@ -472,3 +474,24 @@ function place_structures_wide( `core:struct_air` - a block that should be used in chunks to mark empty space that should not be filled with blocks when generated in the world. + +# Generator 'Demo' (base:demo) + +## Adding new ore + +To add a new ore in your pack: +1. In the `generators` folder, create a `demo.files` folder (you don't need to create demo.toml). + +2. In the created folder, create a fragments folder and place the ore fragment file in it. +3. In `demo.files`, create a structures.toml file: +```toml +fragment_name = {} +``` +4. Also in `demo.files`, create an ores.json file: +```json +[ + {"struct": "fragment_name", "rarity": rarity} +] +``` +The higher the rarity value, the less ore generation chance. +You can rely on the rarity of coal ore: 4400. diff --git a/doc/ru/audio.md b/doc/ru/audio.md index b560b739..1fce6c35 100644 --- a/doc/ru/audio.md +++ b/doc/ru/audio.md @@ -61,7 +61,7 @@ ```lua audio.play_stream( - -- путь к аудио-файлу + -- путь к аудио-файлу (без точки входа, но с указанием расширения) name: string, -- позиция источника аудио в мире x: number, y: number, z: number, @@ -80,7 +80,7 @@ audio.play_stream( ```lua audio.play_stream_2d( - -- путь к аудио-файлу + -- путь к аудио-файлу (без точки входа, но с указанием расширения) name: string, -- громкость аудио (от 0.0 до 1.0) volume: number diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 1862e659..2f958e88 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -35,6 +35,16 @@ - "X" - модель травы (крест из двух спрайтов) - "aabb" - модель, соответствующая хитбоксу блока (составной хитбокс будет объединен в один). Примеры: трубы, лампочки, панели. +### Имя модели - *model-name* + +Кроме встроенных типов моделей, можно использовать собственные, загружаемые из файлов. + +В свойстве указывается имя модели без `точка_входа:models/` и расширения. + +> [!WARNING] +> Текстуры (материалы), использующиеся в модели, должны находиться в атласе `blocks` и указываться в соответствующем формате: +> `blocks:имя_текстуры` + ### Группа отрисовки - *draw-group* Целое число определяющее номер группы отрисовки данного блока. @@ -82,7 +92,6 @@ При значении `true` блок не препятствует прохождению вертикального луча солнечного света. - ### Без освещения - *shadeless* Выключает освещение на модели блока. @@ -91,6 +100,13 @@ Определяет наличие эффекта вершинного AO. Включен по-умолчанию. +### Отсечение - *culling* + +Режим отсечения граней: +- **default** - обычное отсечение граней +- **optional** - отсечение граней среди блоков одной группы отрисовки можно отключить через настройку `graphics.dense-render` (Плотный рендер блоков). +- **disabled** - отсечение граней среди блоков одной группы отрисовки отключено. + ## Физика ### Препятствие - *obstacle* diff --git a/doc/ru/particles.md b/doc/ru/particles.md index 167337f8..90af27f1 100644 --- a/doc/ru/particles.md +++ b/doc/ru/particles.md @@ -18,6 +18,9 @@ | explosion | Сила разлёта частиц при спавне. | {2, 2, 2} | | size | Размер частиц. | {0.1, 0.1, 0.1} | | size_spread | Максимальное отклонение времени размера частиц. | 0.2 | +| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 | +| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | +| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | | spawn_shape | Форма области спавна частиц. (ball/sphere/box) | ball | | spawn_spread | Размер области спавна частиц. | {0, 0, 0} | | random_sub_uv | Размер случайного подрегиона текстуры (1 - будет использована вся текстура). | 1.0 | diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md index 1e0cc6c0..ad0e5b2a 100644 --- a/doc/ru/scripting/builtins/libentities.md +++ b/doc/ru/scripting/builtins/libentities.md @@ -15,7 +15,8 @@ entities.get(uid: int) -> table -- префикс - id пака -- имя - название компонента -- префикс и имя компонента разделяются двумя подчеркиваниями -entities.spawn(name: str, pos: vec3, [optional] args: table) +-- Возвращает обьект сущности +entities.spawn(name: str, pos: vec3, [optional] args: table) -> table -- Проверяет наличие сущности по уникальному идентификатору. entities.exists(uid: int) -> bool diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index d0ddd365..ca165b0c 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -46,6 +46,13 @@ table.remove_value(t: table, x: object) Удаляет элемент **x** из **t**. +```lua +table.shuffle(t: table) -> table +``` + +Перемешивает значения в таблице. + + ```lua table.tostring(t: table) -> string ``` @@ -146,6 +153,18 @@ math.rand(low, high) Возвращает случайное дробное число в диапазоне от **low** до **high**. +```lua +math.normalize(num: number, [опционально] conf: num) -> number +``` + +Возвращает нормализованное значение num относительно conf. + +```lua +math.round(num: number, [опционально] places: num) -> number +``` + +Возвращает округлённое значение num до указанного количества знаков после запятой places. + ## Дополнительные глобальные функции В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 1523f94a..7f7be5c4 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -27,6 +27,7 @@ * [Расстановка малых структур](#расстановка-малых-структур) * [Расстановка 'широких' структур](#расстановка-широких-структур) - [Структурный воздух](#структурный-воздух) +- [Генератор 'Demo' (base:demo)](#генератор-demo-basedemo) ## Основные понятия @@ -52,6 +53,7 @@ - **biomes-bpd** - количество блоков на точку карты параметра выбора биомов. По-умолчанию: 4. - **heights-bpd** - количество блоков на точку карты высот. По-умолчанию: 4. - **wide-structs-chunks-radius** - масимальный радиус размещения 'широких' структур, измеряемый в чанках. +- **heightmap-inputs** - массив номеров карт параметров, которые будут переданы таблицей inputs в функцию генерации карты высот. ## Глобальные переменные @@ -477,3 +479,23 @@ function place_structures_wide( `core:struct_air` - блок, которые следует использовать в фрагментах для обозначения пустого пространства, которое не должно заполняться блоками при генерации в мире. + +# Генератор 'Demo' (base:demo) + +## Добавление новой руды + +Чтобы добавить новую руду из своего пака: +1. В папке `generators` создайте папку `demo.files` (demo.toml создавать не нужно). +2. В созданной папке создайте папку fragments и поместите в неё файл фрагмента руды. +3. В `demo.files` создайте файл structures.toml: +```toml +имя_фрагмента = {} +``` +4. Также в `demo.files` создайте файл ores.json: +```json +[ + {"struct": "имя_фрагмента", "rarity": редкость} +] +``` +Чем выше значение редкости, тем меньше вероятность генерации руды. +Опираться можно на редкость угольной руды: 4400. diff --git a/res/content/base/blocks/leaves.json b/res/content/base/blocks/leaves.json index cdf9f014..4ea4832f 100644 --- a/res/content/base/blocks/leaves.json +++ b/res/content/base/blocks/leaves.json @@ -1,5 +1,26 @@ { "texture": "leaves", "material": "base:grass", + "draw-group": 5, + "culling": "optional", + "particles": { + "lifetime": 4.0, + "spawn_interval": 1000.0, + "acceleration": [0, -0.1, 0], + "velocity": [0.2, -2.5, 0.3], + "explosion": [0, 0, 0], + "collision": false, + "size": [0.3, 0.3, 0.3], + "size_spread": 0.2, + "spawn_shape": "box", + "spawn_spread": [0.2, 0.2, 0.2], + "angle_spread": 1.0, + "min_angular_vel": 0.5, + "max_angular_vel": 2.0, + "lighting": true, + "frames": [ + "particles:leaf_0" + ] + }, "base:durability": 0.7 } diff --git a/res/content/base/textures/blocks/leaves.png b/res/content/base/textures/blocks/leaves.png index 3beaf7ab..46b38d22 100644 Binary files a/res/content/base/textures/blocks/leaves.png and b/res/content/base/textures/blocks/leaves.png differ diff --git a/res/content/base/textures/blocks/leaves_opaque.png b/res/content/base/textures/blocks/leaves_opaque.png new file mode 100644 index 00000000..3beaf7ab Binary files /dev/null and b/res/content/base/textures/blocks/leaves_opaque.png differ diff --git a/res/content/base/textures/particles/leaf_0.png b/res/content/base/textures/particles/leaf_0.png new file mode 100644 index 00000000..282fff80 Binary files /dev/null and b/res/content/base/textures/particles/leaf_0.png differ diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 8a479a1c..c4503e52 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -41,4 +41,5 @@ function on_open() create_setting("graphics.fog-curve", "Fog Curve", 0.1) create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") + create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") end diff --git a/res/modules/data_buffer.lua b/res/modules/data_buffer.lua index e3d207ff..91dce29c 100644 --- a/res/modules/data_buffer.lua +++ b/res/modules/data_buffer.lua @@ -72,9 +72,14 @@ function data_buffer:put_byte(byte) end function data_buffer:put_bytes(bytes) - for i = 1, #bytes do - self:put_byte(bytes[i]) - end + if type(self.bytes) == 'table' then + for i = 1, #bytes do + self:put_byte(bytes[i]) + end + else + self.bytes:insert(self.pos, bytes) + self.pos = self.pos + #bytes + end end function data_buffer:put_single(single) @@ -308,4 +313,4 @@ end setmetatable(data_buffer, data_buffer) -return data_buffer \ No newline at end of file +return data_buffer diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 13934e06..5b93fa2f 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,6 +1,6 @@ -- Check if given table is an array function is_array(x) - if #t > 0 then + if #x > 0 then return true end for k, v in pairs(x) do @@ -51,6 +51,19 @@ function math.rand(low, high) return low + (high - low) * math.random() end +function math.normalize(num, conf) + conf = conf or 1 + + return (num / conf) % 1 +end + +function math.round(num, places) + places = places or 0 + + local mult = 10 ^ places + return math.floor(num * mult + 0.5) / mult +end + ---------------------------------------------- function table.copy(t) @@ -74,7 +87,7 @@ function table.deep_copy(t) end end - return copied + return setmetatable(copied, getmetatable(t)) end function table.count_pairs(t) @@ -91,6 +104,15 @@ function table.random(t) return t[math.random(1, #t)] end +function table.shuffle(t) + for i = #t, 2, -1 do + local j = math.random(i) + t[i], t[j] = t[j], t[i] + end + + return t +end + ---------------------------------------------- local pattern_escape_replacements = { diff --git a/res/shaders/lib/commons.glsl b/res/shaders/lib/commons.glsl index 0a9561b3..a3bdbbc7 100644 --- a/res/shaders/lib/commons.glsl +++ b/res/shaders/lib/commons.glsl @@ -17,7 +17,7 @@ vec3 pick_sky_color(samplerCube cubemap) { vec3 skyLightColor = texture(cubemap, vec3(0.4f, 0.0f, 0.4f)).rgb; skyLightColor *= SKY_LIGHT_TINT; skyLightColor = min(vec3(1.0), skyLightColor*SKY_LIGHT_MUL); - skyLightColor = max(MAX_SKY_LIGHT, skyLightColor); + skyLightColor = max(MIN_SKY_LIGHT, skyLightColor); return skyLightColor; } diff --git a/res/shaders/lib/constants.glsl b/res/shaders/lib/constants.glsl index ad119914..c7020584 100644 --- a/res/shaders/lib/constants.glsl +++ b/res/shaders/lib/constants.glsl @@ -10,7 +10,7 @@ // lighting #define SKY_LIGHT_MUL 2.9 #define SKY_LIGHT_TINT vec3(0.9, 0.8, 1.0) -#define MAX_SKY_LIGHT vec3(0.1, 0.11, 0.14) +#define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33) // fog #define FOG_POS_SCALE vec3(1.0, 0.2, 1.0) diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index c19fe00a..e9b23897 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -16,6 +16,7 @@ devtools.traceback=Traceback (most recent call first) # Tooltips graphics.gamma.tooltip=Lighting brightness curve graphics.backlight.tooltip=Backlight to prevent total darkness +graphics.dense-render.tooltip=Enables transparency in blocks like leaves # settings settings.Controls Search Mode=Search by attached button name diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index ae591042..e78b5f72 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -28,6 +28,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/ # Подсказки graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту +graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья. # Меню menu.Apply=Применить @@ -67,6 +68,7 @@ world.delete-confirm=Удалить мир безвозвратно? # Настройки settings.Ambient=Фон settings.Backlight=Подсветка +settings.Dense blocks render=Плотный рендер блоков settings.Camera Shaking=Тряска Камеры settings.Camera Inertia=Инерция Камеры settings.Camera FOV Effects=Эффекты поля зрения diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 1e954a55..1dc676ea 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -220,11 +220,11 @@ void ContentLoader::loadBlock( } // block model - std::string modelTypeName; + std::string modelTypeName = to_string(def.model); root.at("model").get(modelTypeName); root.at("model-name").get(def.modelName); if (auto model = BlockModel_from(modelTypeName)) { - if (*model == BlockModel::custom) { + if (*model == BlockModel::custom && def.customModelRaw == nullptr) { if (root.has("model-primitives")) { def.customModelRaw = root["model-primitives"]; } else if (def.modelName.empty()) { @@ -239,14 +239,22 @@ void ContentLoader::loadBlock( } def.model = *model; } else if (!modelTypeName.empty()) { - logger.error() << "unknown model " << modelTypeName; + logger.error() << "unknown model: " << modelTypeName; def.model = BlockModel::none; } + std::string cullingModeName = to_string(def.culling); + root.at("culling").get(cullingModeName); + if (auto mode = CullingMode_from(cullingModeName)) { + def.culling = *mode; + } else { + logger.error() << "unknown culling mode: " << cullingModeName; + } + root.at("material").get(def.material); // rotation profile - std::string profile = "none"; + std::string profile = def.rotations.name; root.at("rotation").get(profile); def.rotatable = profile != "none"; @@ -285,8 +293,6 @@ void ContentLoader::loadBlock( ); aabb.b += aabb.a; def.hitboxes = {aabb}; - } else { - def.hitboxes = {AABB()}; } // block light emission [r, g, b] where r,g,b in range [0..15] diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index d2a85f6a..fe251a11 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -68,6 +68,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.section("graphics"); builder.add("fog-curve", &settings.graphics.fogCurve); builder.add("backlight", &settings.graphics.backlight); + builder.add("dense-render", &settings.graphics.denseRender); builder.add("gamma", &settings.graphics.gamma); builder.add("frustum-culling", &settings.graphics.frustumCulling); builder.add("skybox-resolution", &settings.graphics.skyboxResolution); diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index c6ba4e1c..844fd4e6 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -6,53 +6,71 @@ #include "assets/Assets.hpp" #include "content/Content.hpp" #include "content/ContentPack.hpp" -#include "core_defs.hpp" #include "graphics/core/Atlas.hpp" #include "maths/UVRegion.hpp" #include "voxels/Block.hpp" +#include "core_defs.hpp" +#include "settings.hpp" -ContentGfxCache::ContentGfxCache(const Content* content, const Assets& assets) - : content(content) { - auto indices = content->getIndices(); + +ContentGfxCache::ContentGfxCache( + const Content& content, + const Assets& assets, + const GraphicsSettings& settings +) + : content(content), assets(assets), settings(settings) { + refresh(); +} + +void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { + for (uint side = 0; side < 6; side++) { + std::string tex = def.textureFaces[side]; + if (def.culling == CullingMode::OPTIONAL && + !settings.denseRender.get() && atlas.has(tex + "_opaque")) { + tex = tex + "_opaque"; + } + if (atlas.has(tex)) { + sideregions[def.rt.id * 6 + side] = atlas.get(tex); + } else if (atlas.has(TEXTURE_NOTFOUND)) { + sideregions[def.rt.id * 6 + side] = atlas.get(TEXTURE_NOTFOUND); + } + } + if (def.model == BlockModel::custom) { + auto model = assets.require(def.modelName); + // temporary dirty fix tbh + if (def.modelName.find(':') == std::string::npos) { + for (auto& mesh : model.meshes) { + size_t pos = mesh.texture.find(':'); + if (pos == std::string::npos) { + continue; + } + if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { + for (auto& vertex : mesh.vertices) { + vertex.uv = region->apply(vertex.uv); + } + } + } + } + models[def.rt.id] = std::move(model); + } +} + +void ContentGfxCache::refresh() { + auto indices = content.getIndices(); sideregions = std::make_unique(indices->blocks.count() * 6); const auto& atlas = assets.require("blocks"); const auto& blocks = indices->blocks.getIterable(); for (blockid_t i = 0; i < blocks.size(); i++) { auto def = blocks[i]; - for (uint side = 0; side < 6; side++) { - const std::string& tex = def->textureFaces[side]; - if (atlas.has(tex)) { - sideregions[i * 6 + side] = atlas.get(tex); - } else if (atlas.has(TEXTURE_NOTFOUND)) { - sideregions[i * 6 + side] = atlas.get(TEXTURE_NOTFOUND); - } - } - if (def->model == BlockModel::custom) { - auto model = assets.require(def->modelName); - // temporary dirty fix tbh - if (def->modelName.find(':') == std::string::npos) { - for (auto& mesh : model.meshes) { - size_t pos = mesh.texture.find(':'); - if (pos == std::string::npos) { - continue; - } - if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { - for (auto& vertex : mesh.vertices) { - vertex.uv = region->apply(vertex.uv); - } - } - } - } - models[def->rt.id] = std::move(model); - } + refresh(*def, atlas); } } ContentGfxCache::~ContentGfxCache() = default; const Content* ContentGfxCache::getContent() const { - return content; + return &content; } const model::Model& ContentGfxCache::getModel(blockid_t id) const { diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index 96d46ea5..cb5e2ad7 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -10,19 +10,29 @@ class Content; class Assets; +class Atlas; +class Block; struct UVRegion; +struct GraphicsSettings; namespace model { struct Model; } class ContentGfxCache { - const Content* content; + const Content& content; + const Assets& assets; + const GraphicsSettings& settings; + // array of block sides uv regions (6 per block) std::unique_ptr sideregions; std::unordered_map models; public: - ContentGfxCache(const Content* content, const Assets& assets); + ContentGfxCache( + const Content& content, + const Assets& assets, + const GraphicsSettings& settings + ); ~ContentGfxCache(); inline const UVRegion& getRegion(blockid_t id, int side) const { @@ -32,4 +42,8 @@ public: const model::Model& getModel(blockid_t id) const; const Content* getContent() const; + + void refresh(const Block& block, const Atlas& atlas); + + void refresh(); }; diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 79784375..d3cbb9e8 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -14,12 +14,17 @@ #include "world/Level.hpp" LevelFrontend::LevelFrontend( - Player* currentPlayer, LevelController* controller, Assets& assets -) : level(*controller->getLevel()), - controller(controller), - assets(assets), - contentCache(std::make_unique(level.content, assets)) -{ + Player* currentPlayer, + LevelController* controller, + Assets& assets, + const EngineSettings& settings +) + : level(*controller->getLevel()), + controller(controller), + assets(assets), + contentCache(std::make_unique( + *level.content, assets, settings.graphics + )) { assets.store( BlocksPreview::build( *contentCache, assets, *level.content->getIndices() @@ -98,6 +103,10 @@ const Assets& LevelFrontend::getAssets() const { return assets; } +ContentGfxCache& LevelFrontend::getContentGfxCache() { + return *contentCache; +} + const ContentGfxCache& LevelFrontend::getContentGfxCache() const { return *contentCache; } diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index 163fc678..b53d267a 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -7,6 +7,7 @@ class Assets; class Player; class ContentGfxCache; class LevelController; +struct EngineSettings; class LevelFrontend { Level& level; @@ -14,12 +15,18 @@ class LevelFrontend { const Assets& assets; std::unique_ptr contentCache; public: - LevelFrontend(Player* currentPlayer, LevelController* controller, Assets& assets); + LevelFrontend( + Player* currentPlayer, + LevelController* controller, + Assets& assets, + const EngineSettings& settings + ); ~LevelFrontend(); Level& getLevel(); const Level& getLevel() const; const Assets& getAssets() const; const ContentGfxCache& getContentGfxCache() const; + ContentGfxCache& getContentGfxCache(); LevelController* getController() const; }; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 21111e18..01252c4c 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -47,6 +47,7 @@ #include "window/Window.hpp" #include "world/Level.hpp" #include "world/World.hpp" +#include "debug/Logger.hpp" #include #include @@ -56,6 +57,8 @@ using namespace gui; +static debug::Logger logger("hud"); + bool Hud::showGeneratorMinimap = false; // implemented in debug_panel.cpp @@ -485,7 +488,32 @@ void Hud::openPermanent(UiDocument* doc) { add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false)); } +void Hud::dropExchangeSlot() { + auto slotView = std::dynamic_pointer_cast( + gui->get(SlotView::EXCHANGE_SLOT_NAME) + ); + if (slotView == nullptr) { + return; + } + ItemStack& stack = slotView->getStack(); + + auto indices = frontend.getLevel().content->getIndices(); + if (auto invView = std::dynamic_pointer_cast(blockUI)) { + invView->getInventory()->move(stack, indices); + } + if (stack.isEmpty()) { + return; + } + player->getInventory()->move(stack, indices); + if (!stack.isEmpty()) { + logger.warning() << "discard item [" << stack.getItemId() << ":" + << stack.getCount(); + stack.clear(); + } +} + void Hud::closeInventory() { + dropExchangeSlot(); gui->remove(SlotView::EXCHANGE_SLOT_NAME); exchangeSlot = nullptr; exchangeSlotInv = nullptr; diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index 5594664a..ad834955 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -128,6 +128,9 @@ class Hud : public util::ObjectsKeeper { void updateHotbarControl(); void cleanup(); + /// @brief Perform exchange slot removal when it's not empty. + void dropExchangeSlot(); + void showExchangeSlot(); void updateWorldGenDebugVisualization(); public: diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 34d1db67..e38b3bec 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -17,6 +17,7 @@ #include "graphics/render/Decorator.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/GUI.hpp" +#include "frontend/ContentGfxCache.hpp" #include "logic/LevelController.hpp" #include "logic/scripting/scripting_hud.hpp" #include "util/stringutil.hpp" @@ -42,7 +43,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr levelPtr) controller = std::make_unique(engine, std::move(levelPtr)); frontend = std::make_unique( - controller->getPlayer(), controller.get(), assets + controller->getPlayer(), controller.get(), assets, settings ); worldRenderer = std::make_unique( engine, *frontend, controller->getPlayer() @@ -57,6 +58,11 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr levelPtr) controller->getLevel()->chunks->saveAndClear(); worldRenderer->clear(); })); + keepAlive(settings.graphics.denseRender.observe([=](bool) { + controller->getLevel()->chunks->saveAndClear(); + worldRenderer->clear(); + frontend->getContentGfxCache().refresh(); + })); keepAlive(settings.camera.fov.observe([=](double value) { controller->getPlayer()->fpCamera->setFov(glm::radians(value)); })); diff --git a/src/graphics/core/GLTexture.cpp b/src/graphics/core/GLTexture.cpp index c5a918ed..3756d758 100644 --- a/src/graphics/core/GLTexture.cpp +++ b/src/graphics/core/GLTexture.cpp @@ -25,7 +25,7 @@ GLTexture::GLTexture(const ubyte* data, uint width, uint height, ImageFormat ima glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index cb590bcb..56f1c4e2 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -8,9 +8,6 @@ #include "voxels/Chunks.hpp" #include "lighting/Lightmap.hpp" #include "frontend/ContentGfxCache.hpp" -#include "settings.hpp" - -#include const glm::vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f); @@ -342,41 +339,41 @@ void BlocksRenderer::blockCube( } if (ao) { - if (isOpen(coord + Z, group)) { + if (isOpen(coord + Z, block)) { faceAO(coord, X, Y, Z, texfaces[5], lights); } - if (isOpen(coord - Z, group)) { + if (isOpen(coord - Z, block)) { faceAO(coord, -X, Y, -Z, texfaces[4], lights); } - if (isOpen(coord + Y, group)) { + if (isOpen(coord + Y, block)) { faceAO(coord, X, -Z, Y, texfaces[3], lights); } - if (isOpen(coord - Y, group)) { + if (isOpen(coord - Y, block)) { faceAO(coord, X, Z, -Y, texfaces[2], lights); } - if (isOpen(coord + X, group)) { + if (isOpen(coord + X, block)) { faceAO(coord, -Z, Y, X, texfaces[1], lights); } - if (isOpen(coord - X, group)) { + if (isOpen(coord - X, block)) { faceAO(coord, Z, Y, -X, texfaces[0], lights); } } else { - if (isOpen(coord + Z, group)) { + if (isOpen(coord + Z, block)) { face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights); } - if (isOpen(coord - Z, group)) { + if (isOpen(coord - Z, block)) { face(coord, -X, Y, -Z, texfaces[4], pickLight(coord - Z), lights); } - if (isOpen(coord + Y, group)) { + if (isOpen(coord + Y, block)) { face(coord, X, -Z, Y, texfaces[3], pickLight(coord + Y), lights); } - if (isOpen(coord - Y, group)) { + if (isOpen(coord - Y, block)) { face(coord, X, Z, -Y, texfaces[2], pickLight(coord - Y), lights); } - if (isOpen(coord + X, group)) { + if (isOpen(coord + X, block)) { face(coord, -Z, Y, X, texfaces[1], pickLight(coord + X), lights); } - if (isOpen(coord - X, group)) { + if (isOpen(coord - X, block)) { face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights); } } diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index c0e0086e..c652b6a0 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -13,6 +13,7 @@ #include "graphics/core/MeshData.hpp" #include "maths/util.hpp" #include "commons.hpp" +#include "settings.hpp" class Content; class Mesh; @@ -22,7 +23,6 @@ class Chunks; class VoxelsVolume; class Chunks; class ContentGfxCache; -struct EngineSettings; struct UVRegion; class BlocksRenderer { @@ -118,7 +118,7 @@ class BlocksRenderer { bool isOpenForLight(int x, int y, int z) const; // Does block allow to see other blocks sides (is it transparent) - inline bool isOpen(const glm::ivec3& pos, ubyte group) const { + inline bool isOpen(const glm::ivec3& pos, const Block& def) const { auto id = voxelsBuffer->pickBlockId( chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z ); @@ -126,7 +126,13 @@ class BlocksRenderer { return false; } const auto& block = *blockDefsCache[id]; - if ((block.drawGroup != group && block.lightPassing) || !block.rt.solid) { + if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) { + return true; + } + if ((def.culling == CullingMode::DISABLED || + (def.culling == CullingMode::OPTIONAL && + settings.graphics.denseRender.get())) && + id == def.rt.id) { return true; } return !id; diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index b7bd4218..95ccbcc2 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -99,9 +99,8 @@ void Decorator::update( void Decorator::update(float delta, const Camera& camera) { glm::ivec3 pos = camera.position; - pos -= glm::ivec3(UPDATE_AREA_DIAMETER / 2); for (int i = 0; i < ITERATIONS; i++) { - update(delta, pos, camera.position); + update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos); } const auto& chunks = *level.chunks; const auto& indices = *level.content->getIndices(); diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp index fbc196aa..422f6f77 100644 --- a/src/graphics/render/Decorator.hpp +++ b/src/graphics/render/Decorator.hpp @@ -14,7 +14,7 @@ class Chunks; class Camera; class Assets; class Player; -struct Block; +class Block; class Engine; class LevelController; class WorldRenderer; diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp index 82848a63..e908ece8 100644 --- a/src/graphics/render/Emitter.cpp +++ b/src/graphics/render/Emitter.cpp @@ -19,12 +19,13 @@ Emitter::Emitter( ) : level(level), origin(std::move(origin)), - prototype({this, 0, glm::vec3(), preset.velocity, preset.lifetime, region}), + prototype({this, 0, {}, preset.velocity, preset.lifetime, region}), texture(texture), count(count), preset(std::move(preset)) { + random.setSeed(reinterpret_cast(this)); this->prototype.emitter = this; - timer = preset.spawnInterval; + timer = preset.spawnInterval * random.randFloat(); } const Texture* Emitter::getTexture() const { @@ -76,6 +77,10 @@ void Emitter::update( count = std::max(0, count - skipped); timer -= skipped * spawnInterval; } + if (count < 0) { + int skipped = timer / spawnInterval; + timer -= skipped * spawnInterval; + } return; } while (count && timer > spawnInterval) { @@ -83,6 +88,15 @@ void Emitter::update( Particle particle = prototype; particle.emitter = this; particle.random = random.rand32(); + if (glm::abs(preset.angleSpread) >= 0.005f) { + particle.angle = + random.randFloat() * preset.angleSpread * glm::pi() * 2; + } + particle.angularVelocity = + (preset.minAngularVelocity + + random.randFloat() * + (preset.maxAngularVelocity - preset.minAngularVelocity)) * + ((random.rand() % 2) * 2 - 1); glm::vec3 spawnOffset = generate_coord(preset.spawnShape); spawnOffset *= preset.spawnSpread; @@ -103,6 +117,7 @@ void Emitter::update( if (count > 0) { count--; } + refCount++; } } @@ -114,6 +129,10 @@ bool Emitter::isDead() const { return count == 0; } +bool Emitter::isReferred() const { + return refCount > 0; +} + const EmitterOrigin& Emitter::getOrigin() const { return origin; } diff --git a/src/graphics/render/Emitter.hpp b/src/graphics/render/Emitter.hpp index c1d6c168..ce1ec8f4 100644 --- a/src/graphics/render/Emitter.hpp +++ b/src/graphics/render/Emitter.hpp @@ -27,6 +27,10 @@ struct Particle { float lifetime; /// @brief UV region UVRegion region; + /// @brief Current rotation angle + float angle; + /// @brief Angular velocity + float angularVelocity; }; class Texture; @@ -39,7 +43,7 @@ class Emitter { EmitterOrigin origin; /// @brief Particle prototype Particle prototype; - /// @brief Particle + /// @brief Particle texture const Texture* texture; /// @brief Number of particles should be spawned before emitter deactivation. /// -1 is infinite. @@ -50,6 +54,9 @@ class Emitter { util::PseudoRandom random; public: + /// @brief Number of references (alive particles) + int refCount = 0; + /// @brief Particle settings ParticlesPreset preset; Emitter( @@ -82,6 +89,9 @@ public: /// @return true if the emitter has spawned all particles bool isDead() const; + /// @return true if there is at least one alive referring particle left + bool isReferred() const; + const EmitterOrigin& getOrigin() const; void setOrigin(const EmitterOrigin& origin); diff --git a/src/graphics/render/ModelsGenerator.hpp b/src/graphics/render/ModelsGenerator.hpp index 52bc56d0..c4665118 100644 --- a/src/graphics/render/ModelsGenerator.hpp +++ b/src/graphics/render/ModelsGenerator.hpp @@ -7,7 +7,7 @@ struct ItemDef; class Assets; class Content; -struct Block; +class Block; class ModelsGenerator { public: diff --git a/src/graphics/render/ParticlesRenderer.cpp b/src/graphics/render/ParticlesRenderer.cpp index 0e2292a5..e96eb615 100644 --- a/src/graphics/render/ParticlesRenderer.cpp +++ b/src/graphics/render/ParticlesRenderer.cpp @@ -31,12 +31,14 @@ static inline void update_particle( const auto& preset = particle.emitter->preset; auto& pos = particle.position; auto& vel = particle.velocity; + auto& angle = particle.angle; vel += delta * preset.acceleration; if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) { vel *= 0.0f; } pos += vel * delta; + angle += particle.angularVelocity * delta; particle.lifetime -= delta; } @@ -61,7 +63,8 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { auto iter = vec.begin(); while (iter != vec.end()) { auto& particle = *iter; - auto& preset = particle.emitter->preset; + auto& emitter = *particle.emitter; + auto& preset = emitter.preset; if (!preset.frames.empty()) { float time = preset.lifetime - particle.lifetime; @@ -82,19 +85,52 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { } update_particle(particle, delta, chunks); + float scale = 1.0f + ((particle.random ^ 2628172) % 1000) * + 0.001f * preset.sizeSpread; + glm::vec4 light(1, 1, 1, 0); if (preset.lighting) { light = MainBatch::sampleLight( - particle.position, chunks, backlight + particle.position, + chunks, + backlight ); + auto size = glm::max(glm::vec3(0.5f), preset.size * scale); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + light = glm::max( + light, + MainBatch::sampleLight( + particle.position - + size * glm::vec3(x, y, z), + chunks, + backlight + ) + ); + } + } + } light *= 0.9f + (particle.random % 100) * 0.001f; } - float scale = 1.0f + ((particle.random ^ 2628172) % 1000) * - 0.001f * preset.sizeSpread; + + + glm::vec3 localRight = right; + glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up; + float angle = particle.angle; + if (glm::abs(angle) >= 0.005f) { + glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f); + glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f); + + localRight = right * rotatedRight.x + localUp * rotatedRight.y + + camera.front * rotatedRight.z; + localUp = right * rotatedUp.x + localUp * rotatedUp.y + + camera.front * rotatedUp.z; + } batch->quad( particle.position, - right, - preset.globalUpVector ? glm::vec3(0, 1, 0) : up, + localRight, + localUp, preset.size * scale, light, glm::vec3(1.0f), @@ -102,6 +138,7 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { ); if (particle.lifetime <= 0.0f) { iter = vec.erase(iter); + emitter.refCount--; } else { iter++; } @@ -124,19 +161,15 @@ void ParticlesRenderer::render(const Camera& camera, float delta) { auto iter = emitters.begin(); while (iter != emitters.end()) { auto& emitter = *iter->second; + if (emitter.isDead() && !emitter.isReferred()) { + // destruct Emitter only when there is no particles spawned by it + iter = emitters.erase(iter); + continue; + } auto texture = emitter.getTexture(); const auto& found = particles.find(texture); std::vector* vec; - if (found == particles.end()) { - if (emitter.isDead()) { - // destruct Emitter only when there is no particles spawned by it - iter = emitters.erase(iter); - continue; - } - vec = &particles[texture]; - } else { - vec = &found->second; - } + vec = &particles[texture]; emitter.update(delta, camera.position, *vec); iter++; } diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index a27ac949..a38081c9 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -149,27 +149,6 @@ static int l_read_bytes(lua::State* L) { ); } -static void read_bytes_from_table( - lua::State* L, int tableIndex, std::vector& bytes -) { - if (!lua::istable(L, tableIndex)) { - throw std::runtime_error("table expected"); - } else { - size_t size = lua::objlen(L, tableIndex); - for (size_t i = 0; i < size; i++) { - lua::rawgeti(L, i + 1, tableIndex); - const int byte = lua::tointeger(L, -1); - lua::pop(L); - if (byte < 0 || byte > 255) { - throw std::runtime_error( - "invalid byte '" + std::to_string(byte) + "'" - ); - } - bytes.push_back(byte); - } - } -} - static int l_write_bytes(lua::State* L) { fs::path path = get_writeable_path(L); @@ -181,7 +160,7 @@ static int l_write_bytes(lua::State* L) { } std::vector bytes; - read_bytes_from_table(L, 2, bytes); + lua::read_bytes_from_table(L, 2, bytes); return lua::pushboolean( L, files::write_bytes(path, bytes.data(), bytes.size()) ); @@ -223,7 +202,7 @@ static int l_list(lua::State* L) { static int l_gzip_compress(lua::State* L) { std::vector bytes; - read_bytes_from_table(L, 1, bytes); + lua::read_bytes_from_table(L, 1, bytes); auto compressed_bytes = gzip::compress(bytes.data(), bytes.size()); int newTable = lua::gettop(L); @@ -237,7 +216,7 @@ static int l_gzip_compress(lua::State* L) { static int l_gzip_decompress(lua::State* L) { std::vector bytes; - read_bytes_from_table(L, 1, bytes); + lua::read_bytes_from_table(L, 1, bytes); auto decompressed_bytes = gzip::decompress(bytes.data(), bytes.size()); int newTable = lua::gettop(L); diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 983abb39..f9da3b21 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "data/dv.hpp" @@ -698,4 +699,25 @@ namespace lua { } return def; } + + inline void read_bytes_from_table( + lua::State* L, int tableIndex, std::vector& bytes + ) { + if (!lua::istable(L, tableIndex)) { + throw std::runtime_error("table expected"); + } else { + size_t size = lua::objlen(L, tableIndex); + for (size_t i = 0; i < size; i++) { + lua::rawgeti(L, i + 1, tableIndex); + const int byte = lua::tointeger(L, -1); + lua::pop(L); + if (byte < 0 || byte > 255) { + throw std::runtime_error( + "invalid byte '" + std::to_string(byte) + "'" + ); + } + bytes.push_back(byte); + } + } + } } diff --git a/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp b/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp index 0493995b..fc7ecf6f 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp @@ -2,6 +2,7 @@ #include +#include "util/listutil.hpp" #include "../lua_util.hpp" using namespace lua; @@ -18,8 +19,16 @@ LuaBytearray::~LuaBytearray() { static int l_append(lua::State* L) { if (auto buffer = touserdata(L, 1)) { - auto value = tointeger(L, 2); - buffer->data().push_back(static_cast(value)); + if (lua::isnumber(L, 2)) { + auto value = tointeger(L, 2); + buffer->data().push_back(static_cast(value)); + } else if (lua::istable(L, 2)) { + lua::read_bytes_from_table(L, 2, buffer->data()); + } else if (auto extension = lua::touserdata(L, 2)) { + util::concat(buffer->data(), extension->data()); + } else { + throw std::runtime_error("integer/table/Bytearray expected"); + } } return 0; } @@ -34,8 +43,19 @@ static int l_insert(lua::State* L) { if (static_cast(index) > data.size()) { return 0; } - auto value = tointeger(L, 3); - data.insert(data.begin() + index, static_cast(value)); + if (lua::isnumber(L, 3)) { + auto value = tointeger(L, 3); + data.insert(data.begin() + index, static_cast(value)); + } else if (lua::istable(L, 3)) { + std::vector temp; + lua::read_bytes_from_table(L, 3, temp); + data.insert(data.begin() + index, temp.begin(), temp.end()); + } else if (auto extension = lua::touserdata(L, 3)) { + const std::vector& src = extension->data(); + data.insert(data.begin() + index, src.begin(), src.end()); + } else { + throw std::runtime_error("integer/table/Bytearray expected"); + } return 0; } diff --git a/src/maths/UVFace.hpp b/src/maths/UVFace.hpp new file mode 100644 index 00000000..8a0815bb --- /dev/null +++ b/src/maths/UVFace.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "UVRegion.hpp" + +struct UVFace { + std::array points; + + UVFace(const UVRegion& region) { + points[0] = {region.u1, region.v1}; + points[1] = {region.u2, region.v1}; + points[2] = {region.u2, region.v2}; + points[3] = {region.u1, region.v2}; + } + + template + inline void rotate() { + int times = n % 4; + if (times < 0) { + times += 4; + } + std::array temp = points; + points[0] = temp[times]; + points[1] = temp[(times + 1) % 4]; + points[2] = temp[(times + 2) % 4]; + points[3] = temp[(times + 3) % 4]; + } +}; diff --git a/src/maths/util.hpp b/src/maths/util.hpp index ec128917..df39cf41 100644 --- a/src/maths/util.hpp +++ b/src/maths/util.hpp @@ -9,6 +9,12 @@ #include namespace util { + inline uint64_t shuffle_bits_step(uint64_t x, uint64_t m, unsigned shift) { + uint64_t t = ((x >> shift) ^ x) & m; + x = (x ^ t) ^ (t << shift); + return x; + } + constexpr inline float EPSILON = 1e-6f; class PseudoRandom { @@ -57,17 +63,20 @@ namespace util { return randU64() / static_cast(UINT64_MAX); } - void setSeed(int number) { - seed = (static_cast(number * 23729) ^ - static_cast(number + 16786)); - rand(); - } void setSeed(int number1, int number2) { seed = ((static_cast(number1 * 23729) | static_cast(number2 % 16786)) ^ static_cast(number2 * number1)); rand(); } + + void setSeed(long number) { + number = shuffle_bits_step(number, 0x2222222222222222ull, 1); + number = shuffle_bits_step(number, 0x0c0c0c0c0c0c0c0cull, 2); + number = shuffle_bits_step(number, 0x00f000f000f000f0ull, 4); + seed = number; + rand(); + } }; template diff --git a/src/presets/ParticlesPreset.cpp b/src/presets/ParticlesPreset.cpp index 181dbeb5..8a007076 100644 --- a/src/presets/ParticlesPreset.cpp +++ b/src/presets/ParticlesPreset.cpp @@ -43,6 +43,9 @@ dv::value ParticlesPreset::serialize() const { root["explosion"] = dv::to_value(explosion); root["size"] = dv::to_value(size); root["size_spread"] = sizeSpread; + root["angle_spread"] = angleSpread; + root["min_angular_vel"] = minAngularVelocity; + root["max_angular_vel"] = maxAngularVelocity; root["spawn_spread"] = dv::to_value(size); root["spawn_shape"] = to_string(spawnShape); root["random_sub_uv"] = randomSubUV; @@ -58,6 +61,9 @@ void ParticlesPreset::deserialize(const dv::value& src) { src.at("spawn_interval").get(spawnInterval); src.at("lifetime").get(lifetime); src.at("lifetime_spread").get(lifetimeSpread); + src.at("angle_spread").get(angleSpread); + src.at("min_angular_vel").get(minAngularVelocity); + src.at("max_angular_vel").get(maxAngularVelocity); src.at("random_sub_uv").get(randomSubUV); if (src.has("velocity")) { dv::get_vec(src["velocity"], velocity); diff --git a/src/presets/ParticlesPreset.hpp b/src/presets/ParticlesPreset.hpp index f8d5f2b4..2b955d13 100644 --- a/src/presets/ParticlesPreset.hpp +++ b/src/presets/ParticlesPreset.hpp @@ -27,7 +27,7 @@ struct ParticlesPreset : public Serializable { /// @brief Use global up vector instead of camera-dependent one bool globalUpVector = false; /// @brief Max distance of actually spawning particles. - float maxDistance = 16.0f; + float maxDistance = 32.0f; /// @brief Particles spawn interval float spawnInterval = 0.1f; /// @brief Particle life time @@ -44,6 +44,12 @@ struct ParticlesPreset : public Serializable { glm::vec3 size {0.1f}; /// @brief Particles size spread float sizeSpread = 0.2f; + /// @brief Random initial angle spread + float angleSpread = 0.0f; + /// @brief Minimum angular velocity + float minAngularVelocity = 0.0f; + /// @brief Maximum angular velocity + float maxAngularVelocity = 0.0f; /// @brief Spawn spread shape ParticleSpawnShape spawnShape = BALL; /// @brief Spawn spread diff --git a/src/settings.hpp b/src/settings.hpp index b0eee035..48d72b27 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -63,16 +63,22 @@ struct GraphicsSettings { NumberSetting gamma {1.0f, 0.4f, 1.0f}; /// @brief Enable blocks backlight to prevent complete darkness FlagSetting backlight {true}; + /// @brief Disable culling with 'optional' mode + FlagSetting denseRender {true}; /// @brief Enable chunks frustum culling FlagSetting frustumCulling {true}; + /// @brief Skybox texture face resolution IntegerSetting skyboxResolution {64 + 32, 64, 128}; + /// @brief Chunk renderer vertices buffer capacity IntegerSetting chunkMaxVertices {200'000, 0, 4'000'000}; + /// @brief Limit of chunk renderers count IntegerSetting chunkMaxRenderers {6, -4, 32}; }; struct DebugSettings { /// @brief Turns off chunks saving/loading FlagSetting generatorTestMode {false}; + /// @brief Write lights cache FlagSetting doWriteLights {true}; }; diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 520f9f99..96bca245 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -49,6 +49,30 @@ std::optional BlockModel_from(std::string_view str) { return std::nullopt; } +std::string to_string(CullingMode mode) { + switch (mode) { + case CullingMode::DEFAULT: + return "default"; + case CullingMode::OPTIONAL: + return "optional"; + case CullingMode::DISABLED: + return "disabled"; + default: + return "unknown"; + } +} + +std::optional CullingMode_from(std::string_view str) { + if (str == "default") { + return CullingMode::DEFAULT; + } else if (str == "optional") { + return CullingMode::OPTIONAL; + } else if (str == "disabled") { + return CullingMode::DISABLED; + } + return std::nullopt; +} + CoordSystem::CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ) : axisX(axisX), axisY(axisY), axisZ(axisZ) { fix = glm::ivec3(0); diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index e7fb182c..eb232e8d 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -97,6 +97,15 @@ enum class BlockModel { std::string to_string(BlockModel model); std::optional BlockModel_from(std::string_view str); +enum class CullingMode { + DEFAULT, + OPTIONAL, + DISABLED, +}; + +std::string to_string(CullingMode mode); +std::optional CullingMode_from(std::string_view str); + using BoxModel = AABB; /// @brief Common kit of block properties applied to groups of blocks @@ -142,6 +151,9 @@ public: std::string modelName = ""; + /// @brief Culling mode + CullingMode culling = CullingMode::DEFAULT; + /// @brief Does the block passing lights into itself bool lightPassing = false; @@ -181,7 +193,7 @@ public: bool translucent = false; /// @brief Set of block physical hitboxes - std::vector hitboxes; + std::vector hitboxes {AABB()}; /// @brief Set of available block rotations (coord-systems) BlockRotProfile rotations = BlockRotProfile::NONE;