# Генератор мира ## Содержание - [Основные понятия](#основные-понятия) - [Глобальные переменные](#глобальные-переменные) - [Файл конфигурации](#файл-конфигурации) - [Фрагменты](#фрагменты) - [Структуры](#структуры) - [Биомы](#биомы) * [Параметры биомов](#параметры-биомов) * [Выбор биома](#выбор-биома) - [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` - блок, которые следует использовать в фрагментах для обозначения пустого пространства, которое не должно заполняться блоками при генерации в мире. # Генератор '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.