Merge branch 'main' into animation

This commit is contained in:
MihailRis 2026-08-04 23:39:49 +03:00
commit 282c9b3de2
68 changed files with 1419 additions and 488 deletions

View file

@ -18,19 +18,86 @@ Bytearray.insert(arr, {5, 3, 6})
assert(#arr == 12) assert(#arr == 12)
Bytearray.insert(arr, 2, 8) Bytearray.insert(arr, 2, 8)
assert(#arr == 13) asserts.equals(13, #arr)
for i=1,10 do for i=1,10 do
assert(arr[i] == 10 - i) assert(arr[i] == 10 - i)
end end
print(#arr, arr:get_capacity()) print(#arr, arr:get_capacity())
arr:trim() arr:trim()
assert(#arr == arr:get_capacity()) asserts.equals(#arr, arr:get_capacity())
arr = Bytearray({0, 2, 7, 1, 16, 75, 25}) arr = Bytearray({0, 2, 7, 1, 16, 75, 25})
assert(arr[6] == 75) assert(arr[6] == 75)
arr:insert(2, {5, 6}) arr:insert(2, {5, 6})
assert(arr[#arr] == 25) asserts.equals(25, arr[#arr])
assert(arr[2] == 5) asserts.equals(5, arr[2])
assert(arr[3] == 6) asserts.equals(6, arr[3])
-- =============================
arr = Bytearray({254, 255, 255, 255, 255, 255, 255, 255})
asserts.equals(U64view(arr)[1], ctypes.uint64(2) ^ 64 - 2)
asserts.equals(I64view(arr)[1], -2)
asserts.equals(U32view(arr)[1], 2 ^ 32 - 2)
asserts.equals(I32view(arr)[1], -2)
asserts.equals(U16view(arr)[1], 2 ^ 16 - 2)
asserts.equals(I16view(arr)[1], -2)
asserts.equals(I8view(arr)[1], -2)
asserts.equals(I8view(arr)[2], -1)
asserts.equals(I32view(arr)[2], -1)
arr = Bytearray({0xFF, 0x7F})
local arri16 = I16view(arr)
local arru16 = U16view(arr)
asserts.equals(arri16[1], 32767)
arru16[1] = arru16[1] + 1
asserts.equals(arri16[1], -32768)
asserts.equals(arru16[1], 32768)
arr = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number.
local arrflt = FLTview(arr)
local arru32 = U32view(arr)
asserts.equals(arrflt[1], 3.4000000953674316)
asserts.equals(arru32[1], 0x4059999A)
arrflt[1] = arrflt[1] + 0.1
asserts.equals(arrflt[1], 3.5)
asserts.equals(arru32[1], 0x40600000) -- hexadecimal representation of 3.5 single-precision float number.
arr = Bytearray({0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 9, 64}) -- hexadecimal representation of 3.141592653589793 double-precision float number.
assert(DBLview(arr)[1] == 3.141592653589793)
-- =============================================
function barrtostr(barr)
local outstr = ""
for i = 1, #barr do
local lastfmt = ""
if i ~= #barr then lastfmt = "|" end
outstr = outstr .. barr[i] .. lastfmt
end
return outstr
end
arr = Bytearray({0, 5, 34, 87, 21, 0, 210})
asserts.equals(barrtostr(arr), "0|5|34|87|21|0|210")
arr:move(2, 3, 4)
asserts.equals(barrtostr(arr), "0|5|5|34|87|21|210")
local arr2 = Bytearray({1, 2, 3, 4, 6, 7, 8})
asserts.equals(barrtostr(arr2), "1|2|3|4|6|7|8")
arr:copy(4, arr2, 2, 3)
asserts.equals(barrtostr(arr2), "1|34|87|21|6|7|8")
arr:fill(nil, nil, 192)
asserts.equals(barrtostr(arr), "192|192|192|192|192|192|192")
arr:fill(nil, 3, 233)
asserts.equals(barrtostr(arr), "233|233|233|233|233|233|233")
arr:fill(2, nil, 254)
asserts.equals(barrtostr(arr), "254|254|254|254|254|254|254")
arr:fill(2, 2, 66)
asserts.equals(barrtostr(arr), "254|66|66|254|254|254|254")

View file

@ -1,6 +1,6 @@
# Block properties # Block properties
## Visual ## Visual & Audio
### *texture* ### *texture*
@ -70,6 +70,18 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
- "pane" - panels, doors, signs - "pane" - panels, doors, signs
- "stairs" - "pane" + flipped variants - "stairs" - "pane" + flipped variants
### *particles*
Particles are specified as a JSON object. Property names can be found [in the particles section](particles.md).
When camera is near to the block, the engine will create an emitter that will run
until the block is destroyed or the camera moves away a certain distance.
### Material - *material*
Defines the name of the block's material in the format `pack:material_name`, which affects the selection of block interaction sounds.
Material definitions are located in /block_materials.
## Variants ## Variants
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block. Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.

View file

@ -38,7 +38,7 @@ Models are loaded automatically; adding them to preload.json is not required.
## Models ## Models
Models should be located in the models folder. Currently only OBJ format is supported. Models must be located in the models folder. Currently, the following formats are supported: obj, vec3, and [vcm](vcm.md).
>[!IMPORTANT] >[!IMPORTANT]
> When loading an obj model, the \*.mtl file is ignored. > When loading an obj model, the \*.mtl file is ignored.

View file

@ -16,6 +16,7 @@ Subsections:
- [block](scripting/builtins/libblock.md) - [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md) - [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md) - [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md) - [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.md) - [file](scripting/builtins/libfile.md)
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)

View file

@ -180,14 +180,34 @@ app.get_content_sources() -> table<string>
Returns a list of content sources (paths), in descending priority order. Returns a list of content sources (paths), in descending priority order.
``lua ```lua
app.set_content_sources(sources: table<string>) app.set_content_sources(sources: table<string>)
``` ```
Sets a list of content sources (paths). Specified in descending priority order. Sets a list of content sources (paths). Specified in descending priority order.
``lua ```lua
app.reset_content_sources() app.reset_content_sources()
``` ```
Resets content sources. Resets content sources.
## Sub-instances
```lua
-- Creates a headless engine instance with the current project and the specified application script.
-- Returns the instance ID. The number of active sub-instances is currently limited to one.
app.start_background_instance(
-- script file
app_script: string,
-- log file
output_file: string | nil
) -> int
-- Checks if the engine sub-instance is alive.
app.is_instance_alive(handle: int) -> boolean
-- Stops the engine sub-instance.
-- Returns true if the instance was alive at the time of the call.
app.terminate(handle: int) -> boolean
```

View file

@ -28,7 +28,8 @@ block.get(x: int, y: int, z: int) -> int
block.get_states(x: int, y: int, z: int) -> int block.get_states(x: int, y: int, z: int) -> int
-- Set block with given integer ID and state (default - 0) at given position. -- Set block with given integer ID and state (default - 0) at given position.
block.set(x: int, y: int, z: int, id: int, states: int) -- If noupdate=true is passed, the `on_update` event will not be called for adjacent blocks.
block.set(x: int, y: int, z: int, id: int, states: int, noupdate: boolean=false)
-- Places a block with a given integer id and state (default - 0) at given position. -- Places a block with a given integer id and state (default - 0) at given position.
-- on behalf of the player, calling the on_placed event. -- on behalf of the player, calling the on_placed event.

View file

@ -0,0 +1,28 @@
# *ctypes* Library
A library for safe work with C types.
| ctypes.\<name\> | C-Type | Minimum | Maximum |
| --------------- | -------- | -------------------- | -------------------- |
| uint8 | uint8_t | 0 | 255 |
| uint16 | uint16_t | 0 | 65535 |
| uint32 | uint32_t | 0 | 4294967295 |
| uint64 | uint64_t | 0 | 18446744073709551615 |
| int8 | int8_t | -128 | 127 |
| int16 | int16_t | -32768 | 32767 |
| int32 | int32_t | -2147483648 | 2147483647 |
| int64 | int64_t | -9223372036854775808 | 9223372036854775807 |
Usage examples:
```lua
local x = ctypes.uint64(1234)
print(x) --> 1234ULL (cdata)
local y = ctypes.uint64("18446744073709551615")
print(y) --> 18446744073709551615ULL (cdata)
print(x + y) --> 1233ULL
local z = tonumber(x)
print(z) --> 1234 (number)
```

View file

@ -1,6 +1,6 @@
# Свойства блоков # Свойства блоков
## Вид ## Вид & Звук
### Текстура - *texture* ### Текстура - *texture*
@ -78,6 +78,11 @@
При приближении к блоку движок создаст эмиттер, который будет работать При приближении к блоку движок создаст эмиттер, который будет работать
до разрушения блока или отдаления камеры на некоторое расстояние. до разрушения блока или отдаления камеры на некоторое расстояние.
### Материал - *material*
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
Определения материалов расположены в /block_materials.
## Варианты ## Варианты
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока. Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.

View file

@ -38,7 +38,7 @@
## Модели ## Модели
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат. Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
>[!IMPORTANT] >[!IMPORTANT]
> При загрузке obj модели игнорируется файл \*.mtl. > При загрузке obj модели игнорируется файл \*.mtl.

View file

@ -16,6 +16,7 @@
- [block](scripting/builtins/libblock.md) - [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md) - [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md) - [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md) - [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.md) - [file](scripting/builtins/libfile.md)
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)

View file

@ -142,4 +142,24 @@ app.set_content_sources(sources: table<string>)
-- Сбрасывает список источников контента. -- Сбрасывает список источников контента.
app.reset_content_sources() app.reset_content_sources()
``` ```
## Под-экземпляры
```lua
-- Создаёт headless-экземпляр движка с текущим проектом и указанным сценарием.
-- Возвращает id экземпляра. Число живых под-экземпляров, на данный момент, ограничено одним.
app.start_background_instance(
-- файл сценария
app_script: string,
-- файл лога
output_file: string | nil
) -> int
-- Проверяет, жив ли под-экземпляр движка.
app.is_instance_alive(handle: int) -> boolean
-- Останавливает под-экземпляр движка.
-- Возвращает true если экземпляр был жив в момент вызова.
app.terminate_instance(handle: int) -> boolean
```

View file

@ -0,0 +1,28 @@
# Библиотека *ctypes*
Библиотека для безопасной работы с типами языка С.
| ctypes.\<name\> | С-тип | Минимум | Максимум |
| ------------- | -------- | -------------------- | -------------------- |
| uint8 | uint8_t | 0 | 255 |
| uint16 | uint16_t | 0 | 65535 |
| uint32 | uint32_t | 0 | 4294967295 |
| uint64 | uint64_t | 0 | 18446744073709551615 |
| int8 | int8_t | -128 | 127 |
| int16 | int16_t | -32768 | 32767 |
| int32 | int32_t | -2147483648 | 2147483647 |
| int64 | int64_t | -9223372036854775808 | 9223372036854775807 |
Примеры использования:
```lua
local x = ctypes.uint64(1234)
print(x) --> 1234ULL (cdata)
local y = ctypes.uint64("18446744073709551615")
print(y) --> 18446744073709551615ULL (cdata)
print(x + y) --> 1233ULL
local z = tonumber(x)
print(z) --> 1234 (number)
```

View file

@ -2,6 +2,8 @@
*Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs** *Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs**
<small>Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число.</small>
## Основное ## Основное
### Создание массива ### Создание массива
```lua ```lua
@ -22,29 +24,64 @@ bytes:insert(index: int, value: int)
-- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса. -- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса.
bytes:remove(index: int, [опционально] count: int) bytes:remove(index: int, [опционально] count: int)
-- Очищает массив -- Очищает массив.
bytes:clear() bytes:clear()
--[[
Заполняет массив определённым байтом полностью либо в определённом диапазоне.
Логика работы следующая: если параметр size или index равны nil, то указанным байтом будет заполнен весь массив,
иначе будет заполнен диапазон внутри массива начиная с индекса index и размером size.
В случае выхода заполняемого блока за границы массива генерирует соответствующую ошибку.
]]
bytes:fill(index: posint/nil, size: posint/nil, byte: uint8)
-- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length -- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length
bytes:slice(offset: int, length: int) -> Bytearray bytes:slice(offset: int, length: int) -> Bytearray
--[[
Копирует блок байт из исходного массива, начиная с индекса srcindex, в массив dst, начиная в нём с индекса dstindex.
В случае выхода за границы исходного, конечного массива, либо при некорректном значении параметра size генерирует соответствующую ошибку.
]]
bytes:copy(srcindex: posint, dst: Bytearray, dstindex: posint, size: posint)
--[[
Безопасно копирует перекрывающиеся области памяти (байт) в пределах одного массива.
В случае выхода за пределы массива начального или конечного диапазона байт генерирует соответствующую ошибку.
]]
bytes:move(fromindex: posint, toindex: posint, size: posint)
``` ```
## View ## View
Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**. Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**.
Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray. Практически не имеют своих методов, являются простыми объектными ссылками на оригинальный Bytearray.
| Класс | Тип элементов | Размер | | Класс | Тип элементов | Размер |
| ------------ | ------------------ | ------- | | ------------ | ------------------ | --------- |
| `I16view` | `int16_t` | 2 байта | | `I8view` | `int8_t` | 1 байт |
| `U16view` | `uint16_t` | 2 байта | | `I16view` | `int16_t` | 2 байта |
| `I32view` | `int32_t` | 4 байта | | `U16view` | `uint16_t` | 2 байта |
| `U32view` | `uint32_t` | 4 байта | | `I32view` | `int32_t` | 4 байта |
| `U32view` | `uint32_t` | 4 байта |
| `I64view` | `int64_t` | 8 байт |
| `U64view` | `uint64_t` | 8 байт |
| `FLTview` | `float` | 4 байта* |
| `DBLview` | `double` | 8 байт* |
--- <small>* Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов.</small>
### Пример использования ### Специфические методы "вьюшек"
```lua
--[[
Возвращает размер типа вьюшки. Результат тот же, что и от применения оператора sizeof() из языка C для данного типа.
Полезно для вьюшек, использующих C-шные типы нефиксированного размера, например те же float или double.
]]
view:typesize() -> posint
```
## Пример использования
```lua ```lua
local bytes = Bytearray({1, 0, 2, 0, 250, 255}) local bytes = Bytearray({1, 0, 2, 0, 250, 255})
@ -59,4 +96,4 @@ print(i16[3]) -- -6 (signed вьюшка)
for _, num in ipairs(i16) do for _, num in ipairs(i16) do
print(num) print(num)
end -- 1; 2; -6 end -- 1; 2; -6
``` ```

View file

@ -1,3 +1,4 @@
generator = "base:demo" generator = "base:demo"
player-entity = "base:player" player-entity = "base:player"
hand-skeleton = "base:hand" hand-skeleton = "base:hand"
block-material = "base:stone"

View file

@ -12,14 +12,14 @@ function util.drop(ppos, itemid, count, data, pickup_delay)
}}) }})
end end
local function calc_loot(loot_table) function util.calc_loot(loot_table)
local results = {} local results = {}
for _, loot in ipairs(loot_table) do for _, loot in ipairs(loot_table) do
local chance = loot.chance or 1 local chance = loot.chance or 1
local count = loot.count or 1 local count = loot.count or 1
local roll = math.random() local roll = math.random()
if roll < chance then if roll < chance then
if loot.min and loot.max then if loot.min and loot.max then
count = math.random(loot.min, loot.max) count = math.random(loot.min, loot.max)
@ -37,7 +37,7 @@ end
function util.block_loot(blockid) function util.block_loot(blockid)
local lootscheme = block.properties[blockid]["base:loot"] local lootscheme = block.properties[blockid]["base:loot"]
if lootscheme then if lootscheme then
return calc_loot(lootscheme) return util.calc_loot(lootscheme)
end end
return {{item=block.get_picking_item(blockid), count=1}} return {{item=block.get_picking_item(blockid), count=1}}
end end

View file

@ -1,3 +1,6 @@
local dirtid = block.index("base:dirt")
local grass_blockid = block.index("base:grass_block")
local offsets = {} local offsets = {}
for lx=-1,1 do for lx=-1,1 do
for ly=-1,1 do for ly=-1,1 do
@ -8,18 +11,17 @@ for lx=-1,1 do
end end
function on_random_update(x, y, z) function on_random_update(x, y, z)
local dirtid = block.index('base:dirt');
if block.is_solid_at(x, y+1, z) then if block.is_solid_at(x, y+1, z) then
block.set(x, y, z, dirtid, 0) block.set(x, y, z, dirtid, 0)
return return
end end
local grassblockid = block.index('base:grass_block')
for _, offset in ipairs(offsets) do for _, offset in ipairs(offsets) do
local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
if block.get(nx, ny, nz) == dirtid and if block.get(nx, ny, nz) == dirtid and
not block.is_solid_at(nx, ny + 1, nz) then not block.is_solid_at(nx, ny + 1, nz) then
block.set(nx, ny, nz, grassblockid, 0) block.set(nx, ny, nz, grass_blockid, 0)
return return
end end
end end

View file

@ -3,6 +3,7 @@ function on_open(params)
mode = params.mode mode = params.mode
end end
refresh() refresh()
document.search_textbox.focused = true
end end
-- add - packs to be added to the world (after apply) -- add - packs to be added to the world (after apply)
@ -178,40 +179,40 @@ function place_pack(panel, packinfo, callback, position_func)
end end
end end
local Version = {}; local Version = {}
function Version.matches_pattern(version) function Version.matches_pattern(version)
for _, letter in string.gmatch(version, "%.+") do local t = string.split(version, ".")
if type(letter) ~= "number" or letter ~= "." then if #t ~= 2 and #t ~= 3 then return false end
return false;
end
local t = string.split(version, "."); for i = 1, #t do
local matched = string.match(t[i], "^%d+$")
return #t == 2 or #t == 3; if not matched then return false end
end end
return true
end end
function Version.__equal(ver1, ver2) function Version.__equal(ver1, ver2)
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]; return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]
end end
function Version.__greater(ver1, ver2) function Version.__greater(ver1, ver2)
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end; if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end; if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end
return ver1[3] > ver2[3]; return ver1[3] > ver2[3]
end end
function Version.__less(ver1, ver2) function Version.__less(ver1, ver2)
return Version.__greater(ver2, ver1); return Version.__greater(ver2, ver1)
end end
function Version.__greater_or_equal(ver1, ver2) function Version.__greater_or_equal(ver1, ver2)
return not Version.__less(ver1, ver2); return not Version.__less(ver2, ver1)
end end
function Version.__less_or_equal(ver1, ver2) function Version.__less_or_equal(ver1, ver2)
return not Version.__greater(ver1, ver2); return not Version.__greater(ver1, ver2)
end end
Version.operators = { Version.operators = {
@ -223,40 +224,39 @@ Version.operators = {
} }
function Version.compare(op, ver1, ver2) function Version.compare(op, ver1, ver2)
ver1 = string.split(ver1, "."); ver1 = string.split(ver1, ".")
ver2 = string.split(ver2, "."); ver2 = string.split(ver2, ".")
local comparison_func = Version.operators[op]; local comparison_func = Version.operators[op];
if comparison_func then if comparison_func then
return comparison_func(ver1, ver2); return comparison_func(ver1, ver2)
else else
return false; return false
end end
end end
function Version.parse(version) function Version.parse(version)
local op = string.sub(version, 1, 2); local op = string.sub(version, 1, 2)
if op == ">=" or op == "<=" then if op == ">=" or op == "<=" then
return op, string.sub(version, #op + 1); return op, string.sub(version, #op + 1)
end end
op = string.sub(version, 1, 1); op = string.sub(version, 1, 1);
if op == ">" or op == "<" then if op == ">" or op == "<" then
return op, string.sub(version, #op + 1); return op, string.sub(version, #op + 1)
end end
return "=", version; return "=", version
end end
local function compare_version(dependent_version, actual_version) local function compare_version(op, dependent_version, actual_version)
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
local op, dep_ver = Version.parse_version(dependent_version); return Version.compare(op, dep_ver, actual_version)
Version.compare(op, dep_ver, actual_version);
elseif dependent_version == "*" or dependent_version == actual_version then elseif dependent_version == "*" or dependent_version == actual_version then
return true; return true
else else
return false; return false
end end
end end
@ -276,15 +276,15 @@ function check_dependencies(packinfo)
) )
end end
local dep_pack = pack.get_info(depid); local dep_pack = pack.get_info(depid)
local op, ver = Version.parse(depver)
if not compare_version(depver, dep_pack.version) then
local op, ver = Version.parse(depver) if not compare_version(op, ver, dep_pack.version) then
return string.format( return string.format(
"%s: %s != %s (%s)", "%s: %s != %s (%s)",
gui.str("error.dependency-version-not-met"), gui.str("error.dependency-version-not-met"),
dep_pack.version, ver, depid dep_pack.version, ver, depid
); )
end end
if table.has(packs_installed, packinfo.id) then if table.has(packs_installed, packinfo.id) then

View file

@ -4,12 +4,14 @@ local FFI = ffi
FFI.cdef[[ FFI.cdef[[
void* malloc(size_t); void* malloc(size_t);
void* realloc(void*, size_t);
void free(void*); void free(void*);
typedef struct { typedef struct {
unsigned char* bytes; unsigned char* bytes;
int size; int size;
int capacity; int capacity;
} bytearray_t; } bytearray_t;
void* memmove(void*, const void*, size_t);
]] ]]
local free = FFI.C.free local free = FFI.C.free
@ -22,12 +24,33 @@ local GC_THRESHOLD = 50 * 1024 * 1024
local function malloc(size) local function malloc(size)
local raw = FFI.C.malloc(size) local raw = FFI.C.malloc(size)
if raw == nil then if not raw then
return nil error("failed memory allocation of " .. size .. " bytes")
end end
allocated_bytes = allocated_bytes + size allocated_bytes = allocated_bytes + size
if allocated_bytes >= GC_THRESHOLD then
collectgarbage("step", 200)
allocated_bytes = 0
end
return raw
end
local function realloc(buff, size, throwerror)
if throwerror == nil then throwerror = true end
if size <= 0 then
free(buff)
return
end
local raw = FFI.C.realloc(buff, size)
if not raw and throwerror then
error("failed memory reallocation of block " .. buff .. " to new size " .. size)
end
allocated_bytes = allocated_bytes + size
if allocated_bytes >= GC_THRESHOLD then if allocated_bytes >= GC_THRESHOLD then
collectgarbage("step", 200) collectgarbage("step", 200)
allocated_bytes = 0 allocated_bytes = 0
@ -38,23 +61,17 @@ end
local function grow_buffer(self, elems) local function grow_buffer(self, elems)
local new_capacity = math.ceil(self.capacity / 0.75 + elems) local new_capacity = math.ceil(self.capacity / 0.75 + elems)
local prev = self.bytes self.bytes = realloc(self.bytes, new_capacity)
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size)
self.capacity = new_capacity self.capacity = new_capacity
free(prev)
end end
local function trim_buffer(self) local function trim_buffer(self)
if self.size == self.capacity then if self.size == self.capacity then return end
return local new_bytes = realloc(self.bytes, self.size, false)
if new_bytes then
self.bytes = new_bytes
self.capacity = self.size
end end
local size = self.size
local prev = self.bytes
self.bytes = malloc(size)
FFI.copy(self.bytes, prev, self.size)
self.capacity = size
free(prev)
end end
local function count_elements(b) local function count_elements(b)
@ -125,15 +142,21 @@ local function clear(self)
self.size = 0 self.size = 0
end end
local function reserve(self, new_capacity) local function fill(self, index, size, byte)
if new_capacity <= self.capacity then if index ~= nil and size ~= nil and size > 0 then
return if index < 1 or index + size - 1 > self.size then
error("selected fill range is out of array bounds")
end
FFI.fill(self.bytes + index - 1, size, byte)
else
FFI.fill(self.bytes, self.size, byte)
end end
local prev = self.bytes end
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size) local function reserve(self, new_capacity)
if new_capacity <= self.capacity then return end
self.bytes = realloc(self.bytes, new_capacity)
self.capacity = new_capacity self.capacity = new_capacity
free(prev)
end end
local function get_capacity(self) local function get_capacity(self)
@ -150,22 +173,46 @@ local function slice(self, offset, length)
length = self.size - offset + 1 length = self.size - offset + 1
end end
local buffer = malloc(length) local buffer = malloc(length)
if not buffer then
error("malloc(" .. length .. ") returned NULL")
end
FFI.copy(buffer, self.bytes + (offset - 1), length) FFI.copy(buffer, self.bytes + (offset - 1), length)
return bytearray_type(buffer, length, length) return bytearray_type(buffer, length, length)
end end
local function copy(self, srcindex, dst, dstindex, size)
if size <= 0 then error("size of byte range must be positive non-zero integer") end
if srcindex < 1 or srcindex + size - 1 > self.size then
error("specified source byte range is out of source array bounds")
end
if dstindex < 1 or dstindex + size - 1 > dst.size then
error("specified destination byte range is out of destination array bounds")
end
FFI.copy(dst.bytes + dstindex - 1, self.bytes + srcindex - 1, size)
end
local function move(self, fromindex, toindex, size)
if size <= 0 then error("size of byte range must be positive non-zero integer") end
if fromindex < 1 or fromindex + size - 1 > self.size then
error("specified source byte range is out of array bounds")
end
if toindex < 1 or toindex + size - 1 > self.size then
error("specified destination byte range is out of array bounds")
end
FFI.C.memmove(self.bytes + toindex - 1, self.bytes + fromindex - 1, size)
end
local bytearray_methods = { local bytearray_methods = {
append=append, append = append,
insert=insert, insert = insert,
remove=remove, remove = remove,
trim=trim_buffer, trim = trim_buffer,
clear=clear, clear = clear,
reserve=reserve, reserve = reserve,
get_capacity=get_capacity, get_capacity = get_capacity,
slice=slice, slice = slice,
fill = fill,
copy = copy,
move = move
} }
local bytearray_mt = { local bytearray_mt = {
@ -256,7 +303,8 @@ local function FFIBytearray_as_string(bytes)
end end
end end
local function create_FFIview_class(name, typename, typesize) local function create_FFIview_class(name, typename)
local typesize = FFI.sizeof(typename)
local ptrtype = typename .. "*" local ptrtype = typename .. "*"
local FFIview_mt = { local FFIview_mt = {
__index = function(self, key) __index = function(self, key)
@ -291,27 +339,44 @@ local function create_FFIview_class(name, typename, typesize)
return i, ptr[i - 1] return i, ptr[i - 1]
end end
end end
end end,
} }
return function (bytes) return setmetatable({}, {
local x = setmetatable({ __call = function (self, bytes)
bytes=bytes, local x = setmetatable({
}, FFIview_mt) bytes=bytes,
return x }, FFIview_mt)
end return x
end,
__index = {
typesize = function()
return typesize
end
}
})
end end
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2) local FFII8view = create_FFIview_class("FFII8view", "int8_t")
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2) local FFII16view = create_FFIview_class("FFII16view", "int16_t")
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4) local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t")
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) local FFII32view = create_FFIview_class("FFII32view", "int32_t")
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t")
local FFII64view = create_FFIview_class("FFII64view", "int64_t")
local FFIU64view = create_FFIview_class("FFIU64view", "uint64_t")
local FFIFLTview = create_FFIview_class("FFIFLTview", "float")
local FFIDBLview = create_FFIview_class("FFIDBLview", "double")
return { return {
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
FFIBytearray_as_string = FFIBytearray_as_string, FFIBytearray_as_string = FFIBytearray_as_string,
FFIBytearray_as_ptr = FFIBytearray_as_ptr, FFIBytearray_as_ptr = FFIBytearray_as_ptr,
FFII8view = FFII8view,
FFIU16view = FFIU16view, FFIU16view = FFIU16view,
FFII16view = FFII16view, FFII16view = FFII16view,
FFIU32view = FFIU32view, FFIU32view = FFIU32view,
FFII32view = FFII32view, FFII32view = FFII32view,
FFIU64view = FFIU64view,
FFII64view = FFII64view,
FFIFLTview = FFIFLTview,
FFIDBLview = FFIDBLview,
} }

View file

@ -0,0 +1,38 @@
local FFI = ffi
FFI.cdef[[
unsigned long long strtoull(const char* nptr, char** endptr, int base);
long long strtoll(const char* nptr, char** endptr, int base);
]]
local this = {}
local function create_integral_type(ctype_name, signed)
local parse_func = signed and FFI.C.strtoll or FFI.C.strtoull
return function(value, base)
value = value or 0
local typename = type(value)
if typename == "number" then
return FFI.cast(ctype_name, value)
elseif typename == "string" then
if type(base) ~= "number" then
base = 10
end
return parse_func(value, nil, base)
end
error(string.format("invalid value type: %s", typename))
end
end
this.uint8 = create_integral_type("uint8_t", false)
this.uint16 = create_integral_type("uint16_t", false)
this.uint32 = create_integral_type("uint32_t", false)
this.uint64 = create_integral_type("uint64_t", false)
this.int8 = create_integral_type("int8_t", true)
this.int16 = create_integral_type("int16_t", true)
this.int32 = create_integral_type("int32_t", true)
this.int64 = create_integral_type("int64_t", true)
return this

View file

@ -1,4 +1,4 @@
local rules = {nexid = 1, rules = {}} local rules = {nextid = 1, rules = {}}
function rules.get_rule(name) function rules.get_rule(name)
local rule = rules.rules[name] local rule = rules.rules[name]
@ -27,13 +27,13 @@ end
function rules.reset(name) function rules.reset(name)
local rule = rules.get_rule(name) local rule = rules.get_rule(name)
rules.set(rule.default) rules.set(name, rule.default)
end end
function rules.listen(name, handler) function rules.listen(name, handler)
local rule = rules.get_rule(name) local rule = rules.get_rule(name)
local id = rules.nexid local id = rules.nextid
rules.nextid = rules.nexid + 1 rules.nextid = rules.nextid + 1
rule.listeners[utf8.encode(id)] = handler rule.listeners[utf8.encode(id)] = handler
return id return id
end end

View file

@ -3,5 +3,6 @@ name = "default"
base_packs = ["base"] base_packs = ["base"]
permissions = [ permissions = [
"network", "network",
"record-audio" "record-audio",
"sub-instances",
] ]

View file

@ -140,7 +140,7 @@ console.add_command(
for y=y1,y2 do for y=y1,y2 do
for z=z1,z2 do for z=z1,z2 do
for x=x1,x2 do for x=x1,x2 do
block.set(x, y, z, id) block.set(x, y, z, id, 0, true)
end end
end end
end end

View file

@ -140,7 +140,20 @@ local __vc_named_coroutines = {}
local __vc_next_coroutine = 1 local __vc_next_coroutine = 1
function __vc_start_coroutine(chunk) function __vc_start_coroutine(chunk)
local co = coroutine.create(chunk) local co = coroutine.create(function()
local _, err = xpcall(chunk, function(msg)
local traceback = debug.get_traceback(0)
local s = string.format("%s:", msg)
for i=1,#traceback - 2 do
local frame = traceback[i]
s = s .. "\n\t"..tb_frame_tostring(frame)
end
return s
end)
if err then
error(err)
end
end)
local id = __vc_next_coroutine local id = __vc_next_coroutine
__vc_next_coroutine = __vc_next_coroutine + 1 __vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co __vc_coroutines[id] = co

View file

@ -10,8 +10,9 @@ vc = {
is_client = function() is_client = function()
return not _vc_headless return not _vc_headless
end, end,
get_version = __vc_app.get_version,
get_setting = __vc_app.get_setting, get_setting = __vc_app.get_setting,
get_setting_str = __vc_app.get_setting_str, str_setting = __vc_app.str_setting,
get_setting_info = __vc_app.get_setting_info, get_setting_info = __vc_app.get_setting_info,
} }
@ -310,12 +311,19 @@ local bytearray = require "core:internal/bytearray"
Bytearray = bytearray.FFIBytearray Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr
I8view = bytearray.FFII8view
U16view = bytearray.FFIU16view U16view = bytearray.FFIU16view
I16view = bytearray.FFII16view I16view = bytearray.FFII16view
U32view = bytearray.FFIU32view U32view = bytearray.FFIU32view
I32view = bytearray.FFII32view I32view = bytearray.FFII32view
U64view = bytearray.FFIU64view
I64view = bytearray.FFII64view
FLTview = bytearray.FFIFLTview
DBLview = bytearray.FFIDBLview
Bytearray_construct = function(...) return Bytearray(...) end Bytearray_construct = function(...) return Bytearray(...) end
ctypes = require "core:internal/ctypes"
bit.compile = require "core:bitwise/compiler" bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor" bit.execute = require "core:bitwise/executor"

View file

@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
inline const std::string ENGINE_VERSION_STRING = "0.32"; inline const std::string ENGINE_VERSION_STRING = "0.32";
inline constexpr uint MAX_SUBPROCESSES = 1;
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
/// @brief world regions format version /// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3; inline constexpr uint REGION_FORMAT_VERSION = 3;

View file

@ -39,6 +39,9 @@ std::unique_ptr<Content> ContentBuilder::build() {
} else { } else {
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid; def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
} }
if (def.material.empty()) {
defaults.at("block-material").get(def.material);
}
const float EPSILON = 0.01f; const float EPSILON = 0.01f;
def.rt.solid = def.rt.solid =

View file

@ -9,6 +9,10 @@
#include <stdexcept> #include <stdexcept>
#include <unordered_map> #include <unordered_map>
#ifdef VC_ENABLE_REFLECTION
#include "util/EnumMetadata.hpp"
#endif
namespace util { namespace util {
template<class T> class Buffer; template<class T> class Buffer;
} }
@ -70,6 +74,14 @@ namespace dv {
return ptr != nullptr; return ptr != nullptr;
} }
inline value* operator->() noexcept {
return ptr;
}
inline const value* operator->() const noexcept {
return ptr;
}
inline value& operator*() noexcept { inline value& operator*() noexcept {
return *ptr; return *ptr;
} }
@ -78,6 +90,11 @@ namespace dv {
return *ptr; return *ptr;
} }
#ifdef VC_ENABLE_REFLECTION
template <typename T>
bool get(T& dst, const util::EnumMetadata<T>& mt) const;
#endif
bool get(std::string& dst) const; bool get(std::string& dst) const;
bool get(bool& dst) const; bool get(bool& dst) const;
bool get(char& dst) const; bool get(char& dst) const;
@ -552,6 +569,17 @@ namespace dv {
} }
return false; return false;
} }
#ifdef VC_ENABLE_REFLECTION
template <typename T>
inline bool optionalvalue::get(T& dst, const util::EnumMetadata<T>& mt) const {
if (ptr) {
return mt.getItem(ptr->asString(), dst);
}
return false;
}
#endif
inline bool optionalvalue::get(std::string& dst) const { inline bool optionalvalue::get(std::string& dst) const {
if (ptr) { if (ptr) {
dst = ptr->asString(); dst = ptr->asString();

View file

@ -8,11 +8,7 @@
static debug::Logger logger("app-scripts"); static debug::Logger logger("app-scripts");
AppScriptsControl::AppScriptsControl( AppScriptsControl::AppScriptsControl(const CoreParameters& params) {
const CoreParameters& params, const Project& project
)
: project(project) {
io::path scriptFile = io::path scriptFile =
params.scriptFile.empty() params.scriptFile.empty()
? "project:start.lua" ? "project:start.lua"

View file

@ -14,7 +14,7 @@ struct Project;
class AppScriptsControl { class AppScriptsControl {
public: public:
AppScriptsControl(const CoreParameters& params, const Project& project); AppScriptsControl(const CoreParameters& params);
void tick(); void tick();
void loadProjectClientScript(); void loadProjectClientScript();
@ -24,7 +24,6 @@ public:
bool isFinished() const; bool isFinished() const;
private: private:
const Project& project;
std::unique_ptr<scripting::IClientProjectScript> clientScript; std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> scriptCoroutine; std::unique_ptr<Process> scriptCoroutine;
}; };

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "interfaces/Serializable.hpp" #include "interfaces/Serializable.hpp"
#include "io/path.hpp"
#include <set> #include <set>
#include <string> #include <string>
@ -12,6 +13,7 @@ struct Permissions {
static inline std::string NETWORK = "network"; static inline std::string NETWORK = "network";
static inline std::string RECORD_AUDIO = "record-audio"; static inline std::string RECORD_AUDIO = "record-audio";
static inline std::string WRITE_TO_USER = "write-to-user"; static inline std::string WRITE_TO_USER = "write-to-user";
static inline std::string SUB_INSTANCES = "sub-instances";
std::set<std::string> permissions; std::set<std::string> permissions;
@ -21,6 +23,7 @@ struct Permissions {
struct Project : Serializable { struct Project : Serializable {
std::string name; std::string name;
std::string title; std::string title;
io::path path;
std::vector<std::string> basePacks; std::vector<std::string> basePacks;
Permissions permissions; Permissions permissions;

View file

@ -12,7 +12,9 @@ struct CoreParameters {
std::filesystem::path userFolder = "."; std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile; std::filesystem::path scriptFile;
std::filesystem::path projectFolder; std::filesystem::path projectFolder;
std::filesystem::path logFile;
std::string debugServerString; std::string debugServerString;
int tps = 20; int tps = 20;
int subProcessDepth = 0;
std::unordered_map<std::string, std::string> projectArgs; std::unordered_map<std::string, std::string> projectArgs;
}; };

View file

@ -192,7 +192,7 @@ void Engine::initialize(CoreParameters coreParameters) {
audio::set_input_device(name == "auto" ? "" : name); audio::set_input_device(name == "auto" ? "" : name);
})); }));
appScripts = std::make_unique<AppScriptsControl>(params, *project); appScripts = std::make_unique<AppScriptsControl>(params);
if (params.stdinCommands) { if (params.stdinCommands) {
cmd::start_stdin_cmd_reader(*this); cmd::start_stdin_cmd_reader(*this);
@ -380,6 +380,7 @@ void Engine::loadAssets() {
void Engine::loadProject() { void Engine::loadProject() {
io::path projectFile = "project:project.toml"; io::path projectFile = "project:project.toml";
project = std::make_unique<Project>(); project = std::make_unique<Project>();
project->path = "project:";
project->deserialize(io::read_object(projectFile)); project->deserialize(io::read_object(projectFile));
logger.info() << "loaded project " << util::quote(project->name); logger.info() << "loaded project " << util::quote(project->name);
} }
@ -412,6 +413,7 @@ void Engine::onWorldClosed() {
} }
void Engine::quit() { void Engine::quit() {
logger.info() << "quitSignal set to true";
quitSignal = true; quitSignal = true;
if (!isHeadless()) { if (!isHeadless()) {
window->setShouldClose(true); window->setShouldClose(true);

View file

@ -43,7 +43,7 @@ void Mainloop::run() {
auto& appScripts = engine.getAppScripts(); auto& appScripts = engine.getAppScripts();
logger.info() << "main loop started"; logger.info() << "main loop started";
while (!window.isShouldClose()) { while (!window.isShouldClose() && !engine.isQuitSignal()) {
time.update(window.time()); time.update(window.time());
engine.applicationTick(); engine.applicationTick();
engine.updateFrontend(); engine.updateFrontend();

View file

@ -4,7 +4,7 @@
#include <GL/glew.h> #include <GL/glew.h>
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat) Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
: Texture(0, width, height) : Texture(0U, width, height, imageFormat)
{ {
glGenTextures(1, &id); glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_CUBE_MAP, id); glBindTexture(GL_TEXTURE_CUBE_MAP, id);
@ -14,7 +14,6 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
uint format = gl::to_glenum(imageFormat);
for (uint face = 0; face < 6; face++) { for (uint face = 0; face < 6; face++) {
glTexImage2D( glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
@ -37,3 +36,23 @@ void Cubemap::bind() const {
void Cubemap::unbind() const { void Cubemap::unbind() const {
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
} }
void Cubemap::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
0,
format,
width,
height,
0,
format,
GL_UNSIGNED_BYTE,
data
);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}

View file

@ -9,4 +9,5 @@ public:
virtual void bind() const override; virtual void bind() const override;
virtual void unbind() const override; virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h) override;
}; };

View file

@ -74,6 +74,18 @@ DrawContext::~DrawContext() {
if (lineWidth != parent->lineWidth) { if (lineWidth != parent->lineWidth) {
glLineWidth(parent->lineWidth); glLineWidth(parent->lineWidth);
} }
for (int i = 0; i < textures.size(); i++) {
auto texture = parent->textures[i];
if (textures[i] != texture) {
glActiveTexture(GL_TEXTURE0 + i);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
}
}
} }
const glm::uvec2& DrawContext::getViewport() const { const glm::uvec2& DrawContext::getViewport() const {
@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) {
lineWidth = width; lineWidth = width;
glLineWidth(width); glLineWidth(width);
} }
void DrawContext::useTexture(int target, const Bindable* texture) {
glActiveTexture(GL_TEXTURE0 + target);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
textures.at(target) = texture;
}

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array>
#include <glm/vec2.hpp> #include <glm/vec2.hpp>
#include <glm/vec4.hpp> #include <glm/vec4.hpp>
@ -23,6 +24,7 @@ class DrawContext {
BlendMode blendMode = BlendMode::normal; BlendMode blendMode = BlendMode::normal;
int scissorsCount = 0; int scissorsCount = 0;
float lineWidth = 1.0f; float lineWidth = 1.0f;
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
public: public:
DrawContext( DrawContext(
const DrawContext* parent, const DrawContext* parent,
@ -44,4 +46,5 @@ public:
void setBlendMode(BlendMode mode); void setBlendMode(BlendMode mode);
void setScissors(const glm::vec4& area); void setScissors(const glm::vec4& area);
void setLineWidth(float width); void setLineWidth(float width);
void useTexture(int target, const Bindable* texture);
}; };

View file

@ -1,14 +1,14 @@
#include "Framebuffer.hpp" #include "Framebuffer.hpp"
#include <GL/glew.h> #include <GL/glew.h>
#include "Texture.hpp" #include "Texture.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
static debug::Logger logger("gl-framebuffer"); static debug::Logger logger("gl-framebuffer");
Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture) Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
: fbo(fbo), depth(depth), texture(std::move(texture)) : fbo(fbo), depth(depth), texture(std::move(texture)) {
{
if (this->texture) { if (this->texture) {
width = this->texture->getWidth(); width = this->texture->getWidth();
height = this->texture->getHeight(); height = this->texture->getHeight();
@ -18,35 +18,55 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
} }
} }
static std::unique_ptr<Texture> create_texture(int width, int height, int format) { static std::unique_ptr<Texture> create_texture(
GLuint tex; int width, int height, bool alpha
glGenTextures(1, &tex); ) {
glBindTexture(GL_TEXTURE_2D, tex); GLenum glformat = alpha ? GL_RGBA : GL_RGB;
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
GLuint textureid;
glGenTextures(1, &textureid);
glBindTexture(GL_TEXTURE_2D, textureid);
glTexImage2D(
GL_TEXTURE_2D,
0,
glformat,
width,
height,
0,
glformat,
GL_UNSIGNED_BYTE,
nullptr
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); glFramebufferTexture2D(
return std::make_unique<Texture>(tex, width, height); GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid, 0
);
return std::make_unique<Texture>(
textureid,
width,
height,
alpha ? ImageFormat::RGBA8888 : ImageFormat::RGB888
);
} }
Framebuffer::Framebuffer(uint width, uint height, bool alpha) Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height) : width(width), height(height) {
{
glGenFramebuffers(1, &fbo); glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
format = alpha ? GL_RGBA : GL_RGB;
// Setup color attachment (texture) // Setup color attachment (texture)
texture = create_texture(width, height, format); texture = create_texture(width, height, alpha);
// Setup depth attachment // Setup depth attachment
glGenRenderbuffers(1, &depth); glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth); glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth
);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
logger.error() << "framebuffer is not complete!"; logger.error() << "framebuffer is not complete!";
@ -60,11 +80,15 @@ Framebuffer::~Framebuffer() {
glDeleteRenderbuffers(1, &depth); glDeleteRenderbuffers(1, &depth);
} }
void Framebuffer::bind() { void Framebuffer::setTexture(std::unique_ptr<Texture> texture) {
this->texture = std::move(texture);
}
void Framebuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
} }
void Framebuffer::unbind() { void Framebuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0);
texture = create_texture(width, height, format); texture->resize(width, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const {
} }
uint Framebuffer::getFBO() const { uint Framebuffer::getFBO() const {
return fbo; return fbo;
} }

View file

@ -12,18 +12,19 @@ class Framebuffer : public Bindable {
uint depth; uint depth;
uint width; uint width;
uint height; uint height;
uint format;
std::shared_ptr<Texture> texture; std::shared_ptr<Texture> texture;
public: public:
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture); Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
Framebuffer(uint width, uint height, bool alpha=false); Framebuffer(uint width, uint height, bool alpha=false);
~Framebuffer(); ~Framebuffer();
void setTexture(std::unique_ptr<Texture> texture);
/// @brief Use framebuffer /// @brief Use framebuffer
void bind() override; void bind() const override;
/// @brief Stop using framebuffer /// @brief Stop using framebuffer
void unbind() override; void unbind() const override;
/// @brief Update framebuffer texture size /// @brief Update framebuffer texture size
/// @param width new width /// @param width new width

View file

@ -189,7 +189,7 @@ GBuffer::~GBuffer() {
glDeleteFramebuffers(1, &ssaoFbo); glDeleteFramebuffers(1, &ssaoFbo);
} }
void GBuffer::bind() { void GBuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
} }
@ -198,7 +198,7 @@ void GBuffer::bindSSAO() const {
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo); glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo);
} }
void GBuffer::unbind() { void GBuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }

View file

@ -9,9 +9,9 @@ public:
GBuffer(uint width, uint height); GBuffer(uint width, uint height);
~GBuffer() override; ~GBuffer() override;
void bind() override; void bind() const override;
void bindSSAO() const; void bindSSAO() const;
void unbind() override; void unbind() const override;
void bindBuffers() const; void bindBuffers() const;
void bindSSAOBuffer() const; void bindSSAOBuffer() const;

View file

@ -7,8 +7,8 @@
uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it
Texture::Texture(uint id, uint width, uint height) Texture::Texture(uint id, uint width, uint height, ImageFormat imageFormat)
: id(id), width(width), height(height) { : id(id), width(width), height(height), format(gl::to_glenum(imageFormat)) {
} }
Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat) Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat)
@ -17,7 +17,7 @@ Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFo
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = gl::to_glenum(imageFormat); format = gl::to_glenum(imageFormat);
glTexImage2D( glTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0, GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data) format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
@ -41,23 +41,27 @@ void Texture::unbind() const {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
void Texture::resize(uint width, uint height) {
reload(nullptr, width, height);
}
void Texture::reload(const ImageData& image) { void Texture::reload(const ImageData& image) {
width = image.getWidth();
height = image.getHeight();
reload(image.getData(), width, height); reload(image.getData(), width, height);
} }
void Texture::reload(const ubyte* data, uint width, uint height) { void Texture::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)); format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, GL_UNSIGNED_BYTE, image.getData());
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }

View file

@ -1,29 +1,32 @@
#pragma once #pragma once
#include "typedefs.hpp"
#include "maths/UVRegion.hpp"
#include "ImageData.hpp"
#include <memory> #include <memory>
class Texture { #include "ImageData.hpp"
#include "commons.hpp"
#include "maths/UVRegion.hpp"
#include "typedefs.hpp"
class Texture : public Bindable {
protected: protected:
uint id; uint id;
uint width; uint width;
uint height; uint height;
uint format;
public: public:
Texture(uint id, uint width, uint height); Texture(uint id, uint width, uint height, ImageFormat imageFormat);
Texture(const ubyte* data, uint width, uint height, ImageFormat format); Texture(const ubyte* data, uint width, uint height, ImageFormat format);
virtual ~Texture(); virtual ~Texture();
virtual void bind() const; virtual void bind() const override;
virtual void unbind() const; virtual void unbind() const override;
void reload(const ubyte* data, uint w, uint h); virtual void reload(const ubyte* data, uint w, uint h);
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h); void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
void setNearestFilter(); void setNearestFilter();
void reload(const ImageData& image); void reload(const ImageData& image);
virtual void resize(uint w, uint h);
void setMipMapping(bool flag, bool pixelated); void setMipMapping(bool flag, bool pixelated);

View file

@ -59,6 +59,8 @@ namespace advanced_pipeline {
inline constexpr int TARGET_SSAO = 5; inline constexpr int TARGET_SSAO = 5;
inline constexpr int TARGET_SHADOWS0 = 6; inline constexpr int TARGET_SHADOWS0 = 6;
inline constexpr int TARGET_SHADOWS1 = 7; inline constexpr int TARGET_SHADOWS1 = 7;
inline constexpr int TARGETS_COUNT = 8;
} }
VC_ENUM_METADATA(CursorShape) VC_ENUM_METADATA(CursorShape)
@ -85,6 +87,6 @@ class Bindable {
public: public:
virtual ~Bindable() = default; virtual ~Bindable() = default;
virtual void bind() = 0; virtual void bind() const = 0;
virtual void unbind() = 0; virtual void unbind() const = 0;
}; };

View file

@ -34,4 +34,7 @@ void HandsRenderer::render(const Camera& camera) {
glm::vec3(), glm::vec3(),
glm::vec3(1.0f) glm::vec3(1.0f)
); );
modelBatch.render();
modelBatch.setLightsOffset(glm::vec3());
} }

View file

@ -1,91 +1,64 @@
#include "Skybox.hpp" #include "Skybox.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Shader.hpp" #include <GL/glew.h>
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/DrawContext.hpp"
#include "window/Window.hpp"
#include "window/Camera.hpp"
#include "maths/UVRegion.hpp"
#include <cmath> #include <cmath>
#include <iostream>
#include <GL/glew.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp> #include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <limits>
#include "assets/Assets.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "maths/UVRegion.hpp"
#include "window/Camera.hpp"
#include "window/Window.hpp"
using namespace advanced_pipeline; using namespace advanced_pipeline;
const int STARS_COUNT = 3000; const int STARS_COUNT = 3000;
const int STARS_SEED = 632; const int STARS_SEED = 632;
Skybox::Skybox(uint size, Shader& shader) Skybox::Skybox(uint size, const Assets& assets)
: size(size), : size(size),
shader(shader), assets(assets),
batch3d(std::make_unique<Batch3D>(4096)) shader(assets.require<Shader>("skybox_gen")),
{ batch3d(std::make_unique<Batch3D>(4096)) {
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
uint fboid; SkyboxVertex vertices[] {
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(fboid, 0, std::move(cubemap));
SkyboxVertex vertices[]{
{{-1.0f, -1.0f}}, {{-1.0f, -1.0f}},
{{-1.0f, 1.0f}}, {{-1.0f, 1.0f}},
{{1.0f, 1.0f}}, {{1.0f, 1.0f}},
{{-1.0f, -1.0f}}, {{-1.0f, -1.0f}},
{{1.0f, 1.0f}}, {{1.0f, 1.0f}},
{{1.0f, -1.0f}} {{1.0f, -1.0f}}};
};
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6); mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
setMode(mode);
sprites.push_back(SkySprite {
"misc/moon",
glm::pi<float>() * 0.5f,
4.0f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/moon_flare",
glm::pi<float>() * 0.5f,
0.5f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/sun",
glm::pi<float>() * 1.5f,
4.0f,
true,
glm::pi<float>() * 0.25f,
});
} }
Skybox::~Skybox() = default; Skybox::~Skybox() = default;
void Skybox::drawBackground( void Skybox::drawBackground(const Camera& camera, int width, int height) {
const Camera& camera, const Assets& assets, int width, int height
) {
auto backShader = assets.get<Shader>("background"); auto backShader = assets.get<Shader>("background");
backShader->use(); backShader->use();
backShader->uniformMatrix("u_view", camera.getView(false)); backShader->uniformMatrix("u_view", camera.getView(false));
backShader->uniform1f( backShader->uniform1f(
"u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>() "u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>()
); );
backShader->uniform1f("u_ar", float(width)/float(height)); backShader->uniform1f("u_ar", float(width) / float(height));
backShader->uniform1i("u_skybox", 1); backShader->uniform1i("u_skybox", 1);
bind();
auto texture = fbo->getTexture();
texture->bind();
mesh->draw(); mesh->draw();
unbind(); texture->unbind();
} }
void Skybox::drawStars(float angle, float opacity) { void Skybox::drawStars(float angle, float opacity) {
@ -93,9 +66,7 @@ void Skybox::drawStars(float angle, float opacity) {
random.setSeed(STARS_SEED); random.setSeed(STARS_SEED);
glm::mat4 rotation = glm::rotate( glm::mat4 rotation = glm::rotate(
glm::mat4(1.0f), glm::mat4(1.0f), -angle + glm::pi<float>() * 0.5f, glm::vec3(0, 0, -1)
-angle + glm::pi<float>() * 0.5f,
glm::vec3(0, 0, -1)
); );
rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0)); rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0));
@ -108,41 +79,21 @@ void Skybox::drawStars(float angle, float opacity) {
glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation; glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation;
float sopacity = random.randFloat(); float sopacity = random.randFloat();
if (pos.y < 0.0f) if (pos.y < 0.0f) continue;
continue;
sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f; sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f;
glm::vec4 tint (1,1,1, sopacity * opacity); glm::vec4 tint(1, 1, 1, sopacity * opacity);
batch3d->point(pos * depth, tint); batch3d->point(pos * depth, tint);
} }
batch3d->flushPoints(); batch3d->flushPoints();
} }
void Skybox::draw( void Skybox::drawSkySprites(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
float daytime, float daytime,
float fog) float angle,
{ float opacity,
const glm::uvec2& viewport = pctx.getViewport(); const std::vector<SkySprite>& sprites
) {
glActiveTexture(GL_TEXTURE0);
drawBackground(camera, assets, viewport.x, viewport.y);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
float depthScale = 2e3; float depthScale = 2e3;
for (auto& sprite : sprites) { for (auto& sprite : sprites) {
batch3d->texture(assets.get<Texture>(sprite.texture)); batch3d->texture(assets.get<Texture>(sprite.texture));
@ -159,39 +110,124 @@ void Skybox::draw(
glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation; glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation;
glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation; glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation;
glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation; glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation;
glm::vec4 tint (1,1,1, opacity); glm::vec4 tint(1, 1, 1, opacity);
if (!sprite.emissive) { if (!sprite.emissive) {
tint *= 0.6f + std::cos(angle)*0.4; tint *= 0.6f + std::cos(angle) * 0.4;
} }
batch3d->sprite(pos, right, batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint);
up, 1, 1, UVRegion(), tint);
} }
batch3d->flush(); batch3d->flush();
drawStars(angle, opacity);
} }
void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) { void Skybox::draw(
const Environment& environment,
const DrawContext& pctx,
const Camera& camera,
float daytime,
float fog
) {
const glm::uvec2& viewport = pctx.getViewport();
drawBackground(camera, viewport.x, viewport.y);
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
if (environment.sky.sprites.empty() && !environment.sky.stars) {
return;
}
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
if (!environment.sky.sprites.empty()) {
drawSkySprites(daytime, angle, opacity, environment.sky.sprites);
}
if (environment.sky.stars) {
drawStars(angle, opacity);
}
}
void Skybox::setMode(SkyMode mode) {
if (this->mode == mode && fbo) {
return;
}
this->mode = mode;
uint size = mode == SkyMode::BOX ? this->size : 1U;
prevT = std::numeric_limits<float>().min();
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(
fboid,
false,
std::make_unique<Cubemap>(size, size, ImageFormat::RGB888)
);
}
void Skybox::refresh(
const Environment& environment,
const DrawContext& pctx,
float dayTime,
float mie,
const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
) {
setMode(environment.sky.mode);
if (mode == SkyMode::NONE) {
return;
} else if (mode == SkyMode::SOLID) {
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get());
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr);
for (int face = 0; face < 6; face++) {
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
0
);
glClearColor(tint.r, tint.g, tint.b, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
return;
}
frameid++; frameid++;
float dayTime = t;
DrawContext ctx = pctx.sub(); DrawContext ctx = pctx.sub();
ctx.setDepthMask(false); ctx.setDepthMask(false);
ctx.setDepthTest(false); ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get()); ctx.setFramebuffer(fbo.get());
ctx.setViewport({size, size}); ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture()); auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr); assert(cubemap != nullptr);
ready = true; float t = dayTime * glm::two_pi<float>();
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
cubemap->bind();
shader.use();
t *= glm::two_pi<float>();
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
float sunAngle = glm::radians((t / glm::two_pi<float>() - 0.25f) * 360.0f); float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) * glm::radians(sunAltitude); float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) *
glm::radians(sunAltitude);
float y = sunAngle - glm::pi<float>() * 0.5f; float y = sunAngle - glm::pi<float>() * 0.5f;
float z = glm::radians(0.0f); float z = glm::radians(0.0f);
rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0)); rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0));
@ -199,6 +235,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1)); rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1));
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1)); lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
shader.use();
shader.uniform1i("u_quality", quality); shader.uniform1i("u_quality", quality);
shader.uniform1f("u_mie", mie); shader.uniform1f("u_mie", mie);
shader.uniform3f("u_tint", tint); shader.uniform3f("u_tint", tint);
@ -207,23 +244,22 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
shader.uniform3f("u_lightDir", lightDir); shader.uniform3f("u_lightDir", lightDir);
shader.uniform1f("u_dayTime", dayTime); shader.uniform1f("u_dayTime", dayTime);
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) { if (glm::abs(mie - prevMie) + glm::abs(t - prevT) +
glm::abs(prevHighlight.r - hightlight.r) >=
0.01) {
for (uint face = 0; face < 6; face++) { for (uint face = 0; face < 6; face++) {
refreshFace(face, cubemap); refreshFace(face, *cubemap);
} }
} else { } else {
uint face = frameid % 6; uint face = frameid % 6;
refreshFace(face, cubemap); refreshFace(face, *cubemap);
} }
prevMie = mie; prevMie = mie;
prevT = t; prevT = t;
prevHighlight = hightlight; prevHighlight = hightlight;
cubemap->unbind();
glActiveTexture(GL_TEXTURE0);
} }
void Skybox::refreshFace(uint face, Cubemap* cubemap) { void Skybox::refreshFace(uint face, Cubemap& cubemap) {
const glm::vec3 xaxs[] = { const glm::vec3 xaxs[] = {
{0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f},
@ -256,7 +292,7 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
GL_FRAMEBUFFER, GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(), cubemap.getId(),
0 0
); );
shader.uniform3f("u_xaxis", xaxs[face]); shader.uniform3f("u_xaxis", xaxs[face]);
@ -265,14 +301,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
mesh->draw(); mesh->draw();
} }
void Skybox::bind() const { const Cubemap* Skybox::getCubemap() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); return dynamic_cast<const Cubemap*>(fbo->getTexture());
fbo->getTexture()->bind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::unbind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->unbind();
glActiveTexture(GL_TEXTURE0);
} }

View file

@ -1,14 +1,17 @@
#pragma once #pragma once
#include <glm/glm.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <glm/glm.hpp>
#include "typedefs.hpp"
#include "maths/fastmaths.hpp"
#include "graphics/core/MeshData.hpp"
template<typename VertexStructure> class Mesh; #include "graphics/core/MeshData.hpp"
#include "maths/fastmaths.hpp"
#include "typedefs.hpp"
#include "world/Environment.hpp"
template <typename VertexStructure>
class Mesh;
class Shader; class Shader;
class Assets; class Assets;
class Camera; class Camera;
@ -21,29 +24,20 @@ struct SkyboxVertex {
glm::vec2 position; glm::vec2 position;
static constexpr VertexAttribute ATTRIBUTES[] { static constexpr VertexAttribute ATTRIBUTES[] {
{VertexAttribute::Type::FLOAT, false, 2}, {VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}};
{{}, 0}};
};
struct SkySprite {
std::string texture;
float phase;
float distance;
bool emissive;
float altitude;
}; };
class Skybox { class Skybox {
std::unique_ptr<Framebuffer> fbo; SkyMode mode = SkyMode::SOLID;
uint size; uint size;
std::unique_ptr<Framebuffer> fbo;
const Assets& assets;
Shader& shader; Shader& shader;
bool ready = false;
FastRandom random; FastRandom random;
glm::vec3 lightDir; glm::vec3 lightDir;
std::unique_ptr<Mesh<SkyboxVertex>> mesh; std::unique_ptr<Mesh<SkyboxVertex>> mesh;
std::unique_ptr<Batch3D> batch3d; std::unique_ptr<Batch3D> batch3d;
std::vector<SkySprite> sprites;
int frameid = 0; int frameid = 0;
float prevMie = -1.0f; float prevMie = -1.0f;
@ -53,30 +47,41 @@ class Skybox {
glm::mat4 rotation; glm::mat4 rotation;
void drawStars(float angle, float opacity); void drawStars(float angle, float opacity);
void drawBackground( void drawBackground(const Camera& camera, int width, int height);
const Camera& camera, const Assets& assets, int width, int height void drawSkySprites(
float daytime,
float angle,
float opacity,
const std::vector<SkySprite>& sprites
); );
void refreshFace(uint face, Cubemap* cubemap); void refreshFace(uint face, Cubemap& cubemap);
public: public:
Skybox(uint size, Shader& shader); Skybox(uint size, const Assets& assets);
~Skybox(); ~Skybox();
void setMode(SkyMode mode);
void draw( void draw(
const DrawContext& pctx, const Environment& environment,
const Camera& camera, const DrawContext& pctx,
const Assets& assets, const Camera& camera,
float daytime, float daytime,
float fog float fog
); );
void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality); void refresh(
void bind() const; const Environment& environment,
void unbind() const; const DrawContext& pctx,
bool isReady() const { float t,
return ready; float mie,
} const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
);
const glm::vec3 getLightDir() const { const Cubemap* getCubemap() const;
const glm::vec3& getLightDir() const {
return lightDir; return lightDir;
} }
}; };

View file

@ -1,13 +1,40 @@
#include "WorldRenderer.hpp" #include "WorldRenderer.hpp"
#include "animation/rigging.hpp" #include "animation/rigging.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "CloudsRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "Emitter.hpp"
#include "HandsRenderer.hpp"
#include "ModelBatch.hpp"
#include "NamedSkeletons.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "Skybox.hpp"
#include "TextNote.hpp"
#include "TextsRenderer.hpp"
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "assets/assets_util.hpp" #include "assets/assets_util.hpp"
#include "coders/GLSLExtension.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "coders/GLSLExtension.hpp"
#include "frontend/LevelFrontend.hpp"
#include "frontend/ContentGfxCache.hpp" #include "frontend/ContentGfxCache.hpp"
#include "frontend/LevelFrontend.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/GBuffer.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Texture.hpp"
#include "items/Inventory.hpp" #include "items/Inventory.hpp"
#include "items/ItemDef.hpp" #include "items/ItemDef.hpp"
#include "items/ItemStack.hpp" #include "items/ItemStack.hpp"
@ -25,32 +52,6 @@
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/LevelEvents.hpp" #include "world/LevelEvents.hpp"
#include "world/World.hpp" #include "world/World.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/GBuffer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "HandsRenderer.hpp"
#include "NamedSkeletons.hpp"
#include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "ModelBatch.hpp"
#include "Skybox.hpp"
#include "Emitter.hpp"
#include "TextNote.hpp"
#include "CloudsRenderer.hpp"
#include <assert.h> #include <assert.h>
#include <algorithm> #include <algorithm>
@ -58,8 +59,6 @@
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <memory> #include <memory>
using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
@ -105,8 +104,7 @@ WorldRenderer::WorldRenderer(
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); } [this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
); );
skybox = std::make_unique<Skybox>( skybox = std::make_unique<Skybox>(
settings.graphics.skyboxResolution.get(), settings.graphics.skyboxResolution.get(), assets
assets.require<Shader>("skybox_gen")
); );
const auto& content = level.content; const auto& content = level.content;
@ -160,7 +158,7 @@ void WorldRenderer::setupWorldShader(
shader.uniform1i("u_debugNormals", false); 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.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform1i("u_skybox", TARGET_SKYBOX); shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
auto indices = level.content.getIndices(); auto indices = level.content.getIndices();
// Light emission when an emissive item is chosen // Light emission when an emissive item is chosen
@ -179,6 +177,11 @@ void WorldRenderer::setupWorldShader(
} }
} }
float WorldRenderer::calcFogFactor() const {
const auto& settings = engine.getSettings();
return 15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
}
void WorldRenderer::renderOpaque( void WorldRenderer::renderOpaque(
const DrawContext& ctx, const DrawContext& ctx,
const Camera& camera, const Camera& camera,
@ -187,14 +190,12 @@ void WorldRenderer::renderOpaque(
) { ) {
texts->render(ctx, camera, settings, hudVisible, false); texts->render(ctx, camera, settings, hudVisible, false);
bool culling = engine.getSettings().graphics.frustumCulling.get(); float fogFactor = calcFogFactor();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
auto& entityShader = assets.require<Shader>("entity"); auto& entityShader = assets.require<Shader>("entity");
setupWorldShader(entityShader, camera, settings, fogFactor); setupWorldShader(entityShader, camera, settings, fogFactor);
skybox->bind();
bool culling = engine.getSettings().graphics.frustumCulling.get();
if (culling) { if (culling) {
frustumCulling->update(camera.getProjView()); frustumCulling->update(camera.getProjView());
} }
@ -213,25 +214,26 @@ void WorldRenderer::renderOpaque(
auto& shader = assets.require<Shader>("main"); auto& shader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds"); auto& cloudsShader = assets.require<Shader>("clouds");
auto& linesShader = assets.require<Shader>("lines");
setupWorldShader(shader, camera, settings, fogFactor); setupWorldShader(shader, camera, settings, fogFactor);
chunksRenderer->drawChunks(camera, shader); chunksRenderer->drawChunks(camera, shader);
blockWraps->draw(ctx); blockWraps->draw(ctx);
int cloudsQuality = settings.graphics.cloudsQuality.get(); if (level.environment.sky.clouds) {
if (cloudsQuality > 0) { int cloudsQuality = settings.graphics.cloudsQuality.get();
setupWorldShader(cloudsShader, camera, settings, fogFactor); if (cloudsQuality > 0) {
cloudsRenderer->draw( setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality cloudsRenderer->draw(
); cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
}
} }
if (hudVisible) { if (hudVisible) {
auto& linesShader = assets.require<Shader>("lines");
renderLines(camera, linesShader, ctx); renderLines(camera, linesShader, ctx);
} }
skybox->unbind();
} }
void WorldRenderer::renderBlockSelection() { void WorldRenderer::renderBlockSelection() {
@ -286,21 +288,19 @@ void WorldRenderer::refreshSettings(Shader** shaders) {
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline; int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality); shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings { CompileTimeShaderSettings currentSettings {
gbufferPipeline, gbufferPipeline,
shadowsQuality != 0, shadowsQuality != 0,
graphics.ssao.get() && gbufferPipeline graphics.ssao.get() && gbufferPipeline};
}; if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
if (
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows || prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao prevCTShaderSettings.ssao != currentSettings.ssao) {
) {
std::vector<std::string> defines; std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS"); if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO"); if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER"); if (currentSettings.advancedRender)
defines.emplace_back("ADVANCED_RENDER");
for (size_t i = 0; shaders[i]; i++) { for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines); shaders[i]->recompile(defines);
@ -332,7 +332,8 @@ void WorldRenderer::renderFrame(
auto& entityShader = assets.require<Shader>("entity"); auto& entityShader = assets.require<Shader>("entity");
auto& cloudsShader = assets.require<Shader>("clouds"); auto& cloudsShader = assets.require<Shader>("clouds");
auto& translucentShader = assets.require<Shader>("translucent"); auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader(); auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
@ -342,19 +343,19 @@ void WorldRenderer::renderFrame(
&cloudsShader, &cloudsShader,
&translucentShader, &translucentShader,
&deferredShader, &deferredShader,
nullptr nullptr};
};
refreshSettings(affectedShaders); refreshSettings(affectedShaders);
const auto& worldInfo = world->getInfo(); const auto& worldInfo = world->getInfo();
float clouds = weather.clouds(); float clouds = weather.clouds();
clouds = glm::max(worldInfo.fog, clouds); clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f; float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
float random = rand() / static_cast<float>(RAND_MAX); float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh( skybox->refresh(
level.environment,
pctx, pctx,
worldInfo.daytime, worldInfo.daytime,
mie, mie,
@ -373,20 +374,19 @@ void WorldRenderer::renderFrame(
{ {
DrawContext wctx = pctx.sub(); DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline); postProcessing.use(wctx, gbufferPipeline);
display::clearDepth(); display::clearDepth();
/* Main opaque pass (GBuffer pass) */ { /* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub(); DrawContext ctx = wctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.setCullFace(true); ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible); renderOpaque(ctx, camera, settings, hudVisible);
} }
texts->render(pctx, camera, settings, hudVisible, true); texts->render(pctx, camera, settings, hudVisible, true);
} }
skybox->bind(); float fogFactor = calcFogFactor();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
if (gbufferPipeline) { if (gbufferPipeline) {
deferredShader.use(); deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor); setupWorldShader(deferredShader, camera, settings, fogFactor);
@ -395,6 +395,7 @@ void WorldRenderer::renderFrame(
{ {
DrawContext ctx = pctx.sub(); DrawContext ctx = pctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
if (gbufferPipeline) { if (gbufferPipeline) {
postProcessing.bindDepthBuffer(); postProcessing.bindDepthBuffer();
@ -403,7 +404,9 @@ void WorldRenderer::renderFrame(
} }
// Background sky plane // Background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, clouds
);
auto& linesShader = assets.require<Shader>("lines"); auto& linesShader = assets.require<Shader>("lines");
linesShader.use(); linesShader.use();
@ -415,10 +418,12 @@ void WorldRenderer::renderFrame(
linesShader.uniformMatrix("u_projview", projView); linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush(); lineBatch->flush();
skybox->bind(); DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks // Translucent blocks
{ {
auto sctx = ctx.sub(); auto sctx = wctx.sub();
sctx.setCullFace(true); sctx.setCullFace(true);
translucentShader.use(); translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor); setupWorldShader(translucentShader, camera, settings, fogFactor);
@ -429,44 +434,41 @@ void WorldRenderer::renderFrame(
entityShader.use(); entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor); setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> weatherInstances {&weather.a, &weather.b}; std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) { for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity; float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity; float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity; float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero; float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque); entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t); entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) { if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather); precipitation->render(camera, *weather);
} }
} }
skybox->unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
postProcessing.render(pctx, assets, timer, camera); postProcessing.render(pctx, assets, timer, camera);
if (player.currentCamera == player.fpCamera) { if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub(); DrawContext ctx = pctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.setCullFace(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 // prepare modified HUD camera
Camera hudcam = camera; Camera hudcam = camera;
hudcam.far = 10.0f; hudcam.far = 10.0f;
hudcam.setFov(0.9f); hudcam.setFov(0.9f);
hudcam.position = {}; hudcam.position = {};
hands->render(camera);
display::clearDepth();
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
skybox->bind();
modelBatch->render();
modelBatch->setLightsOffset(glm::vec3());
skybox->unbind();
} }
renderBlockOverlay(pctx); renderBlockOverlay(pctx);
@ -486,13 +488,12 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
if (def.overlayTexture.empty()) { if (def.overlayTexture.empty()) {
return; return;
} }
auto textureRegion = util::get_texture_region( auto textureRegion =
assets, def.overlayTexture, "blocks:notfound" util::get_texture_region(assets, def.overlayTexture, "blocks:notfound");
);
DrawContext ctx = wctx.sub(); DrawContext ctx = wctx.sub();
ctx.setDepthTest(false); ctx.setDepthTest(false);
ctx.setCullFace(false); ctx.setCullFace(false);
auto& shader = assets.require<Shader>("ui3d"); auto& shader = assets.require<Shader>("ui3d");
shader.use(); shader.use();
batch3d->begin(); batch3d->begin();

View file

@ -99,6 +99,8 @@ class WorldRenderer {
); );
void refreshSettings(Shader** shaders); void refreshSettings(Shader** shaders);
float calcFogFactor() const;
public: public:
std::unique_ptr<ParticlesRenderer> particles; std::unique_ptr<ParticlesRenderer> particles;
std::unique_ptr<TextsRenderer> texts; std::unique_ptr<TextsRenderer> texts;

View file

@ -33,6 +33,8 @@ namespace io {
} }
} }
path(std::string_view str) : path(std::string(str)) {}
path(const char* str) : path(std::string(str)) {} path(const char* str) : path(std::string(str)) {}
bool operator==(const std::string& other) const { bool operator==(const std::string& other) const {

View file

@ -25,7 +25,7 @@ const uint MIN_SURROUNDING = 9;
ChunksController::ChunksController(Level& level) ChunksController::ChunksController(Level& level)
: level(level), : level(level),
generator(std::make_unique<WorldGenerator>( generator(std::make_unique<WorldGenerator>(
level.content.generators.require(level.getWorld()->getGenerator()), level.content.generators.require(level.environment.generator),
level.content, level.content,
level.getWorld()->getSeed() level.getWorld()->getSeed()
)) {} )) {}

View file

@ -179,7 +179,7 @@ void EngineController::onMissingContent(
if (engine.isHeadless()) { if (engine.isHeadless()) {
throw std::runtime_error( throw std::runtime_error(
"missing content: " + "missing content: " +
json::stringify(create_missing_content_report(report), true, " ") util::quote(json::stringify(create_missing_content_report(report), true, " "))
); );
} else { } else {
engine.setScreen(std::make_shared<MenuScreen>(engine)); engine.setScreen(std::make_shared<MenuScreen>(engine));
@ -268,7 +268,7 @@ inline uint64_t str2seed(const std::string& seedstr) {
void EngineController::createWorld( void EngineController::createWorld(
const std::string& name, const std::string& name,
const std::string& seedstr, const std::string& seedstr,
const std::string& generatorID const std::string& environment
) { ) {
uint64_t seed = str2seed(seedstr); uint64_t seed = str2seed(seedstr);
@ -279,17 +279,31 @@ void EngineController::createWorld(
paths.setCurrentWorldFolder(folder); paths.setCurrentWorldFolder(folder);
engine.getContentControl().loadContent(); engine.getContentControl().loadContent();
}); });
const auto& generators = engine.getContentControl().get()->generators;
// generator-based environments must die in 1.0
bool genBasedEnv = false;
if (auto genDef = generators.find(environment)) {
genBasedEnv = true;
}
auto& contentControl = engine.getContentControl(); auto& contentControl = engine.getContentControl();
auto level = World::create( auto level = World::create(
name, name,
generatorID, genBasedEnv ? "" : environment,
genBasedEnv ? environment : "",
folder, folder,
seed, seed,
engine.getSettings(), engine.getSettings(),
*contentControl.get(), *contentControl.get(),
contentControl.getContentPacks() contentControl.getContentPacks()
); );
// generator-based environment initialization
if (genBasedEnv) {
level->environment.generator = environment;
}
if (!engine.isHeadless()) { if (!engine.isHeadless()) {
level->players->create(localPlayer); level->players->create(localPlayer);
} }

View file

@ -149,7 +149,7 @@ void LevelController::saveWorld() {
world->wfile->createDirectories(); world->wfile->createDirectories();
scripting::on_world_save(); scripting::on_world_save();
level->onSave(); level->onSave();
level->getWorld()->write(level.get()); level->getWorld()->write(*level);
} }
void LevelController::onWorldQuit() { void LevelController::onWorldQuit() {

View file

@ -20,8 +20,14 @@
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include <array>
using namespace scripting; using namespace scripting;
namespace {
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
}
/// @brief Check if content is loaded /// @brief Check if content is loaded
static int l_is_content_loaded(lua::State* L) { static int l_is_content_loaded(lua::State* L) {
return lua::pushboolean(L, content != nullptr); return lua::pushboolean(L, content != nullptr);
@ -337,6 +343,10 @@ static int l_start_debug_instance(lua::State* L) {
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
throw std::runtime_error("project has no debugging permission"); throw std::runtime_error("project has no debugging permission");
} }
const auto& params = engine->getCoreParameters();
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
throw std::runtime_error("max subprocess depth exceeded");
}
int port = lua::tointeger(L, 1); int port = lua::tointeger(L, 1);
if (port == 0) { if (port == 0) {
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
"--res", paths.getResourcesFolder().u8string(), "--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(), "--dir", paths.getUserFilesFolder().u8string(),
"--dbg-server", "tcp:" + std::to_string(port), "--dbg-server", "tcp:" + std::to_string(port),
"--sub-depth", std::to_string(engine->getCoreParameters()
.subProcessDepth + 1),
}; };
if (!projectPath.empty()) { if (!projectPath.empty()) {
args.emplace_back("--project"); args.emplace_back("--project");
@ -365,11 +377,76 @@ static int l_start_debug_instance(lua::State* L) {
platform::new_engine_instance( platform::new_engine_instance(
std::move(args), std::move(args),
outputPath.empty() ? "" : io::resolve(std::string(outputPath)) outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
false
); );
return lua::pushinteger(L, port); return lua::pushinteger(L, port);
} }
static int l_start_background_instance(lua::State* L) {
if (!engine->getProject().permissions.has(Permissions::SUB_INSTANCES)) {
throw std::runtime_error("project has no sub-instances permission");
}
const auto& params = engine->getCoreParameters();
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
throw std::runtime_error("max subprocess depth exceeded");
}
auto scriptPath = lua::require_lstring(L, 1);
io::path outputPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : "";
const auto& paths = engine->getPaths();
std::vector<std::string> args {
"--headless",
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--script", io::resolve(scriptPath).u8string(),
"--sub-depth", std::to_string(engine->getCoreParameters()
.subProcessDepth + 1),
"--log", io::resolve(outputPath).u8string(),
};
args.emplace_back("--project");
args.emplace_back(io::resolve(engine->getProject().path).u8string());
int handle = -1;
for (int i = 0; i < ::processes.size(); i++) {
if (!::processes[i] || !::processes[i]->isActive()) {
handle = i;
break;
}
}
if (handle == -1) {
throw std::runtime_error("sub-processes limit exceeded");
}
::processes[handle] =
platform::new_engine_instance(std::move(args), "", true);
return lua::pushinteger(L, handle);
}
static int l_is_instance_alive(lua::State* L) {
int handle = lua::tointeger(L, 1);
if (handle < 0 || handle >= ::processes.size()) {
throw std::runtime_error("invalid process handle");
}
auto& process = ::processes[handle];
return lua::pushboolean(L, process && process->isActive());
}
static int l_terminate_instance(lua::State* L) {
int handle = lua::tointeger(L, 1);
if (handle < 0 || handle >= ::processes.size()) {
throw std::runtime_error("invalid process handle");
}
auto& process = ::processes[handle];
if (process == nullptr) {
return lua::pushboolean(L, false);
}
bool active = process->isActive();
process.reset();
return lua::pushboolean(L, active);
}
const luaL_Reg applib[] = { const luaL_Reg applib[] = {
/// content /// content
{"is_content_loaded", lua::wrap<l_is_content_loaded>}, {"is_content_loaded", lua::wrap<l_is_content_loaded>},
@ -402,5 +479,8 @@ const luaL_Reg applib[] = {
{"get_version", lua::wrap<l_get_version>}, {"get_version", lua::wrap<l_get_version>},
{"create_memory_device", lua::wrap<l_create_memory_device>}, {"create_memory_device", lua::wrap<l_create_memory_device>},
{"start_debug_instance", lua::wrap<l_start_debug_instance>}, {"start_debug_instance", lua::wrap<l_start_debug_instance>},
{"start_background_instance", lua::wrap<l_start_background_instance>},
{"is_instance_alive", lua::wrap<l_is_instance_alive>},
{"terminate_instance", lua::wrap<l_terminate_instance>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -125,7 +125,7 @@ static int l_is_night(lua::State* L) {
} }
static int l_get_generator(lua::State* L) { static int l_get_generator(lua::State* L) {
return lua::pushstring(L, require_world_info().generator); return lua::pushstring(L, require_level().environment.generator);
} }
static int l_get_chunk_data(lua::State* L) { static int l_get_chunk_data(lua::State* L) {

View file

@ -161,17 +161,13 @@ static LuaCanvas& require_canvas(State* L, int idx) {
static int l_clear(State* L) { static int l_clear(State* L) {
auto& canvas = require_canvas(L, 1); auto& canvas = require_canvas(L, 1);
auto& image = canvas.getData(); auto& image = canvas.getData();
ubyte* data = image.getData(); size_t pixelscount = image.getWidth() * image.getHeight();
RGBA rgba {}; uint32_t* data = reinterpret_cast<uint32_t*>(image.getData());
if (gettop(L) == 1) { if (gettop(L) == 1) {
std::fill(data, data + image.getDataSize(), 0); std::fill(data, data + pixelscount, 0);
return 0; } else {
} std::fill(data, data + pixelscount, get_rgba(L, 2).rgba);
rgba = get_rgba(L, 2);
size_t pixels = image.getWidth() * image.getHeight();
const size_t channels = 4;
for (size_t i = 0; i < pixels * channels; i++) {
data[i] = rgba.arr[i % channels];
} }
return 0; return 0;
} }

View file

@ -7,9 +7,11 @@
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
using namespace std::literals;
static debug::Logger logger("main"); static debug::Logger logger("main");
static void sigterm_handler(int signum) { static void sigterm_handler(int signum) {
logger.info() << (signum == SIGTERM ? "SIGTERM" : "SIGINT") << " received";
Engine::getInstance().quit(); Engine::getInstance().quit();
} }
@ -25,13 +27,24 @@ int main(int argc, char** argv) {
if (!parse_cmdline(argc, argv, coreParameters)) { if (!parse_cmdline(argc, argv, coreParameters)) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
logger.debug() << "sub-process depth: "
<< coreParameters.subProcessDepth;
} catch (const std::runtime_error& err) { } catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl; std::cerr << err.what() << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::signal(SIGTERM, sigterm_handler); std::signal(SIGTERM, sigterm_handler);
#ifdef NDEBUG
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log"); std::signal(SIGINT, sigterm_handler);
#endif
std::filesystem::path logFile = coreParameters.logFile;
if (logFile.empty()) {
logFile = coreParameters.userFolder.string() + "/latest"s +
(coreParameters.subProcessDepth > 0
? ".sub" + std::to_string(coreParameters.subProcessDepth)
: "") + ".log"s;
}
debug::Logger::init(logFile.u8string());
platform::configure_encoding(); platform::configure_encoding();
auto& engine = Engine::getInstance(); auto& engine = Engine::getInstance();

View file

@ -128,7 +128,7 @@ void Player::teleport(glm::vec3 position) {
void Player::attemptToChooseSpawnpoint() { void Player::attemptToChooseSpawnpoint() {
// looks bad to be here tbh // looks bad to be here tbh
const auto& generatorDef = const auto& generatorDef =
level.content.generators.require(level.getWorld()->getGenerator()); level.content.generators.require(level.environment.generator);
int minHeight = generatorDef.playerMinSpawnHeight; int minHeight = generatorDef.playerMinSpawnHeight;
int maxHeight = generatorDef.playerMaxSpawnHeight; int maxHeight = generatorDef.playerMaxSpawnHeight;

View file

@ -13,22 +13,22 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
class ArgC { class ArgC {
public: public:
std::string keyword; std::string keyword;
std::function<bool()> execute; std::function<bool(CoreParameters&, util::ArgsReader&)> execute;
std::string args; std::string args;
std::string help; std::string help;
ArgC( ArgC(
const std::string& keyword, const std::string& keyword,
std::function<bool()> execute, std::function<bool(CoreParameters&, util::ArgsReader&)> execute,
const std::string& args, const std::string& args,
const std::string& help const std::string& help
) { ) {
this->keyword = keyword; this->keyword = keyword;
this->execute = execute; this->execute = execute;
this->args = args; this->args = args;
this->help = help; this->help = help;
} }
}; };
@ -36,54 +36,66 @@ static bool perform_keyword(
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
) { ) {
static const std::vector<ArgC> argumentsCommandline = { static const std::vector<ArgC> argumentsCommandline = {
ArgC("--res", [&params, &reader]() -> bool { ArgC("--res", [](auto& params, auto& reader) -> bool {
params.resFolder = reader.next(); params.resFolder = reader.next();
return true; return true;
}, "<path>", "set resources directory."), }, "<path>", "set resources directory."),
ArgC("--dir", [&params, &reader]() -> bool { ArgC("--dir", [](auto& params, auto& reader) -> bool {
params.userFolder = reader.next(); params.userFolder = reader.next();
return true; return true;
}, "<path>", "set userfiles directory."), }, "<path>", "set userfiles directory."),
ArgC("--project", [&params, &reader]() -> bool { ArgC("--project", [](auto& params, auto& reader) -> bool {
params.projectFolder = reader.next(); params.projectFolder = reader.next();
return true; return true;
}, "<path>", "set project directory."), }, "<path>", "set project directory."),
ArgC("--test", [&params, &reader]() -> bool { ArgC("--test", [](auto& params, auto& reader) -> bool {
params.testMode = true; params.testMode = true;
params.scriptFile = reader.next(); params.scriptFile = reader.next();
return true; return true;
}, "<path>", "test script file."), }, "<path>", "test script file."),
ArgC("--script", [&params, &reader]() -> bool { ArgC("--script", [](auto& params, auto& reader) -> bool {
params.testMode = false; params.testMode = false;
params.scriptFile = reader.next(); params.scriptFile = reader.next();
return true; return true;
}, "<path>", "main script file."), }, "<path>", "main script file."),
ArgC("--headless", [&params]() -> bool { ArgC("--headless", [](auto& params, auto& reader) -> bool {
params.headless = true; params.headless = true;
return true; return true;
}, "", "run in headless mode."), }, "", "run in headless mode."),
ArgC("--stdin-cmd", [&params]() -> bool { ArgC("--stdin-cmd", [](auto& params, auto& reader) -> bool {
params.stdinCommands = true; params.stdinCommands = true;
return true; return true;
}, "", "run commands from stdin."), }, "", "run commands from stdin."),
ArgC("--tps", [&params, &reader]() -> bool { ArgC("--tps", [](auto& params, auto& reader) -> bool {
params.tps = reader.nextInt(); params.tps = reader.nextInt();
return true; return true;
}, "<tps>", "headless mode tick-rate (default - 20)."), }, "<tps>", "headless mode tick-rate (default - 20)."),
ArgC("--version", []() -> bool { ArgC("--version", [](auto&, auto&) -> bool {
std::cout << ENGINE_VERSION_STRING << std::endl; std::cout << ENGINE_VERSION_STRING << std::endl;
return false; return false;
}, "", "display the engine version."), }, "", "display the engine version."),
ArgC("--dbg-server", [&params, &reader]() -> bool { ArgC("--dbg-server", [](auto& params, auto& reader) -> bool {
params.debugServerString = reader.next(); params.debugServerString = reader.next();
return true; return true;
}, "<serv>", "open debugging server where <serv> is {transport}:{port}"), }, "<serv>", "open debugging server where <serv> is {transport}:{port}"),
ArgC("--help", []() -> bool { ArgC("--sub-depth", [](auto& params, auto& reader) -> bool {
params.subProcessDepth = reader.nextInt();
return true;
}, "<depth>", "sub-process depth"),
ArgC("--log", [](auto& params, auto& reader) -> bool {
params.logFile = reader.next();
return true;
}, "<file>", "target log file"),
ArgC("--help", [](auto&, auto&) -> bool {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n"; std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "Command-line arguments:\n"; std::cout << "Command-line arguments:\n";
for (auto& a : argumentsCommandline) { for (auto& arg : argumentsCommandline) {
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args); if (arg.help.empty()) {
std::cout << "- " << a.help << std::endl; continue;
}
std::cout << std::setw(24) << std::left
<< (arg.keyword + " " + arg.args);
std::cout << "- " << arg.help << std::endl;
} }
std::cout << std::endl; std::cout << std::endl;
return false; return false;
@ -91,7 +103,7 @@ static bool perform_keyword(
}; };
for (auto& a : argumentsCommandline) { for (auto& a : argumentsCommandline) {
if (a.keyword == keyword) { if (a.keyword == keyword) {
return a.execute(); return a.execute(params, reader);
} }
} }
throw std::runtime_error("unknown argument " + keyword); throw std::runtime_error("unknown argument " + keyword);

View file

@ -19,7 +19,16 @@
#else #else
#include <sys/poll.h> #include <sys/poll.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#endif #endif
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/wait.h>
#endif
using namespace std::literals;
namespace platform::internal { namespace platform::internal {
std::filesystem::path get_executable_path(); std::filesystem::path get_executable_path();
@ -215,8 +224,102 @@ std::filesystem::path platform::get_executable_path() {
#endif #endif
} }
void platform::new_engine_instance( class SystemProcess final : public Process {
const std::vector<std::string>& args, std::filesystem::path outputFile public:
#ifdef __linux__
SystemProcess(pid_t pid) : pid(pid) {
}
~SystemProcess() {
terminate();
}
#elif defined(_WIN32)
SystemProcess(const PROCESS_INFORMATION& pi)
: processHandle(pi.hProcess), pid(pi.dwProcessId) {
if (pi.hThread != nullptr) {
CloseHandle(pi.hThread);
}
}
~SystemProcess() {
terminate();
}
#endif
SystemProcess(const SystemProcess&) = delete;
SystemProcess(SystemProcess&&) noexcept = default;
SystemProcess& operator=(const SystemProcess&) = delete;
SystemProcess& operator=(SystemProcess&& other) noexcept {
if (this != &other) {
#ifdef __linux__
pid = other.pid;
#elif defined(_WIN32)
if (processHandle != nullptr) {
CloseHandle(processHandle);
}
processHandle = other.processHandle;
pid = other.pid;
other.processHandle = nullptr;
other.pid = 0;
#endif
}
return *this;
}
bool isActive() const override {
#ifdef __linux__
if (kill(pid, 0) == 0) {
return true;
}
return errno != ESRCH;
#elif defined(_WIN32)
if (processHandle == nullptr) {
return false;
}
DWORD exitCode;
if (GetExitCodeProcess(processHandle, &exitCode)) {
return exitCode == STILL_ACTIVE;
}
return false;
#endif
}
void update() override {}
void waitForEnd() override {
#ifdef __linux__
waitpid(pid, nullptr, 0);
#elif defined(_WIN32)
if (processHandle != nullptr) {
WaitForSingleObject(processHandle, INFINITE);
}
#endif
};
void terminate() final override {
#ifdef __linux__
logger.info() << "terminating " << pid;
kill(pid, SIGTERM);
#elif defined(_WIN32)
if (processHandle != nullptr) {
TerminateProcess(processHandle, 1);
WaitForSingleObject(processHandle, INFINITE);
}
#endif
}
private:
#ifdef __linux__
pid_t pid;
#elif defined(_WIN32)
HANDLE processHandle = nullptr;
DWORD pid = 0;
#endif
};
std::unique_ptr<Process> platform::new_engine_instance(
const std::vector<std::string>& args,
std::filesystem::path outputFile,
bool subProcess
) { ) {
auto executable = get_executable_path(); auto executable = get_executable_path();
@ -255,7 +358,7 @@ void platform::new_engine_instance(
if (!outputFile.empty()) { if (!outputFile.empty()) {
si.dwFlags |= STARTF_USESTDHANDLES; si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = CreateFileW( si.hStdOutput = CreateFileW(
toWString(outputFile.u8string()).data(), toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s).data(),
GENERIC_WRITE, GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ, FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr, nullptr,
@ -285,6 +388,38 @@ void platform::new_engine_instance(
&si, &si,
&pi &pi
); );
if (subProcess) {
HANDLE job = CreateJobObjectW(nullptr, nullptr);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
try {
if (!SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
&info,
sizeof(info))) {
throw std::runtime_error(
"SetInformationJobObject failed with code: " +
std::to_string(GetLastError())
);
}
if (!AssignProcessToJobObject(job, pi.hProcess)) {
throw std::runtime_error(
"SetInformationJobObject failed with code: " +
std::to_string(GetLastError())
);
}
return std::make_unique<SystemProcess>(pi);
} catch (const std::exception& err) {
CloseHandle(job);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return nullptr;
}
}
if (success) { if (success) {
CloseHandle(pi.hProcess); CloseHandle(pi.hProcess);
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
@ -295,6 +430,49 @@ void platform::new_engine_instance(
); );
} }
#else #else
// TODO: implement
#ifndef __APPLE__
if (subProcess) {
pid_t pid = fork();
if (outputFile.empty()) {
outputFile = "/dev/null";
}
int fd = open(
outputFile.string().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644
);
if (pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGTERM);
if (getppid() == 1) {
_exit(1);
}
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
std::vector<const char*> argv(args.size() + 2);
argv[0] = const_cast<char*>(executable.c_str());
for (int i = 0; i < args.size(); i++) {
argv[i + 1] = args[i].c_str();
}
argv[args.size() + 1] = nullptr;
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
execvp(executable.c_str(), const_cast<char**>(argv.data()));
_exit(127);
} else {
close(fd);
usleep(1000); // waiting for execvp call
return std::make_unique<SystemProcess>(pid);
}
}
#endif
std::stringstream ss; std::stringstream ss;
ss << executable; ss << executable;
for (int i = 0; i < args.size(); i++) { for (int i = 0; i < args.size(); i++) {
@ -315,6 +493,7 @@ void platform::new_engine_instance(
); );
} }
#endif #endif
return nullptr;
} }
bool platform::stdin_has_data() { bool platform::stdin_has_data() {

View file

@ -2,8 +2,11 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include <filesystem> #include <filesystem>
#include "interfaces/Process.hpp"
namespace platform { namespace platform {
void configure_encoding(); void configure_encoding();
/// @brief Get Environment locale in ISO format ll_CC /// @brief Get Environment locale in ISO format ll_CC
@ -18,8 +21,10 @@ namespace platform {
/// @brief Get current process running executable path /// @brief Get current process running executable path
std::filesystem::path get_executable_path(); std::filesystem::path get_executable_path();
/// @brief Run a separate engine instance with specified arguments /// @brief Run a separate engine instance with specified arguments
void new_engine_instance( std::unique_ptr<Process> new_engine_instance(
const std::vector<std::string>& args, std::filesystem::path outputFile const std::vector<std::string>& args,
std::filesystem::path outputFile,
bool subProcess
); );
/// @brief Open URL in web browser /// @brief Open URL in web browser
bool open_url(const std::string& url); bool open_url(const std::string& url);

View file

@ -37,8 +37,6 @@ inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
inline constexpr int BLOCK_MAX_VARIANTS = 16; inline constexpr int BLOCK_MAX_VARIANTS = 16;
inline std::string DEFAULT_MATERIAL = "base:stone";
struct BlockFuncsSet { struct BlockFuncsSet {
bool init : 1; bool init : 1;
bool update : 1; bool update : 1;
@ -206,7 +204,7 @@ public:
dv::value properties = nullptr; dv::value properties = nullptr;
/// @brief id of used BlockMaterial, may specify non-existing material /// @brief id of used BlockMaterial, may specify non-existing material
std::string material = DEFAULT_MATERIAL; std::string material;
/// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive /// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive
/// clouds) /// clouds)

49
src/world/Environment.cpp Normal file
View file

@ -0,0 +1,49 @@
#define VC_ENABLE_REFLECTION
#include "Environment.hpp"
#include "data/dv.hpp"
#include "data/dv_util.hpp"
dv::value Environment::serialize() const {
auto skySpritesList = dv::list();
for (const auto& sprite : sky.sprites) {
skySpritesList.add(dv::object({
{"texture", sprite.texture},
{"phase", sprite.phase},
{"distance", sprite.distance},
{"emissive", sprite.emissive},
{"altitude", sprite.altitude},
}));
}
return dv::object({
{"sky", dv::object({
{"mode", SkyModeMeta.getNameString(sky.mode)},
{"stars", sky.stars},
{"clouds", sky.clouds},
{"sprites", std::move(skySpritesList)},
})},
{"generator", generator}
});
}
void Environment::deserialize(const dv::value& src) {
if (auto skyItem = src.at("sky")) {
skyItem->at("mode").get(sky.mode, SkyModeMeta);
skyItem->at("stars").get(sky.stars);
skyItem->at("clouds").get(sky.clouds);
if (auto skySpritesList = skyItem->at("sprites")) {
for (int i = 0; i < skySpritesList->size(); i++) {
SkySprite sprite;
const auto& item = (*skySpritesList.ptr)[i];
item.at("texture").get(sprite.texture);
item.at("phase").get(sprite.phase);
item.at("distance").get(sprite.distance);
item.at("emissive").get(sprite.emissive);
item.at("altitude").get(sprite.altitude);
sky.sprites.push_back(std::move(sprite));
}
}
}
src.at("generator").get(generator);
}

66
src/world/Environment.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include "interfaces/Serializable.hpp"
#include "util/EnumMetadata.hpp"
#include <vector>
#include <glm/gtc/constants.hpp>
enum class SkyMode {
NONE, SOLID, BOX
};
VC_ENUM_METADATA(SkyMode)
{"none", SkyMode::NONE},
{"solid", SkyMode::SOLID},
{"box", SkyMode::BOX},
VC_ENUM_END
struct SkySprite {
std::string texture;
float phase;
float distance;
bool emissive;
float altitude;
};
class Environment : public Serializable {
public:
struct {
SkyMode mode = SkyMode::BOX;
bool stars = true;
bool clouds = true;
// TODO: move defaults to config file
std::vector<SkySprite> sprites {
SkySprite {
"misc/moon",
glm::pi<float>() * 0.5f,
4.0f,
false,
glm::pi<float>() * 0.25f,
},
SkySprite {
"misc/moon_flare",
glm::pi<float>() * 0.5f,
0.5f,
false,
glm::pi<float>() * 0.25f,
},
SkySprite {
"misc/sun",
glm::pi<float>() * 1.5f,
4.0f,
true,
glm::pi<float>() * 0.25f,
},
};
} sky;
std::string generator;
Environment() = default;
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
private:
};

View file

@ -31,6 +31,7 @@ Level::Level(
entities(std::make_unique<Entities>(*this)), entities(std::make_unique<Entities>(*this)),
players(std::make_unique<Players>(*this)), players(std::make_unique<Players>(*this)),
pathfinding(std::make_unique<voxels::Pathfinding>(*this)) { pathfinding(std::make_unique<voxels::Pathfinding>(*this)) {
const auto& worldInfo = world->getInfo(); const auto& worldInfo = world->getInfo();
auto& cameraIndices = content.getIndices(ResourceType::CAMERA); auto& cameraIndices = content.getIndices(ResourceType::CAMERA);
for (size_t i = 0; i < cameraIndices.size(); i++) { for (size_t i = 0; i < cameraIndices.size(); i++) {

View file

@ -6,6 +6,7 @@
#include <unordered_map> #include <unordered_map>
#include "typedefs.hpp" #include "typedefs.hpp"
#include "Environment.hpp"
class Content; class Content;
class World; class World;
@ -36,6 +37,7 @@ public:
std::unique_ptr<Players> players; std::unique_ptr<Players> players;
std::unique_ptr<voxels::Pathfinding> pathfinding; std::unique_ptr<voxels::Pathfinding> pathfinding;
std::vector<std::shared_ptr<Camera>> cameras; // move somewhere? std::vector<std::shared_ptr<Camera>> cameras; // move somewhere?
Environment environment {};
Level( Level(
std::unique_ptr<World> world, std::unique_ptr<World> world,

View file

@ -65,12 +65,12 @@ void World::writeResources(const Content& content) {
io::write_json(wfile->getResourcesFile(), root); io::write_json(wfile->getResourcesFile(), root);
} }
void World::write(Level* level) { void World::write(Level& level) {
level->chunks->saveAll(); level.chunks->saveAll();
info.nextEntityId = level->entities->peekNextID(); info.nextEntityId = level.entities->peekNextID();
wfile->write(this, &content); wfile->write(this, &content);
auto playerFile = level->players->serialize(); auto playerFile = level.players->serialize();
io::write_json(wfile->getPlayerFile(), playerFile); io::write_json(wfile->getPlayerFile(), playerFile);
writeResources(content); writeResources(content);
@ -78,7 +78,8 @@ void World::write(Level* level) {
std::unique_ptr<Level> World::create( std::unique_ptr<Level> World::create(
const std::string& name, const std::string& name,
const std::string& generator, const std::string& environment,
const std::string& generatorOverride,
const io::path& directory, const io::path& directory,
uint64_t seed, uint64_t seed,
EngineSettings& settings, EngineSettings& settings,
@ -87,7 +88,8 @@ std::unique_ptr<Level> World::create(
) { ) {
WorldInfo info {}; WorldInfo info {};
info.name = name; info.name = name;
info.generator = generator; info.environment = environment;
info.explicitGenerator = generatorOverride;
info.seed = seed; info.seed = seed;
auto world = std::make_unique<World>( auto world = std::make_unique<World>(
info, info,
@ -101,7 +103,7 @@ std::unique_ptr<Level> World::create(
logger.info() << "created world '" << name << "' (" logger.info() << "created world '" << name << "' ("
<< directory.string() << ")"; << directory.string() << ")";
} }
logger.info() << "world seed: " << seed << " generator: " << generator; logger.info() << "world seed: " << seed << " environment: " << environment;
return std::make_unique<Level>(std::move(world), content, settings); return std::make_unique<Level>(std::move(world), content, settings);
} }
@ -122,16 +124,19 @@ std::unique_ptr<Level> World::load(
<< worldFilesPtr->getFolder().string() << ")"; << worldFilesPtr->getFolder().string() << ")";
logger.info() << "world version: " << info->major << "." << info->minor logger.info() << "world version: " << info->major << "." << info->minor
<< " seed: " << info->seed << " seed: " << info->seed
<< " generator: " << info->generator; << " environment: " << info->environment;
auto world = std::make_unique<World>( auto worldPtr = std::make_unique<World>(
info.value(), std::move(worldFilesPtr), content, packs info.value(), std::move(worldFilesPtr), content, packs
); );
auto world = worldPtr.get();
auto& wfile = world->wfile; auto& wfile = world->wfile;
wfile->readResourcesData(content); wfile->readResourcesData(content);
auto level = std::make_unique<Level>(std::move(world), content, settings); auto level = std::make_unique<Level>(std::move(worldPtr), content, settings);
if (world->getEnvironment().empty()) {
level->environment.generator = world->getInfo().explicitGenerator;
}
io::path file = wfile->getPlayerFile(); io::path file = wfile->getPlayerFile();
if (!io::is_regular_file(file)) { if (!io::is_regular_file(file)) {
logger.warning() << "player.json does not exists"; logger.warning() << "player.json does not exists";
@ -163,8 +168,8 @@ void World::setName(const std::string& name) {
this->info.name = name; this->info.name = name;
} }
void World::setGenerator(const std::string& generator) { void World::setEnvironment(const std::string& environment) {
this->info.generator = generator; this->info.environment = environment;
} }
bool World::hasPack(const std::string& id) const { bool World::hasPack(const std::string& id) const {
@ -186,8 +191,8 @@ uint64_t World::getSeed() const {
return info.seed; return info.seed;
} }
std::string World::getGenerator() const { std::string World::getEnvironment() const {
return info.generator; return info.environment;
} }
const std::vector<ContentPack>& World::getPacks() const { const std::vector<ContentPack>& World::getPacks() const {
@ -196,7 +201,8 @@ const std::vector<ContentPack>& World::getPacks() const {
void WorldInfo::deserialize(const dv::value& root) { void WorldInfo::deserialize(const dv::value& root) {
name = root["name"].asString(); name = root["name"].asString();
generator = root["generator"].asString(generator); root.at("environment").get(environment);
root.at("generator").get(explicitGenerator);
seed = root["seed"].asInteger(seed); seed = root["seed"].asInteger(seed);
if (root.has("version")) { if (root.has("version")) {
@ -226,7 +232,11 @@ dv::value WorldInfo::serialize() const {
versionobj["minor"] = ENGINE_VERSION_MINOR; versionobj["minor"] = ENGINE_VERSION_MINOR;
root["name"] = name; root["name"] = name;
root["generator"] = generator; if (environment.empty()) {
root["generator"] = explicitGenerator;
} else {
root["environment"] = environment;
}
root["seed"] = seed; root["seed"] = seed;
auto& timeobj = root.object("time"); auto& timeobj = root.object("time");

View file

@ -23,7 +23,8 @@ public:
struct WorldInfo : public Serializable { struct WorldInfo : public Serializable {
std::string name; std::string name;
std::string generator; std::string environment;
std::string explicitGenerator;
uint64_t seed; uint64_t seed;
int64_t nextInventoryId = 1; int64_t nextInventoryId = 1;
int64_t nextPlayerId = 0; int64_t nextPlayerId = 0;
@ -76,7 +77,7 @@ public:
void updateTimers(float delta); void updateTimers(float delta);
/// @brief Write all unsaved level data to the world directory /// @brief Write all unsaved level data to the world directory
void write(Level* level); void write(Level& level);
/// @brief Check world indices and generate ContentReport if convert required /// @brief Check world indices and generate ContentReport if convert required
/// @param directory world directory /// @param directory world directory
@ -98,7 +99,8 @@ public:
/// @return Level instance containing World instance /// @return Level instance containing World instance
static std::unique_ptr<Level> create( static std::unique_ptr<Level> create(
const std::string& name, const std::string& name,
const std::string& generator, const std::string& environment,
const std::string& generatorOverride,
const io::path& directory, const io::path& directory,
uint64_t seed, uint64_t seed,
EngineSettings& settings, EngineSettings& settings,
@ -123,7 +125,7 @@ public:
void setName(const std::string& name); void setName(const std::string& name);
void setSeed(uint64_t seed); void setSeed(uint64_t seed);
void setGenerator(const std::string& generator); void setEnvironment(const std::string& environment);
/// @brief Check if world has content-pack installed /// @brief Check if world has content-pack installed
/// @param id content-pack id /// @param id content-pack id
@ -137,7 +139,7 @@ public:
uint64_t getSeed() const; uint64_t getSeed() const;
/// @brief Get world generator id /// @brief Get world generator id
std::string getGenerator() const; std::string getEnvironment() const;
bool isNameless() const { bool isNameless() const {
return info.name.empty(); return info.name.empty();