diff --git a/index.html b/index.html index 873bd9e5..6898c955 100644 --- a/index.html +++ b/index.html @@ -1332,7 +1332,7 @@ - + diff --git a/modules/dynamic/heightmap-selection.js b/modules/dynamic/heightmap-selection.js index 271ef5ac..c9e28fb8 100644 --- a/modules/dynamic/heightmap-selection.js +++ b/modules/dynamic/heightmap-selection.js @@ -136,7 +136,10 @@ function insertEditorHtml() { const templatesHtml = templates .map(({id, name}) => { Math.random = aleaPRNG(seed); + + HeightmapGenerator.resetHeights(); const heights = HeightmapGenerator.fromTemplate(id); + HeightmapGenerator.cleanup(); const dataUrl = drawHeights(heights); return /* html */ `
@@ -224,7 +227,9 @@ function regeneratePreview(article, id) { article.dataset.seed = seed; Math.random = aleaPRNG(seed); + HeightmapGenerator.resetHeights(); const heights = HeightmapGenerator.fromTemplate(id); + HeightmapGenerator.cleanup(); const dataUrl = drawHeights(heights); article.querySelector("img").src = dataUrl; } diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js index d7ff0ff5..2213deb7 100644 --- a/modules/heightmap-generator.js +++ b/modules/heightmap-generator.js @@ -1,12 +1,14 @@ "use strict"; window.HeightmapGenerator = (function () { - let cells, p, heights; + let heights = null; + const setHeights = h => (heights = h); + const resetHeights = () => (heights = new Uint8Array(grid.points.length)); + const getHeights = () => heights; + const cleanup = () => (heights = null); const generate = async function () { - cells = grid.cells; - p = grid.points; - heights = new Uint8Array(grid.points.length); + resetHeights(); const input = document.getElementById("templateInput"); const selectedId = input.selectedIndex >= 0 ? input.selectedIndex : 0; @@ -33,8 +35,8 @@ window.HeightmapGenerator = (function () { canvas.remove(); img.remove(); - cells.h = heights; - heights = null; + grid.cells.h = heights; + cleanup(); TIME && console.timeEnd("defineHeightmap"); resolve(); }; @@ -55,8 +57,8 @@ window.HeightmapGenerator = (function () { addStep(...elements); } - cells.h = heights; - heights = null; + grid.cells.h = heights; + cleanup(); TIME && console.timeEnd("generateHeightmap"); }; @@ -66,8 +68,6 @@ window.HeightmapGenerator = (function () { if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${template}. Steps: ${steps}`); - heights = new Uint8Array(grid.points.length); - for (const step of steps) { const elements = step.trim().split(" "); if (elements.length < 2) throw new Error(`Heightmap template: steps < 2. Template: ${template}. Step: ${elements}`); @@ -77,20 +77,21 @@ window.HeightmapGenerator = (function () { return heights; }; - function addStep(a1, a2, a3, a4, a5) { - if (a1 === "Hill") return addHill(a2, a3, a4, a5); - if (a1 === "Pit") return addPit(a2, a3, a4, a5); - if (a1 === "Range") return addRange(a2, a3, a4, a5); - if (a1 === "Trough") return addTrough(a2, a3, a4, a5); - if (a1 === "Strait") return addStrait(a2, a3); - if (a1 === "Mask") return mask(a2); - if (a1 === "Add") return modify(a3, +a2, 1); - if (a1 === "Multiply") return modify(a3, 0, +a2); - if (a1 === "Smooth") return smooth(a2); + function addStep(tool, a2, a3, a4, a5) { + if (tool === "Hill") return addHill(a2, a3, a4, a5); + if (tool === "Pit") return addPit(a2, a3, a4, a5); + if (tool === "Range") return addRange(a2, a3, a4, a5); + if (tool === "Trough") return addTrough(a2, a3, a4, a5); + if (tool === "Strait") return addStrait(a2, a3); + if (tool === "Mask") return mask(a2); + if (tool === "Invert") return invert(a2, a3); + if (tool === "Add") return modify(a3, +a2, 1); + if (tool === "Multiply") return modify(a3, 0, +a2); + if (tool === "Smooth") return smooth(a2); } function getBlobPower() { - const cells = +pointsInput.dataset.cells; + const cells = +byId("pointsInput").dataset.cells; if (cells === 1000) return 0.93; if (cells === 2000) return 0.95; if (cells === 5000) return 0.96; @@ -107,7 +108,7 @@ window.HeightmapGenerator = (function () { } function getLinePower() { - const cells = +pointsInput.dataset.cells; + const cells = +byId("pointsInput").dataset.cells; if (cells === 1000) return 0.74; if (cells === 2000) return 0.75; if (cells === 5000) return 0.78; @@ -149,7 +150,7 @@ window.HeightmapGenerator = (function () { while (queue.length) { const q = queue.shift(); - for (const c of cells.c[q]) { + for (const c of grid.cells.c[q]) { if (change[c]) continue; change[c] = change[q] ** power * (Math.random() * 0.2 + 0.9); if (change[c] > 1) queue.push(c); @@ -186,7 +187,7 @@ window.HeightmapGenerator = (function () { h = h ** getBlobPower() * (Math.random() * 0.2 + 0.9); if (h < 1) return; - cells.c[q].forEach(function (c, i) { + grid.cells.c[q].forEach(function (c, i) { if (used[c]) return; heights[c] = lim(heights[c] - h * (Math.random() * 0.2 + 0.9)); used[c] = 1; @@ -228,11 +229,12 @@ window.HeightmapGenerator = (function () { // get main ridge function getRange(cur, end) { const range = [cur]; + const p = grid.points; used[cur] = 1; while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function (e) { + grid.cells.c[cur].forEach(function (e) { if (used[e]) return; let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; if (Math.random() > 0.85) diff = diff / 2; @@ -261,7 +263,7 @@ window.HeightmapGenerator = (function () { h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { - cells.c[f].forEach(i => { + grid.cells.c[f].forEach(i => { if (!used[i]) { queue.push(i); used[i] = 1; @@ -274,7 +276,7 @@ window.HeightmapGenerator = (function () { range.forEach((cur, d) => { if (d % 6 !== 0) return; for (const l of d3.range(i)) { - const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => heights[a] - heights[b])]; // downhill cell + const min = grid.cells.c[cur][d3.scan(grid.cells.c[cur], (a, b) => heights[a] - heights[b])]; // downhill cell heights[min] = (heights[cur] * 2 + heights[min]) / 3; cur = min; } @@ -322,11 +324,12 @@ window.HeightmapGenerator = (function () { // get main ridge function getRange(cur, end) { const range = [cur]; + const p = grid.points; used[cur] = 1; while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function (e) { + grid.cells.c[cur].forEach(function (e) { if (used[e]) return; let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; if (Math.random() > 0.8) diff = diff / 2; @@ -355,7 +358,7 @@ window.HeightmapGenerator = (function () { h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { - cells.c[f].forEach(i => { + grid.cells.c[f].forEach(i => { if (!used[i]) { queue.push(i); used[i] = 1; @@ -368,7 +371,7 @@ window.HeightmapGenerator = (function () { range.forEach((cur, d) => { if (d % 6 !== 0) return; for (const l of d3.range(i)) { - const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => heights[a] - heights[b])]; // downhill cell + const min = grid.cells.c[cur][d3.scan(grid.cells.c[cur], (a, b) => heights[a] - heights[b])]; // downhill cell //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); heights[min] = (heights[cur] * 2 + heights[min]) / 3; cur = min; @@ -394,10 +397,11 @@ window.HeightmapGenerator = (function () { function getRange(cur, end) { const range = []; + const p = grid.points; while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function (e) { + grid.cells.c[cur].forEach(function (e) { let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; if (Math.random() > 0.8) diff = diff / 2; if (diff < min) { @@ -416,7 +420,7 @@ window.HeightmapGenerator = (function () { while (width > 0) { const exp = 0.9 - step * width; range.forEach(function (r) { - cells.c[r].forEach(function (e) { + grid.cells.c[r].forEach(function (e) { if (used[e]) return; used[e] = 1; query.push(e); @@ -448,7 +452,7 @@ window.HeightmapGenerator = (function () { const smooth = (fr = 2, add = 0) => { heights = heights.map((h, i) => { const a = [h]; - cells.c[i].forEach(c => a.push(heights[c])); + grid.cells.c[i].forEach(c => a.push(heights[c])); if (fr === 1) return d3.mean(a) + add; return lim((h * (fr - 1) + d3.mean(a) + add) / fr); }); @@ -458,7 +462,7 @@ window.HeightmapGenerator = (function () { const fr = power ? Math.abs(power) : 1; heights = heights.map((h, i) => { - const [x, y] = p[i]; + const [x, y] = grid.points[i]; const nx = (2 * x) / graphWidth - 1; // [-1, 1], 0 is center const ny = (2 * y) / graphHeight - 1; // [-1, 1], 0 is center let distance = (1 - nx ** 2) * (1 - ny ** 2); // 1 is center, 0 is edge @@ -500,12 +504,12 @@ window.HeightmapGenerator = (function () { } function assignColorsToHeight(imageData) { - for (let i = 0; i < cells.i.length; i++) { + for (let i = 0; i < grid.cells.i.length; i++) { const lightness = imageData[i * 4] / 255; const powered = lightness < 0.2 ? lightness : 0.2 + (lightness - 0.2) ** 0.8; heights[i] = minmax(Math.floor(powered * 100), 0, 100); } } - return {generate, fromTemplate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify, mask, invert}; + return {setHeights, resetHeights, getHeights, cleanup, generate, fromTemplate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify, mask, invert}; })(); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 5345d4fa..0ade51b7 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -652,17 +652,24 @@ function editHeightmap() { if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error"); if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error"); + HeightmapGenerator.setHeights(grid.cells.h); + if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); else if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0); else if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0); else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand); + grid.cells.h = HeightmapGenerator.getHeights(); + HeightmapGenerator.cleanup(); updateHeightmap(); } function smoothAllHeights() { + HeightmapGenerator.setHeights(grid.cells.h); HeightmapGenerator.smooth(4, 1.5); + grid.cells.h = HeightmapGenerator.getHeights(); + HeightmapGenerator.cleanup(); updateHeightmap(); } @@ -879,10 +886,7 @@ function editHeightmap() { const steps = body.querySelectorAll("div").length; const changed = +body.getAttribute("data-changed"); const template = e.target.value; - if (!steps || !changed) { - changeTemplate(template); - return; - } + if (!steps || !changed) return changeTemplate(template); alertMessage.innerHTML = "Are you sure you want to select a different template? All changes will be lost."; $("#alert").dialog({ @@ -921,10 +925,10 @@ function editHeightmap() { const steps = byId("templateBody").querySelectorAll("#templateBody > div"); if (!steps.length) return; - grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights - const seed = byId("templateSeed").value; if (seed) Math.random = aleaPRNG(seed); + + HeightmapGenerator.resetHeights(); restartHistory(); for (const step of steps) { @@ -948,9 +952,12 @@ function editHeightmap() { else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +count); else if (type === "Smooth") HeightmapGenerator.smooth(+count); + grid.cells.h = HeightmapGenerator.getHeights(); updateHistory("noStat"); // update history on every step } + grid.cells.h = HeightmapGenerator.getHeights(); + HeightmapGenerator.cleanup(); updateStatistics(); mockHeightmap(); if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened diff --git a/modules/ui/options.js b/modules/ui/options.js index c392bf14..fa82ed06 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -142,7 +142,7 @@ optionsContent.addEventListener("click", function (event) { else if (id === "optionsMapHistory") showSeedHistoryDialog(); else if (id === "optionsCopySeed") copyMapURL(); else if (id === "optionsEraRegenerate") regenerateEra(); - else if (id === "templateSelect") openTemplateSelectionDialog(); + else if (id === "templateSelectButton") openTemplateSelectionDialog(); else if (id === "zoomExtentDefault") restoreDefaultZoomExtent(); else if (id === "translateExtent") toggleTranslateExtent(event.target); else if (id === "speakerTest") testSpeaker();