mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
separete rivere rendering from generation
This commit is contained in:
parent
d9d349f3a1
commit
dbcf46f83b
6 changed files with 71 additions and 68 deletions
1
main.js
1
main.js
|
|
@ -624,6 +624,7 @@ function generate() {
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
Rivers.generate();
|
Rivers.generate();
|
||||||
|
drawRivers();
|
||||||
Lakes.defineGroup();
|
Lakes.defineGroup();
|
||||||
defineBiomes();
|
defineBiomes();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,12 +271,13 @@ function parseLoadedData(data) {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
void (function restoreLayersState() {
|
||||||
|
// helper functions
|
||||||
const notHidden = selection => selection.node() && selection.style("display") !== "none";
|
const notHidden = selection => selection.node() && selection.style("display") !== "none";
|
||||||
const hasChildren = selection => selection.node()?.hasChildNodes();
|
const hasChildren = selection => selection.node()?.hasChildNodes();
|
||||||
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
||||||
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
|
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
|
||||||
|
|
||||||
void (function restoreLayersState() {
|
|
||||||
// turn all layers off
|
// turn all layers off
|
||||||
document
|
document
|
||||||
.getElementById("mapLayers")
|
.getElementById("mapLayers")
|
||||||
|
|
@ -291,7 +292,7 @@ function parseLoadedData(data) {
|
||||||
if (hasChildren(gridOverlay)) turnOn("toggleGrid");
|
if (hasChildren(gridOverlay)) turnOn("toggleGrid");
|
||||||
if (hasChildren(coordinates)) turnOn("toggleCoordinates");
|
if (hasChildren(coordinates)) turnOn("toggleCoordinates");
|
||||||
if (notHidden(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
|
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 (notHidden(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
|
||||||
if (hasChildren(relig)) turnOn("toggleReligions");
|
if (hasChildren(relig)) turnOn("toggleReligions");
|
||||||
if (hasChildren(cults)) turnOn("toggleCultures");
|
if (hasChildren(cults)) turnOn("toggleCultures");
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
Lakes.prepareLakeData(h);
|
Lakes.prepareLakeData(h);
|
||||||
resolveDepressions(h);
|
resolveDepressions(h);
|
||||||
drainWater();
|
drainWater();
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
defineRivers();
|
defineRivers();
|
||||||
calculateConfluenceFlux();
|
calculateConfluenceFlux();
|
||||||
Lakes.cleanupLakeData();
|
Lakes.cleanupLakeData();
|
||||||
|
|
@ -150,7 +149,6 @@
|
||||||
cells.r = new Uint16Array(cells.i.length);
|
cells.r = new Uint16Array(cells.i.length);
|
||||||
cells.conf = new Uint16Array(cells.i.length);
|
cells.conf = new Uint16Array(cells.i.length);
|
||||||
pack.rivers = [];
|
pack.rivers = [];
|
||||||
const riverPaths = [];
|
|
||||||
|
|
||||||
for (const key in riversData) {
|
for (const key in riversData) {
|
||||||
const riverCells = riversData[key];
|
const riverCells = riversData[key];
|
||||||
|
|
@ -170,20 +168,13 @@
|
||||||
const parent = riverParents[key] || 0;
|
const parent = riverParents[key] || 0;
|
||||||
|
|
||||||
const widthFactor = !parent || parent === riverId ? 1.2 : 1;
|
const widthFactor = !parent || parent === riverId ? 1.2 : 1;
|
||||||
const riverMeandered = addMeandering(riverCells);
|
const meanderedPoints = 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 discharge = cells.fl[mouth]; // m3 in second
|
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});
|
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw rivers
|
|
||||||
rivers.html(riverPaths.map(d => `<path id="river${d[1]}" d="${d[0]}"/>`).join(""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateConfluenceFlux() {
|
function calculateConfluenceFlux() {
|
||||||
|
|
@ -346,18 +337,21 @@
|
||||||
return [graphWidth, y];
|
return [graphWidth, y];
|
||||||
};
|
};
|
||||||
|
|
||||||
const fluxFactor = 500;
|
const FLUX_FACTOR = 500;
|
||||||
const maxFluxWidth = 2;
|
const MAX_FLUX_WIDTH = 2;
|
||||||
const lengthFactor = 200;
|
const LENGTH_FACTOR = 200;
|
||||||
const stepWidth = 1 / lengthFactor;
|
const STEP_WIDTH = 1 / LENGTH_FACTOR;
|
||||||
const lengthProgression = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / lengthFactor);
|
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
|
||||||
const maxProgression = last(lengthProgression);
|
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)
|
// build polygon from a list of points and calculated offset (width)
|
||||||
const getRiverPath = function (points, widthFactor = 1, startingWidth = 0) {
|
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 riverPointsLeft = [];
|
||||||
const riverPointsRight = [];
|
const riverPointsRight = [];
|
||||||
|
|
||||||
|
|
@ -366,13 +360,10 @@
|
||||||
const [x1, y1, flux] = points[p];
|
const [x1, y1, flux] = points[p];
|
||||||
const [x2, y2] = points[p + 1] || points[p];
|
const [x2, y2] = points[p + 1] || points[p];
|
||||||
|
|
||||||
const fluxWidth = Math.min(flux ** 0.9 / fluxFactor, maxFluxWidth);
|
const offset = getOffset(flux, p, widthFactor, startingWidth);
|
||||||
const lengthWidth = p * stepWidth + (lengthProgression[p] || maxProgression);
|
|
||||||
width = widthFactor * (lengthWidth + fluxWidth) + startingWidth;
|
|
||||||
|
|
||||||
const angle = Math.atan2(y0 - y2, x0 - x2);
|
const angle = Math.atan2(y0 - y2, x0 - x2);
|
||||||
const sinOffset = Math.sin(angle) * width;
|
const sinOffset = Math.sin(angle) * offset;
|
||||||
const cosOffset = Math.cos(angle) * width;
|
const cosOffset = Math.cos(angle) * offset;
|
||||||
|
|
||||||
riverPointsLeft.push([x1 - sinOffset, y1 + cosOffset]);
|
riverPointsLeft.push([x1 - sinOffset, y1 + cosOffset]);
|
||||||
riverPointsRight.push([x1 + sinOffset, y1 - cosOffset]);
|
riverPointsRight.push([x1 + sinOffset, y1 - cosOffset]);
|
||||||
|
|
@ -382,7 +373,7 @@
|
||||||
let left = lineGen(riverPointsLeft);
|
let left = lineGen(riverPointsLeft);
|
||||||
left = left.substring(left.indexOf("C"));
|
left = left.substring(left.indexOf("C"));
|
||||||
|
|
||||||
return [round(right + left, 2), rn(riverLength, 2), width];
|
return round(right + left, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const specify = function () {
|
const specify = function () {
|
||||||
|
|
@ -424,6 +415,12 @@
|
||||||
return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]);
|
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
|
// remove river and all its tributaries
|
||||||
const remove = function (id) {
|
const remove = function (id) {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
|
|
@ -444,5 +441,5 @@
|
||||||
return getBasin(parent);
|
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};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawRivers();
|
||||||
Lakes.defineGroup();
|
Lakes.defineGroup();
|
||||||
defineBiomes();
|
defineBiomes();
|
||||||
rankCells();
|
rankCells();
|
||||||
|
|
|
||||||
|
|
@ -875,7 +875,6 @@ function toggleStates(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw states
|
|
||||||
function drawStates() {
|
function drawStates() {
|
||||||
TIME && console.time("drawStates");
|
TIME && console.time("drawStates");
|
||||||
regions.selectAll("path").remove();
|
regions.selectAll("path").remove();
|
||||||
|
|
@ -1015,6 +1014,21 @@ function drawStates() {
|
||||||
TIME && console.timeEnd("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
|
// draw state and province borders
|
||||||
function drawBorders() {
|
function drawBorders() {
|
||||||
TIME && console.time("drawBorders");
|
TIME && console.time("drawBorders");
|
||||||
|
|
@ -1118,21 +1132,6 @@ function drawBorders() {
|
||||||
TIME && console.timeEnd("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) {
|
function toggleProvinces(event) {
|
||||||
if (!layerIsOn("toggleProvinces")) {
|
if (!layerIsOn("toggleProvinces")) {
|
||||||
turnButtonOn("toggleProvinces");
|
turnButtonOn("toggleProvinces");
|
||||||
|
|
@ -1454,14 +1453,18 @@ function toggleRivers(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRivers() {
|
function drawRivers() {
|
||||||
|
TIME && console.time("drawRivers");
|
||||||
|
const {addMeandering, getRiverPath} = Rivers;
|
||||||
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
const riverPaths = pack.rivers.map(river => {
|
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 widthFactor = river.widthFactor || 1;
|
||||||
const startingWidth = river.startingWidth || 0;
|
const startingWidth = river.startingWidth || 0;
|
||||||
const [path] = Rivers.getRiverPath(riverMeandered, widthFactor, startingWidth);
|
const path = getRiverPath(riverMeandered, widthFactor, startingWidth);
|
||||||
return [path, river.i];
|
return `<path id="river${river.i}" d="${path}"/>`;
|
||||||
});
|
});
|
||||||
rivers.html(riverPaths.map(d => `<path id="river${d[1]}" d="${d[0]}"/>`).join(""));
|
rivers.html(riverPaths.join(""));
|
||||||
|
TIME && console.timeEnd("drawRivers");
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleRoutes(event) {
|
function toggleRoutes(event) {
|
||||||
|
|
|
||||||
|
|
@ -538,7 +538,7 @@ 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} = Rivers;
|
const {alterHeights, resolveDepressions, addMeandering, getRiverPath, getBasin, getName, getType, getWidth, getOffset, getApproximateLength} = Rivers;
|
||||||
const riverCells = [];
|
const riverCells = [];
|
||||||
let riverId = last(rivers).id + 1;
|
let riverId = last(rivers).id + 1;
|
||||||
let parent = 0;
|
let parent = 0;
|
||||||
|
|
@ -614,22 +614,15 @@ function addRiverOnClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const river = rivers.find(r => r.i === riverId);
|
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 source = riverCells[0];
|
||||||
const mouth = riverCells[riverCells.length - 2];
|
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 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) {
|
if (river) {
|
||||||
river.source = source;
|
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});
|
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) {
|
if (d3.event.shiftKey === false) {
|
||||||
Lakes.cleanupLakeData();
|
Lakes.cleanupLakeData();
|
||||||
unpressClickToAddButton();
|
unpressClickToAddButton();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue