L-системы , из того, что я могу сказать *, представляют собой набор правил подстановки, подобных грамматике, которые вы можете применять рекурсивно, чтобы получить интересные, «органические» результаты.
На заводах часто используются L-системы, так как они демонстрируют большой рекурсивный рост (то есть ветвь распадается на большее количество ветвей). Для простого примера я покажу дерево «леденцов», созданное с использованием L-системы:
variables : | o (these are the things that will grow)
start : o
| (this is what we start with)
rules : (o → o o) (these are the substitution rules that we apply
\ / one step at a time)
Итак, в первом поколении у нас только есть начало:
o
|
Во втором поколении мы следуем каждому из правил и заменяем существующие части согласно правилам. Заменим «шары» на «две палочки и шары»:
o o
\ /
|
Поколение 3:
o o o o
\| |/
\ /
|
Скоро у нас будет довольно (дрянное) большое дерево!
Чтобы сделать это в коде, вы можете сделать это рекурсивно (например, DFS), непрерывно применяя правила к одним и тем же частям, пока не достигнете некоторого произвольного конца, или вы можете сделать это итеративно (например, BFS), как мы делали в этом примере выполняя одно правило «пройти» на всех элементах и повторяя несколько шагов. Это:
Рекурсивный:
tree = start
grow(tree, start)
func grow(tree, part)
if this part of the tree is big enough
stop
if part is 'o'
replace part with 'o\/o'
grow(tree, the left 'o')
grow(tree, the right 'o')
Итеративно:
tree = start
for a number of iterations
for each part in tree
if part is 'o':
replace with 'o\/o'
Многие применения L-Systems выполняют этап «роста» с использованием подразделения - то есть, части становятся меньше по мере того, как они «растут», большие части просто делятся. В противном случае ваша растущая система может начать перекрывать себя. В моем примере с леденцом на палочке, я волшебным образом гарантировал, что две ветви не пересекаются в середине, изменяя форму новых ветвей. Давайте сделаем пример города, используя подразделение:
variables: block_vertical block_horizontal road_vertical road_horizontal
start: block_vertical
rules: (block_vertical → block_horizontal road_vertical block_horizontal)
(block_horizontal → block_vertical road_horizontal block_vertical)
Это будет иметь смысл через минуту.
Поколение 1:
+--------------------+
| |
| |
| |
| V |
| |
| |
| |
+--------------------+
Единственный, скучный вертикальный блок. (V обозначает вертикальный.)
Поколение 2: мы заменяем вертикальный блок горизонтальными блоками на вертикальную дорогу посередине
+--------------------+
| r |
| r |
| r |
| H r H |
| r |
| r |
| r |
+--------------------+
R обозначает дорогу! Я случайно разбил разделение, мы не хотим скучных регулярных частей в PCG.
Поколение 3: мы заменяем горизонтальные блоки вертикальными блоками, разделенными горизонтальными дорогами. Существующие дороги остаются; для них нет правил.
+--------------------+
| V r |
| r |
|rrrrrrrr |
| r V |
| V r |
| rrrrrrrrrrrrr|
| r V |
+--------------------+
Обратите внимание, как дороги соединяются друг с другом, что приятно. Повторите это достаточно много раз, и в итоге вы получите что-то вроде этого (откровенно сорвало связанный ответ ):
Обратите внимание, что есть много деталей, которые я не раскрыл, и что этот результат выглядит «явно» сгенерированным - реальные города выглядят несколько иначе. Вот что делает PCG веселым / сложным. Есть бесконечные вещи, которые вы можете сделать, чтобы настроить и улучшить свои результаты, но будучи не связанным с L-Systems, я оставлю этот ответ здесь; надеюсь, это поможет вам начать.
* - Я не изучал L-системы формально, хотя я встречал определенные типы, такие как грамматика и растительность PCG; поправьте меня, если я ошибаюсь в определениях или понятиях