addLakesInDeepDepressions

This commit is contained in:
Azgaar 2021-06-06 20:25:36 +03:00
parent 6cca0b7f38
commit c8c1c24909
5 changed files with 113 additions and 39 deletions

View file

@ -1358,9 +1358,9 @@
<input id="renderOcean" class="checkbox" type="checkbox">
<label for="renderOcean" class="checkbox-label">Render ocean cells</label>
</div>
<div id="changeHeightsBox" data-tip="Regenerate rivers and allow water flow to slightly change heights">
<input id="changeHeights" class="checkbox" type="checkbox" checked>
<label for="changeHeights" class="checkbox-label">Allow water erosion</label>
<div id="allowErosionBox" data-tip="Regenerate rivers and allow water flow to change heights and form lakes">
<input id="allowErosion" class="checkbox" type="checkbox" checked>
<label for="allowErosion" class="checkbox-label">Allow water erosion</label>
</div>
</div>

65
main.js
View file

@ -598,6 +598,7 @@ function generate() {
HeightmapGenerator.generate();
markFeatures();
markupGridOcean();
addLakesInDeepDepressions();
openNearSeaLakes();
OceanLayers();
@ -770,11 +771,71 @@ function markup(cells, start, increment, limit) {
}
}
function addLakesInDeepDepressions() {
console.time("addLakesInDeepDepressions");
const {cells, features, points} = grid;
const {c, h, b} = cells;
const ELEVATION_LIMIT = 10;
for (const i of cells.i) {
if (b[i] || h[i] < 20) continue;
const minHeight = d3.min(c[i].map(c => h[c]));
if (h[i] > minHeight) continue;
let deep = true;
const treshold = h[i] + ELEVATION_LIMIT;
const queue = [i];
const checked = [];
checked[i] = true;
// check if elevated cell can potentially pour to water
while (deep && queue.length) {
const q = queue.pop();
for (const n of c[q]) {
if (checked[n]) continue;
if (h[n] < 20) {
deep = false;
break;
}
if (h[n] >= treshold) continue;
checked[n] = true;
queue.push(n);
}
}
// if not, add a lake
if (deep) {
debug.append("circle").attr("cx", points[i][0]).attr("cy", points[i][1]).attr("r", 1).attr("fill", "red");
const lakeCells = [i].concat(c[i].filter(n => h[n] === h[i]));
addLake(lakeCells);
}
}
function addLake(lakeCells) {
const f = features.length;
lakeCells.forEach(i => {
cells.h[i] = 19;
cells.t[i] = -1;
cells.f[i] = f;
c[i].forEach(n => !lakeCells.includes(n) && (cells.t[c] = 1));
});
features.push({i: f, land: false, border: false, type: "lake"});
}
console.timeEnd("addLakesInDeepDepressions");
}
// near sea lakes usually get a lot of water inflow, most of them should brake treshold and flow out to sea (see Ancylus Lake)
function openNearSeaLakes() {
if (templateInput.value === "Atoll") return; // no need for Atolls
const cells = grid.cells,
features = grid.features;
const cells = grid.cells;
const features = grid.features;
if (!features.find(f => f.type === "lake")) return; // no lakes
TIME && console.time("openLakes");
const LIMIT = 22; // max height that can be breached by water

View file

@ -1,8 +1,7 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.OceanLayers = factory());
}(this, (function () { 'use strict';
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.OceanLayers = factory());
})(this, function () {
"use strict";
let cells, vertices, pointsN, used;
@ -12,11 +11,11 @@
TIME && console.time("drawOceanLayers");
lineGen.curve(d3.curveBasisClosed);
cells = grid.cells, pointsN = grid.cells.i.length, vertices = grid.vertices;
(cells = grid.cells), (pointsN = grid.cells.i.length), (vertices = grid.vertices);
const limits = outline === "random" ? randomizeOutline() : outline.split(",").map(s => +s);
const chains = [];
const opacity = rn(.4 / limits.length, 2);
const opacity = rn(0.4 / limits.length, 2);
used = new Uint8Array(pointsN); // to detect already passed cells
for (const i of cells.i) {
@ -29,10 +28,13 @@
const chain = connectVertices(start, t); // vertices chain to form a path
if (chain.length < 4) continue;
const relax = 1 + t * -2; // select only n-th point
const relaxed = chain.filter((v, i) => !(i%relax) || vertices.c[v].some(c => c >= pointsN));
const relaxed = chain.filter((v, i) => !(i % relax) || vertices.c[v].some(c => c >= pointsN));
if (relaxed.length < 4) continue;
const points = clipPoly(relaxed.map(v => vertices.p[v]), 1);
chains.push([t, points]);
const points = clipPoly(
relaxed.map(v => vertices.p[v]),
1
);
chains.push([t, points]);
}
for (const t of limits) {
@ -48,14 +50,18 @@
}
TIME && console.timeEnd("drawOceanLayers");
}
};
function randomizeOutline() {
const limits = [];
let odd = .2
let odd = 0.2;
for (let l = -9; l < 0; l++) {
if (P(odd)) {odd = .2; limits.push(l);}
else {odd *= 2;}
if (P(odd)) {
odd = 0.2;
limits.push(l);
} else {
odd *= 2;
}
}
return limits;
}
@ -63,24 +69,26 @@
// connect vertices to chain
function connectVertices(start, t) {
const chain = []; // vertices chain to form a path
for (let i=0, current = start; i === 0 || current !== start && i < 10000; i++) {
for (let i = 0, current = start; i === 0 || (current !== start && i < 10000); i++) {
const prev = chain[chain.length - 1]; // previous vertex in chain
chain.push(current); // add current vertex to sequence
const c = vertices.c[current]; // cells adjacent to vertex
c.filter(c => cells.t[c] === t).forEach(c => used[c] = 1);
c.filter(c => cells.t[c] === t).forEach(c => (used[c] = 1));
const v = vertices.v[current]; // neighboring vertices
const c0 = !cells.t[c[0]] || cells.t[c[0]] === t-1;
const c1 = !cells.t[c[1]] || cells.t[c[1]] === t-1;
const c2 = !cells.t[c[2]] || cells.t[c[2]] === t-1;
const c0 = !cells.t[c[0]] || cells.t[c[0]] === t - 1;
const c1 = !cells.t[c[1]] || cells.t[c[1]] === t - 1;
const c2 = !cells.t[c[2]] || cells.t[c[2]] === t - 1;
if (v[0] !== undefined && v[0] !== prev && c0 !== c1) current = v[0];
else if (v[1] !== undefined && v[1] !== prev && c1 !== c2) current = v[1];
else if (v[2] !== undefined && v[2] !== prev && c0 !== c2) current = v[2];
if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;}
if (current === chain[chain.length - 1]) {
ERROR && console.error("Next vertex is not found");
break;
}
}
chain.push(chain[0]); // push first vertex as the last one
return chain;
}
return OceanLayers;
})));
});

View file

@ -3,7 +3,7 @@
})(this, function () {
"use strict";
const generate = function (changeHeights = true) {
const generate = function (allowErosion = true) {
TIME && console.time("generateRivers");
Math.random = aleaPRNG(seed);
const cells = pack.cells,
@ -23,7 +23,7 @@
defineRivers();
Lakes.cleanupLakeData();
if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one
if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one
TIME && console.timeEnd("generateRivers");

View file

@ -77,7 +77,7 @@ function editHeightmap() {
convertImage.style.display = type === "erase" ? "inline-block" : "none";
// hide erosion checkbox if mode is Keep
changeHeightsBox.style.display = type === "keep" ? "none" : "inline-block";
allowErosionBox.style.display = type === "keep" ? "none" : "inline-block";
// show finalize button
if (!sessionStorage.getItem("noExitButtonAnimation")) {
@ -182,19 +182,22 @@ function editHeightmap() {
INFO && console.group("Edit Heightmap");
TIME && console.time("regenerateErasedData");
const change = changeHeights.checked;
const erosionAllowed = allowErosion.checked;
markFeatures();
markupGridOcean();
if (change) openNearSeaLakes();
if (erosionAllowed) {
addLakesInDeepDepressions();
openNearSeaLakes();
}
OceanLayers();
calculateTemperatures();
generatePrecipitation();
reGraph();
drawCoastline();
Rivers.generate(change);
Rivers.generate(erosionAllowed);
if (!change) {
if (!erosionAllowed) {
for (const i of pack.cells.i) {
const g = pack.cells.g[i];
if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g];
@ -236,6 +239,7 @@ function editHeightmap() {
function restoreRiskedData() {
INFO && console.group("Edit Heightmap");
TIME && console.time("restoreRiskedData");
const erosionAllowed = allowErosion.checked;
// assign pack data to grid cells
const l = grid.cells.i.length;
@ -250,7 +254,7 @@ function editHeightmap() {
const culture = new Uint16Array(l);
const religion = new Uint16Array(l);
// rivers data, stored only if changeHeights is unchecked
// rivers data, stored only if allowErosion is unchecked
const fl = new Uint16Array(l);
const r = new Uint16Array(l);
const conf = new Uint8Array(l);
@ -268,7 +272,7 @@ function editHeightmap() {
burg[g] = pack.cells.burg[i];
religion[g] = pack.cells.religion[i];
if (!changeHeights.checked) {
if (!erosionAllowed) {
fl[g] = pack.cells.fl[i];
r[g] = pack.cells.r[i];
conf[g] = pack.cells.conf[i];
@ -301,13 +305,14 @@ function editHeightmap() {
markFeatures();
markupGridOcean();
if (erosionAllowed) addLakesInDeepDepressions();
OceanLayers();
calculateTemperatures();
generatePrecipitation();
reGraph();
drawCoastline();
if (changeHeights.checked) Rivers.generate(changeHeights.checked);
if (erosionAllowed) Rivers.generate(true);
// assign saved pack data from grid back to pack
const n = pack.cells.i.length;
@ -322,7 +327,7 @@ function editHeightmap() {
pack.cells.religion = new Uint16Array(n);
pack.cells.biome = new Uint8Array(n);
if (!changeHeights.checked) {
if (!erosionAllowed) {
pack.cells.r = new Uint16Array(n);
pack.cells.conf = new Uint8Array(n);
pack.cells.fl = new Uint16Array(n);
@ -336,7 +341,7 @@ function editHeightmap() {
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
// rivers data
if (!changeHeights.checked) {
if (!erosionAllowed) {
pack.cells.r[i] = r[g];
pack.cells.conf[i] = conf[g];
pack.cells.fl[i] = fl[g];
@ -400,7 +405,7 @@ function editHeightmap() {
drawStates();
drawBorders();
if (changeHeights.checked) {
if (erosion) {
Rivers.specify();
Lakes.generateName();
}