You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1074 lines
30 KiB
JavaScript

5 years ago
"use strict";
function runif(lo, hi) {
return lo + Math.random() * (hi - lo);
}
var rnorm = (function () {
var z2 = null;
function rnorm() {
if (z2 != null) {
var tmp = z2;
z2 = null;
return tmp;
}
var x1 = 0;
var x2 = 0;
var w = 2.0;
while (w >= 1) {
x1 = runif(-1, 1);
x2 = runif(-1, 1);
w = x1 * x1 + x2 * x2;
}
w = Math.sqrt(-2 * Math.log(w) / w);
z2 = x2 * w;
return x1 * w;
}
return rnorm;
})();
function randomVector(scale) {
return [scale * rnorm(), scale * rnorm()];
}
var defaultExtent = {
width: 1,
height: 1
};
function generatePoints(n, extent) {
extent = extent || defaultExtent;
var pts = [];
for (var i = 0; i < n; i++) {
pts.push([(Math.random() - 0.5) * extent.width, (Math.random() - 0.5) * extent.height]);
}
return pts;
}
function centroid(pts) {
var x = 0;
var y = 0;
for (var i = 0; i < pts.length; i++) {
x += pts[i][0];
y += pts[i][1];
}
return [x/pts.length, y/pts.length];
}
function improvePoints(pts, n, extent) {
n = n || 1;
extent = extent || defaultExtent;
for (var i = 0; i < n; i++) {
pts = voronoi(pts, extent)
.polygons(pts)
.map(centroid);
}
return pts;
}
function generateGoodPoints(n, extent) {
extent = extent || defaultExtent;
var pts = generatePoints(n, extent);
pts = pts.sort(function (a, b) {
return a[0] - b[0];
});
return improvePoints(pts, 1, extent);
}
function voronoi(pts, extent) {
extent = extent || defaultExtent;
var w = extent.width/2;
var h = extent.height/2;
return d3.voronoi().extent([[-w, -h], [w, h]])(pts);
}
function makeMesh(pts, extent) {
extent = extent || defaultExtent;
var vor = voronoi(pts, extent);
var vxs = [];
var vxids = {};
var adj = [];
var edges = [];
var tris = [];
for (var i = 0; i < vor.edges.length; i++) {
var e = vor.edges[i];
if (e == undefined) continue;
var e0 = vxids[e[0]];
var e1 = vxids[e[1]];
if (e0 == undefined) {
e0 = vxs.length;
vxids[e[0]] = e0;
vxs.push(e[0]);
}
if (e1 == undefined) {
e1 = vxs.length;
vxids[e[1]] = e1;
vxs.push(e[1]);
}
adj[e0] = adj[e0] || [];
adj[e0].push(e1);
adj[e1] = adj[e1] || [];
adj[e1].push(e0);
edges.push([e0, e1, e.left, e.right]);
tris[e0] = tris[e0] || [];
if (!tris[e0].includes(e.left)) tris[e0].push(e.left);
if (e.right && !tris[e0].includes(e.right)) tris[e0].push(e.right);
tris[e1] = tris[e1] || [];
if (!tris[e1].includes(e.left)) tris[e1].push(e.left);
if (e.right && !tris[e1].includes(e.right)) tris[e1].push(e.right);
}
var mesh = {
pts: pts,
vor: vor,
vxs: vxs,
adj: adj,
tris: tris,
edges: edges,
extent: extent
}
mesh.map = function (f) {
var mapped = vxs.map(f);
mapped.mesh = mesh;
return mapped;
}
return mesh;
}
function generateGoodMesh(n, extent) {
extent = extent || defaultExtent;
var pts = generateGoodPoints(n, extent);
return makeMesh(pts, extent);
}
function isedge(mesh, i) {
return (mesh.adj[i].length < 3);
}
function isnearedge(mesh, i) {
var x = mesh.vxs[i][0];
var y = mesh.vxs[i][1];
var w = mesh.extent.width;
var h = mesh.extent.height;
return x < -0.45 * w || x > 0.45 * w || y < -0.45 * h || y > 0.45 * h;
}
function neighbours(mesh, i) {
var onbs = mesh.adj[i];
var nbs = [];
for (var i = 0; i < onbs.length; i++) {
nbs.push(onbs[i]);
}
return nbs;
}
function distance(mesh, i, j) {
var p = mesh.vxs[i];
var q = mesh.vxs[j];
return Math.sqrt((p[0] - q[0]) * (p[0] - q[0]) + (p[1] - q[1]) * (p[1] - q[1]));
}
function quantile(h, q) {
var sortedh = [];
for (var i = 0; i < h.length; i++) {
sortedh[i] = h[i];
}
sortedh.sort(d3.ascending);
return d3.quantile(sortedh, q);
}
function zero(mesh) {
var z = [];
for (var i = 0; i < mesh.vxs.length; i++) {
z[i] = 0;
}
z.mesh = mesh;
return z;
}
function slope(mesh, direction) {
return mesh.map(function (x) {
return x[0] * direction[0] + x[1] * direction[1];
});
}
function cone(mesh, slope) {
return mesh.map(function (x) {
return Math.pow(x[0] * x[0] + x[1] * x[1], 0.5) * slope;
});
}
function map(h, f) {
var newh = h.map(f);
newh.mesh = h.mesh;
return newh;
}
function normalize(h) {
var lo = d3.min(h);
var hi = d3.max(h);
return map(h, function (x) {return (x - lo) / (hi - lo)});
}
function peaky(h) {
return map(normalize(h), Math.sqrt);
}
function add() {
var n = arguments[0].length;
var newvals = zero(arguments[0].mesh);
for (var i = 0; i < n; i++) {
for (var j = 0; j < arguments.length; j++) {
newvals[i] += arguments[j][i];
}
}
return newvals;
}
function mountains(mesh, n, r) {
r = r || 0.05;
var mounts = [];
for (var i = 0; i < n; i++) {
mounts.push([mesh.extent.width * (Math.random() - 0.5), mesh.extent.height * (Math.random() - 0.5)]);
}
var newvals = zero(mesh);
for (var i = 0; i < mesh.vxs.length; i++) {
var p = mesh.vxs[i];
for (var j = 0; j < n; j++) {
var m = mounts[j];
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);
}
}
return newvals;
}
function relax(h) {
var newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
var nbs = neighbours(h.mesh, i);
if (nbs.length < 3) {
newh[i] = 0;
continue;
}
newh[i] = d3.mean(nbs.map(function (j) {return h[j]}));
}
return newh;
}
function downhill(h) {
if (h.downhill) return h.downhill;
function downfrom(i) {
if (isedge(h.mesh, i)) return -2;
var best = -1;
var besth = h[i];
var nbs = neighbours(h.mesh, i);
for (var j = 0; j < nbs.length; j++) {
if (h[nbs[j]] < besth) {
besth = h[nbs[j]];
best = nbs[j];
}
}
return best;
}
var downs = [];
for (var i = 0; i < h.length; i++) {
downs[i] = downfrom(i);
}
h.downhill = downs;
return downs;
}
function findSinks(h) {
var dh = downhill(h);
var sinks = [];
for (var i = 0; i < dh.length; i++) {
var node = i;
while (true) {
if (isedge(h.mesh, node)) {
sinks[i] = -2;
break;
}
if (dh[node] == -1) {
sinks[i] = node;
break;
}
node = dh[node];
}
}
}
function fillSinks(h, epsilon) {
epsilon = epsilon || 1e-5;
var infinity = 999999;
var newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
if (isnearedge(h.mesh, i)) {
newh[i] = h[i];
} else {
newh[i] = infinity;
}
}
while (true) {
var changed = false;
for (var i = 0; i < h.length; i++) {
if (newh[i] == h[i]) continue;
var nbs = neighbours(h.mesh, i);
for (var j = 0; j < nbs.length; j++) {
if (h[i] >= newh[nbs[j]] + epsilon) {
newh[i] = h[i];
changed = true;
break;
}
var oh = newh[nbs[j]] + epsilon;
if ((newh[i] > oh) && (oh > h[i])) {
newh[i] = oh;
changed = true;
}
}
}
if (!changed) return newh;
}
}
function getFlux(h) {
var dh = downhill(h);
var idxs = [];
var flux = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
idxs[i] = i;
flux[i] = 1/h.length;
}
idxs.sort(function (a, b) {
return h[b] - h[a];
});
for (var i = 0; i < h.length; i++) {
var j = idxs[i];
if (dh[j] >= 0) {
flux[dh[j]] += flux[j];
}
}
return flux;
}
function getSlope(h) {
var dh = downhill(h);
var slope = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
var s = trislope(h, i);
slope[i] = Math.sqrt(s[0] * s[0] + s[1] * s[1]);
continue;
if (dh[i] < 0) {
slope[i] = 0;
} else {
slope[i] = (h[i] - h[dh[i]]) / distance(h.mesh, i, dh[i]);
}
}
return slope;
}
function erosionRate(h) {
var flux = getFlux(h);
var slope = getSlope(h);
var newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
var river = Math.sqrt(flux[i]) * slope[i];
var creep = slope[i] * slope[i];
var total = 1000 * river + creep;
total = total > 200 ? 200 : total;
newh[i] = total;
}
return newh;
}
function erode(h, amount) {
var er = erosionRate(h);
var newh = zero(h.mesh);
var maxr = d3.max(er);
for (var i = 0; i < h.length; i++) {
newh[i] = h[i] - amount * (er[i] / maxr);
}
return newh;
}
function doErosion(h, amount, n) {
n = n || 1;
h = fillSinks(h);
for (var i = 0; i < n; i++) {
h = erode(h, amount);
h = fillSinks(h);
}
return h;
}
function setSeaLevel(h, q) {
var newh = zero(h.mesh);
var delta = quantile(h, q);
for (var i = 0; i < h.length; i++) {
newh[i] = h[i] - delta;
}
return newh;
}
function cleanCoast(h, iters) {
for (var iter = 0; iter < iters; iter++) {
var changed = 0;
var newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
newh[i] = h[i];
var nbs = neighbours(h.mesh, i);
if (h[i] <= 0 || nbs.length != 3) continue;
var count = 0;
var best = -999999;
for (var j = 0; j < nbs.length; j++) {
if (h[nbs[j]] > 0) {
count++;
} else if (h[nbs[j]] > best) {
best = h[nbs[j]];
}
}
if (count > 1) continue;
newh[i] = best / 2;
changed++;
}
h = newh;
newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
newh[i] = h[i];
var nbs = neighbours(h.mesh, i);
if (h[i] > 0 || nbs.length != 3) continue;
var count = 0;
var best = 999999;
for (var j = 0; j < nbs.length; j++) {
if (h[nbs[j]] <= 0) {
count++;
} else if (h[nbs[j]] < best) {
best = h[nbs[j]];
}
}
if (count > 1) continue;
newh[i] = best / 2;
changed++;
}
h = newh;
}
return h;
}
function trislope(h, i) {
var nbs = neighbours(h.mesh, i);
if (nbs.length != 3) return [0,0];
var p0 = h.mesh.vxs[nbs[0]];
var p1 = h.mesh.vxs[nbs[1]];
var p2 = h.mesh.vxs[nbs[2]];
var x1 = p1[0] - p0[0];
var x2 = p2[0] - p0[0];
var y1 = p1[1] - p0[1];
var y2 = p2[1] - p0[1];
var det = x1 * y2 - x2 * y1;
var h1 = h[nbs[1]] - h[nbs[0]];
var h2 = h[nbs[2]] - h[nbs[0]];
return [(y2 * h1 - y1 * h2) / det,
(-x2 * h1 + x1 * h2) / det];
}
function cityScore(h, cities) {
var score = map(getFlux(h), Math.sqrt);
for (var i = 0; i < h.length; i++) {
if (h[i] <= 0 || isnearedge(h.mesh, i)) {
score[i] = -999999;
continue;
}
score[i] += 0.01 / (1e-9 + Math.abs(h.mesh.vxs[i][0]) - h.mesh.extent.width/2)
score[i] += 0.01 / (1e-9 + Math.abs(h.mesh.vxs[i][1]) - h.mesh.extent.height/2)
for (var j = 0; j < cities.length; j++) {
score[i] -= 0.02 / (distance(h.mesh, cities[j], i) + 1e-9);
}
}
return score;
}
function placeCity(render) {
render.cities = render.cities || [];
var score = cityScore(render.h, render.cities);
var newcity = d3.scan(score, d3.descending);
render.cities.push(newcity);
}
function placeCities(render) {
var params = render.params;
var h = render.h;
var n = params.ncities;
for (var i = 0; i < n; i++) {
placeCity(render);
}
}
function contour(h, level) {
level = level || 0;
var edges = [];
for (var i = 0; i < h.mesh.edges.length; i++) {
var e = h.mesh.edges[i];
if (e[3] == undefined) continue;
if (isnearedge(h.mesh, e[0]) || isnearedge(h.mesh, e[1])) continue;
if ((h[e[0]] > level && h[e[1]] <= level) ||
(h[e[1]] > level && h[e[0]] <= level)) {
edges.push([e[2], e[3]]);
}
}
return mergeSegments(edges);
}
function getRivers(h, limit) {
var dh = downhill(h);
var flux = getFlux(h);
var links = [];
var above = 0;
for (var i = 0; i < h.length; i++) {
if (h[i] > 0) above++;
}
limit *= above / h.length;
for (var i = 0; i < dh.length; i++) {
if (isnearedge(h.mesh, i)) continue;
if (flux[i] > limit && h[i] > 0 && dh[i] >= 0) {
var up = h.mesh.vxs[i];
var down = h.mesh.vxs[dh[i]];
if (h[dh[i]] > 0) {
links.push([up, down]);
} else {
links.push([up, [(up[0] + down[0])/2, (up[1] + down[1])/2]]);
}
}
}
return mergeSegments(links).map(relaxPath);
}
function getTerritories(render) {
var h = render.h;
var cities = render.cities;
var n = render.params.nterrs;
if (n > render.cities.length) n = render.cities.length;
var flux = getFlux(h);
var terr = [];
var queue = new PriorityQueue({comparator: function (a, b) {return a.score - b.score}});
function weight(u, v) {
var horiz = distance(h.mesh, u, v);
var vert = h[v] - h[u];
if (vert > 0) vert /= 10;
var diff = 1 + 0.25 * Math.pow(vert/horiz, 2);
diff += 100 * Math.sqrt(flux[u]);
if (h[u] <= 0) diff = 100;
if ((h[u] > 0) != (h[v] > 0)) return 1000;
return horiz * diff;
}
for (var i = 0; i < n; i++) {
terr[cities[i]] = cities[i];
var nbs = neighbours(h.mesh, cities[i]);
for (var j = 0; j < nbs.length; j++) {
queue.queue({
score: weight(cities[i], nbs[j]),
city: cities[i],
vx: nbs[j]
});
}
}
while (queue.length) {
var u = queue.dequeue();
if (terr[u.vx] != undefined) continue;
terr[u.vx] = u.city;
var nbs = neighbours(h.mesh, u.vx);
for (var i = 0; i < nbs.length; i++) {
var v = nbs[i];
if (terr[v] != undefined) continue;
var newdist = weight(u.vx, v);
queue.queue({
score: u.score + newdist,
city: u.city,
vx: v
});
}
}
terr.mesh = h.mesh;
return terr;
}
function getBorders(render) {
var terr = render.terr;
var h = render.h;
var edges = [];
for (var i = 0; i < terr.mesh.edges.length; i++) {
var e = terr.mesh.edges[i];
if (e[3] == undefined) continue;
if (isnearedge(terr.mesh, e[0]) || isnearedge(terr.mesh, e[1])) continue;
if (h[e[0]] < 0 || h[e[1]] < 0) continue;
if (terr[e[0]] != terr[e[1]]) {
edges.push([e[2], e[3]]);
}
}
return mergeSegments(edges).map(relaxPath);
}
function mergeSegments(segs) {
var adj = {};
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
var a0 = adj[seg[0]] || [];
var a1 = adj[seg[1]] || [];
a0.push(seg[1]);
a1.push(seg[0]);
adj[seg[0]] = a0;
adj[seg[1]] = a1;
}
var done = [];
var paths = [];
var path = null;
while (true) {
if (path == null) {
for (var i = 0; i < segs.length; i++) {
if (done[i]) continue;
done[i] = true;
path = [segs[i][0], segs[i][1]];
break;
}
if (path == null) break;
}
var changed = false;
for (var i = 0; i < segs.length; i++) {
if (done[i]) continue;
if (adj[path[0]].length == 2 && segs[i][0] == path[0]) {
path.unshift(segs[i][1]);
} else if (adj[path[0]].length == 2 && segs[i][1] == path[0]) {
path.unshift(segs[i][0]);
} else if (adj[path[path.length - 1]].length == 2 && segs[i][0] == path[path.length - 1]) {
path.push(segs[i][1]);
} else if (adj[path[path.length - 1]].length == 2 && segs[i][1] == path[path.length - 1]) {
path.push(segs[i][0]);
} else {
continue;
}
done[i] = true;
changed = true;
break;
}
if (!changed) {
paths.push(path);
path = null;
}
}
return paths;
}
function relaxPath(path) {
var newpath = [path[0]];
for (var i = 1; i < path.length - 1; i++) {
var newpt = [0.25 * path[i-1][0] + 0.5 * path[i][0] + 0.25 * path[i+1][0],
0.25 * path[i-1][1] + 0.5 * path[i][1] + 0.25 * path[i+1][1]];
newpath.push(newpt);
}
newpath.push(path[path.length - 1]);
return newpath;
}
function visualizePoints(svg, pts) {
var circle = svg.selectAll('circle').data(pts);
circle.enter()
.append('circle');
circle.exit().remove();
d3.selectAll('circle')
.attr('cx', function (d) {return 1000*d[0]})
.attr('cy', function (d) {return 1000*d[1]})
.attr('r', 100 / Math.sqrt(pts.length));
}
function makeD3Path(path) {
var p = d3.path();
p.moveTo(1000*path[0][0], 1000*path[0][1]);
for (var i = 1; i < path.length; i++) {
p.lineTo(1000*path[i][0], 1000*path[i][1]);
}
return p.toString();
}
function visualizeVoronoi(svg, field, lo, hi) {
if (hi == undefined) hi = d3.max(field) + 1e-9;
if (lo == undefined) lo = d3.min(field) - 1e-9;
var mappedvals = field.map(function (x) {return x > hi ? 1 : x < lo ? 0 : (x - lo) / (hi - lo)});
var tris = svg.selectAll('path.field').data(field.mesh.tris)
tris.enter()
.append('path')
.classed('field', true);
tris.exit()
.remove();
svg.selectAll('path.field')
.attr('d', makeD3Path)
.style('fill', function (d, i) {
return d3.interpolateViridis(mappedvals[i]);
});
}
function visualizeDownhill(h) {
var links = getRivers(h, 0.01);
drawPaths('river', links);
}
function drawPaths(svg, cls, paths) {
var paths = svg.selectAll('path.' + cls).data(paths)
paths.enter()
.append('path')
.classed(cls, true)
paths.exit()
.remove();
svg.selectAll('path.' + cls)
.attr('d', makeD3Path);
}
function visualizeSlopes(svg, render) {
var h = render.h;
var strokes = [];
var r = 0.25 / Math.sqrt(h.length);
for (var i = 0; i < h.length; i++) {
if (h[i] <= 0 || isnearedge(h.mesh, i)) continue;
var nbs = neighbours(h.mesh, i);
nbs.push(i);
var s = 0;
var s2 = 0;
for (var j = 0; j < nbs.length; j++) {
var slopes = trislope(h, nbs[j]);
s += slopes[0] / 10;
s2 += slopes[1];
}
s /= nbs.length;
s2 /= nbs.length;
if (Math.abs(s) < runif(0.1, 0.4)) continue;
var l = r * runif(1, 2) * (1 - 0.2 * Math.pow(Math.atan(s), 2)) * Math.exp(s2/100);
var x = h.mesh.vxs[i][0];
var y = h.mesh.vxs[i][1];
if (Math.abs(l*s) > 2 * r) {
var n = Math.floor(Math.abs(l*s/r));
l /= n;
if (n > 4) n = 4;
for (var j = 0; j < n; j++) {
var u = rnorm() * r;
var v = rnorm() * r;
strokes.push([[x+u-l, y+v+l*s], [x+u+l, y+v-l*s]]);
}
} else {
strokes.push([[x-l, y+l*s], [x+l, y-l*s]]);
}
}
var lines = svg.selectAll('line.slope').data(strokes)
lines.enter()
.append('line')
.classed('slope', true);
lines.exit()
.remove();
svg.selectAll('line.slope')
.attr('x1', function (d) {return 1000*d[0][0]})
.attr('y1', function (d) {return 1000*d[0][1]})
.attr('x2', function (d) {return 1000*d[1][0]})
.attr('y2', function (d) {return 1000*d[1][1]})
}
function visualizeContour(h, level) {
level = level || 0;
var links = contour(h, level);
drawPaths('coast', links);
}
function visualizeBorders(h, cities, n) {
var links = getBorders(h, getTerritories(h, cities, n));
drawPaths('border', links);
}
function visualizeCities(svg, render) {
var cities = render.cities;
var h = render.h;
var n = render.params.nterrs;
var circs = svg.selectAll('circle.city').data(cities);
circs.enter()
.append('circle')
.classed('city', true);
circs.exit()
.remove();
svg.selectAll('circle.city')
.attr('cx', function (d) {return 1000*h.mesh.vxs[d][0]})
.attr('cy', function (d) {return 1000*h.mesh.vxs[d][1]})
.attr('r', function (d, i) {return i >= n ? 4 : 10})
.style('fill', 'white')
.style('stroke-width', 5)
.style('stroke-linecap', 'round')
.style('stroke', 'black')
.raise();
}
function dropEdge(h, p) {
p = p || 4
var newh = zero(h.mesh);
for (var i = 0; i < h.length; i++) {
var v = h.mesh.vxs[i];
var x = 2.4*v[0] / h.mesh.extent.width;
var y = 2.4*v[1] / h.mesh.extent.height;
newh[i] = h[i] - Math.exp(10*(Math.pow(Math.pow(x, p) + Math.pow(y, p), 1/p) - 1));
}
return newh;
}
function generateCoast(params) {
var mesh = generateGoodMesh(params.npts, params.extent);
var h = add(
slope(mesh, randomVector(6)),
cone(mesh, runif(-10, -10)),
mountains(mesh, 50)
);
for (var i = 0; i < 10; i++) {
h = relax(h);
}
h = peaky(h);
h = doErosion(h, runif(0, 0.1), 5);
h = setSeaLevel(h, runif(0.8, 0.7));
h = fillSinks(h);
h = cleanCoast(h, 3);
return h;
}
function terrCenter(h, terr, city, landOnly) {
var x = 0;
var y = 0;
var n = 0;
for (var i = 0; i < terr.length; i++) {
if (terr[i] != city) continue;
if (landOnly && h[i] <= 0) continue;
x += terr.mesh.vxs[i][0];
y += terr.mesh.vxs[i][1];
n++;
}
return [x/n, y/n];
}
function drawLabels(svg, render) {
var params = render.params;
var h = render.h;
var terr = render.terr;
var cities = render.cities;
var nterrs = render.params.nterrs;
var avoids = [render.rivers, render.coasts, render.borders];
var lang = makeRandomLanguage();
var citylabels = [];
function penalty(label) {
var pen = 0;
if (label.x0 < -0.45 * h.mesh.extent.width) pen += 100;
if (label.x1 > 0.45 * h.mesh.extent.width) pen += 100;
if (label.y0 < -0.45 * h.mesh.extent.height) pen += 100;
if (label.y1 > 0.45 * h.mesh.extent.height) pen += 100;
for (var i = 0; i < citylabels.length; i++) {
var olabel = citylabels[i];
if (label.x0 < olabel.x1 && label.x1 > olabel.x0 &&
label.y0 < olabel.y1 && label.y1 > olabel.y0) {
pen += 100;
}
}
for (var i = 0; i < cities.length; i++) {
var c = h.mesh.vxs[cities[i]];
if (label.x0 < c[0] && label.x1 > c[0] && label.y0 < c[1] && label.y1 > c[1]) {
pen += 100;
}
}
for (var i = 0; i < avoids.length; i++) {
var avoid = avoids[i];
for (var j = 0; j < avoid.length; j++) {
var avpath = avoid[j];
for (var k = 0; k < avpath.length; k++) {
var pt = avpath[k];
if (pt[0] > label.x0 && pt[0] < label.x1 && pt[1] > label.y0 && pt[1] < label.y1) {
pen++;
}
}
}
}
return pen;
}
for (var i = 0; i < cities.length; i++) {
var x = h.mesh.vxs[cities[i]][0];
var y = h.mesh.vxs[cities[i]][1];
var text = makeName(lang, 'city');
var size = i < nterrs ? params.fontsizes.city : params.fontsizes.town;
var sx = 0.65 * size/1000 * text.length;
var sy = size/1000;
var posslabels = [
{
x: x + 0.8 * sy,
y: y + 0.3 * sy,
align: 'start',
x0: x + 0.7 * sy,
y0: y - 0.6 * sy,
x1: x + 0.7 * sy + sx,
y1: y + 0.6 * sy
},
{
x: x - 0.8 * sy,
y: y + 0.3 * sy,
align: 'end',
x0: x - 0.9 * sy - sx,
y0: y - 0.7 * sy,
x1: x - 0.9 * sy,
y1: y + 0.7 * sy
},
{
x: x,
y: y - 0.8 * sy,
align: 'middle',
x0: x - sx/2,
y0: y - 1.9*sy,
x1: x + sx/2,
y1: y - 0.7 * sy
},
{
x: x,
y: y + 1.2 * sy,
align: 'middle',
x0: x - sx/2,
y0: y + 0.1*sy,
x1: x + sx/2,
y1: y + 1.3*sy
}
];
var label = posslabels[d3.scan(posslabels, function (a, b) {return penalty(a) - penalty(b)})];
label.text = text;
label.size = size;
citylabels.push(label);
}
var texts = svg.selectAll('text.city').data(citylabels);
texts.enter()
.append('text')
.classed('city', true);
texts.exit()
.remove();
svg.selectAll('text.city')
.attr('x', function (d) {return 1000*d.x})
.attr('y', function (d) {return 1000*d.y})
.style('font-size', function (d) {return d.size})
.style('text-anchor', function (d) {return d.align})
.text(function (d) {return d.text})
.raise();
var reglabels = [];
for (var i = 0; i < nterrs; i++) {
var city = cities[i];
var text = makeName(lang, 'region');
var sy = params.fontsizes.region / 1000;
var sx = 0.6 * text.length * sy;
var lc = terrCenter(h, terr, city, true);
var oc = terrCenter(h, terr, city, false);
var best = 0;
var bestscore = -999999;
for (var j = 0; j < h.length; j++) {
var score = 0;
var v = h.mesh.vxs[j];
score -= 3000 * Math.sqrt((v[0] - lc[0]) * (v[0] - lc[0]) + (v[1] - lc[1]) * (v[1] - lc[1]));
score -= 1000 * Math.sqrt((v[0] - oc[0]) * (v[0] - oc[0]) + (v[1] - oc[1]) * (v[1] - oc[1]));
if (terr[j] != city) score -= 3000;
for (var k = 0; k < cities.length; k++) {
var u = h.mesh.vxs[cities[k]];
if (Math.abs(v[0] - u[0]) < sx &&
Math.abs(v[1] - sy/2 - u[1]) < sy) {
score -= k < nterrs ? 4000 : 500;
}
if (v[0] - sx/2 < citylabels[k].x1 &&
v[0] + sx/2 > citylabels[k].x0 &&
v[1] - sy < citylabels[k].y1 &&
v[1] > citylabels[k].y0) {
score -= 5000;
}
}
for (var k = 0; k < reglabels.length; k++) {
var label = reglabels[k];
if (v[0] - sx/2 < label.x + label.width/2 &&
v[0] + sx/2 > label.x - label.width/2 &&
v[1] - sy < label.y &&
v[1] > label.y - label.size) {
score -= 20000;
}
}
if (h[j] <= 0) score -= 500;
if (v[0] + sx/2 > 0.5 * h.mesh.extent.width) score -= 50000;
if (v[0] - sx/2 < -0.5 * h.mesh.extent.width) score -= 50000;
if (v[1] > 0.5 * h.mesh.extent.height) score -= 50000;
if (v[1] - sy < -0.5 * h.mesh.extent.height) score -= 50000;
if (score > bestscore) {
bestscore = score;
best = j;
}
}
reglabels.push({
text: text,
x: h.mesh.vxs[best][0],
y: h.mesh.vxs[best][1],
size:sy,
width:sx
});
}
texts = svg.selectAll('text.region').data(reglabels);
texts.enter()
.append('text')
.classed('region', true);
texts.exit()
.remove();
svg.selectAll('text.region')
.attr('x', function (d) {return 1000*d.x})
.attr('y', function (d) {return 1000*d.y})
.style('font-size', function (d) {return 1000*d.size})
.style('text-anchor', 'middle')
.text(function (d) {return d.text})
.raise();
}
function drawMap(svg, render) {
render.rivers = getRivers(render.h, 0.01);
render.coasts = contour(render.h, 0);
render.terr = getTerritories(render);
render.borders = getBorders(render);
drawPaths(svg, 'river', render.rivers);
drawPaths(svg, 'coast', render.coasts);
drawPaths(svg, 'border', render.borders);
visualizeSlopes(svg, render);
visualizeCities(svg, render);
drawLabels(svg, render);
}
function doMap(svg, params) {
var render = {
params: params
};
var width = svg.attr('width');
svg.attr('height', width * params.extent.height / params.extent.width);
svg.attr('viewBox', -1000 * params.extent.width/2 + ' ' +
-1000 * params.extent.height/2 + ' ' +
1000 * params.extent.width + ' ' +
1000 * params.extent.height);
svg.selectAll().remove();
render.h = params.generator(params);
placeCities(render);
drawMap(svg, render);
}
var defaultParams = {
extent: defaultExtent,
generator: generateCoast,
npts: 16384,
ncities: 15,
nterrs: 5,
fontsizes: {
region: 40,
city: 25,
town: 20
}
}