From 499762f1bbbcd5818f745e2397306c9ae381a437 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 21 Oct 2024 16:37:12 +0200 Subject: [PATCH] refactor: submap - restore rivers --- main.js | 4 +-- modules/resample.js | 33 ++++++++++++------- modules/river-generator.js | 63 +++++++++++++++++++----------------- modules/ui/rivers-creator.js | 26 ++++++++------- modules/ui/rivers-editor.js | 12 +++++-- modules/ui/tools.js | 43 +++++++++++------------- 6 files changed, 100 insertions(+), 81 deletions(-) diff --git a/main.js b/main.js index 5284c5bd..86df7d65 100644 --- a/main.js +++ b/main.js @@ -1177,8 +1177,8 @@ function rankCells() { cells.s = new Int16Array(cells.i.length); // cell suitability array cells.pop = new Float32Array(cells.i.length); // cell population array - const flMean = d3.median(cells.fl.filter(f => f)) || 0, - flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux + const flMean = d3.median(cells.fl.filter(f => f)) || 0; + const flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux const areaMean = d3.mean(cells.area); // to adjust population by cell area for (const i of cells.i) { diff --git a/modules/resample.js b/modules/resample.js index fff67266..22f565af 100644 --- a/modules/resample.js +++ b/modules/resample.js @@ -22,16 +22,13 @@ window.Resample = (function () { OceanLayers(); calculateMapCoordinates(); calculateTemperatures(); - generatePrecipitation(); reGraph(); Features.markupPack(); createDefaultRuler(); - restoreCellData(parentMap, inverse); - restoreRivers(parentMap, projection); - rankCells(); - + restoreCellData(parentMap, inverse, scale); + restoreRivers(parentMap, projection, scale); restoreCultures(parentMap, projection); restoreBurgs(parentMap, projection, scale); restoreStates(parentMap, projection); @@ -71,9 +68,11 @@ window.Resample = (function () { }); } - function restoreCellData(parentMap, inverse) { + function restoreCellData(parentMap, inverse, scale) { pack.cells.biome = new Uint8Array(pack.cells.i.length); pack.cells.fl = new Uint16Array(pack.cells.i.length); + pack.cells.s = new Int16Array(pack.cells.i.length); + pack.cells.pop = new Float32Array(pack.cells.i.length); pack.cells.culture = new Uint16Array(pack.cells.i.length); pack.cells.state = new Uint16Array(pack.cells.i.length); pack.cells.burg = new Uint16Array(pack.cells.i.length); @@ -88,8 +87,14 @@ window.Resample = (function () { if (isWater(pack, newPackCell)) continue; const parentPackCell = parentPackLandCellsQuadtree.find(x, y, Infinity)[2]; + const parentCellArea = parentMap.pack.cells.area[parentPackCell]; + const areaRatio = pack.cells.area[newPackCell] / parentCellArea; + const scaleRatio = areaRatio / scale; + pack.cells.biome[newPackCell] = parentMap.pack.cells.biome[parentPackCell]; pack.cells.fl[newPackCell] = parentMap.pack.cells.fl[parentPackCell]; + pack.cells.s[newPackCell] = parentMap.pack.cells.s[parentPackCell] * scaleRatio; + pack.cells.pop[newPackCell] = parentMap.pack.cells.pop[parentPackCell] * scaleRatio; pack.cells.culture[newPackCell] = parentMap.pack.cells.culture[parentPackCell]; pack.cells.state[newPackCell] = parentMap.pack.cells.state[parentPackCell]; pack.cells.religion[newPackCell] = parentMap.pack.cells.religion[parentPackCell]; @@ -97,9 +102,10 @@ window.Resample = (function () { } } - function restoreRivers(parentMap, projection) { + function restoreRivers(parentMap, projection, scale) { pack.cells.r = new Uint16Array(pack.cells.i.length); pack.cells.conf = new Uint8Array(pack.cells.i.length); + const offset = grid.spacing * 2; pack.rivers = parentMap.pack.rivers .map(river => { @@ -107,7 +113,7 @@ window.Resample = (function () { const points = parentPoints .map(([parentX, parentY]) => { const [x, y] = projection(parentX, parentY); - return isInMap(x, y) ? [rn(x, 2), rn(y, 2)] : null; + return isInMap(x, y, offset) ? [rn(x, 2), rn(y, 2)] : null; }) .filter(Boolean); if (points.length < 2) return null; @@ -118,7 +124,8 @@ window.Resample = (function () { pack.cells.r[cellId] = river.i; }); - return {...river, cells, points, source: cells.at(0), mouth: cells.at(-2)}; + const widthFactor = river.widthFactor * scale; + return {...river, cells, points, source: cells.at(0), mouth: cells.at(-2), widthFactor}; }) .filter(Boolean); @@ -203,12 +210,14 @@ window.Resample = (function () { } function restoreRoutes(parentMap, projection) { + const offset = grid.spacing * 2; + pack.routes = parentMap.pack.routes .map(route => { const points = route.points .map(([parentX, parentY]) => { const [x, y] = projection(parentX, parentY); - if (!isInMap(x, y)) return null; + if (!isInMap(x, y, offset)) return null; const cell = findCell(x, y); return [rn(x, 2), rn(y, 2), cell]; @@ -320,8 +329,8 @@ window.Resample = (function () { return graph.cells.h[cellId] < 20; } - function isInMap(x, y) { - return x >= 0 && x <= graphWidth && y >= 0 && y <= graphHeight; + function isInMap(x, y, offset = 0) { + return x + offset >= 0 && x - offset <= graphWidth && y + offset >= 0 && y - offset <= graphHeight; } return {process}; diff --git a/modules/river-generator.js b/modules/river-generator.js index b7216db0..8ce926fc 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -190,7 +190,15 @@ window.Rivers = (function () { const meanderedPoints = addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second const length = getApproximateLength(meanderedPoints); - const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0)); + const sourceWidth = getSourceWidth(cells.fl[source]); + const width = getWidth( + getOffset({ + flux: discharge, + pointIndex: meanderedPoints.length, + widthFactor, + sourceWidth + }) + ); pack.rivers.push({ i: riverId, @@ -200,7 +208,7 @@ window.Rivers = (function () { length, width, widthFactor, - sourceWidth: 0, + sourceWidth, parent, cells: riverCells }); @@ -306,59 +314,49 @@ window.Rivers = (function () { // add points at 1/3 and 2/3 of a line between adjacents river cells const addMeandering = function (riverCells, riverPoints = null, meandering = 0.5) { - const {fl, conf, h} = pack.cells; + const {fl, h} = pack.cells; const meandered = []; const lastStep = riverCells.length - 1; const points = getRiverPoints(riverCells, riverPoints); let step = h[riverCells[0]] < 20 ? 1 : 10; - let fluxPrev = 0; - const getFlux = (step, flux) => (step === lastStep ? fluxPrev : flux); - for (let i = 0; i <= lastStep; i++, step++) { const cell = riverCells[i]; const isLastCell = i === lastStep; const [x1, y1] = points[i]; - const flux1 = getFlux(i, fl[cell]); - fluxPrev = flux1; - meandered.push([x1, y1, flux1]); + meandered.push([x1, y1, fl[cell]]); if (isLastCell) break; const nextCell = riverCells[i + 1]; const [x2, y2] = points[i + 1]; if (nextCell === -1) { - meandered.push([x2, y2, fluxPrev]); + meandered.push([x2, y2, fl[cell]]); break; } const dist2 = (x2 - x1) ** 2 + (y2 - y1) ** 2; // square distance between cells if (dist2 <= 25 && riverCells.length >= 6) continue; - const flux2 = getFlux(i + 1, fl[nextCell]); - const keepInitialFlux = conf[nextCell] || flux1 === flux2; - const meander = meandering + 1 / step + Math.max(meandering - step / 100, 0); const angle = Math.atan2(y2 - y1, x2 - x1); const sinMeander = Math.sin(angle) * meander; const cosMeander = Math.cos(angle) * meander; - if (step < 10 && (dist2 > 64 || (dist2 > 36 && riverCells.length < 5))) { + if (step < 20 && (dist2 > 64 || (dist2 > 36 && riverCells.length < 5))) { // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment const p1x = (x1 * 2 + x2) / 3 + -sinMeander; const p1y = (y1 * 2 + y2) / 3 + cosMeander; const p2x = (x1 + x2 * 2) / 3 + sinMeander / 2; const p2y = (y1 + y2 * 2) / 3 - cosMeander / 2; - const [p1fl, p2fl] = keepInitialFlux ? [flux1, flux1] : [(flux1 * 2 + flux2) / 3, (flux1 + flux2 * 2) / 3]; - meandered.push([p1x, p1y, p1fl], [p2x, p2y, p2fl]); + meandered.push([p1x, p1y, 0], [p2x, p2y, 0]); } else if (dist2 > 25 || riverCells.length < 6) { // if dist is medium or river is small add 1 extra middlepoint const p1x = (x1 + x2) / 2 + -sinMeander; const p1y = (y1 + y2) / 2 + cosMeander; - const p1fl = keepInitialFlux ? flux1 : (flux1 + flux2) / 2; - meandered.push([p1x, p1y, p1fl]); + meandered.push([p1x, p1y, 0]); } } @@ -385,29 +383,35 @@ window.Rivers = (function () { }; const FLUX_FACTOR = 500; - const MAX_FLUX_WIDTH = 2; + const MAX_FLUX_WIDTH = 1; const LENGTH_FACTOR = 200; - const STEP_WIDTH = 1 / LENGTH_FACTOR; + const LENGTH_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, startingWidth = 0) => { - const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH); - const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION); + const getOffset = ({flux, pointIndex, widthFactor, startingWidth}) => { + if (pointIndex === 0) return startingWidth; + + const fluxWidth = Math.min(flux ** 0.7 / FLUX_FACTOR, MAX_FLUX_WIDTH); + const lengthWidth = pointIndex * LENGTH_STEP_WIDTH + (LENGTH_PROGRESSION[pointIndex] || MAX_PROGRESSION); return widthFactor * (lengthWidth + fluxWidth) + startingWidth; }; + const getSourceWidth = flux => rn(Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH), 2); + // build polygon from a list of points and calculated offset (width) - const getRiverPath = function (points, widthFactor, startingWidth = 0) { + const getRiverPath = (points, widthFactor, startingWidth) => { const riverPointsLeft = []; const riverPointsRight = []; + let flux = 0; - for (let p = 0; p < points.length; p++) { - const [x0, y0] = points[p - 1] || points[p]; - const [x1, y1, flux] = points[p]; - const [x2, y2] = points[p + 1] || points[p]; + for (let pointIndex = 0; pointIndex < points.length; pointIndex++) { + const [x0, y0] = points[pointIndex - 1] || points[pointIndex]; + const [x1, y1, pointFlux] = points[pointIndex]; + const [x2, y2] = points[pointIndex + 1] || points[pointIndex]; + if (pointFlux > flux) flux = pointFlux; - const offset = getOffset(flux, p, widthFactor, startingWidth); + const offset = getOffset({flux, pointIndex, widthFactor, startingWidth}); const angle = Math.atan2(y0 - y2, x0 - x2); const sinOffset = Math.sin(angle) * offset; const cosOffset = Math.cos(angle) * offset; @@ -507,6 +511,7 @@ window.Rivers = (function () { getBasin, getWidth, getOffset, + getSourceWidth, getApproximateLength, getRiverPoints, remove, diff --git a/modules/ui/rivers-creator.js b/modules/ui/rivers-creator.js index a8600917..8c01565a 100644 --- a/modules/ui/rivers-creator.js +++ b/modules/ui/rivers-creator.js @@ -74,13 +74,10 @@ function createRiver() { function addRiver() { const {rivers, cells} = pack; - const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin, getNextId} = - Rivers; - const riverCells = createRiver.cells; if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error"); - const riverId = getNextId(rivers); + const riverId = Rivers.getNextId(rivers); const parent = cells.r[last(riverCells)] || riverId; riverCells.forEach(cell => { @@ -89,17 +86,24 @@ function createRiver() { const source = riverCells[0]; const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2]; - const sourceWidth = 0.05; + const sourceWidth = Rivers.getSourceWidth(cells.fl[source]); const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); const widthFactor = 1.2 * defaultWidthFactor; - const meanderedPoints = addMeandering(riverCells); + const meanderedPoints = Rivers.addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second - const length = getApproximateLength(meanderedPoints); - const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); - const name = getName(mouth); - const basin = getBasin(parent); + const length = Rivers.getApproximateLength(meanderedPoints); + const width = Rivers.getWidth( + Rivers.getOffset({ + flux: discharge, + pointIndex: meanderedPoints.length, + widthFactor, + startingWidth: sourceWidth + }) + ); + const name = Rivers.getName(mouth); + const basin = Rivers.getBasin(parent); rivers.push({ i: riverId, @@ -124,7 +128,7 @@ function createRiver() { .select("#rivers") .append("path") .attr("id", id) - .attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth)); + .attr("d", Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth)); editRiver(id); } diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js index 4a061179..3944fdb6 100644 --- a/modules/ui/rivers-editor.js +++ b/modules/ui/rivers-editor.js @@ -86,10 +86,16 @@ function editRiver(id) { } function updateRiverWidth(river) { - const {addMeandering, getWidth, getOffset} = Rivers; const {cells, discharge, widthFactor, sourceWidth} = river; - const meanderedPoints = addMeandering(cells); - river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); + const meanderedPoints = Rivers.addMeandering(cells); + river.width = Rivers.getWidth( + Rivers.getOffset({ + flux: discharge, + pointIndex: meanderedPoints.length, + widthFactor, + startingWidth: sourceWidth + }) + ); const width = `${rn(river.width * distanceScale, 3)} ${distanceUnitInput.value}`; byId("riverWidth").value = width; diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 8e756e6b..67c4ae4e 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -668,28 +668,15 @@ 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, - getWidth, - getOffset, - getApproximateLength, - getNextId - } = Rivers; const riverCells = []; - let riverId = getNextId(rivers); + let riverId = Rivers.getNextId(rivers); let parent = riverId; const initialFlux = grid.cells.prec[cells.g[i]]; cells.fl[i] = initialFlux; - const h = alterHeights(); - resolveDepressions(h); + const h = Rivers.alterHeights(); + Rivers.resolveDepressions(h); while (i) { cells.r[i] = riverId; @@ -763,11 +750,19 @@ function addRiverOnClick() { 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 sourceWidth = river?.sourceWidth || Rivers.getSourceWidth(cells.fl[source]); + const meanderedPoints = Rivers.addMeandering(riverCells); const discharge = cells.fl[mouth]; // m3 in second - const length = getApproximateLength(meanderedPoints); - const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor)); + const length = Rivers.getApproximateLength(meanderedPoints); + const width = Rivers.getWidth( + Rivers.getOffset({ + flux: discharge, + pointIndex: meanderedPoints.length, + widthFactor, + startingWidth: sourceWidth + }) + ); if (river) { river.source = source; @@ -776,9 +771,9 @@ function addRiverOnClick() { river.width = width; river.cells = riverCells; } else { - const basin = getBasin(parent); - const name = getName(mouth); - const type = getType({i: riverId, length, parent}); + const basin = Rivers.getBasin(parent); + const name = Rivers.getName(mouth); + const type = Rivers.getType({i: riverId, length, parent}); rivers.push({ i: riverId, @@ -788,7 +783,7 @@ function addRiverOnClick() { length, width, widthFactor, - sourceWidth: 0, + sourceWidth, parent, cells: riverCells, basin, @@ -799,7 +794,7 @@ function addRiverOnClick() { // render river lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - const path = getRiverPath(meanderedPoints, widthFactor); + const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth); const id = "river" + riverId; const riversG = viewbox.select("#rivers"); riversG.append("path").attr("id", id).attr("d", path);