refactor: submap - restore rivers

This commit is contained in:
Azgaar 2024-10-21 16:37:12 +02:00
parent 1d9ba6f17c
commit 499762f1bb
6 changed files with 100 additions and 81 deletions

View file

@ -1177,8 +1177,8 @@ function rankCells() {
cells.s = new Int16Array(cells.i.length); // cell suitability array cells.s = new Int16Array(cells.i.length); // cell suitability array
cells.pop = new Float32Array(cells.i.length); // cell population array cells.pop = new Float32Array(cells.i.length); // cell population array
const flMean = d3.median(cells.fl.filter(f => f)) || 0, const flMean = d3.median(cells.fl.filter(f => f)) || 0;
flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux 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 const areaMean = d3.mean(cells.area); // to adjust population by cell area
for (const i of cells.i) { for (const i of cells.i) {

View file

@ -22,16 +22,13 @@ window.Resample = (function () {
OceanLayers(); OceanLayers();
calculateMapCoordinates(); calculateMapCoordinates();
calculateTemperatures(); calculateTemperatures();
generatePrecipitation();
reGraph(); reGraph();
Features.markupPack(); Features.markupPack();
createDefaultRuler(); createDefaultRuler();
restoreCellData(parentMap, inverse); restoreCellData(parentMap, inverse, scale);
restoreRivers(parentMap, projection); restoreRivers(parentMap, projection, scale);
rankCells();
restoreCultures(parentMap, projection); restoreCultures(parentMap, projection);
restoreBurgs(parentMap, projection, scale); restoreBurgs(parentMap, projection, scale);
restoreStates(parentMap, projection); 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.biome = new Uint8Array(pack.cells.i.length);
pack.cells.fl = new Uint16Array(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.culture = new Uint16Array(pack.cells.i.length);
pack.cells.state = new Uint16Array(pack.cells.i.length); pack.cells.state = new Uint16Array(pack.cells.i.length);
pack.cells.burg = 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; if (isWater(pack, newPackCell)) continue;
const parentPackCell = parentPackLandCellsQuadtree.find(x, y, Infinity)[2]; 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.biome[newPackCell] = parentMap.pack.cells.biome[parentPackCell];
pack.cells.fl[newPackCell] = parentMap.pack.cells.fl[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.culture[newPackCell] = parentMap.pack.cells.culture[parentPackCell];
pack.cells.state[newPackCell] = parentMap.pack.cells.state[parentPackCell]; pack.cells.state[newPackCell] = parentMap.pack.cells.state[parentPackCell];
pack.cells.religion[newPackCell] = parentMap.pack.cells.religion[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.r = new Uint16Array(pack.cells.i.length);
pack.cells.conf = new Uint8Array(pack.cells.i.length); pack.cells.conf = new Uint8Array(pack.cells.i.length);
const offset = grid.spacing * 2;
pack.rivers = parentMap.pack.rivers pack.rivers = parentMap.pack.rivers
.map(river => { .map(river => {
@ -107,7 +113,7 @@ window.Resample = (function () {
const points = parentPoints const points = parentPoints
.map(([parentX, parentY]) => { .map(([parentX, parentY]) => {
const [x, y] = projection(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); .filter(Boolean);
if (points.length < 2) return null; if (points.length < 2) return null;
@ -118,7 +124,8 @@ window.Resample = (function () {
pack.cells.r[cellId] = river.i; 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); .filter(Boolean);
@ -203,12 +210,14 @@ window.Resample = (function () {
} }
function restoreRoutes(parentMap, projection) { function restoreRoutes(parentMap, projection) {
const offset = grid.spacing * 2;
pack.routes = parentMap.pack.routes pack.routes = parentMap.pack.routes
.map(route => { .map(route => {
const points = route.points const points = route.points
.map(([parentX, parentY]) => { .map(([parentX, parentY]) => {
const [x, y] = projection(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); const cell = findCell(x, y);
return [rn(x, 2), rn(y, 2), cell]; return [rn(x, 2), rn(y, 2), cell];
@ -320,8 +329,8 @@ window.Resample = (function () {
return graph.cells.h[cellId] < 20; return graph.cells.h[cellId] < 20;
} }
function isInMap(x, y) { function isInMap(x, y, offset = 0) {
return x >= 0 && x <= graphWidth && y >= 0 && y <= graphHeight; return x + offset >= 0 && x - offset <= graphWidth && y + offset >= 0 && y - offset <= graphHeight;
} }
return {process}; return {process};

View file

@ -190,7 +190,15 @@ window.Rivers = (function () {
const meanderedPoints = addMeandering(riverCells); const meanderedPoints = addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second const discharge = cells.fl[mouth]; // m3 in second
const length = getApproximateLength(meanderedPoints); 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({ pack.rivers.push({
i: riverId, i: riverId,
@ -200,7 +208,7 @@ window.Rivers = (function () {
length, length,
width, width,
widthFactor, widthFactor,
sourceWidth: 0, sourceWidth,
parent, parent,
cells: riverCells cells: riverCells
}); });
@ -306,59 +314,49 @@ window.Rivers = (function () {
// add points at 1/3 and 2/3 of a line between adjacents river cells // 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 addMeandering = function (riverCells, riverPoints = null, meandering = 0.5) {
const {fl, conf, h} = pack.cells; const {fl, h} = pack.cells;
const meandered = []; const meandered = [];
const lastStep = riverCells.length - 1; const lastStep = riverCells.length - 1;
const points = getRiverPoints(riverCells, riverPoints); const points = getRiverPoints(riverCells, riverPoints);
let step = h[riverCells[0]] < 20 ? 1 : 10; 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++) { for (let i = 0; i <= lastStep; i++, step++) {
const cell = riverCells[i]; const cell = riverCells[i];
const isLastCell = i === lastStep; const isLastCell = i === lastStep;
const [x1, y1] = points[i]; 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; if (isLastCell) break;
const nextCell = riverCells[i + 1]; const nextCell = riverCells[i + 1];
const [x2, y2] = points[i + 1]; const [x2, y2] = points[i + 1];
if (nextCell === -1) { if (nextCell === -1) {
meandered.push([x2, y2, fluxPrev]); meandered.push([x2, y2, fl[cell]]);
break; break;
} }
const dist2 = (x2 - x1) ** 2 + (y2 - y1) ** 2; // square distance between cells const dist2 = (x2 - x1) ** 2 + (y2 - y1) ** 2; // square distance between cells
if (dist2 <= 25 && riverCells.length >= 6) continue; 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 meander = meandering + 1 / step + Math.max(meandering - step / 100, 0);
const angle = Math.atan2(y2 - y1, x2 - x1); const angle = Math.atan2(y2 - y1, x2 - x1);
const sinMeander = Math.sin(angle) * meander; const sinMeander = Math.sin(angle) * meander;
const cosMeander = Math.cos(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 // 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 p1x = (x1 * 2 + x2) / 3 + -sinMeander;
const p1y = (y1 * 2 + y2) / 3 + cosMeander; const p1y = (y1 * 2 + y2) / 3 + cosMeander;
const p2x = (x1 + x2 * 2) / 3 + sinMeander / 2; const p2x = (x1 + x2 * 2) / 3 + sinMeander / 2;
const p2y = (y1 + y2 * 2) / 3 - cosMeander / 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, 0], [p2x, p2y, 0]);
meandered.push([p1x, p1y, p1fl], [p2x, p2y, p2fl]);
} else if (dist2 > 25 || riverCells.length < 6) { } else if (dist2 > 25 || riverCells.length < 6) {
// if dist is medium or river is small add 1 extra middlepoint // if dist is medium or river is small add 1 extra middlepoint
const p1x = (x1 + x2) / 2 + -sinMeander; const p1x = (x1 + x2) / 2 + -sinMeander;
const p1y = (y1 + y2) / 2 + cosMeander; const p1y = (y1 + y2) / 2 + cosMeander;
const p1fl = keepInitialFlux ? flux1 : (flux1 + flux2) / 2; meandered.push([p1x, p1y, 0]);
meandered.push([p1x, p1y, p1fl]);
} }
} }
@ -385,29 +383,35 @@ window.Rivers = (function () {
}; };
const FLUX_FACTOR = 500; const FLUX_FACTOR = 500;
const MAX_FLUX_WIDTH = 2; const MAX_FLUX_WIDTH = 1;
const LENGTH_FACTOR = 200; 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 LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
const MAX_PROGRESSION = last(LENGTH_PROGRESSION); const MAX_PROGRESSION = last(LENGTH_PROGRESSION);
const getOffset = (flux, pointNumber, widthFactor, startingWidth = 0) => { const getOffset = ({flux, pointIndex, widthFactor, startingWidth}) => {
const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH); if (pointIndex === 0) return startingWidth;
const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION);
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; 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) // 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 riverPointsLeft = [];
const riverPointsRight = []; const riverPointsRight = [];
let flux = 0;
for (let p = 0; p < points.length; p++) { for (let pointIndex = 0; pointIndex < points.length; pointIndex++) {
const [x0, y0] = points[p - 1] || points[p]; const [x0, y0] = points[pointIndex - 1] || points[pointIndex];
const [x1, y1, flux] = points[p]; const [x1, y1, pointFlux] = points[pointIndex];
const [x2, y2] = points[p + 1] || points[p]; 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 angle = Math.atan2(y0 - y2, x0 - x2);
const sinOffset = Math.sin(angle) * offset; const sinOffset = Math.sin(angle) * offset;
const cosOffset = Math.cos(angle) * offset; const cosOffset = Math.cos(angle) * offset;
@ -507,6 +511,7 @@ window.Rivers = (function () {
getBasin, getBasin,
getWidth, getWidth,
getOffset, getOffset,
getSourceWidth,
getApproximateLength, getApproximateLength,
getRiverPoints, getRiverPoints,
remove, remove,

View file

@ -74,13 +74,10 @@ function createRiver() {
function addRiver() { function addRiver() {
const {rivers, cells} = pack; const {rivers, cells} = pack;
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin, getNextId} =
Rivers;
const riverCells = createRiver.cells; const riverCells = createRiver.cells;
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error"); 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; const parent = cells.r[last(riverCells)] || riverId;
riverCells.forEach(cell => { riverCells.forEach(cell => {
@ -89,17 +86,24 @@ function createRiver() {
const source = riverCells[0]; const source = riverCells[0];
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2]; 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 defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = 1.2 * defaultWidthFactor; const widthFactor = 1.2 * defaultWidthFactor;
const meanderedPoints = addMeandering(riverCells); const meanderedPoints = Rivers.addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second const discharge = cells.fl[mouth]; // m3 in second
const length = getApproximateLength(meanderedPoints); const length = Rivers.getApproximateLength(meanderedPoints);
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); const width = Rivers.getWidth(
const name = getName(mouth); Rivers.getOffset({
const basin = getBasin(parent); flux: discharge,
pointIndex: meanderedPoints.length,
widthFactor,
startingWidth: sourceWidth
})
);
const name = Rivers.getName(mouth);
const basin = Rivers.getBasin(parent);
rivers.push({ rivers.push({
i: riverId, i: riverId,
@ -124,7 +128,7 @@ function createRiver() {
.select("#rivers") .select("#rivers")
.append("path") .append("path")
.attr("id", id) .attr("id", id)
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth)); .attr("d", Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth));
editRiver(id); editRiver(id);
} }

View file

@ -86,10 +86,16 @@ function editRiver(id) {
} }
function updateRiverWidth(river) { function updateRiverWidth(river) {
const {addMeandering, getWidth, getOffset} = Rivers;
const {cells, discharge, widthFactor, sourceWidth} = river; const {cells, discharge, widthFactor, sourceWidth} = river;
const meanderedPoints = addMeandering(cells); const meanderedPoints = Rivers.addMeandering(cells);
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); river.width = Rivers.getWidth(
Rivers.getOffset({
flux: discharge,
pointIndex: meanderedPoints.length,
widthFactor,
startingWidth: sourceWidth
})
);
const width = `${rn(river.width * distanceScale, 3)} ${distanceUnitInput.value}`; const width = `${rn(river.width * distanceScale, 3)} ${distanceUnitInput.value}`;
byId("riverWidth").value = width; byId("riverWidth").value = width;

View file

@ -668,28 +668,15 @@ function addRiverOnClick() {
if (cells.h[i] < 20) return tip("Cannot create river in water cell", false, "error"); if (cells.h[i] < 20) return tip("Cannot create river in water cell", false, "error");
if (cells.b[i]) return; if (cells.b[i]) return;
const {
alterHeights,
resolveDepressions,
addMeandering,
getRiverPath,
getBasin,
getName,
getType,
getWidth,
getOffset,
getApproximateLength,
getNextId
} = Rivers;
const riverCells = []; const riverCells = [];
let riverId = getNextId(rivers); let riverId = Rivers.getNextId(rivers);
let parent = riverId; let parent = riverId;
const initialFlux = grid.cells.prec[cells.g[i]]; const initialFlux = grid.cells.prec[cells.g[i]];
cells.fl[i] = initialFlux; cells.fl[i] = initialFlux;
const h = alterHeights(); const h = Rivers.alterHeights();
resolveDepressions(h); Rivers.resolveDepressions(h);
while (i) { while (i) {
cells.r[i] = riverId; cells.r[i] = riverId;
@ -763,11 +750,19 @@ function addRiverOnClick() {
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = const widthFactor =
river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor); 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 discharge = cells.fl[mouth]; // m3 in second
const length = getApproximateLength(meanderedPoints); const length = Rivers.getApproximateLength(meanderedPoints);
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor)); const width = Rivers.getWidth(
Rivers.getOffset({
flux: discharge,
pointIndex: meanderedPoints.length,
widthFactor,
startingWidth: sourceWidth
})
);
if (river) { if (river) {
river.source = source; river.source = source;
@ -776,9 +771,9 @@ function addRiverOnClick() {
river.width = width; river.width = width;
river.cells = riverCells; river.cells = riverCells;
} else { } else {
const basin = getBasin(parent); const basin = Rivers.getBasin(parent);
const name = getName(mouth); const name = Rivers.getName(mouth);
const type = getType({i: riverId, length, parent}); const type = Rivers.getType({i: riverId, length, parent});
rivers.push({ rivers.push({
i: riverId, i: riverId,
@ -788,7 +783,7 @@ function addRiverOnClick() {
length, length,
width, width,
widthFactor, widthFactor,
sourceWidth: 0, sourceWidth,
parent, parent,
cells: riverCells, cells: riverCells,
basin, basin,
@ -799,7 +794,7 @@ function addRiverOnClick() {
// render river // render river
lineGen.curve(d3.curveCatmullRom.alpha(0.1)); lineGen.curve(d3.curveCatmullRom.alpha(0.1));
const path = getRiverPath(meanderedPoints, widthFactor); const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
const id = "river" + riverId; const id = "river" + riverId;
const riversG = viewbox.select("#rivers"); const riversG = viewbox.select("#rivers");
riversG.append("path").attr("id", id).attr("d", path); riversG.append("path").attr("id", id).attr("d", path);