From e3da664e5690d141218147870d68682f0d4dfcbf Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Jun 2021 16:42:47 +0300 Subject: [PATCH] template editor: allow single value --- index.html | 4 +- modules/heightmap-generator.js | 368 ++++++++++++++++++++------------- modules/ui/heightmap-editor.js | 3 +- 3 files changed, 233 insertions(+), 142 deletions(-) diff --git a/index.html b/index.html index db3487e7..4d9f90cb 100644 --- a/index.html +++ b/index.html @@ -2322,8 +2322,8 @@
Hill
- y: - x: + y: + x: h: n: diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js index d9943819..8e0dc42a 100644 --- a/modules/heightmap-generator.js +++ b/modules/heightmap-generator.js @@ -1,49 +1,72 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.HeightmapGenerator = factory()); -}(this, (function () { 'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.HeightmapGenerator = factory()); +})(this, function () { + "use strict"; let cells, p; - const generate = function() { - TIME && console.time('generateHeightmap'); - cells = grid.cells, p = grid.points; + const generate = function () { + TIME && console.time("generateHeightmap"); + cells = grid.cells; + p = grid.points; cells.h = new Uint8Array(grid.points.length); - switch (document.getElementById("templateInput").value) { - case "Volcano": templateVolcano(); break; - case "High Island": templateHighIsland(); break; - case "Low Island": templateLowIsland(); break; - case "Continents": templateContinents(); break; - case "Archipelago": templateArchipelago(); break; - case "Atoll": templateAtoll(); break; - case "Mediterranean": templateMediterranean(); break; - case "Peninsula": templatePeninsula(); break; - case "Pangea": templatePangea(); break; - case "Isthmus": templateIsthmus(); break; - case "Shattered": templateShattered(); break; + const template = document.getElementById("templateInput").value; + switch (template) { + case "Volcano": + templateVolcano(); + break; + case "High Island": + templateHighIsland(); + break; + case "Low Island": + templateLowIsland(); + break; + case "Continents": + templateContinents(); + break; + case "Archipelago": + templateArchipelago(); + break; + case "Atoll": + templateAtoll(); + break; + case "Mediterranean": + templateMediterranean(); + break; + case "Peninsula": + templatePeninsula(); + break; + case "Pangea": + templatePangea(); + break; + case "Isthmus": + templateIsthmus(); + break; + case "Shattered": + templateShattered(); + break; } - TIME && console.timeEnd('generateHeightmap'); - } + TIME && console.timeEnd("generateHeightmap"); + }; // parse template step function addStep(a1, a2, a3, a4, a5) { - if (a1 === "Hill") addHill(a2, a3, a4, a5); else - if (a1 === "Pit") addPit(a2, a3, a4, a5); else - if (a1 === "Range") addRange(a2, a3, a4, a5); else - if (a1 === "Trough") addTrough(a2, a3, a4, a5); else - if (a1 === "Strait") addStrait(a2, a3); else - if (a1 === "Add") modify(a3, a2, 1); else - if (a1 === "Multiply") modify(a3, 0, a2); else - if (a1 === "Smooth") smooth(a2); + 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 === "Add") return modify(a3, a2, 1); + if (a1 === "Multiply") return modify(a3, 0, a2); + if (a1 === "Smooth") return smooth(a2); } // Heighmap Template: Volcano function templateVolcano() { addStep("Hill", "1", "90-100", "44-56", "40-60"); - addStep("Multiply", .8, "50-100"); + addStep("Multiply", 0.8, "50-100"); addStep("Range", "1.5", "30-55", "45-55", "40-60"); addStep("Smooth", 2); addStep("Hill", "1.5", "25-35", "25-30", "20-75"); @@ -62,7 +85,7 @@ addStep("Trough", "2-3", "20-30", "60-80", "70-80"); addStep("Hill", "1", "10-15", "60-60", "50-50"); addStep("Hill", "1.5", "13-16", "15-20", "20-75"); - addStep("Multiply", .8, "20-100"); + addStep("Multiply", 0.8, "20-100"); addStep("Range", "1.5", "30-40", "15-85", "30-40"); addStep("Range", "1.5", "30-40", "15-85", "60-70"); addStep("Pit", "2-3", "10-15", "15-85", "20-80"); @@ -79,14 +102,14 @@ addStep("Hill", "1.5", "10-15", "5-15", "20-80"); addStep("Hill", "1", "10-15", "85-95", "70-80"); addStep("Pit", "3-5", "10-15", "15-85", "20-80"); - addStep("Multiply", .4, "20-100"); + addStep("Multiply", 0.4, "20-100"); } // Heighmap Template: Continents function templateContinents() { addStep("Hill", "1", "80-85", "75-80", "40-60"); addStep("Hill", "1", "80-85", "20-25", "40-60"); - addStep("Multiply", .22, "20-100"); + addStep("Multiply", 0.22, "20-100"); addStep("Hill", "5-6", "15-20", "25-75", "20-82"); addStep("Range", ".8", "30-60", "5-15", "20-45"); addStep("Range", ".8", "30-60", "5-15", "55-80"); @@ -118,7 +141,7 @@ addStep("Hill", "1.5", "30-50", "25-75", "30-70"); addStep("Hill", ".5", "30-50", "25-35", "30-70"); addStep("Smooth", 1); - addStep("Multiply", .2, "25-100"); + addStep("Multiply", 0.2, "25-100"); addStep("Hill", ".5", "10-20", "50-55", "48-52"); } @@ -131,7 +154,7 @@ addStep("Smooth", 1); addStep("Hill", "2-3", "30-70", "0-5", "20-80"); addStep("Hill", "2-3", "30-70", "95-100", "20-80"); - addStep("Multiply", .8, "land"); + addStep("Multiply", 0.8, "land"); addStep("Trough", "3-5", "40-50", "0-100", "0-10"); addStep("Trough", "3-5", "40-50", "0-100", "90-100"); } @@ -156,7 +179,7 @@ addStep("Hill", "1-2", "5-40", "15-50", "90-100"); addStep("Hill", "8-12", "20-40", "20-80", "48-52"); addStep("Smooth", 2); - addStep("Multiply", .7, "land"); + addStep("Multiply", 0.7, "land"); addStep("Trough", "3-4", "25-35", "5-95", "10-20"); addStep("Trough", "3-4", "25-35", "5-95", "80-90"); addStep("Range", "5-6", "30-40", "10-90", "35-65"); @@ -187,48 +210,78 @@ function getBlobPower() { switch (+pointsInput.dataset.cells) { - case 1000: return .93; - case 2000: return .95; - case 5000: return .96; - case 10000: return .98; - case 20000: return .985; - case 30000: return .987; - case 40000: return .9892; - case 50000: return .9911; - case 60000: return .9921; - case 70000: return .9934; - case 80000: return .9942; - case 90000: return .9946; - case 100000: return .995; + case 1000: + return 0.93; + case 2000: + return 0.95; + case 5000: + return 0.96; + case 10000: + return 0.98; + case 20000: + return 0.985; + case 30000: + return 0.987; + case 40000: + return 0.9892; + case 50000: + return 0.9911; + case 60000: + return 0.9921; + case 70000: + return 0.9934; + case 80000: + return 0.9942; + case 90000: + return 0.9946; + case 100000: + return 0.995; } } function getLinePower() { switch (+pointsInput.dataset.cells) { - case 1000: return .74; - case 2000: return .75; - case 5000: return .78; - case 10000: return .81; - case 20000: return .82; - case 30000: return .83; - case 40000: return .84; - case 50000: return .855; - case 60000: return .87; - case 70000: return .885; - case 80000: return .91; - case 90000: return .92; - case 100000: return .93; + case 1000: + return 0.74; + case 2000: + return 0.75; + case 5000: + return 0.78; + case 10000: + return 0.81; + case 20000: + return 0.82; + case 30000: + return 0.83; + case 40000: + return 0.84; + case 50000: + return 0.855; + case 60000: + return 0.87; + case 70000: + return 0.885; + case 80000: + return 0.91; + case 90000: + return 0.92; + case 100000: + return 0.93; } } - const addHill = function(count, height, rangeX, rangeY) { + const addHill = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getBlobPower(); - while (count > 0) {addOneHill(); count--;} + while (count > 0) { + addOneHill(); + count--; + } function addOneHill() { - const change = new Uint8Array( cells.h.length); - let limit = 0, start; + const change = new Uint8Array(cells.h.length); + let limit = 0, + start; let h = lim(getNumberInRange(height)); do { @@ -236,7 +289,7 @@ const y = getPointInRange(rangeY, graphHeight); start = findGridCell(x, y); limit++; - } while (cells.h[start] + h > 90 && limit < 50) + } while (cells.h[start] + h > 90 && limit < 50); change[start] = h; const queue = [start]; @@ -245,23 +298,26 @@ for (const c of cells.c[q]) { if (change[c]) continue; - change[c] = change[q] ** power * (Math.random() * .2 + .9); + change[c] = change[q] ** power * (Math.random() * 0.2 + 0.9); if (change[c] > 1) queue.push(c); } } cells.h = cells.h.map((h, i) => lim(h + change[i])); } + }; - } - - const addPit = function(count, height, rangeX, rangeY) { + const addPit = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); - while (count > 0) {addOnePit(); count--;} + while (count > 0) { + addOnePit(); + count--; + } function addOnePit() { const used = new Uint8Array(cells.h.length); - let limit = 0, start; + let limit = 0, + start; let h = lim(getNumberInRange(height)); do { @@ -269,28 +325,31 @@ const y = getPointInRange(rangeY, graphHeight); start = findGridCell(x, y); limit++; - } while (cells.h[start] < 20 && limit < 50) + } while (cells.h[start] < 20 && limit < 50); const queue = [start]; while (queue.length) { const q = queue.shift(); - h = h ** getBlobPower() * (Math.random() * .2 + .9); + h = h ** getBlobPower() * (Math.random() * 0.2 + 0.9); if (h < 1) return; - cells.c[q].forEach(function(c, i) { + cells.c[q].forEach(function (c, i) { if (used[c]) return; - cells.h[c] = lim(cells.h[c] - h * (Math.random() * .2 + .9)); + cells.h[c] = lim(cells.h[c] - h * (Math.random() * 0.2 + 0.9)); used[c] = 1; queue.push(c); }); } } - } + }; - const addRange = function(count, height, rangeX, rangeY) { + const addRange = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getLinePower(); - while (count > 0) {addOneRange(); count--;} + while (count > 0) { + addOneRange(); + count--; + } function addOneRange() { const used = new Uint8Array(cells.h.length); @@ -300,13 +359,16 @@ const startX = getPointInRange(rangeX, graphWidth); const startY = getPointInRange(rangeY, graphHeight); - let dist = 0, limit = 0, endX, endY; + let dist = 0, + limit = 0, + endX, + endY; do { - endX = Math.random() * graphWidth * .8 + graphWidth * .1; - endY = Math.random() * graphHeight * .7 + graphHeight * .15; + endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1; + endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15; dist = Math.abs(endY - startY) + Math.abs(endX - startX); limit++; - } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50) + } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50); let range = getRange(findGridCell(startX, startY), findGridCell(endX, endY)); @@ -317,11 +379,14 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + 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() > .85) diff = diff / 2; - if (diff < min) {min = diff; cur = e;} + if (Math.random() > 0.85) diff = diff / 2; + if (diff < min) { + min = diff; + cur = e; + } }); if (min === Infinity) return range; range.push(cur); @@ -332,25 +397,29 @@ } // add height to ridge and cells around - let queue = range.slice(), i = 0; + let queue = range.slice(), + i = 0; while (queue.length) { const frontier = queue.slice(); - queue = [], i++; + (queue = []), i++; frontier.forEach(i => { - cells.h[i] = lim(cells.h[i] + h * (Math.random() * .3 + .85)); + cells.h[i] = lim(cells.h[i] + h * (Math.random() * 0.3 + 0.85)); }); h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { cells.c[f].forEach(i => { - if (!used[i]) {queue.push(i); used[i] = 1;} + if (!used[i]) { + queue.push(i); + used[i] = 1; + } }); }); } // generate prominences range.forEach((cur, d) => { - if (d%6 !== 0) return; + if (d % 6 !== 0) return; for (const l of d3.range(i)) { const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); @@ -358,35 +427,43 @@ cur = min; } }); - } - } + }; - const addTrough = function(count, height, rangeX, rangeY) { + const addTrough = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getLinePower(); - while (count > 0) {addOneTrough(); count--;} + while (count > 0) { + addOneTrough(); + count--; + } function addOneTrough() { const used = new Uint8Array(cells.h.length); let h = lim(getNumberInRange(height)); // find start and end points - let limit = 0, startX, startY, start, dist = 0, endX, endY; + let limit = 0, + startX, + startY, + start, + dist = 0, + endX, + endY; do { startX = getPointInRange(rangeX, graphWidth); startY = getPointInRange(rangeY, graphHeight); start = findGridCell(startX, startY); limit++; - } while (cells.h[start] < 20 && limit < 50) + } while (cells.h[start] < 20 && limit < 50); limit = 0; do { - endX = Math.random() * graphWidth * .8 + graphWidth * .1; - endY = Math.random() * graphHeight * .7 + graphHeight * .15; + endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1; + endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15; dist = Math.abs(endY - startY) + Math.abs(endX - startX); limit++; - } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50) + } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50); let range = getRange(start, findGridCell(endX, endY)); @@ -397,11 +474,14 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + 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() > .8) diff = diff / 2; - if (diff < min) {min = diff; cur = e;} + if (Math.random() > 0.8) diff = diff / 2; + if (diff < min) { + min = diff; + cur = e; + } }); if (min === Infinity) return range; range.push(cur); @@ -412,25 +492,29 @@ } // add height to ridge and cells around - let queue = range.slice(), i = 0; + let queue = range.slice(), + i = 0; while (queue.length) { const frontier = queue.slice(); - queue = [], i++; + (queue = []), i++; frontier.forEach(i => { - cells.h[i] = lim(cells.h[i] - h * (Math.random() * .3 + .85)); + cells.h[i] = lim(cells.h[i] - h * (Math.random() * 0.3 + 0.85)); }); h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { cells.c[f].forEach(i => { - if (!used[i]) {queue.push(i); used[i] = 1;} + if (!used[i]) { + queue.push(i); + used[i] = 1; + } }); }); } // generate prominences range.forEach((cur, d) => { - if (d%6 !== 0) return; + if (d % 6 !== 0) return; for (const l of d3.range(i)) { const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); @@ -438,21 +522,21 @@ cur = min; } }); - } - } + }; - const addStrait = function(width, direction = "vertical") { - width = Math.min(getNumberInRange(width), grid.cellsX/3); + const addStrait = function (width, direction = "vertical") { + width = Math.min(getNumberInRange(width), grid.cellsX / 3); if (width < 1 && P(width)) return; const used = new Uint8Array(cells.h.length); const vert = direction === "vertical"; - const startX = vert ? Math.floor(Math.random() * graphWidth * .4 + graphWidth * .3) : 5; - const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * .4 + graphHeight * .3); - const endX = vert ? Math.floor((graphWidth - startX) - (graphWidth * .1) + (Math.random() * graphWidth * .2)) : graphWidth - 5; - const endY = vert ? graphHeight - 5 : Math.floor((graphHeight - startY) - (graphHeight * .1) + (Math.random() * graphHeight * .2)); + const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5; + const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3); + const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5; + const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2); - const start = findGridCell(startX, startY), end = findGridCell(endX, endY); + const start = findGridCell(startX, startY), + end = findGridCell(endX, endY); let range = getRange(start, end); const query = []; @@ -461,10 +545,13 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + 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) {min = diff; cur = e;} + if (diff < min) { + min = diff; + cur = e; + } }); range.push(cur); } @@ -472,12 +559,12 @@ return range; } - const step = .1 / width; + const step = 0.1 / width; while (width > 0) { - const exp = .9 - step * width; - range.forEach(function(r) { - cells.c[r].forEach(function(e) { + const exp = 0.9 - step * width; + range.forEach(function (r) { + cells.c[r].forEach(function (e) { if (used[e]) return; used[e] = 1; query.push(e); @@ -486,39 +573,42 @@ }); }); range = query.slice(); - + width--; } - } + }; - const modify = function(range, add, mult, power) { + const modify = function (range, add, mult, power) { const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0]; const max = range === "land" || range === "all" ? 100 : +range.split("-")[1]; - grid.cells.h = grid.cells.h.map(h => h >= min && h <= max ? mod(h) : h); + grid.cells.h = grid.cells.h.map(h => (h >= min && h <= max ? mod(h) : h)); function mod(v) { if (add) v = min === 20 ? Math.max(v + add, 20) : v + add; - if (mult !== 1) v = min === 20 ? (v-20) * mult + 20 : v * mult; - if (power) v = min === 20 ? (v-20) ** power + 20 : v ** power; + if (mult !== 1) v = min === 20 ? (v - 20) * mult + 20 : v * mult; + if (power) v = min === 20 ? (v - 20) ** power + 20 : v ** power; return lim(v); } - } + }; - const smooth = function(fr = 2, add = 0) { + const smooth = function (fr = 2, add = 0) { cells.h = cells.h.map((h, i) => { const a = [h]; cells.c[i].forEach(c => a.push(cells.h[c])); - return lim((h * (fr-1) + d3.mean(a) + add) / fr); + return lim((h * (fr - 1) + d3.mean(a) + add) / fr); }); - } + }; function getPointInRange(range, length) { - if (typeof range !== "string") {ERROR && console.error("Range should be a string"); return;} - const min = range.split("-")[0]/100 || 0; - const max = range.split("-")[1]/100 || 100; + if (typeof range !== "string") { + ERROR && console.error("Range should be a string"); + return; + } + + const min = range.split("-")[0] / 100 || 0; + const max = range.split("-")[1] / 100 || min; return rand(min * length, max * length); } return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify}; - -}))); +}); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 1b4e58bf..7c8cf5b1 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -950,7 +950,8 @@ function editHeightmap() { for (const s of steps) { if (s.style.opacity == 0.5) continue; - const type = s.getAttribute("data-type"); + const type = s.dataset.type; + const elCount = s.querySelector(".templateCount") || ""; const elHeight = s.querySelector(".templateHeight") || "";