Merge branch 'main' into update-particles

This commit is contained in:
MihailRis 2024-11-19 10:15:42 +03:00
commit b66fb87427
163 changed files with 3071 additions and 1037 deletions

View File

@ -39,6 +39,12 @@ Block model type from list:
Integer specifying number of block draw group (render order). Used for semi-transparent blocks. Integer specifying number of block draw group (render order). Used for semi-transparent blocks.
### *translucent*
Enables translucency support in block textures (examples: water, ice).
Should only be used when needed, as it impacts performance.
Not required for full transparency (grass, flowers).
### *rotation* ### *rotation*
Rotation profile (set of available block rotations and behaviour of placing block rotation) from list: Rotation profile (set of available block rotations and behaviour of placing block rotation) from list:

View File

@ -1,6 +1,8 @@
# Documentation # Documentation
Documentation for the engine of version 0.24. Documentation for the engine of in-development version 0.25.
[Documentation for stable release 0.24.x.](https://github.com/MihailRis/VoxelEngine-Cpp/blob/release-0.24/doc/en/main-page.md)
## Sections ## Sections

View File

@ -7,6 +7,10 @@ hud.open_inventory()
-- Close inventory. -- Close inventory.
hud.close_inventory() hud.close_inventory()
-- Open UI and inventory.
-- Throws an exception if has no UI layout.
hud.open(invid: int, layoutid: str)
-- Open block UI and inventory. -- Open block UI and inventory.
-- Throws an exception if block has no UI layout. -- Throws an exception if block has no UI layout.
-- Returns block inventory ID (if *"inventory-size"=0* a virtual -- Returns block inventory ID (if *"inventory-size"=0* a virtual

View File

@ -40,12 +40,18 @@ inventory.bind_block(invid: int, x: int, y: int, z: int)
-- Unbind inventory from the specified block. -- Unbind inventory from the specified block.
inventory.unbind_block(x: int, y: int, z: int) inventory.unbind_block(x: int, y: int, z: int)
-- Remove inventory.
inventory.remove(invid: int)
``` ```
> [!WARNING] > [!WARNING]
> Unbound inventories will be deleted on world close. > Unbound inventories will be deleted on world close.
```lua ```lua
-- Create inventory. Returns the created ID.
inventory.create(size: int) -> int
-- Create inventory copy. Returns the created copy ID. -- Create inventory copy. Returns the created copy ID.
inventory.clone(invid: int) -> int inventory.clone(invid: int) -> int

View File

@ -27,4 +27,7 @@ utf8.upper(text: str) -> str
-- Converts a string to lowercase -- Converts a string to lowercase
utf8.lower(text: str) -> str utf8.lower(text: str) -> str
-- Escapes a string
utf8.escape(text: str) -> str
``` ```

View File

@ -74,17 +74,22 @@ Properties:
| ----------- | ------ | ---- | ----- | ------------------------------------------------------------------------------------ | | ----------- | ------ | ---- | ----- | ------------------------------------------------------------------------------------ |
| text | string | yes | yes | entered text or placeholder | | text | string | yes | yes | entered text or placeholder |
| placeholder | string | yes | yes | placeholder (used if nothing has been entered) | | placeholder | string | yes | yes | placeholder (used if nothing has been entered) |
| hint | string | yes | yes | text to display when nothing is entered |
| caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text | | caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text |
| editable | bool | yes | yes | text mutability | | editable | bool | yes | yes | text mutability |
| multiline | bool | yes | yes | multiline support | | multiline | bool | yes | yes | multiline support |
| lineNumbers | bool | yes | yes | display line numbers |
| textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") | | textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") |
| valid | bool | yes | no | is the entered text correct | | valid | bool | yes | no | is the entered text correct |
| textColor | vec4 | yes | yes | text color |
Methods: Methods:
| Method | Description | | Method | Description |
| ----------- | ------------------------------------------------ | | ------------------------- | ---------------------------------------------------------------- |
| paste(text) | inserts the specified text at the caret position | | paste(text: str) | inserts the specified text at the caret position |
| lineAt(pos: int) -> int | determines the line number by position in the text |
| linePos(line: int) -> int | determines the position of the beginning of the line in the text |
## Slider (trackbar) ## Slider (trackbar)

View File

@ -72,17 +72,20 @@ Fragments used by the generator must present in the directory:
## Structures ## Structures
A structure is a set of rules for inserting a fragment into the world by the generator. It currently has no properties, being created as empty objects in the `generators/generator_name.files/structures.toml` file. Example: A structure is a set of rules for inserting a fragment into the world by the generator. Structures are declared as objects in the file `generators/generator_name.files/structures.toml`. Example:
```toml ```toml
tree0 = {} tree0 = {}
tree1 = {} tree1 = {}
tree2 = {} tree2 = {}
tower = {} tower = {lowering=2}
coal_ore0 = {} coal_ore0 = {}
``` ```
Currently, the name of the structure must match the name of the fragment used. Currently, the name of the structure must match the name of the fragment used.
Available properties:
- lowering - depth of structure lowering.
## Biomes ## Biomes
A biome defines what blocks and layers the terrain is generated from, as well as a set of plants and structures. A biome defines what blocks and layers the terrain is generated from, as well as a set of plants and structures.

View File

@ -56,7 +56,8 @@ Buttons and panels are also containers.
- `padding` - element padding. Type: 4D vector. - `padding` - element padding. Type: 4D vector.
*left, top, right, bottom* *left, top, right, bottom*
`scrollable` - element scrollability. Works on panels only. Type: boolean - `scrollable` - element scrollability. Type: boolean.
- `scroll-step` - scrolling step. Type: integer.
# Common *panel* attributes # Common *panel* attributes
@ -104,7 +105,9 @@ Inner text - initially entered text
- `multiline` - allows display of multiline text. - `multiline` - allows display of multiline text.
- `text-wrap` - allows automatic text wrapping (works only with multiline: "true") - `text-wrap` - allows automatic text wrapping (works only with multiline: "true")
- `editable` - determines whether the text can be edited. - `editable` - determines whether the text can be edited.
- `line-numbers` - enables line numbers display.
- `error-color` - color when entering incorrect data (the text does not pass the validator check). Type: RGBA color. - `error-color` - color when entering incorrect data (the text does not pass the validator check). Type: RGBA color.
- `text-color` - text color. Type: RGBA color.
- `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct. - `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct.
- `onup` - lua function called when the up arrow is pressed. - `onup` - lua function called when the up arrow is pressed.
- `ondown` - lua function called when the down arrow is pressed. - `ondown` - lua function called when the down arrow is pressed.

View File

@ -40,6 +40,12 @@
Целое число определяющее номер группы отрисовки данного блока. Целое число определяющее номер группы отрисовки данного блока.
Актуально для полупрозрачных блоков - решает проблемы невидимых сторон блоков за этим блоком. Актуально для полупрозрачных блоков - решает проблемы невидимых сторон блоков за этим блоком.
### Полупрозрачность - *translucent*
Включает поддержку полупрозрачности в текстурах блока (примеры: вода, лёд).
Следует использовать только при надобности, так как влияет на производительность.
Не требуется для полной прозрачности (трава, цветы).
### Вращение - *rotation* ### Вращение - *rotation*
Профиль вращения (набор положений, в которые можно установить блок) из списка: Профиль вращения (набор положений, в которые можно установить блок) из списка:

View File

@ -22,6 +22,8 @@
} }
``` ```
Вместо `creator` можно указать массив `creators`
Уровни зависимостей указываются с помощью префиксов в имени: Уровни зависимостей указываются с помощью префиксов в имени:
- '!' - обязательная зависимость - '!' - обязательная зависимость
- '?' - опциональная зависимость - '?' - опциональная зависимость

View File

@ -1,6 +1,8 @@
# Документация # Документация
Документация движка версии 0.24. Документация движка разрабатываемой версии 0.25.
[Документация стабильной версии 0.24.x.](https://github.com/MihailRis/VoxelEngine-Cpp/blob/release-0.24/doc/ru/main-page.md)
## Разделы ## Разделы

View File

@ -7,6 +7,10 @@ hud.open_inventory()
-- Закрывает инвентарь. -- Закрывает инвентарь.
hud.close_inventory() hud.close_inventory()
-- Открывает инвентарь и UI.
-- Если не имеет макета UI - бросается исключение.
hud.open(invid: int, layoutid: str)
-- Открывает инвентарь и UI блока. -- Открывает инвентарь и UI блока.
-- Если блок не имеет макета UI - бросается исключение. -- Если блок не имеет макета UI - бросается исключение.
-- Возвращает id инвентаря блока -- Возвращает id инвентаря блока

View File

@ -23,7 +23,7 @@ inventory.set(
count: int count: int
) )
-- Возращает размер инвентаря (число слотов). -- Возвращает размер инвентаря (число слотов).
-- Если указанного инвентаря не существует, бросает исключение. -- Если указанного инвентаря не существует, бросает исключение.
inventory.size(invid: int) -> int inventory.size(invid: int) -> int
@ -47,12 +47,18 @@ inventory.bind_block(invid: int, x: int, y: int, z: int)
-- Отвязывает инвентарь от блока. -- Отвязывает инвентарь от блока.
inventory.unbind_block(x: int, y: int, z: int) inventory.unbind_block(x: int, y: int, z: int)
-- Удаляет инвентарь.
inventory.remove(invid: int)
``` ```
> [!WARNING] > [!WARNING]
> Инвентари, не привязанные ни к одному из блоков, удаляются при выходе из мира. > Инвентари, не привязанные ни к одному из блоков, удаляются при выходе из мира.
```lua ```lua
-- Создаёт инвентарь и возвращает id.
inventory.create(size: int) -> int
-- Создает копию инвентаря и возвращает id копии. -- Создает копию инвентаря и возвращает id копии.
-- Если копируемого инвентаря не существует, возвращает 0. -- Если копируемого инвентаря не существует, возвращает 0.
inventory.clone(invid: int) -> int inventory.clone(invid: int) -> int
@ -62,5 +68,3 @@ inventory.clone(invid: int) -> int
-- slotB будет выбран автоматически, если не указывать явно. -- slotB будет выбран автоматически, если не указывать явно.
inventory.move(invA: int, slotA: int, invB: int, slotB: int) inventory.move(invA: int, slotA: int, invB: int, slotB: int)
``` ```

View File

@ -27,4 +27,7 @@ utf8.upper(text: str) -> str
-- Переводит строку в нижний регистр -- Переводит строку в нижний регистр
utf8.lower(text: str) -> str utf8.lower(text: str) -> str
-- Экранирует строку
utf8.escape(text: str) -> str
``` ```

View File

@ -4,92 +4,109 @@
## Расширения для table ## Расширения для table
Создаёт и возвращает копию переданной таблицы путём создания новой и копирования в неё всех элементов из переданной
```lua ```lua
function table.copy(t: table) -> table table.copy(t: table) -> table
``` ```
Возвращает количество пар в переданной таблице Создаёт и возвращает копию переданной таблицы путём создания новой и копирования в неё всех элементов из переданной.
```lua ```lua
function table.count_pairs(t: table) -> integer table.count_pairs(t: table) -> integer
``` ```
Возвращает один элемент из переданной таблицы на случайной позиции Возвращает количество пар в переданной таблице.
```lua ```lua
function table.random(t: table) -> object table.random(t: table) -> object
``` ```
Возвращает **true**, если **x** содержится в **t** Возвращает один элемент из переданной таблицы на случайной позиции.
```lua ```lua
function table.has(t: table, x: object) -> bool table.has(t: table, x: object) -> bool
``` ```
Возвращает индекс обьекта **x** в **t**. Если переданный обьект не содержится в таблице, то функция вернёт значение **-1** Возвращает **true**, если **x** содержится в **t**.
```lua ```lua
function table.index(t: table, x: object) -> integer table.index(t: table, x: object) -> integer
``` ```
Удаляет элемент **x** из **t** Возвращает индекс обьекта **x** в **t**. Если переданный обьект не содержится в таблице, то функция вернёт значение **-1**.
```lua ```lua
function table.remove_value(t: table, x: object) table.remove_value(t: table, x: object)
``` ```
Конвертирует переданную таблицу в строку Удаляет элемент **x** из **t**.
```lua ```lua
function table.tostring(t: table) -> string table.tostring(t: table) -> string
``` ```
Конвертирует переданную таблицу в строку.
## Расширения для string ## Расширения для string
Разбивает строку **str** на части по указанному разделителю/выражению **separator** и возвращает результат ввиде таблицы из строк. Если **withpattern** равен **true**, то параметр **separator** будет определяться как регулярное выражение
```lua ```lua
function string.explode(separator: string, str: string, withpattern: bool) -> table[string] string.explode(separator: string, str: string, withpattern: bool) -> table[string]
``` ```
Разбивает строку **str** на части по указанному разделителю **delimiter** и возвращает результат ввиде таблицы из строк Разбивает строку **str** на части по указанному разделителю/выражению **separator** и возвращает результат ввиде таблицы из строк. Если **withpattern** равен **true**, то параметр **separator** будет определяться как регулярное выражение.
```lua ```lua
function string.split(str: string, delimiter: string) -> table[string] string.split(str: string, delimiter: string) -> table[string]
``` ```
Экранирует специальные символы в строке, такие как `()[]+-.$%^?*` в формате `%символ`. Символ `NUL` (`\0`) будет преобразован в `%z` Разбивает строку **str** на части по указанному разделителю **delimiter** и возвращает результат ввиде таблицы из строк.
```lua ```lua
function string.pattern_safe(str: string) string.pattern_safe(str: string)
``` ```
Разбивает секунды на часы, минуты и миллисекунды и форматирует в **format** с следующим порядком параметров: `минуты, секунды, миллисекунды` и после возвращает результат. Если **format** не указан, то возвращает таблицу, где: **h** - hours, **m** - minutes, **s** - seconds, **ms** - milliseconds Экранирует специальные символы в строке, такие как `()[]+-.$%^?*` в формате `%символ`. Символ `NUL` (`\0`) будет преобразован в `%z`.
```lua ```lua
function string.formatted_time(seconds: number, format: string) -> string | table string.formatted_time(seconds: number, format: string) -> string | table
``` ```
Заменяет все подстроки в **str**, равные **tofind** на **toreplace** и возвращает строку со всеми измененными подстроками Разбивает секунды на часы, минуты и миллисекунды и форматирует в **format** с следующим порядком параметров: `минуты, секунды, миллисекунды` и после возвращает результат. Если **format** не указан, то возвращает таблицу, где: **h** - hours, **m** - minutes, **s** - seconds, **ms** - milliseconds.
```lua ```lua
function string.replace(str: string, tofind: string, toreplace: string) -> string string.replace(str: string, tofind: string, toreplace: string) -> string
```
Заменяет все подстроки в **str**, равные **tofind** на **toreplace** и возвращает строку со всеми измененными подстроками.
```lua
string.trim(str: string, char: string) -> string
``` ```
Удаляет все символы, равные **char** из строки **str** с левого и правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы. Удаляет все символы, равные **char** из строки **str** с левого и правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua ```lua
function string.trim(str: string, char: string) -> string string.trim_left(str: string, char: string) -> string
``` ```
Удаляет все символы, равные **char** из строки **str** с левого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы. Удаляет все символы, равные **char** из строки **str** с левого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua ```lua
function string.trim_left(str: string, char: string) -> string string.trim_right(str: string, char: string) -> string
``` ```
Удаляет все символы, равные **char** из строки **str** с правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы. Удаляет все символы, равные **char** из строки **str** с правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua ```lua
function string.trim_right(str: string, char: string) -> string string.starts_with(str: string, start: string) -> bool
``` ```
Возвращает **true**, если строка **str** начинается на подстроку **start** Возвращает **true**, если строка **str** начинается на подстроку **start**
```lua ```lua
function string.starts_with(str: string, start: string) -> bool string.ends_with(str: string, endStr: string) -> bool
``` ```
Возвращает **true**, если строка **str** заканчивается на подстроку **endStr** Возвращает **true**, если строка **str** заканчивается на подстроку **endStr**
```lua
function string.ends_with(str: string, endStr: string) -> bool
```
Также важно подметить, что все выше перечисленные функции, расширяющие **string** можно использовать как мета-методы на экземплярах строк, т.е.: Также важно подметить, что все выше перечисленные функции, расширяющие **string** можно использовать как мета-методы на экземплярах строк, т.е.:
@ -103,39 +120,51 @@ end
Также функции `string.lower` и `string.upper` переопределены на `utf8.lower` и `utf8.upper` Также функции `string.lower` и `string.upper` переопределены на `utf8.lower` и `utf8.upper`
```lua
string.escape(str: string) -> string
```
Экранирует строку. Является псевдонимом `utf8.escape`.
## Расширения для math ## Расширения для math
Ограничивает число **_in** по лимитам **low** и **high**. Т.е.: Если **_in** больше чем **high** - вернётся **high**, если **_in** меньше чем **low** - вернётся **low**. В противном случае вернётся само число
```lua ```lua
function math.clamp(_in, low, high) math.clamp(_in, low, high)
``` ```
Возвращает случайное дробное число в диапазоне от **low** до **high** Ограничивает число **_in** по лимитам **low** и **high**. Т.е.: Если **_in** больше чем **high** - вернётся **high**, если **_in** меньше чем **low** - вернётся **low**. В противном случае вернётся само число.
```lua ```lua
function math.rand(low, high) math.rand(low, high)
``` ```
Возвращает случайное дробное число в диапазоне от **low** до **high**.
## Дополнительные глобальные функции ## Дополнительные глобальные функции
В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список
Возвращает **true**, если переданная таблица является массивом, тоесть если каждый ключ это целое число больше или равное единице и если каждый ключ следует за прошлым
```lua ```lua
function is_array(x: table) -> bool is_array(x: table) -> bool
``` ```
Разбивает путь на две части и возвращает их: входную точку и путь к файлу Возвращает **true**, если переданная таблица является массивом, тоесть если каждый ключ это целое число больше или равное единице и если каждый ключ следует за прошлым.
```lua ```lua
function parse_path(path: string) -> string, string function parse_path(path: string) -> string, string
``` ```
Вызывает функцию **func** **iters** раз, передавая ей аргументы `...`, а после выводит в консоль время в микросекундах, которое прошло с момента вызова **timeit** Разбивает путь на две части и возвращает их: входную точку и путь к файлу.
```lua ```lua
function timeit(iters: integer, func: func, ...) function timeit(iters: integer, func: func, ...)
``` ```
Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины Вызывает функцию **func** **iters** раз, передавая ей аргументы `...`, а после выводит в консоль время в микросекундах, которое прошло с момента вызова **timeit**.
```lua ```lua
function sleep(timesec: number) function sleep(timesec: number)
``` ```
Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины.

View File

@ -74,17 +74,22 @@ document["worlds-panel"]:clear()
| ----------- | ------ | ------ | ------ | ---------------------------------------------------------------------- | | ----------- | ------ | ------ | ------ | ---------------------------------------------------------------------- |
| text | string | да | да | введенный текст или заполнитель | | text | string | да | да | введенный текст или заполнитель |
| placeholder | string | да | да | заполнитель (используется если ничего не было введено) | | placeholder | string | да | да | заполнитель (используется если ничего не было введено) |
| hint | string | да | да | текст, отображаемый, когда ничего не введено |
| caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста | | caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста |
| editable | bool | да | да | изменяемость текста | | editable | bool | да | да | изменяемость текста |
| multiline | bool | да | да | поддержка многострочности | | multiline | bool | да | да | поддержка многострочности |
| lineNumbers | bool | да | да | отображение номеров строк |
| textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") | | textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") |
| valid | bool | да | нет | является ли введенный текст корректным | | valid | bool | да | нет | является ли введенный текст корректным |
| textColor | vec4 | да | да | цвет текста |
Методы: Методы:
| Метод | Описание | | Метод | Описание |
| ----------- | -------------------------------------------- | | ------------------------- | -------------------------------------------- |
| paste(text) | вставляет указанный текст на позицию каретки | | paste(text: str) | вставляет указанный текст на позицию каретки |
| lineAt(pos: int) -> int | определяет номер строки по позиции в тексте |
| linePos(line: int) -> int | определяет позицию начала строки в тексте |
## Ползунок (trackbar) ## Ползунок (trackbar)

View File

@ -72,17 +72,20 @@
## Структуры ## Структуры
Структура - набор правил по вставке фрагмента в мир генератором. На данный момент не имеет свойств, создаваясь в виде пустых объектов в файле `generators/имя_генератора.files/structures.toml`. Пример: Структура - набор правил по вставке фрагмента в мир генератором. Структуры объявляются в виде объектов в файле `generators/имя_генератора.files/structures.toml`. Пример:
```toml ```toml
tree0 = {} tree0 = {}
tree1 = {} tree1 = {}
tree2 = {} tree2 = {}
tower = {} tower = {lowering=-2}
coal_ore0 = {} coal_ore0 = {}
``` ```
На данный момент, имя структуры должно совпадать с именем использованного фрагмента. На данный момент, имя структуры должно совпадать с именем использованного фрагмента.
Доступные свойства:
- lowering - глубина погружения структуры под поверхность.
## Биомы ## Биомы
Биом определяет то, из каких блоков и какими слоями генерируется ландшафт, а так же набор растений, структур. Биом определяет то, из каких блоков и какими слоями генерируется ландшафт, а так же набор растений, структур.

View File

@ -59,7 +59,8 @@
В число контейнеров также входят панели и кнопки. В число контейнеров также входят панели и кнопки.
- `padding` - внутренний отступ элемента. Тип: 4D вектор. - `padding` - внутренний отступ элемента. Тип: 4D вектор.
Порядок: `"left,top,right,bottom"` Порядок: `"left,top,right,bottom"`
- `scrollable` - возможность скроллинга. Работает только у Panel. Тип: логический. - `scrollable` - возможность скроллинга. Тип: логический.
- `scroll-step` - шаг скроллинга. Тип: целочисленный.
# Общие атрибуты панелей # Общие атрибуты панелей
@ -105,7 +106,9 @@
- `multiline` - разрешает отображение многострочного текста. - `multiline` - разрешает отображение многострочного текста.
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true") - `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
- `editable`- определяет возможность редактирования текста. - `editable`- определяет возможность редактирования текста.
- `line-numbers` - включает отображение номеров строк.
- `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет. - `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет.
- `text-color` - цвет текста. Тип: RGBA цвет.
- `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен. - `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен.
- `onup` - lua функция вызываемая при нажатии стрелки вверх. - `onup` - lua функция вызываемая при нажатии стрелки вверх.
- `ondown` - lua функция вызываемая при нажатии стрелки вниз. - `ondown` - lua функция вызываемая при нажатии стрелки вниз.

View File

@ -3,5 +3,6 @@
"material": "base:glass", "material": "base:glass",
"draw-group": 2, "draw-group": 2,
"light-passing": true, "light-passing": true,
"sky-light-passing": true "sky-light-passing": true,
"translucent": true
} }

View File

@ -0,0 +1,7 @@
{
"texture": "ice",
"material": "base:glass",
"draw-group": 4,
"light-passing": true,
"translucent": true
}

View File

@ -6,5 +6,6 @@
"sky-light-passing": false, "sky-light-passing": false,
"obstacle": false, "obstacle": false,
"selectable": false, "selectable": false,
"replaceable": true "replaceable": true,
"translucent": true
} }

View File

@ -1,6 +1,8 @@
{ {
"items": [ "entities": [
"bazalt_breaker" "drop",
"player",
"falling_block"
], ],
"blocks": [ "blocks": [
"dirt", "dirt",
@ -27,11 +29,10 @@
"lightbulb", "lightbulb",
"torch", "torch",
"wooden_door", "wooden_door",
"coal_ore" "coal_ore",
"ice"
], ],
"entities": [ "items": [
"drop", "bazalt_breaker"
"player",
"falling_block"
] ]
} }

View File

@ -3,5 +3,5 @@
"base:falling_block" "base:falling_block"
], ],
"skeleton-name": "base:block", "skeleton-name": "base:block",
"hitbox": [0.8, 0.8, 0.8] "hitbox": [0.98, 0.98, 0.98]
} }

View File

@ -1,5 +1,5 @@
tree0 = {} tree0 = {}
tree1 = {} tree1 = {}
tree2 = {} tree2 = {}
tower = {} tower = {lowering=2}
coal_ore0 = {} coal_ore0 = {}

View File

@ -1,6 +1,6 @@
{ {
"id": "base", "id": "base",
"title": "Base", "title": "Base",
"version": "0.24", "version": "0.25",
"description": "basic content package" "description": "basic content package"
} }

View File

@ -1,3 +1,4 @@
bazalt breaker=крушитель базальта
bazalt=базальт bazalt=базальт
blue lamp=синяя лампа blue lamp=синяя лампа
brick=кирпич brick=кирпич
@ -5,8 +6,8 @@ dirt=земля
flower=цветок flower=цветок
glass=стекло glass=стекло
grass block=дёрн grass block=дёрн
tall grass=высокая трава
green lamp=зелёная лампа green lamp=зелёная лампа
ice=лёд
lamp=лампа lamp=лампа
leaves=листва leaves=листва
light bulb=лампочка light bulb=лампочка
@ -18,8 +19,8 @@ red lamp=красная лампа
rust=ржавчина rust=ржавчина
sand=песок sand=песок
stone=камень stone=камень
tall grass=высокая трава
torch=факел
water=вода water=вода
wood=бревно wood=бревно
torch=факел
bazalt breaker=крушитель базальта
wooden door=деревянная дверь wooden door=деревянная дверь

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,5 +1,18 @@
<container color='#00000080' size='400' size-func="unpack(gui.get_viewport())"> <container color='#00000080' size='400' size-func="unpack(gui.get_viewport())">
<container size-func="gui.get_viewport()[1],gui.get_viewport()[2]-40"> <panel interval="0"
orientation="horizontal"
color="#00000010"
size-func="gui.get_viewport()[1]-350,30">
<button id="s_chat" size="110,30" onclick="modes:set('chat')">@Chat</button>
<button id="s_console" size="110,30" onclick="modes:set('console')">@Console</button>
<button id="s_debug" size="110,30" onclick="modes:set('debug')">@Debug</button>
</panel>
<container pos="0,30" size-func="gui.get_viewport()[1]-350,30" color="#00000020">
<label id="title" pos="8,8"></label>
</container>
<container id="logContainer" pos="0,60"
size-func="unpack(vec2.add(gui.get_viewport(), {0,-100}))">
<textbox <textbox
id='log' id='log'
color='0' color='0'
@ -11,12 +24,33 @@
gravity="bottom-left" gravity="bottom-left"
></textbox> ></textbox>
</container> </container>
<container id="editorContainer" pos="0,60" color="#00000080"
size-func="unpack(vec2.add(gui.get_viewport(), {-350,-230}))">
<textbox
id='editor'
color='0'
autoresize='true'
margin='0'
padding='5'
editable='false'
multiline='true'
line-numbers='true'
text-color="#FFFFFFA0"
size-func="gui.get_viewport()[1]-350,40"
gravity="top-left"
text-wrap='false'
scroll-step='50'
></textbox>
</container>
<panel id="traceback" gravity="bottom-left" padding="4" color="#000000A0"
max-length="170" size-func="gui.get_viewport()[1]-350,170">
</panel>
<panel id="problemsLog" <panel id="problemsLog"
color="#00000010" color="#00000010"
position-func="gui.get_viewport()[1]-350,0" position-func="gui.get_viewport()[1]-350,0"
size-func="351,gui.get_viewport()[2]-40" size-func="350,gui.get_viewport()[2]-40"
padding="5,15,5,15"> padding="5,15,5,15">
<label>@Problems</label> <label margin="0,0,0,5">@Problems</label>
</panel> </panel>
<textbox id='prompt' <textbox id='prompt'
consumer='submit' consumer='submit'

View File

@ -1,21 +1,101 @@
console_mode = "console"
history = session.get_entry("commands_history") history = session.get_entry("commands_history")
history_pointer = #history history_pointer = #history
local warnings_all = {} local warnings_all = {}
local errors_all = {}
local warning_id = 0 local warning_id = 0
events.on("core:warning", function (wtype, text) local error_id = 0
events.on("core:warning", function (wtype, text, traceback)
local full = wtype..": "..text local full = wtype..": "..text
if table.has(warnings_all, full) then if table.has(warnings_all, full) then
return return
end end
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
document.problemsLog:add(gui.template("problem", { document.problemsLog:add(gui.template("problem", {
type="warning", text=full, id=tostring(warning_id) type="warning",
text=full,
traceback=encoded,
id=tostring(warning_id)
})) }))
warning_id = warning_id + 1 warning_id = warning_id + 1
table.insert(warnings_all, full) table.insert(warnings_all, full)
end) end)
events.on("core:error", function (msg, traceback)
local _, endindex = string.find(msg, ": ")
local full = ""
for i,frame in ipairs(traceback) do
full = full..frame.source..tostring(frame.currentline)
end
if table.has(errors_all, full) then
return
end
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
document.problemsLog:add(gui.template("problem", {
type="error",
text=msg:sub(endindex),
traceback=encoded,
id=tostring(error_id)
}))
error_id = error_id + 1
table.insert(errors_all, full)
end)
events.on("core:open_traceback", function(traceback_b64)
local traceback = bjson.frombytes(base64.decode(traceback_b64))
modes:set('debug')
local tb_list = document.traceback
local srcsize = tb_list.size
tb_list:clear()
tb_list:add("<label enabled='false' margin='2'>@devtools.traceback</label>")
for _, frame in ipairs(traceback.frames) do
local callback = ""
local framestr = ""
if frame.what == "C" then
framestr = "C/C++ "
else
framestr = frame.source..":"..tostring(frame.currentline).." "
if file.exists(frame.source) then
callback = string.format(
"local editor = document.editor "..
"local source = file.read('%s'):gsub('\t', ' ') "..
"editor.text = source "..
"editor.focused = true "..
"time.post_runnable(function()"..
"editor.caret = editor:linePos(%s) "..
"end)",
frame.source, frame.currentline-1
)
else
callback = "document.editor.text = 'Could not open source file'"
end
callback = string.format(
"%s document.title.text = gui.str('File')..' - %s'",
callback,
frame.source
)
end
if frame.name then
framestr = framestr.."("..tostring(frame.name)..")"
end
local color = "#FFFFFF"
if frame.source:starts_with("core:") then
color = "#C0D0C5"
end
tb_list:add(gui.template("stack_frame", {
location=framestr,
color=color,
callback=callback
}))
end
tb_list.size = srcsize
end)
function setup_variables() function setup_variables()
local pid = hud.get_player() local pid = hud.get_player()
local x,y,z = player.get_pos(pid) local x,y,z = player.get_pos(pid)
@ -56,10 +136,19 @@ function add_to_history(text)
end end
function submit(text) function submit(text)
text = text:trim()
add_to_history(text) add_to_history(text)
if console_mode == "chat" then
if not text:starts_with("/") then
text = "chat "..string.escape(text)
else
text = text:sub(2)
end
end
setup_variables() setup_variables()
text = text:trim()
local name local name
for s in text:gmatch("%S+") do for s in text:gmatch("%S+") do
name = s name = s
@ -84,6 +173,38 @@ function submit(text)
document.prompt.focused = true document.prompt.focused = true
end end
function on_open() function set_mode(mode)
local show_prompt = mode == 'chat' or mode == 'console'
document.title.text = ""
document.editorContainer.visible = mode == 'debug'
document.logContainer.visible = mode ~= 'debug'
if mode == 'debug' then
document.root.color = {16, 18, 20, 220}
else
document.root.color = {0, 0, 0, 128}
end
document.traceback.visible = mode == 'debug'
document.prompt.visible = show_prompt
if show_prompt then
document.prompt.focused = true document.prompt.focused = true
end end
console_mode = mode
end
function on_open(mode)
if modes == nil then
modes = RadioGroup({
chat=document.s_chat,
console=document.s_console,
debug=document.s_debug
}, function (mode)
set_mode(mode)
end, "console")
end
if mode then
modes:set(mode)
end
end

View File

@ -1,9 +1,13 @@
<container size='1000,480' color='#0F1E2DB2' padding='8' interval='5' context='menu'> <container size='1000,580' color='#0F1E2DB2' interval='5' context='menu'>
<panel id='contents' pos='15,15' size='440,390' color='0' max-length='406' scrollable='true'> <panel id='contents' pos='15,15' size='440,490' color='0' max-length='455' scrollable='true'>
<!-- content is generated in script --> <!-- content is generated in script -->
</panel> </panel>
<button pos='15,430' size='440,40' onclick='menu:back()'>@Back</button> <button pos='15,525' size='440,40' onclick='menu:back()'>@Back</button>
<button pos='485,430' size='500,40' onclick='core.open_folder("user:content")'>@Open content folder</button> <button pos='485,525' size='500,40' onclick='core.open_folder("user:content")'>@Open content folder</button>
<panel id='search_panel' size='440,35' pos='15,485' interval='1' color='#0000004C'>
<textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox>
</panel>
<panel id='content_info' pos='485,15' size='440,406' color='0' max-length='406' scrollable='true'> <panel id='content_info' pos='485,15' size='440,406' color='0' max-length='406' scrollable='true'>
<label>@Creator</label> <label>@Creator</label>

View File

@ -1,3 +1,5 @@
local packs_installed = {}
function on_open(params) function on_open(params)
refresh() refresh()
end end
@ -13,10 +15,32 @@ function place_pack(panel, packinfo, callback)
end end
packinfo.callback = callback packinfo.callback = callback
panel:add(gui.template("pack", packinfo)) panel:add(gui.template("pack", packinfo))
if not callback then end
document["pack_"..packinfo.id].enabled = false
function refresh_search()
local search_text = document.search_textbox.text:lower()
local visible = 0
local interval = 4
local step = -1
for i, v in ipairs(packs_installed) do
local id = v[1]
local title = v[2]
local content = document["pack_" .. id]
local pos = content.pos
local size = content.size
if title:lower():find(search_text) or search_text == '' then
content.enabled = true
content.pos = {pos[1], visible * (size[2] + interval) - step}
visible = visible + 1
else
content.enabled = false
content.pos = {pos[1], (visible + #packs_installed - i) * (size[2] + interval) - step}
end end
end end
end
function open_pack(id) function open_pack(id)
local packinfo = pack.get_info(id) local packinfo = pack.get_info(id)
@ -28,8 +52,8 @@ function open_pack(id)
end end
function refresh() function refresh()
local packs_installed = pack.get_installed()
local packs_available = pack.get_available() local packs_available = pack.get_available()
packs_installed = pack.get_installed()
for i,k in ipairs(packs_available) do for i,k in ipairs(packs_available) do
table.insert(packs_installed, k) table.insert(packs_installed, k)
@ -41,7 +65,8 @@ function refresh()
for i,id in ipairs(packs_installed) do for i,id in ipairs(packs_installed) do
local packinfo = pack.get_info(id) local packinfo = pack.get_info(id)
packinfo.index = i packinfo.id = id
packs_installed[i] = {packinfo.id, packinfo.title}
local callback = string.format('open_pack("%s")', id) local callback = string.format('open_pack("%s")', id)
place_pack(contents, packinfo, callback) place_pack(contents, packinfo, callback)
end end

View File

@ -1,9 +1,9 @@
<container size='668,418' color='#0F1E2DB2' context='menu'> <container size='668,418' color='#0F1E2DB2' context='menu'>
<panel pos='6' size='250' color='0' interval='1'> <panel pos='6' size='250' color='0' interval='1'>
<button id='s_aud' onclick='set_page("s_aud", "settings_audio")'>@Audio</button> <button id='s_aud' onclick='sections:set("audio")'>@Audio</button>
<button id='s_dsp' onclick='set_page("s_dsp", "settings_display")'>@Display</button> <button id='s_dsp' onclick='sections:set("display")'>@Display</button>
<button id='s_gfx' onclick='set_page("s_gfx", "settings_graphics")'>@Graphics</button> <button id='s_gfx' onclick='sections:set("graphics")'>@Graphics</button>
<button id='s_ctl' onclick='set_page("s_ctl", "settings_controls")'>@Controls</button> <button id='s_ctl' onclick='sections:set("controls")'>@Controls</button>
</panel> </panel>
<pagebox id='menu' pos='260,6' size='400'> <pagebox id='menu' pos='260,6' size='400'>
</pagebox> </pagebox>
@ -11,7 +11,7 @@
<panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'> <panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'>
<button onclick='menu.page="languages"' id='langs_btn'>-</button> <button onclick='menu.page="languages"' id='langs_btn'>-</button>
<button onclick='core.open_folder("user:")'>@Open data folder</button> <button onclick='core.open_folder("user:")'>@Open data folder</button>
<button id='s_rst' onclick='set_page("s_rst", "settings_reset")'>@Reset settings</button> <button id='s_rst' onclick='sections:set("reset")'>@Reset settings</button>
<button onclick='menu:back()'>@Back</button> <button onclick='menu:back()'>@Back</button>
</panel> </panel>
</container> </container>

View File

@ -3,15 +3,13 @@ function on_open()
"%s: %s", gui.str("Language", "settings"), "%s: %s", gui.str("Language", "settings"),
gui.get_locales_info()[core.get_setting("ui.language")].name gui.get_locales_info()[core.get_setting("ui.language")].name
) )
set_page("s_gfx", "settings_graphics") sections = RadioGroup({
end audio=document.s_aud,
display=document.s_dsp,
function set_page(btn, page) graphics=document.s_gfx,
document.s_aud.enabled = true controls=document.s_ctl,
document.s_dsp.enabled = true reset=document.s_rst
document.s_gfx.enabled = true }, function (page)
document.s_ctl.enabled = true document.menu.page = "settings_"..page
document.s_rst.enabled = true end, "graphics")
document[btn].enabled = false
document.menu.page = page
end end

View File

@ -1,4 +1,5 @@
<container id="%{id}" size="32" tooltip="%{text}"> <container id="%{id}" size="32" tooltip="%{text}"
onclick="events.emit('core:open_traceback', '%{traceback}')">
<image src="gui/%{type}" size="32"/> <image src="gui/%{type}" size="32"/>
<label pos="36,2">%{text}</label> <label pos="36,2">%{text}</label>
<image src="gui/cross" interactive="true" size="16" gravity="top-right" <image src="gui/cross" interactive="true" size="16" gravity="top-right"

View File

@ -0,0 +1,3 @@
<label hover-color="#A0A0FF" interactive="true" onclick="%{callback}" color="%{color}">
%{location}
</label>

View File

@ -0,0 +1,18 @@
local Text3D = {__index={
hide=function(self) return gfx.text3d.hide(self.id) end,
get_pos=function(self) return gfx.text3d.get_pos(self.id) end,
set_pos=function(self, v) return gfx.text3d.set_pos(self.id, v) end,
get_axis_x=function(self) return gfx.text3d.get_axis_x(self.id) end,
set_axis_x=function(self, v) return gfx.text3d.set_axis_x(self.id, v) end,
get_axis_y=function(self) return gfx.text3d.get_axis_y(self.id) end,
set_axis_y=function(self, v) return gfx.text3d.set_axis_y(self.id, v) end,
set_rotation=function(self, m) return gfx.text3d.set_rotation(self.id, m) end,
get_text=function(self) return gfx.text3d.get_text(self.id) end,
set_text=function(self, s) return gfx.text3d.set_text(self.id, s) end,
update_settings=function(self, t) return gfx.text3d.update_settings(self.id, t) end,
}}
gfx.text3d.new = function(pos, text, preset, extension)
local id = gfx.text3d.show(pos, text, preset, extension)
return setmetatable({id=id}, Text3D)
end

View File

@ -256,6 +256,14 @@ console.add_command(
end end
) )
console.add_command(
"chat text:str",
"Send chat message",
function (args, kwargs)
console.log("[you] "..args[1])
end
)
console.cheats = { console.cheats = {
"blocks.fill", "blocks.fill",
"tp", "tp",

View File

@ -84,6 +84,32 @@ function Document.new(docname)
}) })
end end
local _RadioGroup = {}
function _RadioGroup.set(self, key)
if type(self) ~= 'table' then
error("called as non-OOP via '.', use radiogroup:set")
end
if self.current then
self.elements[self.current].enabled = true
end
self.elements[key].enabled = false
self.current = key
if self.callback then
self.callback(key)
end
end
function _RadioGroup.__call(self, elements, onset, default)
local group = setmetatable({
elements=elements,
callback=onset,
current=nil
}, {__index=_RadioGroup})
group:set(default)
return group
end
setmetatable(_RadioGroup, _RadioGroup)
RadioGroup = _RadioGroup
_GUI_ROOT = Document.new("core:root") _GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu _MENU = _GUI_ROOT.menu
menu = _MENU menu = _MENU

View File

@ -162,6 +162,7 @@ end
string.lower = utf8.lower string.lower = utf8.lower
string.upper = utf8.upper string.upper = utf8.upper
string.escape = utf8.escape
local meta = getmetatable("") local meta = getmetatable("")
@ -227,6 +228,20 @@ function file.readlines(path)
return lines return lines
end end
function debug.get_traceback(start)
local frames = {}
local n = 2 + (start or 0)
while true do
local info = debug.getinfo(n)
if info then
table.insert(frames, info)
else
return frames
end
n = n + 1
end
end
package = { package = {
loaded = {} loaded = {}
} }
@ -238,7 +253,7 @@ function on_deprecated_call(name, alternatives)
return return
end end
__warnings_hidden[name] = true __warnings_hidden[name] = true
events.emit("core:warning", "deprecated call", name) events.emit("core:warning", "deprecated call", name, debug.get_traceback(2))
if alternatives then if alternatives then
debug.warning("deprecated function called ("..name.."), use ".. debug.warning("deprecated function called ("..name.."), use "..
alternatives.." instead\n"..debug.traceback()) alternatives.." instead\n"..debug.traceback())
@ -292,3 +307,17 @@ function __scripts_cleanup()
end end
end end
end end
function __vc__error(msg, frame)
if events then
events.emit("core:error", msg, debug.get_traceback(1))
end
return debug.traceback(msg, frame)
end
function __vc_warning(msg, detail, n)
if events then
events.emit(
"core:warning", msg, detail, debug.get_traceback(1 + (n or 0)))
end
end

View File

@ -9,14 +9,14 @@ uniform samplerCube u_cubemap;
uniform vec3 u_fogColor; uniform vec3 u_fogColor;
uniform float u_fogFactor; uniform float u_fogFactor;
uniform float u_fogCurve; uniform float u_fogCurve;
uniform bool u_alphaClip;
void main() { void main() {
vec3 fogColor = texture(u_cubemap, a_dir).rgb; vec3 fogColor = texture(u_cubemap, a_dir).rgb;
vec4 tex_color = texture(u_texture0, a_texCoord); vec4 tex_color = texture(u_texture0, a_texCoord);
float depth = (a_distance/256.0); float depth = (a_distance/256.0);
float alpha = a_color.a * tex_color.a; float alpha = a_color.a * tex_color.a;
// anyway it's any alpha-test alternative required if (u_alphaClip && alpha < 0.9f)
if (alpha < 0.3f)
discard; discard;
f_color = mix(a_color * tex_color, vec4(fogColor,1.0), f_color = mix(a_color * tex_color, vec4(fogColor,1.0),
min(1.0, pow(depth*u_fogFactor, u_fogCurve))); min(1.0, pow(depth*u_fogFactor, u_fogCurve)));

View File

@ -11,6 +11,8 @@ world.delete-confirm=Do you want to delete world forever?
world.generators.default=Default world.generators.default=Default
world.generators.flat=Flat world.generators.flat=Flat
devtools.traceback=Traceback (most recent call first)
# Tooltips # Tooltips
graphics.gamma.tooltip=Lighting brightness curve graphics.gamma.tooltip=Lighting brightness curve
graphics.backlight.tooltip=Backlight to prevent total darkness graphics.backlight.tooltip=Backlight to prevent total darkness

View File

@ -12,7 +12,15 @@ Dependencies=Зависимости
Description=Описание Description=Описание
Converting world...=Выполняется конвертация мира... Converting world...=Выполняется конвертация мира...
Unlimited=Неограниченно Unlimited=Неограниченно
Chat=Чат
Console=Консоль
Log=Лог
Problems=Проблемы
Monitor=Мониторинг
Debug=Отладка
File=Файл
devtools.traceback=Стек вызовов (от последнего)
error.pack-not-found=Не удалось найти пакет error.pack-not-found=Не удалось найти пакет
error.dependency-not-found=Используемая зависимость не найдена error.dependency-not-found=Используемая зависимость не найдена
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?

View File

@ -32,7 +32,7 @@ static debug::Logger logger("assetload-funcs");
namespace fs = std::filesystem; namespace fs = std::filesystem;
static bool animation( static bool load_animation(
Assets* assets, Assets* assets,
const ResPaths* paths, const ResPaths* paths,
const std::string& atlasName, const std::string& atlasName,
@ -102,8 +102,13 @@ static bool append_atlas(AtlasBuilder& atlas, const fs::path& file) {
return true; return true;
} }
assetload::postfunc assetload:: assetload::postfunc assetload::atlas(
atlas(AssetsLoader*, const ResPaths* paths, const std::string& directory, const std::string& name, const std::shared_ptr<AssetCfg>&) { AssetsLoader*,
const ResPaths* paths,
const std::string& directory,
const std::string& name,
const std::shared_ptr<AssetCfg>&
) {
AtlasBuilder builder; AtlasBuilder builder;
for (const auto& file : paths->listdir(directory)) { for (const auto& file : paths->listdir(directory)) {
if (!imageio::is_read_supported(file.extension().u8string())) continue; if (!imageio::is_read_supported(file.extension().u8string())) continue;
@ -115,24 +120,41 @@ assetload::postfunc assetload::
atlas->prepare(); atlas->prepare();
assets->store(std::unique_ptr<Atlas>(atlas), name); assets->store(std::unique_ptr<Atlas>(atlas), name);
for (const auto& file : names) { for (const auto& file : names) {
animation(assets, paths, name, directory, file, atlas); load_animation(assets, paths, name, directory, file, atlas);
} }
}; };
} }
assetload::postfunc assetload:: assetload::postfunc assetload::font(
font(AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr<AssetCfg>&) { AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>&
) {
auto pages = std::make_shared<std::vector<std::unique_ptr<ImageData>>>(); auto pages = std::make_shared<std::vector<std::unique_ptr<ImageData>>>();
for (size_t i = 0; i <= 4; i++) { for (size_t i = 0; i <= 1024; i++) {
std::string pagefile = filename + "_" + std::to_string(i) + ".png"; std::string pagefile = filename + "_" + std::to_string(i) + ".png";
pagefile = paths->find(pagefile).string(); auto file = paths->find(pagefile);
pages->push_back(imageio::read(pagefile)); if (fs::exists(file)) {
pages->push_back(imageio::read(file.u8string()));
} else if (i == 0) {
throw std::runtime_error("font must have page 0");
} else {
pages->push_back(nullptr);
}
} }
return [=](auto assets) { return [=](auto assets) {
int res = pages->at(0)->getHeight() / 16; int res = pages->at(0)->getHeight() / 16;
std::vector<std::unique_ptr<Texture>> textures; std::vector<std::unique_ptr<Texture>> textures;
for (auto& page : *pages) { for (auto& page : *pages) {
textures.emplace_back(Texture::from(page.get())); if (page == nullptr) {
textures.emplace_back(nullptr);
} else {
auto texture = Texture::from(page.get());
texture->setMipMapping(false);
textures.emplace_back(std::move(texture));
}
} }
assets->store( assets->store(
std::make_unique<Font>(std::move(textures), res, 4), name std::make_unique<Font>(std::move(textures), res, 4), name
@ -150,7 +172,9 @@ assetload::postfunc assetload::layout(
return [=](auto assets) { return [=](auto assets) {
try { try {
auto cfg = std::dynamic_pointer_cast<LayoutCfg>(config); auto cfg = std::dynamic_pointer_cast<LayoutCfg>(config);
assets->store(UiDocument::read(cfg->env, name, file), name); assets->store(
UiDocument::read(cfg->env, name, file, "abs:" + file), name
);
} catch (const parsing_error& err) { } catch (const parsing_error& err) {
throw std::runtime_error( throw std::runtime_error(
"failed to parse layout XML '" + file + "':\n" + err.errorLog() "failed to parse layout XML '" + file + "':\n" + err.errorLog()
@ -316,11 +340,9 @@ static TextureAnimation create_animation(
if (elem.second > 0) { if (elem.second > 0) {
frame.duration = static_cast<float>(elem.second) / 1000.0f; frame.duration = static_cast<float>(elem.second) / 1000.0f;
} }
frame.srcPos = frame.srcPos = glm::ivec2(
glm::ivec2(
region.u1 * srcWidth, srcHeight - region.v2 * srcHeight region.u1 * srcWidth, srcHeight - region.v2 * srcHeight
) - ) - extension;
extension;
animation.addFrame(frame); animation.addFrame(frame);
} }
return animation; return animation;
@ -338,7 +360,7 @@ inline bool contains(
return false; return false;
} }
static bool animation( static bool load_animation(
Assets* assets, Assets* assets,
const ResPaths* paths, const ResPaths* paths,
const std::string& atlasName, const std::string& atlasName,

View File

@ -373,7 +373,7 @@ std::string BasicParser::parseString(char quote, bool closeRequired) {
case 'b': ss << '\b'; break; case 'b': ss << '\b'; break;
case 't': ss << '\t'; break; case 't': ss << '\t'; break;
case 'f': ss << '\f'; break; case 'f': ss << '\f'; break;
case '\'': ss << '\\'; break; case '\'': ss << '\''; break;
case '"': ss << '"'; break; case '"': ss << '"'; break;
case '\\': ss << '\\'; break; case '\\': ss << '\\'; break;
case '/': ss << '/'; break; case '/': ss << '/'; break;

View File

@ -265,5 +265,5 @@ dv::value json::parse(
} }
dv::value json::parse(std::string_view source) { dv::value json::parse(std::string_view source) {
return parse("<string>", source); return parse("[string]", source);
} }

View File

@ -250,7 +250,8 @@ std::string Parser::parseText() {
} }
nextChar(); nextChar();
} }
return std::string(source.substr(start, pos - start)); return Parser("[string]", std::string(source.substr(start, pos - start)))
.parseString('\0', false);
} }
inline bool is_xml_identifier_start(char c) { inline bool is_xml_identifier_start(char c) {
@ -336,7 +337,7 @@ xmldocument Parser::parse() {
return document; return document;
} }
xmldocument xml::parse(const std::string& filename, const std::string& source) { xmldocument xml::parse(std::string_view filename, std::string_view source) {
Parser parser(filename, source); Parser parser(filename, source);
return parser.parse(); return parser.parse();
} }

View File

@ -140,6 +140,6 @@ namespace xml {
/// @param source xml source code string /// @param source xml source code string
/// @return xml document /// @return xml document
extern xmldocument parse( extern xmldocument parse(
const std::string& filename, const std::string& source std::string_view filename, std::string_view source
); );
} }

View File

@ -6,7 +6,7 @@
#include <string> #include <string>
inline constexpr int ENGINE_VERSION_MAJOR = 0; inline constexpr int ENGINE_VERSION_MAJOR = 0;
inline constexpr int ENGINE_VERSION_MINOR = 24; inline constexpr int ENGINE_VERSION_MINOR = 25;
#ifdef NDEBUG #ifdef NDEBUG
inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = false;
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
inline constexpr bool ENGINE_DEBUG_BUILD = true; inline constexpr bool ENGINE_DEBUG_BUILD = true;
#endif // NDEBUG #endif // NDEBUG
inline const std::string ENGINE_VERSION_STRING = "0.24"; inline const std::string ENGINE_VERSION_STRING = "0.25";
/// @brief world regions format version /// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3; inline constexpr uint REGION_FORMAT_VERSION = 3;
@ -35,9 +35,6 @@ inline constexpr int CHUNK_D = 16;
inline constexpr uint VOXEL_USER_BITS = 8; inline constexpr uint VOXEL_USER_BITS = 8;
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;
/// @brief pixel size of an item inventory icon
inline constexpr int ITEM_ICON_SIZE = 48;
/// @brief chunk volume (count of voxels per Chunk) /// @brief chunk volume (count of voxels per Chunk)
inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D); inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D);
@ -53,6 +50,11 @@ inline constexpr uint vox_index(uint x, uint y, uint z, uint w=CHUNK_W, uint d=C
return (y * d + z) * w + x; return (y * d + z) * w + x;
} }
/// @brief pixel size of an item inventory icon
inline constexpr int ITEM_ICON_SIZE = 48;
inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string SHADERS_FOLDER = "shaders";
inline const std::string TEXTURES_FOLDER = "textures"; inline const std::string TEXTURES_FOLDER = "textures";
inline const std::string FONTS_FOLDER = "fonts"; inline const std::string FONTS_FOLDER = "fonts";

View File

@ -333,6 +333,7 @@ void ContentLoader::loadBlock(
root.at("inventory-size").get(def.inventorySize); root.at("inventory-size").get(def.inventorySize);
root.at("tick-interval").get(def.tickInterval); root.at("tick-interval").get(def.tickInterval);
root.at("overlay-texture").get(def.overlayTexture); root.at("overlay-texture").get(def.overlayTexture);
root.at("translucent").get(def.translucent);
if (root.has("fields")) { if (root.has("fields")) {
def.dataStruct = std::make_unique<StructLayout>(); def.dataStruct = std::make_unique<StructLayout>();
@ -484,7 +485,13 @@ void ContentLoader::loadBlock(
auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua"); auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua");
if (fs::is_regular_file(scriptfile)) { if (fs::is_regular_file(scriptfile)) {
scripting::load_block_script(env, full, scriptfile, def.rt.funcsset); scripting::load_block_script(
env,
full,
scriptfile,
pack->id + ":scripts/" + def.scriptName + ".lua",
def.rt.funcsset
);
} }
if (!def.hidden) { if (!def.hidden) {
auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX); auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX);
@ -510,7 +517,13 @@ void ContentLoader::loadItem(
auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua"); auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua");
if (fs::is_regular_file(scriptfile)) { if (fs::is_regular_file(scriptfile)) {
scripting::load_item_script(env, full, scriptfile, def.rt.funcsset); scripting::load_item_script(
env,
full,
scriptfile,
pack->id + ":scripts/" + def.scriptName + ".lua",
def.rt.funcsset
);
} }
} }
@ -719,7 +732,11 @@ void ContentLoader::load() {
fs::path scriptFile = folder / fs::path("scripts/world.lua"); fs::path scriptFile = folder / fs::path("scripts/world.lua");
if (fs::is_regular_file(scriptFile)) { if (fs::is_regular_file(scriptFile)) {
scripting::load_world_script( scripting::load_world_script(
env, pack->id, scriptFile, runtime->worldfuncsset env,
pack->id,
scriptFile,
pack->id + ":scripts/world.lua",
runtime->worldfuncsset
); );
} }
@ -794,7 +811,11 @@ void ContentLoader::load() {
fs::path componentsDir = folder / fs::u8path("scripts/components"); fs::path componentsDir = folder / fs::u8path("scripts/components");
foreach_file(componentsDir, [this](const fs::path& file) { foreach_file(componentsDir, [this](const fs::path& file) {
auto name = pack->id + ":" + file.stem().u8string(); auto name = pack->id + ":" + file.stem().u8string();
scripting::load_entity_component(name, file); scripting::load_entity_component(
name,
file,
pack->id + ":scripts/components/" + file.filename().u8string()
);
}); });
// Process content.json and load defined content units // Process content.json and load defined content units

View File

@ -76,8 +76,19 @@ ContentPack ContentPack::read(const fs::path& folder) {
root.at("id").get(pack.id); root.at("id").get(pack.id);
root.at("title").get(pack.title); root.at("title").get(pack.title);
root.at("version").get(pack.version); root.at("version").get(pack.version);
if (root.has("creators")) {
const auto& creators = root["creators"];
for (int i = 0; i < creators.size(); i++) {
if (i > 0) {
pack.creator += ", ";
}
pack.creator += creators[i].asString();
}
} else {
root.at("creator").get(pack.creator); root.at("creator").get(pack.creator);
}
root.at("description").get(pack.description); root.at("description").get(pack.description);
root.at("source").get(pack.source);
pack.folder = folder; pack.folder = folder;
if (auto found = root.at("dependencies")) { if (auto found = root.at("dependencies")) {

View File

@ -44,6 +44,7 @@ struct ContentPack {
std::string description = "no description"; std::string description = "no description";
fs::path folder; fs::path folder;
std::vector<DependencyPack> dependencies; std::vector<DependencyPack> dependencies;
std::string source = "";
fs::path getContentFile() const; fs::path getContentFile() const;

View File

@ -127,7 +127,7 @@ static VoxelStructureMeta load_structure_meta(
) { ) {
VoxelStructureMeta meta; VoxelStructureMeta meta;
meta.name = name; meta.name = name;
config.at("lowering").get(meta.lowering);
return meta; return meta;
} }

View File

@ -11,25 +11,25 @@
#include "maths/UVRegion.hpp" #include "maths/UVRegion.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) ContentGfxCache::ContentGfxCache(const Content* content, const Assets& assets)
: content(content) { : content(content) {
auto indices = content->getIndices(); auto indices = content->getIndices();
sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6); sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6);
auto atlas = assets->get<Atlas>("blocks"); const auto& atlas = assets.require<Atlas>("blocks");
const auto& blocks = indices->blocks.getIterable(); const auto& blocks = indices->blocks.getIterable();
for (blockid_t i = 0; i < blocks.size(); i++) { for (blockid_t i = 0; i < blocks.size(); i++) {
auto def = blocks[i]; auto def = blocks[i];
for (uint side = 0; side < 6; side++) { for (uint side = 0; side < 6; side++) {
const std::string& tex = def->textureFaces[side]; const std::string& tex = def->textureFaces[side];
if (atlas->has(tex)) { if (atlas.has(tex)) {
sideregions[i * 6 + side] = atlas->get(tex); sideregions[i * 6 + side] = atlas.get(tex);
} else if (atlas->has(TEXTURE_NOTFOUND)) { } else if (atlas.has(TEXTURE_NOTFOUND)) {
sideregions[i * 6 + side] = atlas->get(TEXTURE_NOTFOUND); sideregions[i * 6 + side] = atlas.get(TEXTURE_NOTFOUND);
} }
} }
if (def->model == BlockModel::custom) { if (def->model == BlockModel::custom) {
auto model = assets->require<model::Model>(def->modelName); auto model = assets.require<model::Model>(def->modelName);
// temporary dirty fix tbh // temporary dirty fix tbh
if (def->modelName.find(':') == std::string::npos) { if (def->modelName.find(':') == std::string::npos) {
for (auto& mesh : model.meshes) { for (auto& mesh : model.meshes) {
@ -37,7 +37,7 @@ ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets)
if (pos == std::string::npos) { if (pos == std::string::npos) {
continue; continue;
} }
if (auto region = atlas->getIf(mesh.texture.substr(pos+1))) { if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
for (auto& vertex : mesh.vertices) { for (auto& vertex : mesh.vertices) {
vertex.uv = region->apply(vertex.uv); vertex.uv = region->apply(vertex.uv);
} }

View File

@ -22,7 +22,7 @@ class ContentGfxCache {
std::unique_ptr<UVRegion[]> sideregions; std::unique_ptr<UVRegion[]> sideregions;
std::unordered_map<blockid_t, model::Model> models; std::unordered_map<blockid_t, model::Model> models;
public: public:
ContentGfxCache(const Content* content, Assets* assets); ContentGfxCache(const Content* content, const Assets& assets);
~ContentGfxCache(); ~ContentGfxCache();
inline const UVRegion& getRegion(blockid_t id, int side) const { inline const UVRegion& getRegion(blockid_t id, int side) const {

View File

@ -14,25 +14,28 @@
#include "world/Level.hpp" #include "world/Level.hpp"
LevelFrontend::LevelFrontend( LevelFrontend::LevelFrontend(
Player* currentPlayer, LevelController* controller, Assets* assets Player* currentPlayer, LevelController* controller, Assets& assets
) : level(controller->getLevel()), ) : level(*controller->getLevel()),
controller(controller), controller(controller),
assets(assets), assets(assets),
contentCache(std::make_unique<ContentGfxCache>(level->content, assets)) contentCache(std::make_unique<ContentGfxCache>(level.content, assets))
{ {
assets->store( assets.store(
BlocksPreview::build(contentCache.get(), assets, level->content), BlocksPreview::build(
*contentCache, assets, *level.content->getIndices()
),
"block-previews" "block-previews"
); );
controller->getBlocksController()->listenBlockInteraction( controller->getBlocksController()->listenBlockInteraction(
[=](auto player, const auto& pos, const auto& def, BlockInteraction type) { [currentPlayer, controller, &assets](auto player, const auto& pos, const auto& def, BlockInteraction type) {
auto material = level->content->findBlockMaterial(def.material); const auto& level = *controller->getLevel();
auto material = level.content->findBlockMaterial(def.material);
if (material == nullptr) { if (material == nullptr) {
return; return;
} }
if (type == BlockInteraction::step) { if (type == BlockInteraction::step) {
auto sound = assets->get<audio::Sound>(material->stepsSound); auto sound = assets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {}; glm::vec3 pos {};
auto soundsCamera = currentPlayer->currentCamera.get(); auto soundsCamera = currentPlayer->currentCamera.get();
if (soundsCamera == currentPlayer->spCamera.get() || if (soundsCamera == currentPlayer->spCamera.get() ||
@ -58,10 +61,10 @@ LevelFrontend::LevelFrontend(
audio::Sound* sound = nullptr; audio::Sound* sound = nullptr;
switch (type) { switch (type) {
case BlockInteraction::placing: case BlockInteraction::placing:
sound = assets->get<audio::Sound>(material->placeSound); sound = assets.get<audio::Sound>(material->placeSound);
break; break;
case BlockInteraction::destruction: case BlockInteraction::destruction:
sound = assets->get<audio::Sound>(material->breakSound); sound = assets.get<audio::Sound>(material->breakSound);
break; break;
default: default:
break; break;
@ -83,16 +86,20 @@ LevelFrontend::LevelFrontend(
LevelFrontend::~LevelFrontend() = default; LevelFrontend::~LevelFrontend() = default;
Level* LevelFrontend::getLevel() const { Level& LevelFrontend::getLevel() {
return level; return level;
} }
Assets* LevelFrontend::getAssets() const { const Level& LevelFrontend::getLevel() const {
return level;
}
const Assets& LevelFrontend::getAssets() const {
return assets; return assets;
} }
ContentGfxCache* LevelFrontend::getContentGfxCache() const { const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
return contentCache.get(); return *contentCache;
} }
LevelController* LevelFrontend::getController() const { LevelController* LevelFrontend::getController() const {

View File

@ -9,16 +9,17 @@ class ContentGfxCache;
class LevelController; class LevelController;
class LevelFrontend { class LevelFrontend {
Level* level; Level& level;
LevelController* controller; LevelController* controller;
Assets* assets; const Assets& assets;
std::unique_ptr<ContentGfxCache> contentCache; std::unique_ptr<ContentGfxCache> contentCache;
public: public:
LevelFrontend(Player* currentPlayer, LevelController* controller, Assets* assets); LevelFrontend(Player* currentPlayer, LevelController* controller, Assets& assets);
~LevelFrontend(); ~LevelFrontend();
Level* getLevel() const; Level& getLevel();
Assets* getAssets() const; const Level& getLevel() const;
ContentGfxCache* getContentGfxCache() const; const Assets& getAssets() const;
const ContentGfxCache& getContentGfxCache() const;
LevelController* getController() const; LevelController* getController() const;
}; };

View File

@ -53,7 +53,12 @@ scriptenv UiDocument::getEnvironment() const {
return env; return env;
} }
std::unique_ptr<UiDocument> UiDocument::read(const scriptenv& penv, const std::string& name, const fs::path& file) { std::unique_ptr<UiDocument> UiDocument::read(
const scriptenv& penv,
const std::string& name,
const fs::path& file,
const std::string& fileName
) {
const std::string text = files::read_string(file); const std::string text = files::read_string(file);
auto xmldoc = xml::parse(file.u8string(), text); auto xmldoc = xml::parse(file.u8string(), text);
@ -69,12 +74,16 @@ std::unique_ptr<UiDocument> UiDocument::read(const scriptenv& penv, const std::s
uidocscript script {}; uidocscript script {};
auto scriptFile = fs::path(file.u8string()+".lua"); auto scriptFile = fs::path(file.u8string()+".lua");
if (fs::is_regular_file(scriptFile)) { if (fs::is_regular_file(scriptFile)) {
scripting::load_layout_script(env, name, scriptFile, script); scripting::load_layout_script(
env, name, scriptFile, fileName + ".lua", script
);
} }
return std::make_unique<UiDocument>(name, script, view, env); return std::make_unique<UiDocument>(name, script, view, env);
} }
std::shared_ptr<gui::UINode> UiDocument::readElement(const fs::path& file) { std::shared_ptr<gui::UINode> UiDocument::readElement(
auto document = read(nullptr, file.filename().u8string(), file); const fs::path& file, const std::string& fileName
) {
auto document = read(nullptr, file.filename().u8string(), file, fileName);
return document->getRoot(); return document->getRoot();
} }

View File

@ -45,6 +45,13 @@ public:
const uidocscript& getScript() const; const uidocscript& getScript() const;
scriptenv getEnvironment() const; scriptenv getEnvironment() const;
static std::unique_ptr<UiDocument> read(const scriptenv& parent_env, const std::string& name, const fs::path& file); static std::unique_ptr<UiDocument> read(
static std::shared_ptr<gui::UINode> readElement(const fs::path& file); const scriptenv& parent_env,
const std::string& name,
const fs::path& file,
const std::string& fileName
);
static std::shared_ptr<gui::UINode> readElement(
const fs::path& file, const std::string& fileName
);
}; };

View File

@ -11,6 +11,7 @@
#include "graphics/ui/elements/InputBindBox.hpp" #include "graphics/ui/elements/InputBindBox.hpp"
#include "graphics/render/WorldRenderer.hpp" #include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
@ -41,8 +42,8 @@ static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
// TODO: move to xml finally // TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel( std::shared_ptr<UINode> create_debug_panel(
Engine* engine, Engine* engine,
Level* level, Level& level,
Player* player, Player& player,
bool allowDebugCheats bool allowDebugCheats
) { ) {
auto panel = std::make_shared<Panel>(glm::vec2(300, 200), glm::vec4(5.0f), 2.0f); auto panel = std::make_shared<Panel>(glm::vec2(300, 200), glm::vec4(5.0f), 2.0f);
@ -93,57 +94,58 @@ std::shared_ptr<UINode> create_debug_panel(
L" emitters: " + L" emitters: " +
std::to_wstring(ParticlesRenderer::aliveEmitters); std::to_wstring(ParticlesRenderer::aliveEmitters);
})); }));
panel->add(create_label([=]() { panel->add(create_label([&]() {
return L"chunks: "+std::to_wstring(level->chunks->getChunksCount())+ return L"chunks: "+std::to_wstring(level.chunks->getChunksCount())+
L" visible: "+std::to_wstring(level->chunks->visible); L" visible: "+std::to_wstring(ChunksRenderer::visibleChunks);
})); }));
panel->add(create_label([=]() { panel->add(create_label([&]() {
return L"entities: "+std::to_wstring(level->entities->size())+L" next: "+ return L"entities: "+std::to_wstring(level.entities->size())+L" next: "+
std::to_wstring(level->entities->peekNextID()); std::to_wstring(level.entities->peekNextID());
})); }));
panel->add(create_label([=]() { panel->add(create_label([&]() -> std::wstring {
const auto& vox = player->selection.vox; const auto& vox = player.selection.vox;
std::wstringstream stream; std::wstringstream stream;
stream << "r:" << vox.state.rotation << " s:" stream << "r:" << vox.state.rotation << " s:"
<< std::bitset<3>(vox.state.segment) << " u:" << std::bitset<3>(vox.state.segment) << " u:"
<< std::bitset<8>(vox.state.userbits); << std::bitset<8>(vox.state.userbits);
if (vox.id == BLOCK_VOID) { if (vox.id == BLOCK_VOID) {
return std::wstring {L"block: -"}; return L"block: -";
} else { } else {
return L"block: "+std::to_wstring(vox.id)+ return L"block: "+std::to_wstring(vox.id)+
L" "+stream.str(); L" "+stream.str();
} }
})); }));
panel->add(create_label([=]() -> std::wstring { panel->add(create_label([&]() -> std::wstring {
const auto& vox = player->selection.vox; const auto& selection = player.selection;
const auto& vox = selection.vox;
if (vox.id == BLOCK_VOID) { if (vox.id == BLOCK_VOID) {
return L"x: - y: - z: -"; return L"x: - y: - z: -";
} }
return L"x: " + std::to_wstring(player->selection.actualPosition.x) + return L"x: " + std::to_wstring(selection.actualPosition.x) +
L" y: " + std::to_wstring(player->selection.actualPosition.y) + L" y: " + std::to_wstring(selection.actualPosition.y) +
L" z: " + std::to_wstring(player->selection.actualPosition.z); L" z: " + std::to_wstring(selection.actualPosition.z);
})); }));
panel->add(create_label([=]() { panel->add(create_label([&]() {
auto eid = player->getSelectedEntity(); auto eid = player.getSelectedEntity();
if (eid == ENTITY_NONE) { if (eid == ENTITY_NONE) {
return std::wstring {L"entity: -"}; return std::wstring {L"entity: -"};
} else if (auto entity = level->entities->get(eid)) { } else if (auto entity = level.entities->get(eid)) {
return L"entity: "+util::str2wstr_utf8(entity->getDef().name)+ return L"entity: "+util::str2wstr_utf8(entity->getDef().name)+
L" uid: "+std::to_wstring(entity->getUID()); L" uid: "+std::to_wstring(entity->getUID());
} else { } else {
return std::wstring {L"entity: error (invalid UID)"}; return std::wstring {L"entity: error (invalid UID)"};
} }
})); }));
panel->add(create_label([=](){ panel->add(create_label([&](){
auto* indices = level->content->getIndices(); auto* indices = level.content->getIndices();
if (auto def = indices->blocks.get(player->selection.vox.id)) { if (auto def = indices->blocks.get(player.selection.vox.id)) {
return L"name: " + util::str2wstr_utf8(def->name); return L"name: " + util::str2wstr_utf8(def->name);
} else { } else {
return std::wstring {L"name: void"}; return std::wstring {L"name: void"};
} }
})); }));
panel->add(create_label([=](){ panel->add(create_label([&](){
return L"seed: "+std::to_wstring(level->getWorld()->getSeed()); return L"seed: "+std::to_wstring(level.getWorld()->getSeed());
})); }));
for (int ax = 0; ax < 3; ax++) { for (int ax = 0; ax < 3; ax++) {
@ -160,22 +162,22 @@ std::shared_ptr<UINode> create_debug_panel(
// Coord input // Coord input
auto box = std::make_shared<TextBox>(L""); auto box = std::make_shared<TextBox>(L"");
auto boxRef = box.get(); auto boxRef = box.get();
box->setTextSupplier([=]() { box->setTextSupplier([&player, ax]() {
return util::to_wstring(player->getPosition()[ax], 2); return util::to_wstring(player.getPosition()[ax], 2);
}); });
if (allowDebugCheats) { if (allowDebugCheats) {
box->setTextConsumer([=](const std::wstring& text) { box->setTextConsumer([&player, ax](const std::wstring& text) {
try { try {
glm::vec3 position = player->getPosition(); glm::vec3 position = player.getPosition();
position[ax] = std::stoi(text); position[ax] = std::stoi(text);
player->teleport(position); player.teleport(position);
} catch (std::exception& _){ } catch (std::exception& _){
} }
}); });
} }
box->setOnEditStart([=]() { box->setOnEditStart([&player, boxRef, ax]() {
boxRef->setText( boxRef->setText(
std::to_wstring(static_cast<int>(player->getPosition()[ax])) std::to_wstring(static_cast<int>(player.getPosition()[ax]))
); );
}); });
box->setSize(glm::vec2(230, 27)); box->setSize(glm::vec2(230, 27));
@ -183,7 +185,7 @@ std::shared_ptr<UINode> create_debug_panel(
sub->add(box, glm::vec2(20, 0)); sub->add(box, glm::vec2(20, 0));
panel->add(sub); panel->add(sub);
} }
auto& worldInfo = level->getWorld()->getInfo(); auto& worldInfo = level.getWorld()->getInfo();
panel->add(create_label([&](){ panel->add(create_label([&](){
int hour, minute, second; int hour, minute, second;
timeutil::from_value(worldInfo.daytime, hour, minute, second); timeutil::from_value(worldInfo.daytime, hour, minute, second);

View File

@ -61,8 +61,8 @@ bool Hud::showGeneratorMinimap = false;
// implemented in debug_panel.cpp // implemented in debug_panel.cpp
extern std::shared_ptr<UINode> create_debug_panel( extern std::shared_ptr<UINode> create_debug_panel(
Engine* engine, Engine* engine,
Level* level, Level& level,
Player* player, Player& player,
bool allowDebugCheats bool allowDebugCheats
); );
@ -104,8 +104,7 @@ std::shared_ptr<UINode> HudElement::getNode() const {
} }
std::shared_ptr<InventoryView> Hud::createContentAccess() { std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto level = frontend->getLevel(); auto content = frontend.getLevel().content;
auto content = level->content;
auto indices = content->getIndices(); auto indices = content->getIndices();
auto inventory = player->getInventory(); auto inventory = player->getInventory();
@ -134,7 +133,7 @@ std::shared_ptr<InventoryView> Hud::createContentAccess() {
std::shared_ptr<InventoryView> Hud::createHotbar() { std::shared_ptr<InventoryView> Hud::createHotbar() {
auto inventory = player->getInventory(); auto inventory = player->getInventory();
auto content = frontend->getLevel()->content; auto content = frontend.getLevel().content;
SlotLayout slotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr); SlotLayout slotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr);
InventoryBuilder builder; InventoryBuilder builder;
@ -149,7 +148,7 @@ std::shared_ptr<InventoryView> Hud::createHotbar() {
static constexpr uint WORLDGEN_IMG_SIZE = 128U; static constexpr uint WORLDGEN_IMG_SIZE = 128U;
Hud::Hud(Engine* engine, LevelFrontend* frontend, Player* player) Hud::Hud(Engine* engine, LevelFrontend& frontend, Player* player)
: engine(engine), : engine(engine),
assets(engine->getAssets()), assets(engine->getAssets()),
gui(engine->getGUI()), gui(engine->getGUI()),
@ -178,7 +177,7 @@ Hud::Hud(Engine* engine, LevelFrontend* frontend, Player* player)
uicamera->flipped = true; uicamera->flipped = true;
debugPanel = create_debug_panel( debugPanel = create_debug_panel(
engine, frontend->getLevel(), player, allowDebugCheats engine, frontend.getLevel(), *player, allowDebugCheats
); );
debugPanel->setZIndex(2); debugPanel->setZIndex(2);
gui->add(debugPanel); gui->add(debugPanel);
@ -231,7 +230,11 @@ void Hud::processInput(bool visible) {
} }
} }
if (!pause && Events::jactive(BIND_DEVTOOLS_CONSOLE)) { if (!pause && Events::jactive(BIND_DEVTOOLS_CONSOLE)) {
showOverlay(assets->get<UiDocument>("core:console"), false); showOverlay(
assets->get<UiDocument>("core:console"),
false,
std::string("console")
);
} }
if (!Window::isFocused() && !pause && !isInventoryOpen()) { if (!Window::isFocused() && !pause && !isInventoryOpen()) {
setPause(true); setPause(true);
@ -273,9 +276,9 @@ void Hud::updateHotbarControl() {
} }
void Hud::updateWorldGenDebugVisualization() { void Hud::updateWorldGenDebugVisualization() {
auto level = frontend->getLevel(); auto& level = frontend.getLevel();
auto generator = auto generator =
frontend->getController()->getChunksController()->getGenerator(); frontend.getController()->getChunksController()->getGenerator();
auto debugInfo = generator->createDebugInfo(); auto debugInfo = generator->createDebugInfo();
int width = debugImgWorldGen->getWidth(); int width = debugImgWorldGen->getWidth();
@ -298,9 +301,9 @@ void Hud::updateWorldGenDebugVisualization() {
int az = z - (height - areaHeight) / 2; int az = z - (height - areaHeight) / 2;
data[(flippedZ * width + x) * 4 + 1] = data[(flippedZ * width + x) * 4 + 1] =
level->chunks->getChunk(ax + ox, az + oz) ? 255 : 0; level.chunks->getChunk(ax + ox, az + oz) ? 255 : 0;
data[(flippedZ * width + x) * 4 + 0] = data[(flippedZ * width + x) * 4 + 0] =
level->chunksStorage->get(ax + ox, az + oz) ? 255 : 0; level.chunksStorage->get(ax + ox, az + oz) ? 255 : 0;
if (ax < 0 || az < 0 || if (ax < 0 || az < 0 ||
ax >= areaWidth || az >= areaHeight) { ax >= areaWidth || az >= areaHeight) {
@ -321,7 +324,7 @@ void Hud::updateWorldGenDebugVisualization() {
} }
void Hud::update(bool visible) { void Hud::update(bool visible) {
auto level = frontend->getLevel(); const auto& level = frontend.getLevel();
auto menu = gui->getMenu(); auto menu = gui->getMenu();
debugPanel->setVisible(player->debug && visible); debugPanel->setVisible(player->debug && visible);
@ -341,7 +344,7 @@ void Hud::update(bool visible) {
} }
if (blockUI) { if (blockUI) {
voxel* vox = level->chunks->get(blockPos.x, blockPos.y, blockPos.z); voxel* vox = level.chunks->get(blockPos.x, blockPos.y, blockPos.z);
if (vox == nullptr || vox->id != currentblockid) { if (vox == nullptr || vox->id != currentblockid) {
closeInventory(); closeInventory();
} }
@ -375,8 +378,7 @@ void Hud::update(bool visible) {
/// @brief Show inventory on the screen and turn on inventory mode blocking movement /// @brief Show inventory on the screen and turn on inventory mode blocking movement
void Hud::openInventory() { void Hud::openInventory() {
auto level = frontend->getLevel(); auto content = frontend.getLevel().content;
auto content = level->content;
showExchangeSlot(); showExchangeSlot();
inventoryOpen = true; inventoryOpen = true;
@ -388,6 +390,39 @@ void Hud::openInventory() {
add(HudElement(hud_element_mode::inventory_bound, nullptr, exchangeSlot, false)); add(HudElement(hud_element_mode::inventory_bound, nullptr, exchangeSlot, false));
} }
void Hud::openInventory(
UiDocument* doc,
std::shared_ptr<Inventory> inv,
bool playerInventory
) {
if (inv == nullptr) {
// why try to open nox-existent inventory??
return;
}
if (isInventoryOpen()) {
closeInventory();
}
const auto& level = frontend.getLevel();
auto content = level.content;
secondInvView = std::dynamic_pointer_cast<InventoryView>(doc->getRoot());
if (secondInvView == nullptr) {
throw std::runtime_error("secondary UI root element must be 'inventory'");
}
secondUI = secondInvView;
if (playerInventory) {
openInventory();
} else {
inventoryOpen = true;
}
if (inv == nullptr) {
inv = level.inventories->createVirtual(secondInvView->getSlotsCount());
}
secondInvView->bind(inv, content);
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false));
}
void Hud::openInventory( void Hud::openInventory(
glm::ivec3 block, glm::ivec3 block,
UiDocument* doc, UiDocument* doc,
@ -397,8 +432,8 @@ void Hud::openInventory(
if (isInventoryOpen()) { if (isInventoryOpen()) {
closeInventory(); closeInventory();
} }
auto level = frontend->getLevel(); auto& level = frontend.getLevel();
auto content = level->content; auto content = level.content;
blockUI = std::dynamic_pointer_cast<InventoryView>(doc->getRoot()); blockUI = std::dynamic_pointer_cast<InventoryView>(doc->getRoot());
if (blockUI == nullptr) { if (blockUI == nullptr) {
throw std::runtime_error("block UI root element must be 'inventory'"); throw std::runtime_error("block UI root element must be 'inventory'");
@ -410,19 +445,19 @@ void Hud::openInventory(
inventoryOpen = true; inventoryOpen = true;
} }
if (blockinv == nullptr) { if (blockinv == nullptr) {
blockinv = level->inventories->createVirtual(blockUI->getSlotsCount()); blockinv = level.inventories->createVirtual(blockUI->getSlotsCount());
} }
level->chunks->getChunkByVoxel(block.x, block.y, block.z)->flags.unsaved = true; level.chunks->getChunkByVoxel(block.x, block.y, block.z)->flags.unsaved = true;
blockUI->bind(blockinv, content); blockUI->bind(blockinv, content);
blockPos = block; blockPos = block;
currentblockid = level->chunks->get(block.x, block.y, block.z)->id; currentblockid = level.chunks->get(block.x, block.y, block.z)->id;
add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false)); add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false));
} }
void Hud::showExchangeSlot() { void Hud::showExchangeSlot() {
auto level = frontend->getLevel(); auto& level = frontend.getLevel();
auto content = level->content; auto content = level.content;
exchangeSlotInv = level->inventories->createVirtual(1); exchangeSlotInv = level.inventories->createVirtual(1);
exchangeSlot = std::make_shared<SlotView>( exchangeSlot = std::make_shared<SlotView>(
SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr) SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr)
); );
@ -434,7 +469,9 @@ void Hud::showExchangeSlot() {
} }
void Hud::showOverlay(UiDocument* doc, bool playerInventory) { void Hud::showOverlay(
UiDocument* doc, bool playerInventory, const dv::value& arg
) {
if (isInventoryOpen()) { if (isInventoryOpen()) {
closeInventory(); closeInventory();
} }
@ -445,7 +482,8 @@ void Hud::showOverlay(UiDocument* doc, bool playerInventory) {
showExchangeSlot(); showExchangeSlot();
inventoryOpen = true; inventoryOpen = true;
} }
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false)); add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false),
arg);
} }
void Hud::openPermanent(UiDocument* doc) { void Hud::openPermanent(UiDocument* doc) {
@ -454,7 +492,7 @@ void Hud::openPermanent(UiDocument* doc) {
auto invview = std::dynamic_pointer_cast<InventoryView>(root); auto invview = std::dynamic_pointer_cast<InventoryView>(root);
if (invview) { if (invview) {
invview->bind(player->getInventory(), frontend->getLevel()->content); invview->bind(player->getInventory(), frontend.getLevel().content);
} }
add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false)); add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false));
} }
@ -477,13 +515,13 @@ void Hud::closeInventory() {
cleanup(); cleanup();
} }
void Hud::add(const HudElement& element) { void Hud::add(const HudElement& element, const dv::value& arg) {
gui->add(element.getNode()); gui->add(element.getNode());
auto document = element.getDocument(); auto document = element.getDocument();
if (document) { if (document) {
auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode()); auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode());
auto inventory = invview ? invview->getInventory() : nullptr; auto inventory = invview ? invview->getInventory() : nullptr;
std::vector<dv::value> args; std::vector<dv::value> args {arg};
args.emplace_back(inventory ? inventory.get()->getId() : 0); args.emplace_back(inventory ? inventory.get()->getId() : 0);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
args.emplace_back(static_cast<integer_t>(blockPos[i])); args.emplace_back(static_cast<integer_t>(blockPos[i]));
@ -540,7 +578,7 @@ void Hud::draw(const DrawContext& ctx){
// Crosshair // Crosshair
if (!pause && !inventoryOpen && !player->debug) { if (!pause && !inventoryOpen && !player->debug) {
DrawContext chctx = ctx.sub(); DrawContext chctx = ctx.sub(batch);
chctx.setBlendMode(BlendMode::inversion); chctx.setBlendMode(BlendMode::inversion);
auto texture = assets->get<Texture>("gui/crosshair"); auto texture = assets->get<Texture>("gui/crosshair");
batch->texture(texture); batch->texture(texture);
@ -584,8 +622,11 @@ void Hud::updateElementsPosition(const Viewport& viewport) {
} }
if (secondUI->getPositionFunc() == nullptr) { if (secondUI->getPositionFunc() == nullptr) {
secondUI->setPos(glm::vec2( secondUI->setPos(glm::vec2(
glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth), glm::min(
height/2-totalHeight/2 width / 2.f - invwidth / 2.f,
width - caWidth - (inventoryView ? 10 : 0) - invwidth
),
height / 2.f - totalHeight / 2.f
)); ));
} }
} }
@ -649,7 +690,7 @@ void Hud::setDebugCheats(bool flag) {
gui->remove(debugPanel); gui->remove(debugPanel);
debugPanel = create_debug_panel( debugPanel = create_debug_panel(
engine, frontend->getLevel(), player, allowDebugCheats engine, frontend.getLevel(), *player, allowDebugCheats
); );
debugPanel->setZIndex(2); debugPanel->setZIndex(2);
gui->add(debugPanel); gui->add(debugPanel);

View File

@ -2,6 +2,7 @@
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/ObjectsKeeper.hpp" #include "util/ObjectsKeeper.hpp"
#include "data/dv.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
@ -73,7 +74,7 @@ class Hud : public util::ObjectsKeeper {
Assets* assets; Assets* assets;
std::unique_ptr<Camera> uicamera; std::unique_ptr<Camera> uicamera;
gui::GUI* gui; gui::GUI* gui;
LevelFrontend* frontend; LevelFrontend& frontend;
Player* player; Player* player;
/// @brief Is any overlay/inventory open /// @brief Is any overlay/inventory open
@ -102,6 +103,8 @@ class Hud : public util::ObjectsKeeper {
std::shared_ptr<gui::InventoryView> inventoryView = nullptr; std::shared_ptr<gui::InventoryView> inventoryView = nullptr;
/// @brief Block inventory view /// @brief Block inventory view
std::shared_ptr<gui::InventoryView> blockUI = nullptr; std::shared_ptr<gui::InventoryView> blockUI = nullptr;
/// @brief Secondary inventory view
std::shared_ptr<gui::InventoryView> secondInvView = nullptr;
/// @brief Position of the block open /// @brief Position of the block open
glm::ivec3 blockPos {}; glm::ivec3 blockPos {};
/// @brief Id of the block open (used to detect block destruction or replacement) /// @brief Id of the block open (used to detect block destruction or replacement)
@ -128,7 +131,7 @@ class Hud : public util::ObjectsKeeper {
void showExchangeSlot(); void showExchangeSlot();
void updateWorldGenDebugVisualization(); void updateWorldGenDebugVisualization();
public: public:
Hud(Engine* engine, LevelFrontend* frontend, Player* player); Hud(Engine* engine, LevelFrontend& frontend, Player* player);
~Hud(); ~Hud();
void update(bool hudVisible); void update(bool hudVisible);
@ -146,6 +149,16 @@ public:
/// @brief Show player inventory in inventory-mode /// @brief Show player inventory in inventory-mode
void openInventory(); void openInventory();
/// @brief Show inventory in inventory-mode
/// @param doc ui layout
/// @param inv inventory
/// @param playerInventory show player inventory too
void openInventory(
UiDocument* doc,
std::shared_ptr<Inventory> inv,
bool playerInventory
);
/// @brief Show block inventory in inventory-mode /// @brief Show block inventory in inventory-mode
/// @param block block position /// @param block block position
/// @param doc block ui layout /// @param doc block ui layout
@ -161,7 +174,10 @@ public:
/// @brief Show element in inventory-mode /// @brief Show element in inventory-mode
/// @param doc element layout /// @param doc element layout
/// @param playerInventory show player inventory too /// @param playerInventory show player inventory too
void showOverlay(UiDocument* doc, bool playerInventory); /// @param arg first argument passing to on_open
void showOverlay(
UiDocument* doc, bool playerInventory, const dv::value& arg = nullptr
);
/// @brief Close all open inventories and overlay /// @brief Close all open inventories and overlay
void closeInventory(); void closeInventory();
@ -170,7 +186,7 @@ public:
/// @param doc element layout /// @param doc element layout
void openPermanent(UiDocument* doc); void openPermanent(UiDocument* doc);
void add(const HudElement& element); void add(const HudElement& element, const dv::value& arg=nullptr);
void onRemove(const HudElement& element); void onRemove(const HudElement& element);
void remove(const std::shared_ptr<gui::UINode>& node); void remove(const std::shared_ptr<gui::UINode>& node);

View File

@ -62,7 +62,10 @@ gui::page_loader_func menus::create_page_loader(Engine* engine) {
auto fullname = "core:pages/"+name; auto fullname = "core:pages/"+name;
auto document_ptr = UiDocument::read( auto document_ptr = UiDocument::read(
scripting::get_root_environment(), fullname, file scripting::get_root_environment(),
fullname,
file,
"core:layouts/pages/" + name
); );
auto document = document_ptr.get(); auto document = document_ptr.get();
engine->getAssets()->store(std::move(document_ptr), fullname); engine->getAssets()->store(std::move(document_ptr), fullname);
@ -110,7 +113,7 @@ UiDocument* menus::show(Engine* engine, const std::string& name, std::vector<dv:
auto fullname = "core:layouts/"+name; auto fullname = "core:layouts/"+name;
auto document_ptr = UiDocument::read( auto document_ptr = UiDocument::read(
scripting::get_root_environment(), fullname, file scripting::get_root_environment(), fullname, file, "core:layouts/"+name
); );
auto document = document_ptr.get(); auto document = document_ptr.get();
engine->getAssets()->store(std::move(document_ptr), fullname); engine->getAssets()->store(std::move(document_ptr), fullname);

View File

@ -36,18 +36,21 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr<Level> levelPtr)
Level* level = levelPtr.get(); Level* level = levelPtr.get();
auto& settings = engine->getSettings(); auto& settings = engine->getSettings();
auto assets = engine->getAssets(); auto& assets = *engine->getAssets();
auto menu = engine->getGUI()->getMenu(); auto menu = engine->getGUI()->getMenu();
menu->reset(); menu->reset();
controller = std::make_unique<LevelController>(engine, std::move(levelPtr)); controller = std::make_unique<LevelController>(engine, std::move(levelPtr));
frontend = std::make_unique<LevelFrontend>(controller->getPlayer(), controller.get(), assets); frontend = std::make_unique<LevelFrontend>(
controller->getPlayer(), controller.get(), assets
worldRenderer = std::make_unique<WorldRenderer>(engine, frontend.get(), controller->getPlayer()); );
hud = std::make_unique<Hud>(engine, frontend.get(), controller->getPlayer()); worldRenderer = std::make_unique<WorldRenderer>(
engine, *frontend, controller->getPlayer()
);
hud = std::make_unique<Hud>(engine, *frontend, controller->getPlayer());
decorator = std::make_unique<Decorator>( decorator = std::make_unique<Decorator>(
*controller, *worldRenderer->particles, *assets *controller, *worldRenderer->particles, assets
); );
keepAlive(settings.graphics.backlight.observe([=](bool) { keepAlive(settings.graphics.backlight.observe([=](bool) {
@ -63,7 +66,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr<Level> levelPtr)
})); }));
animator = std::make_unique<TextureAnimator>(); animator = std::make_unique<TextureAnimator>();
animator->addAnimations(assets->getAnimations()); animator->addAnimations(assets.getAnimations());
initializeContent(); initializeContent();
} }
@ -80,7 +83,12 @@ void LevelScreen::initializePack(ContentPackRuntime* pack) {
const ContentPack& info = pack->getInfo(); const ContentPack& info = pack->getInfo();
fs::path scriptFile = info.folder/fs::path("scripts/hud.lua"); fs::path scriptFile = info.folder/fs::path("scripts/hud.lua");
if (fs::is_regular_file(scriptFile)) { if (fs::is_regular_file(scriptFile)) {
scripting::load_hud_script(pack->getEnvironment(), info.id, scriptFile); scripting::load_hud_script(
pack->getEnvironment(),
info.id,
scriptFile,
pack->getId() + ":scripts/hud.lua"
);
} }
} }

View File

@ -11,7 +11,7 @@
inline constexpr uint B2D_VERTEX_SIZE = 8; inline constexpr uint B2D_VERTEX_SIZE = 8;
Batch2D::Batch2D(size_t capacity) : capacity(capacity), color(1.0f){ Batch2D::Batch2D(size_t capacity) : capacity(capacity), color(1.0f){
const vattr attrs[] = { const VertexAttribute attrs[] = {
{2}, {2}, {4}, {0} {2}, {2}, {4}, {0}
}; };

View File

@ -12,7 +12,7 @@ inline constexpr uint B3D_VERTEX_SIZE = 9;
Batch3D::Batch3D(size_t capacity) Batch3D::Batch3D(size_t capacity)
: capacity(capacity) { : capacity(capacity) {
const vattr attrs[] = { const VertexAttribute attrs[] = {
{3}, {2}, {4}, {0} {3}, {2}, {4}, {0}
}; };
@ -117,6 +117,22 @@ void Batch3D::texture(const Texture* new_texture){
new_texture->bind(); new_texture->bind();
} }
void Batch3D::sprite(
const glm::vec3& pos,
const glm::vec3& up,
const glm::vec3& right,
float w,
float h,
int atlasRes,
int index,
const glm::vec4& tint
) {
float scale = 1.0f / static_cast<float>(atlasRes);
float u = (index % atlasRes) * scale;
float v = 1.0f - ((index / atlasRes) * scale) - scale;
sprite(pos, up, right, w, h, UVRegion(u, v, u+scale, v+scale), tint);
}
void Batch3D::sprite( void Batch3D::sprite(
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& up, const glm::vec3& up,
@ -272,3 +288,11 @@ void Batch3D::flushPoints() {
mesh->draw(GL_POINTS); mesh->draw(GL_POINTS);
index = 0; index = 0;
} }
void Batch3D::setColor(const glm::vec4& color) {
tint = color;
}
const glm::vec4& Batch3D::getColor() const {
return tint;
}

View File

@ -17,6 +17,7 @@ class Batch3D : public Flushable {
std::unique_ptr<Mesh> mesh; std::unique_ptr<Mesh> mesh;
std::unique_ptr<Texture> blank; std::unique_ptr<Texture> blank;
size_t index; size_t index;
glm::vec4 tint {1.0f};
const Texture* currentTexture; const Texture* currentTexture;
@ -57,6 +58,16 @@ public:
const UVRegion& uv, const UVRegion& uv,
const glm::vec4& tint const glm::vec4& tint
); );
void sprite(
const glm::vec3& pos,
const glm::vec3& up,
const glm::vec3& right,
float w,
float h,
int atlasRes,
int index,
const glm::vec4& tint
);
void xSprite( void xSprite(
float w, float w,
float h, float h,
@ -81,4 +92,7 @@ public:
void point(const glm::vec3& pos, const glm::vec4& tint); void point(const glm::vec3& pos, const glm::vec4& tint);
void flush() override; void flush() override;
void flushPoints(); void flushPoints();
void setColor(const glm::vec4& color);
const glm::vec4& getColor() const;
}; };

View File

@ -91,6 +91,7 @@ DrawContext DrawContext::sub(Flushable* flushable) const {
auto ctx = DrawContext(*this); auto ctx = DrawContext(*this);
ctx.parent = this; ctx.parent = this;
ctx.flushable = flushable; ctx.flushable = flushable;
ctx.scissorsCount = 0;
return ctx; return ctx;
} }
@ -148,7 +149,7 @@ void DrawContext::setBlendMode(BlendMode mode) {
set_blend_mode(mode); set_blend_mode(mode);
} }
void DrawContext::setScissors(glm::vec4 area) { void DrawContext::setScissors(const glm::vec4& area) {
Window::pushScissor(area); Window::pushScissor(area);
scissorsCount++; scissorsCount++;
} }

View File

@ -34,6 +34,6 @@ public:
void setDepthTest(bool flag); void setDepthTest(bool flag);
void setCullFace(bool flag); void setCullFace(bool flag);
void setBlendMode(BlendMode mode); void setBlendMode(BlendMode mode);
void setScissors(glm::vec4 area); void setScissors(const glm::vec4& area);
void setLineWidth(float width); void setLineWidth(float width);
}; };

View File

@ -3,6 +3,8 @@
#include <utility> #include <utility>
#include "Texture.hpp" #include "Texture.hpp"
#include "Batch2D.hpp" #include "Batch2D.hpp"
#include "Batch3D.hpp"
#include "window/Camera.hpp"
inline constexpr uint GLYPH_SIZE = 16; inline constexpr uint GLYPH_SIZE = 16;
inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has
@ -35,54 +37,93 @@ bool Font::isPrintableChar(uint codepoint) const {
} }
} }
int Font::calcWidth(const std::wstring& text, size_t length) { int Font::calcWidth(const std::wstring& text, size_t length) const {
return calcWidth(text, 0, length); return calcWidth(text, 0, length);
} }
int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) { int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) const {
return std::min(text.length()-offset, length) * 8; return std::min(text.length()-offset, length) * glyphInterval;
} }
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) { static inline void draw_glyph(
draw(batch, std::move(text), x, y, FontStyle::none); Batch2D& batch,
const glm::vec3& pos,
const glm::vec2& offset,
uint c,
const glm::vec3& right,
const glm::vec3& up,
float glyphInterval
) {
batch.sprite(
pos.x + offset.x * right.x,
pos.y + offset.y * right.y,
right.x / glyphInterval,
up.y,
16,
c,
batch.getColor()
);
} }
static inline void drawGlyph(Batch2D* batch, int x, int y, uint c, FontStyle style) { static inline void draw_glyph(
switch (style){ Batch3D& batch,
case FontStyle::none: const glm::vec3& pos,
break; const glm::vec2& offset,
case FontStyle::shadow: uint c,
batch->sprite(x+1, y+1, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT); const glm::vec3& right,
break; const glm::vec3& up,
case FontStyle::outline: float glyphInterval
for (int oy = -1; oy <= 1; oy++){ ) {
for (int ox = -1; ox <= 1; ox++){ batch.sprite(
if (ox || oy) { pos + right * offset.x + up * offset.y,
batch->sprite(x+ox, y+oy, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT); up, right / glyphInterval,
} 0.5f,
} 0.5f,
} 16,
break; c,
} batch.getColor()
batch->sprite(x, y, GLYPH_SIZE, GLYPH_SIZE, 16, c, batch->getColor()); );
} }
void Font::draw(Batch2D* batch, const std::wstring& text, int x, int y, FontStyle style) { template <class Batch>
draw(batch, std::wstring_view(text.c_str(), text.length()), x, y, style); static inline void draw_text(
} const Font& font,
Batch& batch,
void Font::draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style) { std::wstring_view text,
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
float glyphInterval
) {
uint page = 0; uint page = 0;
uint next = MAX_CODEPAGES; uint next = MAX_CODEPAGES;
int init_x = x; int x = 0;
int y = 0;
do { do {
for (uint c : text){ for (uint c : text){
if (!isPrintableChar(c)) { if (!font.isPrintableChar(c)) {
x += 8; x++;
continue; continue;
} }
uint charpage = c >> 8; uint charpage = c >> 8;
if (charpage == page){ if (charpage == page){
batch.texture(font.getPage(charpage));
draw_glyph(
batch, pos, glm::vec2(x, y), c, right, up, glyphInterval
);
}
else if (charpage > page && charpage < next){
next = charpage;
}
x++;
}
page = next;
next = MAX_CODEPAGES;
x = 0;
} while (page < MAX_CODEPAGES);
}
const Texture* Font::getPage(int charpage) const {
Texture* texture = nullptr; Texture* texture = nullptr;
if (charpage < pages.size()) { if (charpage < pages.size()) {
texture = pages[charpage].get(); texture = pages[charpage].get();
@ -90,16 +131,32 @@ void Font::draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle
if (texture == nullptr){ if (texture == nullptr){
texture = pages[0].get(); texture = pages[0].get();
} }
batch->texture(texture); return texture;
drawGlyph(batch, x, y, c, style);
} }
else if (charpage > page && charpage < next){
next = charpage; void Font::draw(
Batch2D& batch, std::wstring_view text, int x, int y, float scale
) const {
draw_text(
*this, batch, text,
glm::vec3(x, y, 0),
glm::vec3(glyphInterval*scale, 0, 0),
glm::vec3(0, lineHeight*scale, 0),
glyphInterval/static_cast<float>(lineHeight)
);
} }
x += 8;//getGlyphWidth(c);
} void Font::draw(
page = next; Batch3D& batch,
next = MAX_CODEPAGES; std::wstring_view text,
x = init_x; const glm::vec3& pos,
} while (page < MAX_CODEPAGES); const glm::vec3& right,
const glm::vec3& up
) const {
draw_text(
*this, batch, text, pos,
right * static_cast<float>(glyphInterval),
up * static_cast<float>(lineHeight),
glyphInterval/static_cast<float>(lineHeight)
);
} }

View File

@ -3,10 +3,13 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <glm/glm.hpp>
#include "typedefs.hpp" #include "typedefs.hpp"
class Texture; class Texture;
class Batch2D; class Batch2D;
class Batch3D;
class Camera;
enum class FontStyle { enum class FontStyle {
none, none,
@ -17,8 +20,9 @@ enum class FontStyle {
class Font { class Font {
int lineHeight; int lineHeight;
int yoffset; int yoffset;
public: int glyphInterval = 8;
std::vector<std::unique_ptr<Texture>> pages; std::vector<std::unique_ptr<Texture>> pages;
public:
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset); Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
~Font(); ~Font();
@ -29,19 +33,28 @@ public:
/// @param text selected text /// @param text selected text
/// @param length max substring length (default: no limit) /// @param length max substring length (default: no limit)
/// @return pixel width of the substring /// @return pixel width of the substring
int calcWidth(const std::wstring& text, size_t length=-1); int calcWidth(const std::wstring& text, size_t length=-1) const;
/// @brief Calculate text width in pixels /// @brief Calculate text width in pixels
/// @param text selected text /// @param text selected text
/// @param offset start of the substring /// @param offset start of the substring
/// @param length max substring length /// @param length max substring length
/// @return pixel width of the substring /// @return pixel width of the substring
int calcWidth(const std::wstring& text, size_t offset, size_t length); int calcWidth(const std::wstring& text, size_t offset, size_t length) const;
/// @brief Check if character is visible (non-whitespace) /// @brief Check if character is visible (non-whitespace)
/// @param codepoint character unicode codepoint /// @param codepoint character unicode codepoint
bool isPrintableChar(uint codepoint) const; bool isPrintableChar(uint codepoint) const;
void draw(Batch2D* batch, std::wstring text, int x, int y);
void draw(Batch2D* batch, const std::wstring& text, int x, int y, FontStyle style); void draw(Batch2D& batch, std::wstring_view text, int x, int y, float scale=1) const;
void draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style);
void draw(
Batch3D& batch,
std::wstring_view text,
const glm::vec3& pos,
const glm::vec3& right={1, 0, 0},
const glm::vec3& up={0, 1, 0}
) const;
const Texture* getPage(int page) const;
}; };

View File

@ -59,7 +59,9 @@ std::unique_ptr<ImageData> GLTexture::readData() {
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.get()); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.get());
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
return std::make_unique<ImageData>(ImageFormat::rgba8888, width, height, data.release()); return std::make_unique<ImageData>(
ImageFormat::rgba8888, width, height, data.release()
);
} }
void GLTexture::setNearestFilter() { void GLTexture::setNearestFilter() {
@ -69,6 +71,18 @@ void GLTexture::setNearestFilter() {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
void GLTexture::setMipMapping(bool flag) {
bind();
if (flag) {
glTexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST
);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
std::unique_ptr<GLTexture> GLTexture::from(const ImageData* image) { std::unique_ptr<GLTexture> GLTexture::from(const ImageData* image) {
uint width = image->getWidth(); uint width = image->getWidth();
uint height = image->getHeight(); uint height = image->getHeight();

View File

@ -18,6 +18,8 @@ public:
virtual void reload(const ImageData& image) override; virtual void reload(const ImageData& image) override;
virtual void setMipMapping(bool flag) override;
virtual std::unique_ptr<ImageData> readData() override; virtual std::unique_ptr<ImageData> readData() override;
virtual uint getId() const override; virtual uint getId() const override;

View File

@ -6,7 +6,7 @@
inline constexpr uint LB_VERTEX_SIZE = (3+4); inline constexpr uint LB_VERTEX_SIZE = (3+4);
LineBatch::LineBatch(size_t capacity) : capacity(capacity) { LineBatch::LineBatch(size_t capacity) : capacity(capacity) {
const vattr attrs[] = { {3},{4}, {0} }; const VertexAttribute attrs[] = { {3},{4}, {0} };
buffer = std::make_unique<float[]>(capacity * LB_VERTEX_SIZE * 2); buffer = std::make_unique<float[]>(capacity * LB_VERTEX_SIZE * 2);
mesh = std::make_unique<Mesh>(buffer.get(), 0, attrs); mesh = std::make_unique<Mesh>(buffer.get(), 0, attrs);
index = 0; index = 0;

View File

@ -4,7 +4,7 @@
int Mesh::meshesCount = 0; int Mesh::meshesCount = 0;
int Mesh::drawCalls = 0; int Mesh::drawCalls = 0;
inline size_t calc_vertex_size(const vattr* attrs) { inline size_t calc_vertex_size(const VertexAttribute* attrs) {
size_t vertexSize = 0; size_t vertexSize = 0;
for (int i = 0; attrs[i].size; i++) { for (int i = 0; attrs[i].size; i++) {
vertexSize += attrs[i].size; vertexSize += attrs[i].size;
@ -19,10 +19,10 @@ Mesh::Mesh(const MeshData& data)
data.indices.size(), data.indices.size(),
data.attrs.data()) {} data.attrs.data()) {}
Mesh::Mesh(const float* vertexBuffer, size_t vertices, const int* indexBuffer, size_t indices, const vattr* attrs) : Mesh::Mesh(const float* vertexBuffer, size_t vertices, const int* indexBuffer, size_t indices, const VertexAttribute* attrs) :
ibo(0), ibo(0),
vertices(vertices), vertices(0),
indices(indices) indices(0)
{ {
meshesCount++; meshesCount++;
vertexSize = 0; vertexSize = 0;
@ -58,10 +58,9 @@ void Mesh::reload(const float* vertexBuffer, size_t vertices, const int* indexBu
glBindVertexArray(vao); glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (vertexBuffer != nullptr && vertices != 0) { if (vertexBuffer != nullptr && vertices != 0) {
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertexSize * vertices, vertexBuffer, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertexSize * vertices, vertexBuffer, GL_STREAM_DRAW);
} } else {
else { glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW);
glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STATIC_DRAW);
} }
if (indexBuffer != nullptr && indices != 0) { if (indexBuffer != nullptr && indices != 0) {
if (ibo == 0) glGenBuffers(1, &ibo); if (ibo == 0) glGenBuffers(1, &ibo);
@ -75,7 +74,7 @@ void Mesh::reload(const float* vertexBuffer, size_t vertices, const int* indexBu
this->indices = indices; this->indices = indices;
} }
void Mesh::draw(unsigned int primitive){ void Mesh::draw(unsigned int primitive) const {
drawCalls++; drawCalls++;
glBindVertexArray(vao); glBindVertexArray(vao);
if (ibo != 0) { if (ibo != 0) {
@ -87,6 +86,6 @@ void Mesh::draw(unsigned int primitive){
glBindVertexArray(0); glBindVertexArray(0);
} }
void Mesh::draw() { void Mesh::draw() const {
draw(GL_TRIANGLES); draw(GL_TRIANGLES);
} }

View File

@ -14,8 +14,8 @@ class Mesh {
size_t vertexSize; size_t vertexSize;
public: public:
Mesh(const MeshData& data); Mesh(const MeshData& data);
Mesh(const float* vertexBuffer, size_t vertices, const int* indexBuffer, size_t indices, const vattr* attrs); Mesh(const float* vertexBuffer, size_t vertices, const int* indexBuffer, size_t indices, const VertexAttribute* attrs);
Mesh(const float* vertexBuffer, size_t vertices, const vattr* attrs) : Mesh(const float* vertexBuffer, size_t vertices, const VertexAttribute* attrs) :
Mesh(vertexBuffer, vertices, nullptr, 0, attrs) {}; Mesh(vertexBuffer, vertices, nullptr, 0, attrs) {};
~Mesh(); ~Mesh();
@ -28,10 +28,10 @@ public:
/// @brief Draw mesh with specified primitives type /// @brief Draw mesh with specified primitives type
/// @param primitive primitives type /// @param primitive primitives type
void draw(unsigned int primitive); void draw(unsigned int primitive) const;
/// @brief Draw mesh as triangles /// @brief Draw mesh as triangles
void draw(); void draw() const;
/// @brief Total numbers of alive mesh objects /// @brief Total numbers of alive mesh objects
static int meshesCount; static int meshesCount;

View File

@ -6,7 +6,7 @@
#include "util/Buffer.hpp" #include "util/Buffer.hpp"
/// @brief Vertex attribute info /// @brief Vertex attribute info
struct vattr { struct VertexAttribute {
ubyte size; ubyte size;
}; };
@ -14,7 +14,7 @@ struct vattr {
struct MeshData { struct MeshData {
util::Buffer<float> vertices; util::Buffer<float> vertices;
util::Buffer<int> indices; util::Buffer<int> indices;
util::Buffer<vattr> attrs; util::Buffer<VertexAttribute> attrs;
MeshData() = default; MeshData() = default;
@ -24,7 +24,7 @@ struct MeshData {
MeshData( MeshData(
util::Buffer<float> vertices, util::Buffer<float> vertices,
util::Buffer<int> indices, util::Buffer<int> indices,
util::Buffer<vattr> attrs util::Buffer<VertexAttribute> attrs
) : vertices(std::move(vertices)), ) : vertices(std::move(vertices)),
indices(std::move(indices)), indices(std::move(indices)),
attrs(std::move(attrs)) {} attrs(std::move(attrs)) {}

View File

@ -14,7 +14,7 @@ PostProcessing::PostProcessing() {
-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f
}; };
vattr attrs[] {{2}, {0}}; VertexAttribute attrs[] {{2}, {0}};
quadMesh = std::make_unique<Mesh>(vertices, 6, attrs); quadMesh = std::make_unique<Mesh>(vertices, 6, attrs);
} }

View File

@ -34,5 +34,7 @@ public:
virtual uint getId() const = 0; virtual uint getId() const = 0;
virtual void setMipMapping(bool flag) = 0;
static std::unique_ptr<Texture> from(const ImageData* image); static std::unique_ptr<Texture> from(const ImageData* image);
}; };

View File

@ -18,18 +18,18 @@
#include <glm/ext.hpp> #include <glm/ext.hpp>
std::unique_ptr<ImageData> BlocksPreview::draw( std::unique_ptr<ImageData> BlocksPreview::draw(
const ContentGfxCache* cache, const ContentGfxCache& cache,
Shader* shader, Shader& shader,
Framebuffer* fbo, const Framebuffer& fbo,
Batch3D* batch, Batch3D& batch,
const Block& def, const Block& def,
int size int size
){ ){
Window::clear(); Window::clear();
blockid_t id = def.rt.id; blockid_t id = def.rt.id;
const UVRegion texfaces[6]{cache->getRegion(id, 0), cache->getRegion(id, 1), const UVRegion texfaces[6]{cache.getRegion(id, 0), cache.getRegion(id, 1),
cache->getRegion(id, 2), cache->getRegion(id, 3), cache.getRegion(id, 2), cache.getRegion(id, 3),
cache->getRegion(id, 4), cache->getRegion(id, 5)}; cache.getRegion(id, 4), cache.getRegion(id, 5)};
glm::vec3 offset(0.1f, 0.5f, 0.1f); glm::vec3 offset(0.1f, 0.5f, 0.1f);
switch (def.model) { switch (def.model) {
@ -37,10 +37,10 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
// something went wrong... // something went wrong...
break; break;
case BlockModel::block: case BlockModel::block:
shader->uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset));
batch->blockCube(glm::vec3(size * 0.63f), texfaces, batch.blockCube(glm::vec3(size * 0.63f), texfaces,
glm::vec4(1.0f), !def.rt.emissive); glm::vec4(1.0f), !def.rt.emissive);
batch->flush(); batch.flush();
break; break;
case BlockModel::aabb: case BlockModel::aabb:
{ {
@ -49,39 +49,39 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
hitbox = glm::max(hitbox, box.size()); hitbox = glm::max(hitbox, box.size());
} }
offset = glm::vec3(1, 1, 0.0f); offset = glm::vec3(1, 1, 0.0f);
shader->uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset));
glm::vec3 scaledSize = glm::vec3(size * 0.63f); glm::vec3 scaledSize = glm::vec3(size * 0.63f);
batch->cube( batch.cube(
-hitbox * scaledSize * 0.5f * glm::vec3(1,1,-1), -hitbox * scaledSize * 0.5f * glm::vec3(1,1,-1),
hitbox * scaledSize, hitbox * scaledSize,
texfaces, glm::vec4(1.0f), texfaces, glm::vec4(1.0f),
!def.rt.emissive !def.rt.emissive
); );
} }
batch->flush(); batch.flush();
break; break;
case BlockModel::custom:{ case BlockModel::custom:{
glm::vec3 pmul = glm::vec3(size * 0.63f); glm::vec3 pmul = glm::vec3(size * 0.63f);
glm::vec3 hitbox = glm::vec3(1.0f); glm::vec3 hitbox = glm::vec3(1.0f);
glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f);
offset.y += (1.0f - hitbox).y * 0.5f; offset.y += (1.0f - hitbox).y * 0.5f;
shader->uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset));
const auto& model = cache->getModel(def.rt.id); const auto& model = cache.getModel(def.rt.id);
for (const auto& mesh : model.meshes) { for (const auto& mesh : model.meshes) {
for (const auto& vertex : mesh.vertices) { for (const auto& vertex : mesh.vertices) {
float d = glm::dot(glm::normalize(vertex.normal), glm::vec3(0.2, 0.8, 0.4)); float d = glm::dot(glm::normalize(vertex.normal), glm::vec3(0.2, 0.8, 0.4));
d = 0.8f + d * 0.2f; d = 0.8f + d * 0.2f;
batch->vertex((vertex.coord - poff)*pmul, vertex.uv, glm::vec4(d, d, d, 1.0f)); batch.vertex((vertex.coord - poff)*pmul, vertex.uv, glm::vec4(d, d, d, 1.0f));
} }
batch->flush(); batch.flush();
} }
break; break;
} }
case BlockModel::xsprite: { case BlockModel::xsprite: {
shader->uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset));
glm::vec3 right = glm::normalize(glm::vec3(1.f, 0.f, -1.f)); glm::vec3 right = glm::normalize(glm::vec3(1.f, 0.f, -1.f));
batch->sprite( batch.sprite(
right*float(size)*0.43f+glm::vec3(0, size*0.4f, 0), right*float(size)*0.43f+glm::vec3(0, size*0.4f, 0),
glm::vec3(0.f, 1.f, 0.f), glm::vec3(0.f, 1.f, 0.f),
right, right,
@ -89,24 +89,23 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
texfaces[0], texfaces[0],
glm::vec4(1.0f) glm::vec4(1.0f)
); );
batch->flush(); batch.flush();
break; break;
} }
} }
return fbo->getTexture()->readData(); return fbo.getTexture()->readData();
} }
std::unique_ptr<Atlas> BlocksPreview::build( std::unique_ptr<Atlas> BlocksPreview::build(
const ContentGfxCache* cache, const ContentGfxCache& cache,
Assets* assets, const Assets& assets,
const Content* content const ContentIndices& indices
) { ) {
auto indices = content->getIndices(); size_t count = indices.blocks.count();
size_t count = indices->blocks.count();
size_t iconSize = ITEM_ICON_SIZE; size_t iconSize = ITEM_ICON_SIZE;
auto shader = assets->get<Shader>("ui3d"); auto& shader = assets.require<Shader>("ui3d");
auto atlas = assets->get<Atlas>("blocks"); const auto& atlas = assets.require<Atlas>("blocks");
Viewport viewport(iconSize, iconSize); Viewport viewport(iconSize, iconSize);
DrawContext pctx(nullptr, viewport, nullptr); DrawContext pctx(nullptr, viewport, nullptr);
@ -118,8 +117,8 @@ std::unique_ptr<Atlas> BlocksPreview::build(
Batch3D batch(1024); Batch3D batch(1024);
batch.begin(); batch.begin();
shader->use(); shader.use();
shader->uniformMatrix("u_projview", shader.uniformMatrix("u_projview",
glm::ortho(0.0f, float(iconSize), 0.0f, float(iconSize), glm::ortho(0.0f, float(iconSize), 0.0f, float(iconSize),
-100.0f, 100.0f) * -100.0f, 100.0f) *
glm::lookAt(glm::vec3(0.57735f), glm::lookAt(glm::vec3(0.57735f),
@ -132,9 +131,9 @@ std::unique_ptr<Atlas> BlocksPreview::build(
fbo.bind(); fbo.bind();
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
auto& def = indices->blocks.require(i); auto& def = indices.blocks.require(i);
atlas->getTexture()->bind(); atlas.getTexture()->bind();
builder.add(def.name, draw(cache, shader, &fbo, &batch, def, iconSize)); builder.add(def.name, draw(cache, shader, fbo, batch, def, iconSize));
} }
fbo.unbind(); fbo.unbind();

View File

@ -11,23 +11,23 @@ class Atlas;
class Framebuffer; class Framebuffer;
class Batch3D; class Batch3D;
class Block; class Block;
class Content; class ContentIndices;
class Shader; class Shader;
class ContentGfxCache; class ContentGfxCache;
class BlocksPreview { class BlocksPreview {
static std::unique_ptr<ImageData> draw( static std::unique_ptr<ImageData> draw(
const ContentGfxCache* cache, const ContentGfxCache& cache,
Shader* shader, Shader& shader,
Framebuffer* framebuffer, const Framebuffer& framebuffer,
Batch3D* batch, Batch3D& batch,
const Block& block, const Block& block,
int size int size
); );
public: public:
static std::unique_ptr<Atlas> build( static std::unique_ptr<Atlas> build(
const ContentGfxCache* cache, const ContentGfxCache& cache,
Assets* assets, const Assets& assets,
const Content* content const ContentIndices& indices
); );
}; };

View File

@ -5,23 +5,22 @@
#include "maths/UVRegion.hpp" #include "maths/UVRegion.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "voxels/ChunksStorage.hpp" #include "voxels/Chunks.hpp"
#include "lighting/Lightmap.hpp" #include "lighting/Lightmap.hpp"
#include "frontend/ContentGfxCache.hpp" #include "frontend/ContentGfxCache.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
const uint BlocksRenderer::VERTEX_SIZE = 6;
const glm::vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f); const glm::vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f);
BlocksRenderer::BlocksRenderer( BlocksRenderer::BlocksRenderer(
size_t capacity, size_t capacity,
const Content* content, const Content& content,
const ContentGfxCache* cache, const ContentGfxCache& cache,
const EngineSettings* settings const EngineSettings& settings
) : content(content), ) : content(content),
vertexBuffer(std::make_unique<float[]>(capacity * VERTEX_SIZE)), vertexBuffer(std::make_unique<float[]>(capacity * CHUNK_VERTEX_SIZE)),
indexBuffer(std::make_unique<int[]>(capacity)), indexBuffer(std::make_unique<int[]>(capacity)),
vertexOffset(0), vertexOffset(0),
indexOffset(0), indexOffset(0),
@ -34,7 +33,7 @@ BlocksRenderer::BlocksRenderer(
CHUNK_W + voxelBufferPadding*2, CHUNK_W + voxelBufferPadding*2,
CHUNK_H, CHUNK_H,
CHUNK_D + voxelBufferPadding*2); CHUNK_D + voxelBufferPadding*2);
blockDefsCache = content->getIndices()->blocks.getDefs(); blockDefsCache = content.getIndices()->blocks.getDefs();
} }
BlocksRenderer::~BlocksRenderer() { BlocksRenderer::~BlocksRenderer() {
@ -85,7 +84,7 @@ void BlocksRenderer::face(
const glm::vec4(&lights)[4], const glm::vec4(&lights)[4],
const glm::vec4& tint const glm::vec4& tint
) { ) {
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * 4 > capacity) { if (vertexOffset + CHUNK_VERTEX_SIZE * 4 > capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -125,7 +124,7 @@ void BlocksRenderer::faceAO(
const UVRegion& region, const UVRegion& region,
bool lights bool lights
) { ) {
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * 4 > capacity) { if (vertexOffset + CHUNK_VERTEX_SIZE * 4 > capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -163,7 +162,7 @@ void BlocksRenderer::face(
glm::vec4 tint, glm::vec4 tint,
bool lights bool lights
) { ) {
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * 4 > capacity) { if (vertexOffset + CHUNK_VERTEX_SIZE * 4 > capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -286,32 +285,38 @@ void BlocksRenderer::blockCustomModel(
Z = orient.axisZ; Z = orient.axisZ;
} }
const auto& model = cache->getModel(block->rt.id); const auto& model = cache.getModel(block->rt.id);
for (const auto& mesh : model.meshes) { for (const auto& mesh : model.meshes) {
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * mesh.vertices.size() > capacity) { if (vertexOffset + CHUNK_VERTEX_SIZE * mesh.vertices.size() > capacity) {
overflow = true; overflow = true;
return; return;
} }
int i = 0; for (int triangle = 0; triangle < mesh.vertices.size() / 3; triangle++) {
for (const auto& vertex : mesh.vertices) { auto r = mesh.vertices[triangle * 3 + (triangle % 2) * 2].coord -
auto n = mesh.vertices[triangle * 3 + 1].coord;
vertex.normal.x * X + vertex.normal.y * Y + vertex.normal.z * Z; r = glm::normalize(r);
float d = glm::dot(glm::normalize(n), SUN_VECTOR);
for (int i = 0; i < 3; i++) {
const auto& vertex = mesh.vertices[triangle * 3 + i];
auto n = vertex.normal.x * X + vertex.normal.y * Y +
vertex.normal.z * Z;
float d = glm::dot(n, SUN_VECTOR);
d = 0.8f + d * 0.2f; d = 0.8f + d * 0.2f;
const auto& vcoord = vertex.coord - 0.5f; const auto& vcoord = vertex.coord - 0.5f;
vertexAO( vertexAO(
coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z, coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z,
vertex.uv.x, vertex.uv.x,
vertex.uv.y, vertex.uv.y,
glm::vec4(1, 1, 1, 1), glm::vec4(d, d, d, d),
glm::vec3(1, 0, 0), glm::cross(r, n),
glm::vec3(0, 1, 0), r,
n n
); );
indexBuffer[indexSize++] = indexOffset++; indexBuffer[indexSize++] = indexOffset++;
} }
} }
} }
}
/* Fastest solid shaded blocks render method */ /* Fastest solid shaded blocks render method */
void BlocksRenderer::blockCube( void BlocksRenderer::blockCube(
@ -427,11 +432,16 @@ glm::vec4 BlocksRenderer::pickSoftLight(
right, up); right, up);
} }
void BlocksRenderer::render(const voxel* voxels) { void BlocksRenderer::render(
int begin = chunk->bottom * (CHUNK_W * CHUNK_D); const voxel* voxels, int beginEnds[256][2]
int end = chunk->top * (CHUNK_W * CHUNK_D); ) {
for (const auto drawGroup : *content->drawGroups) { for (const auto drawGroup : *content.drawGroups) {
for (int i = begin; i < end; i++) { int begin = beginEnds[drawGroup][0];
if (begin == 0) {
continue;
}
int end = beginEnds[drawGroup][1];
for (int i = begin-1; i <= end; i++) {
const voxel& vox = voxels[i]; const voxel& vox = voxels[i];
blockid_t id = vox.id; blockid_t id = vox.id;
blockstate state = vox.state; blockstate state = vox.state;
@ -439,13 +449,13 @@ void BlocksRenderer::render(const voxel* voxels) {
if (id == 0 || def.drawGroup != drawGroup || state.segment) { if (id == 0 || def.drawGroup != drawGroup || state.segment) {
continue; continue;
} }
if (def.translucent) {
continue;
}
const UVRegion texfaces[6] { const UVRegion texfaces[6] {
cache->getRegion(id, 0), cache.getRegion(id, 0), cache.getRegion(id, 1),
cache->getRegion(id, 1), cache.getRegion(id, 2), cache.getRegion(id, 3),
cache->getRegion(id, 2), cache.getRegion(id, 4), cache.getRegion(id, 5)
cache->getRegion(id, 3),
cache->getRegion(id, 4),
cache->getRegion(id, 5)
}; };
int x = i % CHUNK_W; int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W); int y = i / (CHUNK_D * CHUNK_W);
@ -480,43 +490,185 @@ void BlocksRenderer::render(const voxel* voxels) {
} }
} }
void BlocksRenderer::build(const Chunk* chunk, const ChunksStorage* chunks) { SortingMeshData BlocksRenderer::renderTranslucent(
const voxel* voxels, int beginEnds[256][2]
) {
SortingMeshData sortingMesh {{}};
AABB aabb {};
bool aabbInit = false;
size_t totalSize = 0;
for (const auto drawGroup : *content.drawGroups) {
int begin = beginEnds[drawGroup][0];
if (begin == 0) {
continue;
}
int end = beginEnds[drawGroup][1];
for (int i = begin-1; i <= end; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
if (id == 0 || def.drawGroup != drawGroup || state.segment) {
continue;
}
if (!def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, 0), cache.getRegion(id, 1),
cache.getRegion(id, 2), cache.getRegion(id, 3),
cache.getRegion(id, 4), cache.getRegion(id, 5)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.model) {
case BlockModel::block:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion);
break;
case BlockModel::xsprite: {
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModel::aabb: {
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion);
break;
}
case BlockModel::custom: {
blockCustomModel({x, y, z}, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion);
break;
}
default:
break;
}
if (vertexOffset == 0) {
continue;
}
SortingMeshEntry entry {
glm::vec3(
x + chunk->x * CHUNK_W + 0.5f,
y + 0.5f,
z + chunk->z * CHUNK_D + 0.5f
),
util::Buffer<float>(indexSize * CHUNK_VERTEX_SIZE)};
totalSize += entry.vertexData.size();
for (int j = 0; j < indexSize; j++) {
std::memcpy(
entry.vertexData.data() + j * CHUNK_VERTEX_SIZE,
vertexBuffer.get() + indexBuffer[j] * CHUNK_VERTEX_SIZE,
sizeof(float) * CHUNK_VERTEX_SIZE
);
float& vx = entry.vertexData[j * CHUNK_VERTEX_SIZE + 0];
float& vy = entry.vertexData[j * CHUNK_VERTEX_SIZE + 1];
float& vz = entry.vertexData[j * CHUNK_VERTEX_SIZE + 2];
if (!aabbInit) {
aabbInit = true;
aabb.a = aabb.b = {vx, vy, vz};
} else {
aabb.addPoint(glm::vec3(vx, vy, vz));
}
vx += chunk->x * CHUNK_W + 0.5f;
vy += 0.5f;
vz += chunk->z * CHUNK_D + 0.5f;
}
sortingMesh.entries.push_back(std::move(entry));
vertexOffset = 0;
indexOffset = indexSize = 0;
}
}
// additional powerful optimization
auto size = aabb.size();
if ((size.y < 0.01f || size.x < 0.01f || size.z < 0.01f) &&
sortingMesh.entries.size() > 1) {
SortingMeshEntry newEntry {
sortingMesh.entries[0].position,
util::Buffer<float>(totalSize)
};
size_t offset = 0;
for (const auto& entry : sortingMesh.entries) {
std::memcpy(
newEntry.vertexData.data() + offset,
entry.vertexData.data(), entry.vertexData.size() * sizeof(float)
);
offset += entry.vertexData.size();
}
return SortingMeshData {{std::move(newEntry)}};
}
return sortingMesh;
}
void BlocksRenderer::build(const Chunk* chunk, const Chunks* chunks) {
this->chunk = chunk; this->chunk = chunk;
voxelsBuffer->setPosition( voxelsBuffer->setPosition(
chunk->x * CHUNK_W - voxelBufferPadding, 0, chunk->x * CHUNK_W - voxelBufferPadding, 0,
chunk->z * CHUNK_D - voxelBufferPadding); chunk->z * CHUNK_D - voxelBufferPadding);
chunks->getVoxels(voxelsBuffer.get(), settings->graphics.backlight.get()); chunks->getVoxels(voxelsBuffer.get(), settings.graphics.backlight.get());
overflow = false;
vertexOffset = 0;
indexOffset = indexSize = 0;
if (voxelsBuffer->pickBlockId( if (voxelsBuffer->pickBlockId(
chunk->x * CHUNK_W, 0, chunk->z * CHUNK_D chunk->x * CHUNK_W, 0, chunk->z * CHUNK_D
) == BLOCK_VOID) { ) == BLOCK_VOID) {
cancelled = true; cancelled = true;
return; return;
} }
cancelled = false;
const voxel* voxels = chunk->voxels; const voxel* voxels = chunk->voxels;
render(voxels);
int totalBegin = chunk->bottom * (CHUNK_W * CHUNK_D);
int totalEnd = chunk->top * (CHUNK_W * CHUNK_D);
int beginEnds[256][2] {};
for (int i = totalBegin; i < totalEnd; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
const auto& def = *blockDefsCache[id];
if (beginEnds[def.drawGroup][0] == 0) {
beginEnds[def.drawGroup][0] = i+1;
}
beginEnds[def.drawGroup][1] = i;
}
cancelled = false;
overflow = false;
vertexOffset = 0;
indexOffset = indexSize = 0;
sortingMesh = std::move(renderTranslucent(voxels, beginEnds));
overflow = false;
vertexOffset = 0;
indexOffset = indexSize = 0;
render(voxels, beginEnds);
} }
MeshData BlocksRenderer::createMesh() { ChunkMeshData BlocksRenderer::createMesh() {
const vattr attrs[]{ {3}, {2}, {1}, {0} }; return ChunkMeshData {
return MeshData( MeshData(
util::Buffer<float>(vertexBuffer.get(), vertexOffset), util::Buffer<float>(vertexBuffer.get(), vertexOffset),
util::Buffer<int>(indexBuffer.get(), indexSize), util::Buffer<int>(indexBuffer.get(), indexSize),
util::Buffer<vattr>({{3}, {2}, {1}, {0}}) util::Buffer<VertexAttribute>(
); CHUNK_VATTRS, sizeof(CHUNK_VATTRS) / sizeof(VertexAttribute)
)
),
std::move(sortingMesh)};
} }
std::shared_ptr<Mesh> BlocksRenderer::render(const Chunk* chunk, const ChunksStorage* chunks) { ChunkMesh BlocksRenderer::render(const Chunk* chunk, const Chunks* chunks) {
build(chunk, chunks); build(chunk, chunks);
const vattr attrs[]{ {3}, {2}, {1}, {0} }; size_t vcount = vertexOffset / CHUNK_VERTEX_SIZE;
size_t vcount = vertexOffset / BlocksRenderer::VERTEX_SIZE; return ChunkMesh{std::make_unique<Mesh>(
return std::make_shared<Mesh>( vertexBuffer.get(), vcount, indexBuffer.get(), indexSize, CHUNK_VATTRS
vertexBuffer.get(), vcount, indexBuffer.get(), indexSize, attrs ), std::move(sortingMesh)};
);
} }
VoxelsVolume* BlocksRenderer::getVoxelsBuffer() const { VoxelsVolume* BlocksRenderer::getVoxelsBuffer() const {

View File

@ -12,6 +12,7 @@
#include "voxels/VoxelsVolume.hpp" #include "voxels/VoxelsVolume.hpp"
#include "graphics/core/MeshData.hpp" #include "graphics/core/MeshData.hpp"
#include "maths/util.hpp" #include "maths/util.hpp"
#include "commons.hpp"
class Content; class Content;
class Mesh; class Mesh;
@ -19,15 +20,14 @@ class Block;
class Chunk; class Chunk;
class Chunks; class Chunks;
class VoxelsVolume; class VoxelsVolume;
class ChunksStorage; class Chunks;
class ContentGfxCache; class ContentGfxCache;
struct EngineSettings; struct EngineSettings;
struct UVRegion; struct UVRegion;
class BlocksRenderer { class BlocksRenderer {
static const glm::vec3 SUN_VECTOR; static const glm::vec3 SUN_VECTOR;
static const uint VERTEX_SIZE; const Content& content;
const Content* const content;
std::unique_ptr<float[]> vertexBuffer; std::unique_ptr<float[]> vertexBuffer;
std::unique_ptr<int[]> indexBuffer; std::unique_ptr<int[]> indexBuffer;
size_t vertexOffset; size_t vertexOffset;
@ -40,11 +40,13 @@ class BlocksRenderer {
std::unique_ptr<VoxelsVolume> voxelsBuffer; std::unique_ptr<VoxelsVolume> voxelsBuffer;
const Block* const* blockDefsCache; const Block* const* blockDefsCache;
const ContentGfxCache* const cache; const ContentGfxCache& cache;
const EngineSettings* settings; const EngineSettings& settings;
util::PseudoRandom randomizer; util::PseudoRandom randomizer;
SortingMeshData sortingMesh;
void vertex(const glm::vec3& coord, float u, float v, const glm::vec4& light); void vertex(const glm::vec3& coord, float u, float v, const glm::vec4& light);
void index(int a, int b, int c, int d, int e, int f); void index(int a, int b, int c, int d, int e, int f);
@ -115,7 +117,6 @@ class BlocksRenderer {
bool isOpenForLight(int x, int y, int z) const; bool isOpenForLight(int x, int y, int z) const;
// Does block allow to see other blocks sides (is it transparent) // Does block allow to see other blocks sides (is it transparent)
inline bool isOpen(const glm::ivec3& pos, ubyte group) const { inline bool isOpen(const glm::ivec3& pos, ubyte group) const {
auto id = voxelsBuffer->pickBlockId( auto id = voxelsBuffer->pickBlockId(
@ -135,14 +136,21 @@ class BlocksRenderer {
glm::vec4 pickLight(const glm::ivec3& coord) const; glm::vec4 pickLight(const glm::ivec3& coord) const;
glm::vec4 pickSoftLight(const glm::ivec3& coord, const glm::ivec3& right, const glm::ivec3& up) const; glm::vec4 pickSoftLight(const glm::ivec3& coord, const glm::ivec3& right, const glm::ivec3& up) const;
glm::vec4 pickSoftLight(float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up) const; glm::vec4 pickSoftLight(float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up) const;
void render(const voxel* voxels);
void render(const voxel* voxels, int beginEnds[256][2]);
SortingMeshData renderTranslucent(const voxel* voxels, int beginEnds[256][2]);
public: public:
BlocksRenderer(size_t capacity, const Content* content, const ContentGfxCache* cache, const EngineSettings* settings); BlocksRenderer(
size_t capacity,
const Content& content,
const ContentGfxCache& cache,
const EngineSettings& settings
);
virtual ~BlocksRenderer(); virtual ~BlocksRenderer();
void build(const Chunk* chunk, const ChunksStorage* chunks); void build(const Chunk* chunk, const Chunks* chunks);
std::shared_ptr<Mesh> render(const Chunk* chunk, const ChunksStorage* chunks); ChunkMesh render(const Chunk* chunk, const Chunks* chunks);
MeshData createMesh(); ChunkMeshData createMesh();
VoxelsVolume* getVoxelsBuffer() const; VoxelsVolume* getVoxelsBuffer() const;
bool isCancelled() const { bool isCancelled() const {

View File

@ -1,32 +1,38 @@
#include "ChunksRenderer.hpp" #include "ChunksRenderer.hpp"
#include "BlocksRenderer.hpp" #include "BlocksRenderer.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Mesh.hpp" #include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "window/Camera.hpp"
#include "maths/FrustumCulling.hpp"
#include "util/listutil.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <iostream>
#include <glm/glm.hpp>
#include <glm/ext.hpp>
static debug::Logger logger("chunks-render"); static debug::Logger logger("chunks-render");
size_t ChunksRenderer::visibleChunks = 0;
class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, RendererResult> { class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, RendererResult> {
Level* level; const Level& level;
BlocksRenderer renderer; BlocksRenderer renderer;
public: public:
RendererWorker( RendererWorker(
Level* level, const Level& level,
const ContentGfxCache* cache, const ContentGfxCache& cache,
const EngineSettings* settings const EngineSettings& settings
) : level(level), ) : level(level),
renderer(settings->graphics.chunkMaxVertices.get(), renderer(settings.graphics.chunkMaxVertices.get(),
level->content, cache, settings) *level.content, cache, settings)
{} {}
RendererResult operator()(const std::shared_ptr<Chunk>& chunk) override { RendererResult operator()(const std::shared_ptr<Chunk>& chunk) override {
renderer.build(chunk.get(), level->chunksStorage.get()); renderer.build(chunk.get(), level.chunks.get());
if (renderer.isCancelled()) { if (renderer.isCancelled()) {
return RendererResult { return RendererResult {
glm::ivec2(chunk->x, chunk->z), true, MeshData()}; glm::ivec2(chunk->x, chunk->z), true, MeshData()};
@ -38,24 +44,33 @@ public:
}; };
ChunksRenderer::ChunksRenderer( ChunksRenderer::ChunksRenderer(
Level* level, const Level* level,
const ContentGfxCache* cache, const Assets& assets,
const EngineSettings* settings const Frustum& frustum,
) : level(level), const ContentGfxCache& cache,
const EngineSettings& settings
) : level(*level),
assets(assets),
frustum(frustum),
settings(settings),
threadPool( threadPool(
"chunks-render-pool", "chunks-render-pool",
[=](){return std::make_shared<RendererWorker>(level, cache, settings);}, [&](){return std::make_shared<RendererWorker>(*level, cache, settings);},
[=](RendererResult& result){ [&](RendererResult& result){
if (!result.cancelled) { if (!result.cancelled) {
meshes[result.key] = std::make_shared<Mesh>(result.meshData); auto meshData = std::move(result.meshData);
meshes[result.key] = ChunkMesh {
std::make_unique<Mesh>(meshData.mesh),
std::move(meshData.sortingMesh)
};
} }
inwork.erase(result.key); inwork.erase(result.key);
}, settings->graphics.chunkMaxRenderers.get()) }, settings.graphics.chunkMaxRenderers.get())
{ {
threadPool.setStopOnFail(false); threadPool.setStopOnFail(false);
renderer = std::make_unique<BlocksRenderer>( renderer = std::make_unique<BlocksRenderer>(
settings->graphics.chunkMaxVertices.get(), settings.graphics.chunkMaxVertices.get(),
level->content, cache, settings *level->content, cache, settings
); );
logger.info() << "created " << threadPool.getWorkersCount() << " workers"; logger.info() << "created " << threadPool.getWorkersCount() << " workers";
} }
@ -63,12 +78,16 @@ ChunksRenderer::ChunksRenderer(
ChunksRenderer::~ChunksRenderer() { ChunksRenderer::~ChunksRenderer() {
} }
std::shared_ptr<Mesh> ChunksRenderer::render(const std::shared_ptr<Chunk>& chunk, bool important) { const Mesh* ChunksRenderer::render(
const std::shared_ptr<Chunk>& chunk, bool important
) {
chunk->flags.modified = false; chunk->flags.modified = false;
if (important) { if (important) {
auto mesh = renderer->render(chunk.get(), level->chunksStorage.get()); auto mesh = renderer->render(chunk.get(), level.chunks.get());
meshes[glm::ivec2(chunk->x, chunk->z)] = mesh; meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh {
return mesh; std::move(mesh.mesh), std::move(mesh.sortingMeshData)
};
return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get();
} }
glm::ivec2 key(chunk->x, chunk->z); glm::ivec2 key(chunk->x, chunk->z);
if (inwork.find(key) != inwork.end()) { if (inwork.find(key) != inwork.end()) {
@ -92,7 +111,9 @@ void ChunksRenderer::clear() {
threadPool.clearQueue(); threadPool.clearQueue();
} }
std::shared_ptr<Mesh> ChunksRenderer::getOrRender(const std::shared_ptr<Chunk>& chunk, bool important) { const Mesh* ChunksRenderer::getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important
) {
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end()) { if (found == meshes.end()) {
return render(chunk, important); return render(chunk, important);
@ -100,17 +121,180 @@ std::shared_ptr<Mesh> ChunksRenderer::getOrRender(const std::shared_ptr<Chunk>&
if (chunk->flags.modified) { if (chunk->flags.modified) {
render(chunk, important); render(chunk, important);
} }
return found->second; return found->second.mesh.get();
}
std::shared_ptr<Mesh> ChunksRenderer::get(Chunk* chunk) {
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found != meshes.end()) {
return found->second;
}
return nullptr;
} }
void ChunksRenderer::update() { void ChunksRenderer::update() {
threadPool.update(); threadPool.update();
} }
const Mesh* ChunksRenderer::retrieveChunk(
size_t index, const Camera& camera, Shader& shader, bool culling
) {
auto chunk = level.chunks->getChunks()[index];
if (chunk == nullptr || !chunk->flags.lighted) {
return nullptr;
}
float distance = glm::distance(
camera.position,
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
camera.position.y,
(chunk->z + 0.5f) * CHUNK_D
)
);
auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f);
if (mesh == nullptr) {
return nullptr;
}
if (culling) {
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
glm::vec3 max(
chunk->x * CHUNK_W + CHUNK_W,
chunk->top,
chunk->z * CHUNK_D + CHUNK_D
);
if (!frustum.isBoxVisible(min, max)) return nullptr;
}
return mesh;
}
void ChunksRenderer::drawChunks(
const Camera& camera, Shader& shader
) {
const auto& chunks = *level.chunks;
const auto& atlas = assets.require<Atlas>("blocks");
atlas.getTexture()->bind();
update();
// [warning] this whole method is not thread-safe for chunks
int chunksWidth = chunks.getWidth();
int chunksOffsetX = chunks.getOffsetX();
int chunksOffsetY = chunks.getOffsetY();
if (indices.size() != chunks.getVolume()) {
indices.clear();
for (int i = 0; i < chunks.getVolume(); i++) {
indices.push_back(ChunksSortEntry {i, 0});
}
}
float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f;
float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f;
for (auto& index : indices) {
float x = index.index % chunksWidth + chunksOffsetX - px;
float z = index.index / chunksWidth + chunksOffsetY - pz;
index.d = (x * x + z * z) * 1024;
}
util::insertion_sort(indices.begin(), indices.end());
bool culling = settings.graphics.frustumCulling.get();
visibleChunks = 0;
shader.uniform1i("u_alphaClip", true);
// TODO: minimize draw calls number
for (size_t i = 0; i < indices.size(); i++) {
auto chunk = chunks.getChunks()[indices[i].index];
auto mesh = retrieveChunk(indices[i].index, camera, shader, culling);
if (mesh) {
glm::vec3 coord(
chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f
);
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
shader.uniformMatrix("u_model", model);
mesh->draw();
visibleChunks++;
}
}
}
static inline void write_sorting_mesh_entries(
float* buffer, const std::vector<SortingMeshEntry>& chunkEntries
) {
for (const auto& entry : chunkEntries) {
const auto& vertexData = entry.vertexData;
std::memcpy(
buffer,
vertexData.data(),
vertexData.size() * sizeof(float)
);
buffer += vertexData.size();
}
}
void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) {
const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL;
static int frameid = 0;
frameid++;
bool culling = settings.graphics.frustumCulling.get();
const auto& chunks = level.chunks->getChunks();
const auto& cameraPos = camera.position;
const auto& atlas = assets.require<Atlas>("blocks");
atlas.getTexture()->bind();
shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader.uniform1i("u_alphaClip", false);
for (const auto& index : indices) {
const auto& chunk = chunks[index.index];
if (chunk == nullptr || !chunk->flags.lighted) {
continue;
}
const auto& found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end() || found->second.sortingMeshData.entries.empty()) {
continue;
}
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
glm::vec3 max(
chunk->x * CHUNK_W + CHUNK_W,
chunk->top,
chunk->z * CHUNK_D + CHUNK_D
);
if (!frustum.isBoxVisible(min, max)) continue;
auto& chunkEntries = found->second.sortingMeshData.entries;
if (chunkEntries.size() == 1) {
auto& entry = chunkEntries.at(0);
if (found->second.sortedMesh == nullptr) {
found->second.sortedMesh = std::make_unique<Mesh>(
entry.vertexData.data(),
entry.vertexData.size() / CHUNK_VERTEX_SIZE,
CHUNK_VATTRS
);
}
found->second.sortedMesh->draw();
continue;
}
for (auto& entry : chunkEntries) {
entry.distance = static_cast<long long>(
glm::distance2(entry.position, cameraPos)
);
}
if (found->second.sortedMesh == nullptr ||
(frameid + chunk->x) % sortInterval == 0) {
std::sort(chunkEntries.begin(), chunkEntries.end());
size_t size = 0;
for (const auto& entry : chunkEntries) {
size += entry.vertexData.size();
}
static util::Buffer<float> buffer;
if (buffer.size() < size) {
buffer = util::Buffer<float>(size);
}
write_sorting_mesh_entries(buffer.data(), chunkEntries);
found->second.sortedMesh = std::make_unique<Mesh>(
buffer.data(), size / CHUNK_VERTEX_SIZE, CHUNK_VATTRS
);
}
found->second.sortedMesh->draw();
}
}

View File

@ -4,47 +4,80 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/ChunksStorage.hpp"
#include "util/ThreadPool.hpp" #include "util/ThreadPool.hpp"
#include "graphics/core/MeshData.hpp" #include "graphics/core/MeshData.hpp"
#include "commons.hpp"
class Mesh; class Mesh;
class Chunk; class Chunk;
class Level; class Level;
class Camera;
class Shader;
class Assets;
class Frustum;
class BlocksRenderer; class BlocksRenderer;
class ContentGfxCache; class ContentGfxCache;
struct EngineSettings; struct EngineSettings;
struct ChunksSortEntry {
int index;
int d;
inline bool operator<(const ChunksSortEntry& o) const noexcept {
return d > o.d;
}
};
struct RendererResult { struct RendererResult {
glm::ivec2 key; glm::ivec2 key;
bool cancelled; bool cancelled;
MeshData meshData; ChunkMeshData meshData;
}; };
class ChunksRenderer { class ChunksRenderer {
Level* level; const Level& level;
std::unique_ptr<BlocksRenderer> renderer; const Assets& assets;
std::unordered_map<glm::ivec2, std::shared_ptr<Mesh>> meshes; const Frustum& frustum;
std::unordered_map<glm::ivec2, bool> inwork; const EngineSettings& settings;
std::unique_ptr<BlocksRenderer> renderer;
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
std::unordered_map<glm::ivec2, bool> inwork;
std::vector<ChunksSortEntry> indices;
util::ThreadPool<std::shared_ptr<Chunk>, RendererResult> threadPool; util::ThreadPool<std::shared_ptr<Chunk>, RendererResult> threadPool;
const Mesh* retrieveChunk(
size_t index, const Camera& camera, Shader& shader, bool culling
);
public: public:
ChunksRenderer( ChunksRenderer(
Level* level, const Level* level,
const ContentGfxCache* cache, const Assets& assets,
const EngineSettings* settings const Frustum& frustum,
const ContentGfxCache& cache,
const EngineSettings& settings
); );
virtual ~ChunksRenderer(); virtual ~ChunksRenderer();
std::shared_ptr<Mesh> render(const std::shared_ptr<Chunk>& chunk, bool important); const Mesh* render(
const std::shared_ptr<Chunk>& chunk, bool important
);
void unload(const Chunk* chunk); void unload(const Chunk* chunk);
void clear(); void clear();
std::shared_ptr<Mesh> getOrRender(const std::shared_ptr<Chunk>& chunk, bool important); const Mesh* getOrRender(
std::shared_ptr<Mesh> get(Chunk* chunk); const std::shared_ptr<Chunk>& chunk, bool important
);
void drawChunks(const Camera& camera, Shader& shader);
void drawSortedMeshes(const Camera& camera, Shader& shader);
void update(); void update();
static size_t visibleChunks;
}; };

View File

@ -64,6 +64,7 @@ void Emitter::update(
return; return;
} }
} }
timer += delta;
const float maxDistance = preset.maxDistance; const float maxDistance = preset.maxDistance;
if (glm::distance2(position, cameraPosition) > maxDistance * maxDistance) { if (glm::distance2(position, cameraPosition) > maxDistance * maxDistance) {
if (count > 0) { if (count > 0) {
@ -77,7 +78,6 @@ void Emitter::update(
} }
return; return;
} }
timer += delta;
while (count && timer > spawnInterval) { while (count && timer > spawnInterval) {
// spawn particle // spawn particle
Particle particle = prototype; Particle particle = prototype;

View File

@ -0,0 +1,109 @@
#include "GuidesRenderer.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "graphics/core/Shader.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/DrawContext.hpp"
#include "maths/voxmaths.hpp"
#include "window/Camera.hpp"
#include "constants.hpp"
void GuidesRenderer::drawBorders(
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
) {
int ww = ex - sx;
int dd = ez - sz;
/*corner*/ {
batch.line(sx, sy, sz, sx, ey, sz, 0.8f, 0, 0.8f, 1);
batch.line(sx, sy, ez, sx, ey, ez, 0.8f, 0, 0.8f, 1);
batch.line(ex, sy, sz, ex, ey, sz, 0.8f, 0, 0.8f, 1);
batch.line(ex, sy, ez, ex, ey, ez, 0.8f, 0, 0.8f, 1);
}
for (int i = 2; i < ww; i += 2) {
batch.line(sx + i, sy, sz, sx + i, ey, sz, 0, 0, 0.8f, 1);
batch.line(sx + i, sy, ez, sx + i, ey, ez, 0, 0, 0.8f, 1);
}
for (int i = 2; i < dd; i += 2) {
batch.line(sx, sy, sz + i, sx, ey, sz + i, 0.8f, 0, 0, 1);
batch.line(ex, sy, sz + i, ex, ey, sz + i, 0.8f, 0, 0, 1);
}
for (int i = sy; i < ey; i += 2) {
batch.line(sx, i, sz, sx, i, ez, 0, 0.8f, 0, 1);
batch.line(sx, i, ez, ex, i, ez, 0, 0.8f, 0, 1);
batch.line(ex, i, ez, ex, i, sz, 0, 0.8f, 0, 1);
batch.line(ex, i, sz, sx, i, sz, 0, 0.8f, 0, 1);
}
batch.flush();
}
void GuidesRenderer::drawCoordSystem(
LineBatch& batch, const DrawContext& pctx, float length
) {
auto ctx = pctx.sub();
ctx.setDepthTest(false);
batch.lineWidth(4.0f);
batch.line(0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
batch.line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 1.f);
batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 1.f);
batch.flush();
ctx.setDepthTest(true);
batch.lineWidth(2.0f);
batch.line(0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 0.f, 1.f);
batch.line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 1.f);
batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
}
void GuidesRenderer::renderDebugLines(
const DrawContext& pctx,
const Camera& camera,
LineBatch& batch,
Shader& linesShader,
bool showChunkBorders
) {
DrawContext ctx = pctx.sub(&batch);
const auto& viewport = ctx.getViewport();
uint displayWidth = viewport.getWidth();
uint displayHeight = viewport.getHeight();
ctx.setDepthTest(true);
linesShader.use();
if (showChunkBorders) {
linesShader.uniformMatrix("u_projview", camera.getProjView());
glm::vec3 coord = camera.position;
if (coord.x < 0) coord.x--;
if (coord.z < 0) coord.z--;
int cx = floordiv(static_cast<int>(coord.x), CHUNK_W);
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
drawBorders(
batch,
cx * CHUNK_W,
0,
cz * CHUNK_D,
(cx + 1) * CHUNK_W,
CHUNK_H,
(cz + 1) * CHUNK_D
);
}
float length = 40.f;
glm::vec3 tsl(displayWidth / 2, displayHeight / 2, 0.f);
glm::mat4 model(glm::translate(glm::mat4(1.f), tsl));
linesShader.uniformMatrix(
"u_projview",
glm::ortho(
0.f,
static_cast<float>(displayWidth),
0.f,
static_cast<float>(displayHeight),
-length,
length
) * model *
glm::inverse(camera.rotation)
);
drawCoordSystem(batch, ctx, length);
}

View File

@ -0,0 +1,28 @@
#pragma once
class LineBatch;
class DrawContext;
class Camera;
class Shader;
class GuidesRenderer {
public:
void drawBorders(
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
);
void drawCoordSystem(
LineBatch& batch, const DrawContext& pctx, float length
);
/// @brief Render all debug lines (chunks borders, coord system guides)
/// @param context graphics context
/// @param camera active camera
/// @param linesShader shader used
void renderDebugLines(
const DrawContext& context,
const Camera& camera,
LineBatch& batch,
Shader& linesShader,
bool showChunkBorders
);
};

View File

@ -6,7 +6,7 @@
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
static const vattr attrs[] = { static const VertexAttribute attrs[] = {
{3}, {2}, {3}, {1}, {0} {3}, {2}, {3}, {1}, {0}
}; };

View File

@ -47,9 +47,9 @@ static glm::mat4 extract_rotation(glm::mat4 matrix) {
ModelBatch::ModelBatch( ModelBatch::ModelBatch(
size_t capacity, size_t capacity,
Assets* assets, const Assets& assets,
Chunks* chunks, const Chunks& chunks,
const EngineSettings* settings const EngineSettings& settings
) )
: batch(std::make_unique<MainBatch>(capacity)), : batch(std::make_unique<MainBatch>(capacity)),
assets(assets), assets(assets),
@ -73,7 +73,7 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix,
if (mesh.lighting) { if (mesh.lighting) {
glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
gpos += lightsOffset; gpos += lightsOffset;
lights = MainBatch::sampleLight(gpos, *chunks, backlight); lights = MainBatch::sampleLight(gpos, chunks, backlight);
} }
for (size_t i = 0; i < vcount / 3; i++) { for (size_t i = 0; i < vcount / 3; i++) {
batch->prepare(3); batch->prepare(3);
@ -107,7 +107,7 @@ void ModelBatch::render() {
return a.mesh->texture < b.mesh->texture; return a.mesh->texture < b.mesh->texture;
} }
); );
bool backlight = settings->graphics.backlight.get(); bool backlight = settings.graphics.backlight.get();
for (auto& entry : entries) { for (auto& entry : entries) {
draw( draw(
*entry.mesh, *entry.mesh,
@ -136,6 +136,6 @@ void ModelBatch::setTexture(const std::string& name,
return setTexture(found->second, varTextures); return setTexture(found->second, varTextures);
} }
} }
auto region = util::get_texture_region(*assets, name, "blocks:notfound"); auto region = util::get_texture_region(assets, name, "blocks:notfound");
batch->setTexture(region.texture, region.region); batch->setTexture(region.texture, region.region);
} }

View File

@ -23,10 +23,10 @@ namespace model {
using texture_names_map = std::unordered_map<std::string, std::string>; using texture_names_map = std::unordered_map<std::string, std::string>;
class ModelBatch { class ModelBatch {
Assets* assets; const Assets& assets;
Chunks* chunks; const Chunks& chunks;
const EngineSettings* settings; const EngineSettings& settings;
glm::vec3 lightsOffset {}; glm::vec3 lightsOffset {};
static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f}; static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f};
@ -39,6 +39,7 @@ class ModelBatch {
glm::vec3 tint, glm::vec3 tint,
const texture_names_map* varTextures, const texture_names_map* varTextures,
bool backlight); bool backlight);
void setTexture(const std::string& name, void setTexture(const std::string& name,
const texture_names_map* varTextures); const texture_names_map* varTextures);
@ -53,9 +54,9 @@ class ModelBatch {
public: public:
ModelBatch( ModelBatch(
size_t capacity, size_t capacity,
Assets* assets, const Assets& assets,
Chunks* chunks, const Chunks& chunks,
const EngineSettings* settings const EngineSettings& settings
); );
~ModelBatch(); ~ModelBatch();

View File

@ -60,43 +60,42 @@ model::Model ModelsGenerator::fromCustom(
auto& mesh = model.addMesh("blocks:" + modelTextures[i * 6]); auto& mesh = model.addMesh("blocks:" + modelTextures[i * 6]);
mesh.lighting = lighting; mesh.lighting = lighting;
const UVRegion boxtexfaces[6] = { const UVRegion boxtexfaces[6] = {
get_region_for(modelTextures[i * 6], assets), get_region_for(modelTextures[i * 6 + 5], assets),
get_region_for(modelTextures[i * 6 + 1], assets),
get_region_for(modelTextures[i * 6 + 2], assets),
get_region_for(modelTextures[i * 6 + 3], assets),
get_region_for(modelTextures[i * 6 + 4], assets), get_region_for(modelTextures[i * 6 + 4], assets),
get_region_for(modelTextures[i * 6 + 5], assets) get_region_for(modelTextures[i * 6 + 3], assets),
get_region_for(modelTextures[i * 6 + 2], assets),
get_region_for(modelTextures[i * 6 + 1], assets),
get_region_for(modelTextures[i * 6 + 0], assets)
}; };
mesh.addBox( mesh.addBox(
modelBoxes[i].center(), modelBoxes[i].size() * 0.5f, boxtexfaces modelBoxes[i].center(), modelBoxes[i].size() * 0.5f, boxtexfaces
); );
} }
glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 norm {0, 1, 0}; glm::vec3 norm {0, 1, 0};
for (size_t i = 0; i < points.size() / 4; i++) { for (size_t i = 0; i < points.size() / 4; i++) {
auto texture = "blocks:" + modelTextures[modelBoxes.size() * 6 + i]; auto texture = modelTextures[modelBoxes.size() * 6 + i];
auto& mesh = model.addMesh(texture); auto& mesh = model.addMesh(texture);
mesh.lighting = lighting; mesh.lighting = lighting;
auto reg = get_region_for(texture, assets); auto reg = get_region_for(texture, assets);
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 0] - poff, glm::vec2(reg.u1, reg.v1), norm} {points[i * 4 + 0], glm::vec2(reg.u1, reg.v1), norm}
); );
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 1] - poff, glm::vec2(reg.u2, reg.v1), norm} {points[i * 4 + 1], glm::vec2(reg.u2, reg.v1), norm}
); );
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 2] - poff, glm::vec2(reg.u2, reg.v2), norm} {points[i * 4 + 2], glm::vec2(reg.u2, reg.v2), norm}
); );
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 3] - poff, glm::vec2(reg.u1, reg.v1), norm} {points[i * 4 + 0], glm::vec2(reg.u1, reg.v1), norm}
); );
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 4] - poff, glm::vec2(reg.u2, reg.v2), norm} {points[i * 4 + 2], glm::vec2(reg.u2, reg.v2), norm}
); );
mesh.vertices.push_back( mesh.vertices.push_back(
{points[i * 4 + 0] - poff, glm::vec2(reg.u1, reg.v2), norm} {points[i * 4 + 3], glm::vec2(reg.u1, reg.v2), norm}
); );
} }
return model; return model;

View File

@ -18,7 +18,7 @@ size_t ParticlesRenderer::aliveEmitters = 0;
ParticlesRenderer::ParticlesRenderer( ParticlesRenderer::ParticlesRenderer(
const Assets& assets, const Level& level, const GraphicsSettings* settings const Assets& assets, const Level& level, const GraphicsSettings* settings
) )
: batch(std::make_unique<MainBatch>(1024)), : batch(std::make_unique<MainBatch>(4096)),
level(level), level(level),
assets(assets), assets(assets),
settings(settings) {} settings(settings) {}

View File

@ -15,6 +15,7 @@
#include <iostream> #include <iostream>
#include <GL/glew.h> #include <GL/glew.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#ifndef M_PI #ifndef M_PI
#define M_PI 3.141592 #define M_PI 3.141592
@ -23,7 +24,7 @@
const int STARS_COUNT = 3000; const int STARS_COUNT = 3000;
const int STARS_SEED = 632; const int STARS_SEED = 632;
Skybox::Skybox(uint size, Shader* shader) Skybox::Skybox(uint size, Shader& shader)
: size(size), : size(size),
shader(shader), shader(shader),
batch3d(std::make_unique<Batch3D>(4096)) batch3d(std::make_unique<Batch3D>(4096))
@ -38,19 +39,19 @@ Skybox::Skybox(uint size, Shader* shader)
-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f
}; };
vattr attrs[] {{2}, {0}}; VertexAttribute attrs[] {{2}, {0}};
mesh = std::make_unique<Mesh>(vertices, 6, attrs); mesh = std::make_unique<Mesh>(vertices, 6, attrs);
sprites.push_back(skysprite { sprites.push_back(skysprite {
"misc/moon", "misc/moon",
M_PI*0.5f, glm::pi<float>()*0.5f,
4.0f, 4.0f,
false false
}); });
sprites.push_back(skysprite { sprites.push_back(skysprite {
"misc/sun", "misc/sun",
M_PI*1.5f, glm::pi<float>()*1.5f,
4.0f, 4.0f,
true true
}); });
@ -115,13 +116,13 @@ void Skybox::draw(
p_shader->uniformMatrix("u_apply", glm::mat4(1.0f)); p_shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin(); batch3d->begin();
float angle = daytime * float(M_PI) * 2.0f; float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f-fog, 7.0f); float opacity = glm::pow(1.0f-fog, 7.0f);
for (auto& sprite : sprites) { for (auto& sprite : sprites) {
batch3d->texture(assets.get<Texture>(sprite.texture)); batch3d->texture(assets.get<Texture>(sprite.texture));
float sangle = daytime * float(M_PI)*2.0 + sprite.phase; float sangle = daytime * glm::pi<float>()*2.0 + sprite.phase;
float distance = sprite.distance; float distance = sprite.distance;
glm::vec3 pos(-std::cos(sangle)*distance, std::sin(sangle)*distance, 0); glm::vec3 pos(-std::cos(sangle)*distance, std::sin(sangle)*distance, 0);
@ -152,15 +153,15 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality)
ready = true; ready = true;
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
cubemap->bind(); cubemap->bind();
shader->use(); shader.use();
t *= M_PI*2.0f; t *= glm::pi<float>()*2.0f;
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
shader->uniform1i("u_quality", quality); shader.uniform1i("u_quality", quality);
shader->uniform1f("u_mie", mie); shader.uniform1f("u_mie", mie);
shader->uniform1f("u_fog", mie - 1.0f); shader.uniform1f("u_fog", mie - 1.0f);
shader->uniform3f("u_lightDir", lightDir); shader.uniform3f("u_lightDir", lightDir);
shader->uniform1f("u_dayTime", dayTime); shader.uniform1f("u_dayTime", dayTime);
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) >= 0.01) { if (glm::abs(mie-prevMie) + glm::abs(t-prevT) >= 0.01) {
for (uint face = 0; face < 6; face++) { for (uint face = 0; face < 6; face++) {
@ -206,10 +207,16 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
{0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f},
}; };
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap->getId(), 0); glFramebufferTexture2D(
shader->uniform3f("u_xaxis", xaxs[face]); GL_FRAMEBUFFER,
shader->uniform3f("u_yaxis", yaxs[face]); GL_COLOR_ATTACHMENT0,
shader->uniform3f("u_zaxis", zaxs[face]); GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
0
);
shader.uniform3f("u_xaxis", xaxs[face]);
shader.uniform3f("u_yaxis", yaxs[face]);
shader.uniform3f("u_zaxis", zaxs[face]);
mesh->draw(); mesh->draw();
} }

View File

@ -27,7 +27,7 @@ struct skysprite {
class Skybox { class Skybox {
std::unique_ptr<Framebuffer> fbo; std::unique_ptr<Framebuffer> fbo;
uint size; uint size;
Shader* shader; Shader& shader;
bool ready = false; bool ready = false;
FastRandom random; FastRandom random;
glm::vec3 lightDir; glm::vec3 lightDir;
@ -46,7 +46,7 @@ class Skybox {
); );
void refreshFace(uint face, Cubemap* cubemap); void refreshFace(uint face, Cubemap* cubemap);
public: public:
Skybox(uint size, Shader* shader); Skybox(uint size, Shader& shader);
~Skybox(); ~Skybox();
void draw( void draw(

View File

@ -0,0 +1,47 @@
#include "TextNote.hpp"
TextNote::TextNote(std::wstring text, NotePreset preset, glm::vec3 position)
: text(std::move(text)),
preset(std::move(preset)),
position(std::move(position)) {
}
void TextNote::setText(std::wstring_view text) {
this->text = text;
}
const std::wstring& TextNote::getText() const {
return text;
}
const NotePreset& TextNote::getPreset() const {
return preset;
}
void TextNote::updatePreset(const dv::value& data) {
preset.deserialize(data);
}
void TextNote::setPosition(const glm::vec3& position) {
this->position = position;
}
const glm::vec3& TextNote::getPosition() const {
return position;
}
const glm::vec3& TextNote::getAxisX() const {
return xAxis;
}
const glm::vec3& TextNote::getAxisY() const {
return yAxis;
}
void TextNote::setAxisX(const glm::vec3& vec) {
xAxis = vec;
}
void TextNote::setAxisY(const glm::vec3& vec) {
yAxis = vec;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "presets/NotePreset.hpp"
/// @brief 3D text instance
class TextNote {
std::wstring text;
NotePreset preset;
glm::vec3 position;
glm::vec3 xAxis {1, 0, 0};
glm::vec3 yAxis {0, 1, 0};
public:
TextNote(std::wstring text, NotePreset preset, glm::vec3 position);
void setText(std::wstring_view text);
const std::wstring& getText() const;
const NotePreset& getPreset() const;
void updatePreset(const dv::value& data);
void setPosition(const glm::vec3& position);
const glm::vec3& getPosition() const;
const glm::vec3& getAxisX() const;
const glm::vec3& getAxisY() const;
void setAxisX(const glm::vec3& vec);
void setAxisY(const glm::vec3& vec);
};

View File

@ -0,0 +1,146 @@
#include "TextsRenderer.hpp"
#include "TextNote.hpp"
#include "maths/util.hpp"
#include "assets/Assets.hpp"
#include "window/Camera.hpp"
#include "window/Window.hpp"
#include "maths/FrustumCulling.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Shader.hpp"
#include "presets/NotePreset.hpp"
TextsRenderer::TextsRenderer(
Batch3D& batch, const Assets& assets, const Frustum& frustum
)
: batch(batch), assets(assets), frustum(frustum) {
}
void TextsRenderer::renderNote(
const TextNote& note,
const DrawContext& context,
const Camera& camera,
const EngineSettings& settings,
bool hudVisible,
bool frontLayer,
bool projected
) {
const auto& text = note.getText();
const auto& preset = note.getPreset();
auto pos = note.getPosition();
if (util::distance2(pos, camera.position) >
util::sqr(preset.renderDistance / camera.zoom)) {
return;
}
// Projected notes are displayed on the front layer only
if ((preset.displayMode == NoteDisplayMode::PROJECTED) != projected) {
return;
}
float opacity = 1.0f;
if (frontLayer && preset.displayMode != NoteDisplayMode::PROJECTED) {
if (preset.xrayOpacity <= 0.0001f) {
return;
}
opacity = preset.xrayOpacity;
}
const auto& font = assets.require<Font>("normal");
glm::vec3 xvec = note.getAxisX();
glm::vec3 yvec = note.getAxisY();
int width = font.calcWidth(text, text.length());
if (preset.displayMode == NoteDisplayMode::Y_FREE_BILLBOARD ||
preset.displayMode == NoteDisplayMode::XY_FREE_BILLBOARD) {
xvec = camera.position - pos;
xvec.y = 0;
std::swap(xvec.x, xvec.z);
xvec.z *= -1;
xvec = glm::normalize(xvec);
if (preset.displayMode == NoteDisplayMode::XY_FREE_BILLBOARD) {
yvec = camera.up;
}
}
if (preset.displayMode != NoteDisplayMode::PROJECTED) {
if (!frustum.isBoxVisible(pos - xvec * (width * 0.5f),
pos + xvec * (width * 0.5f))) {
return;
}
} else {
float scale = 1.0f;
if (glm::abs(preset.perspective) > 0.0001f) {
float scale2 = scale /
(glm::distance(camera.position, pos) *
util::sqr(camera.zoom) *
glm::sqrt(glm::tan(camera.getFov() * 0.5f)));
scale = scale2 * preset.perspective +
scale * (1.0f - preset.perspective);
}
auto projpos = camera.getProjView() * glm::vec4(pos, 1.0f);
pos = projpos;
if (pos.z < 0.0f) {
return;
}
pos /= pos.z;
pos.z = 0;
xvec = {2.0f/Window::width*scale, 0, 0};
yvec = {0, 2.0f/Window::height*scale, 0};
}
auto color = preset.color;
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
font.draw(
batch,
text,
pos - xvec * (width * 0.5f) * preset.scale,
xvec * preset.scale,
yvec * preset.scale
);
}
void TextsRenderer::render(
const DrawContext& context,
const Camera& camera,
const EngineSettings& settings,
bool hudVisible,
bool frontLayer
) {
auto& shader = assets.require<Shader>("ui3d");
shader.use();
shader.uniformMatrix("u_projview", camera.getProjView());
shader.uniformMatrix("u_apply", glm::mat4(1.0f));
batch.begin();
for (const auto& [_, note] : notes) {
renderNote(*note, context, camera, settings, hudVisible, frontLayer, false);
}
batch.flush();
if (frontLayer) {
shader.uniformMatrix(
"u_projview",
glm::mat4(1.0f)
);
for (const auto& [_, note] : notes) {
renderNote(*note, context, camera, settings, hudVisible, true, true);
}
batch.flush();
}
}
u64id_t TextsRenderer::add(std::unique_ptr<TextNote> note) {
u64id_t uid = nextNote++;
notes[uid] = std::move(note);
return uid;
}
TextNote* TextsRenderer::get(u64id_t id) const {
const auto& found = notes.find(id);
if (found == notes.end()) {
return nullptr;
}
return found->second.get();
}
void TextsRenderer::remove(u64id_t id) {
notes.erase(id);
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <unordered_map>
#include <memory>
#include "typedefs.hpp"
class DrawContext;
class Camera;
class Assets;
class Batch3D;
class Frustum;
class TextNote;
struct EngineSettings;
class TextsRenderer {
Batch3D& batch;
const Assets& assets;
const Frustum& frustum;
std::unordered_map<u64id_t, std::unique_ptr<TextNote>> notes;
u64id_t nextNote = 1;
void renderNote(
const TextNote& note,
const DrawContext& context,
const Camera& camera,
const EngineSettings& settings,
bool hudVisible,
bool frontLayer,
bool projected
);
public:
TextsRenderer(Batch3D& batch, const Assets& assets, const Frustum& frustum);
void render(
const DrawContext& context,
const Camera& camera,
const EngineSettings& settings,
bool hudVisible,
bool frontLayer
);
u64id_t add(std::unique_ptr<TextNote> note);
TextNote* get(u64id_t id) const;
void remove(u64id_t id);
};

View File

@ -40,160 +40,94 @@
#include "graphics/core/PostProcessing.hpp" #include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "ParticlesRenderer.hpp" #include "ParticlesRenderer.hpp"
#include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp" #include "ChunksRenderer.hpp"
#include "GuidesRenderer.hpp"
#include "ModelBatch.hpp" #include "ModelBatch.hpp"
#include "Skybox.hpp" #include "Skybox.hpp"
#include "Emitter.hpp" #include "Emitter.hpp"
#include "TextNote.hpp"
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false; bool WorldRenderer::showEntitiesDebug = false;
WorldRenderer::WorldRenderer( WorldRenderer::WorldRenderer(
Engine* engine, LevelFrontend* frontend, Player* player Engine* engine, LevelFrontend& frontend, Player* player
) )
: engine(engine), : engine(engine),
level(frontend->getLevel()), level(frontend.getLevel()),
player(player), player(player),
assets(*engine->getAssets()),
frustumCulling(std::make_unique<Frustum>()), frustumCulling(std::make_unique<Frustum>()),
lineBatch(std::make_unique<LineBatch>()), lineBatch(std::make_unique<LineBatch>()),
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
modelBatch(std::make_unique<ModelBatch>( modelBatch(std::make_unique<ModelBatch>(
20'000, MODEL_BATCH_CAPACITY, assets, *level.chunks, engine->getSettings()
engine->getAssets(),
level->chunks.get(),
&engine->getSettings()
)), )),
particles(std::make_unique<ParticlesRenderer>( particles(std::make_unique<ParticlesRenderer>(
*engine->getAssets(), assets, level, &engine->getSettings().graphics
*frontend->getLevel(), )),
&engine->getSettings().graphics texts(std::make_unique<TextsRenderer>(*batch3d, assets, *frustumCulling)),
guides(std::make_unique<GuidesRenderer>()),
chunks(std::make_unique<ChunksRenderer>(
&level,
assets,
*frustumCulling,
frontend.getContentGfxCache(),
engine->getSettings()
)) { )) {
renderer = std::make_unique<ChunksRenderer>(
level, frontend->getContentGfxCache(), &engine->getSettings()
);
batch3d = std::make_unique<Batch3D>(4096);
auto& settings = engine->getSettings(); auto& settings = engine->getSettings();
level->events->listen( level.events->listen(
EVT_CHUNK_HIDDEN, EVT_CHUNK_HIDDEN,
[this](lvl_event_type, Chunk* chunk) { renderer->unload(chunk); } [this](lvl_event_type, Chunk* chunk) { chunks->unload(chunk); }
); );
auto assets = engine->getAssets(); auto assets = engine->getAssets();
skybox = std::make_unique<Skybox>( skybox = std::make_unique<Skybox>(
settings.graphics.skyboxResolution.get(), settings.graphics.skyboxResolution.get(),
assets->get<Shader>("skybox_gen") assets->require<Shader>("skybox_gen")
); );
} }
WorldRenderer::~WorldRenderer() = default; WorldRenderer::~WorldRenderer() = default;
bool WorldRenderer::drawChunk(
size_t index, const Camera& camera, Shader* shader, bool culling
) {
auto chunk = level->chunks->getChunks()[index];
if (!chunk->flags.lighted) {
return false;
}
float distance = glm::distance(
camera.position,
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
camera.position.y,
(chunk->z + 0.5f) * CHUNK_D
)
);
auto mesh = renderer->getOrRender(chunk, distance < CHUNK_W * 1.5f);
if (mesh == nullptr) {
return false;
}
if (culling) {
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
glm::vec3 max(
chunk->x * CHUNK_W + CHUNK_W,
chunk->top,
chunk->z * CHUNK_D + CHUNK_D
);
if (!frustumCulling->isBoxVisible(min, max)) return false;
}
glm::vec3 coord(chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f);
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
shader->uniformMatrix("u_model", model);
mesh->draw();
return true;
}
void WorldRenderer::drawChunks(
Chunks* chunks, const Camera& camera, Shader* shader
) {
auto assets = engine->getAssets();
auto atlas = assets->get<Atlas>("blocks");
atlas->getTexture()->bind();
renderer->update();
// [warning] this whole method is not thread-safe for chunks
std::vector<size_t> indices;
for (size_t i = 0; i < chunks->getVolume(); i++) {
if (chunks->getChunks()[i] == nullptr) continue;
indices.emplace_back(i);
}
float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f;
float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f;
std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) {
const auto& chunksBuffer = chunks->getChunks();
const auto a = chunksBuffer[i].get();
const auto b = chunksBuffer[j].get();
auto adx = (a->x - px);
auto adz = (a->z - pz);
auto bdx = (b->x - px);
auto bdz = (b->z - pz);
return (adx * adx + adz * adz > bdx * bdx + bdz * bdz);
});
bool culling = engine->getSettings().graphics.frustumCulling.get();
if (culling) {
frustumCulling->update(camera.getProjView());
}
chunks->visible = 0;
for (size_t i = 0; i < indices.size(); i++) {
chunks->visible += drawChunk(indices[i], camera, shader, culling);
}
}
void WorldRenderer::setupWorldShader( void WorldRenderer::setupWorldShader(
Shader* shader, Shader& shader,
const Camera& camera, const Camera& camera,
const EngineSettings& settings, const EngineSettings& settings,
float fogFactor float fogFactor
) { ) {
shader->use(); shader.use();
shader->uniformMatrix("u_model", glm::mat4(1.0f)); shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader->uniformMatrix("u_proj", camera.getProjection()); shader.uniformMatrix("u_proj", camera.getProjection());
shader->uniformMatrix("u_view", camera.getView()); shader.uniformMatrix("u_view", camera.getView());
shader->uniform1f("u_timer", timer); shader.uniform1f("u_timer", timer);
shader->uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_gamma", settings.graphics.gamma.get());
shader->uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogFactor", fogFactor);
shader->uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader->uniform1f("u_dayTime", level->getWorld()->getInfo().daytime); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader->uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform2f("u_lightDir", skybox->getLightDir());
shader->uniform3f("u_cameraPos", camera.position); shader.uniform3f("u_cameraPos", camera.position);
shader->uniform1i("u_cubemap", 1); shader.uniform1i("u_cubemap", 1);
auto indices = level->content->getIndices(); auto indices = level.content->getIndices();
// Light emission when an emissive item is chosen // Light emission when an emissive item is chosen
{ {
auto inventory = player->getInventory(); auto inventory = player->getInventory();
ItemStack& stack = inventory->getSlot(player->getChosenSlot()); ItemStack& stack = inventory->getSlot(player->getChosenSlot());
auto& item = indices->items.require(stack.getItemId()); auto& item = indices->items.require(stack.getItemId());
float multiplier = 0.5f; float multiplier = 0.5f;
shader->uniform3f( shader.uniform3f(
"u_torchlightColor", "u_torchlightColor",
item.emission[0] / 15.0f * multiplier, item.emission[0] / 15.0f * multiplier,
item.emission[1] / 15.0f * multiplier, item.emission[1] / 15.0f * multiplier,
item.emission[2] / 15.0f * multiplier item.emission[2] / 15.0f * multiplier
); );
shader->uniform1f("u_torchlightDistance", 6.0f); shader.uniform1f("u_torchlightDistance", 6.0f);
} }
} }
@ -202,32 +136,45 @@ void WorldRenderer::renderLevel(
const Camera& camera, const Camera& camera,
const EngineSettings& settings, const EngineSettings& settings,
float delta, float delta,
bool pause bool pause,
bool hudVisible
) { ) {
auto assets = engine->getAssets(); texts->render(ctx, camera, settings, hudVisible, false);
bool culling = engine->getSettings().graphics.frustumCulling.get(); bool culling = engine->getSettings().graphics.frustumCulling.get();
float fogFactor = float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2); 15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
auto entityShader = assets->get<Shader>("entity"); auto& entityShader = assets.require<Shader>("entity");
setupWorldShader(entityShader, camera, settings, fogFactor); setupWorldShader(entityShader, camera, settings, fogFactor);
skybox->bind(); skybox->bind();
level->entities->render( if (culling) {
frustumCulling->update(camera.getProjView());
}
level.entities->render(
assets, assets,
*modelBatch, *modelBatch,
culling ? frustumCulling.get() : nullptr, culling ? frustumCulling.get() : nullptr,
delta, delta,
pause pause
); );
particles->render(camera, delta * !pause);
modelBatch->render(); modelBatch->render();
particles->render(camera, delta * !pause);
auto& shader = assets.require<Shader>("main");
auto& linesShader = assets.require<Shader>("lines");
auto shader = assets->get<Shader>("main");
setupWorldShader(shader, camera, settings, fogFactor); setupWorldShader(shader, camera, settings, fogFactor);
drawChunks(level->chunks.get(), camera, shader); chunks->drawChunks(camera, shader);
if (hudVisible) {
renderLines(camera, linesShader, ctx);
}
shader.use();
chunks->drawSortedMeshes(camera, shader);
if (!pause) { if (!pause) {
scripting::on_frontend_render(); scripting::on_frontend_render();
@ -238,7 +185,7 @@ void WorldRenderer::renderLevel(
void WorldRenderer::renderBlockSelection() { void WorldRenderer::renderBlockSelection() {
const auto& selection = player->selection; const auto& selection = player->selection;
auto indices = level->content->getIndices(); auto indices = level.content->getIndices();
blockid_t id = selection.vox.id; blockid_t id = selection.vox.id;
auto& block = indices->blocks.require(id); auto& block = indices->blocks.require(id);
const glm::ivec3 pos = player->selection.position; const glm::ivec3 pos = player->selection.position;
@ -254,7 +201,7 @@ void WorldRenderer::renderBlockSelection() {
const glm::vec3 center = glm::vec3(pos) + hitbox.center(); const glm::vec3 center = glm::vec3(pos) + hitbox.center();
const glm::vec3 size = hitbox.size(); const glm::vec3 size = hitbox.size();
lineBatch->box( lineBatch->box(
center, size + glm::vec3(0.02), glm::vec4(0.f, 0.f, 0.f, 0.5f) center, size + glm::vec3(0.01), glm::vec4(0.f, 0.f, 0.f, 0.5f)
); );
if (player->debug) { if (player->debug) {
lineBatch->line( lineBatch->line(
@ -266,87 +213,27 @@ void WorldRenderer::renderBlockSelection() {
} }
void WorldRenderer::renderLines( void WorldRenderer::renderLines(
const Camera& camera, Shader* linesShader, const DrawContext& pctx const Camera& camera, Shader& linesShader, const DrawContext& pctx
) { ) {
linesShader->use(); linesShader.use();
linesShader->uniformMatrix("u_projview", camera.getProjView()); linesShader.uniformMatrix("u_projview", camera.getProjView());
if (player->selection.vox.id != BLOCK_VOID) { if (player->selection.vox.id != BLOCK_VOID) {
renderBlockSelection(); renderBlockSelection();
} }
if (player->debug && showEntitiesDebug) { if (player->debug && showEntitiesDebug) {
auto ctx = pctx.sub(lineBatch.get()); auto ctx = pctx.sub(lineBatch.get());
bool culling = engine->getSettings().graphics.frustumCulling.get(); bool culling = engine->getSettings().graphics.frustumCulling.get();
level->entities->renderDebug( level.entities->renderDebug(
*lineBatch, culling ? frustumCulling.get() : nullptr, ctx *lineBatch, culling ? frustumCulling.get() : nullptr, ctx
); );
} }
} }
void WorldRenderer::renderDebugLines(
const DrawContext& pctx, const Camera& camera, Shader* linesShader
) {
DrawContext ctx = pctx.sub(lineBatch.get());
const auto& viewport = ctx.getViewport();
uint displayWidth = viewport.getWidth();
uint displayHeight = viewport.getHeight();
ctx.setDepthTest(true);
linesShader->use();
if (showChunkBorders) {
linesShader->uniformMatrix("u_projview", camera.getProjView());
glm::vec3 coord = player->fpCamera->position;
if (coord.x < 0) coord.x--;
if (coord.z < 0) coord.z--;
int cx = floordiv(static_cast<int>(coord.x), CHUNK_W);
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
drawBorders(
cx * CHUNK_W,
0,
cz * CHUNK_D,
(cx + 1) * CHUNK_W,
CHUNK_H,
(cz + 1) * CHUNK_D
);
}
float length = 40.f;
glm::vec3 tsl(displayWidth / 2, displayHeight / 2, 0.f);
glm::mat4 model(glm::translate(glm::mat4(1.f), tsl));
linesShader->uniformMatrix(
"u_projview",
glm::ortho(
0.f,
static_cast<float>(displayWidth),
0.f,
static_cast<float>(displayHeight),
-length,
length
) * model *
glm::inverse(camera.rotation)
);
ctx.setDepthTest(false);
lineBatch->lineWidth(4.0f);
lineBatch->line(0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
lineBatch->line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 1.f);
lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 1.f);
lineBatch->flush();
ctx.setDepthTest(true);
lineBatch->lineWidth(2.0f);
lineBatch->line(0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 0.f, 1.f);
lineBatch->line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 1.f);
lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
}
void WorldRenderer::renderHands( void WorldRenderer::renderHands(
const Camera& camera, const Assets& assets, float delta const Camera& camera, float delta
) { ) {
auto entityShader = assets.get<Shader>("entity"); auto& entityShader = assets.require<Shader>("entity");
auto indices = level->content->getIndices(); auto indices = level.content->getIndices();
// get current chosen item // get current chosen item
const auto& inventory = player->getInventory(); const auto& inventory = player->getInventory();
@ -414,7 +301,7 @@ void WorldRenderer::draw(
PostProcessing* postProcessing PostProcessing* postProcessing
) { ) {
timer += delta * !pause; timer += delta * !pause;
auto world = level->getWorld(); auto world = level.getWorld();
const Viewport& vp = pctx.getViewport(); const Viewport& vp = pctx.getViewport();
camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight()); camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight());
@ -424,10 +311,9 @@ void WorldRenderer::draw(
skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4); skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4);
const auto& assets = *engine->getAssets(); const auto& assets = *engine->getAssets();
auto linesShader = assets.get<Shader>("lines"); auto& linesShader = assets.require<Shader>("lines");
// World render scope with diegetic HUD included /* World render scope with diegetic HUD included */ {
{
DrawContext wctx = pctx.sub(); DrawContext wctx = pctx.sub();
postProcessing->use(wctx); postProcessing->use(wctx);
@ -436,24 +322,28 @@ void WorldRenderer::draw(
// Drawing background sky plane // Drawing background sky plane
skybox->draw(pctx, camera, assets, worldInfo.daytime, worldInfo.fog); skybox->draw(pctx, camera, assets, worldInfo.daytime, worldInfo.fog);
// Actually world render with depth buffer on /* Actually world render with depth buffer on */ {
{
DrawContext ctx = wctx.sub(); DrawContext ctx = wctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.setCullFace(true); ctx.setCullFace(true);
renderLevel(ctx, camera, settings, delta, pause); renderLevel(ctx, camera, settings, delta, pause, hudVisible);
// Debug lines // Debug lines
if (hudVisible) { if (hudVisible) {
renderLines(camera, linesShader, ctx); if (player->debug) {
guides->renderDebugLines(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
if (player->currentCamera == player->fpCamera) { if (player->currentCamera == player->fpCamera) {
renderHands(camera, assets, delta * !pause); renderHands(camera, delta * !pause);
} }
} }
} }
if (hudVisible && player->debug) { {
renderDebugLines(wctx, camera, linesShader); DrawContext ctx = wctx.sub();
texts->render(ctx, camera, settings, hudVisible, true);
} }
renderBlockOverlay(wctx, assets); renderBlockOverlay(wctx);
} }
// Rendering fullscreen quad with // Rendering fullscreen quad with
@ -464,14 +354,14 @@ void WorldRenderer::draw(
postProcessing->render(pctx, screenShader); postProcessing->render(pctx, screenShader);
} }
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& assets) { void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
int x = std::floor(player->currentCamera->position.x); int x = std::floor(player->currentCamera->position.x);
int y = std::floor(player->currentCamera->position.y); int y = std::floor(player->currentCamera->position.y);
int z = std::floor(player->currentCamera->position.z); int z = std::floor(player->currentCamera->position.z);
auto block = level->chunks->get(x, y, z); auto block = level.chunks->get(x, y, z);
if (block && block->id) { if (block && block->id) {
const auto& def = const auto& def =
level->content->getIndices()->blocks.require(block->id); level.content->getIndices()->blocks.require(block->id);
if (def.overlayTexture.empty()) { if (def.overlayTexture.empty()) {
return; return;
} }
@ -487,7 +377,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& as
batch3d->begin(); batch3d->begin();
shader.uniformMatrix("u_projview", glm::mat4(1.0f)); shader.uniformMatrix("u_projview", glm::mat4(1.0f));
shader.uniformMatrix("u_apply", glm::mat4(1.0f)); shader.uniformMatrix("u_apply", glm::mat4(1.0f));
auto light = level->chunks->getLight(x, y, z); auto light = level.chunks->getLight(x, y, z);
float s = Lightmap::extract(light, 3) / 15.0f; float s = Lightmap::extract(light, 3) / 15.0f;
glm::vec4 tint( glm::vec4 tint(
glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s), glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s),
@ -509,34 +399,6 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& as
} }
} }
void WorldRenderer::drawBorders(
int sx, int sy, int sz, int ex, int ey, int ez
) {
int ww = ex - sx;
int dd = ez - sz;
/*corner*/ {
lineBatch->line(sx, sy, sz, sx, ey, sz, 0.8f, 0, 0.8f, 1);
lineBatch->line(sx, sy, ez, sx, ey, ez, 0.8f, 0, 0.8f, 1);
lineBatch->line(ex, sy, sz, ex, ey, sz, 0.8f, 0, 0.8f, 1);
lineBatch->line(ex, sy, ez, ex, ey, ez, 0.8f, 0, 0.8f, 1);
}
for (int i = 2; i < ww; i += 2) {
lineBatch->line(sx + i, sy, sz, sx + i, ey, sz, 0, 0, 0.8f, 1);
lineBatch->line(sx + i, sy, ez, sx + i, ey, ez, 0, 0, 0.8f, 1);
}
for (int i = 2; i < dd; i += 2) {
lineBatch->line(sx, sy, sz + i, sx, ey, sz + i, 0.8f, 0, 0, 1);
lineBatch->line(ex, sy, sz + i, ex, ey, sz + i, 0.8f, 0, 0, 1);
}
for (int i = sy; i < ey; i += 2) {
lineBatch->line(sx, i, sz, sx, i, ez, 0, 0.8f, 0, 1);
lineBatch->line(sx, i, ez, ex, i, ez, 0, 0.8f, 0, 1);
lineBatch->line(ex, i, ez, ex, i, sz, 0, 0.8f, 0, 1);
lineBatch->line(ex, i, sz, sx, i, sz, 0, 0.8f, 0, 1);
}
lineBatch->flush();
}
void WorldRenderer::clear() { void WorldRenderer::clear() {
renderer->clear(); chunks->clear();
} }

View File

@ -17,76 +17,62 @@ class Batch3D;
class LineBatch; class LineBatch;
class ChunksRenderer; class ChunksRenderer;
class ParticlesRenderer; class ParticlesRenderer;
class GuidesRenderer;
class TextsRenderer;
class Shader; class Shader;
class Frustum; class Frustum;
class Engine; class Engine;
class Chunks;
class LevelFrontend; class LevelFrontend;
class Skybox; class Skybox;
class PostProcessing; class PostProcessing;
class DrawContext; class DrawContext;
class ModelBatch; class ModelBatch;
class Assets; class Assets;
class Emitter;
struct EngineSettings; struct EngineSettings;
namespace model {
struct Model;
}
class WorldRenderer { class WorldRenderer {
Engine* engine; Engine* engine;
Level* level; const Level& level;
Player* player; Player* player;
const Assets& assets;
std::unique_ptr<Frustum> frustumCulling; std::unique_ptr<Frustum> frustumCulling;
std::unique_ptr<LineBatch> lineBatch; std::unique_ptr<LineBatch> lineBatch;
std::unique_ptr<ChunksRenderer> renderer;
std::unique_ptr<Skybox> skybox;
std::unique_ptr<Batch3D> batch3d; std::unique_ptr<Batch3D> batch3d;
std::unique_ptr<ChunksRenderer> chunks;
std::unique_ptr<GuidesRenderer> guides;
std::unique_ptr<Skybox> skybox;
std::unique_ptr<ModelBatch> modelBatch; std::unique_ptr<ModelBatch> modelBatch;
float timer = 0.0f; float timer = 0.0f;
bool drawChunk(size_t index, const Camera& camera, Shader* shader, bool culling);
void drawChunks(Chunks* chunks, const Camera& camera, Shader* shader);
/// @brief Render block selection lines /// @brief Render block selection lines
void renderBlockSelection(); void renderBlockSelection();
void renderHands(const Camera& camera, const Assets& assets, float delta); void renderHands(const Camera& camera, float delta);
/// @brief Render lines (selection and debug) /// @brief Render lines (selection and debug)
/// @param camera active camera /// @param camera active camera
/// @param linesShader shader used /// @param linesShader shader used
void renderLines( void renderLines(
const Camera& camera, Shader* linesShader, const DrawContext& pctx const Camera& camera, Shader& linesShader, const DrawContext& pctx
); );
/// @brief Render all debug lines (chunks borders, coord system guides) void renderBlockOverlay(const DrawContext& context);
/// @param context graphics context
/// @param camera active camera
/// @param linesShader shader used
void renderDebugLines(
const DrawContext& context,
const Camera& camera,
Shader* linesShader
);
void renderBlockOverlay(const DrawContext& context, const Assets& assets);
void setupWorldShader( void setupWorldShader(
Shader* shader, Shader& shader,
const Camera& camera, const Camera& camera,
const EngineSettings& settings, const EngineSettings& settings,
float fogFactor float fogFactor
); );
public: public:
std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<ParticlesRenderer> particles; std::unique_ptr<ParticlesRenderer> particles;
static bool showChunkBorders; static bool showChunkBorders;
static bool showEntitiesDebug; static bool showEntitiesDebug;
WorldRenderer(Engine* engine, LevelFrontend* frontend, Player* player); WorldRenderer(Engine* engine, LevelFrontend& frontend, Player* player);
~WorldRenderer(); ~WorldRenderer();
void draw( void draw(
@ -97,7 +83,6 @@ public:
float delta, float delta,
PostProcessing* postProcessing PostProcessing* postProcessing
); );
void drawBorders(int sx, int sy, int sz, int ex, int ey, int ez);
/// @brief Render level without diegetic interface /// @brief Render level without diegetic interface
/// @param context graphics context /// @param context graphics context
@ -108,7 +93,8 @@ public:
const Camera& camera, const Camera& camera,
const EngineSettings& settings, const EngineSettings& settings,
float delta, float delta,
bool pause bool pause,
bool hudVisible
); );
void clear(); void clear();

Some files were not shown because too many files have changed in this diff Show More