The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── LICENSE.md
├── README.md
└── terrain.js


/LICENSE.md:
--------------------------------------------------------------------------------
 1 | This code is licensed under the MIT License:
 2 | 
 3 | > Copyright (c) 2016: Martin O'Leary
 4 | > 
 5 | > Permission is hereby granted, free of charge, to any person obtaining
 6 | > a copy of this software and associated documentation files (the
 7 | > "Software"), to deal in the Software without restriction, including
 8 | > without limitation the rights to use, copy, modify, merge, publish,
 9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | > 
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | > 
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Fantasy map generator
 2 | 
 3 | This is code for generating fantasy maps, using the algorithm behind [@unchartedatlas][uncharted]. For more details, see [these notes][notes].
 4 | 
 5 | ## Dependencies
 6 | 
 7 | This code depends on the following:
 8 | 
 9 |  * [D3.js][d3] (tested with version 4.2.0)
10 |  * Adam Hooper's [js-priority-queue][priority]
11 |  * My [language generation code][language]
12 | 
13 | ## Support, licensing, ongoing development
14 | 
15 | This project is, from my perspective, finished.
16 | 
17 | The code is available under the [MIT license][license], so you can fork it,
18 | improve it, learn from it, build upon it. However, I have no interest in
19 | maintaining it as an ongoing open source project, nor in providing support for
20 | it. Pull requests will be either ignored or closed.
21 | 
22 | If you do make something interesting with this code, please do still let me know! I'm sorry that I can't provide any support, but I am still genuinely interested in seeing creative applications of the code.
23 | 
24 | [uncharted]: https://twitter.com/unchartedatlas
25 | [notes]: https://mewo2.com/notes/terrain/
26 | [language]: https://github.com/mewo2/naming-language/
27 | [priority]: https://github.com/adamhooper/js-priority-queue
28 | [d3]: https://d3js.org/
29 | [license]: https://github.com/mewo2/terrain/blob/master/LICENSE.md
30 | 


--------------------------------------------------------------------------------
/terrain.js:
--------------------------------------------------------------------------------
   1 | "use strict";
   2 | 
   3 | function runif(lo, hi) {
   4 |     return lo + Math.random() * (hi - lo);
   5 | }
   6 | 
   7 | var rnorm = (function () {
   8 |     var z2 = null;
   9 |     function rnorm() {
  10 |         if (z2 != null) {
  11 |             var tmp = z2;
  12 |             z2 = null;
  13 |             return tmp;
  14 |         }
  15 |         var x1 = 0;
  16 |         var x2 = 0;
  17 |         var w = 2.0;
  18 |         while (w >= 1) {
  19 |             x1 = runif(-1, 1);
  20 |             x2 = runif(-1, 1);
  21 |             w = x1 * x1 + x2 * x2;
  22 |         }
  23 |         w = Math.sqrt(-2 * Math.log(w) / w);
  24 |         z2 = x2 * w;
  25 |         return x1 * w;
  26 |     }
  27 |     return rnorm;
  28 | })();
  29 | 
  30 | function randomVector(scale) {
  31 |     return [scale * rnorm(), scale * rnorm()];
  32 | }
  33 | 
  34 | var defaultExtent = {
  35 |     width: 1,
  36 |     height: 1
  37 | };
  38 | 
  39 | function generatePoints(n, extent) {
  40 |     extent = extent || defaultExtent;
  41 |     var pts = [];
  42 |     for (var i = 0; i < n; i++) {
  43 |         pts.push([(Math.random() - 0.5) * extent.width, (Math.random() - 0.5) * extent.height]);
  44 |     }
  45 |     return pts;
  46 | }
  47 | 
  48 | function centroid(pts) {
  49 |     var x = 0;
  50 |     var y = 0;
  51 |     for (var i = 0; i < pts.length; i++) {
  52 |         x += pts[i][0];
  53 |         y += pts[i][1];
  54 |     }
  55 |     return [x/pts.length, y/pts.length];
  56 | }
  57 | 
  58 | function improvePoints(pts, n, extent) {
  59 |     n = n || 1;
  60 |     extent = extent || defaultExtent;
  61 |     for (var i = 0; i < n; i++) {
  62 |         pts = voronoi(pts, extent)
  63 |             .polygons(pts)
  64 |             .map(centroid);
  65 |     }
  66 |     return pts;
  67 | }
  68 | 
  69 | function generateGoodPoints(n, extent) {
  70 |     extent = extent || defaultExtent;
  71 |     var pts = generatePoints(n, extent);
  72 |     pts = pts.sort(function (a, b) {
  73 |         return a[0] - b[0];
  74 |     });
  75 |     return improvePoints(pts, 1, extent);
  76 | }
  77 | 
  78 | function voronoi(pts, extent) {
  79 |     extent = extent || defaultExtent;
  80 |     var w = extent.width/2;
  81 |     var h = extent.height/2;
  82 |     return d3.voronoi().extent([[-w, -h], [w, h]])(pts);
  83 | }
  84 | 
  85 | function makeMesh(pts, extent) {
  86 |     extent = extent || defaultExtent;
  87 |     var vor = voronoi(pts, extent);
  88 |     var vxs = [];
  89 |     var vxids = {};
  90 |     var adj = [];
  91 |     var edges = [];
  92 |     var tris = [];
  93 |     for (var i = 0; i < vor.edges.length; i++) {
  94 |         var e = vor.edges[i];
  95 |         if (e == undefined) continue;
  96 |         var e0 = vxids[e[0]];
  97 |         var e1 = vxids[e[1]];
  98 |         if (e0 == undefined) {
  99 |             e0 = vxs.length;
 100 |             vxids[e[0]] = e0;
 101 |             vxs.push(e[0]);
 102 |         }
 103 |         if (e1 == undefined) {
 104 |             e1 = vxs.length;
 105 |             vxids[e[1]] = e1;
 106 |             vxs.push(e[1]);
 107 |         }
 108 |         adj[e0] = adj[e0] || [];
 109 |         adj[e0].push(e1);
 110 |         adj[e1] = adj[e1] || [];
 111 |         adj[e1].push(e0);
 112 |         edges.push([e0, e1, e.left, e.right]);
 113 |         tris[e0] = tris[e0] || [];
 114 |         if (!tris[e0].includes(e.left)) tris[e0].push(e.left);
 115 |         if (e.right && !tris[e0].includes(e.right)) tris[e0].push(e.right);
 116 |         tris[e1] = tris[e1] || [];
 117 |         if (!tris[e1].includes(e.left)) tris[e1].push(e.left);
 118 |         if (e.right && !tris[e1].includes(e.right)) tris[e1].push(e.right);
 119 |     }
 120 | 
 121 |     var mesh = {
 122 |         pts: pts,
 123 |         vor: vor,
 124 |         vxs: vxs,
 125 |         adj: adj,
 126 |         tris: tris,
 127 |         edges: edges,
 128 |         extent: extent
 129 |     }
 130 |     mesh.map = function (f) {
 131 |         var mapped = vxs.map(f);
 132 |         mapped.mesh = mesh;
 133 |         return mapped;
 134 |     }
 135 |     return mesh;
 136 | }
 137 | 
 138 | 
 139 | 
 140 | function generateGoodMesh(n, extent) {
 141 |     extent = extent || defaultExtent;
 142 |     var pts = generateGoodPoints(n, extent);
 143 |     return makeMesh(pts, extent);
 144 | }
 145 | function isedge(mesh, i) {
 146 |     return (mesh.adj[i].length < 3);
 147 | }
 148 | 
 149 | function isnearedge(mesh, i) {
 150 |     var x = mesh.vxs[i][0];
 151 |     var y = mesh.vxs[i][1];
 152 |     var w = mesh.extent.width;
 153 |     var h = mesh.extent.height;
 154 |     return x < -0.45 * w || x > 0.45 * w || y < -0.45 * h || y > 0.45 * h;
 155 | }
 156 | 
 157 | function neighbours(mesh, i) {
 158 |     var onbs = mesh.adj[i];
 159 |     var nbs = [];
 160 |     for (var i = 0; i < onbs.length; i++) {
 161 |         nbs.push(onbs[i]);
 162 |     }
 163 |     return nbs;
 164 | }
 165 | 
 166 | function distance(mesh, i, j) {
 167 |     var p = mesh.vxs[i];
 168 |     var q = mesh.vxs[j];
 169 |     return Math.sqrt((p[0] - q[0]) * (p[0] - q[0]) + (p[1] - q[1]) * (p[1] - q[1]));
 170 | }
 171 | 
 172 | function quantile(h, q) {
 173 |     var sortedh = [];
 174 |     for (var i = 0; i < h.length; i++) {
 175 |         sortedh[i] = h[i];
 176 |     }
 177 |     sortedh.sort(d3.ascending);
 178 |     return d3.quantile(sortedh, q);
 179 | }
 180 | 
 181 | function zero(mesh) {
 182 |     var z = [];
 183 |     for (var i = 0; i < mesh.vxs.length; i++) {
 184 |         z[i] = 0;
 185 |     }
 186 |     z.mesh = mesh;
 187 |     return z;
 188 | }
 189 | 
 190 | function slope(mesh, direction) {
 191 |     return mesh.map(function (x) {
 192 |         return x[0] * direction[0] + x[1] * direction[1];
 193 |     });
 194 | }
 195 | 
 196 | function cone(mesh, slope) {
 197 |     return mesh.map(function (x) {
 198 |         return Math.pow(x[0] * x[0] + x[1] * x[1], 0.5) * slope;
 199 |     });
 200 | }
 201 | 
 202 | function map(h, f) {
 203 |     var newh = h.map(f);
 204 |     newh.mesh = h.mesh;
 205 |     return newh;
 206 | }
 207 | 
 208 | function normalize(h) {
 209 |     var lo = d3.min(h);
 210 |     var hi = d3.max(h);
 211 |     return map(h, function (x) {return (x - lo) / (hi - lo)});
 212 | }
 213 | 
 214 | function peaky(h) {
 215 |     return map(normalize(h), Math.sqrt);
 216 | }
 217 | 
 218 | function add() {
 219 |     var n = arguments[0].length;
 220 |     var newvals = zero(arguments[0].mesh);
 221 |     for (var i = 0; i < n; i++) {
 222 |         for (var j = 0; j < arguments.length; j++) {
 223 |             newvals[i] += arguments[j][i];
 224 |         }
 225 |     }
 226 |     return newvals;
 227 | }
 228 | 
 229 | function mountains(mesh, n, r) {
 230 |     r = r || 0.05;
 231 |     var mounts = [];
 232 |     for (var i = 0; i < n; i++) {
 233 |         mounts.push([mesh.extent.width * (Math.random() - 0.5), mesh.extent.height * (Math.random() - 0.5)]);
 234 |     }
 235 |     var newvals = zero(mesh);
 236 |     for (var i = 0; i < mesh.vxs.length; i++) {
 237 |         var p = mesh.vxs[i];
 238 |         for (var j = 0; j < n; j++) {
 239 |             var m = mounts[j];
 240 |             newvals[i] += Math.pow(Math.exp(-((p[0] - m[0]) * (p[0] - m[0]) + (p[1] - m[1]) * (p[1] - m[1])) / (2 * r * r)), 2);
 241 |         }
 242 |     }
 243 |     return newvals;
 244 | }
 245 | 
 246 | function relax(h) {
 247 |     var newh = zero(h.mesh);
 248 |     for (var i = 0; i < h.length; i++) {
 249 |         var nbs = neighbours(h.mesh, i);
 250 |         if (nbs.length < 3) {
 251 |             newh[i] = 0;
 252 |             continue;
 253 |         }
 254 |         newh[i] = d3.mean(nbs.map(function (j) {return h[j]}));
 255 |     }
 256 |     return newh;
 257 | }
 258 | 
 259 | function downhill(h) {
 260 |     if (h.downhill) return h.downhill;
 261 |     function downfrom(i) {
 262 |         if (isedge(h.mesh, i)) return -2;
 263 |         var best = -1;
 264 |         var besth = h[i];
 265 |         var nbs = neighbours(h.mesh, i);
 266 |         for (var j = 0; j < nbs.length; j++) {
 267 |             if (h[nbs[j]] < besth) {
 268 |                 besth = h[nbs[j]];
 269 |                 best = nbs[j];
 270 |             }
 271 |         }
 272 |         return best;
 273 |     }
 274 |     var downs = [];
 275 |     for (var i = 0; i < h.length; i++) {
 276 |         downs[i] = downfrom(i);
 277 |     }
 278 |     h.downhill = downs;
 279 |     return downs;
 280 | }
 281 | 
 282 | function findSinks(h) {
 283 |     var dh = downhill(h);
 284 |     var sinks = [];
 285 |     for (var i = 0; i < dh.length; i++) {
 286 |         var node = i;
 287 |         while (true) {
 288 |             if (isedge(h.mesh, node)) {
 289 |                 sinks[i] = -2;
 290 |                 break;
 291 |             }
 292 |             if (dh[node] == -1) {
 293 |                 sinks[i] = node;
 294 |                 break;
 295 |             }
 296 |             node = dh[node];
 297 |         }
 298 |     }
 299 | }
 300 | 
 301 | function fillSinks(h, epsilon) {
 302 |     epsilon = epsilon || 1e-5;
 303 |     var infinity = 999999;
 304 |     var newh = zero(h.mesh);
 305 |     for (var i = 0; i < h.length; i++) {
 306 |         if (isnearedge(h.mesh, i)) {
 307 |             newh[i] = h[i];
 308 |         } else {
 309 |             newh[i] = infinity;
 310 |         }
 311 |     }
 312 |     while (true) {
 313 |         var changed = false;
 314 |         for (var i = 0; i < h.length; i++) {
 315 |             if (newh[i] == h[i]) continue;
 316 |             var nbs = neighbours(h.mesh, i);
 317 |             for (var j = 0; j < nbs.length; j++) {
 318 |                 if (h[i] >= newh[nbs[j]] + epsilon) {
 319 |                     newh[i] = h[i];
 320 |                     changed = true;
 321 |                     break;
 322 |                 }
 323 |                 var oh = newh[nbs[j]] + epsilon;
 324 |                 if ((newh[i] > oh) && (oh > h[i])) {
 325 |                     newh[i] = oh;
 326 |                     changed = true;
 327 |                 }
 328 |             }
 329 |         }
 330 |         if (!changed) return newh;
 331 |     }
 332 | }
 333 | 
 334 | function getFlux(h) {
 335 |     var dh = downhill(h);
 336 |     var idxs = [];
 337 |     var flux = zero(h.mesh); 
 338 |     for (var i = 0; i < h.length; i++) {
 339 |         idxs[i] = i;
 340 |         flux[i] = 1/h.length;
 341 |     }
 342 |     idxs.sort(function (a, b) {
 343 |         return h[b] - h[a];
 344 |     });
 345 |     for (var i = 0; i < h.length; i++) {
 346 |         var j = idxs[i];
 347 |         if (dh[j] >= 0) {
 348 |             flux[dh[j]] += flux[j];
 349 |         }
 350 |     }
 351 |     return flux;
 352 | }
 353 | 
 354 | function getSlope(h) {
 355 |     var dh = downhill(h);
 356 |     var slope = zero(h.mesh);
 357 |     for (var i = 0; i < h.length; i++) {
 358 |         var s = trislope(h, i);
 359 |         slope[i] = Math.sqrt(s[0] * s[0] + s[1] * s[1]);
 360 |         continue;
 361 |         if (dh[i] < 0) {
 362 |             slope[i] = 0;
 363 |         } else {
 364 |             slope[i] = (h[i] - h[dh[i]]) / distance(h.mesh, i, dh[i]);
 365 |         }
 366 |     }
 367 |     return slope;
 368 | }
 369 | 
 370 | function erosionRate(h) {
 371 |     var flux = getFlux(h);
 372 |     var slope = getSlope(h);
 373 |     var newh = zero(h.mesh);
 374 |     for (var i = 0; i < h.length; i++) {
 375 |         var river = Math.sqrt(flux[i]) * slope[i];
 376 |         var creep = slope[i] * slope[i];
 377 |         var total = 1000 * river + creep;
 378 |         total = total > 200 ? 200 : total;
 379 |         newh[i] = total;
 380 |     }
 381 |     return newh;
 382 | }
 383 | 
 384 | function erode(h, amount) {
 385 |     var er = erosionRate(h);
 386 |     var newh = zero(h.mesh);
 387 |     var maxr = d3.max(er);
 388 |     for (var i = 0; i < h.length; i++) {
 389 |         newh[i] = h[i] - amount * (er[i] / maxr);
 390 |     }
 391 |     return newh;
 392 | }
 393 | 
 394 | function doErosion(h, amount, n) {
 395 |     n = n || 1;
 396 |     h = fillSinks(h);
 397 |     for (var i = 0; i < n; i++) {
 398 |         h = erode(h, amount);
 399 |         h = fillSinks(h);
 400 |     }
 401 |     return h;
 402 | }
 403 | 
 404 | function setSeaLevel(h, q) {
 405 |     var newh = zero(h.mesh);
 406 |     var delta = quantile(h, q);
 407 |     for (var i = 0; i < h.length; i++) {
 408 |         newh[i] = h[i] - delta;
 409 |     }
 410 |     return newh;
 411 | }
 412 | 
 413 | function cleanCoast(h, iters) {
 414 |     for (var iter = 0; iter < iters; iter++) {
 415 |         var changed = 0;
 416 |         var newh = zero(h.mesh);
 417 |         for (var i = 0; i < h.length; i++) {
 418 |             newh[i] = h[i];
 419 |             var nbs = neighbours(h.mesh, i);
 420 |             if (h[i] <= 0 || nbs.length != 3) continue;
 421 |             var count = 0;
 422 |             var best = -999999;
 423 |             for (var j = 0; j < nbs.length; j++) {
 424 |                 if (h[nbs[j]] > 0) {
 425 |                     count++;
 426 |                 } else if (h[nbs[j]] > best) {
 427 |                     best = h[nbs[j]];    
 428 |                 }
 429 |             }
 430 |             if (count > 1) continue;
 431 |             newh[i] = best / 2;
 432 |             changed++;
 433 |         }
 434 |         h = newh;
 435 |         newh = zero(h.mesh);
 436 |         for (var i = 0; i < h.length; i++) {
 437 |             newh[i] = h[i];
 438 |             var nbs = neighbours(h.mesh, i);
 439 |             if (h[i] > 0 || nbs.length != 3) continue;
 440 |             var count = 0;
 441 |             var best = 999999;
 442 |             for (var j = 0; j < nbs.length; j++) {
 443 |                 if (h[nbs[j]] <= 0) {
 444 |                     count++;
 445 |                 } else if (h[nbs[j]] < best) {
 446 |                     best = h[nbs[j]];
 447 |                 }
 448 |             }
 449 |             if (count > 1) continue;
 450 |             newh[i] = best / 2;
 451 |             changed++;
 452 |         }
 453 |         h = newh;
 454 |     }
 455 |     return h;
 456 | }
 457 | 
 458 | function trislope(h, i) {
 459 |     var nbs = neighbours(h.mesh, i);
 460 |     if (nbs.length != 3) return [0,0];
 461 |     var p0 = h.mesh.vxs[nbs[0]];
 462 |     var p1 = h.mesh.vxs[nbs[1]];
 463 |     var p2 = h.mesh.vxs[nbs[2]];
 464 | 
 465 |     var x1 = p1[0] - p0[0];
 466 |     var x2 = p2[0] - p0[0];
 467 |     var y1 = p1[1] - p0[1];
 468 |     var y2 = p2[1] - p0[1];
 469 | 
 470 |     var det = x1 * y2 - x2 * y1;
 471 |     var h1 = h[nbs[1]] - h[nbs[0]];
 472 |     var h2 = h[nbs[2]] - h[nbs[0]];
 473 | 
 474 |     return [(y2 * h1 - y1 * h2) / det,
 475 |             (-x2 * h1 + x1 * h2) / det];
 476 | }
 477 | 
 478 | function cityScore(h, cities) {
 479 |     var score = map(getFlux(h), Math.sqrt);
 480 |     for (var i = 0; i < h.length; i++) {
 481 |         if (h[i] <= 0 || isnearedge(h.mesh, i)) {
 482 |             score[i] = -999999;
 483 |             continue;
 484 |         }
 485 |         score[i] += 0.01 / (1e-9 + Math.abs(h.mesh.vxs[i][0]) - h.mesh.extent.width/2)
 486 |         score[i] += 0.01 / (1e-9 + Math.abs(h.mesh.vxs[i][1]) - h.mesh.extent.height/2)
 487 |         for (var j = 0; j < cities.length; j++) {
 488 |             score[i] -= 0.02 / (distance(h.mesh, cities[j], i) + 1e-9);
 489 |         }
 490 |     }
 491 |     return score;
 492 | }
 493 | function placeCity(render) {
 494 |     render.cities = render.cities || [];
 495 |     var score = cityScore(render.h, render.cities);
 496 |     var newcity = d3.scan(score, d3.descending);
 497 |     render.cities.push(newcity);
 498 | }
 499 | 
 500 | function placeCities(render) {
 501 |     var params = render.params;
 502 |     var h = render.h;
 503 |     var n = params.ncities;
 504 |     for (var i = 0; i < n; i++) {
 505 |         placeCity(render);
 506 |     }
 507 | }
 508 | 
 509 | function contour(h, level) {
 510 |     level = level || 0;
 511 |     var edges = [];
 512 |     for (var i = 0; i < h.mesh.edges.length; i++) {
 513 |         var e = h.mesh.edges[i];
 514 |         if (e[3] == undefined) continue;
 515 |         if (isnearedge(h.mesh, e[0]) || isnearedge(h.mesh, e[1])) continue;
 516 |         if ((h[e[0]] > level && h[e[1]] <= level) ||
 517 |             (h[e[1]] > level && h[e[0]] <= level)) {
 518 |             edges.push([e[2], e[3]]);
 519 |         }
 520 |     }
 521 |     return mergeSegments(edges);
 522 | }
 523 | 
 524 | function getRivers(h, limit) {
 525 |     var dh = downhill(h);
 526 |     var flux = getFlux(h);
 527 |     var links = [];
 528 |     var above = 0;
 529 |     for (var i = 0; i < h.length; i++) {
 530 |         if (h[i] > 0) above++;
 531 |     }
 532 |     limit *= above / h.length;
 533 |     for (var i = 0; i < dh.length; i++) {
 534 |         if (isnearedge(h.mesh, i)) continue;
 535 |         if (flux[i] > limit && h[i] > 0 && dh[i] >= 0) {
 536 |             var up = h.mesh.vxs[i];
 537 |             var down = h.mesh.vxs[dh[i]];
 538 |             if (h[dh[i]] > 0) {
 539 |                 links.push([up, down]);
 540 |             } else {
 541 |                 links.push([up, [(up[0] + down[0])/2, (up[1] + down[1])/2]]);
 542 |             }
 543 |         }
 544 |     }
 545 |     return mergeSegments(links).map(relaxPath);
 546 | }
 547 | 
 548 | function getTerritories(render) {
 549 |     var h = render.h;
 550 |     var cities = render.cities;
 551 |     var n = render.params.nterrs;
 552 |     if (n > render.cities.length) n = render.cities.length;
 553 |     var flux = getFlux(h);
 554 |     var terr = [];
 555 |     var queue = new PriorityQueue({comparator: function (a, b) {return a.score - b.score}});
 556 |     function weight(u, v) {
 557 |         var horiz = distance(h.mesh, u, v);
 558 |         var vert = h[v] - h[u];
 559 |         if (vert > 0) vert /= 10;
 560 |         var diff = 1 + 0.25 * Math.pow(vert/horiz, 2);
 561 |         diff += 100 * Math.sqrt(flux[u]);
 562 |         if (h[u] <= 0) diff = 100;
 563 |         if ((h[u] > 0) != (h[v] > 0)) return 1000;
 564 |         return horiz * diff;
 565 |     }
 566 |     for (var i = 0; i < n; i++) {
 567 |         terr[cities[i]] = cities[i];
 568 |         var nbs = neighbours(h.mesh, cities[i]);
 569 |         for (var j = 0; j < nbs.length; j++) {
 570 |             queue.queue({
 571 |                 score: weight(cities[i], nbs[j]),
 572 |                 city: cities[i],
 573 |                 vx: nbs[j]
 574 |             });
 575 |         }
 576 |     }
 577 |     while (queue.length) {
 578 |         var u = queue.dequeue();
 579 |         if (terr[u.vx] != undefined) continue;
 580 |         terr[u.vx] = u.city;
 581 |         var nbs = neighbours(h.mesh, u.vx);
 582 |         for (var i = 0; i < nbs.length; i++) {
 583 |             var v = nbs[i];
 584 |             if (terr[v] != undefined) continue;
 585 |             var newdist = weight(u.vx, v);
 586 |             queue.queue({
 587 |                 score: u.score + newdist,
 588 |                 city: u.city,
 589 |                 vx: v
 590 |             });
 591 |         }
 592 |     }
 593 |     terr.mesh = h.mesh;
 594 |     return terr;
 595 | }
 596 | 
 597 | function getBorders(render) {
 598 |     var terr = render.terr;
 599 |     var h = render.h;
 600 |     var edges = [];
 601 |     for (var i = 0; i < terr.mesh.edges.length; i++) {
 602 |         var e = terr.mesh.edges[i];
 603 |         if (e[3] == undefined) continue;
 604 |         if (isnearedge(terr.mesh, e[0]) || isnearedge(terr.mesh, e[1])) continue;
 605 |         if (h[e[0]] < 0 || h[e[1]] < 0) continue;
 606 |         if (terr[e[0]] != terr[e[1]]) {
 607 |             edges.push([e[2], e[3]]);
 608 |         }
 609 |     }
 610 |     return mergeSegments(edges).map(relaxPath);
 611 | }
 612 | 
 613 | function mergeSegments(segs) {
 614 |     var adj = {};
 615 |     for (var i = 0; i < segs.length; i++) {
 616 |         var seg = segs[i];
 617 |         var a0 = adj[seg[0]] || [];
 618 |         var a1 = adj[seg[1]] || [];
 619 |         a0.push(seg[1]);
 620 |         a1.push(seg[0]);
 621 |         adj[seg[0]] = a0;
 622 |         adj[seg[1]] = a1;
 623 |     }
 624 |     var done = [];
 625 |     var paths = [];
 626 |     var path = null;
 627 |     while (true) {
 628 |         if (path == null) {
 629 |             for (var i = 0; i < segs.length; i++) {
 630 |                 if (done[i]) continue;
 631 |                 done[i] = true;
 632 |                 path = [segs[i][0], segs[i][1]];
 633 |                 break;
 634 |             }
 635 |             if (path == null) break;
 636 |         }
 637 |         var changed = false;
 638 |         for (var i = 0; i < segs.length; i++) {
 639 |             if (done[i]) continue;
 640 |             if (adj[path[0]].length == 2 && segs[i][0] == path[0]) {
 641 |                 path.unshift(segs[i][1]);
 642 |             } else if (adj[path[0]].length == 2 && segs[i][1] == path[0]) {
 643 |                 path.unshift(segs[i][0]);
 644 |             } else if (adj[path[path.length - 1]].length == 2 && segs[i][0] == path[path.length - 1]) {
 645 |                 path.push(segs[i][1]);
 646 |             } else if (adj[path[path.length - 1]].length == 2 && segs[i][1] == path[path.length - 1]) {
 647 |                 path.push(segs[i][0]);
 648 |             } else {
 649 |                 continue;
 650 |             }
 651 |             done[i] = true;
 652 |             changed = true;
 653 |             break;
 654 |         }
 655 |         if (!changed) {
 656 |             paths.push(path);
 657 |             path = null;
 658 |         }
 659 |     }
 660 |     return paths;
 661 | }
 662 | 
 663 | function relaxPath(path) {
 664 |     var newpath = [path[0]];
 665 |     for (var i = 1; i < path.length - 1; i++) {
 666 |         var newpt = [0.25 * path[i-1][0] + 0.5 * path[i][0] + 0.25 * path[i+1][0],
 667 |                      0.25 * path[i-1][1] + 0.5 * path[i][1] + 0.25 * path[i+1][1]];
 668 |         newpath.push(newpt);
 669 |     }
 670 |     newpath.push(path[path.length - 1]);
 671 |     return newpath;
 672 | }
 673 | function visualizePoints(svg, pts) {
 674 |     var circle = svg.selectAll('circle').data(pts);
 675 |     circle.enter()
 676 |         .append('circle');
 677 |     circle.exit().remove();
 678 |     d3.selectAll('circle')
 679 |         .attr('cx', function (d) {return 1000*d[0]})
 680 |         .attr('cy', function (d) {return 1000*d[1]})
 681 |         .attr('r', 100 / Math.sqrt(pts.length));
 682 | }
 683 | 
 684 | function makeD3Path(path) {
 685 |     var p = d3.path();
 686 |     p.moveTo(1000*path[0][0], 1000*path[0][1]);
 687 |     for (var i = 1; i < path.length; i++) {
 688 |         p.lineTo(1000*path[i][0], 1000*path[i][1]);
 689 |     }
 690 |     return p.toString();
 691 | }
 692 | 
 693 | function visualizeVoronoi(svg, field, lo, hi) {
 694 |     if (hi == undefined) hi = d3.max(field) + 1e-9;
 695 |     if (lo == undefined) lo = d3.min(field) - 1e-9;
 696 |     var mappedvals = field.map(function (x) {return x > hi ? 1 : x < lo ? 0 : (x - lo) / (hi - lo)});
 697 |     var tris = svg.selectAll('path.field').data(field.mesh.tris)
 698 |     tris.enter()
 699 |         .append('path')
 700 |         .classed('field', true);
 701 |     
 702 |     tris.exit()
 703 |         .remove();
 704 | 
 705 |     svg.selectAll('path.field')
 706 |         .attr('d', makeD3Path)
 707 |         .style('fill', function (d, i) {
 708 |             return d3.interpolateViridis(mappedvals[i]);
 709 |         });
 710 | }
 711 | 
 712 | function visualizeDownhill(h) {
 713 |     var links = getRivers(h, 0.01);
 714 |     drawPaths('river', links);
 715 | }
 716 | 
 717 | function drawPaths(svg, cls, paths) {
 718 |     var paths = svg.selectAll('path.' + cls).data(paths)
 719 |     paths.enter()
 720 |             .append('path')
 721 |             .classed(cls, true)
 722 |     paths.exit()
 723 |             .remove();
 724 |     svg.selectAll('path.' + cls)
 725 |         .attr('d', makeD3Path);
 726 | }
 727 | 
 728 | function visualizeSlopes(svg, render) {
 729 |     var h = render.h;
 730 |     var strokes = [];
 731 |     var r = 0.25 / Math.sqrt(h.length);
 732 |     for (var i = 0; i < h.length; i++) {
 733 |         if (h[i] <= 0 || isnearedge(h.mesh, i)) continue;
 734 |         var nbs = neighbours(h.mesh, i);
 735 |         nbs.push(i);
 736 |         var s = 0;
 737 |         var s2 = 0;
 738 |         for (var j = 0; j < nbs.length; j++) {
 739 |             var slopes = trislope(h, nbs[j]);
 740 |             s += slopes[0] / 10;
 741 |             s2 += slopes[1];
 742 |         }
 743 |         s /= nbs.length;
 744 |         s2 /= nbs.length;
 745 |         if (Math.abs(s) < runif(0.1, 0.4)) continue;
 746 |         var l = r * runif(1, 2) * (1 - 0.2 * Math.pow(Math.atan(s), 2)) * Math.exp(s2/100);
 747 |         var x = h.mesh.vxs[i][0];
 748 |         var y = h.mesh.vxs[i][1];
 749 |         if (Math.abs(l*s) > 2 * r) {
 750 |             var n = Math.floor(Math.abs(l*s/r));
 751 |             l /= n;
 752 |             if (n > 4) n = 4;
 753 |             for (var j = 0; j < n; j++) {
 754 |                 var u = rnorm() * r;
 755 |                 var v = rnorm() * r;
 756 |                 strokes.push([[x+u-l, y+v+l*s], [x+u+l, y+v-l*s]]);
 757 |             }
 758 |         } else {
 759 |             strokes.push([[x-l, y+l*s], [x+l, y-l*s]]);
 760 |         }
 761 |     }
 762 |     var lines = svg.selectAll('line.slope').data(strokes)
 763 |     lines.enter()
 764 |             .append('line')
 765 |             .classed('slope', true);
 766 |     lines.exit()
 767 |             .remove();
 768 |     svg.selectAll('line.slope')
 769 |         .attr('x1', function (d) {return 1000*d[0][0]})
 770 |         .attr('y1', function (d) {return 1000*d[0][1]})
 771 |         .attr('x2', function (d) {return 1000*d[1][0]})
 772 |         .attr('y2', function (d) {return 1000*d[1][1]})
 773 | }
 774 | 
 775 | 
 776 | function visualizeContour(h, level) {
 777 |     level = level || 0;
 778 |     var links = contour(h, level);
 779 |     drawPaths('coast', links);
 780 | }
 781 | 
 782 | function visualizeBorders(h, cities, n) {
 783 |     var links = getBorders(h, getTerritories(h, cities, n));
 784 |     drawPaths('border', links);
 785 | }
 786 | 
 787 | 
 788 | function visualizeCities(svg, render) {
 789 |     var cities = render.cities;
 790 |     var h = render.h;
 791 |     var n = render.params.nterrs;
 792 | 
 793 |     var circs = svg.selectAll('circle.city').data(cities);
 794 |     circs.enter()
 795 |             .append('circle')
 796 |             .classed('city', true);
 797 |     circs.exit()
 798 |             .remove();
 799 |     svg.selectAll('circle.city')
 800 |         .attr('cx', function (d) {return 1000*h.mesh.vxs[d][0]})
 801 |         .attr('cy', function (d) {return 1000*h.mesh.vxs[d][1]})
 802 |         .attr('r', function (d, i) {return i >= n ? 4 : 10})
 803 |         .style('fill', 'white')
 804 |         .style('stroke-width', 5)
 805 |         .style('stroke-linecap', 'round')
 806 |         .style('stroke', 'black')
 807 |         .raise();
 808 | }
 809 | 
 810 | function dropEdge(h, p) {
 811 |     p = p || 4
 812 |     var newh = zero(h.mesh);
 813 |     for (var i = 0; i < h.length; i++) {
 814 |         var v = h.mesh.vxs[i];
 815 |         var x = 2.4*v[0] / h.mesh.extent.width;
 816 |         var y = 2.4*v[1] / h.mesh.extent.height;
 817 |         newh[i] = h[i] - Math.exp(10*(Math.pow(Math.pow(x, p) + Math.pow(y, p), 1/p) - 1));
 818 |     }
 819 |     return newh;
 820 | }
 821 | 
 822 | function generateCoast(params) {
 823 |     var mesh = generateGoodMesh(params.npts, params.extent);
 824 |     var h = add(
 825 |             slope(mesh, randomVector(4)),
 826 |             cone(mesh, runif(-1, -1)),
 827 |             mountains(mesh, 50)
 828 |             );
 829 |     for (var i = 0; i < 10; i++) {
 830 |         h = relax(h);
 831 |     }
 832 |     h = peaky(h);
 833 |     h = doErosion(h, runif(0, 0.1), 5);
 834 |     h = setSeaLevel(h, runif(0.2, 0.6));
 835 |     h = fillSinks(h);
 836 |     h = cleanCoast(h, 3);
 837 |     return h;
 838 | }
 839 | 
 840 | function terrCenter(h, terr, city, landOnly) {
 841 |     var x = 0;
 842 |     var y = 0;
 843 |     var n = 0;
 844 |     for (var i = 0; i < terr.length; i++) {
 845 |         if (terr[i] != city) continue;
 846 |         if (landOnly && h[i] <= 0) continue;
 847 |         x += terr.mesh.vxs[i][0];
 848 |         y += terr.mesh.vxs[i][1];
 849 |         n++;
 850 |     }
 851 |     return [x/n, y/n];
 852 | }
 853 | 
 854 | function drawLabels(svg, render) {
 855 |     var params = render.params;
 856 |     var h = render.h;
 857 |     var terr = render.terr;
 858 |     var cities = render.cities;
 859 |     var nterrs = render.params.nterrs;
 860 |     var avoids = [render.rivers, render.coasts, render.borders];
 861 |     var lang = makeRandomLanguage();
 862 |     var citylabels = [];
 863 |     function penalty(label) {
 864 |         var pen = 0;
 865 |         if (label.x0 < -0.45 * h.mesh.extent.width) pen += 100;
 866 |         if (label.x1 > 0.45 * h.mesh.extent.width) pen += 100;
 867 |         if (label.y0 < -0.45 * h.mesh.extent.height) pen += 100;
 868 |         if (label.y1 > 0.45 * h.mesh.extent.height) pen += 100;
 869 |         for (var i = 0; i < citylabels.length; i++) {
 870 |             var olabel = citylabels[i];
 871 |             if (label.x0 < olabel.x1 && label.x1 > olabel.x0 &&
 872 |                 label.y0 < olabel.y1 && label.y1 > olabel.y0) {
 873 |                 pen += 100;
 874 |             }
 875 |         }
 876 | 
 877 |         for (var i = 0; i < cities.length; i++) {
 878 |             var c = h.mesh.vxs[cities[i]];
 879 |             if (label.x0 < c[0] && label.x1 > c[0] && label.y0 < c[1] && label.y1 > c[1]) {
 880 |                 pen += 100;
 881 |             }
 882 |         }
 883 |         for (var i = 0; i < avoids.length; i++) {
 884 |             var avoid = avoids[i];
 885 |             for (var j = 0; j < avoid.length; j++) {
 886 |                 var avpath = avoid[j];
 887 |                 for (var k = 0; k < avpath.length; k++) {
 888 |                     var pt = avpath[k];
 889 |                     if (pt[0] > label.x0 && pt[0] < label.x1 && pt[1] > label.y0 && pt[1] < label.y1) {
 890 |                         pen++;
 891 |                     }
 892 |                 }
 893 |             }
 894 |         }
 895 |         return pen;
 896 |     }
 897 |     for (var i = 0; i < cities.length; i++) {
 898 |         var x = h.mesh.vxs[cities[i]][0];
 899 |         var y = h.mesh.vxs[cities[i]][1];
 900 |         var text = makeName(lang, 'city');
 901 |         var size = i < nterrs ? params.fontsizes.city : params.fontsizes.town;
 902 |         var sx = 0.65 * size/1000 * text.length;
 903 |         var sy = size/1000;
 904 |         var posslabels = [
 905 |         {
 906 |             x: x + 0.8 * sy,
 907 |             y: y + 0.3 * sy,
 908 |             align: 'start',
 909 |             x0: x + 0.7 * sy,
 910 |             y0: y - 0.6 * sy,
 911 |             x1: x + 0.7 * sy + sx,
 912 |             y1: y + 0.6 * sy
 913 |         },
 914 |         {
 915 |             x: x - 0.8 * sy,
 916 |             y: y + 0.3 * sy,
 917 |             align: 'end',
 918 |             x0: x - 0.9 * sy - sx,
 919 |             y0: y - 0.7 * sy,
 920 |             x1: x - 0.9 * sy,
 921 |             y1: y + 0.7 * sy
 922 |         },
 923 |         {
 924 |             x: x,
 925 |             y: y - 0.8 * sy,
 926 |             align: 'middle',
 927 |             x0: x - sx/2,
 928 |             y0: y - 1.9*sy,
 929 |             x1: x + sx/2,
 930 |             y1: y - 0.7 * sy
 931 |         },
 932 |         {
 933 |             x: x,
 934 |             y: y + 1.2 * sy,
 935 |             align: 'middle',
 936 |             x0: x - sx/2,
 937 |             y0: y + 0.1*sy,
 938 |             x1: x + sx/2,
 939 |             y1: y + 1.3*sy
 940 |         }
 941 |         ];
 942 |         var label = posslabels[d3.scan(posslabels, function (a, b) {return penalty(a) - penalty(b)})];
 943 |         label.text = text;
 944 |         label.size = size;
 945 |         citylabels.push(label);
 946 |     }
 947 |     var texts = svg.selectAll('text.city').data(citylabels);
 948 |     texts.enter()
 949 |         .append('text')
 950 |         .classed('city', true);
 951 |     texts.exit()
 952 |         .remove();
 953 |     svg.selectAll('text.city')
 954 |         .attr('x', function (d) {return 1000*d.x})
 955 |         .attr('y', function (d) {return 1000*d.y})
 956 |         .style('font-size', function (d) {return d.size})
 957 |         .style('text-anchor', function (d) {return d.align})
 958 |         .text(function (d) {return d.text})
 959 |         .raise();
 960 | 
 961 |     var reglabels = [];
 962 |     for (var i = 0; i < nterrs; i++) {
 963 |         var city = cities[i];
 964 |         var text = makeName(lang, 'region');
 965 |         var sy = params.fontsizes.region / 1000;
 966 |         var sx = 0.6 * text.length * sy;
 967 |         var lc = terrCenter(h, terr, city, true);
 968 |         var oc = terrCenter(h, terr, city, false);
 969 |         var best = 0;
 970 |         var bestscore = -999999;
 971 |         for (var j = 0; j < h.length; j++) {
 972 |             var score = 0;
 973 |             var v = h.mesh.vxs[j];
 974 |             score -= 3000 * Math.sqrt((v[0] - lc[0]) * (v[0] - lc[0]) + (v[1] - lc[1]) * (v[1] - lc[1]));
 975 |             score -= 1000 * Math.sqrt((v[0] - oc[0]) * (v[0] - oc[0]) + (v[1] - oc[1]) * (v[1] - oc[1]));
 976 |             if (terr[j] != city) score -= 3000;
 977 |             for (var k = 0; k < cities.length; k++) {
 978 |                 var u = h.mesh.vxs[cities[k]];
 979 |                 if (Math.abs(v[0] - u[0]) < sx && 
 980 |                     Math.abs(v[1] - sy/2 - u[1]) < sy) {
 981 |                     score -= k < nterrs ? 4000 : 500;
 982 |                 }
 983 |                 if (v[0] - sx/2 < citylabels[k].x1 &&
 984 |                     v[0] + sx/2 > citylabels[k].x0 &&
 985 |                     v[1] - sy < citylabels[k].y1 &&
 986 |                     v[1] > citylabels[k].y0) {
 987 |                     score -= 5000;
 988 |                 }
 989 |             }
 990 |             for (var k = 0; k < reglabels.length; k++) {
 991 |                 var label = reglabels[k];
 992 |                 if (v[0] - sx/2 < label.x + label.width/2 &&
 993 |                     v[0] + sx/2 > label.x - label.width/2 &&
 994 |                     v[1] - sy < label.y &&
 995 |                     v[1] > label.y - label.size) {
 996 |                     score -= 20000;
 997 |                 }
 998 |             }
 999 |             if (h[j] <= 0) score -= 500;
1000 |             if (v[0] + sx/2 > 0.5 * h.mesh.extent.width) score -= 50000;
1001 |             if (v[0] - sx/2 < -0.5 * h.mesh.extent.width) score -= 50000;
1002 |             if (v[1] > 0.5 * h.mesh.extent.height) score -= 50000;
1003 |             if (v[1] - sy < -0.5 * h.mesh.extent.height) score -= 50000;
1004 |             if (score > bestscore) {
1005 |                 bestscore = score;
1006 |                 best = j;
1007 |             }
1008 |         }
1009 |         reglabels.push({
1010 |             text: text, 
1011 |             x: h.mesh.vxs[best][0], 
1012 |             y: h.mesh.vxs[best][1], 
1013 |             size:sy, 
1014 |             width:sx
1015 |         });
1016 |     }
1017 |     texts = svg.selectAll('text.region').data(reglabels);
1018 |     texts.enter()
1019 |         .append('text')
1020 |         .classed('region', true);
1021 |     texts.exit()
1022 |         .remove();
1023 |     svg.selectAll('text.region')
1024 |         .attr('x', function (d) {return 1000*d.x})
1025 |         .attr('y', function (d) {return 1000*d.y})
1026 |         .style('font-size', function (d) {return 1000*d.size})
1027 |         .style('text-anchor', 'middle')
1028 |         .text(function (d) {return d.text})
1029 |         .raise();
1030 | 
1031 | }
1032 | function drawMap(svg, render) {
1033 |     render.rivers = getRivers(render.h, 0.01);
1034 |     render.coasts = contour(render.h, 0);
1035 |     render.terr = getTerritories(render);
1036 |     render.borders = getBorders(render);
1037 |     drawPaths(svg, 'river', render.rivers);
1038 |     drawPaths(svg, 'coast', render.coasts);
1039 |     drawPaths(svg, 'border', render.borders);
1040 |     visualizeSlopes(svg, render);
1041 |     visualizeCities(svg, render);
1042 |     drawLabels(svg, render);
1043 | }
1044 | 
1045 | function doMap(svg, params) {
1046 |     var render = {
1047 |         params: params
1048 |     };
1049 |     var width = svg.attr('width');
1050 |     svg.attr('height', width * params.extent.height / params.extent.width);
1051 |     svg.attr('viewBox', -1000 * params.extent.width/2 + ' ' + 
1052 |                         -1000 * params.extent.height/2 + ' ' + 
1053 |                         1000 * params.extent.width + ' ' + 
1054 |                         1000 * params.extent.height);
1055 |     svg.selectAll().remove();
1056 |     render.h = params.generator(params);
1057 |     placeCities(render);
1058 |     drawMap(svg, render);
1059 | }
1060 | 
1061 | var defaultParams = {
1062 |     extent: defaultExtent,
1063 |     generator: generateCoast,
1064 |     npts: 16384,
1065 |     ncities: 15,
1066 |     nterrs: 5,
1067 |     fontsizes: {
1068 |         region: 40,
1069 |         city: 25,
1070 |         town: 20
1071 |     }
1072 | }
1073 | 
1074 | 


--------------------------------------------------------------------------------