window.Lakes = (function () { 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 precipitation around lake f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0); // 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 + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11] f.evaporation = rn(evaporation * f.cells); // no outlet for lakes in depressed areas if (f.closed) return; // lake outlet cell f.outCell = f.shoreline[d3.scan(f.shoreline, (a, b) => h[a] - h[b])]; lakeOutCells[f.outCell] = f.i; }); return lakeOutCells; }; // get array of land cells aroound lake const getShoreline = function (lake) { const uniqueCells = new Set(); lake.vertices.forEach(v => pack.vertices.c[v].forEach(c => pack.cells.h[c] >= 20 && uniqueCells.add(c))); lake.shoreline = [...uniqueCells]; }; const prepareLakeData = h => { const cells = pack.cells; const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; pack.features.forEach(f => { if (f.type !== "lake") return; delete f.flux; delete f.inlets; delete f.outlet; delete f.height; delete f.closed; !f.shoreline && Lakes.getShoreline(f); // lake surface height is as lowest land cells around const min = f.shoreline.sort((a, b) => h[a] - h[b])[0]; f.height = h[min] - 0.1; // check if lake can be open (not in deep depression) if (ELEVATION_LIMIT === 80) { f.closed = false; return; } let deep = true; const threshold = f.height + ELEVATION_LIMIT; const queue = [min]; const checked = []; checked[min] = true; // check if elevated lake can potentially pour to another water body while (deep && queue.length) { const q = queue.pop(); for (const n of cells.c[q]) { if (checked[n]) continue; if (h[n] >= threshold) continue; if (h[n] < 20) { const nFeature = pack.features[cells.f[n]]; if (nFeature.type === "ocean" || f.height > nFeature.height) { deep = false; break; } } checked[n] = true; queue.push(n); } } f.closed = deep; }); }; const cleanupLakeData = function () { for (const feature of pack.features) { if (feature.type !== "lake") continue; delete feature.river; delete feature.enteringFlux; delete feature.outCell; delete feature.closed; feature.height = rn(feature.height, 3); 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 () { Math.random = aleaPRNG(seed); 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 % 10 === 0) return "lava"; if (!feature.inlets && !feature.outlet) { if (feature.evaporation > feature.flux * 4) return "dry"; if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole"; } if (!feature.outlet && feature.evaporation > feature.flux) return "salt"; return "freshwater"; } return {setClimateData, cleanupLakeData, prepareLakeData, defineGroup, generateName, getName, getShoreline}; })();