-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmapgen.js
More file actions
235 lines (191 loc) · 7.29 KB
/
Copy pathmapgen.js
File metadata and controls
235 lines (191 loc) · 7.29 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// -- A simple class to store the current options --
function MapGenSettings(heightOffset, falloffGradient, falloffArea, outlineMode, extraColour) {
this.heightOffset = heightOffset;
this.falloffGradient = falloffGradient;
this.falloffArea = falloffArea;
this.outlineMode = outlineMode;
this.extraColour = extraColour;
}
// -- TerrainMap is a map of a value (from the DS algorithm) to a colour,
// representing a different height of terrain --
var terrainMap = new Map()
// Deep Ocean
terrainMap[0.05] = [31, 3, 168]
// Ocean
terrainMap[0.15] = [15, 144, 199]
// Beach/Shore
terrainMap[0.30] = [217, 219, 141]
// Lowest/grassland/forest/etc
terrainMap[0.40] = [99, 196, 0]
// Low/grassland/forest/etc
terrainMap[0.60] = [0, 127, 20]
// Hills/rocky/high terrain
terrainMap[0.80] = [100, 82, 29]
// Mountains/highest terrain
terrainMap[0.95] = [100, 83, 64]
function DiamondSquare(size, heightOffset) {
// heightOffset is a number by which (alongside another random number) the height each tile of island
// is modified. it correlates to smoothness
this.size = size;
this.heightOffset = heightOffset;
// Create an empty grid, an array of arrays (size * size)
this.data = createGrid(size);
}
DiamondSquare.prototype = {
diamond:function(x, y, sideLength) {
var halfSide = sideLength / 2;
// Get the average of the four points on the diamond (wrapping
// if necessary)
var avg = this.data[(x - halfSide + this.size - 1) % (this.size - 1)][y] +
this.data[(x + halfSide) % (this.size - 1)][y] +
this.data[x][(y + halfSide) % (this.size - 1)] +
this.data[x][(y - halfSide + this.size - 1) % (this.size - 1)];
avg /= 4.0;
// Add a "random" offset
avg += (Math.random() * 2 * this.heightOffset) - this.heightOffset;
// Clamp between 0 and 1
avg = clamp(avg, 0.0, 1.0)
this.data[x][y] = avg;
// Wrap the values
if (x == 0) this.data[this.size - 1][y] = avg;
if (y == 0) this.data[x][this.size - 1] = avg;
},
square:function(x, y, sideLength) {
var halfSide = sideLength / 2;
// Get the average of the four surrounding corners
var avg = this.data[x][y] +
this.data[x + sideLength][y] +
this.data[x][y + sideLength] +
this.data[x + sideLength][y + sideLength];
avg /= 4.0;
// Add a "random" offset
avg += (Math.random() * 2 * this.heightOffset) - this.heightOffset;
// Clamp between 0 and 1
avg = clamp(avg, 0.0, 1.0);
// Set the square value to the average + a random number affected by the smoothness
this.data[x + halfSide][y + halfSide] = avg;
},
// -- Main DS function --
generate:function() {
// Seed the data: set the top left, top right, bottom left and bottom right values to 0.5 - this
// gives the algorithm a place to start on its first "Square" phase
this.data[0][0] = this.data[0][this.size - 1] = this.data[this.size - 1][0]
= this.data[this.size -1][this.size - 1] = 0.5;
// This iterates the algorithm (based on smoothness), increasing the "depth",
// so it starts with one big square/diamond, and gets smaller and smaller.
// This is why a higher resolution image is basically the same as a lower one,
// except expressed in more details, with more pixels
for (var sideLength = this.size - 1; sideLength >= 2; sideLength /= 2, this.heightOffset /= 2.0) {
// -- Square --
for (var x = 0; x < this.size - 1; x += sideLength) {
for (var y = 0; y < this.size - 1; y += sideLength) {
this.square(x, y, sideLength);
}
}
// -- Diamond --
for (var x = 0; x < this.size - 1; x += sideLength / 2) {
for (var y = (x + sideLength / 2) % sideLength; y < this.size - 1; y += sideLength) {
this.diamond(x, y, sideLength);
}
}
}
return this.data;
}
}
function FallOff(size, gradient, area) {
this.size = size;
this.gradient = gradient;
this.area = area;
}
FallOff.prototype = {
// Credit Sebastian Lague for falloffMap formula
// Evaluates a value from the fall-off map grid
evaluatePoint:function(value) {
// a - Lower a means more gradual fall-off
var a = this.gradient;
// b - lower b means less space for island
var b = this.area;
return Math.pow(value, a) / (Math.pow(value, a) + Math.pow(b - b * value, a));
},
// Generates a fall-off map, a gradual slope towards the sides.
// Using Math.abs(), values towards the sides will be higher and values in the
// middle will be lower
// When taken away from the values generated by the DS algorithm, this means the sides
// of the heightmap will be lower, creating an "island" effect
generate:function() {
// Create a new array grid, the same size as the DS map above
var grid = new Array(this.size)
for (i = 0; i < this.size; i++) {
grid[i] = new Array(this.size)
for (j = 0; j < this.size; j++) {
var x = i / this.size * 2 - 1
var y = j / this.size * 2 - 1
// Use fancy math to determine the fall-off value for this position on the map
var value = Math.max(Math.abs(x), Math.abs(y))
grid[i][j] = this.evaluatePoint(value)
}
}
return grid;
}
}
// -- Function that actually gets a grid of values, then gets a grid of colours, then
// prints it to the off-screen canvas --
function createMap(context, size, settings) {
// Generate the required maps
var dsMap = new DiamondSquare(size, settings.heightOffset).generate()
var alphaDsMap = new DiamondSquare(size, 2).generate()
var falloffMap = new FallOff(size, settings.falloffGradient, settings.falloffArea).generate()
// Get the imagedata of the canvas (for drawing to it by changing the buffer)
var imageData = context.getImageData(0, 0, size, size);
var buffer = new Uint8ClampedArray(size * size * 4);
// Generate the requested style of map
var previousKey = 0.05;
for (var x = 0; x < size; x++) {
for (var y = 0; y < size; y++) {
var DSvalue = clamp(dsMap[x][y] - falloffMap[x][y], 0.0, 1.0);
var terrainMapKey = getTerrainMapKey(DSvalue);
var colour = [];
// If this is a new "strata?" "level?"
if (terrainMapKey != previousKey && settings.outlineMode) {
colour = [21, 21, 18];
} else {
colour = terrainMap[terrainMapKey];
}
var alpha = 255;
if (settings.extraColour) {
alpha = scale(alphaDsMap[x][y], 0, 1, 0.5, 1) * 255;
}
setBufferColourAtPosition(buffer, colour, x, y, size, alpha);
previousKey = terrainMapKey;
}
}
// Change the imagedata's buffer
imageData.data.set(buffer);
// Update the canvas's imagedata
context.putImageData(imageData, 0, 0);
}
// -- Given a colour and a position, sets that position in the buffer to that colour --
function setBufferColourAtPosition(buffer, colour, x, y, size, alpha) {
var pos = (y * size + x) * 4;
for (var i = 0; i < 3; i++) {
buffer[pos + i] = colour[i];
}
buffer[pos + 3] = alpha;
}
// -- Function that iterates through the TerrainMap, and returns the colour
// of the "band" or "strata" of terrain the value belongs to --
function getTerrainMapKey(DSvalue) {
var returnKey;
for (var key in terrainMap) {
if (terrainMap.hasOwnProperty(key)) {
if (DSvalue > key) {
returnKey = key;
}
}
}
if (returnKey == null) {
return 0.05;
} else {
return returnKey;
}
}