Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.vscode
.idea
.idea/Fantasy-Map-Generator.iml
node_modules
24 changes: 19 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,20 @@
</td>
</tr>

<tr data-tip="Select grid generation algorithm">
<td></td>
<td>Grid type</td>
<td>
<select id="gridAlgorithm">
<option value="jitteredGridPoints" selected>Default</option>
<option value="hexPointsF">Hex flat</option>
<option value="hexPointsP">Hex pointy</option>
<option value="squarePoints">Squares</option>
</select>
</td>
<td></td>
</tr>

<tr data-tip="Define map name (will be used to name downloaded files)">
<td>
<i data-locked="0" id="lock_mapName" class="icon-lock-open"></i>
Expand Down Expand Up @@ -7781,7 +7795,7 @@
<script src="utils/commonUtils.js"></script>
<script src="utils/arrayUtils.js?v=02062022"></script>
<script src="utils/colorUtils.js"></script>
<script src="utils/graphUtils.js?v=02062022"></script>
<script src="utils/graphUtils.js?v=06192022"></script>
<script src="utils/nodeUtils.js"></script>
<script src="utils/numberUtils.js"></script>
<script src="utils/polyfills.js"></script>
Expand All @@ -7805,7 +7819,7 @@
<script src="modules/military-generator.js"></script>
<script src="modules/markers-generator.js"></script>
<script src="modules/coa-generator.js"></script>
<script src="modules/submap.js?v=02062022"></script>
<script src="modules/submap.js?v=06192022"></script>
<script src="libs/polylabel.min.js"></script>
<script src="libs/lineclip.min.js"></script>
<script src="libs/alea.min.js"></script>
Expand Down Expand Up @@ -7852,14 +7866,14 @@
<script defer src="modules/ui/emblems-editor.js"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/3d.js"></script>
<script defer src="modules/ui/submap.js?v=29052022"></script>
<script defer src="modules/ui/submap.js?v=06192022"></script>
<script defer src="modules/ui/hotkeys.js?v=17062022"></script>
<script defer src="modules/coa-renderer.js"></script>
<script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>

<script defer src="modules/io/save.js?v=29052022"></script>
<script defer src="modules/io/load.js?v=12062022"></script>
<script defer src="modules/io/save.js?v=06122022"></script>
<script defer src="modules/io/load.js?v=06122022"></script>
<script defer src="modules/io/cloud.js?v=04062022"></script>
<script defer src="modules/io/export.js?v=04062022"></script>
<script defer src="modules/io/formats.js"></script>
Expand Down
15 changes: 11 additions & 4 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ void (function addDragToUpload() {
});
})();

const gridOptimizationRequired = () => globalThis[byId('gridAlgorithm').value] == jitteredGridPoints;
async function generate(options) {
try {
const timeStart = performance.now();
Expand All @@ -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);

Expand Down Expand Up @@ -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()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it can be a simple check:

const gridType = document.getElementById('gridType')?.value;
const optimizeWaterCells = gridType === "jiterred";

Copy link
Copy Markdown
Collaborator Author

@goteguru goteguru Jun 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's not exactly the same, and not a good design. String constants of the module (which is non-existent here of course :) shouldn't be used outside the module. One always should export an accessor (like gridOptimizationRequired) or define constants. Using functions instead of those "magic strings" has an additional advantage: your IDE can identify misspelling.
Of course there is no module, therefore the whole question is academic, but this accessor is a reminder to do the proper thing when the stuff will finally become a module.
We can remove it if you don't like it, but that one has a purpose.

Copy link
Copy Markdown
Owner

@Azgaar Azgaar Jun 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I agree, but in this case gridOptimizationRequired should not be in main.js. We can have a separate module for points generation and put this function there. Something like 'points-generator'

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course it shouldn't.There are quite a lot of stuff what shouldn't be there. :) It started as a small patch not a full blown refactoring project. That's the other branch. ^-^


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) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If optimization is not required, probably the whole rePack is not required as well

Copy link
Copy Markdown
Collaborator Author

@goteguru goteguru Jun 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. you are absolutely right. But that pack object is super coupled with er... everything. I didn't dare to eliminate it. This is the safe bet. When we rewrite this part, this can be changed. If you are absolutely sure there will be no problem (you know the code much better than me) let's change it.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but we already can do a minor refactoring, leaving the resulting pack object as is. We can extract the logic for points repacking into a separate function and call it conditionally. If optimization is not required, then just return existing p, g and h arrays (one day will have to rename these craziness 😀)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that was my first intention, but I was lazy to do it. It would certainly save some bytes but memory is cheap even in case of 100k maps (SVG eats several orders of magnitude more ram).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more of a refactoring issue. Adding more options we make the code harder to understand, so if anything new is added, we need to make the old part simpler to not increase the entropy too much

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;
Expand Down
4 changes: 3 additions & 1 deletion modules/dynamic/heightmap-selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions modules/io/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
3 changes: 2 additions & 1 deletion modules/io/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -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("|");
Expand Down
2 changes: 1 addition & 1 deletion modules/submap.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ window.Submap = (function () {

// create new grid
applyMapSize();
grid = generateGrid();
grid = generateGrid(options.cellsDesired, options.gridAlgorithm);

drawScaleBar(scale);

Expand Down
6 changes: 5 additions & 1 deletion modules/ui/submap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
Expand All @@ -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
Expand Down
84 changes: 75 additions & 9 deletions utils/graphUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,34 @@
// 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;

const newSpacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2);
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
Expand All @@ -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");
Expand Down Expand Up @@ -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
Expand Down