diff --git a/.gitignore b/.gitignore index c0b0e1c9d..4d0155832 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ appimage-build/ # libs /libs/ /vcpkg_installed/ + +# vcstudio +.vcstudio diff --git a/README.md b/README.md index e672ef541..1fd77cc8c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Installing last version that supports C++17. ```sh git clone --branch v3.16.0 https://github.com/skypjack/entt.git cd entt -mkdir build && cd build +mkdir -p build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=ON .. sudo make install ``` diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index d98a8cac5..61f44e844 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -69,6 +69,7 @@ Rotation profile (set of available block rotations and behaviour of placing bloc - "pipe" - wood logs, pipes, pillars - "pane" - panels, doors, signs - "stairs" - "pane" + flipped variants +- "ladder" - like a "pipe", but on horizontal surfaces it is installed like "pane" ### *particles* diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index c193fb188..48ec57e6d 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -12,7 +12,7 @@ Content-pack folder must contain file **package.json** with following contents: { "id": "pack_id", "title": "pack name will be displayed in the content menu", - "version": "content-pack version - major.minor", + "version": "content-pack version - major.minor.[patch])", "creator": "content-pack creator", "description": "short description", "dependencies": [ @@ -26,6 +26,7 @@ Dependency levels are indicated by prefixes in the name: - '!' - required dependency - '?' - optional dependency - '~' - weak dependency + If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md index 53d6e4ad2..a7ddbeaec 100644 --- a/doc/en/scripting/builtins/libgui.md +++ b/doc/en/scripting/builtins/libgui.md @@ -139,7 +139,7 @@ gui.confirm( ## Documents and templates ```lua --- Loads a UI document with its script, returns the name of the document if successfully loaded. +-- Loads a UI document with its script. Returns document environment table gui.load_document( -- Path to the xml file of the page. Example: `core:layouts/pages/main.xml` path: str, @@ -147,7 +147,7 @@ gui.load_document( name: str -- Table of parameters passed to the on_open event args: table -) -> str +) -> table -- Loads and processes layout template from file gui.template( diff --git a/doc/en/scripting/builtins/libhud.md b/doc/en/scripting/builtins/libhud.md index 1d6264b2d..550659ccc 100644 --- a/doc/en/scripting/builtins/libhud.md +++ b/doc/en/scripting/builtins/libhud.md @@ -57,6 +57,10 @@ hud.get_block_inventory() -> int -- Gives the ID of the second open inventory (block, virtual inventory...) or 0. hud.get_second_inventory() -> int +-- Get the inventory ID of exchange item (grabbed item in inventory) or 0. +-- It has only one slot - 0 +hud.get_exchange_inventory() -> int + -- Gives the ID of the player that the UI is bound to. hud.get_player() -> int diff --git a/doc/en/scripting/builtins/libquat.md b/doc/en/scripting/builtins/libquat.md index f7cc7a0f6..5d72413e2 100644 --- a/doc/en/scripting/builtins/libquat.md +++ b/doc/en/scripting/builtins/libquat.md @@ -2,14 +2,36 @@ Quaternions manipulation library. -## Quaternion from matrix - *mat4.from_quat(...)* +Quaternions can be created as a table of 4 numbers: +`{w, x, y, z}` + +## Quaternion from matrix - *quat.from_mat4(...)* ```lua -- creates a quaternion based on the rotation matrix -quat.from_mat4(m: matrix) +quat.from_mat4(m: matrix, [optional] dst: quat) -> quat +``` --- writes a quaternion from the rotation matrix to dst -quat.from_mat4(m: matrix, dst: quat) +## Quaternion of Euler angles - *quat.from_euler(...)* + +```lua +-- creates quaternion from euler angles passed as XYZ (pitch, yaw, roll) +-- angles in degrees +quat.from_euler(euler: vec3, [optional] dst: quat) -> quat +``` + +## Quaternion composition - quat.mul(...) + +```lua +-- multiplies quaternions +quat.mul(a: quat, b: quat, [optional] dst: quat) -> quat +``` + +## 3D vector rotation - quat.mul_vec3(...) + +```lua +-- rotates vector by quaternion +quat.mul_vec3(a: quat, b: vec3, [optional] dst: vec3) -> vec3 ``` ## Spherical linear interpolation - *quat.slerp(...)* @@ -19,16 +41,12 @@ The interpolation always take the short path and the rotation is performed at co ```lua -- creates a quaternion as an interpolation between a and b, -- where t is interpolation factor -quat.slerp(a: quat, b: quat, t: number) - --- writes a quaternion as an interpolation between a and b to dst, --- where t is interpolation factor -quat.slerp(a: quat, b: quat, t: number, dst: quat) +quat.slerp(a: quat, b: quat, t: number, [optional] dst: quat) -> quat ``` ## Casting to string - *quat.tostring(...)* ```lua -- returns a string representing the contents of the quaternion -quat.tostring(q: quat) +quat.tostring(q: quat) -> string ``` diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 75180edc3..9baa9fd85 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -24,7 +24,7 @@ Called on block broken by player function on_replaced(x, y, z, playerid) ``` -Called on block replaced with other by player +Called before a player replaces a block ```lua function on_interact(x, y, z, playerid) -> bool diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 2d338a5aa..7a11b2f5e 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -53,7 +53,7 @@ Properties that apply to all elements: | contentOffset | vec2 | yes | *no* | element content offset | | cursor | string | yes | yes | cursor displayed on hover | | parent | Element | yes | *no* | parent element or nil | -| zIndex | bool | yes | *no* | z-index value. In panel, it determines the order | +| zIndex | int | yes | *no* | z-index value. In panel, it determines the order | Common element methods: diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index cd868b404..3c0a774f3 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -212,10 +212,27 @@ local map = Heightmap(width, height) Operations apply to all height values. ```lua +-- Convert to absolute values map:abs() -``` -Casts height values ​​to absolute. +-- Round down +map:floor() + +-- Round up +map:ceil() + +-- Round to the nearest integer +map:round() + +-- Sine of height values ​​(in radians) +map:sin() + +-- Cosine of height values ​​(in radians) +map:cos() + +-- Tangent of height values ​​(in radians) +map:tan() +``` ### Binary Operations diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 0bd1b5353..704a0b786 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -39,6 +39,8 @@ Examples: - `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open* - `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called. - `onclick` - lua function called when an element is clicked. +- `onrightclick` - lua function called when the right mouse button is pressed on an element. +- `onmiddleclick` - lua function called when the middle mouse button is pressed on an element. - `ondoubleclick` - lua function called when you double click on an element. - `onfocus` - lua function called when focusing on an element. - `ondefocus` - lua function called when the element loses focus. @@ -192,6 +194,9 @@ Example of list description: - `width` - minimum content width. Default: 100. - `selected` - initially selected value. Default: "". - `onselect` - function to which the user-selected value is passed +- `mode` - behaviour / display mode: + - `select` - default mode. + - `button` - button mode: the text is not replaced with the selected option; the element rendered as a regular button. ## *bindbox* diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index e139f635d..6d613d888 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -70,6 +70,7 @@ - "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка - "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка - "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты) +- "ladder" - профиль "лестница" (как "pipe", но на горизонтальных поверхностях как "pane") ### Испускаемые частицы - *particles* diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 32f9be593..4ab002104 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -12,7 +12,7 @@ { "id": "выбранное_имя_пака", "title": "имя контент-пака для отображения в меню контента", - "version": "версия контент-пака в формате major.minor", + "version": "версия контент-пака в формате major.minor.[patch]", "creator": "создатель контент-пака", "description": "краткое описание", "dependencies": [ @@ -28,6 +28,7 @@ - '!' - обязательная зависимость - '?' - опциональная зависимость - '~' - слабая зависимость + Отсутствие префикса интерпретируется как '!'. Пример: '~randutil' - слабая зависимость 'randutil'. diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md index 9c2eb7418..ea1221415 100644 --- a/doc/ru/scripting/builtins/libgui.md +++ b/doc/ru/scripting/builtins/libgui.md @@ -140,12 +140,12 @@ gui.confirm( ## Документы и шаблоны ```lua --- Загружает UI документ и его скрипт. Возвращает имя документа. +-- Загружает UI документ и его скрипт. Возвращает пространство имён документа gui.load_document( path: string, -- путь к xml файлу, например: core:layouts/pages/main.xml name: string, -- id документа, например: core:pages/main args: table -- параметры для события on_open -) -> string +) -> table -- Обрабатывает xml шаблон макета из файла gui.template( diff --git a/doc/ru/scripting/builtins/libhud.md b/doc/ru/scripting/builtins/libhud.md index bad0e93c6..7a93c93e4 100644 --- a/doc/ru/scripting/builtins/libhud.md +++ b/doc/ru/scripting/builtins/libhud.md @@ -58,6 +58,11 @@ hud.get_block_inventory() -> int -- Дает ID второго открытого инвентаря (блок, виртуальный инвентарь...) или 0. hud.get_second_inventory() -> int +-- Получить ID инвентаря, в котором находится предмет во время +-- его перемещения в инвентаре, или 0 если его не существует. +-- Имеет единственный слот - 0 +hud.get_exchange_inventory() -> int + -- Дает ID игрока, к которому привязан пользовательский интерфейс. hud.get_player() -> int @@ -81,4 +86,4 @@ hud.set_allow_pause(flag: boolean) -- Функция, управляющая именованным скелетом 'hand' (см. gfx.skeletons) hud.hand_controller: function() -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/builtins/libquat.md b/doc/ru/scripting/builtins/libquat.md index e991de252..a32a24e6d 100644 --- a/doc/ru/scripting/builtins/libquat.md +++ b/doc/ru/scripting/builtins/libquat.md @@ -2,14 +2,14 @@ Библиотека для работы с кватернионами. +Кватернионы задаюся таблицей, состоящей из 4 компонентов: +`{w, x, y, z}` + ## Кватернион из матрицы - *quat.from_mat4(...)* ```lua -- создаёт кватернион на основе матрицы вращения -quat.from_mat4(m: matrix) -> quat - --- записывает кватернион по матрице вращения в dst -quat.from_mat4(m: matrix, dst: quat) +quat.from_mat4(m: matrix, [опционально] dst: quat) -> quat ``` ## Кватернион из углов Эйлера - *quat.from_euler(...)* @@ -17,11 +17,21 @@ quat.from_mat4(m: matrix, dst: quat) ```lua -- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) -- (значения углов строго в градусах) -quat.from_euler(euler: vec3) -> quat +quat.from_euler(euler: vec3, [опционально] dst: quat) -> quat +``` --- записывает кватернион в dst на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) --- (значения углов строго в градусах) -quat.from_euler(euler: vec3, dst: quat) +## Композиция кватернионов - quat.mul(...) + +```lua +-- Умножает два кватерниона (композиция поворотов) +quat.mul(a: quat, b: quat, [опционально] dst: quat) -> quat +``` + +## Поворот 3д вектора на кватернион - quat.mul_vec3(...) + +```lua +-- Поворачивает вектор на заданный кватернион +quat.mul_vec3(a: quat, b: vec3, [опционально] dst: vec3) -> vec3 ``` ## Сферическая линейная интерполяция - *quat.slerp(...)* @@ -31,11 +41,7 @@ quat.from_euler(euler: vec3, dst: quat) ```lua -- создаёт кватернион как интерполяцию между a и b, -- где t - фактор интерполяции -quat.slerp(a: quat, b: quat, t: number) -> quat - --- записывает кватернион как интерполяцию между a и b в dst, --- где t - фактор интерполяции -quat.slerp(a: quat, b: quat, t: number, dst: quat) +quat.slerp(a: quat, b: quat, t: number, [опционально] dst: quat) -> quat ``` ## Перевод в строку - *quat.tostring(...)* diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index ff4bfb456..bfa0b9388 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -34,7 +34,7 @@ function on_broken(x, y, z, playerid) function on_replaced(x, y, z, playerid) ``` -Вызывается после замены блока игроком +Вызывается перед заменой блока игроком ```lua function on_interact(x, y, z, playerid) -> bool diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 631d5e00f..09e296f9b 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -53,7 +53,7 @@ document["worlds-panel"]:clear() | contentOffset | vec2 | да | *нет* | смещение содержимого | | cursor | string | да | да | курсор, отображаемый при наведении | | parent | Element | да | *нет* | родительский элемент или nil | -| zIndex | bool | да | *нет* | значение z-индекса. В panel определяет порядок | +| zIndex | int | да | *нет* | значение z-индекса. В panel определяет порядок | Общие методы элементов: diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 30e4b1098..3415a81d6 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота) Операции применяются ко всем значениям высоты. ```lua +-- Приведение значений высот к абсолютным map:abs() + +-- Округление к меньшему целому +map:floor() + +-- Округление к большему целому +map:ceil() + +-- Округление к ближайшему целому +map:round() + +-- Синус от значений высот (в радианах) +map:sin() + +-- Косинус от значений высот (в радианах) +map:cos() + +-- Тангенс от значений высот (в радианах) +map:tan() ``` -Приводит значения высот к абсолютным. - - ### Бинарные операции Операции с применением второй карты или скаляра. diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 2ca6da1ce..9be788e7f 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -43,6 +43,8 @@ - `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `onclick` - lua функция вызываемая при нажатии на элемент. +- `onrightclick` - lua функция вызываемая при нажатии правой кнопки мыши на элемент. +- `onmiddleclick` - lua функция вызываемая при нажатии средней кнопки мыши на элемент. - `ondoubleclick` - lua функция вызываемая при двойном нажатии на элемент. - `onfocus` - lua функция вызываемая при фокусировке на элемент. - `ondefocus` - lua функция вызываемая при потере фокуса элеметом. @@ -193,6 +195,9 @@ - `width` - минимальная ширина содержимого. По-умолчанию: 100. - `selected` - изначально выбранное значение. По-умолчанию: "". - `onselect` - функция, в которую передаётся выбранное пользователем значение +- `mode` - режим работы: + - `select` - режим по-умолчанию. + - `button` - режим кнопки: текст не заменяется на выбранную опцию, а элемент выглядит как обычная кнопка. ## Привязка ввода - *bindbox* diff --git a/res/content/base/generators/demo.files/script.lua b/res/content/base/generators/demo.files/script.lua index 7a3282c3c..ff05e08cc 100644 --- a/res/content/base/generators/demo.files/script.lua +++ b/res/content/base/generators/demo.files/script.lua @@ -86,12 +86,27 @@ function generate_heightmap(x, y, w, h, s, inputs) local rivermap = Heightmap(w, h) rivermap.noiseSeed = SEED - rivermap:noise({x+21, y+12}, 0.1*s, 4) - rivermap:abs() - rivermap:mul(2.0) - rivermap:pow(0.15) - rivermap:max(0.5) - map:mul(rivermap) + rivermap:noise({ x + 21, y + 12 }, 0.1 * s, 4) + rivermap:pow(4) + rivermap:mul(3500) + rivermap:add(1) + rivermap:pow(-1) + rivermap:mul(-1) + rivermap:add(1) + + local rivermap_bottom = Heightmap(w, h) + rivermap_bottom:add(0.23) + rivermap_bottom:noise({ x, y }, 2 * s, 4, 0.005) + + map:sub(rivermap_bottom) + + local map_multiplied = Heightmap(w, h) + map_multiplied:add(map) + map_multiplied:mul(rivermap) + + map:min(map_multiplied) + + map:add(rivermap_bottom) local desertmap = Heightmap(w, h) desertmap.noiseSeed = SEED diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index 874e6d3d6..4d25ccb91 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -1,5 +1,44 @@ local util = {} +local DROP_FORCE = 8 +local DROP_INIT_VEL = {0, 3, 0} + +---@param mode integer 0 - whole stack, 1 - single item, 2 - dupe whole stack +function util.drop_from_slot(invid, slot, mode) + if invid == 0 then + return + end + local pid = hud.get_player() + if mode == 2 and not player.is_infinite_items(pid) then + return + end + local itemid, itemcount = inventory.get(invid, slot) + if itemid == 0 then + return + end + + local drop_itemcount = 1 + if mode == 0 then + drop_itemcount = itemcount + end + if mode == 2 then + drop_itemcount = item.stack_size(itemid) + else + inventory.set(invid, slot, itemid, itemcount - drop_itemcount) + end + + local data = inventory.get_all_data(invid, slot) + local pvel = { player.get_vel(pid) } + local ppos = vec3.add({ player.get_pos(pid) }, { 0, 0.7, 0 }) + local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) + local drop = util.drop(ppos, itemid, drop_itemcount, data, 1.5) + if not drop then + return + end + local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) + drop.rigidbody:set_vel(velocity) +end + function util.drop(ppos, itemid, count, data, pickup_delay) if itemid == 0 or not itemid then return nil diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua index 9fc10f2a1..d0e3d1532 100644 --- a/res/content/base/scripts/hud.lua +++ b/res/content/base/scripts/hud.lua @@ -1,30 +1,21 @@ local base_util = require "util" -local DROP_FORCE = 8 -local DROP_INIT_VEL = {0, 3, 0} - function on_hud_open() - input.add_callback("player.drop", function () + events.on("core:drop_outside_inventory", function(mode) + local invid = hud.get_exchange_inventory() + base_util.drop_from_slot(invid, 0, mode) + end) + input.add_callback("player.drop", function() if hud.is_paused() or hud.is_inventory_open() then return end local pid = hud.get_player() local invid, slot = player.get_inventory(pid) - local itemid, itemcount = inventory.get(invid, slot) - if itemid == 0 then - return + local mode = 1 + if input.is_pressed("key:left-ctrl") or input.is_pressed("key:right-ctrl") then + mode = 0 end - local data = inventory.get_all_data(invid, slot) - inventory.set(invid, slot, itemid, itemcount-1) - - local pvel = {player.get_vel(pid)} - local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0}) - local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) - local drop = base_util.drop(ppos, itemid, 1, data, 1.5) - if not drop then - return - end - local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) - drop.rigidbody:set_vel(velocity) + base_util.drop_from_slot(invid, slot, mode) end) + rules.create("do-loot-non-player", true) end diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 419dcc769..3da6f6c2a 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -16,6 +16,4 @@ function on_block_broken(id, x, y, z, playerid) spawn_spread=vec3.mul(size, 0.4) }) end - - rules.create("do-loot-non-player", true) end diff --git a/res/layouts/inventory.xml b/res/layouts/inventory.xml index 4291ab4d3..7a33b054f 100644 --- a/res/layouts/inventory.xml +++ b/res/layouts/inventory.xml @@ -1,4 +1,4 @@ - - + + diff --git a/res/layouts/inventory.xml.lua b/res/layouts/inventory.xml.lua index 9f836223d..5c79db6ff 100644 --- a/res/layouts/inventory.xml.lua +++ b/res/layouts/inventory.xml.lua @@ -1,12 +1 @@ -function inventory_share_func(invid, slotid) - local blockinv = hud.get_block_inventory() - if blockinv ~= 0 then - inventory.move(invid, slotid, blockinv) - elseif rules.get("allow-content-access") then - inventory.set(invid, slotid, 0, 0) - elseif slotid < 10 then - inventory.move_range(invid, slotid, invid, 10) - else - inventory.move_range(invid, slotid, invid, 0, 9) - end -end +share_func = require("core:inventory_utils").share_default_func diff --git a/res/layouts/templates/pack.xml b/res/layouts/templates/pack.xml index 56bd50e50..d4e721d0c 100644 --- a/res/layouts/templates/pack.xml +++ b/res/layouts/templates/pack.xml @@ -1,6 +1,6 @@ - - ]], id, string.escape_xml(text)), {on_yes=yes_callback, on_no=no_callback}, - string.escape_xml(yes_text), string.escape_xml(no_text)) + ]], id, + string.escape_xml(text), + string.escape_xml(yes_text), + string.escape_xml(no_text)), {on_yes=yes_callback, on_no=no_callback}) input.add_callback("key:escape", no_callback, gui.root[id]) end diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index 4278f14f6..dbf7f9776 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -129,7 +129,7 @@ return { goto continue end for _, component in pairs(entity.components) do - local callback = component.on_update + local callback = rawget(component, "on_update") if not component.__disabled and callback then local result, err = pcall(callback, tps) if err then @@ -143,7 +143,7 @@ return { physics_update = function(delta) for uid, entity in pairs(entities) do for _, component in pairs(entity.components) do - local callback = component.on_physics_update + local callback = rawget(component, "on_physics_update") if not component.__disabled and callback then local result, err = pcall(callback, delta) if err then @@ -156,7 +156,7 @@ return { render = function(delta) for _,entity in pairs(entities) do for _, component in pairs(entity.components) do - local callback = component.on_render + local callback = rawget(component, "on_render") if not component.__disabled and callback then local result, err = pcall(callback, delta) if err then diff --git a/res/modules/inventory_utils.lua b/res/modules/inventory_utils.lua new file mode 100644 index 000000000..17089cdab --- /dev/null +++ b/res/modules/inventory_utils.lua @@ -0,0 +1,21 @@ +local M = {} + +function M.share_default_func(invid, slotid) + local blockinv = hud.get_block_inventory() + local playerinv = player.get_inventory(hud.get_player()) + if blockinv ~= 0 then + if invid == blockinv then + inventory.move(blockinv, slotid, playerinv) + else + inventory.move(invid, slotid, blockinv) + end + elseif rules.get("allow-content-access") then + inventory.set(invid, slotid, 0, 0) + elseif slotid < 10 then + inventory.move_range(invid, slotid, invid, 10) + else + inventory.move_range(invid, slotid, invid, 0, 9) + end +end + +return M diff --git a/res/shaders/effects/deferred_lighting.glsl b/res/shaders/effects/deferred_lighting.glsl index c314df02a..55e267bf3 100644 --- a/res/shaders/effects/deferred_lighting.glsl +++ b/res/shaders/effects/deferred_lighting.glsl @@ -1,6 +1,9 @@ #include #include +#define CLOUDS_FOG_FACTOR_MUL 0.3f +#define CLOUDS_FOG_CURVE_MUL 0.4f + vec4 effect() { vec4 pos = texture(u_position, v_uv); float light = 1.0; @@ -32,6 +35,10 @@ vec4 effect() { light = pow(light, u_gamma); vec3 fogColor = texture(u_skybox, dir).rgb; - float fog = calc_fog(length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0); + float fog = calc_fog( + length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0, + mix(1.0, CLOUDS_FOG_FACTOR_MUL, emission), + mix(1.0, CLOUDS_FOG_CURVE_MUL, emission) + ); return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0); } diff --git a/res/shaders/entity.glslf b/res/shaders/entity.glslf index f3293adb7..a817d963a 100644 --- a/res/shaders/entity.glslf +++ b/res/shaders/entity.glslf @@ -15,11 +15,33 @@ uniform float u_fogCurve; uniform bool u_alphaClip; uniform vec3 u_sunDir; +const float BAYER_MATRIX[64] = float[]( + 0.0, 32.0, 8.0, 40.0, 2.0, 34.0, 10.0, 42.0, + 48.0, 16.0, 56.0, 24.0, 50.0, 18.0, 58.0, 26.0, + 12.0, 44.0, 4.0, 36.0, 14.0, 46.0, 6.0, 38.0, + 60.0, 28.0, 52.0, 20.0, 62.0, 30.0, 54.0, 22.0, + 3.0, 35.0, 11.0, 43.0, 1.0, 33.0, 9.0, 41.0, + 51.0, 19.0, 59.0, 27.0, 49.0, 17.0, 57.0, 25.0, + 15.0, 47.0, 7.0, 39.0, 13.0, 45.0, 5.0, 37.0, + 63.0, 31.0, 55.0, 23.0, 61.0, 29.0, 53.0, 21.0 +); + +float calc_bayer(vec2 pos){ + ivec2 p = ivec2(mod(pos, 8.0)); + return BAYER_MATRIX[p.y * 8 + p.x] / 64.0; +} + void main() { vec4 texColor = texture(u_texture0, a_texCoord); float alpha = a_color.a * texColor.a; // anyway it's any alpha-test alternative required - if (alpha < (u_alphaClip ? 0.5f : 0.15f)) { + if (u_alphaClip) { + float bayer = calc_bayer(gl_FragCoord.xy); + if (alpha - bayer < 0.01f) { + discard; + } + alpha = 1.0; + } else if (alpha < 0.05f) { discard; } f_color = a_color * texColor; diff --git a/res/shaders/lib/fog.glsl b/res/shaders/lib/fog.glsl index e5ff18bbd..597380bb6 100644 --- a/res/shaders/lib/fog.glsl +++ b/res/shaders/lib/fog.glsl @@ -7,13 +7,17 @@ uniform float u_weatherFogOpacity; uniform float u_weatherFogDencity; uniform float u_weatherFogCurve; -float calc_fog(float depth) { +float calc_fog(float depth, float fogFactor, float fogCurve) { return min( 1.0, - max(pow(depth * u_fogFactor, u_fogCurve), + max(pow(depth * u_fogFactor * fogFactor, u_fogCurve * fogCurve), min(pow(depth * u_weatherFogDencity, u_weatherFogCurve), u_weatherFogOpacity)) ); } +float calc_fog(float depth) { + return calc_fog(depth, 1.0, 1.0); +} + #endif // FOG_GLSL_ diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 56903db79..d6bcad92b 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -151,6 +151,8 @@ template<> void ContentUnitLoader::loadUnit( def.rotations = BlockRotProfile::PANE; } else if (profile == BlockRotProfile::STAIRS_NAME) { def.rotations = BlockRotProfile::STAIRS; + } else if (profile == BlockRotProfile::LADDER_NAME) { + def.rotations = BlockRotProfile::LADDER; } else if (profile != "none") { logger.error() << "unknown rotation profile " << profile; def.rotatable = false; diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index c1a95330d..9790723a9 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -48,7 +48,7 @@ void ServerMainloop::run() { delta = time.getDelta(); } if (controller) { - controller->getLevel()->getWorld()->updateTimers(delta); + controller->getLevel()->getWorld().updateTimers(delta); controller->update(glm::min(delta, 0.2), false); } engine.applicationTick(); diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index d70f09ba2..2143a4778 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -147,3 +147,7 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const { LevelController& LevelFrontend::getController() const { return controller; } + +Weather& LevelFrontend::getWeather() { + return weather; +} diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index 451f88675..28c8a5832 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -1,5 +1,7 @@ #pragma once +#include "world/Weather.hpp" + #include class Assets; @@ -15,6 +17,7 @@ class LevelFrontend { LevelController& controller; Assets& assets; std::unique_ptr contentCache; + Weather weather {}; public: LevelFrontend( Engine& engine, @@ -28,4 +31,5 @@ public: const ContentGfxCache& getContentGfxCache() const; ContentGfxCache& getContentGfxCache(); LevelController& getController() const; + Weather& getWeather(); }; diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 780bbf00a..84e85f3e9 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -254,7 +254,7 @@ std::shared_ptr create_debug_panel( } })); panel->add(create_label(gui, [&]() { - return L"seed: " + std::to_wstring(level.getWorld()->getSeed()); + return L"seed: " + std::to_wstring(level.getWorld().getSeed()); })); for (int ax = 0; ax < 3; ax++) { @@ -294,7 +294,7 @@ std::shared_ptr create_debug_panel( sub->add(box, glm::vec2(20, 0)); panel->add(sub); } - auto& worldInfo = level.getWorld()->getInfo(); + auto& worldInfo = level.getWorld().getInfo(); panel->add(create_label(gui, [&]() { int hour, minute, second; timeutil::from_value(worldInfo.daytime, hour, minute, second); @@ -310,12 +310,6 @@ std::shared_ptr create_debug_panel( bar->setConsumer([&](double val) { worldInfo.daytime = val; }); panel->add(bar); } - if (allowDebugCheats) { - auto bar = std::make_shared(gui, 0.0f, 1.0f, 0.0f, 0.005f, 8); - bar->setSupplier([&]() { return worldInfo.fog; }); - bar->setConsumer([&](double val) { worldInfo.fog = val; }); - panel->add(bar); - } { auto checkbox = std::make_shared( gui, L"Show Chunk Borders", glm::vec2(400, 24) diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 1aa03436b..6751b885c 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -106,6 +106,7 @@ std::shared_ptr HudElement::getNode() const { std::shared_ptr Hud::createContentAccess() { auto& content = frontend.getLevel().content; auto& indices = *content.getIndices(); + auto& input = this->input; auto inventory = player.getInventory(); size_t itemsCount = indices.items.count(); @@ -115,12 +116,19 @@ std::shared_ptr Hud::createContentAccess() { } SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr, - [inventory, &indices](uint, ItemStack& item) { + [inventory, &indices, &input](uint, ItemStack& item) { auto copy = ItemStack(item); + if (input.pressed(Keycode::LEFT_CONTROL)) { + copy.maximizeCount(indices); + } inventory->move(copy, indices); }, - [this, inventory](uint, ItemStack& item) { - inventory->getSlot(player.getChosenSlot()).set(item); + [this, inventory, &indices, &input](uint, ItemStack& item) { + auto copy = ItemStack(item); + if (input.pressed(Keycode::LEFT_CONTROL)) { + copy.maximizeCount(indices); + } + inventory->getSlot(player.getChosenSlot()).set(copy); }); InventoryBuilder builder(gui); @@ -174,6 +182,15 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) gui, "" ); + + inventoryDropArea = guiutil::create( + gui, + R"()" + ); uicamera = std::make_unique(glm::vec3(), 1); uicamera->perspective = false; @@ -188,6 +205,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) gui.add(debugPanel); gui.add(darkOverlay); + gui.add(inventoryDropArea); gui.add(hotbarView); gui.add(contentAccessPanel); @@ -213,6 +231,7 @@ Hud::~Hud() { } gui.remove(hotbarView); gui.remove(darkOverlay); + gui.remove(inventoryDropArea); gui.remove(contentAccessPanel); gui.remove(debugPanel); } @@ -358,6 +377,7 @@ void Hud::update(bool visible) { contentAccess->setMinSize(glm::vec2(1, windowSize.y)); hotbarView->setVisible(visible && !(secondUI && !inventoryView)); darkOverlay->setVisible(isMenuOpen); + inventoryDropArea->setVisible(inventoryOpen); menu.setVisible(isMenuOpen); if (visible) { @@ -739,6 +759,10 @@ std::shared_ptr Hud::getSecondInventory() { return nullptr; } +std::shared_ptr Hud::getExchangeInventory() { + return exchangeSlotInv; +} + bool Hud::isContentAccess() const { return showContentPanel; } diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index f80c36369..f40a53581 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -93,6 +93,8 @@ class Hud : public util::ObjectsKeeper { std::shared_ptr debugPanel; /// @brief Overlay used in pause mode std::shared_ptr darkOverlay; + /// @brief Overlay used in inventory + std::shared_ptr inventoryDropArea; /// @brief Inventories interaction agent (grabbed item) std::shared_ptr exchangeSlot; /// @brief Exchange slot inventory (1 slot only) @@ -207,6 +209,8 @@ public: std::shared_ptr getSecondInventory(); + std::shared_ptr getExchangeInventory(); + bool isContentAccess() const; void setContentAccess(bool flag); diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 33b637531..24ee44378 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -43,7 +43,7 @@ LevelScreen::LevelScreen( Engine& engine, std::unique_ptr levelPtr, int64_t localPlayer ) : Screen(engine), - world(*levelPtr->getWorld()), + world(levelPtr->getWorld()), postProcessing(std::make_unique( levelPtr->content.getIndices(ResourceType::POST_EFFECT_SLOT).size() )), @@ -81,7 +81,7 @@ LevelScreen::LevelScreen( auto resetChunks = [=](bool) { player->chunks->saveAndClear(); - renderer->clear(); + renderer->resetCache(); }; keepAlive(settings.graphics.backlight.observe(resetChunks)); keepAlive(settings.graphics.softLighting.observe(resetChunks)); @@ -94,11 +94,11 @@ LevelScreen::LevelScreen( })); keepAlive(input.addCallback(BIND_CHUNKS_RELOAD, [=]() { player->chunks->saveAndClear(); - renderer->clear(); + renderer->resetCache(); return false; })); controller->preQuitCallbacks.listen([this]() { - if (!controller->getLevel()->getWorld()->isNameless()) { + if (!controller->getLevel()->getWorld().isNameless()) { saveWorldPreview(); } }); @@ -109,7 +109,7 @@ LevelScreen::LevelScreen( } LevelScreen::~LevelScreen() { - if (!controller->getLevel()->getWorld()->isNameless()) { + if (!controller->getLevel()->getWorld().isNameless()) { saveDecorations(); } scripting::on_frontend_close(); @@ -153,7 +153,7 @@ void LevelScreen::loadDecorations() { } auto data = io::read_object(CLIENT_FILE); if (data.has("weather")) { - renderer->getWeather().deserialize(data["weather"]); + frontend->getWeather().deserialize(data["weather"]); } } @@ -161,7 +161,7 @@ void LevelScreen::saveDecorations() { io::create_directory("world:client"); auto data = dv::object(); - data["weather"] = renderer->getWeather().serialize(); + data["weather"] = frontend->getWeather().serialize(); io::write_json(CLIENT_FILE, data, true); } @@ -257,7 +257,7 @@ void LevelScreen::update(float delta) { hud->update(hudVisible); - const Weather& weather = renderer->getWeather(); + const Weather& weather = frontend->getWeather(); const Player& player = playerController->getPlayer(); const Camera& camera = *player.currentCamera; decorator->update(paused ? 0.0f : delta, camera, weather); diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp index 7aa856f3f..33265b49f 100644 --- a/src/graphics/core/Batch2D.cpp +++ b/src/graphics/core/Batch2D.cpp @@ -372,7 +372,7 @@ void Batch2D::sprite( void Batch2D::flush() { if (index == 0) return; - mesh->reload(buffer.get(), index); + mesh->reload(buffer.get(), index, true); mesh->draw(gl::to_glenum(primitive)); index = 0; } diff --git a/src/graphics/core/Batch3D.cpp b/src/graphics/core/Batch3D.cpp index ce9f176c7..3aa996706 100644 --- a/src/graphics/core/Batch3D.cpp +++ b/src/graphics/core/Batch3D.cpp @@ -292,13 +292,13 @@ void Batch3D::point(const glm::vec3& coord, const glm::vec4& tint) { } void Batch3D::flush() { - mesh->reload(buffer.get(), index); + mesh->reload(buffer.get(), index, true); mesh->draw(); index = 0; } void Batch3D::flushPoints() { - mesh->reload(buffer.get(), index); + mesh->reload(buffer.get(), index, true); mesh->draw(GL_POINTS); index = 0; } diff --git a/src/graphics/core/LineBatch.cpp b/src/graphics/core/LineBatch.cpp index 9e33398ac..514f550d6 100644 --- a/src/graphics/core/LineBatch.cpp +++ b/src/graphics/core/LineBatch.cpp @@ -57,7 +57,7 @@ void LineBatch::box(float x, float y, float z, float w, float h, float d, void LineBatch::flush(){ if (index == 0) return; - mesh->reload(buffer.get(), index); + mesh->reload(buffer.get(), index, true); mesh->draw(GL_LINES); index = 0; } diff --git a/src/graphics/core/Mesh.hpp b/src/graphics/core/Mesh.hpp index 48c029696..2ff15cc2a 100644 --- a/src/graphics/core/Mesh.hpp +++ b/src/graphics/core/Mesh.hpp @@ -47,12 +47,15 @@ public: void reload( const VertexStructure* vertexBuffer, size_t vertexCount, - const std::vector& indices + const std::vector& indices, + bool streaming ); - void reload(const VertexStructure* vertexBuffer, size_t vertexCount) { + void reload( + const VertexStructure* vertexBuffer, size_t vertexCount, bool streaming + ) { static const std::vector indices {}; - reload(vertexBuffer, vertexCount, indices); + reload(vertexBuffer, vertexCount, indices, streaming); } /// @brief Draw mesh with specified primitives type @@ -61,6 +64,8 @@ public: /// @brief Draw mesh as triangles void draw() const; +private: + }; #include "graphics/core/Mesh.inl" diff --git a/src/graphics/core/Mesh.inl b/src/graphics/core/Mesh.inl index 913da2bce..4c6424dd1 100644 --- a/src/graphics/core/Mesh.inl +++ b/src/graphics/core/Mesh.inl @@ -46,9 +46,9 @@ Mesh::Mesh( glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); - reload(vertexBuffer, vertices, std::move(indices)); - glBindVertexArray(vao); + reload(vertexBuffer, vertices, std::move(indices), false); + // attributes int offset = 0; for (int i = 0; attrs[i].count; i++) { @@ -82,20 +82,22 @@ template void Mesh::reload( const VertexStructure* vertexBuffer, size_t vertexCount, - const std::vector& indices + const std::vector& indices, + bool streaming ) { this->vertexCount = vertexCount; - glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); if (vertexBuffer != nullptr && vertexCount != 0) { glBufferData( GL_ARRAY_BUFFER, vertexCount * sizeof(VertexStructure), vertexBuffer, - GL_STREAM_DRAW + streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW ); } else { - glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW); + glBufferData( + GL_ARRAY_BUFFER, 0, {}, streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW + ); } for (int i = 0; i < ibos.size(); i++) { @@ -116,7 +118,6 @@ void Mesh::reload( GL_STATIC_DRAW ); } - glBindVertexArray(0); } template diff --git a/src/graphics/core/Shader.cpp b/src/graphics/core/Shader.cpp index 8cc6f2025..1c752ef5a 100644 --- a/src/graphics/core/Shader.cpp +++ b/src/graphics/core/Shader.cpp @@ -186,7 +186,8 @@ void Shader::recompile(const std::vector& defines) { glDeleteProgram(id); id = newProgram; uniformLocations.clear(); - logger.info() << "shader " << id << " has been recompiled"; + logger.debug() << "shader program (" << vertexSource.file << ", " + << fragmentSource.file << ") has been recompiled"; } std::unique_ptr Shader::create( diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp index c9ecc108d..8defa40f8 100644 --- a/src/graphics/core/Shadows.cpp +++ b/src/graphics/core/Shadows.cpp @@ -99,8 +99,8 @@ void Shadows::setup(Shader& shader, const Weather& weather) { if (!shadows) { return; } - const auto& worldInfo = level.getWorld()->getInfo(); - float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); + const auto& worldInfo = level.getWorld().getInfo(); + float cloudsIntensity = weather.clouds(); float shadowsOpacity = 1.0f - cloudsIntensity; shadowsOpacity *= glm::sqrt(glm::abs( glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f @@ -158,8 +158,8 @@ void Shadows::generateShadowsMap( float scale, const std::function& renderShadowPass ) { - auto world = level.getWorld(); - const auto& worldInfo = world->getInfo(); + const auto& world = level.getWorld(); + const auto& worldInfo = world.getInfo(); int resolution = shadowMap.getResolution(); float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 33ecc8e17..f75bd675c 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -10,15 +10,14 @@ #include "frontend/ContentGfxCache.hpp" const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704); -const float DIRECTIONAL_LIGHT_FACTOR = 0.3f; +constexpr float DIRECTIONAL_LIGHT_FACTOR = 0.3f; BlocksRenderer::BlocksRenderer( size_t capacity, - const Content& content, + const Block* const* blockDefs, const ContentGfxCache& cache, const EngineSettings& settings -) : content(content), - vertexBuffer(std::make_unique(capacity)), +) : vertexBuffer(std::make_unique(capacity)), indexBuffer(std::make_unique(capacity)), denseIndexBuffer(std::make_unique(capacity)), vertexCount(0), @@ -28,7 +27,7 @@ BlocksRenderer::BlocksRenderer( cache(cache), settings(settings) { - blockDefsCache = content.getIndices()->blocks.getDefs(); + blockDefsCache = blockDefs; } BlocksRenderer::~BlocksRenderer() = default; @@ -482,83 +481,78 @@ glm::vec4 BlocksRenderer::pickSoftLight( } void BlocksRenderer::render( - const voxel* voxels, const int beginEnds[256][2] + const voxel* voxels, int totalBegin, int totalEnd ) { bool denseRender = this->denseRender; bool densePass = this->densePass; bool enableAO = settings.graphics.softLighting.get(); - for (const auto drawGroup : *content.drawGroups) { - int begin = beginEnds[drawGroup][0]; - if (begin == 0) { + + for (int i = totalBegin; i < totalEnd; i++) { + const voxel& vox = voxels[i]; + blockid_t id = vox.id; + blockstate state = vox.state; + const auto& def = *blockDefsCache[id]; + uint8_t variantId = def.getVariantIndex(state.userbits); + const auto& variant = def.getVariant(variantId); + if (id == 0 || state.segment) { continue; } - int end = beginEnds[drawGroup][1]; - for (int i = begin-1; i <= end; i++) { - const voxel& vox = voxels[i]; - blockid_t id = vox.id; - blockstate state = vox.state; - const auto& def = *blockDefsCache[id]; - uint8_t variantId = def.getVariantIndex(state.userbits); - const auto& variant = def.getVariant(variantId); - if (id == 0 || variant.drawGroup != drawGroup || state.segment) { - continue; + if (denseRender != (variant.culling == CullingMode::OPTIONAL)) { + continue; + } + if (def.translucent) { + continue; + } + const UVRegion texfaces[6] { + cache.getRegion(id, variantId, 0, densePass), + cache.getRegion(id, variantId, 1, densePass), + cache.getRegion(id, variantId, 2, densePass), + cache.getRegion(id, variantId, 3, densePass), + cache.getRegion(id, variantId, 4, densePass), + cache.getRegion(id, variantId, 5, densePass) + }; + int x = i % CHUNK_W; + int y = i / (CHUNK_D * CHUNK_W); + int z = (i / CHUNK_D) % CHUNK_W; + + switch (def.getModel(state.userbits).type) { + case BlockModelType::BLOCK: + blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, + def.ambientOcclusion && enableAO); + break; + case BlockModelType::XSPRITE: { + if (!denseRender) + blockXSprite(x, y, z, glm::vec3(1.0f), + texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f); + break; } - if (denseRender != (variant.culling == CullingMode::OPTIONAL)) { - continue; + case BlockModelType::AABB: { + if (!denseRender) + blockAABB({x, y, z}, texfaces, &def, vox.state.rotation, + !def.shadeless, def.ambientOcclusion && enableAO); + break; } - if (def.translucent) { - continue; - } - const UVRegion texfaces[6] { - cache.getRegion(id, variantId, 0, densePass), - cache.getRegion(id, variantId, 1, densePass), - cache.getRegion(id, variantId, 2, densePass), - cache.getRegion(id, variantId, 3, densePass), - cache.getRegion(id, variantId, 4, densePass), - cache.getRegion(id, variantId, 5, densePass) - }; - int x = i % CHUNK_W; - int y = i / (CHUNK_D * CHUNK_W); - int z = (i / CHUNK_D) % CHUNK_W; - switch (def.getModel(state.userbits).type) { - case BlockModelType::BLOCK: - blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, - def.ambientOcclusion && enableAO); - break; - case BlockModelType::XSPRITE: { - if (!denseRender) - blockXSprite(x, y, z, glm::vec3(1.0f), - texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f); - break; - } - case BlockModelType::AABB: { - if (!denseRender) - blockAABB({x, y, z}, texfaces, &def, vox.state.rotation, - !def.shadeless, def.ambientOcclusion && enableAO); - break; - } - case BlockModelType::CUSTOM: { - blockCustomModel( - {x, y, z}, - def, - vox.state, - !def.shadeless, - def.ambientOcclusion && enableAO - ); - break; - } - default: - break; - } - if (overflow) { - return; + case BlockModelType::CUSTOM: { + blockCustomModel( + {x, y, z}, + def, + vox.state, + !def.shadeless, + def.ambientOcclusion && enableAO + ); + break; } + default: + break; + } + if (overflow) { + return; } } } SortingMeshData BlocksRenderer::renderTranslucent( - const voxel* voxels, int beginEnds[256][2] + const voxel* voxels, int totalBegin, int totalEnd ) { SortingMeshData sortingMesh {{}}; @@ -568,106 +562,98 @@ SortingMeshData BlocksRenderer::renderTranslucent( bool densePass = this->densePass; bool enableAO = settings.graphics.softLighting.get(); - for (const auto drawGroup : *content.drawGroups) { - int begin = beginEnds[drawGroup][0]; - if (begin == 0) { + for (int i = totalBegin; i < totalEnd; i++) { + const voxel& vox = voxels[i]; + blockid_t id = vox.id; + blockstate state = vox.state; + const auto& def = *blockDefsCache[id]; + uint8_t variantId = def.getVariantIndex(state.userbits); + if (id == 0 || state.segment) { continue; } - int end = beginEnds[drawGroup][1]; - for (int i = begin-1; i <= end; i++) { - const voxel& vox = voxels[i]; - blockid_t id = vox.id; - blockstate state = vox.state; - const auto& def = *blockDefsCache[id]; - uint8_t variantId = def.getVariantIndex(state.userbits); - const auto& variant = def.getVariant(variantId); - if (id == 0 || variant.drawGroup != drawGroup || state.segment) { - continue; - } - if (!def.translucent) { - continue; - } - const UVRegion texfaces[6] { - cache.getRegion(id, variantId, 0, densePass), - cache.getRegion(id, variantId, 1, densePass), - cache.getRegion(id, variantId, 2, densePass), - cache.getRegion(id, variantId, 3, densePass), - cache.getRegion(id, variantId, 4, densePass), - cache.getRegion(id, variantId, 5, densePass) - }; - int x = i % CHUNK_W; - int y = i / (CHUNK_D * CHUNK_W); - int z = (i / CHUNK_D) % CHUNK_W; - switch (def.getModel(state.userbits).type) { - case BlockModelType::BLOCK: - blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, - def.ambientOcclusion && enableAO); - break; - case BlockModelType::XSPRITE: { - blockXSprite(x, y, z, glm::vec3(1.0f), - texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f); - break; - } - case BlockModelType::AABB: { - blockAABB( - {x, y, z}, - texfaces, - &def, - vox.state.rotation, - !def.shadeless, - def.ambientOcclusion && enableAO - ); - break; - } - case BlockModelType::CUSTOM: { - blockCustomModel( - {x, y, z}, - def, - vox.state, - !def.shadeless, - def.ambientOcclusion && enableAO - ); - break; - } - default: - break; - } - if (vertexCount == 0) { - continue; - } - SortingMeshEntry entry { - glm::vec3( - x + chunk->x * CHUNK_W + 0.5f, - y + 0.5f, - z + chunk->z * CHUNK_D + 0.5f - ), - util::Buffer(indexCount), 0}; - - totalSize += entry.vertexData.size(); - - for (int j = 0; j < indexCount; j++) { - std::memcpy( - entry.vertexData.data() + j, - vertexBuffer.get() + indexBuffer[j], - sizeof(ChunkVertex) - ); - ChunkVertex& vertex = entry.vertexData[j]; - - if (!aabbInit) { - aabbInit = true; - aabb.a = aabb.b = vertex.position; - } else { - aabb.addPoint(vertex.position); - } - - vertex.position.x += chunk->x * CHUNK_W + 0.5f; - vertex.position.y += 0.5f; - vertex.position.z += chunk->z * CHUNK_D + 0.5f; - } - sortingMesh.entries.push_back(std::move(entry)); - vertexCount = 0; - vertexOffset = indexCount = 0; + if (!def.translucent) { + continue; } + const UVRegion texfaces[6] { + cache.getRegion(id, variantId, 0, densePass), + cache.getRegion(id, variantId, 1, densePass), + cache.getRegion(id, variantId, 2, densePass), + cache.getRegion(id, variantId, 3, densePass), + cache.getRegion(id, variantId, 4, densePass), + cache.getRegion(id, variantId, 5, densePass) + }; + int x = i % CHUNK_W; + int y = i / (CHUNK_D * CHUNK_W); + int z = (i / CHUNK_D) % CHUNK_W; + switch (def.getModel(state.userbits).type) { + case BlockModelType::BLOCK: + blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, + def.ambientOcclusion && enableAO); + break; + case BlockModelType::XSPRITE: { + blockXSprite(x, y, z, glm::vec3(1.0f), + texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f); + break; + } + case BlockModelType::AABB: { + blockAABB( + {x, y, z}, + texfaces, + &def, + vox.state.rotation, + !def.shadeless, + def.ambientOcclusion && enableAO + ); + break; + } + case BlockModelType::CUSTOM: { + blockCustomModel( + {x, y, z}, + def, + vox.state, + !def.shadeless, + def.ambientOcclusion && enableAO + ); + break; + } + default: + break; + } + if (vertexCount == 0) { + continue; + } + SortingMeshEntry entry { + glm::vec3( + x + chunk->x * CHUNK_W + 0.5f, + y + 0.5f, + z + chunk->z * CHUNK_D + 0.5f + ), + util::Buffer(indexCount), 0}; + + totalSize += entry.vertexData.size(); + + for (int j = 0; j < indexCount; j++) { + std::memcpy( + entry.vertexData.data() + j, + vertexBuffer.get() + indexBuffer[j], + sizeof(ChunkVertex) + ); + ChunkVertex& vertex = entry.vertexData[j]; + + if (!aabbInit) { + aabbInit = true; + aabb.a = aabb.b = vertex.position; + } else { + aabb.addPoint(vertex.position); + } + + vertex.position.x += chunk->x * CHUNK_W + 0.5f; + vertex.position.y += 0.5f; + vertex.position.z += chunk->z * CHUNK_D + 0.5f; + } + sortingMesh.entries.push_back(std::move(entry)); + vertexCount = 0; + vertexOffset = indexCount = 0; } // additional powerful optimization @@ -709,19 +695,16 @@ void BlocksRenderer::build( int totalBegin = chunk->bottom * (CHUNK_W * CHUNK_D); int totalEnd = chunk->top * (CHUNK_W * CHUNK_D); + bool hasTranslucent = false; - int beginEnds[256][2] {}; for (int i = totalBegin; i < totalEnd; i++) { const voxel& vox = voxels[i]; blockid_t id = vox.id; const auto& def = *blockDefsCache[id]; - const auto& variant = def.getVariantByBits(vox.state.userbits); - hasTranslucent = def.translucent || hasTranslucent; - - if (beginEnds[variant.drawGroup][0] == 0) { - beginEnds[variant.drawGroup][0] = i + 1; + if (def.translucent) { + hasTranslucent = true; + break; } - beginEnds[variant.drawGroup][1] = i; } cancelled = false; @@ -733,7 +716,7 @@ void BlocksRenderer::build( densePass = false; if (hasTranslucent) { - sortingMesh = renderTranslucent(voxels, beginEnds); + sortingMesh = renderTranslucent(voxels, totalBegin, totalEnd); } else { sortingMesh = {}; } @@ -746,13 +729,13 @@ void BlocksRenderer::build( denseRender = false; densePass = false; - render(voxels, beginEnds); + render(voxels, totalBegin, totalEnd); size_t endIndex = indexCount; denseRender = true; densePass = settings.graphics.denseRender.get(); - render(voxels, beginEnds); + render(voxels, totalBegin, totalEnd); denseIndexCount = indexCount; for (size_t i = 0; i < denseIndexCount; i++) { @@ -761,7 +744,7 @@ void BlocksRenderer::build( indexCount = endIndex; densePass = false; - render(voxels, beginEnds); + render(voxels, totalBegin, totalEnd); } ChunkMeshData BlocksRenderer::createMesh() { diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index 94b708a54..0488f0080 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -25,7 +25,7 @@ class BlocksRenderer final { public: BlocksRenderer( size_t capacity, - const Content& content, + const Block* const* blockDefs, const ContentGfxCache& cache, const EngineSettings& settings ); @@ -44,7 +44,6 @@ public: } private: static const glm::vec3 SUN_VECTOR; - const Content& content; std::unique_ptr vertexBuffer; std::unique_ptr indexBuffer; std::unique_ptr denseIndexBuffer; @@ -145,13 +144,16 @@ private: ); // Does block allow to see other blocks sides (is it transparent) - inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const { + bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const { const auto& vox = voxelsBuffer->pickBlock( chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z ); if (vox.id == BLOCK_VOID) { return false; } + if (def.rt.id == vox.id && def.rt.solid && variant.culling == CullingMode::DEFAULT) { + return false; + } const auto& block = *blockDefsCache[vox.id]; const auto& blockVariant = block.getVariantByBits(vox.state.userbits); uint8_t otherDrawGroup = blockVariant.drawGroup; @@ -178,6 +180,6 @@ private: float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up ) const; - void render(const voxel* voxels, const int beginEnds[256][2]); - SortingMeshData renderTranslucent(const voxel* voxels, int beginEnds[256][2]); + void render(const voxel* voxels, int totalBegin, int totalEnd); + SortingMeshData renderTranslucent(const voxel* voxels, int totalBegin, int totalEnd); }; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index baf4cf356..82a7fa6d6 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -11,9 +11,12 @@ #include "world/Level.hpp" #include "window/Camera.hpp" #include "maths/FrustumCulling.hpp" +#include "maths/util.hpp" #include "util/listutil.hpp" #include "util/ObjectsPool.hpp" +#include "util/timeutil.hpp" #include "settings.hpp" +#include "content/Content.hpp" static debug::Logger logger("chunks-render"); @@ -33,7 +36,7 @@ public: settings.graphics.denseRender.get() ? settings.graphics.chunkMaxVerticesDense.get() : settings.graphics.chunkMaxVertices.get(), - level.content, + level.content.getIndices()->blocks.getDefs(), cache, settings ) { @@ -92,7 +95,7 @@ ChunksRenderer::ChunksRenderer( threadPool.setStopOnFail(false); renderer = std::make_unique( settings.graphics.chunkMaxVertices.get(), - level.content, cache, settings + level.content.getIndices()->blocks.getDefs(), cache, settings ); logger.info() << "created " << threadPool.getWorkersCount() << " workers"; logger.info() << "memory consumption is " @@ -122,30 +125,30 @@ std::shared_ptr ChunksRenderer::prepareVoxelsVolume( return voxelsBuffer; } -const ChunkMesh* ChunksRenderer::render( - const std::shared_ptr& chunk, bool important, bool lowPriority +void ChunksRenderer::renderBlocking(const std::shared_ptr& chunk) { + glm::ivec2 key(chunk->x, chunk->z); + ChunkMesh mesh {}; + auto voxelsBuffer = prepareVoxelsVolume(*chunk); + mesh = renderer->render(chunk.get(), *voxelsBuffer); + meshes[key] = std::move(mesh); + chunk->flags.modified = false; +} + +void ChunksRenderer::render( + const std::shared_ptr& chunk, bool lowPriority ) { glm::ivec2 key(chunk->x, chunk->z); - if (important) { - ChunkMesh mesh {}; - auto voxelsBuffer = prepareVoxelsVolume(*chunk); - mesh = renderer->render(chunk.get(), *voxelsBuffer); - meshes[key] = std::move(mesh); - chunk->flags.modified = false; - return &meshes[key]; - } if (inwork.find(key) != inwork.end() || ((inwork.size() >= threadPool.getWorkersCount() || enqueuedInFrame >= MAX_CHUNKS_ENQUEUED_IN_FRAME) && lowPriority)) { - return nullptr; + return; } chunk->flags.modified = false; enqueuedInFrame++; auto voxelsBuffer = prepareVoxelsVolume(*chunk); threadPool.enqueueJob({chunk, std::move(voxelsBuffer)}); inwork[key] = true; - return nullptr; } void ChunksRenderer::unload(const Chunk* chunk) { @@ -161,76 +164,69 @@ void ChunksRenderer::clear() { threadPool.clearQueue(); } -const ChunkMesh* ChunksRenderer::getOrRender( - const std::shared_ptr& chunk, bool important, bool lowPriority -) { - auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); - if (found == meshes.end()) { - return render(chunk, important, lowPriority); - } - if (chunk->flags.modified && chunk->flags.lighted) { - render(chunk, important, lowPriority); - } - return &found->second; -} - void ChunksRenderer::update() { threadPool.pullResults(); enqueuedInFrame = 0; -} -const Mesh* ChunksRenderer::retrieveChunk( - size_t index, const Camera& camera, bool culling -) { - auto chunk = chunks.getChunks()[index]; - if (chunk == nullptr) { - return nullptr; - } - if (!chunk->flags.lighted) { - const auto& found = meshes.find({chunk->x, chunk->z}); - if (found == meshes.end()) { - return nullptr; - } else { - return found->second.mesh.get(); + int width = chunks.getWidth(); + int halfWidth = width / 2; + int halfHeight = chunks.getHeight() / 2; + int centerX = chunks.getOffsetX() + halfWidth; + int centerY = chunks.getOffsetY() + halfHeight; + + meshBuildQueue.clear(); + auto& chunksArray = chunks.getChunks(); + for (int index = 0; index < chunks.getVolume(); index++) { + const auto& chunk = chunks.getChunks()[index]; + if (chunk == nullptr || !chunk->flags.lighted) { + continue; + } + int x = chunk->x; + int z = chunk->z; + if (chunk->flags.modified || meshes.find({x, z}) == meshes.end()) { + meshBuildQueue.emplace_back(x - centerX, z - centerY); } } - float distance = glm::distance( - camera.position, - glm::vec3( - (chunk->x + 0.5f) * CHUNK_W, - camera.position.y, - (chunk->z + 0.5f) * CHUNK_D - ) + std::sort( + meshBuildQueue.begin(), + meshBuildQueue.end(), + [](const glm::ivec2& a, const glm::ivec2& b) { + return util::length2(a) < util::length2(b); + } ); - auto mesh = getOrRender( - chunk, - distance < CHUNK_W * 1.5f, - distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5 - ); - if (mesh == nullptr) { - return nullptr; - } - if (chunk->flags.dirtyHeights) { - chunk->updateHeights(); - } - if (culling) { - const auto& meshAABB = mesh->meshAABB; - auto aabbMin = meshAABB.min(); - auto aabbMax = meshAABB.max(); - glm::vec3 min( - chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x), - chunk->bottom, - chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z) - ); - glm::vec3 max( - chunk->x * CHUNK_W + aabbMax.x, - chunk->top, - chunk->z * CHUNK_D + aabbMax.z - ); - if (!frustum.isBoxVisible(min, max)) return nullptr; + int loadDistance = settings.chunks.loadDistance.get(); + + const int topN = 10; + int top = std::min(meshBuildQueue.size(), topN); + for (int i = 0; i < top; i++) { + glm::ivec2 offset = meshBuildQueue[i]; + size_t index = + (offset.y + halfHeight) * width + offset.x + halfWidth; + + auto& chunk = chunksArray[index]; + assert(chunk != nullptr); + + float distance = glm::distance( + glm::vec3 (centerX, 0.0f, centerY), + glm::vec3( + (chunk->x + 0.5f) * CHUNK_W, + 0, + (chunk->z + 0.5f) * CHUNK_D + ) + ); + bool important = distance < CHUNK_W * 1.5f; + bool lowPriority = distance > CHUNK_W * loadDistance * 0.5; + + if (chunk->flags.dirtyHeights) { + chunk->updateHeights(); + } + if (important) { + renderBlocking(chunk); + } else { + render(chunk, lowPriority); + } } - return mesh->mesh.get(); } void ChunksRenderer::drawShadowsPass( @@ -317,16 +313,43 @@ void ChunksRenderer::drawChunks( // TODO: minimize draw calls number for (int i = indices.size()-1; i >= 0; i--) { auto& chunk = chunks.getChunks()[indices[i].index]; - auto mesh = retrieveChunk(indices[i].index, camera, culling); - if (mesh == nullptr) { + if (chunk == nullptr) { continue; } + auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); + if (found == meshes.end()) { + continue; + } + auto& mesh = found->second; + if (mesh.mesh == nullptr) { + continue; + } + if (culling) { + const auto& meshAABB = mesh.meshAABB; + auto aabbMin = meshAABB.min(); + auto aabbMax = meshAABB.max(); + glm::vec3 min( + chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x), + chunk->bottom, + chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z) + ); + glm::vec3 max( + chunk->x * CHUNK_W + aabbMax.x, + chunk->top, + chunk->z * CHUNK_D + aabbMax.z + ); + + if (!frustum.isBoxVisible(min, max)) { + continue; + } + } + glm::vec3 coord( chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f ); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); shader.uniformMatrix("u_model", model); - mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1), + mesh.mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1), (coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2); visibleChunks++; } diff --git a/src/graphics/render/ChunksRenderer.hpp b/src/graphics/render/ChunksRenderer.hpp index 5e6783d97..5e26adc43 100644 --- a/src/graphics/render/ChunksRenderer.hpp +++ b/src/graphics/render/ChunksRenderer.hpp @@ -1,16 +1,17 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL + +#include "util/ThreadPool.hpp" +#include "commons.hpp" + #include #include #include #include -#define GLM_ENABLE_EXPERIMENTAL #include -#include "util/ThreadPool.hpp" -#include "commons.hpp" - template class Mesh; class Chunk; class Level; @@ -44,22 +45,6 @@ struct RendererJob { }; class ChunksRenderer { - const Chunks& chunks; - const Assets& assets; - const Frustum& frustum; - const EngineSettings& settings; - - std::unique_ptr renderer; - std::unordered_map meshes; - std::unordered_map inwork; - std::vector indices; - util::ThreadPool threadPool; - const Mesh* retrieveChunk( - size_t index, const Camera& camera, bool culling - ); - std::shared_ptr prepareVoxelsVolume(const Chunk& chunk); - - size_t enqueuedInFrame = 0; public: ChunksRenderer( const Level& level, @@ -69,18 +54,11 @@ public: const ContentGfxCache& cache, const EngineSettings& settings ); - virtual ~ChunksRenderer(); + ~ChunksRenderer(); - const ChunkMesh* render( - const std::shared_ptr& chunk, bool important, bool lowPriority - ); void unload(const Chunk* chunk); void clear(); - const ChunkMesh* getOrRender( - const std::shared_ptr& chunk, bool important, bool lowPriority - ); - void drawShadowsPass( const Camera& camera, Shader& shader, const Camera& playerCamera ); @@ -92,4 +70,25 @@ public: void update(); static size_t visibleChunks; + +private: + const Chunks& chunks; + const Assets& assets; + const Frustum& frustum; + const EngineSettings& settings; + + std::unique_ptr renderer; + std::unordered_map meshes; + std::unordered_map inwork; + std::vector indices; + util::ThreadPool threadPool; + std::vector meshBuildQueue; + + size_t enqueuedInFrame = 0; + + std::shared_ptr prepareVoxelsVolume(const Chunk& chunk); + + void render(const std::shared_ptr& chunk, bool lowPriority); + + void renderBlocking(const std::shared_ptr& chunk); }; diff --git a/src/graphics/render/CloudsRenderer.cpp b/src/graphics/render/CloudsRenderer.cpp index e2047eb63..bfc53e4bb 100755 --- a/src/graphics/render/CloudsRenderer.cpp +++ b/src/graphics/render/CloudsRenderer.cpp @@ -329,7 +329,7 @@ void CloudsRenderer::draw( shader.uniform4f("u_tint", glm::vec4(weather.cloudsTint(), 1.0f)); shader.uniform1f("u_fogFactor", fogFactor * 0.03f); - shader.uniform1f("u_fogCurve", 0.7f - 0.3f); + shader.uniform1f("u_fogCurve", 0.4f); for (int i = 0; i < std::min(quality, layers.size()); i++) { draw(layers[i], frustum, shader, camera, timer, i); diff --git a/src/graphics/render/DebugLinesRenderer.cpp b/src/graphics/render/DebugLinesRenderer.cpp index df0b1af78..3750b2cd0 100644 --- a/src/graphics/render/DebugLinesRenderer.cpp +++ b/src/graphics/render/DebugLinesRenderer.cpp @@ -93,7 +93,7 @@ void DebugLinesRenderer::drawCoordSystem( } void DebugLinesRenderer::render( - DrawContext& pctx, + const DrawContext& pctx, const Camera& camera, LineBatch& linesBatch, Shader& linesShader, diff --git a/src/graphics/render/DebugLinesRenderer.hpp b/src/graphics/render/DebugLinesRenderer.hpp index 7a9f0a324..bc6609c5a 100644 --- a/src/graphics/render/DebugLinesRenderer.hpp +++ b/src/graphics/render/DebugLinesRenderer.hpp @@ -20,7 +20,7 @@ public: /// @param linesShader Shader used for rendering lines /// @param showChunkBorders Whether to show chunk borders void render( - DrawContext& ctx, + const DrawContext& ctx, const Camera& camera, LineBatch& linesBatch, Shader& linesShader, diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 1fa2abd0b..26a2b302e 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -25,6 +25,7 @@ #include "voxels/blocks_agent.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" +#include "world/Weather.hpp" #include "WorldRenderer.hpp" namespace fs = std::filesystem; diff --git a/src/graphics/render/MainBatch.cpp b/src/graphics/render/MainBatch.cpp index 1a501ca15..91364e510 100644 --- a/src/graphics/render/MainBatch.cpp +++ b/src/graphics/render/MainBatch.cpp @@ -46,7 +46,7 @@ void MainBatch::flush() { texture = blank.get(); } texture->bind(); - mesh->reload(buffer.get(), index); + mesh->reload(buffer.get(), index, true); mesh->draw(); index = 0; } diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index d06119425..f303bb817 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -51,7 +51,9 @@ #include "window/Window.hpp" #include "world/Level.hpp" #include "world/LevelEvents.hpp" +#include "world/Weather.hpp" #include "world/World.hpp" +#include "debug/Logger.hpp" #include #include @@ -62,9 +64,20 @@ inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; +static debug::Logger logger("world-renderer"); + bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showEntitiesDebug = false; +template +static ObserverHandler observe_setting( + ObservableSetting& setting, bool& dirtySettings +) { + return setting.observe([&dirtySettings](auto const&) { + dirtySettings = true; + }); +} + WorldRenderer::WorldRenderer( Engine& engine, LevelFrontend& frontend, Player& player ) @@ -72,6 +85,7 @@ WorldRenderer::WorldRenderer( level(frontend.getLevel()), player(player), assets(*engine.getAssets()), + weather(frontend.getWeather()), frustumCulling(std::make_unique()), lineBatch(std::make_unique()), batch3d(std::make_unique(BATCH3D_CAPACITY)), @@ -120,10 +134,41 @@ WorldRenderer::WorldRenderer( shadowMapping = std::make_unique(level); debugLines = std::make_unique(level); cloudsRenderer = std::make_unique(); + + keepAlive(observe_setting(settings.graphics.advancedRender, dirtySettings)); + keepAlive(observe_setting(settings.graphics.shadowsQuality, dirtySettings)); + keepAlive(observe_setting(settings.graphics.ssao, dirtySettings)); } WorldRenderer::~WorldRenderer() = default; +void WorldRenderer::refreshSettings() { + Shader* affectedShaders[] { + &assets.require("main"), + &assets.require("entity"), + &assets.require("clouds"), + &assets.require("translucent"), + &assets.require("deferred_lighting").getShader(), + nullptr + }; + + const auto& graphics = engine.getSettings().graphics; + gbufferPipeline = graphics.advancedRender.get(); + + int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline; + shadowMapping->setQuality(shadowsQuality); + + std::vector defines; + if (shadowsQuality != 0) defines.emplace_back("ENABLE_SHADOWS"); + if (graphics.ssao.get()) defines.emplace_back("ENABLE_SSAO"); + if (gbufferPipeline) defines.emplace_back("ADVANCED_RENDER"); + + logger.info() << "recompiling shaders due to settings change"; + for (size_t i = 0; affectedShaders[i]; i++) { + affectedShaders[i]->recompile(defines); + } +} + static void setup_weather(Shader& shader, const Weather& weather) { shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); @@ -156,7 +201,7 @@ void WorldRenderer::setupWorldShader( shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1i("u_debugLights", lightsDebug); shader.uniform1i("u_debugNormals", false); - shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); + shader.uniform1f("u_dayTime", level.getWorld().getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX); @@ -188,8 +233,6 @@ void WorldRenderer::renderOpaque( const EngineSettings& settings, bool hudVisible ) { - texts->render(ctx, camera, settings, hudVisible, false); - float fogFactor = calcFogFactor(); auto& entityShader = assets.require("entity"); @@ -212,12 +255,12 @@ void WorldRenderer::renderOpaque( modelBatch->render(); particles->render(camera); - auto& shader = assets.require("main"); + auto& mainShader = assets.require("main"); auto& cloudsShader = assets.require("clouds"); - setupWorldShader(shader, camera, settings, fogFactor); + setupWorldShader(mainShader, camera, settings, fogFactor); - chunksRenderer->drawChunks(camera, shader); + chunksRenderer->drawChunks(camera, mainShader); blockWraps->draw(ctx); if (level.environment.sky.clouds) { @@ -232,7 +275,7 @@ void WorldRenderer::renderOpaque( if (hudVisible) { auto& linesShader = assets.require("lines"); - renderLines(camera, linesShader, ctx); + renderInWorldLines(camera, linesShader, ctx); } } @@ -265,7 +308,7 @@ void WorldRenderer::renderBlockSelection() { lineBatch->flush(); } -void WorldRenderer::renderLines( +void WorldRenderer::renderInWorldLines( const Camera& camera, Shader& linesShader, const DrawContext& pctx ) { linesShader.use(); @@ -283,193 +326,144 @@ void WorldRenderer::renderLines( } } -void WorldRenderer::refreshSettings(Shader** shaders) { - const auto& graphics = engine.getSettings().graphics; - gbufferPipeline = graphics.advancedRender.get(); - - int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline; - shadowMapping->setQuality(shadowsQuality); - - CompileTimeShaderSettings currentSettings { - gbufferPipeline, - shadowsQuality != 0, - graphics.ssao.get() && gbufferPipeline}; - if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender || - prevCTShaderSettings.shadows != currentSettings.shadows || - prevCTShaderSettings.ssao != currentSettings.ssao) { - std::vector defines; - if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS"); - if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO"); - if (currentSettings.advancedRender) - defines.emplace_back("ADVANCED_RENDER"); - - for (size_t i = 0; shaders[i]; i++) { - shaders[i]->recompile(defines); - } - prevCTShaderSettings = currentSettings; - } -} - void WorldRenderer::update(const Camera& camera, float delta) { timer += delta; + chunksRenderer->update(); weather.update(delta); precipitation->update(delta); particles->update(camera, delta); } +void WorldRenderer::renderFrameClassic( + const DrawContext& pctx, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing +) { + const auto& settings = engine.getSettings(); + const auto& worldInfo = level.getWorld().getInfo(); + + DrawContext ctx = pctx.sub(); + ctx.setDepthTest(true); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + ctx.setFramebuffer(postProcessing.getFramebuffer()); + + skybox->draw( + level.environment, ctx, camera, worldInfo.daytime, weather.clouds() + ); + texts->render(ctx, camera, settings, hudVisible, false); + + if (debug && hudVisible) { + renderDebugLines(ctx, camera); + } + + DrawContext wctx = ctx.sub(); + wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + // Translucent blocks + { + const auto& settings = engine.getSettings(); + auto& translucentShader = assets.require("translucent"); + auto sctx = wctx.sub(); + sctx.setCullFace(true); + translucentShader.use(); + setupWorldShader(translucentShader, camera, settings, calcFogFactor()); + chunksRenderer->drawSortedMeshes(camera, translucentShader); + } + + renderWeatherEffects(camera); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void WorldRenderer::renderFrameAdvanced( + const DrawContext& pctx, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing +) { + const auto& settings = engine.getSettings(); + const auto& worldInfo = level.getWorld().getInfo(); + float fogFactor = calcFogFactor(); + + shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) { + auto& shader = assets.require("shadows"); + setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); + chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); + }); + + auto& deferredShader = + assets.require("deferred_lighting").getShader(); + deferredShader.use(); + setupWorldShader(deferredShader, camera, settings, fogFactor); + postProcessing.renderDeferredShading(pctx, assets, timer, camera); + + DrawContext ctx = pctx.sub(); + ctx.setDepthTest(true); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + + postProcessing.bindDepthBuffer(); + skybox->draw( + level.environment, ctx, camera, worldInfo.daytime, weather.clouds() + ); + texts->render(ctx, camera, settings, hudVisible, false); + if (debug && hudVisible) { + renderDebugLines(ctx, camera); + } + + DrawContext wctx = ctx.sub(); + wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + // Translucent blocks + { + const auto& settings = engine.getSettings(); + auto& translucentShader = assets.require("translucent"); + auto sctx = wctx.sub(); + sctx.setCullFace(true); + translucentShader.use(); + setupWorldShader(translucentShader, camera, settings, fogFactor); + chunksRenderer->drawSortedMeshes(camera, translucentShader); + } + + renderWeatherEffects(camera); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, bool hudVisible, PostProcessing& postProcessing ) { - auto projView = camera.getProjView(); - auto world = level.getWorld(); - const auto& vp = pctx.getViewport(); camera.setAspectRatio(vp.x / static_cast(vp.y)); + if (dirtySettings) { + refreshSettings(); + dirtySettings = false; + } - auto& mainShader = assets.require("main"); - auto& entityShader = assets.require("entity"); - auto& cloudsShader = assets.require("clouds"); - auto& translucentShader = assets.require("translucent"); - auto& deferredShader = - assets.require("deferred_lighting").getShader(); - - const auto& settings = engine.getSettings(); - - Shader* affectedShaders[] { - &mainShader, - &entityShader, - &cloudsShader, - &translucentShader, - &deferredShader, - nullptr}; - - refreshSettings(affectedShaders); - - const auto& worldInfo = world->getInfo(); - + const auto& worldInfo = level.getWorld().getInfo(); float clouds = weather.clouds(); - clouds = glm::max(worldInfo.fog, clouds); - float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f; float random = rand() / static_cast(RAND_MAX); skybox->refresh( level.environment, pctx, worldInfo.daytime, - mie, + 1.0f + clouds, weather.skyTint(), weather.highlight * random, 4 ); - - chunksRenderer->update(); - - shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) { - auto& shader = assets.require("shadows"); - setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); - chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); - }); - { - DrawContext wctx = pctx.sub(); - postProcessing.use(wctx, gbufferPipeline); - display::clearDepth(); - - /* Main opaque pass (GBuffer pass) */ { - DrawContext ctx = wctx.sub(); - ctx.setDepthTest(true); - ctx.setCullFace(true); - ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); - ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); - renderOpaque(ctx, camera, settings, hudVisible); - } - texts->render(pctx, camera, settings, hudVisible, true); - } - float fogFactor = calcFogFactor(); + renderOpaquePass(pctx, camera, hudVisible, postProcessing); if (gbufferPipeline) { - deferredShader.use(); - setupWorldShader(deferredShader, camera, settings, fogFactor); - postProcessing.renderDeferredShading(pctx, assets, timer, camera); - } - { - DrawContext ctx = pctx.sub(); - ctx.setDepthTest(true); - ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); - - if (gbufferPipeline) { - postProcessing.bindDepthBuffer(); - } else { - ctx.setFramebuffer(postProcessing.getFramebuffer()); - } - - // Background sky plane - skybox->draw( - level.environment, ctx, camera, worldInfo.daytime, clouds - ); - - auto& linesShader = assets.require("lines"); - linesShader.use(); - if (debug && hudVisible) { - debugLines->render( - ctx, camera, *lineBatch, linesShader, showChunkBorders - ); - } - linesShader.uniformMatrix("u_projview", projView); - lineBatch->flush(); - - DrawContext wctx = ctx.sub(); - wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); - wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); - // Translucent blocks - { - auto sctx = wctx.sub(); - sctx.setCullFace(true); - translucentShader.use(); - setupWorldShader(translucentShader, camera, settings, fogFactor); - chunksRenderer->drawSortedMeshes(camera, translucentShader); - } - - // Weather effects - entityShader.use(); - setupWorldShader(entityShader, camera, settings, fogFactor); - - std::array weatherInstances { - &weather.a, &weather.b}; - for (const auto& weather : weatherInstances) { - float maxIntensity = weather->fall.maxIntensity; - float zero = weather->fall.minOpacity; - float one = weather->fall.maxOpacity; - float t = (weather->intensity * (one - zero)) * maxIntensity + zero; - entityShader.uniform1i("u_alphaClip", weather->fall.opaque); - entityShader.uniform1f( - "u_opacity", weather->fall.opaque ? t * t : t - ); - if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) { - precipitation->render(camera, *weather); - } - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); + renderFrameAdvanced(pctx, camera, hudVisible, postProcessing); + } else { + renderFrameClassic(pctx, camera, hudVisible, postProcessing); } postProcessing.render(pctx, assets, timer, camera); if (player.currentCamera == player.fpCamera) { - DrawContext ctx = pctx.sub(); - ctx.setDepthTest(true); - ctx.setCullFace(true); - ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); - ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); - display::clearDepth(); - - // prepare modified HUD camera - Camera hudcam = camera; - hudcam.far = 10.0f; - hudcam.setFov(0.9f); - hudcam.position = {}; - - setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); - hands->render(camera); + renderHandsPass(pctx, camera); } { auto bctx = pctx.sub(); @@ -479,6 +473,85 @@ void WorldRenderer::renderFrame( glActiveTexture(GL_TEXTURE0); } + +void WorldRenderer::renderOpaquePass( + const DrawContext& pctx, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing +) { + const auto& settings = engine.getSettings(); + + DrawContext wctx = pctx.sub(); + postProcessing.use(wctx, gbufferPipeline); + display::clearDepth(); + + /* Main opaque pass (GBuffer pass) */ { + DrawContext ctx = wctx.sub(); + ctx.setDepthTest(true); + ctx.setCullFace(true); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + renderOpaque(ctx, camera, settings, hudVisible); + + ctx.setDepthMask(false); + texts->render(wctx, camera, settings, hudVisible, true); + } +} + +void WorldRenderer::renderWeatherEffects(Camera& camera) { + const auto& settings = engine.getSettings(); + auto& entityShader = assets.require("entity"); + entityShader.use(); + setupWorldShader(entityShader, camera, settings, calcFogFactor()); + + std::array weatherInstances { + &weather.a, &weather.b}; + for (const auto& weather : weatherInstances) { + float maxIntensity = weather->fall.maxIntensity; + float zero = weather->fall.minOpacity; + float one = weather->fall.maxOpacity; + float t = (weather->intensity * (one - zero)) * maxIntensity + zero; + entityShader.uniform1i("u_alphaClip", weather->fall.opaque); + entityShader.uniform1f( + "u_opacity", weather->fall.opaque ? t * t : t + ); + if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) { + precipitation->render(camera, *weather); + } + } +} + +void WorldRenderer::renderDebugLines(const DrawContext& ctx, Camera& camera) { + auto& linesShader = assets.require("lines"); + linesShader.use(); + debugLines->render( + ctx, camera, *lineBatch, linesShader, showChunkBorders + ); + linesShader.uniformMatrix("u_projview", camera.getProjView()); + lineBatch->flush(); +} + +void WorldRenderer::renderHandsPass(const DrawContext& pctx, Camera& camera) { + auto& entityShader = assets.require("entity"); + + DrawContext ctx = pctx.sub(); + ctx.setDepthTest(true); + ctx.setCullFace(true); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + display::clearDepth(); + + // prepare modified HUD camera + Camera hudcam = camera; + hudcam.far = 10.0f; + hudcam.setFov(0.9f); + hudcam.position = {}; + + setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); + hands->render(camera); +} + void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { int x = std::floor(player.currentCamera->position.x); int y = std::floor(player.currentCamera->position.y); @@ -524,7 +597,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { batch3d->flush(); } -void WorldRenderer::clear() { +void WorldRenderer::resetCache() { chunksRenderer->clear(); } diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index c3e6b1669..b3b10f88c 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -4,12 +4,11 @@ #include "typedefs.hpp" #include "presets/WeatherPreset.hpp" -#include "world/Weather.hpp" #include "window/Camera.hpp" +#include "util/ObjectsKeeper.hpp" #include #include -#include #include class Assets; @@ -36,18 +35,38 @@ class Skybox; class TextsRenderer; class CloudsRenderer; struct EngineSettings; +struct Weather; -struct CompileTimeShaderSettings { - bool advancedRender = false; - bool shadows = false; - bool ssao = false; -}; +class WorldRenderer final : public util::ObjectsKeeper { +public: + static bool showChunkBorders; + static bool showEntitiesDebug; -class WorldRenderer { + WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player); + ~WorldRenderer(); + + void update(const Camera& camera, float delta); + + void renderFrame( + const DrawContext& context, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing + ); + + void resetCache(); + + void setDebug(bool flag); + + void toggleLightsDebug(); + + Weather& getWeather(); +private: Engine& engine; const Level& level; Player& player; const Assets& assets; + Weather& weather; std::unique_ptr frustumCulling; std::unique_ptr lineBatch; std::unique_ptr batch3d; @@ -59,14 +78,12 @@ class WorldRenderer { std::unique_ptr debugLines; std::unique_ptr precipitation; std::unique_ptr cloudsRenderer; - Weather weather {}; float timer = 0.0f; bool debug = false; bool lightsDebug = false; bool gbufferPipeline = false; - - CompileTimeShaderSettings prevCTShaderSettings {}; + bool dirtySettings = true; /// @brief Render block selection lines void renderBlockSelection(); @@ -74,7 +91,7 @@ class WorldRenderer { /// @brief Render lines (selection and debug) /// @param camera active camera /// @param linesShader shader used - void renderLines( + void renderInWorldLines( const Camera& camera, Shader& linesShader, const DrawContext& pctx ); @@ -98,7 +115,34 @@ class WorldRenderer { bool hudVisible ); - void refreshSettings(Shader** shaders); + void renderOpaquePass( + const DrawContext& context, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing + ); + + void renderWeatherEffects(Camera& camera); + + void renderHandsPass(const DrawContext& pctx, Camera& camera); + + void renderDebugLines(const DrawContext& context, Camera& camera); + + void renderFrameClassic( + const DrawContext& context, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing + ); + + void renderFrameAdvanced( + const DrawContext& context, + Camera& camera, + bool hudVisible, + PostProcessing& postProcessing + ); + + void refreshSettings(); float calcFogFactor() const; public: @@ -106,27 +150,4 @@ public: std::unique_ptr texts; std::unique_ptr blockWraps; std::unique_ptr skeletons; - - static bool showChunkBorders; - static bool showEntitiesDebug; - - WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player); - ~WorldRenderer(); - - void update(const Camera& camera, float delta); - - void renderFrame( - const DrawContext& context, - Camera& camera, - bool hudVisible, - PostProcessing& postProcessing - ); - - void clear(); - - void setDebug(bool flag); - - void toggleLightsDebug(); - - Weather& getWeather(); }; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 8f0f60520..944c32fb5 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -14,7 +14,6 @@ #include "frontend/UiDocument.hpp" #include "frontend/locale.hpp" #include "graphics/core/Batch2D.hpp" -#include "graphics/core/LineBatch.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Font.hpp" #include "graphics/core/DrawContext.hpp" @@ -188,8 +187,8 @@ void GUI::actMouse(Frame& frame, float delta, const CursorState& cursor) { doubleClicked = true; } else { pressed->click(cursorPos.x, cursorPos.y); + doubleClickTimer = 0.0f; } - doubleClickTimer = 0.0f; if (focus && focus != pressed) { focus->defocus(); } @@ -260,7 +259,7 @@ void GUI::act(float delta, const glm::uvec2& vp) { hover = nullptr; } } - if (focus) { + if (focus && focusedOnStart == focus.get()) { actFocused(); } if (focus && !focus->isFocused()) { @@ -274,6 +273,7 @@ void GUI::postAct() { postRunnables.pop(); callback(); } + focusedOnStart = focus.get(); } void GUI::draw(const DrawContext& pctx, Assets& assets) { diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index 7d120c9cc..97fc0ed37 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -72,6 +72,7 @@ namespace gui { std::shared_ptr hover; std::shared_ptr pressed; std::shared_ptr focus; + const UINode* focusedOnStart = nullptr; std::shared_ptr tooltip; std::shared_ptr rootDocument; std::unique_ptr syntaxColorScheme; diff --git a/src/graphics/ui/elements/CheckBox.hpp b/src/graphics/ui/elements/CheckBox.hpp index 5ca144618..0f520d43f 100644 --- a/src/graphics/ui/elements/CheckBox.hpp +++ b/src/graphics/ui/elements/CheckBox.hpp @@ -6,7 +6,7 @@ #include "Panel.hpp" namespace gui { - class CheckBox : public UINode { + class CheckBox final : public UINode { protected: glm::vec4 checkColor {1.0f, 1.0f, 1.0f, 0.4f}; boolsupplier supplier = nullptr; @@ -15,23 +15,22 @@ namespace gui { public: explicit CheckBox(GUI& gui, bool checked = false); - virtual void draw(const DrawContext& pctx, const Assets& assets) - override; + void draw(const DrawContext& pctx, const Assets& assets) override; - virtual void mouseRelease(int x, int y) override; + void mouseRelease(int x, int y) override; - virtual void setSupplier(boolsupplier supplier); - virtual void setConsumer(boolconsumer consumer); + void setSupplier(boolsupplier supplier); + void setConsumer(boolconsumer consumer); - virtual CheckBox* setChecked(bool flag); + CheckBox* setChecked(bool flag); - virtual bool isChecked() const { + bool isChecked() const { if (supplier) return supplier(); return checked; } }; - class FullCheckBox : public Panel { + class FullCheckBox final : public Panel { protected: std::shared_ptr checkbox; std::shared_ptr