From 3a6d2352b072447afa6ccc19f70d031c02101807 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 15 Aug 2021 11:24:12 +0300 Subject: [PATCH 01/33] Reservation to bein upper case, new values --- index.html | 4 +++- modules/burgs-and-states.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 8db2c53c..e58b40e6 100644 --- a/index.html +++ b/index.html @@ -2544,7 +2544,9 @@ + + @@ -2740,7 +2742,7 @@ - + diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index cb486ff9..d3ae5def 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -975,6 +975,7 @@ window.BurgsAndStates = (function () { // Default name depends on exponent tier, some culture bases have special names for tiers if (s.diplomacy) { if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal dutchies on borderland + if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals } From 6e522ec1f15fc6437bc599a5cd33c019af7f4fd6 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 15 Aug 2021 11:39:40 +0300 Subject: [PATCH 02/33] + more government types --- index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index e58b40e6..c8d618e3 100644 --- a/index.html +++ b/index.html @@ -2544,7 +2544,6 @@ - @@ -2720,6 +2719,7 @@ -
+
Source width:
- +
diff --git a/modules/river-generator.js b/modules/river-generator.js index a17cff87..43c67eb1 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -38,7 +38,7 @@ window.Rivers = (function () { const lakeOutCells = Lakes.setClimateData(h); land.forEach(function (i) { - cells.fl[i] += prec[cells.g[i]] * area[i] / 100; // add flux from precipitation + cells.fl[i] += (prec[cells.g[i]] * area[i]) / 100; // add flux from precipitation // create lake outlet if lake is not in deep depression and flux > evaporation const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : []; @@ -169,7 +169,7 @@ window.Rivers = (function () { const widthFactor = !parent || parent === riverId ? 1.2 : 1; const meanderedPoints = addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second - const length = rn(getApproximateLength(meanderedPoints), 2); + const length = getApproximateLength(meanderedPoints); const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0)); pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells}); @@ -414,7 +414,10 @@ window.Rivers = (function () { return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]); }; - const getApproximateLength = points => points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); + const getApproximateLength = points => { + const length = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); + return rn(length, 2); + }; // Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m, // Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m diff --git a/modules/ui/rivers-creator.js b/modules/ui/rivers-creator.js index 0a2d62c6..df6cd3e9 100644 --- a/modules/ui/rivers-creator.js +++ b/modules/ui/rivers-creator.js @@ -100,16 +100,13 @@ function createRiver() { const basin = getBasin(parent); rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"}); + const id = "river" + riverId; // render river lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - viewbox - .select("#rivers") - .append("path") - .attr("id", "river" + riverId) - .attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth)); + viewbox.select("#rivers").append("path").attr("id", id).attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth)); - editRiver(riverId); + editRiver(id); } function closeRiverCreator() { From c1533c54080611fce75517a365856616b4fb9f03 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 24 Aug 2021 20:19:32 +0300 Subject: [PATCH 14/33] save - handle cases when data is missing --- modules/save.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/save.js b/modules/save.js index afcf859e..9538f747 100644 --- a/modules/save.js +++ b/modules/save.js @@ -144,18 +144,18 @@ async function getMapURL(type, options = {}) { cloneEl.id = "fantasyMap"; document.body.appendChild(cloneEl); const clone = d3.select(cloneEl); - if (!debug) clone.select("#debug").remove(); + if (!debug) clone.select("#debug")?.remove(); const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; const svgDefs = document.getElementById("defElements"); const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; - if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); - if (globe) clone.select("#scaleBar").remove(); + if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove(); + if (globe) clone.select("#scaleBar")?.remove(); if (noLabels) { - clone.select("#labels #states").remove(); - clone.select("#labels #burgLabels").remove(); - clone.select("#icons #burgIcons").remove(); + clone.select("#labels #states")?.remove(); + clone.select("#labels #burgLabels")?.remove(); + clone.select("#icons #burgIcons")?.remove(); } if (noWater) { clone.select("#oceanBase").attr("opacity", 0); @@ -258,10 +258,10 @@ async function getMapURL(type, options = {}) { if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); } - if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); // remove unused hatching group - if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); // remove unused fog - if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths - if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths + if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group + if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog + if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths + if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths // add armies style if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", ""); @@ -296,8 +296,8 @@ async function getMapURL(type, options = {}) { // remove hidden g elements and g elements without children to make downloaded svg smaller in size function removeUnusedElements(clone) { - if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove(); - if (markers.style("display") === "none") clone.select("#defs-markers").remove(); + if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove(); + if (markers.style("display") === "none") clone.select("#defs-markers")?.remove(); for (let empty = 1; empty; ) { empty = 0; From 008aea2b2f66d57d643b50054a3a19604f017b9d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 24 Aug 2021 22:24:33 +0300 Subject: [PATCH 15/33] river edit: allow to add interim points --- index.css | 10 ++--- modules/river-generator.js | 3 +- modules/ui/rivers-editor.js | 84 ++++++++++++++++++++++--------------- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/index.css b/index.css index f1159de9..481e6271 100644 --- a/index.css +++ b/index.css @@ -985,16 +985,16 @@ body button.noicon { #controlPoints > path { fill: none; - stroke: #000000; + stroke: #0a0909; stroke-width: 2; opacity: 0.4; cursor: pointer; } -#controlCells > .current { - fill: #82c8ff40; - stroke: #82c8ff; - stroke-width: 0.4; +#controlCells { + pointer-events: none; + fill: #82c8ff80; + stroke: "none"; } #vertices > circle { diff --git a/modules/river-generator.js b/modules/river-generator.js index 43c67eb1..173f5626 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -319,9 +319,10 @@ window.Rivers = (function () { }; const getRiverPoints = (riverCells, riverPoints) => { + if (riverPoints) return riverPoints; + const {p} = pack.cells; return riverCells.map((cell, i) => { - if (riverPoints && riverPoints[i]) return riverPoints[i]; if (cell === -1) return getBorderPoint(riverCells[i - 1]); return p[cell]; }); diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js index 57e6bb1d..c1ca59c8 100644 --- a/modules/ui/rivers-editor.js +++ b/modules/ui/rivers-editor.js @@ -8,9 +8,9 @@ function editRiver(id) { document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells"); if (!layerIsOn("toggleCells")) toggleCells(); - elSelected = d3.select("#" + id); + elSelected = d3.select("#" + id).on("click", addControlPoint); - tip("Drag control points to change the river course. For major changes please create a new river instead", true); + tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true); debug.append("g").attr("id", "controlCells"); debug.append("g").attr("id", "controlPoints"); @@ -19,8 +19,8 @@ function editRiver(id) { const river = getRiver(); const {cells, points} = river; const riverPoints = Rivers.getRiverPoints(cells, points); - drawControlPoints(riverPoints, cells); - drawCells(cells, "current"); + drawControlPoints(riverPoints); + drawCells(cells); $("#riverEditor").dialog({ title: "Edit River", @@ -92,37 +92,35 @@ function editRiver(id) { document.getElementById("riverWidth").value = width; } - function drawControlPoints(points, cells) { + function drawControlPoints(points) { debug .select("#controlPoints") .selectAll("circle") .data(points) - .enter() - .append("circle") + .join("circle") .attr("cx", d => d[0]) .attr("cy", d => d[1]) .attr("r", 0.6) - .attr("data-cell", (d, i) => cells[i]) - .attr("data-i", (d, i) => i) - .call(d3.drag().on("start", dragControlPoint)); + .call(d3.drag().on("start", dragControlPoint)) + .on("click", removeControlPoint); } - function drawCells(cells, type) { + function drawCells(cells) { + const validCells = [...new Set(cells)].filter(i => pack.cells.i[i]); debug .select("#controlCells") - .selectAll(`polygon.${type}`) - .data(cells.filter(i => pack.cells.i[i])) + .selectAll(`polygon`) + .data(validCells) .join("polygon") - .attr("points", d => getPackPolygon(d)) - .attr("class", type); + .attr("points", d => getPackPolygon(d)); } function dragControlPoint() { - const {i, r, fl} = pack.cells; + const {r, fl} = pack.cells; const river = getRiver(); - const initCell = +this.dataset.cell; - const index = +this.dataset.i; + const {x: x0, y: y0} = d3.event; + const initCell = findCell(x0, y0); let movedToCell = null; @@ -136,22 +134,17 @@ function editRiver(id) { this.setAttribute("cy", y); this.__data__ = [rn(x, 1), rn(y, 1)]; redrawRiver(); + drawCells(river.cells); }); d3.event.on("end", () => { - if (movedToCell) { - this.dataset.cell = movedToCell; - river.cells[index] = movedToCell; - drawCells(river.cells, "current"); - - if (!r[movedToCell]) { - // swap river data - r[initCell] = 0; - r[movedToCell] = river.i; - const sourceFlux = fl[initCell]; - fl[initCell] = fl[movedToCell]; - fl[movedToCell] = sourceFlux; - } + if (movedToCell && !r[movedToCell]) { + // swap river data + r[initCell] = 0; + r[movedToCell] = river.i; + const sourceFlux = fl[initCell]; + fl[initCell] = fl[movedToCell]; + fl[movedToCell] = sourceFlux; } }); } @@ -159,8 +152,10 @@ function editRiver(id) { function redrawRiver() { const river = getRiver(); river.points = debug.selectAll("#controlPoints > *").data(); - const {cells, widthFactor, sourceWidth} = river; - const meanderedPoints = Rivers.addMeandering(cells, river.points); + river.cells = river.points.map(([x, y]) => findCell(x, y)); + + const {widthFactor, sourceWidth} = river; + const meanderedPoints = Rivers.addMeandering(river.cells, river.points); lineGen.curve(d3.curveCatmullRom.alpha(0.1)); const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth); @@ -170,6 +165,27 @@ function editRiver(id) { if (modules.elevation) showEPForRiver(elSelected.node()); } + function addControlPoint() { + const [x, y] = d3.mouse(this); + const point = [rn(x, 1), rn(y, 1)]; + + const river = getRiver(); + if (!river.points) river.points = debug.selectAll("#controlPoints > *").data(); + + const index = getSegmentId(river.points, point, 2); + river.points.splice(index, 0, point); + drawControlPoints(river.points); + redrawRiver(); + } + + function removeControlPoint() { + this.remove(); + redrawRiver(); + + const {cells} = getRiver(); + drawCells(cells); + } + function changeName() { getRiver().name = this.value; } @@ -244,6 +260,8 @@ function editRiver(id) { function closeRiverEditor() { debug.select("#controlPoints").remove(); debug.select("#controlCells").remove(); + + elSelected.on("click", null); unselect(); clearMainTip(); From cfdb3a35dfd5df73d64e63ca7c11b68c4e43e526 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 24 Aug 2021 23:51:10 +0300 Subject: [PATCH 16/33] try to restore river course on load --- modules/load.js | 37 ++++++++++++++++++++++++------------- modules/ui/rivers-editor.js | 1 + 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/modules/load.js b/modules/load.js index 323f3496..756f4811 100644 --- a/modules/load.js +++ b/modules/load.js @@ -710,29 +710,40 @@ function parseLoadedData(data) { if (version < 1.65) { // v 1.65 changed rivers data - rivers.attr("style", null); // remove style to unhide layer + d3.select("#rivers").attr("style", null); // remove style to unhide layer + const {cells, rivers} = pack; - for (const river of pack.rivers) { + for (const river of rivers) { const node = document.getElementById("river" + river.i); if (node && !river.cells) { - const riverCells = new Set(); + const riverCells = []; + const riverPoints = []; + const length = node.getTotalLength() / 2; const segments = Math.ceil(length / 6); const increment = length / segments; - for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) { - const p1 = node.getPointAtLength(i); - const p2 = node.getPointAtLength(c); - const x = (p1.x + p2.x) / 2; - const y = (p1.y + p2.y) / 2; - const cell = findCell(x, y, 6); - if (cell) riverCells.add(cell); + + for (let i = 0; i <= segments; i++) { + const shift = increment * i; + const {x: x1, y: y1} = node.getPointAtLength(length + shift); + const {x: x2, y: y2} = node.getPointAtLength(length - shift); + const x = rn((x1 + x2) / 2, 1); + const y = rn((y1 + y2) / 2, 1); + + const cell = findCell(x, y); + riverPoints.push([x, y]); + riverCells.push(cell); } - river.cells = Array.from(riverCells); + river.cells = riverCells; + river.points = riverPoints; } - pack.cells.i.forEach(i => { - if (pack.cells.r[i] && pack.cells.h[i] < 20) pack.cells.r[i] = 0; + river.widthFactor = 1; + + cells.i.forEach(i => { + const riverInWater = cells.r[i] && cells.h[i] < 20; + if (riverInWater) cells.r[i] = 0; }); } } diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js index c1ca59c8..7a24cfe5 100644 --- a/modules/ui/rivers-editor.js +++ b/modules/ui/rivers-editor.js @@ -145,6 +145,7 @@ function editRiver(id) { const sourceFlux = fl[initCell]; fl[initCell] = fl[movedToCell]; fl[movedToCell] = sourceFlux; + redrawRiver(); } }); } From 3be986828eda104fe451308176bfba157dfb71eb Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 25 Aug 2021 14:23:27 +0300 Subject: [PATCH 17/33] round map coordinates --- main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.js b/main.js index 8a3a9a9d..003795d8 100644 --- a/main.js +++ b/main.js @@ -880,8 +880,8 @@ function openNearSeaLakes() { function defineMapSize() { const [size, latitude] = getSizeAndLatitude(); const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options - if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size; - if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude; + if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = rn(size); + if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = rn(latitude); function getSizeAndLatitude() { const template = document.getElementById("templateInput").value; // heightmap template @@ -914,11 +914,11 @@ function calculateMapCoordinates() { const size = +document.getElementById("mapSizeOutput").value; const latShift = +document.getElementById("latitudeOutput").value; - const latT = (size / 100) * 180; - const latN = 90 - ((180 - latT) * latShift) / 100; - const latS = latN - latT; + const latT = rn((size / 100) * 180, 1); + const latN = rn(90 - ((180 - latT) * latShift) / 100, 1); + const latS = rn(latN - latT, 1); - const lon = Math.min(((graphWidth / graphHeight) * latT) / 2, 180); + const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180)); mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon}; } From 5bbca406116650a183896ffd7c8dd7381956f03a Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 26 Aug 2021 19:09:38 +0300 Subject: [PATCH 18/33] redraw rivers on world config change --- modules/ui/layers.js | 2 + modules/ui/world-configurator.js | 63 ++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/modules/ui/layers.js b/modules/ui/layers.js index a0e8b923..622cfc96 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -1456,6 +1456,8 @@ function toggleRivers(event) { function drawRivers() { TIME && console.time("drawRivers"); + rivers.selectAll("*").remove(); + const {addMeandering, getRiverPath} = Rivers; lineGen.curve(d3.curveCatmullRom.alpha(0.1)); diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js index 8cbd6a20..aa83eb82 100644 --- a/modules/ui/world-configurator.js +++ b/modules/ui/world-configurator.js @@ -1,13 +1,17 @@ function editWorld() { if (customization) return; - $("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em", + $("#worldConfigurator").dialog({ + title: "Configure World", + resizable: false, + width: "42em", buttons: { "Whole World": () => applyWorldPreset(100, 50), - "Northern": () => applyWorldPreset(33, 25), - "Tropical": () => applyWorldPreset(33, 50), - "Southern": () => applyWorldPreset(33, 75), + Northern: () => applyWorldPreset(33, 25), + Tropical: () => applyWorldPreset(33, 50), + Southern: () => applyWorldPreset(33, 75), "Restore Winds": restoreDefaultWinds - }, open: function() { + }, + open: function () { const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World")); buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes")); @@ -19,7 +23,8 @@ function editWorld() { const globe = d3.select("#globe"); const clr = d3.scaleSequential(d3.interpolateSpectral); - const tMax = 30, tMin = -25; // temperature extremes + const tMax = 30, + tMin = -25; // temperature extremes const projection = d3.geoOrthographic().translate([100, 100]).scale(100); const path = d3.geoPath(projection); @@ -29,15 +34,15 @@ function editWorld() { if (modules.editWorld) return; modules.editWorld = true; - document.getElementById("worldControls").addEventListener("input", (e) => updateWorld(e.target)); + document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target)); globe.select("#globeWindArrows").on("click", changeWind); globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule updateWindDirections(); function updateWorld(el) { if (el) { - document.getElementById(el.dataset.stored+"Input").value = el.value; - document.getElementById(el.dataset.stored+"Output").value = el.value; + document.getElementById(el.dataset.stored + "Input").value = el.value; + document.getElementById(el.dataset.stored + "Output").value = el.value; if (el.dataset.stored) lock(el.dataset.stored); } @@ -56,16 +61,18 @@ function editWorld() { if (layerIsOn("togglePrec")) drawPrec(); if (layerIsOn("toggleBiomes")) drawBiomes(); if (layerIsOn("toggleCoordinates")) drawCoordinates(); + if (layerIsOn("toggleRivers")) drawRivers(); if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500); } function updateGlobePosition() { const size = +document.getElementById("mapSizeOutput").value; - const eqD = graphHeight / 2 * 100 / size; + const eqD = ((graphHeight / 2) * 100) / size; calculateMapCoordinates(); const mc = mapCoordinates; // shortcut - const scale = +distanceScaleInput.value, unit = distanceUnitInput.value; + const scale = +distanceScaleInput.value, + unit = distanceUnitInput.value; const meridian = toKilometer(eqD * 2 * scale); document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`; document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`; @@ -82,27 +89,35 @@ function editWorld() { return 0; // 0 if distanceUnitInput is a custom unit } - function lat(lat) {return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";} // parse latitude value - const area = d3.geoGraticule().extent([[mc.lonW, mc.latN], [mc.lonE, mc.latS]]); + function lat(lat) { + return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S"; + } // parse latitude value + const area = d3.geoGraticule().extent([ + [mc.lonW, mc.latN], + [mc.lonE, mc.latS] + ]); globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area } function updateGlobeTemperature() { const tEq = +document.getElementById("temperatureEquatorOutput").value; - document.getElementById("temperatureEquatorF").innerHTML = rn(tEq * 9/5 + 32); + document.getElementById("temperatureEquatorF").innerHTML = rn((tEq * 9) / 5 + 32); const tPole = +document.getElementById("temperaturePoleOutput").value; - document.getElementById("temperaturePoleF").innerHTML = rn(tPole * 9/5 + 32); + document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32); globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin))); - globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 2/3 - tMin) / (tMax - tMin))); - globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 1/3 - tMin) / (tMax - tMin))); + globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin))); + globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin))); globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin))); } function updateWindDirections() { - globe.select("#globeWindArrows").selectAll("path").each(function(d, i) { - const tr = parseTransform(this.getAttribute("transform")); - this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`); - }); + globe + .select("#globeWindArrows") + .selectAll("path") + .each(function (d, i) { + const tr = parseTransform(this.getAttribute("transform")); + this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`); + }); } function changeWind() { @@ -112,13 +127,13 @@ function editWorld() { const tr = parseTransform(arrow.getAttribute("transform")); arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`); localStorage.setItem("winds", options.winds); - const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0); + const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => ((90 - c) / 30) | 0); if (mapTiers.includes(tier)) updateWorld(); } function restoreDefaultWinds() { const defaultWinds = [225, 45, 225, 315, 135, 315]; - const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0); + const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => ((90 - c) / 30) | 0); const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]); options.winds = defaultWinds; updateWindDirections(); @@ -132,4 +147,4 @@ function editWorld() { lock("latitude"); updateWorld(); } -} \ No newline at end of file +} From d3e1a6cdf4c4a520eca50b799def8275bf371a6b Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 27 Aug 2021 23:35:32 +0300 Subject: [PATCH 19/33] readme update --- README.md | 16 +++++++++------- Readme.txt | 6 +++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3161d67c..8bf86ca6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Fantasy Map Generator -Azgaar's _Fantasy Map Generator_ is a free client-side web application generating interactive and highly customizable svg maps based on voronoi diagram. +Azgaar's _Fantasy Map Generator_ is a free web application generating interactive and highly customizable svg maps based on voronoi diagram. Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator). -Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator). +Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com). [![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840629213659136/preview1.png)](https://i.redd.it/8bf81ir2cy631.png) @@ -12,18 +12,20 @@ Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki [![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840632296734720/preview3.png)](https://cdn.discordapp.com/attachments/515359096925454350/593891237984206848/The_Wichin_Island_-_diplomacy.png) -Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips). +Join our [Discord server](https://discordapp.com/invite/X7E84HU) and [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) to share your creations, discuss the Generator, suggest ideas and get the most recent updates. + +Contact me via [email](mailto:azgaar.fmg@yandex.by) if you have non-public suggestions. For bug reports please use [GitHub issues](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or _#bugs_ channel on Discord. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips). Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run. -Pull requests are welcomed. The Tool codebase is messy and requires re-design, but I will appreciate if you start with minor changes. +Pull requests are highly welcomed. The codebase is messy and requires re-design, but I will appreciate if you start with minor changes. Check out the [data model](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Data-model) before contributing. You can support the project on [Patreon](https://www.patreon.com/azgaar). _Inspiration:_ -* Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain) +- Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain) -* Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation) +- Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation) -* Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com) +- Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com) diff --git a/Readme.txt b/Readme.txt index 6760d7eb..9d270a7c 100644 --- a/Readme.txt +++ b/Readme.txt @@ -1,5 +1,9 @@ Azgaar's Fantasy Map Generator -This is an open-source software available under MIT license + +Developed by Azgaar (azgaar.fmg@yandex.com) and contributors + +Minsk, 2017-2021. MIT License + https://github.com/Azgaar/Fantasy-Map-Generator To run the tool unzip ALL files and open index.html in browser \ No newline at end of file From da0e7880200dbfade84efa8e8eb1e570ce098bbf Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 28 Aug 2021 14:56:54 +0300 Subject: [PATCH 20/33] allow to focus by burg name --- main.js | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/main.js b/main.js index 003795d8..2e6d0d34 100644 --- a/main.js +++ b/main.js @@ -262,10 +262,12 @@ function focusOn() { const url = new URL(window.location.href); const params = url.searchParams; - if (params.get("from") === "MFCG" && document.referrer) { + const fromMGCG = params.get("from") === "MFCG" && document.referrer; + if (fromMGCG) { if (params.get("seed").length === 13) { // show back burg from MFCG - params.set("burg", params.get("seed").slice(-4)); + const burgSeed = params.get("seed").slice(-4); + params.set("burg", burgSeed); } else { // select burg for MFCG findBurgForMFCG(params); @@ -273,23 +275,33 @@ function focusOn() { } } - const s = +params.get("scale") || 8; - let x = +params.get("x"); - let y = +params.get("y"); + const scaleParam = params.get("scale"); + const cellParam = params.get("cell"); + const burgParam = params.get("burg"); - const c = +params.get("cell"); - if (c) { - x = pack.cells.p[c][0]; - y = pack.cells.p[c][1]; + if (scaleParam || cellParam || burgParam) { + const scale = +scaleParam || 8; + + if (cellParam) { + const cell = +params.get("cell"); + const [x, y] = pack.cells.p[cell]; + zoomTo(x, y, scale, 1600); + return; + } + + if (burgParam) { + const burg = isNaN(+burgParam) ? pack.burgs.find(burg => burg.name === burgParam) : pack.burgs[+burgParam]; + if (!burg) return; + + const {x, y} = burg; + zoomTo(x, y, scale, 1600); + return; + } + + const x = +params.get("x") || graphWidth / 2; + const y = +params.get("y") || graphHeight / 2; + zoomTo(x, y, scale, 1600); } - - const b = +params.get("burg"); - if (b && pack.burgs[b]) { - x = pack.burgs[b].x; - y = pack.burgs[b].y; - } - - if (x && y) zoomTo(x, y, s, 1600); } // find burg for MFCG and focus on it From 46838a1702e2deb8a3dc40380b52e686445ef960 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 31 Aug 2021 23:52:08 +0300 Subject: [PATCH 21/33] hightmap edit - fix mod function --- modules/heightmap-generator.js | 16 +++++----- modules/ui/heightmap-editor.js | 54 +++++++++++++++------------------- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js index 9d12a802..a2567bfd 100644 --- a/modules/heightmap-generator.js +++ b/modules/heightmap-generator.js @@ -379,14 +379,16 @@ window.HeightmapGenerator = (function () { const modify = function (range, add, mult, power) { const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0]; const max = range === "land" || range === "all" ? 100 : +range.split("-")[1]; - grid.cells.h = grid.cells.h.map(h => (h >= min && h <= max ? mod(h) : h)); + const isLand = min === 20; - function mod(v) { - if (add) v = min === 20 ? Math.max(v + add, 20) : v + add; - if (mult !== 1) v = min === 20 ? (v - 20) * mult + 20 : v * mult; - if (power) v = min === 20 ? (v - 20) ** power + 20 : v ** power; - return lim(v); - } + grid.cells.h = grid.cells.h.map(h => { + if (h < min || h > max) return h; + + if (add) h = isLand ? Math.max(h + add, 20) : h + add; + if (mult !== 1) h = isLand ? (h - 20) * mult + 20 : h * mult; + if (power) h = isLand ? (h - 20) ** power + 20 : h ** power; + return lim(h); + }); }; const smooth = function (fr = 2, add = 0) { diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 6bc58b89..32740fd9 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -837,31 +837,27 @@ function editHeightmap() { const steps = body.querySelectorAll("#templateBody > div"); if (!steps.length) return; + const {addHill, addPit, addRange, addTrough, addStrait, modify, smooth} = HeightmapGenerator; grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights - for (const s of steps) { - if (s.style.opacity == 0.5) continue; - const type = s.dataset.type; + for (const step of steps) { + if (step.style.opacity === "0.5") continue; + const type = step.dataset.type; - const elCount = s.querySelector(".templateCount") || ""; - const elHeight = s.querySelector(".templateHeight") || ""; + const count = step.querySelector(".templateCount")?.value || ""; + const height = step.querySelector(".templateHeight")?.value || ""; + const dist = step.querySelector(".templateDist")?.value || null; + const x = step.querySelector(".templateX")?.value || null; + const y = step.querySelector(".templateY")?.value || null; - const elDist = s.querySelector(".templateDist"); - const dist = elDist ? elDist.value : null; - - const templateX = s.querySelector(".templateX"); - const x = templateX ? templateX.value : null; - const templateY = s.querySelector(".templateY"); - const y = templateY ? templateY.value : null; - - if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y); - else if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y); - else if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y); - else if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y); - else if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist); - else if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1); - else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value); - else if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value); + if (type === "Hill") addHill(count, height, x, y); + else if (type === "Pit") addPit(count, height, x, y); + else if (type === "Range") addRange(count, height, x, y); + else if (type === "Trough") addTrough(count, height, x, y); + else if (type === "Strait") addStrait(count, dist); + else if (type === "Add") modify(dist, +count, 1); + else if (type === "Multiply") modify(dist, 0, +count); + else if (type === "Smooth") smooth(+count); updateHistory("noStat"); // update history every step } @@ -880,17 +876,13 @@ function editHeightmap() { let data = ""; for (const s of steps) { - if (s.style.opacity == 0.5) continue; + if (s.style.opacity === "0.5") continue; + const type = s.getAttribute("data-type"); - const elCount = s.querySelector(".templateCount"); - const count = elCount ? elCount.value : "0"; - const elHeight = s.querySelector(".templateHeight"); - const elDist = s.querySelector(".templateDist"); - const arg3 = elHeight ? elHeight.value : elDist ? elDist.value : "0"; - const templateX = s.querySelector(".templateX"); - const x = templateX ? templateX.value : "0"; - const templateY = s.querySelector(".templateY"); - const y = templateY ? templateY.value : "0"; + const count = s.querySelector(".templateCount")?.value || "0"; + const arg3 = s.querySelector(".templateHeight")?.value || s.querySelector(".templateDist")?.value || "0"; + const x = s.querySelector(".templateX")?.value || "0"; + const y = s.querySelector(".templateY")?.value || "0"; data += `${type} ${count} ${arg3} ${x} ${y}\r\n`; } From e42bc58bd13633cff449deffb4826957f21cf8d3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 1 Sep 2021 00:46:57 +0300 Subject: [PATCH 22/33] fix states regen count --- modules/ui/tools.js | 55 +++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 643cf8ac..d47e8b09 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -136,21 +136,11 @@ function recalculatePopulation() { function regenerateStates() { const localSeed = Math.floor(Math.random() * 1e9); // new random seed Math.random = aleaPRNG(localSeed); - const burgs = pack.burgs.filter(b => b.i && !b.removed); - if (!burgs.length) { - tip("No burgs to generate states. Please create burgs first", false, "error"); - return; - } - if (burgs.length < +regionsInput.value) { - tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn"); - } - // burg local ids sorted by a bit randomized population: - const sorted = burgs - .map((b, i) => [i, b.population * Math.random()]) - .sort((a, b) => b[1] - a[1]) - .map(b => b[0]); - const capitalsTree = d3.quadtree(); + const statesCount = +regionsInput.value; + const burgs = pack.burgs.filter(b => b.i && !b.removed); + if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); + if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn"); // turn all old capitals into towns burgs @@ -167,8 +157,7 @@ function regenerateStates() { unfog(); - // if desired states number is 0 - if (regionsInput.value == 0) { + if (!statesCount) { tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); pack.states = pack.states.slice(0, 1); // remove all except of neutrals pack.states[0].diplomacy = []; // clear diplomacy @@ -184,26 +173,34 @@ function regenerateStates() { return; } - const neutral = pack.states[0].name; - const count = Math.min(+regionsInput.value, burgs.length); + // burg local ids sorted by a bit randomized population: + const sortedBurgs = burgs + .map((b, i) => [b, b.population * Math.random()]) + .sort((a, b) => b[1] - a[1]) + .map(b => b[0]); + const capitalsTree = d3.quadtree(); + + const neutral = pack.states[0].name; // neutrals name + const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals + pack.states = d3.range(count).map(i => { if (!i) return {i, name: neutral}; - let capital = null, - x = 0, - y = 0; - for (const i of sorted) { - capital = burgs[i]; - (x = capital.x), (y = capital.y); - if (capitalsTree.find(x, y, spacing) === undefined) break; + let capital = null; + for (const burg of sortedBurgs) { + const {x, y} = burg; + if (capitalsTree.find(x, y, spacing) === undefined) { + burg.capital = 1; + capital = burg; + capitalsTree.add([x, y]); + moveBurgToGroup(burg.i, "cities"); + break; + } + spacing = Math.max(spacing - 1, 1); } - capitalsTree.add([x, y]); - capital.capital = 1; - moveBurgToGroup(capital.i, "cities"); - const culture = capital.culture; const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); const name = Names.getState(basename, culture); From 7865497cd2b19aa58f7f21a03e1674719d7358d0 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 1 Sep 2021 19:16:11 +0300 Subject: [PATCH 23/33] no wetlands in elevated areas --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 2e6d0d34..76b0e8a1 100644 --- a/main.js +++ b/main.js @@ -1417,7 +1417,7 @@ function defineBiomes() { function getBiomeId(moisture, temperature, height) { if (height < 20) return 0; // marine biome: all water cells if (temperature < -5) return 11; // permafrost biome - if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24))) return 12; // wetland biome + if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24 && height < 60))) return 12; // wetland biome const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4] const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25] From c783301de9a12887278efe62eef3b7e505c156f8 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 1 Sep 2021 21:33:49 +0300 Subject: [PATCH 24/33] dropbox - import changes from alpha --- index.css | 23 +++--- index.html | 65 +++++++++++----- main.js | 31 -------- modules/load.js | 51 +++++++++++-- modules/save.js | 135 +++++++++++++++++++++------------ modules/ui/heightmap-editor.js | 2 +- modules/ui/options.js | 30 ++++---- 7 files changed, 208 insertions(+), 129 deletions(-) diff --git a/index.css b/index.css index 481e6271..d7119079 100644 --- a/index.css +++ b/index.css @@ -358,14 +358,11 @@ div.tab > button#optionsHide { } #options { - margin: 10px; - font-family: Consolas, monospace; position: absolute; + font-family: Consolas, monospace; border: solid 1px #5e4fa2; - width: 300px; - background-position: center; - background-size: cover; - background-blend-mode: color-dodge; + margin: 10px; + padding-bottom: 0.3em; } #options input, @@ -576,14 +573,16 @@ input[type="color"]::-webkit-color-swatch-wrapper { padding-left: 2.5px; } +#sticked { + display: flex; + justify-content: space-evenly; + width: 100%; +} + #sticked button { - background-color: #997c8900; - padding: 0; - margin-bottom: 2px; - width: 22%; - font-size: 1em; - border: 0; + background-color: transparent; font-weight: bold; + border: 0; } #sticked button:hover { diff --git a/index.html b/index.html index 828c28f2..4ba01d24 100644 --- a/index.html +++ b/index.html @@ -1447,11 +1447,11 @@
- - + + +
-
@@ -3447,36 +3447,66 @@ - +