generation flow for heightmap select

This commit is contained in:
Azgaar 2022-05-26 00:19:32 +03:00
parent ff31e23a27
commit 6766de46ef
9 changed files with 132 additions and 100 deletions

View file

@ -297,7 +297,7 @@
id="regenerate" id="regenerate"
data-t="tipRegenerate" data-t="tipRegenerate"
data-tip="Click to generate a new map. Shortcut: F2" data-tip="Click to generate a new map. Shortcut: F2"
onclick="regeneratePrompt('drawer')" onclick="regeneratePrompt()"
class="options" class="options"
style="display: none" style="display: none"
> >

62
main.js
View file

@ -628,18 +628,24 @@ void (function addDragToUpload() {
}); });
})(); })();
async function generate() { async function generate(options) {
try { try {
const timeStart = performance.now(); const timeStart = performance.now();
const {seed: precreatedSeed} = options || {};
invokeActiveZooming(); invokeActiveZooming();
generateSeed(); setSeed(precreatedSeed);
INFO && console.group("Generated Map " + seed); INFO && console.group("Generated Map " + seed);
applyMapSize(); applyMapSize();
randomizeOptions(); randomizeOptions();
placePoints();
calculateVoronoi(grid, grid.points); if (shouldRegenerateGrid()) {
drawScaleBar(scale); placePoints();
calculateVoronoi(grid, grid.points);
}
await HeightmapGenerator.generate(); await HeightmapGenerator.generate();
markFeatures(); markFeatures();
markupGridOcean(); markupGridOcean();
addLakesInDeepDepressions(); addLakesInDeepDepressions();
@ -677,6 +683,8 @@ async function generate() {
Military.generate(); Military.generate();
Markers.generate(); Markers.generate();
addZones(); addZones();
drawScaleBar(scale);
Names.getMapName(); Names.getMapName();
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
@ -711,27 +719,41 @@ async function generate() {
} }
} }
// generate map seed (string!) or get it from URL searchParams // set map seed (string!)
function generateSeed() { function setSeed(precreatedSeed) {
const first = !mapHistory[0]; if (!precreatedSeed) {
const url = new URL(window.location.href); const first = !mapHistory[0];
const params = url.searchParams; const url = new URL(window.location.href);
const urlSeed = url.searchParams.get("seed"); const params = url.searchParams;
if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0, -4); const urlSeed = url.searchParams.get("seed");
else if (first && urlSeed) seed = urlSeed; if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0, -4);
else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value; else if (first && urlSeed) seed = urlSeed;
else seed = Math.floor(Math.random() * 1e9).toString(); else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value;
optionsSeed.value = seed; else seed = generateSeed();
} else {
seed = precreatedSeed;
}
byId("optionsSeed").value = seed;
Math.random = aleaPRNG(seed); Math.random = aleaPRNG(seed);
} }
// check if new grid graph should be generated or we can use the existing one
function shouldRegenerateGrid() {
if (!grid.spacing) return true;
const cellsDesired = +byId("pointsInput").dataset.cells;
const newSpacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2);
return grid.spacing !== newSpacing;
}
// Place points to calculate Voronoi diagram // Place points to calculate Voronoi diagram
function placePoints() { function placePoints() {
TIME && console.time("placePoints"); TIME && console.time("placePoints");
Math.random = aleaPRNG(seed); // reset PRNG Math.random = aleaPRNG(seed); // reset PRNG
const cellsDesired = +pointsInput.dataset.cells; const cellsDesired = +byId("pointsInput").dataset.cells;
const spacing = (grid.spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2)); // spacing between points before jirrering const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering
grid.spacing = spacing;
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing); grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
grid.cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing); grid.cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
@ -1921,14 +1943,14 @@ function showStatistics() {
INFO && console.log(stats); INFO && console.log(stats);
} }
const regenerateMap = debounce(async function () { const regenerateMap = debounce(async function (options) {
WARN && console.warn("Generate new random map"); WARN && console.warn("Generate new random map");
showLoading(); showLoading();
closeDialogs("#worldConfigurator, #options3d"); closeDialogs("#worldConfigurator, #options3d");
customization = 0; customization = 0;
resetZoom(1000); resetZoom(1000);
undraw(); undraw();
await generate(); await generate(options);
restoreLayers(); restoreLayers();
if (ThreeD.options.isOn) ThreeD.redraw(); if (ThreeD.options.isOn) ThreeD.redraw();
if ($("#worldConfigurator").is(":visible")) editWorld(); if ($("#worldConfigurator").is(":visible")) editWorld();

View file

@ -1077,7 +1077,7 @@ window.BurgsAndStates = (function () {
const generateProvinces = function (regenerate) { const generateProvinces = function (regenerate) {
TIME && console.time("generateProvinces"); TIME && console.time("generateProvinces");
const localSeed = regenerate ? Math.floor(Math.random() * 1e9).toString() : seed; const localSeed = regenerate ? generateSeed() : seed;
Math.random = aleaPRNG(localSeed); Math.random = aleaPRNG(localSeed);
const {cells, states, burgs} = pack; const {cells, states, burgs} = pack;

View file

@ -41,8 +41,6 @@ const heightmaps = [
{id: "world-from-pacific", name: "World from Pacific"} {id: "world-from-pacific", name: "World from Pacific"}
]; ];
let seed = Math.floor(Math.random() * 1e9);
appendStyleSheet(); appendStyleSheet();
insertEditorHtml(); insertEditorHtml();
addListeners(); addListeners();
@ -62,7 +60,20 @@ export function open() {
$(this).dialog("close"); $(this).dialog("close");
}, },
Select: function () { Select: function () {
$templateInput.value = getSelected(); const id = getSelected();
$templateInput.value = id;
lock("template");
$(this).dialog("close");
},
"New Map": function () {
const id = getSelected();
$templateInput.value = id;
lock("template");
const seed = getSeed();
Math.random = aleaPRNG(seed);
regeneratePrompt({seed});
$(this).dialog("close"); $(this).dialog("close");
} }
} }
@ -133,13 +144,12 @@ function appendStyleSheet() {
} }
function insertEditorHtml() { function insertEditorHtml() {
const seed = generateSeed();
const templatesHtml = templates const templatesHtml = templates
.map(({id, name}) => { .map(({id, name}) => {
Math.random = aleaPRNG(seed); Math.random = aleaPRNG(seed);
const heights = generateHeightmap(id);
HeightmapGenerator.resetHeights();
const heights = HeightmapGenerator.fromTemplate(id);
HeightmapGenerator.cleanup();
const dataUrl = drawHeights(heights); const dataUrl = drawHeights(heights);
return /* html */ `<article data-id="${id}" data-seed="${seed}"> return /* html */ `<article data-id="${id}" data-seed="${seed}">
@ -154,7 +164,7 @@ function insertEditorHtml() {
const heightmapsHtml = heightmaps const heightmapsHtml = heightmaps
.map(({id, name}) => { .map(({id, name}) => {
return /* html */ `<article data-id="${id}"> return /* html */ `<article data-id="${id}" data-seed="${seed}">
<img src="../../heightmaps/${id}.png" alt="${name}" class="heightmap-selection_precreated" /> <img src="../../heightmaps/${id}.png" alt="${name}" class="heightmap-selection_precreated" />
<div>${name}</div> <div>${name}</div>
</article>`; </article>`;
@ -202,6 +212,10 @@ function setSelected(id) {
$heightmapSelection.querySelector(`[data-id="${id}"]`)?.classList?.add("selected"); $heightmapSelection.querySelector(`[data-id="${id}"]`)?.classList?.add("selected");
} }
function getSeed() {
return byId("heightmapSelection").querySelector(".selected")?.dataset?.seed;
}
function drawHeights(heights) { function drawHeights(heights) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = grid.cellsX; canvas.width = grid.cellsX;
@ -222,14 +236,19 @@ function drawHeights(heights) {
return canvas.toDataURL("image/png"); return canvas.toDataURL("image/png");
} }
function regeneratePreview(article, id) { function generateHeightmap(id) {
seed = Math.floor(Math.random() * 1e9);
article.dataset.seed = seed;
Math.random = aleaPRNG(seed);
HeightmapGenerator.resetHeights(); HeightmapGenerator.resetHeights();
const heights = HeightmapGenerator.fromTemplate(id); const heights = HeightmapGenerator.fromTemplate(id);
HeightmapGenerator.cleanup(); HeightmapGenerator.cleanup();
return heights;
}
function regeneratePreview(article, id) {
const seed = generateSeed();
article.dataset.seed = seed;
Math.random = aleaPRNG(seed);
const heights = generateHeightmap(id);
const dataUrl = drawHeights(heights); const dataUrl = drawHeights(heights);
article.querySelector("img").src = dataUrl; article.querySelector("img").src = dataUrl;
} }

View file

@ -7,61 +7,6 @@ window.HeightmapGenerator = (function () {
const getHeights = () => heights; const getHeights = () => heights;
const cleanup = () => (heights = null); const cleanup = () => (heights = null);
const generate = async function () {
resetHeights();
const input = document.getElementById("templateInput");
const selectedId = input.selectedIndex >= 0 ? input.selectedIndex : 0;
const type = input.options[selectedId]?.parentElement?.label;
if (type === "Specific") {
// pre-defined heightmap
TIME && console.time("defineHeightmap");
return new Promise(resolve => {
// create canvas where 1px correcponds to a cell
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const {cellsX, cellsY} = grid;
canvas.width = cellsX;
canvas.height = cellsY;
// load heightmap into image and render to canvas
const img = new Image();
img.src = `./heightmaps/${input.value}.png`;
img.onload = () => {
ctx.drawImage(img, 0, 0, cellsX, cellsY);
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
assignColorsToHeight(imageData.data);
canvas.remove();
img.remove();
grid.cells.h = heights;
cleanup();
TIME && console.timeEnd("defineHeightmap");
resolve();
};
});
}
// heightmap template
TIME && console.time("generateHeightmap");
const template = input.value;
const templateString = HeightmapTemplates[template];
const steps = templateString.split("\n");
if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${template}. Steps: ${steps}`);
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}`);
addStep(...elements);
}
grid.cells.h = heights;
cleanup();
TIME && console.timeEnd("generateHeightmap");
};
const fromTemplate = template => { const fromTemplate = template => {
const templateString = HeightmapTemplates[template]; const templateString = HeightmapTemplates[template];
const steps = templateString.split("\n"); const steps = templateString.split("\n");
@ -77,6 +22,49 @@ window.HeightmapGenerator = (function () {
return heights; return heights;
}; };
const fromPrecreated = id => {
return new Promise(resolve => {
TIME && console.time("defineHeightmap");
// create canvas where 1px corresponts to a cell
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const {cellsX, cellsY} = grid;
canvas.width = cellsX;
canvas.height = cellsY;
// load heightmap into image and render to canvas
const img = new Image();
img.src = `./heightmaps/${id}.png`;
img.onload = () => {
ctx.drawImage(img, 0, 0, cellsX, cellsY);
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
assignColorsToHeight(imageData.data);
canvas.remove();
img.remove();
grid.cells.h = heights;
cleanup();
TIME && console.timeEnd("defineHeightmap");
resolve();
};
});
};
const generate = async function () {
Math.random = aleaPRNG(seed);
resetHeights();
const id = byId("templateInput").value;
if (HeightmapTemplates[id]) {
TIME && console.time("generateHeightmap");
grid.cells.h = fromTemplate(id);
cleanup();
TIME && console.timeEnd("generateHeightmap");
} else {
return fromPrecreated(id);
}
};
function addStep(tool, a2, a3, a4, a5) { function addStep(tool, a2, a3, a4, a5) {
if (tool === "Hill") return addHill(a2, a3, a4, a5); if (tool === "Hill") return addHill(a2, a3, a4, a5);
if (tool === "Pit") return addPit(a2, a3, a4, a5); if (tool === "Pit") return addPit(a2, a3, a4, a5);

View file

@ -25,7 +25,7 @@ function handleKeyup(event) {
const alt = altKey || key === "Alt"; const alt = altKey || key === "Alt";
if (code === "F1") showInfo(); if (code === "F1") showInfo();
else if (code === "F2") regeneratePrompt("hotkey"); else if (code === "F2") regeneratePrompt();
else if (code === "F6") quickSave(); else if (code === "F6") quickSave();
else if (code === "F9") quickLoad(); else if (code === "F9") quickLoad();
else if (code === "Tab") toggleOptions(event); else if (code === "Tab") toggleOptions(event);

View file

@ -249,9 +249,9 @@ function testSpeaker() {
speechSynthesis.speak(speaker); speechSynthesis.speak(speaker);
} }
function generateMapWithSeed(source) { function generateMapWithSeed() {
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error"); if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
regeneratePrompt(source); regeneratePrompt();
} }
function showSeedHistoryDialog() { function showSeedHistoryDialog() {
@ -280,7 +280,7 @@ function restoreSeed(id) {
mapHeightInput.value = mapHistory[id].height; mapHeightInput.value = mapHistory[id].height;
templateInput.value = mapHistory[id].template; templateInput.value = mapHistory[id].template;
if (locked("template")) unlock("template"); if (locked("template")) unlock("template");
regeneratePrompt("seed history"); regeneratePrompt();
} }
function restoreDefaultZoomExtent() { function restoreDefaultZoomExtent() {
@ -513,7 +513,6 @@ function applyStoredOptions() {
// randomize options if randomization is allowed (not locked or options='default') // randomize options if randomization is allowed (not locked or options='default')
function randomizeOptions() { function randomizeOptions() {
Math.random = aleaPRNG(seed); // reset seed to initial one
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
// 'Options' settings // 'Options' settings
@ -638,17 +637,17 @@ function restoreDefaultOptions() {
// Sticked menu Options listeners // Sticked menu Options listeners
document.getElementById("sticked").addEventListener("click", function (event) { document.getElementById("sticked").addEventListener("click", function (event) {
const id = event.target.id; const id = event.target.id;
if (id === "newMapButton") regeneratePrompt("sticky button"); if (id === "newMapButton") regeneratePrompt();
else if (id === "saveButton") showSavePane(); else if (id === "saveButton") showSavePane();
else if (id === "exportButton") showExportPane(); else if (id === "exportButton") showExportPane();
else if (id === "loadButton") showLoadPane(); else if (id === "loadButton") showLoadPane();
else if (id === "zoomReset") resetZoom(1000); else if (id === "zoomReset") resetZoom(1000);
}); });
function regeneratePrompt(source) { function regeneratePrompt(options) {
if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error"); if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
if (workingTime < 5) return regenerateMap(source); if (workingTime < 5) return regenerateMap(options);
alertMessage.innerHTML = /* html */ `Are you sure you want to generate a new map?<br /> alertMessage.innerHTML = /* html */ `Are you sure you want to generate a new map?<br />
All unsaved changes made to the current map will be lost`; All unsaved changes made to the current map will be lost`;
@ -661,7 +660,7 @@ function regeneratePrompt(source) {
}, },
Generate: function () { Generate: function () {
closeDialogs(); closeDialogs();
regenerateMap(source); regenerateMap(options);
} }
} }
}); });

View file

@ -137,7 +137,7 @@ function recalculatePopulation() {
} }
function regenerateStates() { function regenerateStates() {
const localSeed = Math.floor(Math.random() * 1e9); // new random seed const localSeed = generateSeed();
Math.random = aleaPRNG(localSeed); Math.random = aleaPRNG(localSeed);
const statesCount = +regionsOutput.value; const statesCount = +regionsOutput.value;

View file

@ -74,3 +74,7 @@ function getNumberInRange(r) {
} }
return count; return count;
} }
function generateSeed() {
return String(Math.floor(Math.random() * 1e9));
}