diff --git a/.gitignore b/.gitignore
index 8cd42a0de..b7ca7adb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
.vscode
.idea
.idea/Fantasy-Map-Generator.iml
+node_modules
diff --git a/index.html b/index.html
index dc39e1dd8..ae2335349 100644
--- a/index.html
+++ b/index.html
@@ -1500,6 +1500,20 @@
+
|
@@ -7781,7 +7795,7 @@
-
+
@@ -7805,7 +7819,7 @@
-
+
@@ -7852,14 +7866,14 @@
-
+
-
-
+
+
diff --git a/main.js b/main.js
index f443111d2..f9c4bc46f 100644
--- a/main.js
+++ b/main.js
@@ -631,6 +631,7 @@ void (function addDragToUpload() {
});
})();
+const gridOptimizationRequired = () => globalThis[byId('gridAlgorithm').value] == jitteredGridPoints;
async function generate(options) {
try {
const timeStart = performance.now();
@@ -642,8 +643,10 @@ async function generate(options) {
applyMapSize();
randomizeOptions();
+ const method = globalThis[byId('gridAlgorithm').value];
- if (shouldRegenerateGrid(grid)) grid = precreatedGraph || generateGrid();
+ const cellsDesired = +byId("pointsInput").dataset.cells;
+ if (shouldRegenerateGrid(grid, method)) grid = precreatedGraph || generateGrid(cellsDesired, method);
else delete grid.cells.h;
grid.cells.h = await HeightmapGenerator.generate(grid);
@@ -1164,23 +1167,27 @@ function generatePrecipitation() {
}
// recalculate Voronoi Graph to pack cells
+// pars: optimize -> optimize cells structure, copy original graph otherwise.
function reGraph() {
TIME && console.time("reGraph");
const {cells: gridCells, points, features} = grid;
const newCells = {p: [], g: [], h: []}; // store new data
const spacing2 = grid.spacing ** 2;
+ const optimize = gridOptimizationRequired()
for (const i of gridCells.i) {
const height = gridCells.h[i];
const type = gridCells.t[i];
- if (height < 20 && type !== -1 && type !== -2) continue; // exclude all deep ocean points
- if (type === -2 && (i % 4 === 0 || features[gridCells.f[i]].type === "lake")) continue; // exclude non-coastal lake points
+ if (optimize) {
+ if (height < 20 && type !== -1 && type !== -2) continue; // exclude all deep ocean points
+ if (type === -2 && (i % 4 === 0 || features[gridCells.f[i]].type === "lake")) continue; // exclude non-coastal lake points
+ }
const [x, y] = points[i];
addNewPoint(i, x, y, height);
// add additional points for cells along coast
- if (type === 1 || type === -1) {
+ if (optimize && (type === 1 || type === -1)) {
if (gridCells.b[i]) continue; // not for near-border cells
gridCells.c[i].forEach(function (e) {
if (i > e) return;
diff --git a/modules/dynamic/heightmap-selection.js b/modules/dynamic/heightmap-selection.js
index 5ca94e61d..7c048d7c5 100644
--- a/modules/dynamic/heightmap-selection.js
+++ b/modules/dynamic/heightmap-selection.js
@@ -263,7 +263,9 @@ function getName(id) {
}
function getGraph(currentGraph) {
- const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : deepCopy(currentGraph);
+ const newGraph = shouldRegenerateGrid(currentGraph)
+ ? generateGrid(currentGraph.cellsDesired, currentGraph.generator)
+ : deepCopy(currentGraph);
delete newGraph.cells.h;
return newGraph;
}
diff --git a/modules/io/load.js b/modules/io/load.js
index 946df93f4..c6dbf26bc 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -229,6 +229,7 @@ async function parseLoadedData(data) {
if (settings[22]) stylePreset.value = settings[22];
if (settings[23]) rescaleLabels.checked = +settings[23];
if (settings[24]) urbanDensity = urbanDensityInput.value = urbanDensityOutput.value = +settings[24];
+ if (settings[25]) gridAlgorithm.value = settings[25];
})();
void (function applyOptionsToUI() {
diff --git a/modules/io/save.js b/modules/io/save.js
index db303eff5..4c14101e5 100644
--- a/modules/io/save.js
+++ b/modules/io/save.js
@@ -34,7 +34,8 @@ function getMapData() {
+hideLabels.checked,
stylePreset.value,
+rescaleLabels.checked,
- urbanDensity
+ urbanDensity,
+ grid.generator.name,
].join("|");
const coords = JSON.stringify(mapCoordinates);
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
diff --git a/modules/submap.js b/modules/submap.js
index 854084872..71f33df0c 100644
--- a/modules/submap.js
+++ b/modules/submap.js
@@ -40,7 +40,7 @@ window.Submap = (function () {
// create new grid
applyMapSize();
- grid = generateGrid();
+ grid = generateGrid(options.cellsDesired, options.gridAlgorithm);
drawScaleBar(scale);
diff --git a/modules/ui/submap.js b/modules/ui/submap.js
index 737560c38..e367d0344 100644
--- a/modules/ui/submap.js
+++ b/modules/ui/submap.js
@@ -203,6 +203,8 @@ window.UISubmap = (function () {
smoothHeightMap: false,
rescaleStyles: false,
scale: 1,
+ gridAlgorithm: globalThis[byId('gridAlgorithm').value],
+ cellsDesired: +byId("pointsInput").dataset.cells,
projection,
inverse
});
@@ -229,7 +231,9 @@ window.UISubmap = (function () {
smoothHeightMap: scale > 2,
inverse: (x, y) => [x / origScale + x0, y / origScale + y0],
projection: (x, y) => [(x - x0) * origScale, (y - y0) * origScale],
- scale: origScale
+ scale: origScale,
+ gridAlgorithm: globalThis[byId('gridAlgorithm').value],
+ cellsDesired: +byId("pointsInput").dataset.cells,
};
// converting map position on the planet
diff --git a/utils/graphUtils.js b/utils/graphUtils.js
index 75b642530..0cfc3cf58 100644
--- a/utils/graphUtils.js
+++ b/utils/graphUtils.js
@@ -2,7 +2,7 @@
// FMG utils related to graph
// check if new grid graph should be generated or we can use the existing one
-function shouldRegenerateGrid(grid) {
+function shouldRegenerateGrid(grid, method) {
const cellsDesired = +byId("pointsInput").dataset.cells;
if (cellsDesired !== grid.cellsDesired) return true;
@@ -10,21 +10,26 @@ function shouldRegenerateGrid(grid) {
const newCellsX = Math.floor((graphWidth + 0.5 * newSpacing - 1e-10) / newSpacing);
const newCellsY = Math.floor((graphHeight + 0.5 * newSpacing - 1e-10) / newSpacing);
- return grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
+ return grid.generator !== method || grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
}
-function generateGrid() {
+function generateGrid(numberOfCells, generator = jitteredGridPoints) {
Math.random = aleaPRNG(seed); // reset PRNG
- const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
+ //const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
+ const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = generator(numberOfCells);
const {cells, vertices} = calculateVoronoi(points, boundary);
- return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices};
+ return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices, generator};
+}
+
+function squareSpacing() {
+ const cellsDesired = +byId("pointsInput").dataset.cells;
+ return rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jittering
}
// place random points to calculate Voronoi diagram
-function placePoints() {
+function jitteredGridPoints(cellsDesired) {
TIME && console.time("placePoints");
- const cellsDesired = +byId("pointsInput").dataset.cells;
- const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering
+ const spacing = squareSpacing();
const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
const points = getJitteredGrid(graphWidth, graphHeight, spacing); // points of jittered square grid
@@ -35,6 +40,55 @@ function placePoints() {
return {spacing, cellsDesired, boundary, points, cellsX, cellsY};
}
+// alternatively generate hex-grid
+const hexRatio = Math.sqrt(3)/2;
+function hexPointsP(cellsDesired) {
+ const spacing = squareSpacing();
+ return hexPoints(cellsDesired, spacing / hexRatio , spacing);
+}
+function hexPointsF(cellsDesired) {
+ const spacing = squareSpacing();
+ return hexPoints(cellsDesired, spacing * 2, spacing / hexRatio / 2);
+}
+
+function hexPoints(cellsDesired, xSpacing, ySpacing) {
+ TIME && console.time("hexPoints");
+ const spacing = (xSpacing + ySpacing) / 2;
+
+ const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
+ let points = [];
+
+ let rc, lc, x, y;
+ for (y = ySpacing / 2, lc = 0 ; y < graphHeight; y += ySpacing, lc++) {
+ for (x = lc % 2 ? 0 : xSpacing / 2, rc=0; x < graphWidth; x += xSpacing, rc++) {
+ points.push([x, y]);
+ }
+ }
+
+ TIME && console.timeEnd("hexPoints");
+ return {spacing, cellsDesired, boundary, points, cellsX: rc, cellsY: lc};
+}
+
+// square grid
+function squarePoints() {
+ TIME && console.time("squarePoints");
+ const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2);
+
+ const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
+ const cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
+ const cellsY = Math.floor((graphHeight + 0.5 * spacing - 1e-10) / spacing);
+
+ const radius = spacing / 2;
+
+ let points = [];
+ for (let y = radius; y < graphHeight; y += spacing)
+ for (let x = radius; x < graphWidth; x += spacing)
+ points.push([x, y]);
+
+ TIME && console.timeEnd("squarePoints");
+ return {spacing, cellsDesired, boundary, points, cellsX, cellsY};
+}
+
// calculate Delaunay and then Voronoi diagram
function calculateVoronoi(points, boundary) {
TIME && console.time("calculateDelaunay");
@@ -96,7 +150,19 @@ function getJitteredGrid(width, height, spacing) {
// return cell index on a regular square grid
function findGridCell(x, y, grid) {
- return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
+ let xSpacing = grid.spacing;
+ let ySpacing = grid.spacing * Math.sqrt(3) / 2;
+ const maxindex = grid.cells.i.length; // safety belt
+ switch (grid.generator) {
+ case jitteredGridPoints:
+ case squarePoints:
+ return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
+ case hexPointsF:
+ xSpacing = grid.spacing * hexRatio;
+ ySpacing = grid.spacing / 2;
+ case hexPointsP:
+ return Math.min(Math.floor(Math.min(y / ySpacing + 1e-10, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / xSpacing + 1e-10, grid.cellsX - 1)), maxindex);
+ }
}
// return array of cell indexes in radius on a regular square grid
|