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)
Bytearray.insert(arr, 2, 8)
assert(#arr == 13)
asserts.equals(13, #arr)
for i=1,10 do
assert(arr[i] == 10 - i)
end
print(#arr, arr:get_capacity())
arr:trim()
assert(#arr == arr:get_capacity())
asserts.equals(#arr, arr:get_capacity())
arr = Bytearray({0, 2, 7, 1, 16, 75, 25})
assert(arr[6] == 75)
arr:insert(2, {5, 6})
assert(arr[#arr] == 25)
assert(arr[2] == 5)
assert(arr[3] == 6)
asserts.equals(25, arr[#arr])
asserts.equals(5, arr[2])
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
## Visual
## Visual & Audio
### *texture*
@ -70,6 +70,18 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
- "pane" - panels, doors, signs
- "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
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 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]
> When loading an obj model, the \*.mtl file is ignored.

View file

@ -16,6 +16,7 @@ Subsections:
- [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.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.
``lua
```lua
app.set_content_sources(sources: table<string>)
```
Sets a list of content sources (paths). Specified in descending priority order.
``lua
```lua
app.reset_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
-- 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.
-- 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*
@ -78,6 +78,11 @@
При приближении к блоку движок создаст эмиттер, который будет работать
до разрушения блока или отдаления камеры на некоторое расстояние.
### Материал - *material*
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
Определения материалов расположены в /block_materials.
## Варианты
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.

View file

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

View file

@ -16,6 +16,7 @@
- [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.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()
```
```
## Под-экземпляры
```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**
<small>Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число.</small>
## Основное
### Создание массива
```lua
@ -22,29 +24,64 @@ bytes:insert(index: int, value: int)
-- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса.
bytes:remove(index: int, [опционально] count: int)
-- Очищает массив
-- Очищает массив.
bytes:clear()
--[[
Заполняет массив определённым байтом полностью либо в определённом диапазоне.
Логика работы следующая: если параметр size или index равны nil, то указанным байтом будет заполнен весь массив,
иначе будет заполнен диапазон внутри массива начиная с индекса index и размером size.
В случае выхода заполняемого блока за границы массива генерирует соответствующую ошибку.
]]
bytes:fill(index: posint/nil, size: posint/nil, byte: uint8)
-- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length
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
Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**.
Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray.
Практически не имеют своих методов, являются простыми объектными ссылками на оригинальный Bytearray.
| Класс | Тип элементов | Размер |
| ------------ | ------------------ | ------- |
| `I16view` | `int16_t` | 2 байта |
| `U16view` | `uint16_t` | 2 байта |
| `I32view` | `int32_t` | 4 байта |
| `U32view` | `uint32_t` | 4 байта |
| Класс | Тип элементов | Размер |
| ------------ | ------------------ | --------- |
| `I8view` | `int8_t` | 1 байт |
| `I16view` | `int16_t` | 2 байта |
| `U16view` | `uint16_t` | 2 байта |
| `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
local bytes = Bytearray({1, 0, 2, 0, 250, 255})
@ -59,4 +96,4 @@ print(i16[3]) -- -6 (signed вьюшка)
for _, num in ipairs(i16) do
print(num)
end -- 1; 2; -6
```
```

View file

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

View file

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

View file

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

View file

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

View file

@ -4,12 +4,14 @@ local FFI = ffi
FFI.cdef[[
void* malloc(size_t);
void* realloc(void*, size_t);
void free(void*);
typedef struct {
unsigned char* bytes;
int size;
int capacity;
} bytearray_t;
void* memmove(void*, const void*, size_t);
]]
local free = FFI.C.free
@ -22,12 +24,33 @@ local GC_THRESHOLD = 50 * 1024 * 1024
local function malloc(size)
local raw = FFI.C.malloc(size)
if raw == nil then
return nil
if not raw then
error("failed memory allocation of " .. size .. " bytes")
end
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
collectgarbage("step", 200)
allocated_bytes = 0
@ -38,23 +61,17 @@ end
local function grow_buffer(self, elems)
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
local prev = self.bytes
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size)
self.bytes = realloc(self.bytes, new_capacity)
self.capacity = new_capacity
free(prev)
end
local function trim_buffer(self)
if self.size == self.capacity then
return
if self.size == self.capacity then return end
local new_bytes = realloc(self.bytes, self.size, false)
if new_bytes then
self.bytes = new_bytes
self.capacity = self.size
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
local function count_elements(b)
@ -125,15 +142,21 @@ local function clear(self)
self.size = 0
end
local function reserve(self, new_capacity)
if new_capacity <= self.capacity then
return
local function fill(self, index, size, byte)
if index ~= nil and size ~= nil and size > 0 then
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
local prev = self.bytes
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size)
end
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
free(prev)
end
local function get_capacity(self)
@ -150,22 +173,46 @@ local function slice(self, offset, length)
length = self.size - offset + 1
end
local buffer = malloc(length)
if not buffer then
error("malloc(" .. length .. ") returned NULL")
end
FFI.copy(buffer, self.bytes + (offset - 1), length)
return bytearray_type(buffer, length, length)
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 = {
append=append,
insert=insert,
remove=remove,
trim=trim_buffer,
clear=clear,
reserve=reserve,
get_capacity=get_capacity,
slice=slice,
append = append,
insert = insert,
remove = remove,
trim = trim_buffer,
clear = clear,
reserve = reserve,
get_capacity = get_capacity,
slice = slice,
fill = fill,
copy = copy,
move = move
}
local bytearray_mt = {
@ -256,7 +303,8 @@ local function FFIBytearray_as_string(bytes)
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 FFIview_mt = {
__index = function(self, key)
@ -291,27 +339,44 @@ local function create_FFIview_class(name, typename, typesize)
return i, ptr[i - 1]
end
end
end
end,
}
return function (bytes)
local x = setmetatable({
bytes=bytes,
}, FFIview_mt)
return x
end
return setmetatable({}, {
__call = function (self, bytes)
local x = setmetatable({
bytes=bytes,
}, FFIview_mt)
return x
end,
__index = {
typesize = function()
return typesize
end
}
})
end
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2)
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2)
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4)
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
local FFII8view = create_FFIview_class("FFII8view", "int8_t")
local FFII16view = create_FFIview_class("FFII16view", "int16_t")
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t")
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 {
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
FFIBytearray_as_string = FFIBytearray_as_string,
FFIBytearray_as_ptr = FFIBytearray_as_ptr,
FFII8view = FFII8view,
FFIU16view = FFIU16view,
FFII16view = FFII16view,
FFIU32view = FFIU32view,
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)
local rule = rules.rules[name]
@ -27,13 +27,13 @@ end
function rules.reset(name)
local rule = rules.get_rule(name)
rules.set(rule.default)
rules.set(name, rule.default)
end
function rules.listen(name, handler)
local rule = rules.get_rule(name)
local id = rules.nexid
rules.nextid = rules.nexid + 1
local id = rules.nextid
rules.nextid = rules.nextid + 1
rule.listeners[utf8.encode(id)] = handler
return id
end

View file

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

View file

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

View file

@ -140,7 +140,20 @@ local __vc_named_coroutines = {}
local __vc_next_coroutine = 1
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
__vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co

View file

@ -10,8 +10,9 @@ vc = {
is_client = function()
return not _vc_headless
end,
get_version = __vc_app.get_version,
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,
}
@ -310,12 +311,19 @@ local bytearray = require "core:internal/bytearray"
Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr
I8view = bytearray.FFII8view
U16view = bytearray.FFIU16view
I16view = bytearray.FFII16view
U32view = bytearray.FFIU32view
I32view = bytearray.FFII32view
U64view = bytearray.FFIU64view
I64view = bytearray.FFII64view
FLTview = bytearray.FFIFLTview
DBLview = bytearray.FFIDBLview
Bytearray_construct = function(...) return Bytearray(...) end
ctypes = require "core:internal/ctypes"
bit.compile = require "core:bitwise/compiler"
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 constexpr uint MAX_SUBPROCESSES = 1;
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
/// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3;

View file

@ -39,6 +39,9 @@ std::unique_ptr<Content> ContentBuilder::build() {
} else {
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;
def.rt.solid =

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,9 @@ struct CoreParameters {
std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile;
std::filesystem::path projectFolder;
std::filesystem::path logFile;
std::string debugServerString;
int tps = 20;
int subProcessDepth = 0;
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);
}));
appScripts = std::make_unique<AppScriptsControl>(params, *project);
appScripts = std::make_unique<AppScriptsControl>(params);
if (params.stdinCommands) {
cmd::start_stdin_cmd_reader(*this);
@ -380,6 +380,7 @@ void Engine::loadAssets() {
void Engine::loadProject() {
io::path projectFile = "project:project.toml";
project = std::make_unique<Project>();
project->path = "project:";
project->deserialize(io::read_object(projectFile));
logger.info() << "loaded project " << util::quote(project->name);
}
@ -412,6 +413,7 @@ void Engine::onWorldClosed() {
}
void Engine::quit() {
logger.info() << "quitSignal set to true";
quitSignal = true;
if (!isHeadless()) {
window->setShouldClose(true);

View file

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

View file

@ -4,7 +4,7 @@
#include <GL/glew.h>
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
: Texture(0, width, height)
: Texture(0U, width, height, imageFormat)
{
glGenTextures(1, &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_R, GL_CLAMP_TO_EDGE);
uint format = gl::to_glenum(imageFormat);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
@ -37,3 +36,23 @@ void Cubemap::bind() const {
void Cubemap::unbind() const {
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 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) {
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 {
@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) {
lineWidth = 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
#include <array>
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
@ -23,6 +24,7 @@ class DrawContext {
BlendMode blendMode = BlendMode::normal;
int scissorsCount = 0;
float lineWidth = 1.0f;
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
public:
DrawContext(
const DrawContext* parent,
@ -44,4 +46,5 @@ public:
void setBlendMode(BlendMode mode);
void setScissors(const glm::vec4& area);
void setLineWidth(float width);
void useTexture(int target, const Bindable* texture);
};

View file

@ -1,14 +1,14 @@
#include "Framebuffer.hpp"
#include <GL/glew.h>
#include "Texture.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("gl-framebuffer");
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) {
width = this->texture->getWidth();
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) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
static std::unique_ptr<Texture> create_texture(
int width, int height, bool alpha
) {
GLenum glformat = alpha ? GL_RGBA : GL_RGB;
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_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 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);
return std::make_unique<Texture>(tex, width, height);
glFramebufferTexture2D(
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)
: width(width), height(height)
{
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height) {
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
format = alpha ? GL_RGBA : GL_RGB;
// Setup color attachment (texture)
texture = create_texture(width, height, format);
texture = create_texture(width, height, alpha);
// Setup depth attachment
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
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) {
logger.error() << "framebuffer is not complete!";
@ -60,11 +80,15 @@ Framebuffer::~Framebuffer() {
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);
}
void Framebuffer::unbind() {
void Framebuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
texture = create_texture(width, height, format);
texture->resize(width, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const {
}
uint Framebuffer::getFBO() const {
return fbo;
return fbo;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,29 +1,32 @@
#pragma once
#include "typedefs.hpp"
#include "maths/UVRegion.hpp"
#include "ImageData.hpp"
#include <memory>
class Texture {
#include "ImageData.hpp"
#include "commons.hpp"
#include "maths/UVRegion.hpp"
#include "typedefs.hpp"
class Texture : public Bindable {
protected:
uint id;
uint width;
uint height;
uint format;
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);
virtual ~Texture();
virtual void bind() const;
virtual void unbind() const;
void reload(const ubyte* data, uint w, uint h);
virtual void bind() const override;
virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h);
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
void setNearestFilter();
void reload(const ImageData& image);
virtual void resize(uint w, uint h);
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_SHADOWS0 = 6;
inline constexpr int TARGET_SHADOWS1 = 7;
inline constexpr int TARGETS_COUNT = 8;
}
VC_ENUM_METADATA(CursorShape)
@ -85,6 +87,6 @@ class Bindable {
public:
virtual ~Bindable() = default;
virtual void bind() = 0;
virtual void unbind() = 0;
virtual void bind() const = 0;
virtual void unbind() const = 0;
};

View file

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

View file

@ -1,91 +1,64 @@
#include "Skybox.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Shader.hpp"
#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 <GL/glew.h>
#include <cmath>
#include <iostream>
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.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;
const int STARS_COUNT = 3000;
const int STARS_SEED = 632;
Skybox::Skybox(uint size, Shader& shader)
: size(size),
shader(shader),
batch3d(std::make_unique<Batch3D>(4096))
{
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
Skybox::Skybox(uint size, const Assets& assets)
: size(size),
assets(assets),
shader(assets.require<Shader>("skybox_gen")),
batch3d(std::make_unique<Batch3D>(4096)) {
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(fboid, 0, std::move(cubemap));
SkyboxVertex vertices[]{
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}}};
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
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,
});
setMode(mode);
}
Skybox::~Skybox() = default;
void Skybox::drawBackground(
const Camera& camera, const Assets& assets, int width, int height
) {
void Skybox::drawBackground(const Camera& camera, int width, int height) {
auto backShader = assets.get<Shader>("background");
backShader->use();
backShader->uniformMatrix("u_view", camera.getView(false));
backShader->uniform1f(
"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);
bind();
auto texture = fbo->getTexture();
texture->bind();
mesh->draw();
unbind();
texture->unbind();
}
void Skybox::drawStars(float angle, float opacity) {
@ -93,9 +66,7 @@ void Skybox::drawStars(float angle, float opacity) {
random.setSeed(STARS_SEED);
glm::mat4 rotation = glm::rotate(
glm::mat4(1.0f),
-angle + glm::pi<float>() * 0.5f,
glm::vec3(0, 0, -1)
glm::mat4(1.0f), -angle + glm::pi<float>() * 0.5f, glm::vec3(0, 0, -1)
);
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;
float sopacity = random.randFloat();
if (pos.y < 0.0f)
continue;
if (pos.y < 0.0f) continue;
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->flushPoints();
}
void Skybox::draw(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
void Skybox::drawSkySprites(
float daytime,
float fog)
{
const glm::uvec2& viewport = pctx.getViewport();
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 angle,
float opacity,
const std::vector<SkySprite>& sprites
) {
float depthScale = 2e3;
for (auto& sprite : sprites) {
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 up = glm::vec4(depthScale, 0, 0, 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) {
tint *= 0.6f + std::cos(angle)*0.4;
tint *= 0.6f + std::cos(angle) * 0.4;
}
batch3d->sprite(pos, right,
up, 1, 1, UVRegion(), tint);
batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint);
}
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++;
float dayTime = t;
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
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());
assert(cubemap != nullptr);
ready = true;
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
cubemap->bind();
shader.use();
t *= glm::two_pi<float>();
float t = dayTime * glm::two_pi<float>();
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 x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) * glm::radians(sunAltitude);
float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) *
glm::radians(sunAltitude);
float y = sunAngle - glm::pi<float>() * 0.5f;
float z = glm::radians(0.0f);
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));
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
shader.use();
shader.uniform1i("u_quality", quality);
shader.uniform1f("u_mie", mie);
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.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++) {
refreshFace(face, cubemap);
refreshFace(face, *cubemap);
}
} else {
uint face = frameid % 6;
refreshFace(face, cubemap);
refreshFace(face, *cubemap);
}
prevMie = mie;
prevT = t;
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[] = {
{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_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
cubemap.getId(),
0
);
shader.uniform3f("u_xaxis", xaxs[face]);
@ -265,14 +301,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
mesh->draw();
}
void Skybox::bind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->bind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::unbind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->unbind();
glActiveTexture(GL_TEXTURE0);
const Cubemap* Skybox::getCubemap() const {
return dynamic_cast<const Cubemap*>(fbo->getTexture());
}

View file

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

View file

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

View file

@ -99,6 +99,8 @@ class WorldRenderer {
);
void refreshSettings(Shader** shaders);
float calcFogFactor() const;
public:
std::unique_ptr<ParticlesRenderer> particles;
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)) {}
bool operator==(const std::string& other) const {

View file

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

View file

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

View file

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

View file

@ -20,8 +20,14 @@
#include "window/Window.hpp"
#include "world/Level.hpp"
#include <array>
using namespace scripting;
namespace {
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
}
/// @brief Check if content is loaded
static int l_is_content_loaded(lua::State* L) {
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)) {
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);
if (port == 0) {
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--dbg-server", "tcp:" + std::to_string(port),
"--sub-depth", std::to_string(engine->getCoreParameters()
.subProcessDepth + 1),
};
if (!projectPath.empty()) {
args.emplace_back("--project");
@ -365,11 +377,76 @@ static int l_start_debug_instance(lua::State* L) {
platform::new_engine_instance(
std::move(args),
outputPath.empty() ? "" : io::resolve(std::string(outputPath))
outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
false
);
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[] = {
/// content
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
@ -402,5 +479,8 @@ const luaL_Reg applib[] = {
{"get_version", lua::wrap<l_get_version>},
{"create_memory_device", lua::wrap<l_create_memory_device>},
{"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}
};

View file

@ -125,7 +125,7 @@ static int l_is_night(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) {

View file

@ -161,17 +161,13 @@ static LuaCanvas& require_canvas(State* L, int idx) {
static int l_clear(State* L) {
auto& canvas = require_canvas(L, 1);
auto& image = canvas.getData();
ubyte* data = image.getData();
RGBA rgba {};
size_t pixelscount = image.getWidth() * image.getHeight();
uint32_t* data = reinterpret_cast<uint32_t*>(image.getData());
if (gettop(L) == 1) {
std::fill(data, data + image.getDataSize(), 0);
return 0;
}
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];
std::fill(data, data + pixelscount, 0);
} else {
std::fill(data, data + pixelscount, get_rgba(L, 2).rgba);
}
return 0;
}

View file

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

View file

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

View file

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

View file

@ -19,7 +19,16 @@
#else
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#endif
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/wait.h>
#endif
using namespace std::literals;
namespace platform::internal {
std::filesystem::path get_executable_path();
@ -215,8 +224,102 @@ std::filesystem::path platform::get_executable_path() {
#endif
}
void platform::new_engine_instance(
const std::vector<std::string>& args, std::filesystem::path outputFile
class SystemProcess final : public Process {
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();
@ -255,7 +358,7 @@ void platform::new_engine_instance(
if (!outputFile.empty()) {
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = CreateFileW(
toWString(outputFile.u8string()).data(),
toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s).data(),
GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr,
@ -285,6 +388,38 @@ void platform::new_engine_instance(
&si,
&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) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
@ -295,6 +430,49 @@ void platform::new_engine_instance(
);
}
#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;
ss << executable;
for (int i = 0; i < args.size(); i++) {
@ -315,6 +493,7 @@ void platform::new_engine_instance(
);
}
#endif
return nullptr;
}
bool platform::stdin_has_data() {

View file

@ -2,8 +2,11 @@
#include <string>
#include <vector>
#include <memory>
#include <filesystem>
#include "interfaces/Process.hpp"
namespace platform {
void configure_encoding();
/// @brief Get Environment locale in ISO format ll_CC
@ -18,8 +21,10 @@ namespace platform {
/// @brief Get current process running executable path
std::filesystem::path get_executable_path();
/// @brief Run a separate engine instance with specified arguments
void new_engine_instance(
const std::vector<std::string>& args, std::filesystem::path outputFile
std::unique_ptr<Process> new_engine_instance(
const std::vector<std::string>& args,
std::filesystem::path outputFile,
bool subProcess
);
/// @brief Open URL in web browser
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 std::string DEFAULT_MATERIAL = "base:stone";
struct BlockFuncsSet {
bool init : 1;
bool update : 1;
@ -206,7 +204,7 @@ public:
dv::value properties = nullptr;
/// @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
/// 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)),
players(std::make_unique<Players>(*this)),
pathfinding(std::make_unique<voxels::Pathfinding>(*this)) {
const auto& worldInfo = world->getInfo();
auto& cameraIndices = content.getIndices(ResourceType::CAMERA);
for (size_t i = 0; i < cameraIndices.size(); i++) {

View file

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

View file

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

View file

@ -23,7 +23,8 @@ public:
struct WorldInfo : public Serializable {
std::string name;
std::string generator;
std::string environment;
std::string explicitGenerator;
uint64_t seed;
int64_t nextInventoryId = 1;
int64_t nextPlayerId = 0;
@ -76,7 +77,7 @@ public:
void updateTimers(float delta);
/// @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
/// @param directory world directory
@ -98,7 +99,8 @@ public:
/// @return Level instance containing World instance
static std::unique_ptr<Level> create(
const std::string& name,
const std::string& generator,
const std::string& environment,
const std::string& generatorOverride,
const io::path& directory,
uint64_t seed,
EngineSettings& settings,
@ -123,7 +125,7 @@ public:
void setName(const std::string& name);
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
/// @param id content-pack id
@ -137,7 +139,7 @@ public:
uint64_t getSeed() const;
/// @brief Get world generator id
std::string getGenerator() const;
std::string getEnvironment() const;
bool isNameless() const {
return info.name.empty();