diff --git a/index.html b/index.html index fc388af6..85c9b2bd 100644 --- a/index.html +++ b/index.html @@ -541,6 +541,7 @@ id="toggleRivers" data-tip="Rivers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style" data-shortcut="V" + class="buttonoff" onclick="toggleRivers(event)" > Rivers @@ -8032,7 +8033,6 @@ - @@ -8101,5 +8101,8 @@ + + + diff --git a/main.js b/main.js index 8120e591..295d57f8 100644 --- a/main.js +++ b/main.js @@ -639,7 +639,6 @@ async function generate(options) { drawCoastline(); Rivers.generate(); - drawRivers(); Lakes.defineGroup(); Biomes.define(); diff --git a/modules/renderers/draw-heightmap.js b/modules/renderers/draw-heightmap.js new file mode 100644 index 00000000..97505c8d --- /dev/null +++ b/modules/renderers/draw-heightmap.js @@ -0,0 +1,142 @@ +"use strict"; + +function drawHeightmap() { + TIME && console.time("drawHeightmap"); + + const ocean = terrs.select("#oceanHeights"); + const land = terrs.select("#landHeights"); + + ocean.selectAll("*").remove(); + land.selectAll("*").remove(); + + const paths = new Array(101); + const {cells, vertices} = grid; + const used = new Uint8Array(cells.i.length); + const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]); + + // ocean cells + const renderOceanCells = Boolean(+ocean.attr("data-render")); + if (renderOceanCells) { + const skip = +ocean.attr("skip") + 1 || 1; + const relax = +ocean.attr("relax") || 0; + lineGen.curve(d3[ocean.attr("curve") || "curveBasisClosed"]); + + let currentLayer = 0; + for (const i of heights) { + const h = cells.h[i]; + if (h > currentLayer) currentLayer += skip; + if (h < currentLayer) continue; + if (currentLayer >= 20) break; + if (used[i]) continue; // already marked + const onborder = cells.c[i].some(n => cells.h[n] < h); + if (!onborder) continue; + const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); + const chain = connectVertices(cells, vertices, vertex, h, used); + if (chain.length < 3) continue; + const points = simplifyLine(chain, relax).map(v => vertices.p[v]); + if (!paths[h]) paths[h] = ""; + paths[h] += round(lineGen(points)); + } + } + + // land cells + { + const skip = +land.attr("skip") + 1 || 1; + const relax = +land.attr("relax") || 0; + lineGen.curve(d3[land.attr("curve") || "curveBasisClosed"]); + + let currentLayer = 20; + for (const i of heights) { + const h = cells.h[i]; + if (h > currentLayer) currentLayer += skip; + if (h < currentLayer) continue; + if (currentLayer > 100) break; // no layers possible with height > 100 + if (used[i]) continue; // already marked + const onborder = cells.c[i].some(n => cells.h[n] < h); + if (!onborder) continue; + + const startVertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); + const chain = connectVertices(cells, vertices, startVertex, h, used); + if (chain.length < 3) continue; + + const points = simplifyLine(chain, relax).map(v => vertices.p[v]); + if (!paths[h]) paths[h] = ""; + paths[h] += round(lineGen(points)); + } + } + + // render paths + for (const height of d3.range(0, 101)) { + const group = height < 20 ? ocean : land; + const scheme = getColorScheme(group.attr("scheme")); + + if (height === 0 && renderOceanCells) { + // draw base ocean layer + group + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", graphWidth) + .attr("height", graphHeight) + .attr("fill", scheme(1)); + } + + if (height === 20) { + // draw base land layer + group + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", graphWidth) + .attr("height", graphHeight) + .attr("fill", scheme(0.8)); + } + + if (paths[height] && paths[height].length >= 10) { + const terracing = group.attr("terracing") / 10 || 0; + const color = getColor(height, scheme); + + if (terracing) { + group + .append("path") + .attr("d", paths[height]) + .attr("transform", "translate(.7,1.4)") + .attr("fill", d3.color(color).darker(terracing)) + .attr("data-height", height); + } + group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height); + } + } + + // connect vertices to chain: specific case for heightmap + function connectVertices(cells, vertices, start, h, used) { + const n = cells.i.length; + const chain = []; // vertices chain to form a path + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); 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.h[c] === h).forEach(c => (used[c] = 1)); + const c0 = c[0] >= n || cells.h[c[0]] < h; + const c1 = c[1] >= n || cells.h[c[1]] < h; + const c2 = c[2] >= n || cells.h[c[2]] < h; + const v = vertices.v[current]; // neighboring vertices + if (v[0] !== prev && c0 !== c1) current = v[0]; + else if (v[1] !== prev && c1 !== c2) current = v[1]; + else if (v[2] !== prev && c0 !== c2) current = v[2]; + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } + } + return chain; + } + + function simplifyLine(chain, simplification) { + if (!simplification) return chain; + const n = simplification + 1; // filter each nth element + return chain.filter((d, i) => i % n === 0); + } + + TIME && console.timeEnd("drawHeightmap"); +} diff --git a/modules/submap.js b/modules/submap.js index 5389bcaa..d40e225a 100644 --- a/modules/submap.js +++ b/modules/submap.js @@ -208,7 +208,6 @@ window.Submap = (function () { stage("Regenerating river network."); Rivers.generate(); - drawRivers(); Lakes.defineGroup(); // biome calculation based on (resampled) grid.cells.temp and prec diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index fa1427f3..fa9227eb 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -237,7 +237,6 @@ function editHeightmap(options) { } } - drawRivers(); Lakes.defineGroup(); Biomes.define(); rankCells(); diff --git a/modules/ui/layers.js b/modules/ui/layers.js index acb8736a..886ba9f0 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -184,9 +184,7 @@ function restoreLayers() { if (layerIsOn("toggleZones")) drawZones(); if (layerIsOn("toggleBorders")) drawBorders(); if (layerIsOn("toggleStates")) drawStates(); - - // some layers are rendered each time, remove them if they are not on - if (!layerIsOn("toggleRivers")) rivers.selectAll("*").remove(); + if (layerIsOn("toggleRivers")) drawRivers(); } function toggleHeight(event) { @@ -204,151 +202,6 @@ function toggleHeight(event) { } } -function drawHeightmap() { - TIME && console.time("drawHeightmap"); - - const ocean = terrs.select("#oceanHeights"); - const land = terrs.select("#landHeights"); - - ocean.selectAll("*").remove(); - land.selectAll("*").remove(); - - const paths = new Array(101); - - // ocean cells - const renderOceanCells = Boolean(+ocean.attr("data-render")); - if (renderOceanCells) { - const {cells, vertices} = grid; - const used = new Uint8Array(cells.i.length); - - const skip = +ocean.attr("skip") + 1 || 1; - const relax = +ocean.attr("relax") || 0; - lineGen.curve(d3[ocean.attr("curve") || "curveBasisClosed"]); - - let currentLayer = 0; - const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]); - - for (const i of heights) { - const h = cells.h[i]; - if (h > currentLayer) currentLayer += skip; - if (h < currentLayer) continue; - if (currentLayer >= 20) break; - if (used[i]) continue; // already marked - const onborder = cells.c[i].some(n => cells.h[n] < h); - if (!onborder) continue; - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); - const chain = connectVertices(cells, vertices, vertex, h, used); - if (chain.length < 3) continue; - const points = simplifyLine(chain, relax).map(v => vertices.p[v]); - if (!paths[h]) paths[h] = ""; - paths[h] += round(lineGen(points)); - } - } - - // land cells - { - const {cells, vertices} = pack; - const used = new Uint8Array(cells.i.length); - - const skip = +land.attr("skip") + 1 || 1; - const relax = +land.attr("relax") || 0; - lineGen.curve(d3[land.attr("curve") || "curveBasisClosed"]); - - let currentLayer = 20; - const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]); - for (const i of heights) { - const h = cells.h[i]; - if (h > currentLayer) currentLayer += skip; - if (h < currentLayer) continue; - if (currentLayer > 100) break; // no layers possible with height > 100 - if (used[i]) continue; // already marked - const onborder = cells.c[i].some(n => cells.h[n] < h); - if (!onborder) continue; - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); - const chain = connectVertices(cells, vertices, vertex, h, used); - if (chain.length < 3) continue; - const points = simplifyLine(chain, relax).map(v => vertices.p[v]); - if (!paths[h]) paths[h] = ""; - paths[h] += round(lineGen(points)); - } - } - - // render paths - for (const height of d3.range(0, 101)) { - const group = height < 20 ? ocean : land; - const scheme = getColorScheme(group.attr("scheme")); - - if (height === 0 && renderOceanCells) { - // draw base ocean layer - group - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", graphWidth) - .attr("height", graphHeight) - .attr("fill", scheme(1)); - } - - if (height === 20) { - // draw base land layer - group - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", graphWidth) - .attr("height", graphHeight) - .attr("fill", scheme(0.8)); - } - - if (paths[height] && paths[height].length >= 10) { - const terracing = group.attr("terracing") / 10 || 0; - const color = getColor(height, scheme); - - if (terracing) { - group - .append("path") - .attr("d", paths[height]) - .attr("transform", "translate(.7,1.4)") - .attr("fill", d3.color(color).darker(terracing)) - .attr("data-height", height); - } - group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height); - } - } - - // connect vertices to chain - function connectVertices(cells, vertices, start, h, used) { - const n = cells.i.length; - const chain = []; // vertices chain to form a path - for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); 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.h[c] === h).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.h[c[0]] < h; - const c1 = c[1] >= n || cells.h[c[1]] < h; - const c2 = c[2] >= n || cells.h[c[2]] < h; - const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) current = v[0]; - else if (v[1] !== prev && c1 !== c2) current = v[1]; - else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) { - ERROR && console.error("Next vertex is not found"); - break; - } - } - return chain; - } - - function simplifyLine(chain, simplification) { - if (!simplification) return chain; - const n = simplification + 1; // filter each nth element - return chain.filter((d, i) => i % n === 0); - } - - TIME && console.timeEnd("drawHeightmap"); -} - function getColor(value, scheme = getColorScheme("bright")) { return scheme(1 - (value < 20 ? value - 5 : value) / 100); } diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 33bff1aa..91addfaa 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -128,8 +128,7 @@ function regenerateRivers() { Rivers.generate(); Lakes.defineGroup(); Rivers.specify(); - if (!layerIsOn("toggleRivers")) toggleRivers(); - else drawRivers(); + if (layerIsOn("toggleRivers")) drawRivers(); } function recalculatePopulation() {