diff --git a/src/dialogs/dialogs/heightmap-editor.js b/src/dialogs/dialogs/heightmap-editor.js index 16fb109e..61c9320a 100644 --- a/src/dialogs/dialogs/heightmap-editor.js +++ b/src/dialogs/dialogs/heightmap-editor.js @@ -14,7 +14,7 @@ import {changeViewMode} from "modules/ui/options"; import {addZones} from "modules/zones"; import {aleaPRNG} from "scripts/aleaPRNG"; import {setDefaultEventHandlers} from "scripts/events"; -import {undraw} from "scripts/generation"; +import {undraw} from "scripts/generation/generation"; import {prompt} from "scripts/prompt"; import {rankCells} from "scripts/rankCells"; import {reGraph} from "scripts/reGraph"; diff --git a/src/dialogs/dialogs/heightmap-selection.js b/src/dialogs/dialogs/heightmap-selection.js index 25717190..c6a0878c 100644 --- a/src/dialogs/dialogs/heightmap-selection.js +++ b/src/dialogs/dialogs/heightmap-selection.js @@ -2,7 +2,8 @@ import * as d3 from "d3"; import {heightmapTemplates} from "config/heightmap-templates"; import {precreatedHeightmaps} from "config/precreated-heightmaps"; -import {shouldRegenerateGridPoints, generateGrid} from "utils/graphUtils"; +import {generateGrid} from "utils/graphUtils"; +import {shouldRegenerateGridPoints} from "scripts/generation/generation"; import {byId} from "utils/shorthands"; import {generateSeed} from "utils/probabilityUtils"; import {getColorScheme} from "utils/colorUtils"; diff --git a/src/modules/io/load.js b/src/modules/io/load.js index 013e6a50..0a84c183 100644 --- a/src/modules/io/load.js +++ b/src/modules/io/load.js @@ -10,7 +10,7 @@ import {parseError} from "utils/errorUtils"; import {calculateVoronoi, findCell} from "utils/graphUtils"; import {link} from "utils/linkUtils"; import {minmax, rn} from "utils/numberUtils"; -import {regenerateMap} from "scripts/generation"; +import {regenerateMap} from "scripts/generation/generation"; import {reMarkFeatures} from "modules/markup"; import {closeDialogs} from "dialogs/utils"; diff --git a/src/modules/ui/options.js b/src/modules/ui/options.js index 75c20945..3287a334 100644 --- a/src/modules/ui/options.js +++ b/src/modules/ui/options.js @@ -9,7 +9,7 @@ import {applyDropdownOption} from "utils/nodeUtils"; import {minmax, rn} from "utils/numberUtils"; import {gauss, P, rand, rw} from "utils/probabilityUtils"; import {byId, stored} from "utils/shorthands"; -import {regenerateMap} from "scripts/generation"; +import {regenerateMap} from "scripts/generation/generation"; import {fitScaleBar} from "modules/measurers"; import {openDialog} from "dialogs"; import {closeDialogs} from "dialogs/utils"; diff --git a/src/modules/ui/submap.js b/src/modules/ui/submap.js index 4a32a928..8e3e1b8a 100644 --- a/src/modules/ui/submap.js +++ b/src/modules/ui/submap.js @@ -4,7 +4,7 @@ import {parseError} from "utils/errorUtils"; import {rn, minmax} from "utils/numberUtils"; import {debounce} from "utils/functionUtils"; import {restoreLayers} from "layers"; -import {undraw} from "scripts/generation"; +import {undraw} from "scripts/generation/generation"; import {closeDialogs} from "dialogs/utils"; window.UISubmap = (function () { diff --git a/src/scripts/generation.ts b/src/scripts/generation/generation.ts similarity index 70% rename from src/scripts/generation.ts rename to src/scripts/generation/generation.ts index 359dda98..f7cfd1c4 100644 --- a/src/scripts/generation.ts +++ b/src/scripts/generation/generation.ts @@ -5,14 +5,10 @@ import {closeDialogs} from "dialogs/utils"; import {initLayers, renderLayer, restoreLayers} from "layers"; // @ts-expect-error js module import {drawCoastline} from "modules/coastline"; -import {calculateMapCoordinates, defineMapSize} from "modules/coordinates"; -import {markupGridFeatures, reMarkFeatures} from "modules/markup"; +import {reMarkFeatures} from "modules/markup"; // @ts-expect-error js module import {drawScaleBar, Rulers} from "modules/measurers"; // @ts-expect-error js module -import {generatePrecipitation} from "modules/precipitation"; -import {calculateTemperatures} from "modules/temperature"; -// @ts-expect-error js module import {unfog} from "modules/ui/editors"; // @ts-expect-error js module import {applyMapSize, randomizeOptions} from "modules/ui/options"; @@ -26,37 +22,34 @@ import {hideLoading, showLoading} from "scripts/loading"; import {clearMainTip, tip} from "scripts/tooltips"; import {parseError} from "utils/errorUtils"; import {debounce} from "utils/functionUtils"; -import {generateGrid, shouldRegenerateGridPoints} from "utils/graphUtils"; import {rn} from "utils/numberUtils"; import {generateSeed} from "utils/probabilityUtils"; import {byId} from "utils/shorthands"; -import {rankCells} from "./rankCells"; +import {rankCells} from "../rankCells"; +import {showStatistics} from "../statistics"; +import {createGrid} from "./grid"; import {reGraph} from "./reGraph"; -import {showStatistics} from "./statistics"; -const {Zoom, Lakes, HeightmapGenerator, OceanLayers} = window; +const {Zoom, Lakes, OceanLayers, Rivers, Biomes, Cultures, BurgsAndStates, Religions, Military, Markers, Names} = + window; -interface IGenerationOptions { - seed: string; - graph: IGrid; -} - -export async function generate(options?: IGenerationOptions) { +async function generate(options?: {seed: string; graph: IGrid}) { try { const timeStart = performance.now(); const {seed: precreatedSeed, graph: precreatedGraph} = options || {}; Zoom?.invoke(); setSeed(precreatedSeed); + INFO && console.group("Generated Map " + seed); applyMapSize(); randomizeOptions(); - const updatedGrid = await updateGrid(grid, precreatedGraph); + const newGrid = await createGrid(grid, precreatedGraph); - const pack = reGraph(updatedGrid); - reMarkFeatures(pack, grid); + const pack = reGraph(newGrid); + reMarkFeatures(pack, newGrid); drawCoastline(); Rivers.generate(); @@ -84,73 +77,45 @@ export async function generate(options?: IGenerationOptions) { Markers.generate(); addZones(); - OceanLayers(updatedGrid); + OceanLayers(newGrid); - drawScaleBar(scale); + drawScaleBar(window.scale); Names.getMapName(); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); showStatistics(); - INFO && console.groupEnd("Generated Map " + seed); + INFO && console.groupEnd(); } catch (error) { - ERROR && console.error(error); - const parsedError = parseError(error); - clearMainTip(); - - alertMessage.innerHTML = /* html */ `An error has occurred on map generation. Please retry.
If error is critical, clear the stored data and try again. -

${parsedError}

`; - $("#alert").dialog({ - resizable: false, - title: "Generation error", - width: "32em", - buttons: { - "Clear data": function () { - localStorage.clear(); - localStorage.setItem("version", version); - }, - Regenerate: function () { - regenerateMap("generation error"); - $(this).dialog("close"); - }, - Ignore: function () { - $(this).dialog("close"); - } - }, - position: {my: "center", at: "center", of: "svg"} - }); + showGenerationError(error as Error); } } -async function updateGrid(globalGrid: IGrid, precreatedGraph?: IGrid): Promise { - const baseGrid: IGridBase = shouldRegenerateGridPoints(globalGrid) - ? (precreatedGraph && undressGrid(precreatedGraph)) || generateGrid() - : undressGrid(globalGrid); +function showGenerationError(error: Error) { + clearMainTip(); + ERROR && console.error(error); + const message = `An error has occurred on map generation. Please retry.
If error is critical, clear the stored data and try again. +

${parseError(error)}

`; + byId("alertMessage")!.innerHTML = message; - const heights: Uint8Array = await HeightmapGenerator.generate(baseGrid); - if (!heights) throw new Error("Heightmap generation failed"); - const heightsGrid = {...baseGrid, cells: {...baseGrid.cells, h: heights}}; - - const {featureIds, distanceField, features} = markupGridFeatures(heightsGrid); - const markedGrid = {...heightsGrid, features, cells: {...heightsGrid.cells, f: featureIds, t: distanceField}}; - - const touchesEdges = features.some(feature => feature && feature.land && feature.border); - defineMapSize(touchesEdges); - window.mapCoordinates = calculateMapCoordinates(); - - Lakes.addLakesInDeepDepressions(markedGrid); - Lakes.openNearSeaLakes(markedGrid); - - const temperature = calculateTemperatures(markedGrid); - const temperatureGrid = {...markedGrid, cells: {...markedGrid.cells, temp: temperature}}; - - const prec = generatePrecipitation(temperatureGrid); - return {...temperatureGrid, cells: {...temperatureGrid.cells, prec}}; -} - -function undressGrid(extendedGrid: IGrid): IGridBase { - const {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices} = extendedGrid; - const {i, b, c, v} = cells; - return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells: {i, b, c, v}, vertices}; + $("#alert").dialog({ + resizable: false, + title: "Generation error", + width: "32em", + buttons: { + "Clear data": function () { + localStorage.clear(); + localStorage.setItem("version", APP_VERSION); + }, + Regenerate: function () { + regenerateMap("generation error"); + $(this).dialog("close"); + }, + Ignore: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} + }); } export async function generateMapOnLoad() { diff --git a/src/scripts/generation/grid.ts b/src/scripts/generation/grid.ts new file mode 100644 index 00000000..22e54dfa --- /dev/null +++ b/src/scripts/generation/grid.ts @@ -0,0 +1,54 @@ +import {calculateTemperatures} from "modules/temperature"; +import {generateGrid} from "utils/graphUtils"; +import {calculateMapCoordinates, defineMapSize} from "modules/coordinates"; +import {markupGridFeatures} from "modules/markup"; +// @ts-expect-error js module +import {generatePrecipitation} from "modules/precipitation"; +import {byId} from "utils/shorthands"; +import {rn} from "utils/numberUtils"; + +const {Lakes, HeightmapGenerator} = window; + +export async function createGrid(globalGrid: IGrid, precreatedGraph?: IGrid): Promise { + const baseGrid: IGridBase = shouldRegenerateGridPoints(globalGrid) + ? (precreatedGraph && undressGrid(precreatedGraph)) || generateGrid() + : undressGrid(globalGrid); + + const heights: Uint8Array = await HeightmapGenerator.generate(baseGrid); + if (!heights) throw new Error("Heightmap generation failed"); + const heightsGrid = {...baseGrid, cells: {...baseGrid.cells, h: heights}}; + + const {featureIds, distanceField, features} = markupGridFeatures(heightsGrid); + const markedGrid = {...heightsGrid, features, cells: {...heightsGrid.cells, f: featureIds, t: distanceField}}; + + const touchesEdges = features.some(feature => feature && feature.land && feature.border); + defineMapSize(touchesEdges); + window.mapCoordinates = calculateMapCoordinates(); + + Lakes.addLakesInDeepDepressions(markedGrid); + Lakes.openNearSeaLakes(markedGrid); + + const temperature = calculateTemperatures(markedGrid); + const temperatureGrid = {...markedGrid, cells: {...markedGrid.cells, temp: temperature}}; + + const prec = generatePrecipitation(temperatureGrid); + return {...temperatureGrid, cells: {...temperatureGrid.cells, prec}}; +} + +function undressGrid(extendedGrid: IGrid): IGridBase { + const {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices} = extendedGrid; + const {i, b, c, v} = cells; + return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells: {i, b, c, v}, vertices}; +} + +// check if new grid graph should be generated or we can use the existing one +export function shouldRegenerateGridPoints(grid: IGrid) { + const cellsDesired = Number(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; +} diff --git a/src/scripts/reGraph.ts b/src/scripts/generation/reGraph.ts similarity index 100% rename from src/scripts/reGraph.ts rename to src/scripts/generation/reGraph.ts diff --git a/src/scripts/loading.ts b/src/scripts/loading.ts index 1601bdff..0103eb41 100644 --- a/src/scripts/loading.ts +++ b/src/scripts/loading.ts @@ -1,11 +1,13 @@ import * as d3 from "d3"; import {ERROR, WARN} from "config/logging"; +// @ts-expect-error js module import {loadMapFromURL} from "modules/io/load"; import {setDefaultEventHandlers} from "scripts/events"; import {ldb} from "scripts/indexedDB"; import {getInputValue} from "utils/nodeUtils"; -import {generateMapOnLoad} from "./generation.ts"; +import {generateMapOnLoad} from "./generation/generation"; +// @ts-expect-error js module import {showUploadErrorMessage, uploadMap} from "modules/io/load"; export function addOnLoadListener() { diff --git a/src/types/overrides.d.ts b/src/types/overrides.d.ts index b0b099c2..bbf58204 100644 --- a/src/types/overrides.d.ts +++ b/src/types/overrides.d.ts @@ -8,6 +8,10 @@ interface Window { mapCoordinates: IMapCoordinates; $: typeof $; // jQuery + scale: number; + viewX: number; + viewY: number; + // untyped IIFE modules Biomes: any; Names: any; diff --git a/src/utils/graphUtils.ts b/src/utils/graphUtils.ts index 61f183f0..fc3910eb 100644 --- a/src/utils/graphUtils.ts +++ b/src/utils/graphUtils.ts @@ -3,24 +3,13 @@ import Delaunator from "delaunator"; import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; import {Voronoi} from "modules/voronoi"; +// @ts-expect-error js module import {aleaPRNG} from "scripts/aleaPRNG"; import {TIME} from "../config/logging"; import {createTypedArray} from "./arrayUtils"; import {rn} from "./numberUtils"; import {byId} from "./shorthands"; -// check if new grid graph should be generated or we can use the existing one -export function shouldRegenerateGridPoints(grid: IGrid) { - const cellsDesired = Number(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; -} - export function generateGrid() { Math.random = aleaPRNG(seed); // reset PRNG const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints(); @@ -31,7 +20,7 @@ export function generateGrid() { // place random points to calculate Voronoi diagram function placePoints() { TIME && console.time("placePoints"); - const cellsDesired = +byId("pointsInput").dataset.cells; + const cellsDesired = Number(byId("pointsInput")?.dataset.cells); const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing); @@ -100,7 +89,7 @@ function getJitteredGrid(width: number, height: number, spacing: number) { } // return cell index on a regular square grid -export function findGridCell(x: number, y: number, grid) { +export function findGridCell(x: number, y: number, grid: IGrid) { return ( Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1)) @@ -170,7 +159,7 @@ export function isCoastal(i: number) { } // findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e -void (function addFindAll() { +function addFindAll() { const Quad = function (node, x0, y0, x1, y1) { this.node = node; this.x0 = x0; @@ -249,4 +238,4 @@ void (function addFindAll() { } while ((t.node = t.node.next)); } }; -})(); +}