diff --git a/main.js b/main.js index cd52efbc..c8580264 100644 --- a/main.js +++ b/main.js @@ -979,7 +979,10 @@ function generatePrecipitation() { prec.selectAll("*").remove(); const {cells, cellsX, cellsY} = grid; cells.prec = new Uint8Array(cells.i.length); // precipitation array - const modifier = precInput.value / 100; // user's input + + const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25; + const precInputModifier = precInput.value / 100; + const modifier = cellsNumberModifier * precInputModifier; const westerly = []; const easterly = []; diff --git a/modules/load.js b/modules/load.js index cf661c74..67596ba9 100644 --- a/modules/load.js +++ b/modules/load.js @@ -828,6 +828,7 @@ function parseLoadedData(data) { // v 1.65 changed rivers data d3.select("#rivers").attr("style", null); // remove style to unhide layer const {cells, rivers} = pack; + const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); for (const river of rivers) { const node = document.getElementById("river" + river.i); @@ -856,7 +857,7 @@ function parseLoadedData(data) { river.points = riverPoints; } - river.widthFactor = 1; + river.widthFactor = defaultWidthFactor; cells.i.forEach(i => { const riverInWater = cells.r[i] && cells.h[i] < 20; diff --git a/modules/river-generator.js b/modules/river-generator.js index 173f5626..0943ea9c 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -23,22 +23,27 @@ window.Rivers = (function () { resolveDepressions(h); drainWater(); defineRivers(); + calculateConfluenceFlux(); Lakes.cleanupLakeData(); - if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one + if (allowErosion) { + cells.h = Uint8Array.from(h); // apply gradient + downcutRivers(); // downcut river beds + } TIME && console.timeEnd("generateRivers"); function drainWater() { const MIN_FLUX_TO_FORM_RIVER = 30; + const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25; + const prec = grid.cells.prec; - const area = pack.cells.area; const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); 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]] / cellsNumberModifier; // 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) : []; @@ -90,6 +95,15 @@ window.Rivers = (function () { // cells is depressed if (h[i] <= h[min]) return; + // debug + // .append("line") + // .attr("x1", pack.cells.p[i][0]) + // .attr("y1", pack.cells.p[i][1]) + // .attr("x2", pack.cells.p[min][0]) + // .attr("y2", pack.cells.p[min][1]) + // .attr("stroke", "#333") + // .attr("stroke-width", 0.2); + if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) { // flux is too small to operate as a river if (h[min] >= 20) cells.fl[min] += cells.fl[i]; @@ -149,6 +163,9 @@ window.Rivers = (function () { cells.conf = new Uint16Array(cells.i.length); pack.rivers = []; + const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); + const mainStemWidthFactor = defaultWidthFactor * 1.2; + for (const key in riversData) { const riverCells = riversData[key]; if (riverCells.length < 3) continue; // exclude tiny rivers @@ -166,7 +183,7 @@ window.Rivers = (function () { const mouth = riverCells[riverCells.length - 2]; const parent = riverParents[key] || 0; - const widthFactor = !parent || parent === riverId ? 1.2 : 1; + const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor; const meanderedPoints = addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second const length = getApproximateLength(meanderedPoints); @@ -176,6 +193,22 @@ window.Rivers = (function () { } } + function downcutRivers() { + const MAX_DOWNCUT = 5; + + for (const i of pack.cells.i) { + if (cells.h[i] < 35) continue; // don't donwcut lowlands + if (!cells.fl[i]) continue; + + const higherCells = cells.c[i].filter(c => cells.h[c] > cells.h[i]); + const higherFlux = higherCells.reduce((acc, c) => acc + cells.fl[c], 0) / higherCells.length; + if (!higherFlux) continue; + + const downcut = Math.floor(cells.fl[i] / higherFlux); + if (downcut) cells.h[i] -= Math.min(downcut, MAX_DOWNCUT); + } + } + function calculateConfluenceFlux() { for (const i of cells.i) { if (!cells.conf[i]) continue; @@ -344,14 +377,14 @@ window.Rivers = (function () { const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR); const MAX_PROGRESSION = last(LENGTH_PROGRESSION); - const getOffset = (flux, pointNumber, widthFactor = 1, startingWidth = 0) => { + const getOffset = (flux, pointNumber, widthFactor, startingWidth = 0) => { const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH); const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION); return widthFactor * (lengthWidth + fluxWidth) + startingWidth; }; // build polygon from a list of points and calculated offset (width) - const getRiverPath = function (points, widthFactor = 1, startingWidth = 0) { + const getRiverPath = function (points, widthFactor, startingWidth = 0) { const riverPointsLeft = []; const riverPointsRight = []; @@ -444,5 +477,20 @@ window.Rivers = (function () { return getBasin(parent); }; - return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, getWidth, getOffset, getApproximateLength, getRiverPoints, remove}; + return { + generate, + alterHeights, + resolveDepressions, + addMeandering, + getRiverPath, + specify, + getName, + getType, + getBasin, + getWidth, + getOffset, + getApproximateLength, + getRiverPoints, + remove + }; })(); diff --git a/modules/ui/layers.js b/modules/ui/layers.js index baae4c88..a258b241 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -470,23 +470,28 @@ function togglePrec(event) { function drawPrec() { prec.selectAll("circle").remove(); - const cells = grid.cells, - p = grid.points; + const {cells, points} = grid; prec.style("display", "block"); const show = d3.transition().duration(800).ease(d3.easeSinIn); prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1); + const cellsNumberModifier = pointsInput.dataset.cells / 10000; const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]); + const getRadius = prec => { + const base = prec / cellsNumberModifier / 2; + return rn(Math.sqrt(base), 2); + }; + prec .selectAll("circle") .data(data) .enter() .append("circle") - .attr("cx", d => p[d][0]) - .attr("cy", d => p[d][1]) + .attr("cx", d => points[d][0]) + .attr("cy", d => points[d][1]) .attr("r", 0) .transition(show) - .attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * 0.5), 0.8), 2)); + .attr("r", d => getRadius(cells.prec[d])); } function togglePopulation(event) { diff --git a/modules/ui/rivers-creator.js b/modules/ui/rivers-creator.js index df6cd3e9..83a4d1b9 100644 --- a/modules/ui/rivers-creator.js +++ b/modules/ui/rivers-creator.js @@ -89,7 +89,8 @@ function createRiver() { const source = riverCells[0]; const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2]; const sourceWidth = 0.05; - const widthFactor = 1.2; + const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); + const widthFactor = 1.2 * defaultWidthFactor; const meanderedPoints = addMeandering(riverCells); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index a7d413f1..162cf202 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -624,7 +624,9 @@ function addRiverOnClick() { const source = riverCells[0]; const mouth = riverCells[riverCells.length - 2]; - const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1); + + const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); + const widthFactor = river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor); const meanderedPoints = addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second