diff --git a/index.html b/index.html index eb363635..907d690a 100644 --- a/index.html +++ b/index.html @@ -1358,9 +1358,9 @@ -
- - +
+ +
diff --git a/main.js b/main.js index b7a6f37a..347670c4 100644 --- a/main.js +++ b/main.js @@ -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 diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js index 2194f7b0..16a8ce88 100644 --- a/modules/ocean-layers.js +++ b/modules/ocean-layers.js @@ -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; - -}))); \ No newline at end of file +}); diff --git a/modules/river-generator.js b/modules/river-generator.js index 8dcda0f6..72dfac1e 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -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"); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 0b7ef617..935660ca 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -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(); }