From 50f497c62a1b84b2a4990a7859468369e3b35d5e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 23 Jul 2021 20:37:17 +0300 Subject: [PATCH] river type - fix fork type selection --- modules/burgs-and-states.js | 3 +- modules/heightmap-generator.js | 1 - modules/river-generator.js | 67 +++++++++++++++++++++++----------- modules/ui/tools.js | 17 ++++----- modules/utils.js | 4 ++ 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 0dccc4ab..7ed9d724 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -80,6 +80,7 @@ TIME && console.time("createStates"); const states = [{i: 0, name: "Neutrals"}]; const colors = getColors(burgs.length - 1); + const each5th = each(5); burgs.forEach(function (b, i) { if (!i) return; // skip first element @@ -93,7 +94,7 @@ // states data const expansionism = rn(Math.random() * powerInput.value + 1, 1); - const basename = b.name.length < 9 && b.cell % 5 === 0 ? b.name : Names.getCultureShort(b.culture); + const basename = b.name.length < 9 && each5th(b.cell) ? b.name : Names.getCultureShort(b.culture); const name = Names.getState(basename, b.culture); const type = cultures[b.culture].type; diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js index 8e0dc42a..ab2f51a0 100644 --- a/modules/heightmap-generator.js +++ b/modules/heightmap-generator.js @@ -422,7 +422,6 @@ if (d % 6 !== 0) return; for (const l of d3.range(i)) { const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell - //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); cells.h[min] = (cells.h[cur] * 2 + cells.h[min]) / 3; cur = min; } diff --git a/modules/river-generator.js b/modules/river-generator.js index 820492d0..3859ec9d 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -171,14 +171,14 @@ const widthFactor = !parent || parent === riverId ? 1.2 : 1; const initStep = cells.h[source] >= 20 ? 1 : 10; - const riverMeandered = addMeandering(riverCells, initStep, 0.5); + const riverMeandered = addMeandering(riverCells, initStep); 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.4) ** 2, 2); // mouth width in km - const discharge = cells.fl[mouth]; // in m3/s + const discharge = cells.fl[mouth]; // m3 in second pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells}); } @@ -269,11 +269,12 @@ }; // add points at 1/3 and 2/3 of a line between adjacents river cells - const addMeandering = function (riverCells, step = 1, meandering = 0.5) { + const addMeandering = function (riverCells, step = 1, meandering = 0.5, riverPoints = null) { + const {fl, conf} = pack.cells; const meandered = []; - const {p, fl, conf} = pack.cells; - const lastStep = riverCells.length - 1; + const points = getRiverPoints(riverCells, riverPoints); + let fluxPrev = 0; const getFlux = (step, flux) => (step === lastStep ? fluxPrev : flux); @@ -281,22 +282,21 @@ const cell = riverCells[i]; const isLastCell = i === lastStep; - const [x1, y1] = p[cell]; + const [x1, y1] = points[i]; const flux1 = getFlux(i, fl[cell]); fluxPrev = flux1; meandered.push([x1, y1, flux1]); - if (isLastCell) break; const nextCell = riverCells[i + 1]; + const [x2, y2] = points[i + 1]; + if (nextCell === -1) { - const [x, y] = getBorderPoint(cell); - meandered.push([x, y, fluxPrev]); + meandered.push([x2, y2, fluxPrev]); break; } - const [x2, y2] = p[nextCell]; const dist2 = (x2 - x1) ** 2 + (y2 - y1) ** 2; // square distance between cells if (dist2 <= 25 && riverCells.length >= 6) continue; @@ -328,6 +328,24 @@ return meandered; }; + const getRiverPoints = (riverCells, riverPoints) => { + const {p} = pack.cells; + return riverCells.map((cell, i) => { + if (riverPoints && riverPoints[i]) return riverPoints[i]; + if (cell === -1) return getBorderPoint(riverCells[i - 1]); + return p[cell]; + }); + }; + + const getBorderPoint = i => { + const [x, y] = pack.cells.p[i]; + const min = Math.min(y, graphHeight - y, x, graphWidth - x); + if (min === y) return [x, 0]; + else if (min === graphHeight - y) return [x, graphHeight]; + else if (min === x) return [0, y]; + return [graphWidth, y]; + }; + const fluxFactor = 500; const maxFluxWidth = 2; const widthFactor = 200; @@ -373,13 +391,11 @@ Math.random = aleaPRNG(seed); const thresholdElement = Math.ceil(rivers.length * 0.15); const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[thresholdElement]; - const smallType = {Creek: 9, River: 3, Brook: 3, Stream: 1}; // weighted small river types for (const r of rivers) { r.basin = getBasin(r.i); r.name = getName(r.mouth); - const small = r.length < smallLength; - r.type = r.parent && !(r.i % 6) ? (small ? "Branch" : "Fork") : small ? rw(smallType) : "River"; + r.type = getType(r, r.length < smallLength); } }; @@ -387,6 +403,22 @@ return Names.getCulture(pack.cells.culture[cell]); }; + // weighted arrays of river type names + const riverTypes = { + main: { + big: {River: 1}, + small: {Creek: 9, River: 3, Brook: 3, Stream: 1} + }, + fork: { + big: {Fork: 1}, + small: {Branch: 1} + } + }; + const getType = function (river, isSmall) { + const isFork = each(3)(river.i) && river.parent && river.parent !== river.i; + return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]); + }; + // remove river and all its tributaries const remove = function (id) { const cells = pack.cells; @@ -407,14 +439,5 @@ return getBasin(parent); }; - const getBorderPoint = i => { - const [x, y] = pack.cells.p[i]; - const min = Math.min(y, graphHeight - y, x, graphWidth - x); - if (min === y) return [x, 0]; - else if (min === graphHeight - y) return [x, graphHeight]; - else if (min === x) return [0, y]; - return [graphWidth, y]; - }; - return {generate, alterHeights, resolveDepressions, addMeandering, getPath: getRiverPath, specify, getName, getBasin, remove}; }); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 7d2fad8f..f358d517 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -534,12 +534,12 @@ function addRiverOnClick() { const point = d3.mouse(this); let i = findCell(point[0], point[1]); - if (cells.r[i]) return tip("There already a river here", false, "error"); + if (cells.r[i]) return tip("There is already a river here", false, "error"); if (cells.h[i] < 20) return tip("Cannot create river in water cell", false, "error"); if (cells.b[i]) return; const riverCells = []; - let riverId = +getNextId("river").slice(5); + let riverId = last(rivers).id + 1; let parent = 0; const initialFlux = grid.cells.prec[cells.g[i]]; @@ -555,8 +555,6 @@ function addRiverOnClick() { const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); - const [tx, ty] = cells.p[min]; - // pour to water body if (h[min] < 20) { riverCells.push(min); @@ -615,12 +613,11 @@ function addRiverOnClick() { } const river = rivers.find(r => r.i === riverId); - const sourceWidth = 0.1; const widthFactor = river?.widthFactor || (!parent || parent === r ? 1.2 : 1); - const riverMeandered = Rivers.addMeandering(riverCells, sourceWidth * 10, 0.5); + const riverMeandered = Rivers.addMeandering(riverCells, 1); lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - const [path, length, offset] = Rivers.getRiverPath(riverMeandered, widthFactor, sourceWidth); + const [path, length, offset] = Rivers.getRiverPath(riverMeandered, widthFactor); viewbox .select("#rivers") .append("path") @@ -629,9 +626,9 @@ function addRiverOnClick() { // add new river to data or change extended river attributes const source = riverCells[0]; - const mouth = last(riverCells); - const discharge = cells.fl[mouth]; // in m3/s - const width = rn(offset ** 2, 2); // mounth width in km + const mouth = riverCells[riverCells.length - 2]; + const discharge = cells.fl[mouth]; // m3 in second + const width = rn((offset / 1.4) ** 2, 2); // mounth width in km if (river) { river.source = source; diff --git a/modules/utils.js b/modules/utils.js index 9850ea5e..84389a3d 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -236,6 +236,10 @@ function P(probability) { return Math.random() < probability; } +function each(n) { + return i => i % n === 0; +} + // random number (normal or gaussian distribution) function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) { return rn(Math.max(Math.min(d3.randomNormal(expected, deviation)(), max), min), round);