mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +00:00
Merge branch 'main' into animation
This commit is contained in:
commit
49e7f2c631
89 changed files with 1050 additions and 687 deletions
|
|
@ -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*
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
- "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка
|
||||
- "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка
|
||||
- "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты)
|
||||
- "ladder" - профиль "лестница" (как "pipe", но на горизонтальных поверхностях как "pane")
|
||||
|
||||
### Испускаемые частицы - *particles*
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота)
|
|||
Операции применяются ко всем значениям высоты.
|
||||
|
||||
```lua
|
||||
-- Приведение значений высот к абсолютным
|
||||
map:abs()
|
||||
|
||||
-- Округление к меньшему целому
|
||||
map:floor()
|
||||
|
||||
-- Округление к большему целому
|
||||
map:ceil()
|
||||
|
||||
-- Округление к ближайшему целому
|
||||
map:round()
|
||||
|
||||
-- Синус от значений высот (в радианах)
|
||||
map:sin()
|
||||
|
||||
-- Косинус от значений высот (в радианах)
|
||||
map:cos()
|
||||
|
||||
-- Тангенс от значений высот (в радианах)
|
||||
map:tan()
|
||||
```
|
||||
|
||||
Приводит значения высот к абсолютным.
|
||||
|
||||
|
||||
### Бинарные операции
|
||||
|
||||
Операции с применением второй карты или скаляра.
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}">
|
||||
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose}]
|
||||
<label color='#FFFFFF80' size='340,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose} %{version}]
|
||||
</label>
|
||||
<label pos='78,6'>%{title}</label>
|
||||
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'
|
||||
|
|
|
|||
|
|
@ -192,13 +192,15 @@ function gui.ask(text, on_yes, on_no, yes_text, no_text)
|
|||
<container id='%s' color='#00000080' size-func='-1,-1' z-index='10'>
|
||||
<panel color='#507090E0' size='600' padding='16'
|
||||
gravity='center-center' interval='4'>
|
||||
<label margin='4'>%s</label>
|
||||
<label margin='4' markup='md'>%s</label>
|
||||
<button onclick='DATA.on_yes()'>@%s</button>
|
||||
<button onclick='DATA.on_no()'>@%s</button>
|
||||
</panel>
|
||||
</container>
|
||||
]], 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include <shadows>
|
||||
#include <fog>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ template<> void ContentUnitLoader<Block>::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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -147,3 +147,7 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
|
|||
LevelController& LevelFrontend::getController() const {
|
||||
return controller;
|
||||
}
|
||||
|
||||
Weather& LevelFrontend::getWeather() {
|
||||
return weather;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "world/Weather.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Assets;
|
||||
|
|
@ -15,6 +17,7 @@ class LevelFrontend {
|
|||
LevelController& controller;
|
||||
Assets& assets;
|
||||
std::unique_ptr<ContentGfxCache> contentCache;
|
||||
Weather weather {};
|
||||
public:
|
||||
LevelFrontend(
|
||||
Engine& engine,
|
||||
|
|
@ -28,4 +31,5 @@ public:
|
|||
const ContentGfxCache& getContentGfxCache() const;
|
||||
ContentGfxCache& getContentGfxCache();
|
||||
LevelController& getController() const;
|
||||
Weather& getWeather();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ std::shared_ptr<UINode> 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<UINode> 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<UINode> create_debug_panel(
|
|||
bar->setConsumer([&](double val) { worldInfo.daytime = val; });
|
||||
panel->add(bar);
|
||||
}
|
||||
if (allowDebugCheats) {
|
||||
auto bar = std::make_shared<TrackBar>(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<FullCheckBox>(
|
||||
gui, L"Show Chunk Borders", glm::vec2(400, 24)
|
||||
|
|
|
|||
|
|
@ -174,6 +174,15 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
|||
gui,
|
||||
"<container size='4000' color='#00000080' z-index='-1' visible='false'/>"
|
||||
);
|
||||
|
||||
inventoryDropArea = guiutil::create(
|
||||
gui,
|
||||
R"(<container size='4000' color='#00000000' z-index='-1' visible='false'
|
||||
onclick='events.emit(\"core:drop_outside_inventory\", 0)'
|
||||
onrightclick='events.emit(\"core:drop_outside_inventory\", 1)'
|
||||
onmiddleclick='events.emit(\"core:drop_outside_inventory\", 2)'
|
||||
/>)"
|
||||
);
|
||||
|
||||
uicamera = std::make_unique<Camera>(glm::vec3(), 1);
|
||||
uicamera->perspective = false;
|
||||
|
|
@ -188,6 +197,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 +223,7 @@ Hud::~Hud() {
|
|||
}
|
||||
gui.remove(hotbarView);
|
||||
gui.remove(darkOverlay);
|
||||
gui.remove(inventoryDropArea);
|
||||
gui.remove(contentAccessPanel);
|
||||
gui.remove(debugPanel);
|
||||
}
|
||||
|
|
@ -358,6 +369,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 +751,10 @@ std::shared_ptr<Inventory> Hud::getSecondInventory() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Inventory> Hud::getExchangeInventory() {
|
||||
return exchangeSlotInv;
|
||||
}
|
||||
|
||||
bool Hud::isContentAccess() const {
|
||||
return showContentPanel;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ class Hud : public util::ObjectsKeeper {
|
|||
std::shared_ptr<gui::UINode> debugPanel;
|
||||
/// @brief Overlay used in pause mode
|
||||
std::shared_ptr<gui::UINode> darkOverlay;
|
||||
/// @brief Overlay used in inventory
|
||||
std::shared_ptr<gui::UINode> inventoryDropArea;
|
||||
/// @brief Inventories interaction agent (grabbed item)
|
||||
std::shared_ptr<gui::SlotView> exchangeSlot;
|
||||
/// @brief Exchange slot inventory (1 slot only)
|
||||
|
|
@ -207,6 +209,8 @@ public:
|
|||
|
||||
std::shared_ptr<Inventory> getSecondInventory();
|
||||
|
||||
std::shared_ptr<Inventory> getExchangeInventory();
|
||||
|
||||
bool isContentAccess() const;
|
||||
|
||||
void setContentAccess(bool flag);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ LevelScreen::LevelScreen(
|
|||
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
|
||||
)
|
||||
: Screen(engine),
|
||||
world(*levelPtr->getWorld()),
|
||||
world(levelPtr->getWorld()),
|
||||
postProcessing(std::make_unique<PostProcessing>(
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,12 +47,15 @@ public:
|
|||
void reload(
|
||||
const VertexStructure* vertexBuffer,
|
||||
size_t vertexCount,
|
||||
const std::vector<IndexBufferData>& indices
|
||||
const std::vector<IndexBufferData>& 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<IndexBufferData> 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"
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ Mesh<VertexStructure>::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 <typename VertexStructure>
|
|||
void Mesh<VertexStructure>::reload(
|
||||
const VertexStructure* vertexBuffer,
|
||||
size_t vertexCount,
|
||||
const std::vector<IndexBufferData>& indices
|
||||
const std::vector<IndexBufferData>& 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<VertexStructure>::reload(
|
|||
GL_STATIC_DRAW
|
||||
);
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
template <typename VertexStructure>
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ void Shader::recompile(const std::vector<std::string>& 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> Shader::create(
|
||||
|
|
|
|||
|
|
@ -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<void(Camera&)>& 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;
|
||||
|
|
|
|||
|
|
@ -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<ChunkVertex[]>(capacity)),
|
||||
) : vertexBuffer(std::make_unique<ChunkVertex[]>(capacity)),
|
||||
indexBuffer(std::make_unique<uint32_t[]>(capacity)),
|
||||
denseIndexBuffer(std::make_unique<uint32_t[]>(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<ChunkVertex>(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<ChunkVertex>(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() {
|
||||
|
|
|
|||
|
|
@ -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<ChunkVertex[]> vertexBuffer;
|
||||
std::unique_ptr<uint32_t[]> indexBuffer;
|
||||
std::unique_ptr<uint32_t[]> 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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<BlocksRenderer>(
|
||||
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<VoxelsRenderVolume> ChunksRenderer::prepareVoxelsVolume(
|
|||
return voxelsBuffer;
|
||||
}
|
||||
|
||||
const ChunkMesh* ChunksRenderer::render(
|
||||
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
|
||||
void ChunksRenderer::renderBlocking(const std::shared_ptr<Chunk>& 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>& 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>& 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<ChunkVertex>* 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<int>(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++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
|
||||
#include "util/ThreadPool.hpp"
|
||||
#include "commons.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/hash.hpp>
|
||||
|
||||
#include "util/ThreadPool.hpp"
|
||||
#include "commons.hpp"
|
||||
|
||||
template<typename VertexStructure> 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<BlocksRenderer> renderer;
|
||||
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
|
||||
std::unordered_map<glm::ivec2, bool> inwork;
|
||||
std::vector<ChunksSortEntry> indices;
|
||||
util::ThreadPool<RendererJob, RendererResult> threadPool;
|
||||
const Mesh<ChunkVertex>* retrieveChunk(
|
||||
size_t index, const Camera& camera, bool culling
|
||||
);
|
||||
std::shared_ptr<VoxelsRenderVolume> 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>& chunk, bool important, bool lowPriority
|
||||
);
|
||||
void unload(const Chunk* chunk);
|
||||
void clear();
|
||||
|
||||
const ChunkMesh* getOrRender(
|
||||
const std::shared_ptr<Chunk>& 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<BlocksRenderer> renderer;
|
||||
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
|
||||
std::unordered_map<glm::ivec2, bool> inwork;
|
||||
std::vector<ChunksSortEntry> indices;
|
||||
util::ThreadPool<RendererJob, RendererResult> threadPool;
|
||||
std::vector<glm::ivec2> meshBuildQueue;
|
||||
|
||||
size_t enqueuedInFrame = 0;
|
||||
|
||||
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
|
||||
|
||||
void render(const std::shared_ptr<Chunk>& chunk, bool lowPriority);
|
||||
|
||||
void renderBlocking(const std::shared_ptr<Chunk>& chunk);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<int>(quality, layers.size()); i++) {
|
||||
draw(layers[i], frustum, shader, camera, timer, i);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ void DebugLinesRenderer::drawCoordSystem(
|
|||
}
|
||||
|
||||
void DebugLinesRenderer::render(
|
||||
DrawContext& pctx,
|
||||
const DrawContext& pctx,
|
||||
const Camera& camera,
|
||||
LineBatch& linesBatch,
|
||||
Shader& linesShader,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <assert.h>
|
||||
#include <algorithm>
|
||||
|
|
@ -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 <typename T>
|
||||
static ObserverHandler observe_setting(
|
||||
ObservableSetting<T>& 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<Frustum>()),
|
||||
lineBatch(std::make_unique<LineBatch>()),
|
||||
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
|
||||
|
|
@ -120,10 +134,41 @@ WorldRenderer::WorldRenderer(
|
|||
shadowMapping = std::make_unique<Shadows>(level);
|
||||
debugLines = std::make_unique<DebugLinesRenderer>(level);
|
||||
cloudsRenderer = std::make_unique<CloudsRenderer>();
|
||||
|
||||
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<Shader>("main"),
|
||||
&assets.require<Shader>("entity"),
|
||||
&assets.require<Shader>("clouds"),
|
||||
&assets.require<Shader>("translucent"),
|
||||
&assets.require<PostEffect>("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<std::string> 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<Shader>("entity");
|
||||
|
|
@ -212,12 +255,12 @@ void WorldRenderer::renderOpaque(
|
|||
modelBatch->render();
|
||||
particles->render(camera);
|
||||
|
||||
auto& shader = assets.require<Shader>("main");
|
||||
auto& mainShader = assets.require<Shader>("main");
|
||||
auto& cloudsShader = assets.require<Shader>("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<Shader>("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();
|
||||
|
|
@ -282,193 +325,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<std::string> 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<Shader>("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<Shader>("shadows");
|
||||
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
|
||||
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
|
||||
});
|
||||
|
||||
auto& deferredShader =
|
||||
assets.require<PostEffect>("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<Shader>("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<float>(vp.y));
|
||||
if (dirtySettings) {
|
||||
refreshSettings();
|
||||
dirtySettings = false;
|
||||
}
|
||||
|
||||
auto& mainShader = assets.require<Shader>("main");
|
||||
auto& entityShader = assets.require<Shader>("entity");
|
||||
auto& cloudsShader = assets.require<Shader>("clouds");
|
||||
auto& translucentShader = assets.require<Shader>("translucent");
|
||||
auto& deferredShader =
|
||||
assets.require<PostEffect>("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<float>(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<Shader>("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<Shader>("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<const WeatherPreset*, 2> 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();
|
||||
|
|
@ -478,6 +472,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<Shader>("entity");
|
||||
entityShader.use();
|
||||
setupWorldShader(entityShader, camera, settings, calcFogFactor());
|
||||
|
||||
std::array<const WeatherPreset*, 2> 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<Shader>("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<Shader>("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);
|
||||
|
|
@ -523,7 +596,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
|
|||
batch3d->flush();
|
||||
}
|
||||
|
||||
void WorldRenderer::clear() {
|
||||
void WorldRenderer::resetCache() {
|
||||
chunksRenderer->clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@
|
|||
#include "typedefs.hpp"
|
||||
|
||||
#include "presets/WeatherPreset.hpp"
|
||||
#include "world/Weather.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
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<Frustum> frustumCulling;
|
||||
std::unique_ptr<LineBatch> lineBatch;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
|
|
@ -59,14 +78,12 @@ class WorldRenderer {
|
|||
std::unique_ptr<DebugLinesRenderer> debugLines;
|
||||
std::unique_ptr<PrecipitationRenderer> precipitation;
|
||||
std::unique_ptr<CloudsRenderer> 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<TextsRenderer> texts;
|
||||
std::unique_ptr<BlockWrapsRenderer> blockWraps;
|
||||
std::unique_ptr<NamedSkeletons> 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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ namespace gui {
|
|||
std::shared_ptr<UINode> hover;
|
||||
std::shared_ptr<UINode> pressed;
|
||||
std::shared_ptr<UINode> focus;
|
||||
const UINode* focusedOnStart = nullptr;
|
||||
std::shared_ptr<UINode> tooltip;
|
||||
std::shared_ptr<UiDocument> rootDocument;
|
||||
std::unique_ptr<FontStylesScheme> syntaxColorScheme;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ void InlineFrame::setSrc(const std::string& src) {
|
|||
this->src = src;
|
||||
if (document) {
|
||||
scripting::on_ui_close(*document, nullptr);
|
||||
clear();
|
||||
document = nullptr;
|
||||
root = nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,8 @@
|
|||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/render/BlocksPreview.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "items/Inventories.hpp"
|
||||
#include "items/Inventory.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "window/Window.hpp"
|
||||
#include "../GUI.hpp"
|
||||
|
||||
// TODO: remove
|
||||
// TODO: remove
|
||||
#include <GL/glew.h>
|
||||
|
||||
|
|
@ -24,8 +25,7 @@ ModelViewer::ModelViewer(
|
|||
: Container(gui, size),
|
||||
modelName(modelName),
|
||||
camera(),
|
||||
batch(std::make_unique<Batch3D>(1024)),
|
||||
fbo(std::make_unique<Framebuffer>(size.x, size.y)) {
|
||||
batch(std::make_unique<Batch3D>(1024)) {
|
||||
camera.perspective = true;
|
||||
camera.position = glm::vec3(2, 2, 2);
|
||||
}
|
||||
|
|
@ -115,7 +115,13 @@ void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
return;
|
||||
}
|
||||
auto& prevShader = Shader::getUsed();
|
||||
|
||||
|
||||
if (!fbo) {
|
||||
if (size.x <= 0 || size.y <= 0) {
|
||||
return;
|
||||
}
|
||||
fbo = std::make_unique<Framebuffer>(size.x, size.y);
|
||||
}
|
||||
fbo->resize(size.x, size.y);
|
||||
{
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
|
@ -154,6 +160,13 @@ void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
Container::draw(pctx, assets);
|
||||
}
|
||||
|
||||
void ModelViewer::setVisible(bool flag) {
|
||||
if (!flag && fbo) {
|
||||
fbo.reset();
|
||||
}
|
||||
Container::setVisible(flag);
|
||||
}
|
||||
|
||||
void ModelViewer::setCenter(const glm::vec3& center) {
|
||||
this->center = center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ namespace gui {
|
|||
bool grabbing = false;
|
||||
public:
|
||||
ModelViewer(GUI& gui, const glm::vec2& size, const std::string& modelName);
|
||||
|
||||
~ModelViewer();
|
||||
|
||||
void setModel(const std::string& modelName);
|
||||
|
|
@ -34,6 +33,7 @@ namespace gui {
|
|||
|
||||
void act(float delta) override;
|
||||
void draw(const DrawContext& pctx, const Assets& assets) override;
|
||||
void setVisible(bool flag) override;
|
||||
|
||||
void setRotation(const glm::vec3& euler);
|
||||
void setCenter(const glm::vec3& center);
|
||||
|
|
|
|||
|
|
@ -13,14 +13,21 @@ SelectBox::SelectBox(
|
|||
GUI& gui,
|
||||
std::vector<Option>&& options,
|
||||
Option selected,
|
||||
Mode mode,
|
||||
int contentWidth,
|
||||
const glm::vec4& padding
|
||||
)
|
||||
: Button(gui, selected.text, padding, nullptr, glm::vec2(contentWidth, -1)),
|
||||
options(std::move(options)) {
|
||||
|
||||
listenAction(UIAction::CLICK, [this](GUI& gui) {
|
||||
auto panel = std::make_shared<Panel>(gui, getSize());
|
||||
: Button(gui, selected.text, padding, nullptr, glm::vec2(-1, -1)),
|
||||
options(std::move(options)), mode(mode) {
|
||||
listenAction(UIAction::CLICK, [this, contentWidth](GUI& gui) {
|
||||
auto panel = std::make_shared<Panel>(
|
||||
gui,
|
||||
getMode() == Mode::BUTTON ? glm::vec2 {contentWidth, contentWidth}
|
||||
: getSize(),
|
||||
glm::vec4 {2.0f},
|
||||
0.0f
|
||||
);
|
||||
panel->setColor({});
|
||||
panel->setPadding(glm::vec4(0, size.y, 0, 0));
|
||||
panel->setPos(calcPos() + glm::vec2(0, 0));
|
||||
for (const auto& option : this->options) {
|
||||
|
|
@ -51,7 +58,9 @@ void SelectBox::listenChange(OnStringChange&& callback) {
|
|||
|
||||
void SelectBox::setSelected(const Option& selected) {
|
||||
this->selected = selected;
|
||||
this->label->setText(selected.text);
|
||||
if (mode == Mode::SELECT) {
|
||||
this->label->setText(selected.text);
|
||||
}
|
||||
}
|
||||
|
||||
const SelectBox::Option& SelectBox::getSelected() const {
|
||||
|
|
@ -66,6 +75,14 @@ void SelectBox::setOptions(std::vector<Option>&& options) {
|
|||
this->options = std::move(options);
|
||||
}
|
||||
|
||||
SelectBox::Mode SelectBox::getMode() const {
|
||||
return mode;
|
||||
}
|
||||
|
||||
void SelectBox::setMode(SelectBox::Mode mode) {
|
||||
this->mode = mode;
|
||||
}
|
||||
|
||||
void SelectBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
||||
glm::vec2 pos = calcPos();
|
||||
auto batch = pctx.getBatch2D();
|
||||
|
|
@ -73,6 +90,10 @@ void SelectBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
|||
batch->setColor(calcColor());
|
||||
batch->rect(pos.x, pos.y, size.x, size.y);
|
||||
batch->setColor({1.0f, 1.0f, 1.0f, 0.333f});
|
||||
|
||||
if (mode != Mode::SELECT) {
|
||||
return;
|
||||
}
|
||||
|
||||
int paddingRight = padding.w;
|
||||
int widthHalf = 8;
|
||||
|
|
|
|||
|
|
@ -11,15 +11,21 @@ namespace gui {
|
|||
std::string value;
|
||||
std::wstring text;
|
||||
};
|
||||
enum class Mode {
|
||||
SELECT,
|
||||
BUTTON,
|
||||
};
|
||||
private:
|
||||
std::vector<Option> options;
|
||||
Option selected {};
|
||||
StringCallbacksSet changeCallbacks;
|
||||
Mode mode;
|
||||
public:
|
||||
SelectBox(
|
||||
GUI& gui,
|
||||
std::vector<Option>&& elements,
|
||||
Option selected,
|
||||
Mode mode,
|
||||
int contentWidth,
|
||||
const glm::vec4& padding
|
||||
);
|
||||
|
|
@ -34,8 +40,13 @@ namespace gui {
|
|||
|
||||
void setOptions(std::vector<Option>&& options);
|
||||
|
||||
Mode getMode() const;
|
||||
|
||||
void setMode(Mode mode);
|
||||
|
||||
void drawBackground(const DrawContext& pctx, const Assets&) override;
|
||||
|
||||
|
||||
std::shared_ptr<Label> getLabel() const;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ void UINode::click(int, int) {
|
|||
void UINode::clicked(Mousecode button) {
|
||||
if (button == Mousecode::BUTTON_2) {
|
||||
actions.notify(UIAction::RIGHT_CLICK, gui);
|
||||
} else if (button == Mousecode::BUTTON_3) {
|
||||
actions.notify(UIAction::MIDDLE_CLICK, gui);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ namespace gui {
|
|||
FOCUS,
|
||||
DEFOCUS,
|
||||
RIGHT_CLICK,
|
||||
MIDDLE_CLICK,
|
||||
MOUSE_OVER,
|
||||
MOUSE_OUT,
|
||||
MOUSE_ENTER,
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ static void read_uinode(
|
|||
|
||||
register_action(node, reader, element, "onclick", UIAction::CLICK);
|
||||
register_action(node, reader, element, "onrightclick", UIAction::RIGHT_CLICK);
|
||||
register_action(node, reader, element, "onmiddleclick", UIAction::MIDDLE_CLICK);
|
||||
register_action(node, reader, element, "onfocus", UIAction::FOCUS);
|
||||
register_action(node, reader, element, "ondefocus", UIAction::DEFOCUS);
|
||||
register_action(node, reader, element, "ondoubleclick", UIAction::DOUBLE_CLICK);
|
||||
|
|
@ -458,8 +459,7 @@ static std::shared_ptr<UINode> read_select(
|
|||
}
|
||||
auto value = elem->attr("value").getText();
|
||||
auto text = parse_inner_text(*elem, reader.getContext());
|
||||
options.push_back(SelectBox::Option {std::move(value), std::move(text)}
|
||||
);
|
||||
options.push_back(SelectBox::Option {std::move(value), std::move(text)});
|
||||
}
|
||||
|
||||
if (element.has("selected")) {
|
||||
|
|
@ -481,10 +481,19 @@ static std::shared_ptr<UINode> read_select(
|
|||
selected.text = innerText;
|
||||
}
|
||||
|
||||
SelectBox::Mode mode = SelectBox::Mode::SELECT;
|
||||
if (element.has("mode")) {
|
||||
auto modeName = element.attr("mode").getText();
|
||||
if (modeName == "button") {
|
||||
mode = SelectBox::Mode::BUTTON;
|
||||
}
|
||||
}
|
||||
|
||||
auto selectBox = std::make_shared<SelectBox>(
|
||||
gui,
|
||||
std::move(options),
|
||||
std::move(selected),
|
||||
mode,
|
||||
contentWidth,
|
||||
std::move(padding)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <iosfwd>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../path.hpp"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Inventories::Inventories(Level& level) : level(level) {
|
|||
Inventories::~Inventories() = default;
|
||||
|
||||
std::shared_ptr<Inventory> Inventories::create(size_t size) {
|
||||
int64_t id = level.getWorld()->getNextInventoryId();
|
||||
int64_t id = level.getWorld().getNextInventoryId();
|
||||
auto inv = std::make_shared<Inventory>(id, size);
|
||||
store(inv);
|
||||
return inv;
|
||||
|
|
@ -52,7 +52,7 @@ std::shared_ptr<Inventory> Inventories::clone(int64_t id) {
|
|||
auto original = get(id);
|
||||
if (original == nullptr) return nullptr;
|
||||
auto clone = std::make_shared<Inventory>(*original);
|
||||
clone->setId(level.getWorld()->getNextInventoryId());
|
||||
clone->setId(level.getWorld().getNextInventoryId());
|
||||
store(clone);
|
||||
return clone;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ ChunksController::ChunksController(Level& level)
|
|||
generator(std::make_unique<WorldGenerator>(
|
||||
level.content.generators.require(level.environment.generator),
|
||||
level.content,
|
||||
level.getWorld()->getSeed()
|
||||
level.getWorld().getSeed()
|
||||
)) {}
|
||||
|
||||
ChunksController::~ChunksController() = default;
|
||||
|
|
|
|||
|
|
@ -314,8 +314,8 @@ void EngineController::setLocalPlayer(int64_t player) {
|
|||
localPlayer = player;
|
||||
}
|
||||
|
||||
void EngineController::reopenWorld(World* world) {
|
||||
std::string name = world->wfile->getFolder().name();
|
||||
void EngineController::reopenWorld(World& world) {
|
||||
std::string name = world.wfile->getFolder().name();
|
||||
engine.onWorldClosed();
|
||||
openWorld(name, true);
|
||||
}
|
||||
|
|
@ -384,16 +384,16 @@ void EngineController::reconfigPacks(
|
|||
}
|
||||
} else {
|
||||
auto level = controller->getLevel();
|
||||
auto world = level->getWorld();
|
||||
auto& world = level->getWorld();
|
||||
controller->processBeforeQuit();
|
||||
controller->saveWorld();
|
||||
|
||||
auto names = PacksManager::getNames(world->getPacks());
|
||||
auto names = PacksManager::getNames(world.getPacks());
|
||||
auto& manager = contentControl.scan();
|
||||
reconfig_packs_inside(manager, names, packsToAdd, packsToRemove);
|
||||
|
||||
const auto& settings = engine.getSettings();
|
||||
auto& wfile = *world->wfile;
|
||||
auto& wfile = *world.wfile;
|
||||
if (!settings.debug.generatorTestMode.get()) {
|
||||
wfile.removeIndices(packsToRemove);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,5 +41,5 @@ public:
|
|||
|
||||
void setLocalPlayer(int64_t player);
|
||||
|
||||
void reopenWorld(World* world);
|
||||
void reopenWorld(World& world);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -140,16 +140,16 @@ void LevelController::processBeforeQuit() {
|
|||
}
|
||||
|
||||
void LevelController::saveWorld() {
|
||||
auto world = level->getWorld();
|
||||
if (world->isNameless()) {
|
||||
auto& world = level->getWorld();
|
||||
if (world.isNameless()) {
|
||||
logger.info() << "nameless world will not be saved";
|
||||
return;
|
||||
}
|
||||
logger.info() << "writing world '" << world->getName() << "'";
|
||||
world->wfile->createDirectories();
|
||||
logger.info() << "writing world '" << world.getName() << "'";
|
||||
world.wfile->createDirectories();
|
||||
scripting::on_world_save();
|
||||
level->onSave();
|
||||
level->getWorld()->write(*level);
|
||||
level->getWorld().write(*level);
|
||||
}
|
||||
|
||||
void LevelController::onWorldQuit() {
|
||||
|
|
|
|||
|
|
@ -340,6 +340,19 @@ static int determine_rotation(
|
|||
if (camDir.z > 0.0f) return BLOCK_DIR_SOUTH | verticalBit;
|
||||
if (camDir.z < 0.0f) return BLOCK_DIR_NORTH | verticalBit;
|
||||
}
|
||||
} else if (name == "ladder") {
|
||||
if (norm.x < 0.0f) return BLOCK_DIR_EAST;
|
||||
if (norm.x > 0.0f) return BLOCK_DIR_WEST;
|
||||
if (norm.z > 0.0f) return BLOCK_DIR_NORTH;
|
||||
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
|
||||
if (abs(camDir.x) > abs(camDir.z)) {
|
||||
if (camDir.x > 0.0f) return BLOCK_DIR_EAST;
|
||||
if (camDir.x < 0.0f) return BLOCK_DIR_WEST;
|
||||
}
|
||||
if (abs(camDir.x) < abs(camDir.z)) {
|
||||
if (camDir.z > 0.0f) return BLOCK_DIR_SOUTH;
|
||||
if (camDir.z < 0.0f) return BLOCK_DIR_NORTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ static int l_get_model(lua::State* L) {
|
|||
auto& rigConfig = *skeleton->config;
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
const auto& modelOverride = skeleton->modelOverrides[index];
|
||||
if (!modelOverride.model.expired()) {
|
||||
if (modelOverride.model.expired()) {
|
||||
return lua::pushstring(L, modelOverride.name);
|
||||
}
|
||||
return lua::pushstring(L, rigConfig.getBones()[index]->model.name);
|
||||
|
|
|
|||
|
|
@ -1127,6 +1127,7 @@ static int l_gui_load_document(lua::State* L) {
|
|||
}
|
||||
|
||||
auto env = scripting::create_doc_environment(parentEnv, alias);
|
||||
auto envId = *env;
|
||||
// namespace extension
|
||||
if (lua::istable(L, 4)) {
|
||||
if (lua::get_from(L, "table", "extend")) {
|
||||
|
|
@ -1148,7 +1149,7 @@ static int l_gui_load_document(lua::State* L) {
|
|||
engine->requireAssets().store(std::move(documentPtr), alias);
|
||||
|
||||
scripting::on_ui_open(*document, {args});
|
||||
return 0;
|
||||
return lua::pushenv(L, envId);
|
||||
}
|
||||
|
||||
static int l_set_syntax_styles(lua::State* L) {
|
||||
|
|
|
|||
|
|
@ -149,6 +149,15 @@ static int l_get_second_inventory(lua::State* L) {
|
|||
}
|
||||
}
|
||||
|
||||
static int l_get_exchange_inventory(lua::State* L) {
|
||||
auto inventory = hud->getExchangeInventory();
|
||||
if (inventory == nullptr) {
|
||||
return lua::pushinteger(L, 0);
|
||||
} else {
|
||||
return lua::pushinteger(L, inventory->getId());
|
||||
}
|
||||
}
|
||||
|
||||
static int l_get_player(lua::State* L) {
|
||||
auto player = hud->getPlayer();
|
||||
return lua::pushinteger(L, player->getId());
|
||||
|
|
@ -216,6 +225,7 @@ const luaL_Reg hudlib[] = {
|
|||
{"show_overlay", wrap_hud<l_show_overlay>},
|
||||
{"get_block_inventory", wrap_hud<l_get_block_inventory>},
|
||||
{"get_second_inventory", wrap_hud<l_get_second_inventory>},
|
||||
{"get_exchange_inventory", wrap_hud<l_get_exchange_inventory>},
|
||||
{"close", wrap_hud<l_close>},
|
||||
{"pause", wrap_hud<l_pause>},
|
||||
{"resume", wrap_hud<l_resume>},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "world/Weather.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
|
|
@ -9,6 +10,11 @@ static Weather& require_weather() {
|
|||
if (level == nullptr) {
|
||||
throw std::runtime_error("world is not open");
|
||||
}
|
||||
if (renderer == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"world frontend is not initialized yet (wait for on_hud_open event)"
|
||||
);
|
||||
}
|
||||
return renderer->getWeather();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ static Level& require_level() {
|
|||
}
|
||||
|
||||
static WorldInfo& require_world_info() {
|
||||
return require_level().getWorld()->getInfo();
|
||||
return require_level().getWorld().getInfo();
|
||||
}
|
||||
|
||||
static int l_is_open(lua::State* L) {
|
||||
|
|
@ -139,7 +139,7 @@ static int l_get_chunk_data(lua::State* L) {
|
|||
auto voxelData = std::make_unique<ubyte[]>(CHUNK_DATA_LEN);
|
||||
std::vector<ubyte> chunkData;
|
||||
if (chunk == nullptr) {
|
||||
auto& regions = level->getWorld()->wfile->getRegions();
|
||||
auto& regions = level->getWorld().wfile->getRegions();
|
||||
if (!regions.getVoxels(x, z, voxelData.get())) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ static int l_save_chunk_data(lua::State* L) {
|
|||
reinterpret_cast<const ubyte*>(buffer.data()),
|
||||
reinterpret_cast<const ubyte*>(buffer.data()) + buffer.size()
|
||||
),
|
||||
level->getWorld()->wfile->getRegions()
|
||||
level->getWorld().wfile->getRegions()
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,6 +264,12 @@ static std::unordered_map<std::string, lua_CFunction> methods {
|
|||
{"min", lua::wrap<l_binop_func<util::min>>},
|
||||
{"max", lua::wrap<l_binop_func<util::max>>},
|
||||
{"abs", lua::wrap<l_unaryop_func<util::abs>>},
|
||||
{"floor", lua::wrap<l_unaryop_func<util::floor>>},
|
||||
{"round", lua::wrap<l_unaryop_func<util::round>>},
|
||||
{"ceil", lua::wrap<l_unaryop_func<util::ceil>>},
|
||||
{"sin", lua::wrap<l_unaryop_func<util::sin>>},
|
||||
{"cos", lua::wrap<l_unaryop_func<util::cos>>},
|
||||
{"tan", lua::wrap<l_unaryop_func<util::tan>>},
|
||||
{"resize", lua::wrap<l_resize>},
|
||||
{"crop", lua::wrap<l_crop>},
|
||||
{"at", lua::wrap<l_at>},
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ void scripting::on_world_load(LevelController* controller) {
|
|||
for (auto& pack : content_control->getAllContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
|
||||
return lua::pushboolean(
|
||||
L, !scripting::level->getWorld()->getInfo().isLoaded
|
||||
L, !scripting::level->getWorld().getInfo().isLoaded
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,12 +109,16 @@ namespace util {
|
|||
|
||||
/// @return integer square of vector length
|
||||
/// @note glm::length2 does not support integer vectors
|
||||
inline int length2(const glm::ivec3& a) {
|
||||
inline long long int length2(const glm::ivec2& a) {
|
||||
return a.x * a.x + a.y * a.y;
|
||||
}
|
||||
|
||||
inline long long int length2(const glm::ivec3& a) {
|
||||
return a.x * a.x + a.y * a.y + a.z * a.z;
|
||||
}
|
||||
|
||||
/// @return integer square of vector length
|
||||
inline int length2(int x, int y, int z) {
|
||||
inline long long int length2(int x, int y, int z) {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -492,10 +492,11 @@ void Entities::render(
|
|||
}
|
||||
|
||||
bool Entities::hasBlockingInside(AABB aabb) {
|
||||
constexpr float eps = 0.05f;
|
||||
auto view = registry->view<EntityId, Rigidbody>();
|
||||
for (auto [entity, eid, body] : view.each()) {
|
||||
AABB bodyAABB(body.hitbox.getAABB());
|
||||
bodyAABB.scale({1, 0.95f, 1});
|
||||
bodyAABB.scale(glm::vec3(1.0f - eps));
|
||||
if (eid.def.blocking && aabb.intersects(bodyAABB)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ Player* Players::getNearest(const glm::vec3& position) const {
|
|||
}
|
||||
|
||||
Player* Players::create(int64_t id) {
|
||||
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId;
|
||||
int64_t& nextPlayerID = level.getWorld().getInfo().nextPlayerId;
|
||||
if (id == NONE) {
|
||||
id = nextPlayerID++;
|
||||
} else {
|
||||
|
|
@ -146,7 +146,7 @@ void Players::deserialize(const dv::value& src) {
|
|||
auto& inventory = player->getInventory();
|
||||
// invalid inventory id pre 0.25
|
||||
if (inventory->getId() == 0) {
|
||||
inventory->setId(level.getWorld()->getNextInventoryId());
|
||||
inventory->setId(level.getWorld().getNextInventoryId());
|
||||
}
|
||||
level.inventories->store(inventory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#include "Clock.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace util;
|
||||
|
|
@ -20,10 +19,8 @@ int Clock::update(float delta) {
|
|||
tickTimer -= parts * delay / tickParts;
|
||||
tickTimer = std::min<float>(tickTimer, delay);
|
||||
}
|
||||
currentTickPart += parts;
|
||||
if (currentTickPart >= tickParts) {
|
||||
currentTickPart %= tickParts;
|
||||
}
|
||||
lastPartsStart = currentTickPart;
|
||||
currentTickPart = (currentTickPart + parts) % tickParts;
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
|
@ -40,5 +37,5 @@ int Clock::getTickId() const {
|
|||
}
|
||||
|
||||
int Clock::convertPart(int index) const {
|
||||
return (tickParts - currentTickPart) % tickParts + index;
|
||||
return (lastPartsStart + index) % tickParts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace util {
|
|||
float tickTimer = 0.0f;
|
||||
int tickId = 0;
|
||||
int currentTickPart = 0;
|
||||
int lastPartsStart = 0;
|
||||
public:
|
||||
Clock(int tickRate, int tickParts);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#include <optional>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
|
|
|||
|
|
@ -3,31 +3,26 @@
|
|||
#include <glm/glm.hpp>
|
||||
|
||||
namespace util {
|
||||
template<class T>
|
||||
struct pow {
|
||||
constexpr T operator()(T a, T b) const {
|
||||
return glm::pow(a, b);
|
||||
}
|
||||
template<class T, T (*F)(T, T)>
|
||||
struct binary_op {
|
||||
constexpr T operator()(T a, T b) const { return F(a, b); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct min {
|
||||
constexpr T operator()(T a, T b) const {
|
||||
return glm::min(a, b);
|
||||
}
|
||||
template<class T> using pow = binary_op<T, glm::pow>;
|
||||
template<class T> using min = binary_op<T, glm::min>;
|
||||
template<class T> using max = binary_op<T, glm::max>;
|
||||
|
||||
|
||||
template<class T, T (*F)(T)>
|
||||
struct unary_op {
|
||||
constexpr T operator()(T a) const { return F(a); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct max {
|
||||
constexpr T operator()(T a, T b) const {
|
||||
return glm::max(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct abs {
|
||||
constexpr T operator()(T a) const {
|
||||
return glm::abs(a);
|
||||
}
|
||||
};
|
||||
template<typename T> using abs = unary_op<T, glm::abs>;
|
||||
template<typename T> using floor = unary_op<T, glm::floor>;
|
||||
template<typename T> using round = unary_op<T, glm::round>;
|
||||
template<typename T> using ceil = unary_op<T, glm::ceil>;
|
||||
template<typename T> using sin = unary_op<T, glm::sin>;
|
||||
template<typename T> using cos = unary_op<T, glm::cos>;
|
||||
template<typename T> using tan = unary_op<T, glm::tan>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,17 @@ const BlockRotProfile BlockRotProfile::STAIRS {
|
|||
8
|
||||
};
|
||||
|
||||
const BlockRotProfile BlockRotProfile::LADDER {
|
||||
"ladder",
|
||||
{
|
||||
{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // North
|
||||
{{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}, // East
|
||||
{{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}}, // South
|
||||
{{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}, // West
|
||||
},
|
||||
4
|
||||
};
|
||||
|
||||
Block::Block(const std::string& name)
|
||||
: name(name), caption(util::id_to_caption(name)) {
|
||||
for (int i = 0; i < defaults.textureFaces.size(); i++) {
|
||||
|
|
|
|||
|
|
@ -90,9 +90,13 @@ struct BlockRotProfile {
|
|||
/// @brief Stairs, stairs and stairs
|
||||
static const BlockRotProfile STAIRS;
|
||||
|
||||
/// @brief Ladders, wall signs, paintings
|
||||
static const BlockRotProfile LADDER;
|
||||
|
||||
static inline std::string PIPE_NAME = "pipe";
|
||||
static inline std::string PANE_NAME = "pane";
|
||||
static inline std::string STAIRS_NAME = "stairs";
|
||||
static inline std::string LADDER_NAME = "ladder";
|
||||
};
|
||||
|
||||
enum class BlockModelType {
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@ public:
|
|||
voxel voxels[CHUNK_VOL] {};
|
||||
std::shared_ptr<Lightmap> lightmap;
|
||||
struct {
|
||||
bool modified : 1;
|
||||
bool ready : 1;
|
||||
bool loaded : 1;
|
||||
bool lighted : 1;
|
||||
bool unsaved : 1;
|
||||
bool loadedLights : 1;
|
||||
bool entities : 1;
|
||||
bool blocksData : 1;
|
||||
bool dirtyHeights : 1;
|
||||
bool inventoriesRemoved : 1;
|
||||
bool modified : 1; // is chunk mesh should be updated
|
||||
bool ready : 1; // is chunk ready for modifications (loaded / generated)
|
||||
bool loaded : 1; // was chunk loaded from region
|
||||
bool lighted : 1; // is lights built (chunk ready to be visualized)
|
||||
bool unsaved : 1; // does chunk contain unsaved changes
|
||||
bool loadedLights : 1; // was lights loaded from cache
|
||||
bool entities : 1; // does chunk contain entities list changes
|
||||
bool blocksData : 1; // does chunk contain block fields changes
|
||||
bool dirtyHeights : 1; // is chunk bottom, top should be recalculated
|
||||
bool inventoriesRemoved : 1; // was block inventories removed since the last save
|
||||
} flags {};
|
||||
|
||||
uint64_t lastRandomTickId = -1;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ std::shared_ptr<Chunk> GlobalChunks::create(int x, int z, bool lighting) {
|
|||
chunks_pool.create(x, z, lighting ? lightmaps_pool.create() : nullptr);
|
||||
chunksMap[keyfrom(x, z)] = chunk;
|
||||
|
||||
World& world = *level.getWorld();
|
||||
World& world = level.getWorld();
|
||||
auto& regions = world.wfile.get()->getRegions();
|
||||
|
||||
if (regions.getVoxels(chunk->x, chunk->z, voxelDataBuffer.get())) {
|
||||
|
|
@ -197,7 +197,7 @@ void GlobalChunks::save(Chunk* chunk) {
|
|||
if (!entities.empty()) {
|
||||
chunk->flags.entities = true;
|
||||
}
|
||||
level.getWorld()->wfile->getRegions().put(
|
||||
level.getWorld().wfile->getRegions().put(
|
||||
chunk,
|
||||
chunk->flags.entities ? json::to_binary(root, true)
|
||||
: std::vector<ubyte>()
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public:
|
|||
return lights.get();
|
||||
}
|
||||
|
||||
inline blockid_t pickBlockId(int bx, int by, int bz) const {
|
||||
blockid_t pickBlockId(int bx, int by, int bz) const {
|
||||
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
|
||||
bz >= z + d) {
|
||||
return BLOCK_VOID;
|
||||
|
|
@ -75,7 +75,7 @@ public:
|
|||
}
|
||||
|
||||
|
||||
inline voxel pickBlock(int bx, int by, int bz) const {
|
||||
voxel pickBlock(int bx, int by, int bz) const {
|
||||
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
|
||||
bz >= z + d) {
|
||||
return {BLOCK_VOID, {}};
|
||||
|
|
@ -83,7 +83,7 @@ public:
|
|||
return voxels[vox_index(bx - x, by - y, bz - z, w, d)];
|
||||
}
|
||||
|
||||
inline light_t pickLight(int bx, int by, int bz) const {
|
||||
light_t pickLight(int bx, int by, int bz) const {
|
||||
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
|
||||
bz >= z + d) {
|
||||
return 0;
|
||||
|
|
@ -103,10 +103,10 @@ private:
|
|||
template <int w, int h, int d>
|
||||
class StaticVoxelsVolume {
|
||||
public:
|
||||
static inline constexpr size_t size = w * h * d;
|
||||
static inline constexpr int width = w;
|
||||
static inline constexpr int height = h;
|
||||
static inline constexpr int depth = d;
|
||||
static constexpr size_t size = w * h * d;
|
||||
static constexpr int width = w;
|
||||
static constexpr int height = h;
|
||||
static constexpr int depth = d;
|
||||
|
||||
StaticVoxelsVolume()
|
||||
: StaticVoxelsVolume(0, 0, 0) {}
|
||||
|
|
@ -142,7 +142,7 @@ public:
|
|||
return lights;
|
||||
}
|
||||
|
||||
inline blockid_t pickBlockId(uint bx, uint by, uint bz) const {
|
||||
blockid_t pickBlockId(uint bx, uint by, uint bz) const {
|
||||
bx -= x;
|
||||
by -= y;
|
||||
bz -= z;
|
||||
|
|
@ -153,7 +153,7 @@ public:
|
|||
}
|
||||
|
||||
|
||||
inline const voxel& pickBlock(uint bx, uint by, uint bz) const {
|
||||
const voxel& pickBlock(uint bx, uint by, uint bz) const {
|
||||
bx -= x;
|
||||
by -= y;
|
||||
bz -= z;
|
||||
|
|
@ -164,7 +164,7 @@ public:
|
|||
return voxels[vox_index(bx, by, bz, w, d)];
|
||||
}
|
||||
|
||||
inline light_t pickLight(uint bx, uint by, uint bz) const {
|
||||
light_t pickLight(uint bx, uint by, uint bz) const {
|
||||
bx -= x;
|
||||
by -= y;
|
||||
bz -= z;
|
||||
|
|
|
|||
|
|
@ -71,12 +71,12 @@ Level::Level(
|
|||
|
||||
Level::~Level() = default;
|
||||
|
||||
World* Level::getWorld() {
|
||||
return world.get();
|
||||
World& Level::getWorld() {
|
||||
return *world;
|
||||
}
|
||||
|
||||
const World* Level::getWorld() const {
|
||||
return world.get();
|
||||
const World& Level::getWorld() const {
|
||||
return *world;
|
||||
}
|
||||
|
||||
void Level::onSave() {
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ public:
|
|||
);
|
||||
~Level();
|
||||
|
||||
World* getWorld();
|
||||
World& getWorld();
|
||||
|
||||
const World* getWorld() const;
|
||||
const World& getWorld() const;
|
||||
|
||||
void onSave();
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ std::unique_ptr<Level> World::load(
|
|||
|
||||
if (!playerRoot["players"].empty()) {
|
||||
if (!playerRoot["players"][0].has("id")) {
|
||||
level->getWorld()->getInfo().nextPlayerId++;
|
||||
level->getWorld().getInfo().nextPlayerId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -216,9 +216,6 @@ void WorldInfo::deserialize(const dv::value& root) {
|
|||
daytimeSpeed = timeobj["day-time-speed"].asNumber();
|
||||
totalTime = timeobj["total-time"].asNumber();
|
||||
}
|
||||
if (root.has("weather")) {
|
||||
fog = root["weather"]["fog"].asNumber();
|
||||
}
|
||||
nextInventoryId = root["next-inventory-id"].asInteger(2);
|
||||
nextEntityId = root["next-entity-id"].asInteger(1);
|
||||
root.at("next-player-id").get(nextPlayerId);
|
||||
|
|
@ -244,9 +241,6 @@ dv::value WorldInfo::serialize() const {
|
|||
timeobj["day-time-speed"] = daytimeSpeed;
|
||||
timeobj["total-time"] = totalTime;
|
||||
|
||||
root["weather"] = dv::object();
|
||||
root["weather"]["fog"] = fog;
|
||||
|
||||
root["next-inventory-id"] = nextInventoryId;
|
||||
root["next-entity-id"] = nextEntityId;
|
||||
root["next-player-id"] = nextPlayerId;
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@ struct WorldInfo : public Serializable {
|
|||
/// @brief total time passed in the world (not depending on daytimeSpeed)
|
||||
double totalTime = 0.0;
|
||||
|
||||
/// @brief will be replaced with weather in future
|
||||
float fog = 0.0f;
|
||||
|
||||
entityid_t nextEntityId = 0;
|
||||
|
||||
int major = 0, minor = -1;
|
||||
|
|
@ -70,6 +67,8 @@ public:
|
|||
const std::vector<ContentPack>& packs
|
||||
);
|
||||
|
||||
World(const World&) = delete;
|
||||
|
||||
~World();
|
||||
|
||||
/// @brief Update world day-time and total time
|
||||
|
|
|
|||
|
|
@ -219,7 +219,8 @@ std::unique_ptr<VoxelFragment> VoxelFragment::rotated(const Content& content) co
|
|||
| ((voxel.state.segment & 0b100) >> 2);
|
||||
auto& def = content.blocks.require(blockNames[voxel.id]);
|
||||
if (def.rotations.name == BlockRotProfile::PANE_NAME ||
|
||||
def.rotations.name == BlockRotProfile::PIPE_NAME) {
|
||||
def.rotations.name == BlockRotProfile::PIPE_NAME ||
|
||||
def.rotations.name == BlockRotProfile::LADDER_NAME) {
|
||||
if (voxel.state.rotation < 4) {
|
||||
voxel.state.rotation = (voxel.state.rotation + 3) & 0b11;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue