@@ -4037,6 +4053,7 @@
+
diff --git a/main.js b/main.js
index 102f090a..eb086ba8 100644
--- a/main.js
+++ b/main.js
@@ -547,7 +547,7 @@ function generate() {
drawCoastline();
Rivers.generate();
- defineLakesGroup();
+ Lakes.defineGroup();
defineBiomes();
rankCells();
@@ -564,6 +564,7 @@ function generate() {
BurgsAndStates.drawStateLabels();
Rivers.specify();
+ Lakes.generateName();
Military.generate();
addMarkers();
@@ -1120,31 +1121,6 @@ function reMarkFeatures() {
TIME && console.timeEnd("reMarkFeatures");
}
-function defineLakesGroup() {
- for (const feature of pack.features) {
- if (feature.type !== "lake") continue;
- const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
- if (!lakeEl) continue;
-
- feature.group = defineGroup(feature);
- document.getElementById(feature.group).appendChild(lakeEl);
- }
-
- function defineGroup(feature) {
- if (feature.temp < -3) return "frozen";
- if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava";
-
- if (!feature.inlets && !feature.outlet) {
- if (feature.evaporation / 2 > feature.flux) return "dry";
- if (feature.cells < 3 && feature.firstCell % 5 === 0) return "sinkhole";
- }
-
- if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
-
- return "freshwater";
- }
-}
-
// assign biome id for each cell
function defineBiomes() {
TIME && console.time("defineBiomes");
diff --git a/modules/lakes.js b/modules/lakes.js
new file mode 100644
index 00000000..01b26a43
--- /dev/null
+++ b/modules/lakes.js
@@ -0,0 +1,89 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Lakes = factory());
+}(this, (function () {'use strict';
+
+const setClimateData = function(h) {
+ const cells = pack.cells;
+ const lakeOutCells = new Uint16Array(cells.i.length);
+
+ pack.features.forEach(f => {
+ if (f.type !== "lake") return;
+
+ // default flux: sum of precipition around lake first cell
+ f.flux = rn(d3.sum(f.shoreline.map(c => grid.cells.prec[cells.g[c]])) / 2);
+
+ // temperature and evaporation to detect closed lakes
+ f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
+ const height = (f.height - 18) ** heightExponentInput.value; // height in meters
+ const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
+ f.evaporation = rn(evaporation * f.cells);
+
+ // lake outlet cell
+ f.outCell = f.shoreline[d3.scan(f.shoreline, (a,b) => h[a] - h[b])];
+ lakeOutCells[f.outCell] = f.i;
+ });
+
+ return lakeOutCells;
+}
+
+const cleanupLakeData = function() {
+ for (const feature of pack.features) {
+ if (feature.type !== "lake") continue;
+ delete feature.river;
+ delete feature.enteringFlux;
+ delete feature.shoreline;
+ delete feature.outCell;
+ feature.height = rn(feature.height);
+
+ const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r));
+ if (!inlets || !inlets.length) delete feature.inlets;
+ else feature.inlets = inlets;
+
+ const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet);
+ if (!outlet) delete feature.outlet;
+ }
+}
+
+const defineGroup = function() {
+ for (const feature of pack.features) {
+ if (feature.type !== "lake") continue;
+ const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
+ if (!lakeEl) continue;
+
+ feature.group = getGroup(feature);
+ document.getElementById(feature.group).appendChild(lakeEl);
+ }
+}
+
+const generateName = function() {
+ for (const feature of pack.features) {
+ if (feature.type !== "lake") continue;
+ feature.name = getName(feature);
+ }
+}
+
+const getName = function(feature) {
+ const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20);
+ const culture = pack.cells.culture[landCell];
+ return Names.getCulture(culture);
+}
+
+function getGroup(feature) {
+ if (feature.temp < -3) return "frozen";
+ if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava";
+
+ if (!feature.inlets && !feature.outlet) {
+ if (feature.evaporation / 2 > feature.flux) return "dry";
+ if (feature.cells < 3 && feature.firstCell % 5 === 0) return "sinkhole";
+ }
+
+ if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
+
+ return "freshwater";
+}
+
+return {setClimateData, cleanupLakeData, defineGroup, generateName, getName};
+
+})));
\ No newline at end of file
diff --git a/modules/river-generator.js b/modules/river-generator.js
index 0a6e5fd9..2a2e3776 100644
--- a/modules/river-generator.js
+++ b/modules/river-generator.js
@@ -21,7 +21,7 @@ const generate = function(changeHeights = true) {
resolveDepressions(h);
drainWater();
defineRivers();
- cleanupLakeData();
+ Lakes.cleanupLakeData();
if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one
@@ -57,25 +57,7 @@ const generate = function(changeHeights = true) {
function drainWater() {
const MIN_FLUX_TO_FORM_RIVER = 30;
const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]);
-
- const lakeOutCells = new Uint16Array(cells.i.length); // to enumerate lake outlet positions
- features.forEach(f => {
- if (f.type !== "lake") return;
- const gridCell = cells.g[f.firstCell];
-
- // lake possible outlet: cell around with min height
- f.outCell = f.shoreline[d3.scan(f.shoreline, (a,b) => h[a] - h[b])];
- lakeOutCells[f.outCell] = f.i;
-
- // default flux: sum of precipition around lake first cell
- f.flux = rn(d3.sum(f.shoreline.map(c => grid.cells.prec[cells.g[c]])) / 2);
-
- // temperature and evaporation to detect closed lakes
- f.temp = f.cells < 6 ? grid.cells.temp[gridCell] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
- const height = (f.height - 18) ** heightExponentInput.value; // height in meters
- const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
- f.evaporation = rn(evaporation * f.cells);
- });
+ const lakeOutCells = Lakes.setClimateData(h);
land.forEach(function(i) {
cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
@@ -208,24 +190,6 @@ const generate = function(changeHeights = true) {
// draw rivers
rivers.html(riverPaths.map(d => `
`).join(""));
}
-
- function cleanupLakeData() {
- for (const feature of features) {
- if (feature.type !== "lake") continue;
- delete feature.river;
- delete feature.enteringFlux;
- delete feature.shoreline;
- delete feature.outCell;
- feature.height = rn(feature.height);
-
- const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r));
- if (!inlets || !inlets.length) delete feature.inlets;
- else feature.inlets = inlets;
-
- const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet);
- if (!outlet) delete feature.outlet;
- }
- }
}
// depression filling algorithm (for a correct water flux modeling)
@@ -238,7 +202,6 @@ const resolveDepressions = function(h) {
const uniqueCells = new Set();
l.vertices.forEach(v => pack.vertices.c[v].forEach(c => cells.h[c] >= 20 && uniqueCells.add(c)));
l.shoreline = [...uniqueCells];
- l.height = 21;
});
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
diff --git a/modules/ui/general.js b/modules/ui/general.js
index f701d1bb..063e37fe 100644
--- a/modules/ui/general.js
+++ b/modules/ui/general.js
@@ -145,7 +145,12 @@ function showMapTooltip(point, e, i, g) {
}
if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;}
if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;}
- if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
+ if (group === "lakes" && !land) {
+ const lakeId = +e.target.dataset.f;
+ const name = pack.features[lakeId]?.name;
+ const fullName = subgroup === "freshwater" ? name : name + " " + subgroup;
+ tip(`${fullName} lake. Click to edit`); return;
+ }
if (group === "coastline") {tip("Click to edit the coastline"); return;}
if (group === "zones") {
const zone = path[path.length-8];
diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js
index 6b9f77f5..db83a8b4 100644
--- a/modules/ui/heightmap-editor.js
+++ b/modules/ui/heightmap-editor.js
@@ -185,7 +185,7 @@ function editHeightmap() {
}
}
- defineLakesGroup();
+ Lakes.defineGroup();
defineBiomes();
rankCells();
Cultures.generate();
@@ -201,6 +201,8 @@ function editHeightmap() {
BurgsAndStates.drawStateLabels();
Rivers.specify();
+ Lakes.generateName();
+
Military.generate();
addMarkers();
addZones();
diff --git a/modules/ui/lakes-editor.js b/modules/ui/lakes-editor.js
index c0172ea3..4b5c6701 100644
--- a/modules/ui/lakes-editor.js
+++ b/modules/ui/lakes-editor.js
@@ -13,6 +13,7 @@ function editLake() {
const node = d3.event.target;
debug.append("g").attr("id", "vertices");
elSelected = d3.select(node);
+ updateLakeValues();
selectLakeGroup(node);
drawLakeVertices();
viewbox.on("touchmove mousemove", null);
@@ -21,19 +22,34 @@ function editLake() {
modules.editLake = true;
// add listeners
- document.getElementById("lakeGroupsShow").addEventListener("click", showGroupSection);
+ document.getElementById("lakeName").addEventListener("input", changeName);
+ document.getElementById("lakeNameCulture").addEventListener("click", generateNameCulture);
+ document.getElementById("lakeNameRandom").addEventListener("click", generateNameRandom);
+
document.getElementById("lakeGroup").addEventListener("change", changeLakeGroup);
document.getElementById("lakeGroupAdd").addEventListener("click", toggleNewGroupInput);
document.getElementById("lakeGroupName").addEventListener("change", createNewGroup);
document.getElementById("lakeGroupRemove").addEventListener("click", removeLakeGroup);
- document.getElementById("lakeGroupsHide").addEventListener("click", hideGroupSection);
document.getElementById("lakeEditStyle").addEventListener("click", editGroupStyle);
document.getElementById("lakeLegend").addEventListener("click", editLakeLegend);
+ function getLake() {
+ const lakeId = +elSelected.attr("data-f");
+ return pack.features.find(feature => feature.i === lakeId);
+ }
+
+ function updateLakeValues() {
+ const l = getLake();
+ document.getElementById("lakeName").value = l.name;
+ document.getElementById("lakeGroup").value = l.type;
+
+ const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
+ document.getElementById("lakeArea").value = si(l.area * distanceScaleInput.value ** 2) + unit;
+ }
+
function drawLakeVertices() {
- const f = +elSelected.attr("data-f"); // feature id
- const v = pack.features[f].vertices; // lake outer vertices
+ const v = getLake().vertices; // lake outer vertices
const c = [... new Set(v.map(v => pack.vertices.c[v]).flat())];
debug.select("#vertices").selectAll("polygon").data(c).enter().append("polygon")
@@ -43,10 +59,6 @@ function editLake() {
.attr("cx", d => pack.vertices.p[d][0]).attr("cy", d => pack.vertices.p[d][1])
.attr("r", .4).attr("data-v", d => d).call(d3.drag().on("drag", dragVertex))
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights"));
-
- const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
- const area = pack.features[f].area;
- lakeArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
}
function dragVertex() {
@@ -61,29 +73,29 @@ function editLake() {
function redrawLake() {
lineGen.curve(d3.curveBasisClosed);
- const f = +elSelected.attr("data-f");
- const vertices = pack.features[f].vertices;
- const points = vertices.map(v => pack.vertices.p[v]);
+ const feature = getLake();
+ const points = feature.vertices.map(v => pack.vertices.p[v]);
const d = round(lineGen(points));
elSelected.attr("d", d);
- defs.select("mask#land > path#land_"+f).attr("d", d); // update land mask
+ defs.select("mask#land > path#land_"+feature.i).attr("d", d); // update land mask
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
- const area = Math.abs(d3.polygonArea(points));
- lakeArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
+ feature.area = Math.abs(d3.polygonArea(points));
+ document.getElementById("lakeArea").value = si(feature.area * distanceScaleInput.value ** 2) + unit;
}
- function showGroupSection() {
- document.querySelectorAll("#lakeEditor > button").forEach(el => el.style.display = "none");
- document.getElementById("lakeGroupsSelection").style.display = "inline-block";
+ function changeName() {
+ getLake().name = this.value;
}
- function hideGroupSection() {
- document.querySelectorAll("#lakeEditor > button").forEach(el => el.style.display = "inline-block");
- document.getElementById("lakeGroupsSelection").style.display = "none";
- document.getElementById("lakeGroupName").style.display = "none";
- document.getElementById("lakeGroupName").value = "";
- document.getElementById("lakeGroup").style.display = "inline-block";
+ function generateNameCulture() {
+ const lake = getLake();
+ lake.name = lakeName.value = Lakes.getName(lake);
+ }
+
+ function generateNameRandom() {
+ const lake = getLake();
+ lake.name = lakeName.value = Names.getBase(rand(nameBases.length-1));
}
function selectLakeGroup(node) {
diff --git a/modules/ui/options.js b/modules/ui/options.js
index 526921db..56f633be 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -421,7 +421,6 @@ function applyStoredOptions() {
const height = +params.get("height");
if (width) mapWidthInput.value = width;
if (height) mapHeightInput.value = height;
- //window.history.pushState({}, null, "?");
}
// randomize options if randomization is allowed (not locked or options='default')
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index b57c22ab..a0594374 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -95,7 +95,7 @@ async function openEmblemEditor() {
function regenerateRivers() {
Rivers.generate();
- defineLakesGroup();
+ Lakes.defineGroup();
Rivers.specify();
if (!layerIsOn("toggleRivers")) toggleRivers();
}
diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js
index 5a472182..8e7f255d 100644
--- a/modules/ui/world-configurator.js
+++ b/modules/ui/world-configurator.js
@@ -47,7 +47,7 @@ function editWorld() {
generatePrecipitation();
const heights = new Uint8Array(pack.cells.h);
Rivers.generate();
- defineLakesGroup();
+ Lakes.defineGroup();
Rivers.specify();
pack.cells.h = new Float32Array(heights);
defineBiomes();