diff --git a/index.html b/index.html
index 6898c955..b5bae9ca 100644
--- a/index.html
+++ b/index.html
@@ -297,7 +297,7 @@
id="regenerate"
data-t="tipRegenerate"
data-tip="Click to generate a new map. Shortcut: F2"
- onclick="regeneratePrompt('drawer')"
+ onclick="regeneratePrompt()"
class="options"
style="display: none"
>
diff --git a/main.js b/main.js
index afe54d88..e1d59b56 100644
--- a/main.js
+++ b/main.js
@@ -628,18 +628,24 @@ void (function addDragToUpload() {
});
})();
-async function generate() {
+async function generate(options) {
try {
const timeStart = performance.now();
+ const {seed: precreatedSeed} = options || {};
+
invokeActiveZooming();
- generateSeed();
+ setSeed(precreatedSeed);
INFO && console.group("Generated Map " + seed);
+
applyMapSize();
randomizeOptions();
- placePoints();
- calculateVoronoi(grid, grid.points);
- drawScaleBar(scale);
+
+ if (shouldRegenerateGrid()) {
+ placePoints();
+ calculateVoronoi(grid, grid.points);
+ }
await HeightmapGenerator.generate();
+
markFeatures();
markupGridOcean();
addLakesInDeepDepressions();
@@ -677,6 +683,8 @@ async function generate() {
Military.generate();
Markers.generate();
addZones();
+
+ drawScaleBar(scale);
Names.getMapName();
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
-function generateSeed() {
- const first = !mapHistory[0];
- const url = new URL(window.location.href);
- const params = url.searchParams;
- const urlSeed = url.searchParams.get("seed");
- if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0, -4);
- else if (first && urlSeed) seed = urlSeed;
- else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value;
- else seed = Math.floor(Math.random() * 1e9).toString();
- optionsSeed.value = seed;
+// set map seed (string!)
+function setSeed(precreatedSeed) {
+ if (!precreatedSeed) {
+ const first = !mapHistory[0];
+ const url = new URL(window.location.href);
+ const params = url.searchParams;
+ const urlSeed = url.searchParams.get("seed");
+ if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0, -4);
+ else if (first && urlSeed) seed = urlSeed;
+ else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value;
+ else seed = generateSeed();
+ } else {
+ seed = precreatedSeed;
+ }
+
+ byId("optionsSeed").value = 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
function placePoints() {
TIME && console.time("placePoints");
Math.random = aleaPRNG(seed); // reset PRNG
- const cellsDesired = +pointsInput.dataset.cells;
- const spacing = (grid.spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2)); // spacing between points before jirrering
+ const cellsDesired = +byId("pointsInput").dataset.cells;
+ const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering
+ grid.spacing = spacing;
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
grid.cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
@@ -1921,14 +1943,14 @@ function showStatistics() {
INFO && console.log(stats);
}
-const regenerateMap = debounce(async function () {
+const regenerateMap = debounce(async function (options) {
WARN && console.warn("Generate new random map");
showLoading();
closeDialogs("#worldConfigurator, #options3d");
customization = 0;
resetZoom(1000);
undraw();
- await generate();
+ await generate(options);
restoreLayers();
if (ThreeD.options.isOn) ThreeD.redraw();
if ($("#worldConfigurator").is(":visible")) editWorld();
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index 104d91b2..10a71148 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -1077,7 +1077,7 @@ window.BurgsAndStates = (function () {
const generateProvinces = function (regenerate) {
TIME && console.time("generateProvinces");
- const localSeed = regenerate ? Math.floor(Math.random() * 1e9).toString() : seed;
+ const localSeed = regenerate ? generateSeed() : seed;
Math.random = aleaPRNG(localSeed);
const {cells, states, burgs} = pack;
diff --git a/modules/dynamic/heightmap-selection.js b/modules/dynamic/heightmap-selection.js
index c9e28fb8..4dc631d7 100644
--- a/modules/dynamic/heightmap-selection.js
+++ b/modules/dynamic/heightmap-selection.js
@@ -41,8 +41,6 @@ const heightmaps = [
{id: "world-from-pacific", name: "World from Pacific"}
];
-let seed = Math.floor(Math.random() * 1e9);
-
appendStyleSheet();
insertEditorHtml();
addListeners();
@@ -62,7 +60,20 @@ export function open() {
$(this).dialog("close");
},
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");
}
}
@@ -133,13 +144,12 @@ function appendStyleSheet() {
}
function insertEditorHtml() {
+ const seed = generateSeed();
+
const templatesHtml = templates
.map(({id, name}) => {
Math.random = aleaPRNG(seed);
-
- HeightmapGenerator.resetHeights();
- const heights = HeightmapGenerator.fromTemplate(id);
- HeightmapGenerator.cleanup();
+ const heights = generateHeightmap(id);
const dataUrl = drawHeights(heights);
return /* html */ `
@@ -154,7 +164,7 @@ function insertEditorHtml() {
const heightmapsHtml = heightmaps
.map(({id, name}) => {
- return /* html */ `
+ return /* html */ `
${name}
`;
@@ -202,6 +212,10 @@ function setSelected(id) {
$heightmapSelection.querySelector(`[data-id="${id}"]`)?.classList?.add("selected");
}
+function getSeed() {
+ return byId("heightmapSelection").querySelector(".selected")?.dataset?.seed;
+}
+
function drawHeights(heights) {
const canvas = document.createElement("canvas");
canvas.width = grid.cellsX;
@@ -222,14 +236,19 @@ function drawHeights(heights) {
return canvas.toDataURL("image/png");
}
-function regeneratePreview(article, id) {
- seed = Math.floor(Math.random() * 1e9);
- article.dataset.seed = seed;
- Math.random = aleaPRNG(seed);
-
+function generateHeightmap(id) {
HeightmapGenerator.resetHeights();
const heights = HeightmapGenerator.fromTemplate(id);
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);
article.querySelector("img").src = dataUrl;
}
diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js
index 2213deb7..d8ec192d 100644
--- a/modules/heightmap-generator.js
+++ b/modules/heightmap-generator.js
@@ -7,61 +7,6 @@ window.HeightmapGenerator = (function () {
const getHeights = () => heights;
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 templateString = HeightmapTemplates[template];
const steps = templateString.split("\n");
@@ -77,6 +22,49 @@ window.HeightmapGenerator = (function () {
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) {
if (tool === "Hill") return addHill(a2, a3, a4, a5);
if (tool === "Pit") return addPit(a2, a3, a4, a5);
diff --git a/modules/ui/hotkeys.js b/modules/ui/hotkeys.js
index d9f0e234..9c9a4a7f 100644
--- a/modules/ui/hotkeys.js
+++ b/modules/ui/hotkeys.js
@@ -25,7 +25,7 @@ function handleKeyup(event) {
const alt = altKey || key === "Alt";
if (code === "F1") showInfo();
- else if (code === "F2") regeneratePrompt("hotkey");
+ else if (code === "F2") regeneratePrompt();
else if (code === "F6") quickSave();
else if (code === "F9") quickLoad();
else if (code === "Tab") toggleOptions(event);
diff --git a/modules/ui/options.js b/modules/ui/options.js
index fa82ed06..260cdea5 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -249,9 +249,9 @@ function testSpeaker() {
speechSynthesis.speak(speaker);
}
-function generateMapWithSeed(source) {
+function generateMapWithSeed() {
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
- regeneratePrompt(source);
+ regeneratePrompt();
}
function showSeedHistoryDialog() {
@@ -280,7 +280,7 @@ function restoreSeed(id) {
mapHeightInput.value = mapHistory[id].height;
templateInput.value = mapHistory[id].template;
if (locked("template")) unlock("template");
- regeneratePrompt("seed history");
+ regeneratePrompt();
}
function restoreDefaultZoomExtent() {
@@ -513,7 +513,6 @@ function applyStoredOptions() {
// randomize options if randomization is allowed (not locked or options='default')
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
// 'Options' settings
@@ -638,17 +637,17 @@ function restoreDefaultOptions() {
// Sticked menu Options listeners
document.getElementById("sticked").addEventListener("click", function (event) {
const id = event.target.id;
- if (id === "newMapButton") regeneratePrompt("sticky button");
+ if (id === "newMapButton") regeneratePrompt();
else if (id === "saveButton") showSavePane();
else if (id === "exportButton") showExportPane();
else if (id === "loadButton") showLoadPane();
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");
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?
All unsaved changes made to the current map will be lost`;
@@ -661,7 +660,7 @@ function regeneratePrompt(source) {
},
Generate: function () {
closeDialogs();
- regenerateMap(source);
+ regenerateMap(options);
}
}
});
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index 66eaa9ea..20041aaf 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -137,7 +137,7 @@ function recalculatePopulation() {
}
function regenerateStates() {
- const localSeed = Math.floor(Math.random() * 1e9); // new random seed
+ const localSeed = generateSeed();
Math.random = aleaPRNG(localSeed);
const statesCount = +regionsOutput.value;
diff --git a/utils/probabilityUtils.js b/utils/probabilityUtils.js
index 454b659c..7759e330 100644
--- a/utils/probabilityUtils.js
+++ b/utils/probabilityUtils.js
@@ -74,3 +74,7 @@ function getNumberInRange(r) {
}
return count;
}
+
+function generateSeed() {
+ return String(Math.floor(Math.random() * 1e9));
+}