name:
landmap.js
-rw-r--r--
16119
1function indexOfMax(arr) {
2 if (arr.length === 0) {
3 return -1;
4 }
5
6 var max = arr[0];
7 var maxIndex = 0;
8
9 for (var ind = 1; ind < arr.length; ind++) {
10 if (arr[ind] > max) {
11 maxIndex = ind;
12 max = arr[ind];
13 }
14 }
15 return maxIndex;
16}
17
18function indexOfMin(arr) {
19 if (arr.length === 0) {
20 return -1;
21 }
22
23 var min = arr[0];
24 var minIndex = 0;
25
26 for (var ind = 1; ind < arr.length; ind++) {
27 if (arr[ind] < min) {
28 minIndex = ind;
29 min = arr[ind];
30 }
31 }
32 return minIndex;
33}
34
35// options can be a serialized LandMap
36function LandMap(options) {
37 var level = 8;
38 this.containerId = options.containerId;
39 this.size = Math.pow(2, level) + 1;
40 this.max = this.size - 1;
41 this.maps = options.maps || {};
42 this.meta = options.meta || {}
43}
44
45LandMap.prototype.get = function(which, x, y) {
46 if (x < 0 || x > this.max || y < 0 || y > this.max) {
47 return -1;
48 } else {
49 return this.maps[which][x + this.size * y];
50 }
51};
52
53LandMap.prototype.set = function(which, x, y, value) {
54 this.maps[which][(x + this.size * y)] = value;
55};
56
57LandMap.prototype.generate = function(options) {
58 var deviationAmount = options.deviation,
59 feature = options.feature;
60 this.meta[options.feature] = options;
61 var self = this;
62
63 if (!(feature in self.maps)) {
64 this.maps[feature] = new Array(this.size * this.size);
65 }
66
67 this.set(feature, 0, 0, Math.random() * self.max);
68 this.set(feature, this.max, 0, Math.random() * self.max);
69 this.set(feature, this.max, this.max, Math.random() * self.max);
70 this.set(feature, 0, this.max, Math.random() * self.max);
71
72 subdivide(this.max);
73
74 function subdivide(size) {
75 var x, y, half = size / 2;
76 var scale = deviationAmount * size;
77 if (half < 1) return;
78
79 for (y = half; y < self.max; y += size) {
80 for (x = half; x < self.max; x += size) {
81 square(feature, x, y, half, Math.random() * scale * 2 - scale);
82 }
83 }
84 for (y = 0; y <= self.max; y += half) {
85 for (x = (y + half) % size; x <= self.max; x += size) {
86 diamond(feature, x, y, half, Math.random() * scale * 2 - scale);
87 }
88 }
89 subdivide(size / 2);
90 }
91
92 function average(values) {
93 var valid = values.filter(function(val) {
94 return val !== -1;
95 });
96 var total = valid.reduce(function(sum, val) {
97 return sum + val;
98 }, 0);
99 return total / valid.length;
100 }
101
102 function square(which, x, y, size, offset) {
103 var ave = average([
104 self.get(which, x - size, y - size), // upper left
105 self.get(which, x + size, y - size), // upper right
106 self.get(which, x + size, y + size), // lower right
107 self.get(which, x - size, y + size) // lower left
108 ]);
109 self.set(which, x, y, ave + offset);
110 }
111
112 function diamond(which, x, y, size, offset) {
113 var ave = average([
114 self.get(which, x, y - size), // top
115 self.get(which, x + size, y), // right
116 self.get(which, x, y + size), // bottom
117 self.get(which, x - size, y) // left
118 ]);
119 self.set(which, x, y, ave + offset);
120 }
121};
122
123LandMap.prototype.smooth = function(options) {
124 var amount = options.amount,
125 featureFrom = options.from,
126 featureTo = options.to;
127
128 this.meta[featureTo] = options;
129
130 if (!(featureTo in this.maps)) {
131 this.maps[featureTo] = new Array(this.size * this.size);
132 }
133
134 var self = this;
135 var MARGIN = amount;
136 for (var y = 0; y < this.size; y++) {
137 for (var x = 0; x < this.size; x++) {
138 var nextValue = self.get(featureFrom, x, y);
139 var nextValueCount = 0;
140 for (var xRange = Math.max(x - Math.round(MARGIN), 0); xRange < Math.min(x + Math.round(MARGIN), this.size); xRange++) {
141 for (var yRange = Math.max(y - Math.round(MARGIN), 0); yRange < Math.min(y + Math.round(MARGIN), this.size); yRange++) {
142 if (distanceBetween(x, y, xRange, yRange) <= MARGIN) {
143 var value = self.get(featureFrom, xRange, yRange);
144 nextValue = nextValue + value;
145 nextValueCount++;
146 }
147 }
148 }
149 var finalVal = ((nextValue / nextValueCount));
150 self.set(featureTo, x, y, finalVal);
151 }
152 }
153
154 function distanceBetween(ax, ay, bx, by) {
155 return Math.abs(Math.sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by)));
156 }
157};
158
159LandMap.prototype.combine = function(options) {
160 var one = options.one,
161 two = options.two,
162 three = options.three,
163 result = options.result;
164
165 this.meta[result] = options;
166
167 function percent(value, max, min) {
168 return value / Math.abs((max - min));
169 }
170
171 if (!(result in this.maps)) {
172 this.maps[result] = new Array(this.size * this.size);
173 }
174
175 var max = Number.MIN_SAFE_INTEGER;
176 var min = Number.MAX_SAFE_INTEGER;
177 for (var y = 0; y < this.size; y++) {
178 for (var x = 0; x < this.size; x++) {
179 var val = this.get(two, x, y);
180 if (val !== undefined) {
181 max = Math.max(max, val);
182 min = Math.min(min, val);
183 }
184 }
185 }
186
187 for (var y = 0; y < this.size; y++) {
188 for (var x = 0; x < this.size; x++) {
189 var featureOne = this.get(one, x, y);
190 var featureTwo = this.get(two, x, y);
191 var featureThree = this.get(three, x, y);
192 if (featureOne !== undefined && featureTwo !== undefined && featureThree !== undefined) {
193 var featureThreePercent = percent(featureThree, min, max);
194 var val = (1 - featureThreePercent) * featureOne + featureThreePercent * featureTwo;
195 this.set(result, x, y, val);
196 }
197 }
198 }
199};
200
201LandMap.prototype.grd = function(options) {
202 var amount = options.amount,
203 percent = options.percent,
204 featureFrom = options.from,
205 featureTo = options.to;
206
207 this.meta[featureTo] = options;
208
209 this.maps[featureTo] = new Array(this.size * this.size);
210
211 for (var y = 0; y < this.size; y++) {
212 for (var x = 0; x < this.size; x++) {
213 this.set(featureTo, x, y, this.get(featureFrom, x, y));
214 }
215 }
216
217 var operationAray = new Array(this.size * this.size);
218 for (var y = 0; y < this.size; y++) {
219 for (var x = 0; x < this.size; x++) {
220 operationAray[(x + this.size * y)] = [0];
221 }
222 }
223
224 var MARGIN = amount;
225 for (var i = 0; i < MARGIN; i++) {
226 operationAray[(x + this.size * y)] = [0];
227 var max = Number.MIN_SAFE_INTEGER;
228 var min = Number.MAX_SAFE_INTEGER;
229 for (var y = 0; y < this.size; y++) {
230 for (var x = 0; x < this.size; x++) {
231 var val = this.get(featureTo, x, y);
232 if (val !== undefined) {
233 max = Math.max(max, val);
234 min = Math.min(min, val);
235 }
236 }
237 }
238
239 // iterate through all
240 // for (var xRange = Math.max(x - Math.round(MARGIN), 0); xRange < Math.min(x + Math.round(MARGIN), this.size); xRange++) {
241 // for (var yRange = Math.max(y - Math.round(MARGIN), 0); yRange < Math.min(y + Math.round(MARGIN), this.size); yRange++) {
242 for (var y = 1; y < this.size-2; y++) {
243 for (var x = 1; x < this.size-2; x++) {
244 var neighbors = [
245 this.get(featureTo, x - 1, y),
246 this.get(featureTo, x + 1, y),
247 this.get(featureTo, x, y - 1),
248 this.get(featureTo, x, y + 1)
249 ];
250 operationAray[(x + this.size * y)].push(this.get(featureTo, x, y));
251 var index = indexOfMax(featureTo);
252 var thisValue = this.get(featureTo, x, y);
253 if (neighbors[index] > thisValue) {
254 if (index == 0) {
255 // WEST (left)
256 operationAray[(x + this.size * y)].push(this.get(featureTo, x - 1, y) * percent);
257 operationAray[((x - 1) + this.size * y)].push(this.get(featureTo, x - 1, y) * (percent) * (-1));
258 } else if (index == 1) {
259 // EAST (right)
260 operationAray[(x + this.size * y)].push(this.get(featureTo, x + 1, y) * percent);
261 operationAray[((x + 1) + this.size * y)].push(this.get(featureTo, x + 1, y) * (percent) * (-1));
262 } else if (index == 2) {
263 // NORTH (up)
264 operationAray[(x + this.size * y)].push(this.get(featureTo, x, y - 1) * percent);
265 operationAray[(x + this.size * (y - 1))].push(this.get(featureTo, x - 1, y) * (percent) * (-1));
266 } else if (index == 3) {
267 // SOUTH (down)
268 operationAray[(x + this.size * y)].push(this.get(featureTo, x, y + 1) * percent);
269 operationAray[(x + this.size * (y + 1))].push(this.get(featureTo, x, y + 1) * (percent) * (-1));
270 }
271 }
272 }
273 }
274 //iterate through summing the operationAray, and setting it
275 for (var y = 1; y < this.size - 2; y++) {
276 for (var x = 1; x < this.size - 2; x++) {
277 var value = operationAray[(x + this.size * y)].reduce(function(a, b) {
278 return a + b;
279 }, 0);
280 this.set(featureTo, x, y, value);
281 operationAray[(x + this.size * y)] = [value];
282 }
283 }
284 }
285};
286
287LandMap.prototype.simpleErosion = function(options) {
288 var Kq = options.carryingCapacity;
289 var Kd = options.depositionSpeed;
290 var iterations = options.iterations;
291 var drops = options.drops;
292 var one = options.from;
293 var two = options.to;
294
295 this.meta[two] = options;
296
297 var HeightMap = new Array(this.size * this.size);
298 for (var y = 0; y < this.size; y++) {
299 for (var x = 0; x < this.size; x++) {
300 HeightMap[(x + this.size * y)] = this.get(one, x, y);
301 }
302 }
303
304 var HMAP_SIZE = this.size;
305
306 function HMAP_INDEX(x, y) {
307 var val = (x + HMAP_SIZE * y)
308 return val;
309 }
310
311 function HMAP_VALUE(x, y) {
312 return HeightMap[(x + HMAP_SIZE * y)];
313 }
314
315 function DEPOSIT_AT(X, Y) {
316 var c = 0.0;
317 var v = 1.05;
318 var maxVelocity = 10.0;
319
320 // For the number of iterations
321 for (var iter = 0; iter < iterations; iter++) {
322 v = Math.min(v, maxVelocity); // limiting velocity
323 var val = HMAP_VALUE(X, Y);
324 var nv = [
325 HMAP_VALUE(X, Y - 1), //NORTH
326 HMAP_VALUE(X, Y + 1), //SOUTH
327 HMAP_VALUE(X + 1, Y), //EAST
328 HMAP_VALUE(X - 1, Y) //WEST
329 ];
330
331 var minInd = indexOfMin(nv);
332 // if the lowest neighbor is NOT greater than the current value
333 if (nv[minInd] < val) {
334 //deposit or erode
335 var vtc = Kd * v * Math.abs(nv[minInd]); // value to steal is depositionSpeed * velocity * abs(slope);
336 // if carrying amount is greater than Kq
337 if (c > Kq) {
338 //DEPOSIT
339 c -= vtc;
340 HeightMap[HMAP_INDEX(X, Y)] += vtc;
341 } else {
342 //ERODE
343 // if carrying + value to steal > carrying cap
344 if (c + vtc > Kq) {
345 var delta = c + vtc - Kq;
346 c += delta;
347 HeightMap[HMAP_INDEX(X, Y)] -= delta;
348 } else {
349 c += vtc;
350 HeightMap[HMAP_INDEX(X, Y)] -= vtc;
351 }
352 }
353
354 // move to next value
355 if (minInd == 0) {
356 //NORTH
357 Y -= 1
358 }
359 if (minInd == 1) {
360 //SOUTH
361 Y += 1
362 }
363 if (minInd == 2) {
364 //EAST
365 X += 1
366 }
367 if (minInd == 3) {
368 //WEST
369 X -= 1
370 }
371
372 // limiting to edge of map
373 if (X > this.size - 1) {
374 X = this.size;
375 }
376 if (Y > this.size - 1) {
377 Y = this.size;
378 }
379 if (Y < 0) {
380 Y = 0;
381 }
382 if (X < 0) {
383 X = 0;
384 }
385 }
386 }
387 }
388
389 for (var drop = 0; drop < drops; drop++) {
390 DEPOSIT_AT(Math.floor(Math.random() * this.size), Math.floor(Math.random() * this.size));
391 this.maps[two] = HeightMap;
392 }
393};
394
395LandMap.prototype.complexErosion = function(options) {
396 var Kq = options.carryingCapacity;
397 var Kd = options.depositionSpeed;
398 var iterations = options.iterations;
399 var drops = options.drops;
400 var one = options.from;
401 var two = options.to;
402
403 this.meta[two] = options;
404
405 var HeightMap = new Array(this.size * this.size);
406 for (var y = 0; y < this.size; y++) {
407 for (var x = 0; x < this.size; x++) {
408 HeightMap[(x + this.size * y)] = this.get(one, x, y);
409 }
410 }
411
412 var HMAP_SIZE = this.size;
413
414 function HMAP_INDEX(x, y) {
415 var val = (x + HMAP_SIZE * y)
416 return val;
417 }
418
419 function HMAP_VALUE(x, y) {
420 return HeightMap[(x + HMAP_SIZE * y)];
421 }
422
423 function DEPOSIT_AT(X, Y) {
424 var c = 0.0;
425 var v = 1.05;
426 var minSlope = 1.15;
427 var maxVelocity = 10.0;
428
429 // For the number of iterations
430 for (var iter = 0; iter < iterations; iter++) {
431 v = Math.min(v, maxVelocity); // limiting velocity
432 var val = HMAP_VALUE(X, Y);
433 var nv = [
434 HMAP_VALUE(X, Y - 1), //NORTH
435 HMAP_VALUE(X, Y + 1), //SOUTH
436 HMAP_VALUE(X + 1, Y), //EAST
437 HMAP_VALUE(X - 1, Y) //WEST
438 ];
439
440 var minInd = indexOfMin(nv);
441 // if the lowest neighbor is NOT greater than the current value
442 if (nv[minInd] < val) {
443 //deposit or erode
444 var slope = Math.min(minSlope, val - nv[minInd])
445 var vtc = Kd * v * slope; // value to steal is depositionSpeed * velocity * abs(slope);
446 // if carrying amount is greater than Kq
447 if (c > Kq) {
448 //DEPOSIT
449 c -= vtc;
450 HeightMap[HMAP_INDEX(X, Y)] += vtc;
451 } else {
452 //ERODE
453 // if carrying + value to steal > carrying cap
454 if (c + vtc > Kq) {
455 var delta = c + vtc - Kq;
456 c += delta;
457 HeightMap[HMAP_INDEX(X, Y)] -= delta;
458 } else {
459 c += vtc;
460 HeightMap[HMAP_INDEX(X, Y)] -= vtc;
461 }
462 }
463
464 // move to next value
465 if (minInd == 0) {
466 //NORTH
467 Y -= 1
468 }
469 if (minInd == 1) {
470 //SOUTH
471 Y += 1
472 }
473 if (minInd == 2) {
474 //EAST
475 X += 1
476 }
477 if (minInd == 3) {
478 //WEST
479 X -= 1
480 }
481
482 // limiting to edge of map
483 if (X > this.size - 1) {
484 X = this.size;
485 }
486 if (Y > this.size - 1) {
487 Y = this.size;
488 }
489 if (Y < 0) {
490 Y = 0;
491 }
492 if (X < 0) {
493 X = 0;
494 }
495 }
496 }
497 }
498
499 for (var drop = 0; drop < drops; drop++) {
500 DEPOSIT_AT(Math.floor(Math.random() * this.size), Math.floor(Math.random() * this.size));
501 this.maps[two] = HeightMap;
502 }
503};
504
505LandMap.prototype.draw = function() {
506 var html = '<div class="row">';
507 var featureCount = 0;
508 for (feature in this.maps) {
509 html += '<div class="four columns"><strong>' + feature + '</strong><br><div class="box"><span id="' + this.containerId + feature + '"></span><pre>' + JSON.stringify(this.meta[feature], null, 2) + '</pre></div></div>';
510 featureCount++;
511 if (featureCount == 3) {
512 html += '</div><div class="row">'
513 featureCount = 0;
514 }
515 }
516 document.getElementById(this.containerId).innerHTML = html;
517 for (feature in this.maps) {
518 var display = document.getElementById("tmp");
519 var ctx = display.getContext('2d');
520 var width = display.width = 256;
521 var height = display.height = 256;
522 var self = this;
523
524 var cellWidth = 1;
525
526 function drawCell(x, y, inensity) {
527 ctx.fillStyle = inensity;
528 ctx.fillRect(x * cellWidth, y * cellWidth, cellWidth, cellWidth);
529 }
530
531 // finding max, min
532 var max = Number.MIN_SAFE_INTEGER;
533 var min = Number.MAX_SAFE_INTEGER;
534 for (var y = 0; y < this.size; y++) {
535 for (var x = 0; x < this.size; x++) {
536 var val = this.get(feature, x, y);
537 if (val !== undefined) {
538 max = Math.max(max, val);
539 min = Math.min(min, val);
540 }
541 }
542 }
543 // Drawing each cell
544 for (var y = 0; y < this.size; y++) {
545 for (var x = 0; x < this.size; x++) {
546 var val = this.get(feature, x, y);
547 if (val !== undefined) {
548 drawCell(x, y, brightness(val, max, min));
549 }
550 }
551 }
552
553 document.getElementById(this.containerId + feature).innerHTML = '<img src="' + display.toDataURL("image/png") + '" class="u-max-full-width map"/>';
554 }
555
556 function brightness(value, max, min) {
557 var delta = Math.abs((max - min));
558 var b = Math.floor((value / delta) * 255);
559 return 'rgba(' + b + ',' + b + ',' + b + ',1)';
560 }
561};