mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
v 0.8.31b
This commit is contained in:
parent
ddb30d1ff2
commit
83585c3081
4 changed files with 168 additions and 111 deletions
|
|
@ -1012,6 +1012,8 @@
|
|||
<button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button>
|
||||
<button id="regenerateRivers" data-tip="Click to regenerate all rivers. Please note map heights can be changed by water flow">Rivers</button>
|
||||
<button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">Population</button>
|
||||
<button id="regenerateBurgs" data-tip="Click to regenerate all burgs and routes">Burgs</button>
|
||||
<!-- <button id="regenerateStates" data-tip="Click to regenerate states">States</button> -->
|
||||
</div>
|
||||
|
||||
<div id="addFeature">
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
|
||||
const generate = function() {
|
||||
console.time("generateBurgsAndStates");
|
||||
|
||||
const cells = pack.cells, vertices = pack.vertices, features = pack.features, cultures = pack.cultures, n = cells.i.length;
|
||||
const cells = pack.cells, cultures = pack.cultures, n = cells.i.length;
|
||||
|
||||
cells.burg = new Uint16Array(n); // cell burg
|
||||
cells.road = new Uint16Array(n); // cell road power
|
||||
|
|
@ -19,13 +18,14 @@
|
|||
placeTowns();
|
||||
const townRoutes = Routes.getTrails();
|
||||
specifyBurgs();
|
||||
|
||||
const oceanRoutes = Routes.getSearoutes();
|
||||
|
||||
expandStates();
|
||||
normalizeStates();
|
||||
|
||||
Routes.draw(capitalRoutes, townRoutes, oceanRoutes);
|
||||
drawBurgsWithLabels();
|
||||
drawBurgs();
|
||||
|
||||
function placeCapitals() {
|
||||
console.time('placeCapitals');
|
||||
|
|
@ -134,107 +134,108 @@
|
|||
console.timeEnd('placeTowns');
|
||||
}
|
||||
|
||||
// define burg coordinates and define details
|
||||
function specifyBurgs() {
|
||||
console.time("specifyBurgs");
|
||||
|
||||
for (const b of burgs) {
|
||||
if (!b.i) continue;
|
||||
const i = b.cell;
|
||||
|
||||
// asign port status: capital with any harbor and towns with good harbors
|
||||
const port = (b.capital && cells.harbor[i]) || cells.harbor[i] === 1;
|
||||
b.port = port ? cells.f[cells.haven[i]] : 0; // port is defined by feature id it lays on
|
||||
|
||||
// define burg population (keep urbanization at about 10% rate)
|
||||
b.population = rn(Math.max((cells.s[i] + cells.road[i]) / 3 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
|
||||
|
||||
if (port) {
|
||||
b.population = rn(b.population * 1.3, 3); // increase port population
|
||||
const e = cells.v[i].filter(v => vertices.c[v].some(c => c === cells.haven[i])); // vertices of common edge
|
||||
b.x = rn((vertices.p[e[0]][0] + vertices.p[e[1]][0]) / 2, 2);
|
||||
b.y = rn((vertices.p[e[0]][1] + vertices.p[e[1]][1]) / 2, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// shift burgs on rivers semi-randomly and just a bit
|
||||
if (cells.r[i]) {
|
||||
const shift = Math.min(cells.fl[i]/150, 1);
|
||||
if (i%2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
|
||||
if (cells.r[i]%2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// de-assign port status if it's the only one on feature
|
||||
for (const f of features) {
|
||||
if (!f.i || f.land) continue;
|
||||
const onFeature = burgs.filter(b => b.port === f.i);
|
||||
if (onFeature.length === 1) {
|
||||
onFeature[0].port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.timeEnd("specifyBurgs");
|
||||
}
|
||||
|
||||
function drawBurgsWithLabels() {
|
||||
console.time("drawBurgs");
|
||||
|
||||
// remove old data
|
||||
burgIcons.selectAll("circle").remove();
|
||||
burgLabels.selectAll("text").remove();
|
||||
icons.selectAll("use").remove();
|
||||
|
||||
// capitals
|
||||
const capitals = burgs.filter(b => b.capital);
|
||||
const capitalIcons = burgIcons.select("#cities");
|
||||
const capitalLabels = burgLabels.select("#cities");
|
||||
const capitalSize = capitalIcons.attr("size") || 1;
|
||||
const capitalAnchors = anchors.selectAll("#cities");
|
||||
const caSize = capitalAnchors.attr("size") || 2;
|
||||
|
||||
capitalIcons.selectAll("circle").data(capitals).enter()
|
||||
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
|
||||
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", capitalSize);
|
||||
|
||||
capitalLabels.selectAll("text").data(capitals).enter()
|
||||
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
|
||||
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${capitalSize * -1.5}px`).text(d => d.name);
|
||||
|
||||
capitalAnchors.selectAll("use").data(capitals.filter(c => c.port)).enter()
|
||||
.append("use").attr("xlink:href", "#icon-anchor").attr("data-id", d => d.i)
|
||||
.attr("x", d => rn(d.x - caSize * .47, 2)).attr("y", d => rn(d.y - caSize * .47, 2))
|
||||
.attr("width", caSize).attr("height", caSize);
|
||||
|
||||
// towns
|
||||
const towns = burgs.filter(b => b.capital === false);
|
||||
const townIcons = burgIcons.select("#towns");
|
||||
const townLabels = burgLabels.select("#towns");
|
||||
const townSize = townIcons.attr("size") || 0.5;
|
||||
const townsAnchors = anchors.selectAll("#towns");
|
||||
const taSize = townsAnchors.attr("size") || 1;
|
||||
|
||||
townIcons.selectAll("circle").data(towns).enter()
|
||||
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
|
||||
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", townSize);
|
||||
|
||||
townLabels.selectAll("text").data(towns).enter()
|
||||
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
|
||||
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${townSize * -1.5}px`).text(d => d.name);
|
||||
|
||||
townsAnchors.selectAll("use").data(towns.filter(c => c.port)).enter()
|
||||
.append("use").attr("xlink:href", "#icon-anchor").attr("data-id", d => d.i)
|
||||
.attr("x", d => rn(d.x - taSize * .47, 2)).attr("y", d => rn(d.y - taSize * .47, 2))
|
||||
.attr("width", taSize).attr("height", taSize);
|
||||
|
||||
console.timeEnd("drawBurgs");
|
||||
}
|
||||
|
||||
console.timeEnd("generateBurgsAndStates");
|
||||
}
|
||||
|
||||
// define burg coordinates and define details
|
||||
const specifyBurgs = function() {
|
||||
console.time("specifyBurgs");
|
||||
const cells = pack.cells, vertices = pack.vertices;
|
||||
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i) continue;
|
||||
const i = b.cell;
|
||||
|
||||
// asign port status: capital with any harbor and towns with good harbors
|
||||
const port = (b.capital && cells.harbor[i]) || cells.harbor[i] === 1;
|
||||
b.port = port ? cells.f[cells.haven[i]] : 0; // port is defined by feature id it lays on
|
||||
|
||||
// define burg population (keep urbanization at about 10% rate)
|
||||
b.population = rn(Math.max((cells.s[i] + cells.road[i]) / 3 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
|
||||
|
||||
if (port) {
|
||||
b.population = rn(b.population * 1.3, 3); // increase port population
|
||||
const e = cells.v[i].filter(v => vertices.c[v].some(c => c === cells.haven[i])); // vertices of common edge
|
||||
b.x = rn((vertices.p[e[0]][0] + vertices.p[e[1]][0]) / 2, 2);
|
||||
b.y = rn((vertices.p[e[0]][1] + vertices.p[e[1]][1]) / 2, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// shift burgs on rivers semi-randomly and just a bit
|
||||
if (cells.r[i]) {
|
||||
const shift = Math.min(cells.fl[i]/150, 1);
|
||||
if (i%2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
|
||||
if (cells.r[i]%2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// de-assign port status if it's the only one on feature
|
||||
for (const f of pack.features) {
|
||||
if (!f.i || f.land) continue;
|
||||
const onFeature = pack.burgs.filter(b => b.port === f.i);
|
||||
if (onFeature.length === 1) {
|
||||
onFeature[0].port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.timeEnd("specifyBurgs");
|
||||
}
|
||||
|
||||
const drawBurgs = function() {
|
||||
console.time("drawBurgs");
|
||||
|
||||
// remove old data
|
||||
burgIcons.selectAll("circle").remove();
|
||||
burgLabels.selectAll("text").remove();
|
||||
icons.selectAll("use").remove();
|
||||
|
||||
// capitals
|
||||
const capitals = pack.burgs.filter(b => b.capital);
|
||||
const capitalIcons = burgIcons.select("#cities");
|
||||
const capitalLabels = burgLabels.select("#cities");
|
||||
const capitalSize = capitalIcons.attr("size") || 1;
|
||||
const capitalAnchors = anchors.selectAll("#cities");
|
||||
const caSize = capitalAnchors.attr("size") || 2;
|
||||
|
||||
capitalIcons.selectAll("circle").data(capitals).enter()
|
||||
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
|
||||
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", capitalSize);
|
||||
|
||||
capitalLabels.selectAll("text").data(capitals).enter()
|
||||
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
|
||||
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${capitalSize * -1.5}px`).text(d => d.name);
|
||||
|
||||
capitalAnchors.selectAll("use").data(capitals.filter(c => c.port)).enter()
|
||||
.append("use").attr("xlink:href", "#icon-anchor").attr("data-id", d => d.i)
|
||||
.attr("x", d => rn(d.x - caSize * .47, 2)).attr("y", d => rn(d.y - caSize * .47, 2))
|
||||
.attr("width", caSize).attr("height", caSize);
|
||||
|
||||
// towns
|
||||
const towns = pack.burgs.filter(b => b.capital === false);
|
||||
const townIcons = burgIcons.select("#towns");
|
||||
const townLabels = burgLabels.select("#towns");
|
||||
const townSize = townIcons.attr("size") || 0.5;
|
||||
const townsAnchors = anchors.selectAll("#towns");
|
||||
const taSize = townsAnchors.attr("size") || 1;
|
||||
|
||||
townIcons.selectAll("circle").data(towns).enter()
|
||||
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
|
||||
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", townSize);
|
||||
|
||||
townLabels.selectAll("text").data(towns).enter()
|
||||
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
|
||||
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${townSize * -1.5}px`).text(d => d.name);
|
||||
|
||||
townsAnchors.selectAll("use").data(towns.filter(c => c.port)).enter()
|
||||
.append("use").attr("xlink:href", "#icon-anchor").attr("data-id", d => d.i)
|
||||
.attr("x", d => rn(d.x - taSize * .47, 2)).attr("y", d => rn(d.y - taSize * .47, 2))
|
||||
.attr("width", taSize).attr("height", taSize);
|
||||
|
||||
console.timeEnd("drawBurgs");
|
||||
}
|
||||
|
||||
// growth algorithm to assign cells to states like we did for cultures
|
||||
const expandStates = function() {
|
||||
console.time("expandStates");
|
||||
|
|
@ -481,6 +482,6 @@
|
|||
console.timeEnd("drawStateLabels");
|
||||
}
|
||||
|
||||
return {generate, expandStates, normalizeStates, drawStateLabels};
|
||||
return {generate, expandStates, normalizeStates, drawBurgs, specifyBurgs, drawStateLabels};
|
||||
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -19,13 +19,10 @@ toolsContent.addEventListener("click", function(event) {
|
|||
if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else
|
||||
if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else
|
||||
if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else
|
||||
if (button === "regenerateRivers") {
|
||||
const heights = new Uint8Array(pack.cells.h);
|
||||
Rivers.generate();
|
||||
pack.cells.h = new Uint8Array(heights);
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
} else
|
||||
if (button === "regeneratePopulation") recalculatePopulation();
|
||||
if (button === "regenerateRivers") regenerateRivers(); else
|
||||
if (button === "regeneratePopulation") recalculatePopulation(); else
|
||||
if (button === "regenerateBurgs") regenerateBurgs(); else
|
||||
if (button === "regenerateStates") regenerateStates();
|
||||
|
||||
// Click to Add buttons
|
||||
if (button === "addLabel") toggleAddLabel(); else
|
||||
|
|
@ -35,6 +32,13 @@ toolsContent.addEventListener("click", function(event) {
|
|||
if (button === "addMarker") toggleAddMarker();
|
||||
});
|
||||
|
||||
function regenerateRivers() {
|
||||
const heights = new Uint8Array(pack.cells.h);
|
||||
Rivers.generate();
|
||||
pack.cells.h = new Uint8Array(heights);
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
}
|
||||
|
||||
function recalculatePopulation() {
|
||||
rankCells();
|
||||
pack.burgs.forEach(b => {
|
||||
|
|
@ -47,6 +51,56 @@ function recalculatePopulation() {
|
|||
});
|
||||
}
|
||||
|
||||
function regenerateBurgs() {
|
||||
const cells = pack.cells, states = pack.states;
|
||||
rankCells();
|
||||
cells.burg = new Uint16Array(cells.i.length);
|
||||
const burgs = pack.burgs = [0]; // clear burgs array
|
||||
states.filter(s => s.i).forEach(s => s.capital = 0); // clear capitals
|
||||
const burgsTree = d3.quadtree();
|
||||
|
||||
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
|
||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 10 / densityInput.value ** .8) + states.length : +manorsInput.value + states.length;
|
||||
const spacing = (graphWidth + graphHeight) * 9 / burgsCount; // base min distance between towns
|
||||
|
||||
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||
const id = burgs.length;
|
||||
const cell = sorted[i];
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
|
||||
const s = spacing * Math.random() + 0.5; // randomize to make the placement not uniform
|
||||
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||
|
||||
const state = cells.state[cell];
|
||||
const capital = !states[state].capital; // if state doesn't have capital, make this burg a capital
|
||||
if (capital) {states[state].capital = id; states[state].cell = cell;}
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const name = Names.getCulture(culture);
|
||||
burgs.push({cell, x, y, state, i: id, culture, name, capital, feature: cells.f[cell]});
|
||||
burgsTree.add([x, y]);
|
||||
cells.burg[cell] = id;
|
||||
}
|
||||
|
||||
// add a capital at former place for states without added capitals
|
||||
states.filter(s => s.i && !s.removed && !s.capital).forEach(s => {
|
||||
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
|
||||
s.capital = burg;
|
||||
pack.burgs[burg].capital = true;
|
||||
pack.burgs[burg].state = s.i;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
});
|
||||
|
||||
BurgsAndStates.specifyBurgs();
|
||||
BurgsAndStates.drawBurgs();
|
||||
Routes.regenerate();
|
||||
}
|
||||
|
||||
function regenerateStates() {
|
||||
|
||||
}
|
||||
|
||||
function unpressClickToAddButton() {
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
restoreDefaultEvents();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue