From cd45ad91fd5340f5da9e69127b11a0873f65c9ec Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 10 Dec 2023 15:20:26 +0400 Subject: [PATCH] feat: allow to render ocean heightmap --- index.html | 6 +- main.js | 6 +- modules/dynamic/auto-update.js | 42 ++++++++ modules/io/load.js | 8 +- modules/ui/layers.js | 174 ++++++++++++++++++++------------- styles/ancient.json | 4 +- styles/atlas.json | 4 +- styles/clean.json | 4 +- styles/cyberpunk.json | 4 +- styles/default.json | 17 +++- styles/gloom.json | 4 +- styles/light.json | 4 +- styles/monochrome.json | 4 +- styles/night.json | 4 +- styles/pale.json | 4 +- styles/watercolor.json | 4 +- versioning.js | 3 +- 17 files changed, 207 insertions(+), 89 deletions(-) diff --git a/index.html b/index.html index 7405ff94..c0a4abc9 100644 --- a/index.html +++ b/index.html @@ -1310,9 +1310,9 @@ Line style diff --git a/main.js b/main.js index 6db57f59..c065fe51 100644 --- a/main.js +++ b/main.js @@ -92,16 +92,19 @@ let fogging = viewbox let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none"); let debug = viewbox.append("g").attr("id", "debug"); -// lake and coast groups lakes.append("g").attr("id", "freshwater"); lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "lava"); lakes.append("g").attr("id", "dry"); + coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "lake_island"); +terrs.append("g").attr("id", "oceanHeights"); +terrs.append("g").attr("id", "landHeights"); + labels.append("g").attr("id", "states"); labels.append("g").attr("id", "addedLabels"); @@ -135,7 +138,6 @@ fogging .attr("filter", "url(#splotch)"); // assign events separately as not a viewbox child -scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits()); legend .on("mousemove", () => tip("Drag to change the position. Click to hide the legend")) .on("click", () => clearLegend()); diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 5dbebb60..124e32ff 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -736,4 +736,46 @@ export function resolveVersionConflicts(version) { .style("display", "none"); vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); } + + if (version < 1.96) { + // v1.96 added ocean rendering for heightmap + terrs.selectAll("*").remove(); + + const opacity = terrs.attr("opacity"); + const filter = terrs.attr("filter"); + const scheme = terrs.attr("scheme"); + const terracing = terrs.attr("terracing"); + const skip = terrs.attr("skip"); + const relax = terrs.attr("relax"); + + const curveTypes = {0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep"}; + const curve = curveTypes[terrs.attr("curve")] || "curveBasisClosed"; + + terrs.attr("scheme", null).attr("terracing", null).attr("skip", null).attr("relax", null).attr("curve", null); + + terrs + .append("g") + .attr("id", "oceanHeights") + .attr("data-render", 0) + .attr("opacity", opacity) + .attr("filter", filter) + .attr("scheme", scheme) + .attr("terracing", 0) + .attr("skip", 0) + .attr("relax", 1) + .attr("curve", curve); + terrs + .append("g") + .attr("id", "landHeights") + .attr("opacity", opacity) + .attr("scheme", scheme) + .attr("filter", filter) + .attr("terracing", terracing) + .attr("skip", skip) + .attr("relax", relax) + .attr("curve", curve) + .attr("mask", "url(#land)"); + + if (layerIsOn("toggleHeight")) drawHeightmap(); + } } diff --git a/modules/io/load.js b/modules/io/load.js index 769c7d6a..60741753 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -468,10 +468,10 @@ async function parseLoadedData(data) { { // add custom heightmap color scheme if any - const scheme = terrs.attr("scheme"); - if (!(scheme in heightmapColorSchemes)) { - addCustomColorScheme(scheme); - } + const oceanScheme = terrs.select("#oceanHeights").attr("scheme"); + const landScheme = terrs.select("#landHeights").attr("scheme"); + if (!(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme); + if (!(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme); } { diff --git a/modules/ui/layers.js b/modules/ui/layers.js index 945f3df9..e20fe0cf 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -188,92 +188,134 @@ function restoreLayers() { } function toggleHeight(event) { - if (customization === 1) { - tip("You cannot turn off the layer when heightmap is in edit mode", false, "error"); - return; - } + if (customization === 1) return tip("You cannot turn off the layer when heightmap is in edit mode", false, "error"); - if (!terrs.selectAll("*").size()) { + const children = terrs.selectAll("#oceanHeights > *, #landHeights > *"); + if (!children.size()) { turnButtonOn("toggleHeight"); drawHeightmap(); if (event && isCtrlClick(event)) editStyle("terrs"); } else { - if (event && isCtrlClick(event)) { - editStyle("terrs"); - return; - } + if (event && isCtrlClick(event)) return editStyle("terrs"); turnButtonOff("toggleHeight"); - terrs.selectAll("*").remove(); + children.remove(); } } function drawHeightmap() { TIME && console.time("drawHeightmap"); - terrs.selectAll("*").remove(); - const {cells, vertices} = pack; - const n = cells.i.length; - const used = new Uint8Array(cells.i.length); - const paths = new Array(101).fill(""); + const ocean = terrs.select("#oceanHeights"); + const land = terrs.select("#landHeights"); - const scheme = getColorScheme(terrs.attr("scheme")); - const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect - const skip = +terrs.attr("skip") + 1; - const simplification = +terrs.attr("relax"); + ocean.selectAll("*").remove(); + land.selectAll("*").remove(); - switch (+terrs.attr("curve")) { - case 0: - lineGen.curve(d3.curveBasisClosed); - break; - case 1: - lineGen.curve(d3.curveLinear); - break; - case 2: - lineGen.curve(d3.curveStep); - break; - default: - lineGen.curve(d3.curveBasisClosed); + 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; + + let currentLayer = 0; + const heights = 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)); + } } - let currentLayer = 20; - const heights = 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 (currentLayer > 100) break; // no layers possible with height > 100 - if (h < currentLayer) continue; - 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(vertex, h); - if (chain.length < 3) continue; - const points = simplifyLine(chain).map(v => vertices.p[v]); - 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; + + let currentLayer = 20; + const heights = 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)); + } } - terrs - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", graphWidth) - .attr("height", graphHeight) - .attr("fill", scheme(0.8)); // draw base layer - for (const i of d3.range(20, 101)) { - if (paths[i].length < 10) continue; - const color = getColor(i, scheme); - if (terracing) - terrs - .append("path") - .attr("d", paths[i]) - .attr("transform", "translate(.7,1.4)") - .attr("fill", d3.color(color).darker(terracing)) - .attr("data-height", i); - terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i); + // render paths + for (const h of d3.range(0, 101)) { + const group = h < 20 ? ocean : land; + const scheme = getColorScheme(group.attr("scheme")); + + if (h === 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 (h === 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[h] && paths[h].length >= 10) { + const curve = group.attr("curve") || "curveBasisClosed"; + lineGen.curve(d3[curve]); + const terracing = group.attr("terracing") / 10 || 0; // shifted darker layer for pseudo-3d effect + const color = getColor(h, scheme); + + if (terracing && h >= 20) { + land + .append("path") + .attr("d", paths[h]) + .attr("transform", "translate(.7,1.4)") + .attr("fill", d3.color(color).darker(terracing)) + .attr("data-height", h); + } + group.append("path").attr("d", paths[h]).attr("fill", color).attr("data-height", h); + } } // connect vertices to chain - function connectVertices(start, h) { + 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 @@ -295,7 +337,7 @@ function drawHeightmap() { return chain; } - function simplifyLine(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); diff --git a/styles/ancient.json b/styles/ancient.json index e57f2d87..7d675a3f 100644 --- a/styles/ancient.json +++ b/styles/ancient.json @@ -292,7 +292,9 @@ "terracing": 0, "skip": 2, "relax": 1, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "url(#blur3)", "mask": "url(#land)" }, diff --git a/styles/atlas.json b/styles/atlas.json index 6366e5f4..79ba9a87 100644 --- a/styles/atlas.json +++ b/styles/atlas.json @@ -292,7 +292,9 @@ "terracing": 0, "skip": 0, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": null, "mask": "url(#land)" }, diff --git a/styles/clean.json b/styles/clean.json index ba3685a4..53419fba 100644 --- a/styles/clean.json +++ b/styles/clean.json @@ -294,7 +294,9 @@ "terracing": 0, "skip": 5, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": null, "mask": "url(#land)" }, diff --git a/styles/cyberpunk.json b/styles/cyberpunk.json index e69f5c43..c8b99264 100644 --- a/styles/cyberpunk.json +++ b/styles/cyberpunk.json @@ -292,7 +292,9 @@ "terracing": 6, "skip": 0, "relax": 2, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "", "mask": "url(#land)" }, diff --git a/styles/default.json b/styles/default.json index e2cf0e6d..45b28bc5 100644 --- a/styles/default.json +++ b/styles/default.json @@ -286,13 +286,24 @@ "href": "./images/pattern1.png", "opacity": 0.2 }, - "#terrs": { - "opacity": null, + "#terrs > #oceanHeights": { + "data-render": 0, + "opacity": 1, + "scheme": "bright", + "terracing": 0, + "skip": 0, + "relax": 1, + "curve": "curveBasisClosed", + "filter": null, + "mask": null + }, + "#terrs > #landHeights": { + "opacity": 1, "scheme": "bright", "terracing": 0, "skip": 5, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", "filter": null, "mask": "url(#land)" }, diff --git a/styles/gloom.json b/styles/gloom.json index ec4084d2..80e4f196 100644 --- a/styles/gloom.json +++ b/styles/gloom.json @@ -294,7 +294,9 @@ "terracing": 2, "skip": 1, "relax": 2, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "url(#filter-grayscale)", "mask": "url(#land)" }, diff --git a/styles/light.json b/styles/light.json index 978a039b..43f7acd4 100644 --- a/styles/light.json +++ b/styles/light.json @@ -292,7 +292,9 @@ "terracing": 10, "skip": 5, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "url(#turbulence)", "mask": "url(#land)" }, diff --git a/styles/monochrome.json b/styles/monochrome.json index 23ff7a48..7f93bb43 100644 --- a/styles/monochrome.json +++ b/styles/monochrome.json @@ -287,7 +287,9 @@ "terracing": 0, "skip": 5, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "url(#blur3)", "mask": "url(#land)" }, diff --git a/styles/night.json b/styles/night.json index 99df7867..83b278e2 100644 --- a/styles/night.json +++ b/styles/night.json @@ -292,7 +292,9 @@ "terracing": 0, "skip": 10, "relax": 0, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "url(#blurFilter)", "mask": "url(#land)" }, diff --git a/styles/pale.json b/styles/pale.json index 4bddc4d2..4267f47d 100644 --- a/styles/pale.json +++ b/styles/pale.json @@ -292,7 +292,9 @@ "terracing": 0, "skip": 2, "relax": 1, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": "", "mask": "url(#land)" }, diff --git a/styles/watercolor.json b/styles/watercolor.json index 01763c1b..fc0201bc 100644 --- a/styles/watercolor.json +++ b/styles/watercolor.json @@ -292,7 +292,9 @@ "terracing": 0, "skip": 5, "relax": 1, - "curve": 0, + "curve": "curveBasisClosed", + "skipOcean": 0, + "relaxOcean": 1, "filter": null, "mask": "url(#land)" }, diff --git a/versioning.js b/versioning.js index 4e4fb075..5bf367ac 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.95.04"; // generator version, update each time +const version = "1.96.00"; // generator version, update each time { document.title += " v" + version; @@ -28,6 +28,7 @@ const version = "1.95.04"; // generator version, update each time