diff --git a/index.html b/index.html index c83816e6..bff23c0e 100644 --- a/index.html +++ b/index.html @@ -1012,6 +1012,8 @@ + +
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 6ffdb21c..244a0693 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -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'); @@ -43,7 +43,7 @@ } else { console.error(`Not enought populated cells (${sorted.length}). Will generate only ${count} states`); } - } + } let burgsTree = d3.quadtree(); let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals @@ -133,108 +133,109 @@ burgs[0] = {name:undefined}; 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}; }))); diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index 1ced583f..b5addfbd 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -186,7 +186,7 @@ //debug.selectAll(".text").data(cost).enter().append("text").attr("x", (d, e) => cells.p[e][0]-1).attr("y", (d, e) => cells.p[e][1]-1).text(d => d ? rn(d) : "").attr("font-size", 2); console.timeEnd('expandCultures'); } - + function getBiomeCost(c, biome, type) { if (cells.biome[pack.cultures[c].center] === biome) return biomesData.cost[biome] / 2; // tiny penalty for native biome if (type === "Hunting") return biomesData.cost[biome] * 5; // non-native biome penalty for hunters diff --git a/modules/ui/tools.js b/modules/ui/tools.js index b5494829..66966f4f 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -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();