feat: allow to render ocean heightmap - test

This commit is contained in:
Azgaar 2024-02-11 21:33:58 +01:00
parent cd45ad91fd
commit 94280e0acf
7 changed files with 171 additions and 104 deletions

View file

@ -824,6 +824,63 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr id="styleHeightmapRenderOceanOption" data-tip="Check to render ocean heights">
<td colspan="2">
<input id="styleHeightmapRenderOcean" class="checkbox" type="checkbox" />
<label for="styleHeightmapRenderOcean" class="checkbox-label">Render ocean heights</label>
</td>
</tr>
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="curveBasisClosed" selected>Curved</option>
<option value="curveLinear">Linear</option>
<option value="curveStep">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleOpacity" style="display: none"> <tbody id="styleOpacity" style="display: none">
<tr data-tip="Set opacity. 0: transparent, 1: solid"> <tr data-tip="Set opacity. 0: transparent, 1: solid">
<td>Opacity</td> <td>Opacity</td>
@ -1281,56 +1338,6 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="curveBasisClosed" selected>Curved</option>
<option value="curveLinear">Linear</option>
<option value="curveStep">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleArmies"> <tbody id="styleArmies">
<tr data-tip="Set fill transparency. Set to 0 to make it fully transparent"> <tr data-tip="Set fill transparency. Set to 0 to make it fully transparent">
<td>Fill opacity</td> <td>Fill opacity</td>

48
main.js
View file

@ -604,6 +604,7 @@ void (function addDragToUpload() {
async function generate(options) { async function generate(options) {
try { try {
console.log("--- 1 ---", Math.random());
const timeStart = performance.now(); const timeStart = performance.now();
const {seed: precreatedSeed, graph: precreatedGraph} = options || {}; const {seed: precreatedSeed, graph: precreatedGraph} = options || {};
@ -639,7 +640,9 @@ async function generate(options) {
Biomes.define(); Biomes.define();
rankCells(); rankCells();
console.log("--- 6 ---", Math.random());
Cultures.generate(); Cultures.generate();
console.log("--- 7 ---", Math.random());
Cultures.expand(); Cultures.expand();
BurgsAndStates.generate(); BurgsAndStates.generate();
Religions.generate(); Religions.generate();
@ -722,10 +725,30 @@ function markFeatures() {
grid.features = [0]; grid.features = [0];
for (let i = 1, queue = [0]; queue[0] !== -1; i++) { for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
// [1864, 1731]
// if (queue[0] === 1864 || queue[0] === 1731) {
// cells.f[queue[0]] = 1;
// cells.t[queue[0]] = -1;
// queue = [cells.f.findIndex(f => !f)];
// i--;
// continue;
// }
cells.f[queue[0]] = i; // feature number cells.f[queue[0]] = i; // feature number
const land = heights[queue[0]] >= 20; const land = heights[queue[0]] >= 20;
let border = false; // true if feature touches map border let border = false; // true if feature touches map border
// if (i === 11) {
// debug
// .append("g")
// .attr("id", "feature11")
// .append("circle")
// .attr("cx", grid.points[queue[0]][0])
// .attr("cy", grid.points[queue[0]][1])
// .attr("r", 2)
// .attr("fill", "red");
// }
while (queue.length) { while (queue.length) {
const q = queue.pop(); const q = queue.pop();
if (cells.b[q]) border = true; if (cells.b[q]) border = true;
@ -859,6 +882,8 @@ function openNearSeaLakes() {
} }
function removeLake(threshold, lake, ocean) { function removeLake(threshold, lake, ocean) {
debugger;
console.log("removeLake", threshold, lake, ocean);
cells.h[threshold] = 19; cells.h[threshold] = 19;
cells.t[threshold] = -1; cells.t[threshold] = -1;
cells.f[threshold] = ocean; cells.f[threshold] = ocean;
@ -1349,22 +1374,14 @@ function drawCoastline() {
// Re-mark features (ocean, lakes, islands) // Re-mark features (ocean, lakes, islands)
function reMarkFeatures() { function reMarkFeatures() {
TIME && console.time("reMarkFeatures"); TIME && console.time("reMarkFeatures");
const cells = pack.cells, const cells = pack.cells;
features = (pack.features = [0]); const features = (pack.features = [0]);
cells.f = new Uint16Array(cells.i.length); // cell feature number cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast; cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell); cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell);
cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells); cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells);
const defineHaven = i => {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
};
if (!cells.i.length) return; // no cells -> there is nothing to do if (!cells.i.length) return; // no cells -> there is nothing to do
for (let i = 1, queue = [0]; queue[0] !== -1; i++) { for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
const start = queue[0]; // first cell const start = queue[0]; // first cell
@ -1405,6 +1422,15 @@ function reMarkFeatures() {
// markupPackLand // markupPackLand
markup(pack.cells, 3, 1, 0); markup(pack.cells, 3, 1, 0);
function defineHaven(i) {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
}
function defineOceanGroup(number) { function defineOceanGroup(number) {
if (number > grid.cells.i.length / 25) return "ocean"; if (number > grid.cells.i.length / 25) return "ocean";
if (number > grid.cells.i.length / 100) return "sea"; if (number > grid.cells.i.length / 100) return "sea";

View file

@ -54,6 +54,8 @@ window.Cultures = (function () {
const colors = getColors(count); const colors = getColors(count);
const emblemShape = document.getElementById("emblemShape").value; const emblemShape = document.getElementById("emblemShape").value;
console.log("--- c1 ---", Math.random());
const codes = []; const codes = [];
cultures.forEach(function (c, i) { cultures.forEach(function (c, i) {
@ -71,28 +73,40 @@ window.Cultures = (function () {
return; return;
} }
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i])); console.log(JSON.stringify(c, null, 2));
centers.add(cells.p[cell]); console.log(Array.from(pack.cells.s).join());
const sortingFn = c.sort ? c.sort : i => cells.s[i];
const center = placeCenter(sortingFn);
console.log("--- c2-1 ---", i, Math.random());
centers.add(cells.p[center]);
c.center = center;
c.i = newId; c.i = newId;
delete c.odd; delete c.odd;
delete c.sort; delete c.sort;
c.color = colors[i]; c.color = colors[i];
c.type = defineCultureType(cell); console.log("--- c2-2 ---", i, Math.random());
c.type = defineCultureType(center);
console.log("--- c2-3 ---", i, Math.random());
c.expansionism = defineCultureExpansionism(c.type); c.expansionism = defineCultureExpansionism(c.type);
c.origins = [0]; c.origins = [0];
c.code = abbreviate(c.name, codes); c.code = abbreviate(c.name, codes);
codes.push(c.code); codes.push(c.code);
cultureIds[cell] = newId; cultureIds[center] = newId;
if (emblemShape === "random") c.shield = getRandomShield(); if (emblemShape === "random") c.shield = getRandomShield();
console.log("--- c2-4 ---", i, Math.random());
}); });
console.log("--- c3 ---", Math.random());
cells.culture = cultureIds; cells.culture = cultureIds;
function placeCenter(v) { function placeCenter(sortingFn) {
let spacing = (graphWidth + graphHeight) / 2 / count; let spacing = (graphWidth + graphHeight) / 2 / count;
const MAX_ATTEMPTS = 100; const MAX_ATTEMPTS = 100;
const sorted = [...populated].sort((a, b) => v(b) - v(a)); const sorted = [...populated].sort((a, b) => sortingFn(b) - sortingFn(a));
const max = Math.floor(sorted.length / 2); const max = Math.floor(sorted.length / 2);
let cellId = 0; let cellId = 0;

View file

@ -268,12 +268,14 @@ function drawHeightmap() {
} }
} }
return;
// render paths // render paths
for (const h of d3.range(0, 101)) { for (const height of d3.range(0, 101)) {
const group = h < 20 ? ocean : land; const group = height < 20 ? ocean : land;
const scheme = getColorScheme(group.attr("scheme")); const scheme = getColorScheme(group.attr("scheme"));
if (h === 0 && renderOceanCells) { if (height === 0 && renderOceanCells) {
// draw base ocean layer // draw base ocean layer
group group
.append("rect") .append("rect")
@ -284,7 +286,7 @@ function drawHeightmap() {
.attr("fill", scheme(1)); .attr("fill", scheme(1));
} }
if (h === 20) { if (height === 20) {
// draw base land layer // draw base land layer
group group
.append("rect") .append("rect")
@ -295,21 +297,21 @@ function drawHeightmap() {
.attr("fill", scheme(0.8)); .attr("fill", scheme(0.8));
} }
if (paths[h] && paths[h].length >= 10) { if (paths[height] && paths[height].length >= 10) {
const curve = group.attr("curve") || "curveBasisClosed"; const curve = group.attr("curve") || "curveBasisClosed";
lineGen.curve(d3[curve]); lineGen.curve(d3[curve]);
const terracing = group.attr("terracing") / 10 || 0; // shifted darker layer for pseudo-3d effect const terracing = group.attr("terracing") / 10 || 0;
const color = getColor(h, scheme); const color = getColor(height, scheme);
if (terracing && h >= 20) { if (terracing) {
land group
.append("path") .append("path")
.attr("d", paths[h]) .attr("d", paths[height])
.attr("transform", "translate(.7,1.4)") .attr("transform", "translate(.7,1.4)")
.attr("fill", d3.color(color).darker(terracing)) .attr("fill", d3.color(color).darker(terracing))
.attr("data-height", h); .attr("data-height", height);
} }
group.append("path").attr("d", paths[h]).attr("fill", color).attr("data-height", h); group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height);
} }
} }

View file

@ -31,6 +31,7 @@ function editStyle(element, group) {
styleElementSelect.classList.add("glow"); styleElementSelect.classList.add("glow");
if (group) styleGroupSelect.classList.add("glow"); if (group) styleGroupSelect.classList.add("glow");
setTimeout(() => { setTimeout(() => {
styleElementSelect.classList.remove("glow"); styleElementSelect.classList.remove("glow");
if (group) styleGroupSelect.classList.remove("glow"); if (group) styleGroupSelect.classList.remove("glow");
@ -81,10 +82,10 @@ function selectStyleElement() {
styleIsOff.style.display = isLayerOff ? "block" : "none"; styleIsOff.style.display = isLayerOff ? "block" : "none";
// active group element // active group element
const group = styleGroupSelect.value; if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { const group = styleGroupSelect.value;
const gEl = group && el.select("#" + group); const defaultGroupSelector = styleElement === "terrs" ? "#landHeights" : "g";
el = group && gEl.size() ? gEl : el.select("g"); el = group && el.select("#" + group).size() ? el.select("#" + group) : el.select(defaultGroupSelector);
} }
// opacity // opacity
@ -170,11 +171,14 @@ function selectStyleElement() {
if (styleElement === "terrs") { if (styleElement === "terrs") {
styleHeightmap.style.display = "block"; styleHeightmap.style.display = "block";
styleHeightmapScheme.value = terrs.attr("scheme"); styleHeightmapRenderOceanOption.style.display = el.attr("id") === "oceanHeights" ? "block" : "none";
styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing"); styleHeightmapRenderOcean.checked = +el.attr("data-render");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = terrs.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax"); styleHeightmapScheme.value = el.attr("scheme");
styleHeightmapCurve.value = terrs.attr("curve"); styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = el.attr("terracing");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = el.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = el.attr("relax");
styleHeightmapCurve.value = el.attr("curve");
} }
if (styleElement === "markers") { if (styleElement === "markers") {
@ -336,7 +340,7 @@ function selectStyleElement() {
// update group options // update group options
styleGroupSelect.options.length = 0; // remove all options styleGroupSelect.options.length = 0; // remove all options
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
const groups = byId(styleElement).querySelectorAll("g"); const groups = byId(styleElement).querySelectorAll("g");
groups.forEach(el => { groups.forEach(el => {
if (el.id === "burgLabels") return; if (el.id === "burgLabels") return;
@ -519,18 +523,16 @@ outlineLayers.addEventListener("change", function () {
}); });
styleHeightmapScheme.addEventListener("change", function () { styleHeightmapScheme.addEventListener("change", function () {
terrs.attr("scheme", this.value); getEl().attr("scheme", this.value);
drawHeightmap(); drawHeightmap();
}); });
openCreateHeightmapSchemeButton.addEventListener("click", function () { openCreateHeightmapSchemeButton.addEventListener("click", function () {
// start with current scheme // start with current scheme
this.dataset.stops = terrs.attr("scheme").startsWith("#") const scheme = getEl().attr("scheme");
? terrs.attr("scheme") this.dataset.stops = scheme.startsWith("#")
: (function () { ? scheme
const scheme = heightmapColorSchemes[terrs.attr("scheme")]; : (() => [0, 0.25, 0.5, 0.75, 1].map(heightmapColorSchemes[scheme]).map(toHEX).join(","))();
return [0, 0.25, 0.5, 0.75, 1].map(scheme).map(toHEX).join(",");
})();
// render dialog base structure // render dialog base structure
alertMessage.innerHTML = /* html */ `<div> alertMessage.innerHTML = /* html */ `<div>
@ -622,7 +624,7 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error"); if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error");
addCustomColorScheme(stops); addCustomColorScheme(stops);
terrs.attr("scheme", stops); getEl().attr("scheme", stops);
drawHeightmap(); drawHeightmap();
handleClose(); handleClose();
@ -644,23 +646,28 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
}); });
}); });
styleHeightmapRenderOcean.addEventListener("change", function () {
getEl().attr("data-render", +this.checked);
drawHeightmap();
});
styleHeightmapTerracingInput.addEventListener("input", function () { styleHeightmapTerracingInput.addEventListener("input", function () {
terrs.attr("terracing", this.value); getEl().attr("terracing", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSkipInput.addEventListener("input", function () { styleHeightmapSkipInput.addEventListener("input", function () {
terrs.attr("skip", this.value); getEl().attr("skip", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSimplificationInput.addEventListener("input", function () { styleHeightmapSimplificationInput.addEventListener("input", function () {
terrs.attr("relax", this.value); getEl().attr("relax", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapCurve.addEventListener("change", function () { styleHeightmapCurve.addEventListener("change", function () {
terrs.attr("curve", this.value); getEl().attr("curve", this.value);
drawHeightmap(); drawHeightmap();
}); });

View file

@ -239,7 +239,18 @@ function addStylePreset() {
"#oceanLayers": ["filter", "layers"], "#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"], "#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"], "#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], "#terrs #oceanHeights": [
"data-render",
"opacity",
"scheme",
"terracing",
"skip",
"relax",
"curve",
"filter",
"mask"
],
"#terrs #landHeights": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
"#legend": [ "#legend": [
"data-size", "data-size",
"font-size", "font-size",

View file

@ -329,7 +329,7 @@ function drawCellsValue(data) {
function drawPolygons(data) { function drawPolygons(data) {
const max = d3.max(data), const max = d3.max(data),
min = d3.min(data), min = d3.min(data),
scheme = getColorScheme(terrs.attr("scheme")); scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
data = data.map(d => 1 - normalize(d, min, max)); data = data.map(d => 1 - normalize(d, min, max));
debug.selectAll("polygon").remove(); debug.selectAll("polygon").remove();
@ -338,7 +338,7 @@ function drawPolygons(data) {
.data(data) .data(data)
.enter() .enter()
.append("polygon") .append("polygon")
.attr("points", (d, i) => getPackPolygon(i)) .attr("points", (d, i) => getGridPolygon(i))
.attr("fill", d => scheme(d)) .attr("fill", d => scheme(d))
.attr("stroke", d => scheme(d)); .attr("stroke", d => scheme(d));
} }