柏林噪声简化版地形生成算法js版代码


不依赖任何其他模块的js代码,方便随时取用,直接复制到项目非常方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* 此JS模块为 基于柏林噪声简化版本的 随机地形生成器
* 对应原理视频讲解:BV1V8411n7Up
*
* 使用方法:
* 在其他js中引入此文件,调用
let ground = new Ground2D(
randint(0, 123456789123),
0,
[
{diff: 3, loud: 5},
{diff: 2, loud: 5},
]
);
展示了一个默认地形
*/


/**
* 随机数生成器
* @param seed
* @return {function()}
*/
function createRandom(seed) {
let value = seed;

return function () {
value = (value * 9301 + 49297) % 233280;
return value / 233280;
}
}

/**
*
* 在这个 hashCode 函数中,使用了哈希算法中的常见技巧——乘法哈希法,
* 即将当前哈希值乘上一个常数后加上新加入的字符,这个常数被称为“乘数”,
* 一般取一个质数,乘数的选取影响哈希算法的性能。
* 在这里,31 是一个较为常见的乘数,因为它是一个奇素数,
* 而且 31 可以被优化为位运算 31 << 5 - 31,
* 这样可以提高计算速度,同时生成的哈希值分布也比较均匀,避免哈希冲突。
* @param str
* @return {number}
*/
function hashCode(str) {
let hash = 0;
const prime = 1000003; // 取一个大一点的质数作为模数
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) % prime;
}
return hash;
}


/**
* 二维噪音类
*/
class Noise2D {
/**
*
* @param diff {Number} 网格间距 整数
* @param seed {Number} 种子
* @param loud {Number} 响度 可以是小数
*/
constructor(diff, seed, loud) {
this.diff = diff;
this.seed = seed;
this.loud = loud;
}

/**
* 返回四个噪音位置
* @param x {Number} 二维数组中的x下标
* @param y {Number} 二维数组中的y下标
*/
getNoisePosition(x, y) {
let xMin, xMax, yMin, yMax;
if (x % this.diff === 0) {
xMin = x;
xMax = x + this.diff;
} else {
xMin = x - x % this.diff;
xMax = xMin + this.diff;
}
if (y % this.diff === 0) {
yMin = y
yMax = y + this.diff
} else {
yMin = y - y % this.diff
yMax = yMin + this.diff
}

// 左上 右上 左下 右下
return [[xMin, yMax], [xMax, yMax], [xMin, yMin], [xMax, yMin]]
}

/**
* 获取一个位置的噪音值,只有在“噪音源点”位置才有值
* 其他位置为0值
* @param x {Number}
* @param y {Number}
* @return {number}
*/
getCoreNoise(x, y) {
let [p1, p2, p3, p4] = this.getNoisePosition(x, y);
for (let [px, py] of [p1, p2, p3, p4]) {
if (px === x && py === y) {
const randomFunc = createRandom(hashCode(`${x}-${y}-${this.seed}`));
return (randomFunc() * 2 - 1) * this.loud;
}
}
return 0
}

/**
* 获取噪音实际值
* @param x {Number}
* @param y {Number}
*/
getBuff(x, y) {
let s = xi => 3 * xi ** 2 - 2 * xi ** 3; // 平滑函数
let [p1, p2, p3, p4] = this.getNoisePosition(x, y);
let [xMin, yMax] = p1;
let [xMax, yMin] = p4;
let qRight = s((x - xMin) / this.diff);
let qLeft = 1 - qRight;
let qTop = s((y - yMin) / this.diff);
let qDown = 1 - qTop;
let n1 = this.getCoreNoise(...p1);
let n2 = this.getCoreNoise(...p2);
let n3 = this.getCoreNoise(...p3);
let n4 = this.getCoreNoise(...p4);
return n1 * (qLeft * qTop) + n2 * (qRight * qTop) + n3 * (qLeft * qDown) + n4 * (qRight * qDown);
}


}

export default class Ground2D {
/**
*
* @param seed {Number} 整数
* @param baseHeight {Number} 基础高度 可以整数可以小数
* @param noiseConfigList {Array} 这个地形内部的噪音配置
* 例如 [
* {diff: 3, loud: 5},
* {diff: 2, loud: 5},
* ]
* 一个噪音层自身的种子取决于seed
*/
constructor(seed, baseHeight, noiseConfigList) {
this.seed = seed;
this.baseHeight = baseHeight;

this.noiseArr = [];
let i = 0;
for (let n of noiseConfigList) {
this.noiseArr.push(new Noise2D(n.diff, this.seed + i, n.loud));
i++;
}
}

getHeight(x, y) {
let sum = 0;
for (let noise of this.noiseArr) {
sum += noise.getBuff(x, y);
}
return this.baseHeight + sum;
}

}



介绍

柏林噪声是一种能够生成连续、无缝的随机分布的算法。它由 Ken Perlin 在 1983 年创建,常用于生成自然风景、云彩、水波等图像。在《我的世界》游戏中,柏林噪声算法被用于生成游戏世界中的地形。具体来说,每个游戏世界都是由一个大方块网格 组成的,每个小方块都有一个高度值。柏林噪声算法可以根据小方块的位置坐标来生成 一个对应的高度值,以此来生成具有自然形态的山丘、河流、峡谷等地形特征。这种 生成方式使得每个游戏世界都具有随机性和独特性,让玩家感受到更加真实的游戏体验。

传入不同的参数,将高频的噪声和低频的噪声叠加在一起形成丰富的地形,遍历棋 盘上的每一个位置对应的叠加后的噪音网络中的噪音值,即可获得整个地形。

QQ图片20230422093152

侧视图

QQ图片20230422093224

俯视图

机械自走棋地形

将低于绿色平面的部分表示草地,将高于绿色的部分表示为石头。以此可以用js来做一些小东西、小游戏。

具体代码细节

在这个代码中,外层函数 createRandom(seed) 返回了一个内层函数。每次调用这个 内层函数,它都会修改外层函数中的变量 value。这个 value 变量可以被认为是一个状 态,记录了当前的随机数生成器所处的状态。 由于 JavaScript 的函数都是对象,当内层函数被返回后,外层函数的执行环境就会 被销毁,但是它的局部变量 value 会一直存在于内层函数的闭包中,因此不会被垃圾回 收。 这个 value 变量的作用就是存储随机数生成器的状态,从而可以保证在每次调用内 层函数时,返回的随机数都是按照确定的算法生成的。 为了在编写代码时区分各个噪音网络的种子,要使得种子参数可以使字符串,这就需要实现以下将字符串哈希化的方法:

1
2
3
4
5
6
7
8
function hashCode(str) {
let hash = 0;
const prime = 1000003; // 取一个大一点的质数作为模数
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) % prime;
}
return hash;
}

此方法使用了基本的数学运算来实现哈希函数,该函数将字符串 str 中每个字符的 ASCII 码值相加,再乘以一个质数 31,最后对一个质数取余数,得到哈希值。 虽然可以实现字符串转哈希值的功能,但哈希冲突的情况比较多,对于需要高效处 理大量数据的场景,使用一些成熟的哈希函数库更加合适,由于此模式的地图相对较小, 只有 19x19,所以采用了相对简单的方式实现。 在此函数中使用了哈希算法中的常见技巧——乘法哈希法,即将当前哈希值乘上一 个常数后加上新加入的字符,这个常数被称为“乘数”,一般取一个质数,乘数的选取 影响哈希算法的性能。 在这里,31 是一个较为常见的乘数,因为它是一个奇素数,而且 hash * 31 可以 被优化为位运算 hash << 5 - hash,这样可以提高计算速度,同时生成的哈希值分布也比 较均匀,避免哈希冲突。

温馨提示

以上文字说明其实是为了凑字数,因为代码不能算入字数,导致代码不能发表,这些文字由chatGPT根据代码来生成。

本文的前两张示意图由up“在下叶凉陈”帮忙绘制。