feat: allow to render ocean heightmap

This commit is contained in:
Azgaar 2023-12-10 15:20:26 +04:00
parent 52e3088763
commit cd45ad91fd
17 changed files with 207 additions and 89 deletions

View file

@ -1310,9 +1310,9 @@
<td>Line style</td> <td>Line style</td>
<td> <td>
<select id="styleHeightmapCurve"> <select id="styleHeightmapCurve">
<option value="0" selected>Curved</option> <option value="curveBasisClosed" selected>Curved</option>
<option value="1">Linear</option> <option value="curveLinear">Linear</option>
<option value="2">Rectangular</option> <option value="curveStep">Rectangular</option>
</select> </select>
</td> </td>
</tr> </tr>

View file

@ -92,16 +92,19 @@ let fogging = viewbox
let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none"); let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
let debug = viewbox.append("g").attr("id", "debug"); let debug = viewbox.append("g").attr("id", "debug");
// lake and coast groups
lakes.append("g").attr("id", "freshwater"); lakes.append("g").attr("id", "freshwater");
lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "salt");
lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava"); lakes.append("g").attr("id", "lava");
lakes.append("g").attr("id", "dry"); lakes.append("g").attr("id", "dry");
coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_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", "states");
labels.append("g").attr("id", "addedLabels"); labels.append("g").attr("id", "addedLabels");
@ -135,7 +138,6 @@ fogging
.attr("filter", "url(#splotch)"); .attr("filter", "url(#splotch)");
// assign events separately as not a viewbox child // assign events separately as not a viewbox child
scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
legend legend
.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")) .on("mousemove", () => tip("Drag to change the position. Click to hide the legend"))
.on("click", () => clearLegend()); .on("click", () => clearLegend());

View file

@ -736,4 +736,46 @@ export function resolveVersionConflicts(version) {
.style("display", "none"); .style("display", "none");
vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); 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();
}
} }

View file

@ -468,10 +468,10 @@ async function parseLoadedData(data) {
{ {
// add custom heightmap color scheme if any // add custom heightmap color scheme if any
const scheme = terrs.attr("scheme"); const oceanScheme = terrs.select("#oceanHeights").attr("scheme");
if (!(scheme in heightmapColorSchemes)) { const landScheme = terrs.select("#landHeights").attr("scheme");
addCustomColorScheme(scheme); if (!(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme);
} if (!(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme);
} }
{ {

View file

@ -188,92 +188,134 @@ function restoreLayers() {
} }
function toggleHeight(event) { function toggleHeight(event) {
if (customization === 1) { if (customization === 1) return tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
return;
}
if (!terrs.selectAll("*").size()) { const children = terrs.selectAll("#oceanHeights > *, #landHeights > *");
if (!children.size()) {
turnButtonOn("toggleHeight"); turnButtonOn("toggleHeight");
drawHeightmap(); drawHeightmap();
if (event && isCtrlClick(event)) editStyle("terrs"); if (event && isCtrlClick(event)) editStyle("terrs");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("terrs");
editStyle("terrs");
return;
}
turnButtonOff("toggleHeight"); turnButtonOff("toggleHeight");
terrs.selectAll("*").remove(); children.remove();
} }
} }
function drawHeightmap() { function drawHeightmap() {
TIME && console.time("drawHeightmap"); TIME && console.time("drawHeightmap");
terrs.selectAll("*").remove();
const {cells, vertices} = pack; const ocean = terrs.select("#oceanHeights");
const n = cells.i.length; const land = terrs.select("#landHeights");
const used = new Uint8Array(cells.i.length);
const paths = new Array(101).fill("");
const scheme = getColorScheme(terrs.attr("scheme")); ocean.selectAll("*").remove();
const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect land.selectAll("*").remove();
const skip = +terrs.attr("skip") + 1;
const simplification = +terrs.attr("relax");
switch (+terrs.attr("curve")) { const paths = new Array(101);
case 0:
lineGen.curve(d3.curveBasisClosed); // ocean cells
break; const renderOceanCells = Boolean(+ocean.attr("data-render"));
case 1: if (renderOceanCells) {
lineGen.curve(d3.curveLinear); const {cells, vertices} = grid;
break; const used = new Uint8Array(cells.i.length);
case 2:
lineGen.curve(d3.curveStep); const skip = +ocean.attr("skip") + 1 || 1;
break; const relax = +ocean.attr("relax") || 0;
default:
lineGen.curve(d3.curveBasisClosed); 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; // land cells
const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]); {
for (const i of heights) { const {cells, vertices} = pack;
const h = cells.h[i]; const used = new Uint8Array(cells.i.length);
if (h > currentLayer) currentLayer += skip;
if (currentLayer > 100) break; // no layers possible with height > 100 const skip = +land.attr("skip") + 1 || 1;
if (h < currentLayer) continue; const relax = +land.attr("relax") || 0;
if (used[i]) continue; // already marked
const onborder = cells.c[i].some(n => cells.h[n] < h); let currentLayer = 20;
if (!onborder) continue; const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]);
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); for (const i of heights) {
const chain = connectVertices(vertex, h); const h = cells.h[i];
if (chain.length < 3) continue; if (h > currentLayer) currentLayer += skip;
const points = simplifyLine(chain).map(v => vertices.p[v]); if (h < currentLayer) continue;
paths[h] += round(lineGen(points)); 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 // render paths
.append("rect") for (const h of d3.range(0, 101)) {
.attr("x", 0) const group = h < 20 ? ocean : land;
.attr("y", 0) const scheme = getColorScheme(group.attr("scheme"));
.attr("width", graphWidth)
.attr("height", graphHeight) if (h === 0 && renderOceanCells) {
.attr("fill", scheme(0.8)); // draw base layer // draw base ocean layer
for (const i of d3.range(20, 101)) { group
if (paths[i].length < 10) continue; .append("rect")
const color = getColor(i, scheme); .attr("x", 0)
if (terracing) .attr("y", 0)
terrs .attr("width", graphWidth)
.append("path") .attr("height", graphHeight)
.attr("d", paths[i]) .attr("fill", scheme(1));
.attr("transform", "translate(.7,1.4)") }
.attr("fill", d3.color(color).darker(terracing))
.attr("data-height", i); if (h === 20) {
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i); // 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 // 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 const chain = []; // vertices chain to form a path
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
const prev = chain[chain.length - 1]; // previous vertex in chain const prev = chain[chain.length - 1]; // previous vertex in chain
@ -295,7 +337,7 @@ function drawHeightmap() {
return chain; return chain;
} }
function simplifyLine(chain) { function simplifyLine(chain, simplification) {
if (!simplification) return chain; if (!simplification) return chain;
const n = simplification + 1; // filter each nth element const n = simplification + 1; // filter each nth element
return chain.filter((d, i) => i % n === 0); return chain.filter((d, i) => i % n === 0);

View file

@ -292,7 +292,9 @@
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "url(#blur3)", "filter": "url(#blur3)",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 0, "terracing": 0,
"skip": 0, "skip": 0,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -294,7 +294,9 @@
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 6, "terracing": 6,
"skip": 0, "skip": 0,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "", "filter": "",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -286,13 +286,24 @@
"href": "./images/pattern1.png", "href": "./images/pattern1.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "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", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -294,7 +294,9 @@
"terracing": 2, "terracing": 2,
"skip": 1, "skip": 1,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "url(#filter-grayscale)", "filter": "url(#filter-grayscale)",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 10, "terracing": 10,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "url(#turbulence)", "filter": "url(#turbulence)",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -287,7 +287,9 @@
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "url(#blur3)", "filter": "url(#blur3)",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 0, "terracing": 0,
"skip": 10, "skip": 10,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "url(#blurFilter)", "filter": "url(#blurFilter)",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": "", "filter": "",
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -292,7 +292,9 @@
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"skipOcean": 0,
"relaxOcean": 1,
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // 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; document.title += " v" + version;
@ -28,6 +28,7 @@ const version = "1.95.04"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Ability to render ocean heightmap</li>
<li>Vignette visual layer and vignette styling options</li> <li>Vignette visual layer and vignette styling options</li>
<li>Ability to define custom heightmap color scheme</li> <li>Ability to define custom heightmap color scheme</li>
<li>New style preset Night and new heightmap color schemes</li> <li>New style preset Night and new heightmap color schemes</li>