From dbcf46f83bd9a5a1b79eb07104000e5ea5a4b2b9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 24 Jul 2021 18:42:10 +0300 Subject: [PATCH] separete rivere rendering from generation --- main.js | 1 + modules/load.js | 13 ++++---- modules/river-generator.js | 55 ++++++++++++++++------------------ modules/ui/heightmap-editor.js | 1 + modules/ui/layers.js | 43 +++++++++++++------------- modules/ui/tools.js | 26 ++++++++-------- 6 files changed, 71 insertions(+), 68 deletions(-) diff --git a/main.js b/main.js index c0a96eb2..07183d9e 100644 --- a/main.js +++ b/main.js @@ -624,6 +624,7 @@ function generate() { drawCoastline(); Rivers.generate(); + drawRivers(); Lakes.defineGroup(); defineBiomes(); diff --git a/modules/load.js b/modules/load.js index a8a33ac0..cb02d6e9 100644 --- a/modules/load.js +++ b/modules/load.js @@ -271,12 +271,13 @@ function parseLoadedData(data) { } })(); - const notHidden = selection => selection.node() && selection.style("display") !== "none"; - const hasChildren = selection => selection.node()?.hasChildNodes(); - const hasChild = (selection, selector) => selection.node()?.querySelector(selector); - const turnOn = el => document.getElementById(el).classList.remove("buttonoff"); - void (function restoreLayersState() { + // helper functions + const notHidden = selection => selection.node() && selection.style("display") !== "none"; + const hasChildren = selection => selection.node()?.hasChildNodes(); + const hasChild = (selection, selector) => selection.node()?.querySelector(selector); + const turnOn = el => document.getElementById(el).classList.remove("buttonoff"); + // turn all layers off document .getElementById("mapLayers") @@ -291,7 +292,7 @@ function parseLoadedData(data) { if (hasChildren(gridOverlay)) turnOn("toggleGrid"); if (hasChildren(coordinates)) turnOn("toggleCoordinates"); if (notHidden(compass) && hasChild(compass, "use")) turnOn("toggleCompass"); - if (notHidden(rivers)) turnOn("toggleRivers"); + if (hasChildren(rivers)) turnOn("toggleRivers"); if (notHidden(terrain) && hasChildren(terrain)) turnOn("toggleRelief"); if (hasChildren(relig)) turnOn("toggleReligions"); if (hasChildren(cults)) turnOn("toggleCultures"); diff --git a/modules/river-generator.js b/modules/river-generator.js index a1b2bff1..3613820d 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -24,7 +24,6 @@ Lakes.prepareLakeData(h); resolveDepressions(h); drainWater(); - lineGen.curve(d3.curveCatmullRom.alpha(0.1)); defineRivers(); calculateConfluenceFlux(); Lakes.cleanupLakeData(); @@ -150,7 +149,6 @@ cells.r = new Uint16Array(cells.i.length); cells.conf = new Uint16Array(cells.i.length); pack.rivers = []; - const riverPaths = []; for (const key in riversData) { const riverCells = riversData[key]; @@ -170,20 +168,13 @@ const parent = riverParents[key] || 0; const widthFactor = !parent || parent === riverId ? 1.2 : 1; - const riverMeandered = addMeandering(riverCells); - const [path, length, offset] = getRiverPath(riverMeandered, widthFactor); - riverPaths.push([path, riverId]); - - // 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 - const width = rn((offset / 1.5) ** 1.8, 2); // mouth width in km + const meanderedPoints = addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second + const length = getApproximateLength(meanderedPoints); + const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor)); pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells}); } - - // draw rivers - rivers.html(riverPaths.map(d => ``).join("")); } function calculateConfluenceFlux() { @@ -346,18 +337,21 @@ return [graphWidth, y]; }; - const fluxFactor = 500; - const maxFluxWidth = 2; - const lengthFactor = 200; - const stepWidth = 1 / lengthFactor; - const lengthProgression = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / lengthFactor); - const maxProgression = last(lengthProgression); + const FLUX_FACTOR = 500; + const MAX_FLUX_WIDTH = 2; + const LENGTH_FACTOR = 200; + const STEP_WIDTH = 1 / LENGTH_FACTOR; + 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 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 riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); - let width = 0; - const riverPointsLeft = []; const riverPointsRight = []; @@ -366,13 +360,10 @@ const [x1, y1, flux] = points[p]; const [x2, y2] = points[p + 1] || points[p]; - const fluxWidth = Math.min(flux ** 0.9 / fluxFactor, maxFluxWidth); - const lengthWidth = p * stepWidth + (lengthProgression[p] || maxProgression); - width = widthFactor * (lengthWidth + fluxWidth) + startingWidth; - + const offset = getOffset(flux, p, widthFactor, startingWidth); const angle = Math.atan2(y0 - y2, x0 - x2); - const sinOffset = Math.sin(angle) * width; - const cosOffset = Math.cos(angle) * width; + const sinOffset = Math.sin(angle) * offset; + const cosOffset = Math.cos(angle) * offset; riverPointsLeft.push([x1 - sinOffset, y1 + cosOffset]); riverPointsRight.push([x1 + sinOffset, y1 - cosOffset]); @@ -382,7 +373,7 @@ let left = lineGen(riverPointsLeft); left = left.substring(left.indexOf("C")); - return [round(right + left, 2), rn(riverLength, 2), width]; + return round(right + left, 1); }; const specify = function () { @@ -424,6 +415,12 @@ 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); + + // 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 + const getWidth = offset => rn((offset / 1.5) ** 1.8, 2); // mouth width in km + // remove river and all its tributaries const remove = function (id) { const cells = pack.cells; @@ -444,5 +441,5 @@ return getBasin(parent); }; - return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, remove}; + return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, getWidth, getOffset, getApproximateLength, remove}; }); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 4cac39f3..f19e8cf0 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -197,6 +197,7 @@ function editHeightmap() { } } + drawRivers(); Lakes.defineGroup(); defineBiomes(); rankCells(); diff --git a/modules/ui/layers.js b/modules/ui/layers.js index 2063721d..46beb692 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -875,7 +875,6 @@ function toggleStates(event) { } } -// draw states function drawStates() { TIME && console.time("drawStates"); regions.selectAll("path").remove(); @@ -1015,6 +1014,21 @@ function drawStates() { TIME && console.timeEnd("drawStates"); } +function toggleBorders(event) { + if (!layerIsOn("toggleBorders")) { + turnButtonOn("toggleBorders"); + drawBorders(); + if (event && isCtrlClick(event)) editStyle("borders"); + } else { + if (event && isCtrlClick(event)) { + editStyle("borders"); + return; + } + turnButtonOff("toggleBorders"); + borders.selectAll("path").remove(); + } +} + // draw state and province borders function drawBorders() { TIME && console.time("drawBorders"); @@ -1118,21 +1132,6 @@ function drawBorders() { TIME && console.timeEnd("drawBorders"); } -function toggleBorders(event) { - if (!layerIsOn("toggleBorders")) { - turnButtonOn("toggleBorders"); - $("#borders").fadeIn(); - if (event && isCtrlClick(event)) editStyle("borders"); - } else { - if (event && isCtrlClick(event)) { - editStyle("borders"); - return; - } - turnButtonOff("toggleBorders"); - $("#borders").fadeOut(); - } -} - function toggleProvinces(event) { if (!layerIsOn("toggleProvinces")) { turnButtonOn("toggleProvinces"); @@ -1454,14 +1453,18 @@ function toggleRivers(event) { } function drawRivers() { + TIME && console.time("drawRivers"); + const {addMeandering, getRiverPath} = Rivers; + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); const riverPaths = pack.rivers.map(river => { - const riverMeandered = Rivers.addMeandering(river.cells, 0.5, river.points); + const riverMeandered = addMeandering(river.cells, 0.5, river.points); const widthFactor = river.widthFactor || 1; const startingWidth = river.startingWidth || 0; - const [path] = Rivers.getRiverPath(riverMeandered, widthFactor, startingWidth); - return [path, river.i]; + const path = getRiverPath(riverMeandered, widthFactor, startingWidth); + return ``; }); - rivers.html(riverPaths.map(d => ``).join("")); + rivers.html(riverPaths.join("")); + TIME && console.timeEnd("drawRivers"); } function toggleRoutes(event) { diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 4ac393c6..01de28f4 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -538,7 +538,7 @@ function addRiverOnClick() { if (cells.h[i] < 20) return tip("Cannot create river in water cell", false, "error"); if (cells.b[i]) return; - const {alterHeights, resolveDepressions, addMeandering, getRiverPath, getBasin, getName, getType} = Rivers; + const {alterHeights, resolveDepressions, addMeandering, getRiverPath, getBasin, getName, getType, getWidth, getOffset, getApproximateLength} = Rivers; const riverCells = []; let riverId = last(rivers).id + 1; let parent = 0; @@ -614,22 +614,15 @@ function addRiverOnClick() { } const river = rivers.find(r => r.i === riverId); - const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1); - const riverMeandered = addMeandering(riverCells); - lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - const [path, length, offset] = getRiverPath(riverMeandered, widthFactor); - viewbox - .select("#rivers") - .append("path") - .attr("d", path) - .attr("id", "river" + riverId); - - // add new river to data or change extended river attributes const source = riverCells[0]; const mouth = riverCells[riverCells.length - 2]; + const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1); + const riverMeandered = addMeandering(riverCells); + const discharge = cells.fl[mouth]; // m3 in second - const width = rn((offset / 1.4) ** 2, 2); // mounth width in km + const length = getApproximateLength(riverMeandered); + const width = getWidth(getOffset(discharge, riverMeandered.length, widthFactor)); if (river) { river.source = source; @@ -645,6 +638,13 @@ function addRiverOnClick() { rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells, basin, name, type}); } + // render river + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + const path = getRiverPath(riverMeandered, widthFactor); + const id = "river" + riverId; + const riversG = viewbox.select("#rivers"); + riversG.append("path").attr("d", path).attr("id", id); + if (d3.event.shiftKey === false) { Lakes.cleanupLakeData(); unpressClickToAddButton();