# Генератор мира

## Содержание

- [Основные понятия](#основные-понятия)
- [Глобальные переменные](#глобальные-переменные)
- [Файл конфигурации](#файл-конфигурации)
- [Фрагменты](#фрагменты)
- [Структуры](#структуры)
- [Биомы](#биомы)
   * [Параметры биомов](#параметры-биомов)
   * [Выбор биома](#выбор-биома)
- [Heightmap (карта высот)](#heightmap-карта-высот)
   * [Конструктор](#конструктор)
   * [Унарные операции](#унарные-операции)
   * [Бинарные операции](#бинарные-операции)
   * [heightmap:dump(...)](#heightmapdump)
   * [heightmap:noise(...)](#heightmapnoise)
   * [heightmap:cellnoise(...)](#heightmapcellnoise)
   * [heightmap:resize(...)](#heightmapresize)
   * [heightmap:crop(...)](#heightmapcrop)
   * [heightmap:at(x, y)](#heightmapatx-y)
- [VoxelFragment (фрагмент)](#voxelfragment-фрагмент)
- [Генерация карты высот](#генерация-карты-высот)
- [Ручная расстановка структур](#ручная-расстановка-структур)
   * [Размещения структур/тоннелей](#размещения-структуртоннелей)
   * [Расстановка малых структур](#расстановка-малых-структур)
   * [Расстановка 'широких' структур](#расстановка-широких-структур)
- [Структурный воздух](#структурный-воздух)
- [Генератор 'Demo' (base:demo)](#генератор-demo-basedemo)

## Основные понятия

Понятия используемые далее по тексту.

- **Комбинируемый массив/объект** - TOML или JSON файл, который комбинируется из нескольких версий в разных паках, что позволяет добавлять в него данные извне. Поля комбинированного объекта перезаписываются в порядке от первого к последнему, так же, как и другие ресурсы в паках. В случае комбинируемого массива, проверка на дубликаты **не** выполняется.
- **Биом** - информация, определяющая то, из каких блоков и какими слоями генерируется ландшафт, а так же набор растений, структур.
- **Растение** - случайно расставляемый на поверхности блок.
- **Малая структура** - структура, размер которой не превышает размера чанка. Пример: деревья. 

## Файл конфигурации

Генератор мира распознается при наличии файла `generators/имя_генератора.toml`. Другие файлы, относящиеся к генератору, должны находиться в директории `generators/имя_генератора.files/`:
- biomes.toml - объявления биомов
- structures.toml - объявления структур
- script.lua - скрипт генератора
- fragments - директория в которой располагаются файлы фрагментов

Основные свойства, описываемые в файле конфигурации:
- **caption** - отображаемое имя генератора. По-умолчанию генерируется из id.
- **biome-parameters** - количество параметров выбора биомов (от 0 до 4). По-умолчанию: 0.
- **sea-level** - уровень моря (ниже этого уровня вместо воздуха будут генерироваться слои моря (sea-layers)). По-умолчанию: 0.
- **biomes-bpd** - количество блоков на точку карты параметра выбора биомов. По-умолчанию: 4.
- **heights-bpd** - количество блоков на точку карты высот. По-умолчанию: 4.
- **wide-structs-chunks-radius** - масимальный радиус размещения 'широких' структур, измеряемый в чанках.
- **heightmap-inputs** - массив номеров карт параметров, которые будут переданы таблицей inputs в функцию генерации карты высот.

## Глобальные переменные

В скрипте генератора доступны следующие переменные:

- `SEED` - зерно генерации мира
- `__DIR__` - директория генератора (`пак:generators/имя_генератора.files/`)
- `__FILE__` - файл скрипта (`пак:generators/имя_генератора.files/script.lua`)

## Фрагменты

Фрагмент является сохраненной для дальнейшего использования, областью мира, как и чанк, ограниченную некоторой шириной, высотой и длиной. Фрагмент может содержать данные не только о блоках, попадающих в область, но и о инвентарях блоков области, а так же сущностях. В отличие от чанка, размер фрагмента произволен.

На данный момент, фрагмент может быть создан при помощи команды `fragment.save`, либо через функцию `generation.create_fragment`.

Фрагменты, используемые генератором, должны находиться в директории:
`generators/имя_генератора.files/fragments/`

## Структуры

Структура - набор правил по вставке фрагмента в мир генератором. Структуры объявляются в виде объектов в файле `generators/имя_генератора.files/structures.toml`. Пример:
```toml
tree0 = {}
tree1 = {}
tree2 = {}
tower = {lowering=-2}
coal_ore0 = {}
```

На данный момент, имя структуры должно совпадать с именем использованного фрагмента.

Доступные свойства:
- lowering - глубина погружения структуры под поверхность.

## Биомы

Биом определяет то, из каких блоков и какими слоями генерируется ландшафт, а так же набор растений, структур.

Биомы объявляются в комбинируемом объекте:
`generators/имя_генератора.files/biomes.toml`

Разберем структуру биома на примере леса из генератора base:demo:

```toml
[forest]
parameters = [
    {weight=1, value=1},
    {weight=0.5, value=0.2}
]
layers = [
    {below-sea-level=false, height=1, block="base:grass_block"},
    {below-sea-level=false, height=7, block="base:dirt"},
    {height=-1, block="base:stone"},
    {height=1, block="base:bazalt"}
]
sea-layers = [
    {height=-1, block="base:water"}
]
plant-chance = 0.4
plants = [
    {weight=1, block="base:grass"},
    {weight=0.03, block="base:flower"}
]
structure-chance = 0.032
structures = [
    {name="tree0", weight=1},
    {name="tree1", weight=1},
    {name="tree2", weight=1},
    {name="tower", weight=0.002}
]
```

- ключ forest - имя биома
- parameters - веса и центральные значения параметров для биома. См. раздел [выбор биома](#выбор-биома). Количество записей должно соответствовать количеству параметров выбора биомов.
- layers - слои блоков от верхнего к нижнему.
    - height - высота слоя в блоках. -1 используется для обозначения безразмерного (заполняющего) слоя, который может быть только один. Его высота вычисляется автоматически.
    - block - полное имя блока
    - below-sea-level - может ли слой быть сгенерированным ниже уровня моря (пример: дёрн). При значении false, при генерации ниже уровня моря, слой будет заменён на следующий.
- sea-layers - слои океана. Положение верхнего слоя совпадает с высотой уровня моря.
- plant-chance - вероятность генерации растения на блоке поверхности.
- plants - растения, случайно расставляемые на поверхности.
    - weight - вес, напрямую влияющий на шанс выбора конкретного растения.
    - block - блок растения
- structure-chance - вероятность генерации малой структуры на блоке поверхности.
- structures - структуры, случайно расставляемые на поверхности.
    - name - имя структуры, объявленной в `structures.toml`.
    - weight - вес, напрямую влияющий на шанс выбора конкретной структуры.

### Параметры биомов

Параметр генератора `biome-parameters` определяет количество параметров биомов, используемых при их выборе (примеры: температура, влажность).

Карты значений параметров биомов генеруются так же, как и карты высот. 

Требуется реализовать функцию:
```lua
-- x, y - позиция начала карты (в точках)
-- w, h - ширина и высота карты (в точках)
-- bpd  - (blocks per dot) число блоков на точку (масштаб)
function generate_biome_parameters(x, y, w, h, bpd)
    -- создание карт высот (Heightmap) для каждого параметра биомов
    -- ...
    return карты_через_запятую
end

-- пример
function generate_biome_parameters(x, y, w, h, s)
    -- карта температур
    local tempmap = Heightmap(w, h)
    tempmap.noiseSeed = SEED + 5324
    tempmap:noise({x, y}, 0.04*s, 6)
    tempmap:pow(3)
    -- карта влажности
    local hummap = Heightmap(w, h)
    hummap.noiseSeed = SEED + 953
    hummap:noise({x, y}, 0.04*s, 6)
    hummap:pow(3)
    
    return tempmap, hummap
end
```

### Выбор биома

После генерации карт параметров для каждого биома вычисляются оценки по всем параметрам:

$score = \frac{|V - V_b|}{W_b}$

Где $V$ - значение параметра, $V_b$ центральное значение параметра для биома, $W_b$ - вес биома для параметра.

Генератор выбирает биом с **наименьшей** суммой оценок для параметров.


>[!WARNING]
> При неудачной настройке значений и весов параметров у биомов может возникать эффект, имеющий ту же природу, что и конфликт глубины в 3D графике при наложении двух поверхностей.
> 
   В случае биомов, узор выглядит случайным из-за искривления этих 'поверхностей' шумом, используемым при генерации карт параметров.
   >
   Для избавления от эффекта можно либо скорректировать веса или значения параметров биомов, либо увеличить разницу в генерации карт параметров.


## Heightmap (карта высот)

Heightmap это класс для работы с картами высот (матрицами чисел с плавающей точкой произвольного размера).

### Конструктор

Конструктор карты высот требует указания целочисленных ширины и высоты.

```lua
local map = Heightmap(ширина, высота)
```

### Унарные операции

Операции применяются ко всем значениям высоты.

```lua
map:abs()
```

Приводит значения высот к абсолютным.


### Бинарные операции

Операции с применением второй карты или скаляра.

Арифметические операции:

```lua
-- Прибавление
map:add(value: Heightmap|number)

-- Вычитание
map:sub(value: Heightmap|number)

-- Умножение
map:mul(value: Heightmap|number)

-- Возведение в степень
map:pow(value: Heightmap|number)
```

Другие операции:

```lua
-- Минимум
map:min(value: Heightmap|number)

-- Максимум
map:max(value: Heightmap|number)

-- Примешивание
map:mixin(value: Heightmap|number, t: Heightmap|number)
-- t - фактор смешивания от 0.0 до 1.0
-- смешивание производится по формуле:
--    map_value * (1.0 - t) + value * t
```

### heightmap:dump(...)

Метод используемый для отладки, создает изображение на основе карты высот переводя значения из дипазона `[-1.0, 1.0]` в значения яркости `[0, 255]`, сохраняя в указанный файл.

```lua
map:dump('export:test.png')
```

### heightmap:noise(...)

Метод генерирующий симплекс-шум, прибавляя его к имеющимся значениям.

Зерно шума может быть указано в поле `map.noiseSeed`.

```lua
map:noise(
    -- смещение координат
    offset: {number, number},
    -- коэфициент масштабирования координат
    scale: number,
    -- число октав шума (по-умолчанию: 1)
    [опционально] octaves: integer,
    -- множитель амплитуды шума (по-умолчанию: 1.0)
    [опционально] multiplier: number,
    -- карта смещений координаты X при генерации шума
    [опционально] shiftMapX: Heightmap,
    -- карта смещений координаты Y при генерации шума
    [опционально] shiftMapY: Heightmap,
) -> nil
```

Визуализация шума с октавами 1, 2, 3, 4 и 5.

![image](../images/simplex-noise.gif)

### heightmap:cellnoise(...)

Аналог heightmap:noise генерирующий клеточный шум.

Зерно шума может быть указано в поле `map.noiseSeed`.

![image](../images/cell-noise.gif)

### heightmap:resize(...)

```lua
map:resize(ширина, высота, интерполяция)
```

Изменяет размер карты высот.

Доступные режимы интерполяции:
- 'nearest' - без интерполяции
- 'linear' - билинейная интерполяция
- 'cubic' - бикубическая интерполяция

### heightmap:crop(...)

```lua
map:crop(x, y, ширина, высота)
```

Обрезает карту высот до заданной области.

### heightmap:at(x, y)

```lua
map:at(x, y) --> number
```

Возвращает значение высота на заданной позиции.

## VoxelFragment (фрагмент)

Фрагмент создается вызовом функции:
```lua
generation.create_fragment(
    -- точка A
    a: vec3,
    -- точка B
    b: vec3,
    -- автоматически обрезать фрагмент, если возможно
    crop: bool
) -> VoxelFragment
```

Фрагмент может быть загружен из файла:
```lua
generation.load_fragment(
    -- файл фрагмента
    filename: str
) -> VoxelFragment
```

Фрагмент может быть сохранен в файл:
```lua
generation.save_fragment(
    -- сохраняемый фрагмент
    fragment: VoxelFragment,
    -- файл
    filename: str
) -> nil
```

Размер фрагмента доступен как свойство `size`.

### Методы

```lua
-- Обрезает фрагмент до размеров содержимого
fragment:crop()

-- Устанавливает фрагмент в мир на указанной позиции
fragment:place(position: vec3, [опционально] rotation:int=0)
```

## Генерация карты высот

По-умолчанию, движок генерирует карту высот, состоящую из нулей.

Для генерации пользовательских карт высот требуется реализовать функцию:
```lua
function generate_heightmap(
    x, y, -- смещение карты высот
    w, h, -- размер карты высот, ожидаемый движком
    bpd,  -- число блоков на точку карты (blocks per dot) - масштаб
    [опционально] inputs -- массив входных карт параметров биомов
    -- (см. свойство heightmap-inputs генератора)
) --> Heightmap
```

Пример генерации карты высот из простого симплекс-шума с приведением
к нужному диапазону:

```lua
function generate_heightmap(x, y, w, h, bpd)
    -- создаем карту высот с заданным размером
    local map = Heightmap(w, h)
    -- настраиваем зерно шума
    map.noiseSeed = SEED
    -- шум с масштабом 1/10 на 4 октавы с амплитудой 0.5
    map:noise({x, y}, 0.1*bpd, 4, 0.5)
    -- сдвигаем высоты к положительному диапазону
    map:add(0.5)
    return map
end
```

## Ручная расстановка структур

### Размещения структур/тоннелей

Размещение структуры / линии представляет собой массив из заданного
набора параметров.

Структура:
```lua
{имя_структуры, позиция_структуры, поворот, [опционально] приоритет}
```

Где:
- имя_структуры - строка содержащая имя структуры, зарегистрированная в structures.toml.
- позиция_структуры - vec3 (массив из трех чисел) относительно позиции чанка.
- поворот - число от 0 до 3 обозначающая поворот структуры по оси Y.
- приоритет - число определяющее порядок установки структур. Структуры с меньшем приоритетом перекрываются структурами с большим.

Тоннель:
```lua
{":line", блок_заполнитель, точка_а, точка_б, радиус}
```

Где:
- блок_заполнитель - числовой id блока, из которого будет состоять структура.
- точка_а, точка_б - vec3, vec3 позиции начала и конца тоннеля.
- радиус - радиус тоннеля в блоках


### Расстановка малых структур

```lua
function place_structures(
    x, z, -- позиция начала области в блоках
    w, d, -- размер области в блоках
    heights, -- карта высот чанка
    chunk_height, -- высота чанка
) --> массив размещений структур
```

Структуры могут размещаться за пределами чанка, но не дальше, чем на один чанк.

Пример:

```lua
function place_structures(x, z, w, d, hmap, chunk_height)
    local placements = {}
    local height = hmap:at(w/2, h/2) * chunk_height

    -- устанавливает башню по центру чанка
    table.insert(placements, {
        'tower', {w/2, height, d/2}, math.random() * 4, 2
    })
    return placements
end
```

### Расстановка 'широких' структур

Структуры и тоннели могут размещаться за пределами чанка, но не дальше, чем на число чанков, указанное в свойстве генератора `wide-structs-chunks-radius`.

В отличие от прошлой функции, сюда не передается карта высот,
так как вызов происходит на ранних этапах генерации чанка.

```lua
function place_structures_wide(
    x, z, -- позиция начала области в блоках
    w, d, -- размер области в блоках
    chunk_height, -- высота чанка
) --> массив размещений структур / тоннелей
```

## Структурный воздух

`core:struct_air` - блок, которые следует использовать в фрагментах для обозначения пустого пространства, которое не должно заполняться блоками при генерации в мире.

<image src="../../res/textures/blocks/struct_air.png" width="128px" height="128px" style="image-rendering: pixelated">

# Генератор 'Demo' (base:demo)

## Добавление новой руды

Чтобы добавить новую руду из своего пака:
1. В папке `generators` создайте папку `demo.files` (demo.toml создавать не нужно). 
2. В созданной папке создайте папку fragments и поместите в неё файл фрагмента руды.
3. В `demo.files` создайте файл structures.toml:
```toml
имя_фрагмента = {}
```
4. Также в `demo.files` создайте файл ores.json:
```json
[
    {"struct": "имя_фрагмента", "rarity": редкость}
]
```
Чем выше значение редкости, тем меньше вероятность генерации руды.
Опираться можно на редкость угольной руды: 4400.
