mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
Merge 66004de6f4 into 00abd5213b
This commit is contained in:
commit
cf7b977e1c
8 changed files with 272 additions and 83 deletions
25
index.html
25
index.html
|
|
@ -1616,6 +1616,22 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select points sampling algorithm. Defines structure of the underlying graph">
|
||||
<td>
|
||||
<i data-locked="0" id="lock_gridType" class="icon-lock-open"></i>
|
||||
</td>
|
||||
<td>Grid type</td>
|
||||
<td>
|
||||
<select id="gridType" data-stored="gridType">
|
||||
<option value="jittered" selected>Jittered</option>
|
||||
<option value="hexFlat">Hex flat</option>
|
||||
<option value="hexPointy">Hex pointy</option>
|
||||
<option value="square">Square</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>
|
||||
|
|
@ -8080,7 +8096,7 @@
|
|||
<script src="utils/commonUtils.js?v=1.89.29"></script>
|
||||
<script src="utils/arrayUtils.js"></script>
|
||||
<script src="utils/colorUtils.js"></script>
|
||||
<script src="utils/graphUtils.js?v=1.96.00"></script>
|
||||
<script src="utils/graphUtils.js?v=1.99.00"></script>
|
||||
<script src="utils/nodeUtils.js"></script>
|
||||
<script src="utils/numberUtils.js?v=1.89.08"></script>
|
||||
<script src="utils/polyfills.js?v=1.95.03"></script>
|
||||
|
|
@ -8090,6 +8106,7 @@
|
|||
<script src="utils/unitUtils.js?v=1.87.00"></script>
|
||||
|
||||
<script src="modules/voronoi.js"></script>
|
||||
<script src="modules/points-generator.js"></script>
|
||||
<script src="config/heightmap-templates.js"></script>
|
||||
<script src="config/precreated-heightmaps.js"></script>
|
||||
<script src="modules/heightmap-generator.js?v=1.88.00"></script>
|
||||
|
|
@ -8117,7 +8134,7 @@
|
|||
|
||||
<script src="modules/ui/general.js?v=1.98.01"></script>
|
||||
<script src="modules/ui/options.js?v=1.98.04"></script>
|
||||
<script src="main.js?v=1.98.01"></script>
|
||||
<script src="main.js?v=1.99.00"></script>
|
||||
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/style.js?v=1.96.00"></script>
|
||||
|
|
@ -8158,8 +8175,8 @@
|
|||
<script defer src="modules/coa-renderer.js?v=1.94.00"></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=1.98.01"></script>
|
||||
<script defer src="modules/io/load.js?v=1.98.01"></script>
|
||||
<script defer src="modules/io/save.js?v=1.99.00"></script>
|
||||
<script defer src="modules/io/load.js?v=1.99.00"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.96.00"></script>
|
||||
<script defer src="modules/io/export.js?v=1.98.05"></script>
|
||||
|
||||
|
|
|
|||
92
main.js
92
main.js
|
|
@ -310,12 +310,58 @@ async function checkLoadParameters() {
|
|||
generateMapOnLoad();
|
||||
}
|
||||
|
||||
function debugGrids() {
|
||||
// debug
|
||||
// .selectAll("circle.grid")
|
||||
// .data(grid.points)
|
||||
// .enter()
|
||||
// .append("circle")
|
||||
// .attr("data-id", (d, i) => "point-" + i)
|
||||
// .attr("cx", d => d[0])
|
||||
// .attr("cy", d => d[1])
|
||||
// .attr("r", 0.5)
|
||||
// .attr("fill", "blue");
|
||||
|
||||
let path = "";
|
||||
grid.cells.i.forEach(i => (path += "M" + getGridPolygon(i)));
|
||||
debug.append("path").attr("fill", "none").attr("stroke", "blue").attr("stroke-width", 0.3).attr("d", path);
|
||||
|
||||
// debug
|
||||
// .selectAll("circle.boundary")
|
||||
// .data(grid.boundary)
|
||||
// .enter()
|
||||
// .append("circle")
|
||||
// .attr("cx", d => d[0])
|
||||
// .attr("cy", d => d[1])
|
||||
// .attr("r", 0.3)
|
||||
// .attr("fill", "white");
|
||||
|
||||
zoom.translateExtent([
|
||||
[-graphWidth / 2, -graphHeight / 2],
|
||||
[graphWidth * 1.5, graphHeight * 1.5]
|
||||
]);
|
||||
|
||||
const text = debug
|
||||
.append("g")
|
||||
.style("font-size", "0.5px")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "central");
|
||||
|
||||
for (let x = 0; x < 100; x++) {
|
||||
for (let y = 0; y < 100; y++) {
|
||||
const cellId = findGridCell(x, y, grid);
|
||||
text.append("text").attr("x", x).attr("y", y).text(cellId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function generateMapOnLoad() {
|
||||
await applyStyleOnLoad(); // apply previously selected default or custom style
|
||||
await generate(); // generate map
|
||||
applyPreset(); // apply saved layers preset
|
||||
fitMapToScreen();
|
||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||
debugGrids();
|
||||
}
|
||||
|
||||
// focus on coordinates, cell or burg provided in searchParams
|
||||
|
|
@ -1034,15 +1080,22 @@ function generatePrecipitation() {
|
|||
const MAX_PASSABLE_ELEVATION = 85;
|
||||
|
||||
// define wind directions based on cells latitude and prevailing winds there
|
||||
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
|
||||
d3.range(0, cells.i.length, cellsX).forEach(function (cellId, i) {
|
||||
// debug
|
||||
// .append("circle")
|
||||
// .attr("cx", grid.points[cellId][0])
|
||||
// .attr("cy", grid.points[cellId][1])
|
||||
// .attr("r", 2)
|
||||
// .attr("fill", "blue");
|
||||
|
||||
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
|
||||
const latBand = ((Math.abs(lat) - 1) / 5) | 0;
|
||||
const latMod = latitudeModifier[latBand];
|
||||
const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
|
||||
const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
|
||||
|
||||
if (isWest) westerly.push([c, latMod, windTier]);
|
||||
if (isEast) easterly.push([c + cellsX - 1, latMod, windTier]);
|
||||
if (isWest) westerly.push([cellId, latMod, windTier]);
|
||||
if (isEast) easterly.push([cellId + cellsX - 1, latMod, windTier]);
|
||||
if (isNorth) northerly++;
|
||||
if (isSouth) southerly++;
|
||||
});
|
||||
|
|
@ -1066,6 +1119,8 @@ function generatePrecipitation() {
|
|||
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
|
||||
}
|
||||
|
||||
drawWindDirection();
|
||||
|
||||
function getWindDirections(tier) {
|
||||
const angle = options.winds[tier];
|
||||
|
||||
|
|
@ -1120,24 +1175,25 @@ function generatePrecipitation() {
|
|||
return minmax(normalLoss + diff * mod, 1, humidity);
|
||||
}
|
||||
|
||||
void (function drawWindDirection() {
|
||||
function drawWindDirection() {
|
||||
const wind = prec.append("g").attr("id", "wind");
|
||||
|
||||
d3.range(0, 6).forEach(function (t) {
|
||||
options.winds.forEach((direction, tier) => {
|
||||
if (westerly.length > 1) {
|
||||
const west = westerly.filter(w => w[2] === t);
|
||||
const west = westerly.filter(w => w[2] === tier);
|
||||
if (west && west.length > 3) {
|
||||
const from = west[0][0],
|
||||
to = west[west.length - 1][0];
|
||||
const from = west.at(0)[0];
|
||||
const to = west.at(-1)[0];
|
||||
const y = (grid.points[from][1] + grid.points[to][1]) / 2;
|
||||
wind.append("text").attr("x", 20).attr("y", y).text("\u21C9");
|
||||
}
|
||||
}
|
||||
|
||||
if (easterly.length > 1) {
|
||||
const east = easterly.filter(w => w[2] === t);
|
||||
const east = easterly.filter(w => w[2] === tier);
|
||||
if (east && east.length > 3) {
|
||||
const from = east[0][0],
|
||||
to = east[east.length - 1][0];
|
||||
const from = east.at(0)[0];
|
||||
const to = east.at(-1)[0];
|
||||
const y = (grid.points[from][1] + grid.points[to][1]) / 2;
|
||||
wind
|
||||
.append("text")
|
||||
|
|
@ -1160,7 +1216,7 @@ function generatePrecipitation() {
|
|||
.attr("x", graphWidth / 2)
|
||||
.attr("y", graphHeight - 20)
|
||||
.text("\u21C8");
|
||||
})();
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generatePrecipitation");
|
||||
}
|
||||
|
|
@ -1169,20 +1225,24 @@ function generatePrecipitation() {
|
|||
function reGraph() {
|
||||
TIME && console.time("reGraph");
|
||||
const {cells: gridCells, points, features} = grid;
|
||||
const repackGridCells = grid.type === "jittered";
|
||||
const newCells = {p: [], g: [], h: []}; // store new data
|
||||
const spacing2 = grid.spacing ** 2;
|
||||
|
||||
for (const i of gridCells.i) {
|
||||
const height = gridCells.h[i];
|
||||
const type = gridCells.t[i];
|
||||
|
||||
if (repackGridCells) {
|
||||
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];
|
||||
}
|
||||
|
||||
const [x, y] = points[i];
|
||||
addNewPoint(i, x, y, height);
|
||||
|
||||
// add additional points for cells along coast
|
||||
if (type === 1 || type === -1) {
|
||||
if (repackGridCells && (type === 1 || type === -1)) {
|
||||
if (gridCells.b[i]) continue; // not for near-border cells
|
||||
gridCells.c[i].forEach(function (e) {
|
||||
if (i > e) return;
|
||||
|
|
@ -1253,6 +1313,8 @@ function drawCoastline() {
|
|||
vchain.map(v => vertices.p[v]),
|
||||
1
|
||||
);
|
||||
if (points.length < 3) debugger;
|
||||
|
||||
const area = d3.polygonArea(points); // area with lakes/islands
|
||||
if (area > 0 && features[f].type === "lake") {
|
||||
points = points.reverse();
|
||||
|
|
@ -1962,6 +2024,8 @@ const regenerateMap = debounce(async function (options) {
|
|||
fitMapToScreen();
|
||||
shouldShowLoading && hideLoading();
|
||||
clearMainTip();
|
||||
|
||||
debugGrids();
|
||||
}, 250);
|
||||
|
||||
// clear the map
|
||||
|
|
|
|||
|
|
@ -858,4 +858,9 @@ export function resolveVersionConflicts(version) {
|
|||
shiftCompass();
|
||||
}
|
||||
}
|
||||
|
||||
if (version < 1.99) {
|
||||
// v1.99.00 added alternative graph point sampling methods
|
||||
if (!graph.type) graph.type = "jittered";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
{
|
||||
// dynamically import and run auto-update script
|
||||
const versionNumber = parseFloat(params[0]);
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.98.00");
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.99.00");
|
||||
resolveVersionConflicts(versionNumber);
|
||||
}
|
||||
|
||||
|
|
|
|||
101
modules/points-generator.js
Normal file
101
modules/points-generator.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"use strict";
|
||||
|
||||
const pointsGenerators = {
|
||||
jittered: generateJitteredPoints,
|
||||
hexFlat: generateHexFlatPoints,
|
||||
hexPointy: generateHexPointyPoints,
|
||||
square: generateSquarePoints
|
||||
};
|
||||
|
||||
function generatePoints() {
|
||||
TIME && console.time("placePoints");
|
||||
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||
const spacing = Math.sqrt((graphWidth * graphHeight) / cellsDesired); // spacing between points
|
||||
const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
|
||||
|
||||
const type = byId("gridType").value;
|
||||
const {points, cellsX, cellsY} = pointsGenerators[type](graphWidth, graphHeight, spacing);
|
||||
TIME && console.timeEnd("placePoints");
|
||||
|
||||
return {spacing, cellsDesired, type, boundary, points, cellsX, cellsY};
|
||||
}
|
||||
|
||||
function generateJitteredPoints(width, height, spacing) {
|
||||
return generateSquareJitteredPoints(0.9, width, height, spacing);
|
||||
}
|
||||
|
||||
function generateSquarePoints(width, height, spacing) {
|
||||
return generateSquareJitteredPoints(0, width, height, spacing);
|
||||
}
|
||||
|
||||
function generateSquareJitteredPoints(jittering, width, height, spacing) {
|
||||
const radius = spacing / 2;
|
||||
const maxDeviation = radius * jittering;
|
||||
const jitter = () => (jittering ? Math.random() * maxDeviation * 2 - maxDeviation : 0);
|
||||
|
||||
let points = [];
|
||||
for (let y = radius; y < height; y += spacing) {
|
||||
for (let x = radius; x < width; x += spacing) {
|
||||
const xj = Math.min(rn(x + jitter(), 2), width);
|
||||
const yj = Math.min(rn(y + jitter(), 2), height);
|
||||
points.push([xj, yj]);
|
||||
}
|
||||
}
|
||||
|
||||
const cellsX = Math.floor((width + 0.5 * spacing - 1e-10) / spacing);
|
||||
const cellsY = Math.floor((height + 0.5 * spacing - 1e-10) / spacing);
|
||||
return {points, cellsX, cellsY};
|
||||
}
|
||||
|
||||
function generateHexFlatPoints(width, height, spacing) {
|
||||
return generateHexPoints(false, width, height, spacing);
|
||||
}
|
||||
|
||||
function generateHexPointyPoints(width, height, spacing) {
|
||||
return generateHexPoints(true, width, height, spacing);
|
||||
}
|
||||
|
||||
function generateHexPoints(isPointy, width, height, spacing) {
|
||||
const hexRatio = Math.sqrt(3) / 2;
|
||||
const spacingX = isPointy ? spacing / hexRatio : spacing * 2;
|
||||
const spacingY = isPointy ? spacing : spacing / hexRatio / 2;
|
||||
const maxWidth = width + spacingX / 2;
|
||||
const maxHeight = height + spacingY / 2;
|
||||
const indentionX = spacingX / 2;
|
||||
|
||||
let points = [];
|
||||
for (let y = 0, row = 0; y < maxHeight; y += spacingY, row++) {
|
||||
for (let x = row % 2 ? 0 : indentionX; x < maxWidth; x += spacingX) {
|
||||
if (x > width) x = width;
|
||||
if (y > height) y = height;
|
||||
points.push([rn(x, 2), rn(y, 2)]);
|
||||
}
|
||||
}
|
||||
|
||||
const cellsX = Math.ceil(width / spacingX);
|
||||
const cellsY = Math.ceil(height / spacingY);
|
||||
return {points, cellsX, cellsY};
|
||||
}
|
||||
|
||||
// add points along map edge to pseudo-clip voronoi cells
|
||||
function getBoundaryPoints(width, height, spacing) {
|
||||
const offset = rn(-1 * spacing);
|
||||
const bSpacing = spacing * 2;
|
||||
const w = width - offset * 2;
|
||||
const h = height - offset * 2;
|
||||
const numberX = Math.ceil(w / bSpacing) - 1;
|
||||
const numberY = Math.ceil(h / bSpacing) - 1;
|
||||
const points = [];
|
||||
|
||||
for (let i = 0.5; i < numberX; i++) {
|
||||
let x = Math.ceil((w * i) / numberX + offset);
|
||||
points.push([x, offset], [x, h + offset]);
|
||||
}
|
||||
|
||||
for (let i = 0.5; i < numberY; i++) {
|
||||
let y = Math.ceil((h * i) / numberY + offset);
|
||||
points.push([offset, y], [w + offset, y]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
|
@ -120,6 +120,8 @@ function showMapTooltip(point, e, i, g) {
|
|||
const subgroup = path[path.length - 8].id;
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// console.log(findGridCell(point[0], point[1], grid));
|
||||
|
||||
// specific elements
|
||||
if (group === "armies") return tip(e.target.parentNode.dataset.name + ". Click to edit");
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ function shouldRegenerateGrid(grid, expectedSeed) {
|
|||
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||
if (cellsDesired !== grid.cellsDesired) return true;
|
||||
|
||||
const gridType = byId("gridType").value;
|
||||
if (gridType !== grid.type) 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);
|
||||
|
|
@ -17,24 +20,9 @@ function shouldRegenerateGrid(grid, expectedSeed) {
|
|||
|
||||
function generateGrid() {
|
||||
Math.random = aleaPRNG(seed); // reset PRNG
|
||||
const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
|
||||
const {spacing, cellsDesired, type, boundary, points, cellsX, cellsY} = generatePoints();
|
||||
const {cells, vertices} = calculateVoronoi(points, boundary);
|
||||
return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices, seed};
|
||||
}
|
||||
|
||||
// place random points to calculate Voronoi diagram
|
||||
function placePoints() {
|
||||
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 boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
|
||||
const points = getJitteredGrid(graphWidth, graphHeight, spacing); // points of jittered square grid
|
||||
const cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
|
||||
const cellsY = Math.floor((graphHeight + 0.5 * spacing - 1e-10) / spacing);
|
||||
TIME && console.timeEnd("placePoints");
|
||||
|
||||
return {spacing, cellsDesired, boundary, points, cellsX, cellsY};
|
||||
return {spacing, cellsDesired, type, boundary, points, cellsX, cellsY, cells, vertices, seed};
|
||||
}
|
||||
|
||||
// calculate Delaunay and then Voronoi diagram
|
||||
|
|
@ -55,49 +43,63 @@ function calculateVoronoi(points, boundary) {
|
|||
return {cells, vertices};
|
||||
}
|
||||
|
||||
// add points along map edge to pseudo-clip voronoi cells
|
||||
function getBoundaryPoints(width, height, spacing) {
|
||||
const offset = rn(-1 * spacing);
|
||||
const bSpacing = spacing * 2;
|
||||
const w = width - offset * 2;
|
||||
const h = height - offset * 2;
|
||||
const numberX = Math.ceil(w / bSpacing) - 1;
|
||||
const numberY = Math.ceil(h / bSpacing) - 1;
|
||||
const points = [];
|
||||
|
||||
for (let i = 0.5; i < numberX; i++) {
|
||||
let x = Math.ceil((w * i) / numberX + offset);
|
||||
points.push([x, offset], [x, h + offset]);
|
||||
}
|
||||
|
||||
for (let i = 0.5; i < numberY; i++) {
|
||||
let y = Math.ceil((h * i) / numberY + offset);
|
||||
points.push([offset, y], [w + offset, y]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
// get points on a regular square grid and jitter them a bit
|
||||
function getJitteredGrid(width, height, spacing) {
|
||||
const radius = spacing / 2; // square radius
|
||||
const jittering = radius * 0.9; // max deviation
|
||||
const doubleJittering = jittering * 2;
|
||||
const jitter = () => Math.random() * doubleJittering - jittering;
|
||||
|
||||
let points = [];
|
||||
for (let y = radius; y < height; y += spacing) {
|
||||
for (let x = radius; x < width; x += spacing) {
|
||||
const xj = Math.min(rn(x + jitter(), 2), width);
|
||||
const yj = Math.min(rn(y + jitter(), 2), height);
|
||||
points.push([xj, yj]);
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
// return cell index on a regular square grid
|
||||
function findGridCell(x, y, grid) {
|
||||
if (grid.type === "hexFlat") return findHexCellIndex(x, y, false, grid.spacing, grid.cellsX);
|
||||
if (grid.type === "hexPointy") return findHexCellIndex(x, y, true, grid.spacing, grid.cellsX);
|
||||
return findSquareGridCell(x, y, grid);
|
||||
}
|
||||
|
||||
const hexRatio = Math.sqrt(3) / 2;
|
||||
function findHexCellIndex(x, y, isPointy, spacing, cellsX) {
|
||||
const spacingX = isPointy ? spacing / hexRatio : spacing * 2;
|
||||
const spacingY = isPointy ? spacing : spacing / hexRatio / 2;
|
||||
|
||||
let col = Math.floor(x / spacingX);
|
||||
let row = Math.floor((y + spacingY * 1.5) / spacingY);
|
||||
|
||||
if (isPointy) {
|
||||
if (row % 2 === 1 && x < col * spacingX + spacingX / 2) col -= 1;
|
||||
} else {
|
||||
if (col % 2 === 1 && y < row * spacingY + spacingY / 2) row -= 1;
|
||||
}
|
||||
|
||||
const suspect = row * cellsX + col;
|
||||
const candidates = isPointy
|
||||
? [
|
||||
suspect,
|
||||
suspect - cellsX - 1,
|
||||
suspect - cellsX,
|
||||
suspect - 1,
|
||||
suspect + 1,
|
||||
suspect + cellsX - 1,
|
||||
suspect + cellsX
|
||||
]
|
||||
: [
|
||||
suspect,
|
||||
suspect - cellsX,
|
||||
suspect - cellsX * 2,
|
||||
suspect - cellsX + 1,
|
||||
suspect + cellsX,
|
||||
suspect + cellsX + 1,
|
||||
suspect + cellsX * 2
|
||||
];
|
||||
|
||||
const closest = candidates.reduce(
|
||||
(acc, candidate) => {
|
||||
const point = grid.points[candidate];
|
||||
if (!point) return acc;
|
||||
const dist2 = Math.abs(x - point[0]) + Math.abs(y - point[1]);
|
||||
if (dist2 < acc.dist2) return {dist2, cell: candidate};
|
||||
return acc;
|
||||
},
|
||||
{dist2: Infinity, cell: suspect}
|
||||
);
|
||||
|
||||
return closest.cell;
|
||||
}
|
||||
|
||||
function findSquareGridCell(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))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
// version and caching control
|
||||
const version = "1.98.05"; // generator version, update each time
|
||||
const version = "1.99.00"; // generator version, update each time
|
||||
|
||||
{
|
||||
document.title += " v" + version;
|
||||
|
|
@ -28,6 +28,7 @@ const version = "1.98.05"; // generator version, update each time
|
|||
|
||||
<ul>
|
||||
<strong>Latest changes:</strong>
|
||||
<li>Alternative grids (advanced option)</li>
|
||||
<li>Configurable longitude</li>
|
||||
<li>Preview villages map</li>
|
||||
<li>Ability to render ocean heightmap</li>
|
||||
|
|
@ -39,9 +40,6 @@ const version = "1.98.05"; // generator version, update each time
|
|||
<li>Auto-load of the last saved map is now optional (see <i>Onload behavior</i> in Options)</li>
|
||||
<li>New label placement algorithm for states</li>
|
||||
<li>North and South Poles temperature can be set independently</li>
|
||||
<li>More than 70 new heraldic charges</li>
|
||||
<li>Multi-color heraldic charges support</li>
|
||||
<li>New 3D scene options and improvements</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue