- Версия(и) Minecraft
- 1.12.2
Содержание
- Генерация биомов (часть 1)
- Генерация биомов (часть 2)
Предисловие
В первом руководстве мы познакомились почти со всеми стандартными слоями GenLayer и даже добавили генерацию собственных биомов. Теперь самое время разобраться с самыми сложными слоями и попробовать создать новые слои со своими алгоритмами работы.Оглавление
- Генерация биомов в мире (часть 2) - в данном разделе мы закончим знакомство со стандартным алгоритмом генератором биомов.
- Собственные слои - разберем несколько различных алгоритмов генерации биомов.
1 Генерация биомов в мире (часть 2)
1.2 Масштабирование слоев
Первое, что следует разобрать подробнее - это, конечно же, масштабирование слоев. В предыдущем руководстве уже оговаривалось, что такие слои, как GenLayerZoom, GenLayerFuzzyZoom и GenLayerVoronoiZoom масштабируют входящий массив чисел следуя определенным алгоритмам.1.2 Масштабирование слоев
Масштабирование очень сильно влияет на итоговый результат генерации. В качестве примера рассмотрим стандартный алгоритм генерации биомов и простой вариант алгоритма, который генерирует почти такую же поверхность (об этом во второй главе руководства), которые будем использовать для генерации одной и той же области мира (без рек и редких вариаций биомов):
- Первый слой GenLayerIsland. Чтобы определить насколько сильно масштабируется каждый кусочек поверхности сразу же покрашен в уникальный цвет.
- Последний слой GenLayerVoronoiZoom. Целиком влезает только розовая область, которая появилась из одного единственного пикселя. Сразу можно понять, что она внушительных размеров. Размер сгенерированной области: 1200 x 1200 чанков. Розовая область покрывает ~536 x 536 чанков.
- Как генерировать маленькие биомы типа BEACH (Пляж)?
- Чем меньше масштабирующих слоев влияет на область, тем меньше эта область будет в конечном итоге.
А теперь обратите внимание на белую область, которая также присутствует в больших количествах, но которую не генерировал слой GenLayerIsland. Это - поверхность, которая образуется в результате работы слоя GenLayerRemoveTooMuchOcean. Слой не масштабирует, но создает не менее масштабные области посреди океанов. Это не согласуется с ответом, который приведен выше, потому что данный слой во время своей работы создает новую поверхность в количествах часто даже больших, чем получается после GenLayerIsland и трех масштабирований. Из всего этого можно сделать вывод:
- Область, образующаяся после работы масштабируемых слоев, будет тем больше, чем больше масштабируемых слоев используется в алгоритме при условии, что изначальное количество масштабируемых пикселей не меняется.
1.3 GenLayerZoom
Настало время разобраться с самым важным элементом конструктора - алгоритмом, который масштабирует слои. В данном разделе я рассмотрю следующий массив данных, который и буду масштабировать:
Java:
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX >> 1;
int j = areaY >> 1;
int k = (areaWidth >> 1) + 2;
int l = (areaHeight >> 1) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int i1 = k - 1 << 1;
int j1 = l - 1 << 1;
int[] aint1 = IntCache.getIntCache(i1 * j1);
for (int k1 = 0; k1 < l - 1; ++k1) {
int l1 = (k1 << 1) * i1;
int i2 = 0;
int j2 = aint[i2 + 0 + (k1 + 0) * k];
for (int k2 = aint[i2 + 0 + (k1 + 1) * k]; i2 < k - 1; ++i2) {
this.initChunkSeed((long) (i2 + i << 1), (long) (k1 + j << 1));
int l2 = aint[i2 + 1 + (k1 + 0) * k];
int i3 = aint[i2 + 1 + (k1 + 1) * k];
aint1[l1] = j2;
aint1[l1++ + i1] = this.selectRandom(new int[] { j2, k2 });
aint1[l1] = this.selectRandom(new int[] { j2, l2 });
aint1[l1++ + i1] = this.selectModeOrRandom(j2, l2, k2, i3);
j2 = l2;
k2 = i3;
}
}
int[] aint2 = IntCache.getIntCache(areaWidth * areaHeight);
for (int j3 = 0; j3 < areaHeight; ++j3) {
System.arraycopy(aint1, (j3 + (areaY & 1)) * i1 + (areaX & 1), aint2, j3 * areaWidth, areaWidth);
}
return aint2;
}
Java:
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX >> 1;
int j = areaY >> 1;
int k = (areaWidth >> 1) + 2;
int l = (areaHeight >> 1) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int k_support = k - 1 << 1;
int l_support = l - 1 << 1;
int[] aint_support = IntCache.getIntCache(k_support * l_support);
for (int l1 = 0; l1 < l - 1; l1++) {
int id = (l1 << 1) * k_support;
int a1 = aint[0 + 0 + (l1 + 0) * k];
int a2 = aint[0 + 0 + (l1 + 1) * k];
for (int k1 = 0; k1 < k - 1; k1++) {
this.initChunkSeed((long) (k1 + i << 1), (long) (l1 + j << 1));
int a3 = aint[k1 + 1 + (l1 + 0) * k];
int a4 = aint[k1 + 1 + (l1 + 1) * k];
aint_support[id] = a1;
aint_support[id++ + k_support] = this.selectRandom(new int[] { a1, a2 });
aint_support[id] = this.selectRandom(new int[] { a1, a3 });
aint_support[id++ + k_support] = this.selectModeOrRandom(a1, a3, a2, a4);
a1 = a3;
a2 = a4;
}
}
int[] aint_result = IntCache.getIntCache(areaWidth * areaHeight);
for (int j3 = 0; j3 < areaHeight; j3++) {
System.arraycopy(aint_support, (areaX & 1) + (j3 + (areaY & 1)) * k_support, aint_result, j3 * areaWidth, areaWidth);
}
return aint_result;
}
areaX
иareaY
- собственные смещения,areaWidth
иareaHeight
- собственные длины массива.aint
- массив данных, соответствующий родителю.aint_support
- вспомогательный массив данных. Из него будет вырезаться определенная часть данных, которая и станет окончательнымaint_result
.aint_result
- собственный массив данных.i
иj
так и остались смещениями родителя, аk
иl
- длинами массива родителя.k_support
иl_support
- длины вспомогательного массива.k1
иl1
- используются при чтенииaint
.id
- эта переменная является индентификатором определенного элемента массиваaint_support
.a1
,a2
,a3
иa4
- хранят какие-то определенные значения массиваaint
.
aint
(особенность побитовых операций, причем чем больше длины aint_result
, тем сильнее ощущается уменьшения длин aint
). Тем не менее, из-за использования побитовых операций возникают некоторые ограничения, которые исправляются путем подведения всех слоев к каким-то определенным размерам и с которыми алгоритм сможет работать. Из-за этого в алгоритме присутствует вспомогательный aint_support
.Получение массива данных родителя. В данном коде стоит отметить только слагаемые
+ 2
- последствия использования побитовых операций. Увеличение длины aint
необходимо, чтобы длины будущего aint_support
были гарантировано больше, чем у aint_result
Java:
int i = areaX >> 1;
int j = areaY >> 1;
int k = (areaWidth >> 1) + 2;
int l = (areaHeight >> 1) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
aint_support
. Сравнив его размеры с aint
можно понять, что он более, чем в 2 раза превосходит его по размеру и, соответственно, больше aint_result
. По факту весь процесс масштабирования будет проходить в этом буфером массиве, после чего необходимую часть массива мы просто сделаем окончательным aint_result
. Обязательно имеет длины, кратные 2 (необходимо для последующих операций), поэтому код соответствующий:
Java:
int k_support = k - 1 << 1;
int l_support = l - 1 << 1;
int[] aint_support = IntCache.getIntCache(k_support * l_support);
aint
. Считывание начинается в нижнем левом углу сначала по вертикали (координата X), а потом по горизонтали (координата Z):
Java:
for (int l1 = 0; l1 < l - 1; l1++) {
...
for (int k1 = 0; k1 < k - 1; k1++) {
...
}
}
aint_support
в 2 раза больше, чем красный квадрат):aint_support
, соответствующему точке на плоскости (0, 2 * l1)
:
Java:
int id = (l1 << 1) * k_support;
a1
и a2
будут принимать значения массива aint
, соответствующие точкам на плоскости (0, l1)
и (0, l1 + 1)
:
Java:
int a1 = aint[0 + 0 + (l1 + 0) * k];
int a2 = aint[0 + 0 + (l1 + 1) * k];
a1
.
Java:
this.initChunkSeed((long) (k1 + i << 1), (long) (l1 + j << 1));
a3
и a4
, которые соответствуют точкам на плоскости (k1 + 1, l1)
и (k1 + 1, l1 + 1)
:
Java:
int a3 = aint[k1 + 1 + (l1 + 0) * k];
int a4 = aint[k1 + 1 + (l1 + 1) * k];
id
мы будем перемещаться по столбцу вверх и когда достигнем предела, перейдем вниз на новый столбец.
Java:
aint_support[id] = a1;
aint_support[id++ + k_support] = this.selectRandom(new int[] { a1, a2 });
aint_support[id] = this.selectRandom(new int[] { a1, a3 });
aint_support[id++ + k_support] = this.selectModeOrRandom(a1, a3, a2, a4);
aint_support
соответствуют точки a1
, a2
, a3
и a4
:aint_support[id]
- точкаa1
.aint_support[id++ + k_support]
- точка между точкамиa1
иa2
и она принимает случайное из двух значенийa1
иa2
.aint_support[id]
- поскольку в прошлом присваивании переменной значениеid
было увеличено на 1, то теперь эта точка будет находится междуa1
иa3
и принимать случайное из двух значенийa1
иa3
.aint_support[id++ + k_support]
- точка между всеми четырьмя точками. Данной точке будет соответствовать значение, которое встречается чаще всего между точкамиa1
,a2
,a3
иa4
. Если такого значения нет, то выбирает случайное среди всех.
a1
и a2
нам также нужно переопределять:
Java:
a1 = a3;
a2 = a4;
k1
мы получим первый кусочек массива aint_support
. На картинке нет a3
и a4
потому что их места занимают a1
и a2
:aint
, то на нем также будут точки передвигаться по столбцу вверх. Например, при работы второй итерации цикла k1
брались вот такие точки:l1
получим готовый первый столбец. Далее все последующие итерации сгенерируют оставшиеся столбцы:aint_support
:aint_result
:
Java:
int[] aint_result = IntCache.getIntCache(areaWidth * areaHeight);
aint_support
. Мы подошли с самой непонятной части данного алгоритма, а именно: (areaX & 1)
и (areaY & 1)
. Каждое побитовое "И" проверяет на нечетность смещения и если они нечетные, то весь копирующийся кусок массива aint_support
будет сдвинут по соответствующим осям. Смысл данного действия в том, что внутри массива aint_support находится целых 4 массива, которые по размерам соответствуют размеру собственного массива aint_result
(включая его самого) и у всех массив родителя одинаковый - aint
. Тем не менее, эти 4 массива имеют отличные друг от друга смещения, а значит не могут быть равны друг другу. Этот ньюанс возникает из-за работы побитовых сдвигов и решается только соответствующим сдвигом копирующейся области для каждого из этих четырех массивов данных.
Java:
for (int j3 = 0; j3 < areaHeight; j3++) {
System.arraycopy(aint_support, (areaX & 1) + (j3 + (areaY & 1)) * k_support, aint_result, j3 * areaWidth, areaWidth);
}
Также есть возможность вызова метода
GenLayerZoom.magnify(...)
, который масштабирует выбранную область указанное количество раз.1.4 GenLayerFuzzyZoom
Отличается от GenLayerZoom только тем, что точка, находящаяся между a1
, a2
, a3
и a4
, не проверяет на большинство какое-то значение, а сразу выбирает случайное.1.5 GenLayerVoronoiZoom
Название говорит само за себя. В основе лежит разбиение Вороного, о котором можно прочитать в Википедии (обязательно к ознакомлению). Структура алгоритма практически ничем не отличается от GenLayerZoom за исключением того, что родительский массив в 4 раза меньше собственного и как следствие точки a1
, a2
, a3
и a4
будут определять сразу 16 точек, а не 4. Будем масштабировать такой массив данных родителя:
Java:
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
areaX = areaX - 2;
areaY = areaY - 2;
int i = areaX >> 2;
int j = areaY >> 2;
int k = (areaWidth >> 2) + 2;
int l = (areaHeight >> 2) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int i1 = k - 1 << 2;
int j1 = l - 1 << 2;
int[] aint1 = IntCache.getIntCache(i1 * j1);
for (int k1 = 0; k1 < l - 1; ++k1) {
int l1 = 0;
int i2 = aint[l1 + 0 + (k1 + 0) * k];
for (int j2 = aint[l1 + 0 + (k1 + 1) * k]; l1 < k - 1; ++l1) {
double d0 = 3.6D;
this.initChunkSeed((long) (l1 + i << 2), (long) (k1 + j << 2));
double d1 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0;
double d2 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0;
this.initChunkSeed((long) (l1 + i + 1 << 2), (long) (k1 + j << 2));
double d3 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0 + 4.0D;
double d4 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0;
this.initChunkSeed((long) (l1 + i << 2), (long) (k1 + j + 1 << 2));
double d5 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0;
double d6 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0 + 4.0D;
this.initChunkSeed((long) (l1 + i + 1 << 2), (long) (k1 + j + 1 << 2));
double d7 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0 + 4.0D;
double d8 = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * d0 + 4.0D;
int k2 = aint[l1 + 1 + (k1 + 0) * k] & 255;
int l2 = aint[l1 + 1 + (k1 + 1) * k] & 255;
for (int i3 = 0; i3 < 4; ++i3) {
int j3 = ((k1 << 2) + i3) * i1 + (l1 << 2);
for (int k3 = 0; k3 < 4; ++k3) {
double d9 = ((double) i3 - d2) * ((double) i3 - d2) + ((double) k3 - d1) * ((double) k3 - d1);
double d10 = ((double) i3 - d4) * ((double) i3 - d4) + ((double) k3 - d3) * ((double) k3 - d3);
double d11 = ((double) i3 - d6) * ((double) i3 - d6) + ((double) k3 - d5) * ((double) k3 - d5);
double d12 = ((double) i3 - d8) * ((double) i3 - d8) + ((double) k3 - d7) * ((double) k3 - d7);
if (d9 < d10 && d9 < d11 && d9 < d12) {
aint1[j3++] = i2;
} else if (d10 < d9 && d10 < d11 && d10 < d12) {
aint1[j3++] = k2;
} else if (d11 < d9 && d11 < d10 && d11 < d12) {
aint1[j3++] = j2;
} else {
aint1[j3++] = l2;
}
}
}
i2 = k2;
j2 = l2;
}
}
int[] aint2 = IntCache.getIntCache(areaWidth * areaHeight);
for (int l3 = 0; l3 < areaHeight; ++l3) {
System.arraycopy(aint1, (l3 + (areaY & 3)) * i1 + (areaX & 3), aint2, l3 * areaWidth, areaWidth);
}
return aint2;
}
Java:
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
areaX = areaX - 2;
areaY = areaY - 2;
int i = areaX >> 2;
int j = areaY >> 2;
int k = (areaWidth >> 2) + 2;
int l = (areaHeight >> 2) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int k_support = k - 1 << 2;
int l_support = l - 1 << 2;
int[] aint_support = IntCache.getIntCache(k_support * l_support);
for (int l1 = 0; l1 < l - 1; ++l1) {
int a1 = aint[0 + 0 + (l1 + 0) * k];
int a2 = aint[0 + 0 + (l1 + 1) * k];
for (int k1 = 0; k1 < k - 1; ++k1) {
double offset = 3.6D;
this.initChunkSeed((long) (k1 + i << 2), (long) (l1 + j << 2));
double pointA_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointA_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
this.initChunkSeed((long) (k1 + i + 1 << 2), (long) (l1 + j << 2));
double pointC_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
double pointC_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
this.initChunkSeed((long) (k1 + i << 2), (long) (l1 + j + 1 << 2));
double pointB_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointB_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
this.initChunkSeed((long) (k1 + i + 1 << 2), (long) (l1 + j + 1 << 2));
double pointD_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
double pointD_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
int a3 = aint[k1 + 1 + (l1 + 0) * k] & 255;
int a4 = aint[k1 + 1 + (l1 + 1) * k] & 255;
for (int l2 = 0; l2 < 4; ++l2) {
int id = (k1 << 2) + ((l1 << 2) + l2) * k_support;
for (int k2 = 0; k2 < 4; ++k2) {
double distance_A = ((double) l2 - pointA_Z) * ((double) l2 - pointA_Z) + ((double) k2 - pointA_X) * ((double) k2 - pointA_X);
double distance_C = ((double) l2 - pointC_Z) * ((double) l2 - pointC_Z) + ((double) k2 - pointC_X) * ((double) k2 - pointC_X);
double distance_B = ((double) l2 - pointB_Z) * ((double) l2 - pointB_Z) + ((double) k2 - pointB_X) * ((double) k2 - pointB_X);
double distance_D = ((double) l2 - pointD_Z) * ((double) l2 - pointD_Z) + ((double) k2 - pointD_X) * ((double) k2 - pointD_X);
if (distance_A < distance_C && distance_A < distance_B && distance_A < distance_D) {
aint_support[id++] = a1;
} else if (distance_C < distance_A && distance_C < distance_B && distance_C < distance_D) {
aint_support[id++] = a3;
} else if (distance_B < distance_A && distance_B < distance_C && distance_B < distance_D) {
aint_support[id++] = a2;
} else {
aint_support[id++] = a4;
}
}
}
a1 = a3;
a2 = a4;
}
}
int[] aint_result = IntCache.getIntCache(areaWidth * areaHeight);
for (int l3 = 0; l3 < areaHeight; ++l3) {
System.arraycopy(aint_support, (l3 + (areaY & 3)) * k_support + (areaX & 3), areaWidth);
}
return aint_result;
}
offset
- величина, которая определяет насколько сильно будут смещаться вспомогательные точки относительно изначального положения.pointA_X
иpointA_Z
- координаты вспомогательной точки А, соответствующей точкеa1
.pointB_X
иpointB_Z
- координаты вспомогательной точки B, соответствующей точкеa2
.pointC_X
иpointC_Z
- координаты вспомогательной точки C, соответствующей точкеa3
.pointD_X
иpointD_Z
- координаты вспомогательной точки D, соответствующей точкеa4
.l2
иk2
- используются при создании области 4 * 4.distance_A
,distance_B
,distance_C
иdistance_D
- расстояния от точки из области 4 * 4 до точек A, B, C и D соответственно.
Java:
areaX = areaX - 2;
areaY = areaY - 2;
a1
и использовалась всего одна инициализация чанкового сида, соответствующая данной точке, в GenLayerVoronoiZoom смежные точки зависят от всех четырех, поэтому при расчете координат вспомогательных точек чанковый сид инициализируется по новой для a1
, a2
, a3
и a4
.
Java:
this.initChunkSeed((long) (k1 + i << 2), (long) (l1 + j << 2));
this.initChunkSeed((long) (k1 + i + 1 << 2), (long) (l1 + j << 2));
this.initChunkSeed((long) (k1 + i << 2), (long) (l1 + j + 1 << 2));
this.initChunkSeed((long) (k1 + i + 1 << 2), (long) (l1 + j + 1 << 2));
aint_support
). Все координаты вспомогательных точек вычисляются по одному алгоритму:координата вспомогательной точки = смещение + изначальная координата точки
Соответственно, отбросив первое слагаемое координаты вспомогательных точек A, B, C и D будут соответствовать координатам точек
a1
, a2
, a3
и a4
в массиве данных aint_support
, поэтому изначальная координата точки
в нашем случае будет равна либо 0, либо 4. Само же смещение принимает случайное значение в диапазоне [-1.8, 1.8]
.
Java:
double offset = 3.6D;
double pointA_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointA_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointC_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
double pointC_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointB_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset;
double pointB_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
double pointD_X = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
double pointD_Z = ((double) this.nextInt(1024) / 1024.0D - 0.5D) * offset + 4.0D;
Java:
int a3 = aint[k1 + 1 + (l1 + 0) * k] & 255;
int a4 = aint[k1 + 1 + (l1 + 1) * k] & 255;
Java:
for (int l2 = 0; l2 < 4; ++l2) {
...
for (int k2 = 0; k2 < 4; ++k2) {
...
}
}
Java:
int id = (k1 << 2) + ((l1 << 2) + l2) * k_support;
Java:
double distance_A = ((double) l2 - pointA_Z) * ((double) l2 - pointA_Z) + ((double) k2 - pointA_X) * ((double) k2 - pointA_X);
double distance_C = ((double) l2 - pointC_Z) * ((double) l2 - pointC_Z) + ((double) k2 - pointC_X) * ((double) k2 - pointC_X);
double distance_B = ((double) l2 - pointB_Z) * ((double) l2 - pointB_Z) + ((double) k2 - pointB_X) * ((double) k2 - pointB_X);
double distance_D = ((double) l2 - pointD_Z) * ((double) l2 - pointD_Z) + ((double) k2 - pointD_X) * ((double) k2 - pointD_X);
Java:
if (distance_A < distance_C && distance_A < distance_B && distance_A < distance_D) {
aint_support[id++] = a1;
} else if (distance_C < distance_A && distance_C < distance_B && distance_C < distance_D) {
aint_support[id++] = a3;
} else if (distance_B < distance_A && distance_B < distance_C && distance_B < distance_D) {
aint_support[id++] = a2;
} else {
aint_support[id++] = a4;
}
l1
и k1
значения a1
, a2
, a3
и a4
будут следующие:aint_support
(само понятие вспомогательной области 4 * 4 нужно лишь для понимания самого процесса):aint_support
:aint_support
находится 16 массивов, которые по размерам соответствуют aint_result
, поэтому побитовые операции также были изменены на areaX & 3
и areaY & 3
. Как результат в нашем случае (areaX
и areaY
равны -2) будет скопирована данная область:2 Собственные слои
2.1 Алгоритм генерации оболочки (АГО)
Java:
public static GenLayer standartIsland(long seed) {
GenLayer genlayer = new GenLayerIsland(1L);
genlayer = new GenLayerFuzzyZoom(2000L, genlayer);
genlayer = new GenLayerAddIsland(1L, genlayer);
genlayer = new GenLayerZoom(2001L, genlayer);
genlayer = new GenLayerAddIsland(2L, genlayer);
genlayer = new GenLayerAddIsland(50L, genlayer);
genlayer = new GenLayerAddIsland(70L, genlayer);
genlayer = new GenLayerRemoveTooMuchOcean(2L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerZoom(2002L, genlayer);
genlayer = new GenLayerZoom(2003L, genlayer);
GenLayer pre_river = new GenLayerAddIsland(4L, genlayer);
genlayer = GenLayerZoom.magnify(1000L, pre_river, 2);
int biomeSize = 4;
for (int k = 0; k < biomeSize; k++) {
genlayer = new GenLayerZoom((long) (1000 + k), genlayer);
if (k == 0) {
genlayer = new GenLayerAddIsland(3L, genlayer);
}
}
genlayer = new GenLayerSmooth(1000L, genlayer);
// ГЕНЕРАЦИЯ РЕК
int riverSize = biomeSize;
GenLayer river = new GenLayerRiverInit(100L, pre_river);
river = GenLayerZoom.magnify(1000L, river, 2);
river = GenLayerZoom.magnify(1000L, river, riverSize);
river = new GenLayerRiver(1L, river);
river = new GenLayerSmooth(1000L, river);
genlayer = new GenLayerRiverMix(100L, genlayer, river);
// ГЕНЕРАЦИЯ РЕК
genlayer = new GenLayerVoronoiZoom(10L, genlayer);
genlayer.initWorldGenSeed(seed);
return genlayer;
}
- Алгоритм генерирует исключительно поверхность и реки
- Алгоритм не генерирует поверхность в океане (островки)
- В некоторых местах есть океан вместо поверхности, а вместо поверхности - океан. Причиной тому то, что в процессе генерации массива чисел каждый слоем эти массивы отличаются по размеру друг от друга. Если конечный результат по размерам будет одинаковый, то, например, изначальный слой может и будет отличаться.
2.2 Скалистое побережье
Представим, что нам нужно добавить генерацию небольших скоплений скалистых шпилей рядом с побережьем. Самым простым вариантом будет добавить генерацию биома, в котором уже и будут генерироваться эти скалистые шпили. В качестве основы будем использовать АГО (без рек):
Java:
public static GenLayer[] rockyCoast(long seed, WorldType worldType) {
GenLayer genlayer = new GenLayerIsland(1L);
genlayer = new GenLayerFuzzyZoom(2000L, genlayer);
genlayer = new GenLayerAddIsland(1L, genlayer);
genlayer = new GenLayerZoom(2001L, genlayer);
genlayer = new GenLayerAddIsland(2L, genlayer);
genlayer = new GenLayerAddIsland(50L, genlayer);
genlayer = new GenLayerAddIsland(70L, genlayer);
genlayer = new GenLayerRemoveTooMuchOcean(2L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerZoom(2002L, genlayer);
genlayer = new GenLayerZoom(2003L, genlayer);
GenLayer pre_river = new GenLayerAddIsland(4L, genlayer);
genlayer = GenLayerZoom.magnify(1000L, pre_river, 2);
int biomeSize = 4;
for (int k = 0; k < biomeSize; k++) {
genlayer = new GenLayerZoom((long) (1000 + k), genlayer);
if (k == 0) {
genlayer = new GenLayerAddIsland(3L, genlayer);
}
}
GenLayer genlayer_world = new GenLayerSmooth(1000L, genlayer);
genlayer = new GenLayerVoronoiZoom(10L, genlayer_world);
genlayer_world.initWorldGenSeed(seed);
genlayer.initWorldGenSeed(seed);
return new GenLayer[] { genlayer_world, genlayer, genlayer_world };
}
- PRE - создаем область из идентификаторов -100
- POST - вокруг этой области генерируем биом ROCKY_COAST
Java:
public class GenLayerRockyCoastIsland extends GenLayer {
private final Mode mode;
public GenLayerRockyCoastIsland(long seed, GenLayer parent, GenLayerRockyCoastIsland.Mode mode) {
super(seed);
this.parent = parent;
this.mode = mode;
}
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
switch (this.mode) {
case PRE:
return pre_getInts(areaX, areaY, areaWidth, areaHeight);
default:
return post_getInts(areaX, areaY, areaWidth, areaHeight);
}
}
private int[] pre_getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX - 1;
int j = areaY - 1;
int k = areaWidth + 2;
int l = areaHeight + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int[] aint1 = IntCache.getIntCache(areaWidth * areaHeight);
for (int i1 = 0; i1 < areaHeight; ++i1) {
for (int j1 = 0; j1 < areaWidth; ++j1) {
int k1 = aint[j1 + 0 + (i1 + 0) * k];
int l1 = aint[j1 + 2 + (i1 + 0) * k];
int i2 = aint[j1 + 0 + (i1 + 2) * k];
int j2 = aint[j1 + 2 + (i1 + 2) * k];
int k2 = aint[j1 + 1 + (i1 + 1) * k];
this.initChunkSeed((long) (j1 + areaX), (long) (i1 + areaY));
if (k2 == Biome.getIdForBiome(Biomes.PLAINS) && (k1 == Biome.getIdForBiome(Biomes.OCEAN) || l1 == Biome.getIdForBiome(Biomes.OCEAN) || i2 == Biome.getIdForBiome(Biomes.OCEAN) || j2 == Biome.getIdForBiome(Biomes.OCEAN)) && this.nextInt(10) == 0) {
aint1[j1 + i1 * areaWidth] = -100;
} else {
aint1[j1 + i1 * areaWidth] = k2;
}
}
}
return aint1;
}
private int[] post_getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX - 1;
int j = areaY - 1;
int k = areaWidth + 2;
int l = areaHeight + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int[] aint1 = IntCache.getIntCache(areaWidth * areaHeight);
for (int i1 = 0; i1 < areaHeight; ++i1) {
for (int j1 = 0; j1 < areaWidth; ++j1) {
int k1 = aint[j1 + 1 + (i1 + 0) * k];
int l1 = aint[j1 + 0 + (i1 + 1) * k];
int i2 = aint[j1 + 1 + (i1 + 2) * k];
int j2 = aint[j1 + 2 + (i1 + 1) * k];
int k2 = aint[j1 + 1 + (i1 + 1) * k];
this.initChunkSeed((long) (j1 + areaX), (long) (i1 + areaY));
if (k2 == Biome.getIdForBiome(Biomes.PLAINS)
&& (k1 == -100 || l1 == -100 || i2 == -100 || j2 == -100)) {
aint1[j1 + i1 * areaWidth] = Biome.getIdForBiome(BiomeInit.ROCKY_COAST);
} else {
aint1[j1 + i1 * areaWidth] = k2;
}
}
}
return aint1;
}
public static enum Mode {
PRE, POST;
}
}
Java:
public static GenLayer[] rockyCoast(long seed, WorldType worldType) {
GenLayer genlayer = new GenLayerIsland(1L);
genlayer = new GenLayerFuzzyZoom(2000L, genlayer);
genlayer = new GenLayerAddIsland(1L, genlayer);
genlayer = new GenLayerZoom(2001L, genlayer);
genlayer = new GenLayerAddIsland(2L, genlayer);
genlayer = new GenLayerAddIsland(50L, genlayer);
genlayer = new GenLayerAddIsland(70L, genlayer);
genlayer = new GenLayerRemoveTooMuchOcean(2L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerZoom(2002L, genlayer);
genlayer = new GenLayerZoom(2003L, genlayer);
GenLayer pre_river = new GenLayerAddIsland(4L, genlayer);
genlayer = new GenLayerRockyCoastIsland(1L, pre_river, GenLayerRockyCoastIsland.Mode.PRE); // Генерируем область из идентификаторов -100
genlayer = GenLayerZoom.magnify(1000L, ganlayer, 2);
genlayer = new GenLayerRockyCoastIsland(1L, genlayer, GenLayerRockyCoastIsland.Mode.POST); // Генерируем биом ROCKY_COAST на побережье около идентификаторов -100
int biomeSize = 4;
for (int k = 0; k < biomeSize; k++) {
genlayer = new GenLayerZoom((long) (1000 + k), genlayer);
if (k == 0) {
genlayer = new GenLayerAddIsland(3L, genlayer);
}
}
GenLayer genlayer_world = new GenLayerSmooth(1000L, genlayer);
genlayer = new GenLayerVoronoiZoom(10L, genlayer_world);
genlayer_world.initWorldGenSeed(seed);
genlayer.initWorldGenSeed(seed);
return new GenLayer[] { genlayer_world, genlayer, genlayer_world };
}
[2, 2]
и она соответствует биому ROCKY_COAST. Появляется 5 точка только с небольшим шансом при условии, что остальные 4 точки - черная область:
Java:
public class GenLayerRockyCoastOcean extends GenLayer {
public GenLayerRockyCoastOcean(long seed, GenLayer parent) {
super(seed);
super.parent = parent;
}
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX >> 2;
int j = areaY >> 2;
int k = (areaWidth >> 2) + 2;
int l = (areaHeight >> 2) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int k_support = k - 1 << 2;
int l_support = l - 1 << 2;
int[] aint_support = IntCache.getIntCache(k_support * l_support);
for (int l1 = 0; l1 < l - 1; ++l1) {
int a1 = aint[0 + 0 + (l1 + 0) * k];
int a2 = aint[0 + 0 + (l1 + 1) * k];
for (int k1 = 0; k1 < k - 1; ++k1) {
int a3 = aint[k1 + 1 + (l1 + 0) * k];
int a4 = aint[k1 + 1 + (l1 + 1) * k];
List<Integer> alist = new ArrayList<Integer>();
alist.add(a1);
alist.add(a2);
alist.add(a3);
alist.add(a4);
List<Point> points = new ArrayList<Point>();
for (int k3 = 0; k3 < 2; k3++) {
for (int l3 = 0; l3 < 2; l3++) {
this.initChunkSeed((long) (k1 + i + k3 << 2), (long) (l1 + j + l3 << 2));
int x = this.nextInt(1025);
int z = this.nextInt(1025);
Point point = new Point((double) k3 * 4.0D, (double) l3 * 4.0D, 3.6D, alist.get(l3 + k3 * 2));
point.rand(x, z);
points.add(point);
}
}
if (a1 == -100 && a2 == -100 && a3 == -100 && a4 == -100) {
this.initChunkSeed((long) (k1 + i << 2), (long) (l1 + j << 2));
if (this.nextInt(3) == 0) {
int x = this.nextInt(1025);
int z = this.nextInt(1025);
Point point = new Point(2.0D, 2.0D, 4.0D, Biome.getIdForBiome(BiomeInit.ROCKY_COAST));
point.rand(x, z);
points.add(point);
}
}
for (int l2 = 0; l2 < 4; ++l2) {
int id = (k1 << 2) + ((l1 << 2) + l2) * k_support;
for (int k2 = 0; k2 < 4; ++k2) {
int calc = calculateRocks(k2, l2, points);
if (calc == -100) {
calc = Biome.getIdForBiome(Biomes.OCEAN);
}
aint_support[id++] = calc;
}
}
a1 = a3;
a2 = a4;
}
}
int[] aint_result = IntCache.getIntCache(areaWidth * areaHeight);
for (int l3 = 0; l3 < areaHeight; ++l3) {
System.arraycopy(aint_support, (l3 + (areaY & 3)) * k_support + (areaX & 3), aint_result, l3 * areaWidth, areaWidth);
}
return aint_result;
}
private int calculateRocks(int k2, int l2, List<Point> points) {
Point current = new Point(k2, l2);
double distance = 100D;
int calc = 0;
for (Point point : points) {
double d0 = (current.posX - point.posX) * (current.posX - point.posX)
+ (current.posZ - point.posZ) * (current.posZ - point.posZ);
if (d0 < distance) {
distance = d0;
calc = point.id;
}
}
return calc;
}
private class Point {
double posX;
double posZ;
double offset;
int id;
Point(double posX, double posZ) {
this(posX, posZ, 0, 0);
}
Point(double posX, double posZ, double offset, int id) {
this.posX = posX;
this.posZ = posZ;
this.offset = offset;
this.id = id;
}
public void rand(int x, int z) {
this.posX += ((double) x / 1024.0D - 0.5D) * offset;
this.posZ += ((double) z / 1024.0D - 0.5D) * offset;
}
}
}
Java:
int biomeSize = 4;
for (int k = 0; k < biomeSize; k++) {
genlayer = new GenLayerZoom((long) (1000 + k), genlayer);
if (k == 0) {
genlayer = new GenLayerAddIsland(3L, genlayer);
}
}
Java:
genlayer = new GenLayerZoom(1000L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerRockyCoastOcean(1L, genlayer); // Генерируем биом ROCKY_COAST в пределах черной области и заменяем черную область на OCEAN
genlayer = new GenLayerZoom(1003L, genlayer);
Java:
public static GenLayer[] rockyCoast(long seed, WorldType worldType) {
GenLayer genlayer = new GenLayerIsland(1L);
genlayer = new GenLayerFuzzyZoom(2000L, genlayer);
genlayer = new GenLayerAddIsland(1L, genlayer);
genlayer = new GenLayerZoom(2001L, genlayer);
genlayer = new GenLayerAddIsland(2L, genlayer);
genlayer = new GenLayerAddIsland(50L, genlayer);
genlayer = new GenLayerAddIsland(70L, genlayer);
genlayer = new GenLayerRemoveTooMuchOcean(2L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerZoom(2002L, genlayer);
genlayer = new GenLayerZoom(2003L, genlayer);
genlayer = new GenLayerAddIsland(4L, genlayer);
genlayer = new GenLayerRockyCoastIsland(1L, pre_river, GenLayerRockyCoastIsland.Mode.PRE); // Генерируем область из идентификаторов -100
genlayer = GenLayerZoom.magnify(1000L, ganlayer, 2);
genlayer = new GenLayerRockyCoastIsland(1L, genlayer, GenLayerRockyCoastIsland.Mode.POST); // Генерируем биом ROCKY_COAST на побережье около идентификаторов -100
genlayer = new GenLayerZoom(1000L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerRockyCoastOcean(1L, genlayer); // Генерируем биом ROCKY_COAST в пределах черной области и заменяем черную область на OCEAN
genlayer = new GenLayerZoom(1003L, genlayer);
GenLayer genlayer_world = new GenLayerSmooth(1000L, genlayer);
genlayer = new GenLayerVoronoiZoom(10L, genlayer_world);
genlayer_world.initWorldGenSeed(seed);
genlayer.initWorldGenSeed(seed);
return new GenLayer[] { genlayer_world, genlayer, genlayer_world };
}
2.3 Эффект диффузии
А теперь добавим биом REDSERT с границей REDSERT_EDGE, который будет проникать в окружающие его биомы. Используем АГО (без рек). Добавим слой GenLayerDiffusionIsland, который будет генерировать биом REDSERT:
Java:
public class GenLayerDiffusionIsland extends GenLayer {
public GenLayerDiffusionIsland(long seed, GenLayer parent) {
super(seed);
this.parent = parent;
}
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX - 1;
int j = areaY - 1;
int k = areaWidth + 2;
int l = areaHeight + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int[] aint1 = IntCache.getIntCache(areaWidth * areaHeight);
for (int i1 = 0; i1 < areaHeight; ++i1) {
for (int j1 = 0; j1 < areaWidth; ++j1) {
int k1 = aint[j1 + 1 + (i1 + 0) * k];
int l1 = aint[j1 + 0 + (i1 + 1) * k];
int i2 = aint[j1 + 1 + (i1 + 2) * k];
int j2 = aint[j1 + 2 + (i1 + 1) * k];
int k2 = aint[j1 + 1 + (i1 + 1) * k];
this.initChunkSeed((long) (j1 + areaX), (long) (i1 + areaY));
if (k2 == Biome.getIdForBiome(Biomes.PLAINS) && k1 != Biome.getIdForBiome(Biomes.OCEAN) && l1 != Biome.getIdForBiome(Biomes.OCEAN) && i2 != Biome.getIdForBiome(Biomes.OCEAN) && j2 != Biome.getIdForBiome(Biomes.OCEAN) && this.nextInt(10) == 0) {
aint1[j1 + i1 * areaWidth] = Biome.getIdForBiome(BiomeInit.REDSERT);
} else {
aint1[j1 + i1 * areaWidth] = k2;
}
}
}
return aint1;
}
}
a1
и a4
или a2
и a3
), то пятая (центральная) точка будет тоже REDSERT, иначе точки не будет и алгоритм станет обычным разбиением Вороного:
Java:
public class GenLayerDiffusionEffect extends GenLayer {
public GenLayerDiffusionEffect(long seed, GenLayer parent) {
super(seed);
super.parent = parent;
}
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX >> 2;
int j = areaY >> 2;
int k = (areaWidth >> 2) + 2;
int l = (areaHeight >> 2) + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int k_support = k - 1 << 2;
int l_support = l - 1 << 2;
int[] aint_support = IntCache.getIntCache(k_support * l_support);
for (int l1 = 0; l1 < l - 1; ++l1) {
int a1 = aint[0 + 0 + (l1 + 0) * k];
int a2 = aint[0 + 0 + (l1 + 1) * k];
for (int k1 = 0; k1 < k - 1; ++k1) {
int a3 = aint[k1 + 1 + (l1 + 0) * k];
int a4 = aint[k1 + 1 + (l1 + 1) * k];
List<Integer> alist = new ArrayList<Integer>();
alist.add(a1);
alist.add(a2);
alist.add(a3);
alist.add(a4);
List<Point> points = new ArrayList<Point>();
for (int k3 = 0; k3 < 2; k3++) {
for (int l3 = 0; l3 < 2; l3++) {
this.initChunkSeed((long) (k1 + i + k3 << 2), (long) (l1 + j + l3 << 2));
int x = this.nextInt(1025);
int z = this.nextInt(1025);
Point point = new Point((double) k3 * 4.0D, (double) l3 * 4.0D, 3.6D, alist.get(l3 + k3 * 2));
point.rand(x, z);
points.add(point);
}
}
int mesa = 0;
int plains = 0;
for (int a : alist) {
if (a == Biome.getIdForBiome(BiomeInit.REDSERT)) {
mesa++;
} else if (a == Biome.getIdForBiome(Biomes.PLAINS)) {
plains++;
}
}
boolean b1 = mesa == 3 && plains == 1;
boolean b2 = mesa == 1 && plains == 3;
if (b1 || b2) {
Point point = new Point(2.0D, 2.0D, 0.0D, b2 ? Biome.getIdForBiome(BiomeInit.REDSERT) : Biome.getIdForBiome(Biomes.PLAINS));
points.add(point);
}
for (int l2 = 0; l2 < 4; ++l2) {
int id = (k1 << 2) + ((l1 << 2) + l2) * k_support;
for (int k2 = 0; k2 < 4; ++k2) {
aint_support[id++] = calculateRocks(k2, l2, points);
}
}
a1 = a3;
a2 = a4;
}
}
int[] aint_result = IntCache.getIntCache(areaWidth * areaHeight);
for (int l3 = 0; l3 < areaHeight; ++l3) {
System.arraycopy(aint_support, (l3 + (areaY & 3)) * k_support + (areaX & 3), aint_result, l3 * areaWidth, areaWidth);
}
return aint_result;
}
private int calculateRocks(int k2, int l2, List<Point> points) {
Point current = new Point(k2, l2);
double distance = 100D;
int calc = 0;
for (Point point : points) {
double d0 = (current.posX - point.posX) * (current.posX - point.posX)
+ (current.posZ - point.posZ) * (current.posZ - point.posZ);
if (d0 < distance) {
distance = d0;
calc = point.id;
}
}
return calc;
}
private class Point {
double posX;
double posZ;
double offset;
int id;
Point(double posX, double posZ) {
this(posX, posZ, 0, 0);
}
Point(double posX, double posZ, double offset, int id) {
this.posX = posX;
this.posZ = posZ;
this.offset = offset;
this.id = id;
}
public void rand(int x, int z) {
this.posX += ((double) x / 1024.0D - 0.5D) * offset;
this.posZ += ((double) z / 1024.0D - 0.5D) * offset;
}
}
}
Java:
public class GenLayerDiffusionEdge extends GenLayer {
public GenLayerDiffusionEdge(long seed, GenLayer parent) {
super(seed);
this.parent = parent;
}
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
int i = areaX - 1;
int j = areaY - 1;
int k = areaWidth + 2;
int l = areaHeight + 2;
int[] aint = this.parent.getInts(i, j, k, l);
int[] aint1 = IntCache.getIntCache(areaWidth * areaHeight);
for (int i1 = 0; i1 < areaHeight; ++i1) {
for (int j1 = 0; j1 < areaWidth; ++j1) {
int k1 = aint[j1 + 1 + (i1 + 0) * k];
int l1 = aint[j1 + 0 + (i1 + 1) * k];
int i2 = aint[j1 + 1 + (i1 + 2) * k];
int j2 = aint[j1 + 2 + (i1 + 1) * k];
int k2 = aint[j1 + 1 + (i1 + 1) * k];
this.initChunkSeed((long) (j1 + areaX), (long) (i1 + areaY));
if (k2 != Biome.getIdForBiome(BiomeInit.REDSERT) && (k1 == Biome.getIdForBiome(BiomeInit.REDSERT) || l1 == Biome.getIdForBiome(BiomeInit.REDSERT) || i2 == Biome.getIdForBiome(BiomeInit.REDSERT) || j2 == Biome.getIdForBiome(BiomeInit.REDSERT))) {
aint1[j1 + i1 * areaWidth] = Biome.getIdForBiome(BiomeInit.REDSERT_EDGE);
} else {
aint1[j1 + i1 * areaWidth] = k2;
}
}
}
return aint1;
}
}
Java:
public static GenLayer[] diffusion(long seed, WorldType worldType) {
GenLayer genlayer = new GenLayerIsland(1L);
genlayer = new GenLayerFuzzyZoom(2000L, genlayer);
genlayer = new GenLayerAddIsland(1L, genlayer);
genlayer = new GenLayerZoom(2001L, genlayer);
genlayer = new GenLayerAddIsland(2L, genlayer);
genlayer = new GenLayerAddIsland(50L, genlayer);
genlayer = new GenLayerAddIsland(70L, genlayer);
genlayer = new GenLayerRemoveTooMuchOcean(2L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerZoom(2002L, genlayer);
genlayer = new GenLayerDiffusionIsland(1L, genlayer); // Создаем REDSERT
genlayer = new GenLayerZoom(2003L, genlayer);
genlayer = new GenLayerAddIsland(4L, genlayer);
genlayer = GenLayerZoom.magnify(1000L, genlayer, 2);
genlayer = new GenLayerZoom(1000L, genlayer);
genlayer = new GenLayerAddIsland(3L, genlayer);
genlayer = new GenLayerDiffusionEffect(1L, genlayer); // Эффект диффузии
genlayer = new GenLayerDiffusionEdge(1L, genlayer); // Граница
genlayer = new GenLayerZoom(1001L, genlayer);
GenLayer genlayer_world = new GenLayerSmooth(1000L, genlayer);
genlayer = new GenLayerVoronoiZoom(10L, genlayer_world);
genlayer_world.initWorldGenSeed(seed);
genlayer.initWorldGenSeed(seed);
return new GenLayer[] { genlayer_world, genlayer, genlayer_world };
}