diff --git a/index.html b/index.html index d85ab08d..b066aeea 100644 --- a/index.html +++ b/index.html @@ -1869,15 +1869,10 @@ - @@ -5547,8 +5542,8 @@
diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 1c0f536e..413fafdb 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -3,7 +3,7 @@ "display": "standalone", "orientation": "any", "name": "Azgaar's Fantansy Map Generator", - "short_name": "Azgaar's Fantansy Map Generator", + "short_name": "Azgaar's Fantasy Map Generator", "description": "Web application generating interactive and highly customizable maps", "scope": "/Fantasy-Map-Generator/", "start_url": "/Fantasy-Map-Generator/?source=pwa", diff --git a/src/dialogs/dialogs/heightmap-editor.js b/src/dialogs/dialogs/heightmap-editor.js index 61c9320a..b9272c42 100644 --- a/src/dialogs/dialogs/heightmap-editor.js +++ b/src/dialogs/dialogs/heightmap-editor.js @@ -224,10 +224,10 @@ export function open(options) { calculateTemperatures(grid); generatePrecipitation(grid); reGraph(grid); - reMarkFeatures(); - drawCoastline(); + reMarkFeatures(pack, newGrid); + drawCoastline(pack); - Rivers.generate(erosionAllowed); + Rivers.generate(pack, grid, erosionAllowed); if (!erosionAllowed) { for (const i of pack.cells.i) { @@ -237,7 +237,7 @@ export function open(options) { } } - renderLayer("rivers"); + renderLayer("rivers", pack); Lakes.defineGroup(); Biomes.define(); rankCells(); @@ -344,9 +344,9 @@ export function open(options) { calculateTemperatures(grid); generatePrecipitation(grid); reGraph(grid); - drawCoastline(); + drawCoastline(pack); - if (erosionAllowed) Rivers.generate(true); + if (erosionAllowed) Rivers.generate(pack, grid, true); // assign saved pack data from grid back to pack const n = pack.cells.i.length; diff --git a/src/dialogs/dialogs/heightmap-selection.js b/src/dialogs/dialogs/heightmap-selection.js index c6a0878c..542dd82f 100644 --- a/src/dialogs/dialogs/heightmap-selection.js +++ b/src/dialogs/dialogs/heightmap-selection.js @@ -2,7 +2,7 @@ import * as d3 from "d3"; import {heightmapTemplates} from "config/heightmap-templates"; import {precreatedHeightmaps} from "config/precreated-heightmaps"; -import {generateGrid} from "utils/graphUtils"; +import {generateGrid} from "scripts/generation/graph"; import {shouldRegenerateGridPoints} from "scripts/generation/generation"; import {byId} from "utils/shorthands"; import {generateSeed} from "utils/probabilityUtils"; diff --git a/src/dialogs/index.ts b/src/dialogs/index.ts index 6b6d61c8..37f17a3a 100644 --- a/src/dialogs/index.ts +++ b/src/dialogs/index.ts @@ -15,7 +15,8 @@ const dialogsMap = { lakeEditor: "lake-editor", religionsEditor: "religions-editor", statesEditor: "states-editor", - unitsEditor: "units-editor" + unitsEditor: "units-editor", + worldConfigurator: "world-configurator" }; type TDialog = keyof typeof dialogsMap; diff --git a/src/layers/renderers/drawRivers.js b/src/layers/renderers/drawRivers.ts similarity index 78% rename from src/layers/renderers/drawRivers.js rename to src/layers/renderers/drawRivers.ts index c617633e..860418bf 100644 --- a/src/layers/renderers/drawRivers.js +++ b/src/layers/renderers/drawRivers.ts @@ -1,7 +1,7 @@ -export function drawRivers() { +export function drawRivers(pack: IPack) { rivers.selectAll("*").remove(); - const {addMeandering, getRiverPath} = Rivers; + const {addMeandering, getRiverPath} = window.Rivers; const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => { if (!cells || cells.length < 2) return; @@ -12,7 +12,7 @@ export function drawRivers() { points = undefined; } - const meanderedPoints = addMeandering(cells, points); + const meanderedPoints = addMeandering(pack, cells, points); const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth); return ``; }); diff --git a/src/layers/renderers/index.ts b/src/layers/renderers/index.ts index 44f19397..a1cb8913 100644 --- a/src/layers/renderers/index.ts +++ b/src/layers/renderers/index.ts @@ -39,9 +39,9 @@ const layerRenderersMap = { temperature: drawTemperature }; -export function renderLayer(layerName: keyof typeof layerRenderersMap) { - const rendered = layerRenderersMap[layerName]; - TIME && console.time(rendered.name); - rendered(); - TIME && console.timeEnd(rendered.name); +export function renderLayer(layerName: keyof typeof layerRenderersMap, ...args) { + const renderer = layerRenderersMap[layerName]; + TIME && console.time(renderer.name); + renderer(...args); + TIME && console.timeEnd(renderer.name); } diff --git a/src/layers/toggles.ts b/src/layers/toggles.ts index 2d0c0f0c..9ea1c158 100644 --- a/src/layers/toggles.ts +++ b/src/layers/toggles.ts @@ -363,7 +363,7 @@ function toggleTexture(event?: MouseEvent) { function toggleRivers(event?: MouseEvent) { if (!layerIsOn("toggleRivers")) { turnLayerButtonOn("toggleRivers"); - renderLayer("rivers"); + renderLayer("rivers", pack); if (isCtrlPressed(event)) editStyle("rivers"); } else { if (isCtrlPressed(event)) return editStyle("rivers"); diff --git a/src/modules/biomes.js b/src/modules/biomes.js index 56b67731..7295b58c 100644 --- a/src/modules/biomes.js +++ b/src/modules/biomes.js @@ -85,7 +85,7 @@ window.Biomes = (function () { } // assign biome id for each cell - function define() { + function define(pack, grid) { TIME && console.time("defineBiomes"); const {cells} = pack; const {temp, prec} = grid.cells; diff --git a/src/modules/coastline.js b/src/modules/coastline.js index 0bebb076..343ed1e7 100644 --- a/src/modules/coastline.js +++ b/src/modules/coastline.js @@ -6,7 +6,7 @@ import {round} from "utils/stringUtils"; import {Ruler} from "modules/measurers"; // Detect and draw the coastline -export function drawCoastline() { +export function drawCoastline(pack) { TIME && console.time("drawCoastline"); const {cells, vertices, features} = pack; diff --git a/src/modules/dynamic/auto-update.js b/src/modules/dynamic/auto-update.js index 62e9db8d..25011fd9 100644 --- a/src/modules/dynamic/auto-update.js +++ b/src/modules/dynamic/auto-update.js @@ -208,8 +208,8 @@ export function resolveVersionConflicts(version) { coastline.selectAll("path").remove(); lakes.selectAll("path").remove(); - reMarkFeatures(); - drawCoastline(); + reMarkFeatures(pack, newGrid); + drawCoastline(pack); } if (version < 1.11) { diff --git a/src/modules/io/load.js b/src/modules/io/load.js index 0a84c183..54fa8787 100644 --- a/src/modules/io/load.js +++ b/src/modules/io/load.js @@ -1,18 +1,19 @@ import * as d3 from "d3"; import {INFO} from "config/logging"; +import {closeDialogs} from "dialogs/utils"; import {updatePresetInput} from "layers"; +import {reMarkFeatures} from "modules/markup"; import {setDefaultEventHandlers} from "scripts/events"; +import {regenerateMap} from "scripts/generation/generation"; +import {calculateVoronoi} from "scripts/generation/graph"; import {ldb} from "scripts/indexedDB"; import {tip} from "scripts/tooltips"; import {last} from "utils/arrayUtils"; import {parseError} from "utils/errorUtils"; -import {calculateVoronoi, findCell} from "utils/graphUtils"; +import {findCell} from "utils/graphUtils"; import {link} from "utils/linkUtils"; import {minmax, rn} from "utils/numberUtils"; -import {regenerateMap} from "scripts/generation/generation"; -import {reMarkFeatures} from "modules/markup"; -import {closeDialogs} from "dialogs/utils"; // add drag to upload logic, pull request from @evyatron export function addDragToUpload() { diff --git a/src/modules/lakes.ts b/src/modules/lakes.ts index bd7064e3..6d3b07e6 100644 --- a/src/modules/lakes.ts +++ b/src/modules/lakes.ts @@ -2,13 +2,14 @@ import * as d3 from "d3"; import {TIME} from "config/logging"; import {rn} from "utils/numberUtils"; +// @ts-expect-error js module import {aleaPRNG} from "scripts/aleaPRNG"; -import {byId} from "utils/shorthands"; import {getInputNumber, getInputValue} from "utils/nodeUtils"; import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; +import {byId} from "utils/shorthands"; window.Lakes = (function () { - const setClimateData = function (h) { + const setClimateData = function (h: Uint8Array, pack: IPack, grid: IGrid) { const cells = pack.cells; const lakeOutCells = new Uint16Array(cells.i.length); @@ -39,37 +40,39 @@ window.Lakes = (function () { }; // get array of land cells aroound lake - const getShoreline = function (lake) { + const getShoreline = function (lake: IPackFeatureLake, pack: IPack) { const uniqueCells = new Set(); - lake.vertices.forEach(v => pack.vertices.c[v].forEach(c => pack.cells.h[c] >= 20 && uniqueCells.add(c))); + lake.vertices.forEach(v => + pack.vertices.c[v].forEach(c => pack.cells.h[c] >= MIN_LAND_HEIGHT && uniqueCells.add(c)) + ); lake.shoreline = [...uniqueCells]; }; - const prepareLakeData = h => { + const prepareLakeData = (h: Uint8Array, pack: IPack) => { const cells = pack.cells; - const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; + const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput"); - pack.features.forEach(f => { - if (f.type !== "lake") return; - delete f.flux; - delete f.inlets; - delete f.outlet; - delete f.height; - delete f.closed; - !f.shoreline && Lakes.getShoreline(f); + pack.features.forEach(feature => { + if (!feature || feature.type !== "lake") return; + delete feature.flux; + delete feature.inlets; + delete feature.outlet; + delete feature.height; + delete feature.closed; + !feature.shoreline && getShoreline(feature, pack); // lake surface height is as lowest land cells around - const min = f.shoreline.sort((a, b) => h[a] - h[b])[0]; - f.height = h[min] - 0.1; + const min = feature.shoreline.sort((a, b) => h[a] - h[b])[0]; + feature.height = h[min] - 0.1; // check if lake can be open (not in deep depression) if (ELEVATION_LIMIT === 80) { - f.closed = false; + feature.closed = false; return; } let deep = true; - const threshold = f.height + ELEVATION_LIMIT; + const threshold = feature.height + ELEVATION_LIMIT; const queue = [min]; const checked = []; checked[min] = true; @@ -84,7 +87,7 @@ window.Lakes = (function () { if (h[n] < 20) { const nFeature = pack.features[cells.f[n]]; - if (nFeature.type === "ocean" || f.height > nFeature.height) { + if ((nFeature && nFeature.type === "ocean") || feature.height > nFeature.height) { deep = false; break; } @@ -95,11 +98,11 @@ window.Lakes = (function () { } } - f.closed = deep; + feature.closed = deep; }); }; - const cleanupLakeData = function () { + const cleanupLakeData = function (pack: IPack) { for (const feature of pack.features) { if (feature.type !== "lake") continue; delete feature.river; @@ -117,14 +120,15 @@ window.Lakes = (function () { } }; - const defineGroup = function () { + const defineGroup = function (pack: IPack) { for (const feature of pack.features) { - if (feature.type !== "lake") continue; - const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node(); - if (!lakeEl) continue; + if (feature && feature.type === "lake") { + const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node(); + if (!lakeEl) continue; - feature.group = getGroup(feature); - document.getElementById(feature.group).appendChild(lakeEl); + feature.group = getGroup(feature); + byId(feature.group)?.appendChild(lakeEl); + } } }; diff --git a/src/modules/river-generator.js b/src/modules/river-generator.js index 4fbeb2a3..810e3047 100644 --- a/src/modules/river-generator.js +++ b/src/modules/river-generator.js @@ -8,7 +8,7 @@ import {rw, each} from "utils/probabilityUtils"; import {aleaPRNG} from "scripts/aleaPRNG"; window.Rivers = (function () { - const generate = function (allowErosion = true) { + const generate = function (pack, grid, allowErosion = true) { TIME && console.time("generateRivers"); Math.random = aleaPRNG(seed); const {cells, features} = pack; @@ -25,14 +25,14 @@ window.Rivers = (function () { cells.conf = new Uint8Array(cells.i.length); // confluences array let riverNext = 1; // first river id is 1 - const h = alterHeights(); - Lakes.prepareLakeData(h); - resolveDepressions(h); + const h = alterHeights(pack.cells); + Lakes.prepareLakeData(h, pack); + resolveDepressions(pack, h); drainWater(); defineRivers(); calculateConfluenceFlux(); - Lakes.cleanupLakeData(); + Lakes.cleanupLakeData(pack); if (allowErosion) { cells.h = Uint8Array.from(h); // apply gradient @@ -42,14 +42,12 @@ window.Rivers = (function () { TIME && console.timeEnd("generateRivers"); function drainWater() { - //const MIN_FLUX_TO_FORM_RIVER = 10 * distanceScale; const MIN_FLUX_TO_FORM_RIVER = 30; const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25; const prec = grid.cells.prec; - const area = pack.cells.area; const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); - const lakeOutCells = Lakes.setClimateData(h); + const lakeOutCells = Lakes.setClimateData(h, pack, grid); land.forEach(function (i) { cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation @@ -195,7 +193,7 @@ window.Rivers = (function () { const parent = riverParents[key] || 0; const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor; - const meanderedPoints = addMeandering(riverCells); + const meanderedPoints = addMeandering(pack, riverCells); const discharge = cells.fl[mouth]; // m3 in second const length = getApproximateLength(meanderedPoints); const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0)); @@ -245,8 +243,7 @@ window.Rivers = (function () { }; // add distance to water value to land cells to make map less depressed - const alterHeights = () => { - const {h, c, t} = pack.cells; + const alterHeights = ({h, c, t}) => { return Array.from(h).map((h, i) => { if (h < 20 || t[i] < 1) return h; return h + t[i] / 100 + d3.mean(c[i].map(c => t[c])) / 10000; @@ -254,7 +251,7 @@ window.Rivers = (function () { }; // depression filling algorithm (for a correct water flux modeling) - const resolveDepressions = function (h) { + const resolveDepressions = function (pack, h) { const {cells, features} = pack; const maxIterations = +document.getElementById("resolveDepressionsStepsOutput").value; const checkLakeMaxIteration = maxIterations * 0.85; @@ -272,7 +269,7 @@ window.Rivers = (function () { for (let iteration = 0; depressions && iteration < maxIterations; iteration++) { if (progress.length > 5 && d3.sum(progress) > 0) { // bad progress, abort and set heights back - h = alterHeights(); + h = alterHeights(pack.cells); depressions = progress[0]; break; } @@ -313,11 +310,11 @@ window.Rivers = (function () { }; // add points at 1/3 and 2/3 of a line between adjacents river cells - const addMeandering = function (riverCells, riverPoints = null, meandering = 0.5) { + const addMeandering = (pack, riverCells, riverPoints = null, meandering = 0.5) => { const {fl, conf, h} = pack.cells; const meandered = []; const lastStep = riverCells.length - 1; - const points = getRiverPoints(riverCells, riverPoints); + const points = getRiverPoints(pack, riverCells, riverPoints); let step = h[riverCells[0]] < 20 ? 1 : 10; let fluxPrev = 0; @@ -373,17 +370,17 @@ window.Rivers = (function () { return meandered; }; - const getRiverPoints = (riverCells, riverPoints) => { + const getRiverPoints = (pack, riverCells, riverPoints) => { if (riverPoints) return riverPoints; const {p} = pack.cells; return riverCells.map((cell, i) => { - if (cell === -1) return getBorderPoint(riverCells[i - 1]); + if (cell === -1) return getBorderPoint(pack, riverCells[i - 1]); return p[cell]; }); }; - const getBorderPoint = i => { + const getBorderPoint = (pack, i) => { const [x, y] = pack.cells.p[i]; const min = Math.min(y, graphHeight - y, x, graphWidth - x); if (min === y) return [x, 0]; diff --git a/src/modules/submap.js b/src/modules/submap.js index 15d70d76..eaf2fa34 100644 --- a/src/modules/submap.js +++ b/src/modules/submap.js @@ -7,6 +7,7 @@ import {rn} from "utils/numberUtils"; import {aleaPRNG} from "scripts/aleaPRNG"; import {renderLayer} from "layers"; import {markupGridFeatures} from "modules/markup"; +import {generateGrid} from "scripts/generation/graph"; window.Submap = (function () { const isWater = (pack, id) => pack.cells.h[id] < 20; @@ -130,8 +131,8 @@ window.Submap = (function () { // remove misclassified cells stage("Define coastline."); - reMarkFeatures(); - drawCoastline(); + reMarkFeatures(pack, newGrid); + drawCoastline(pack); /****************************************************/ /* Packed Graph */ @@ -210,8 +211,8 @@ window.Submap = (function () { } stage("Regenerating river network."); - Rivers.generate(); - renderLayer("rivers"); + Rivers.generate(pack, grid); + renderLayer("rivers", pack); Lakes.defineGroup(); // biome calculation based on (resampled) grid.cells.temp and prec diff --git a/src/modules/ui/options.js b/src/modules/ui/options.js index 3287a334..177761cb 100644 --- a/src/modules/ui/options.js +++ b/src/modules/ui/options.js @@ -168,6 +168,8 @@ optionsContent.on("click", function (event) { else if (id === "translateExtent") toggleTranslateExtent(event.target); else if (id === "speakerTest") testSpeaker(); else if (id === "themeColorRestore") restoreDefaultThemeColor(); + else if (id === "configureWorld") openDialog("worldConfigurator"); + else if (id === "optionsReset") restoreDefaultOptions(); }); function mapSizeInputChange() { @@ -1025,6 +1027,7 @@ export function toggle3dOptions() { isLoaded = true; byId("options3dUpdate").on("click", ThreeD.update); + byId("options3dConfigureWorld").on("click", () => openDialog("worldConfigurator")); byId("options3dSave").on("click", ThreeD.saveScreenshot); byId("options3dOBJSave").on("click", ThreeD.saveOBJ); diff --git a/src/modules/ui/rivers-creator.js b/src/modules/ui/rivers-creator.js index f93a9a75..be6b0175 100644 --- a/src/modules/ui/rivers-creator.js +++ b/src/modules/ui/rivers-creator.js @@ -102,7 +102,7 @@ export function createRiver() { const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); const widthFactor = 1.2 * defaultWidthFactor; - const meanderedPoints = addMeandering(riverCells); + const meanderedPoints = addMeandering(pack, riverCells); const discharge = cells.fl[mouth]; // m3 in second const length = getApproximateLength(meanderedPoints); diff --git a/src/modules/ui/rivers-editor.js b/src/modules/ui/rivers-editor.js index cfc0da64..473a50f6 100644 --- a/src/modules/ui/rivers-editor.js +++ b/src/modules/ui/rivers-editor.js @@ -31,7 +31,7 @@ export function editRiver(id) { const river = getRiver(); const {cells, points} = river; - const riverPoints = Rivers.getRiverPoints(cells, points); + const riverPoints = Rivers.getRiverPoints(pack, cells, points); drawControlPoints(riverPoints); drawCells(cells); @@ -98,7 +98,7 @@ export function editRiver(id) { function updateRiverWidth(river) { const {addMeandering, getWidth, getOffset} = Rivers; const {cells, discharge, widthFactor, sourceWidth} = river; - const meanderedPoints = addMeandering(cells); + const meanderedPoints = addMeandering(pack, cells); river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`; @@ -169,7 +169,7 @@ export function editRiver(id) { river.cells = river.points.map(([x, y]) => findCell(x, y)); const {widthFactor, sourceWidth} = river; - const meanderedPoints = Rivers.addMeandering(river.cells, river.points); + const meanderedPoints = Rivers.addMeandering(pack, river.cells, river.points); const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth); elSelected.attr("d", path); diff --git a/src/modules/ui/submap.js b/src/modules/ui/submap.js index 8e3e1b8a..b3ef4eb5 100644 --- a/src/modules/ui/submap.js +++ b/src/modules/ui/submap.js @@ -6,6 +6,7 @@ import {debounce} from "utils/functionUtils"; import {restoreLayers} from "layers"; import {undraw} from "scripts/generation/generation"; import {closeDialogs} from "dialogs/utils"; +import {openDialog} from "dialogs"; window.UISubmap = (function () { byId("submapPointsInput").addEventListener("input", function () { @@ -312,7 +313,7 @@ window.UISubmap = (function () { restoreLayers(); if (ThreeD.options.isOn) ThreeD.redraw(); - if ($("#worldConfigurator").is(":visible")) editWorld(); + if ($("#worldConfigurator").is(":visible")) openDialog("worldConfigurator"); } function changeStyles(scale) { diff --git a/src/modules/ui/tools.js b/src/modules/ui/tools.js index 19ca3949..eff5f287 100644 --- a/src/modules/ui/tools.js +++ b/src/modules/ui/tools.js @@ -131,11 +131,11 @@ async function openEmblemEditor() { } function regenerateRivers() { - Rivers.generate(); + Rivers.generate(pack, grid); Lakes.defineGroup(); Rivers.specify(); if (!layerIsOn("toggleRivers")) toggleLayer("toggleRivers"); - else renderLayer("rivers"); + else renderLayer("rivers", pack); } function recalculatePopulation() { @@ -588,8 +588,8 @@ function addRiverOnClick() { const initialFlux = grid.cells.prec[cells.g[i]]; cells.fl[i] = initialFlux; - const h = alterHeights(); - resolveDepressions(h); + const h = alterHeights(pacl.cells); + resolveDepressions(pack, h); while (i) { cells.r[i] = riverId; @@ -663,7 +663,7 @@ function addRiverOnClick() { const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2); const widthFactor = river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor); - const meanderedPoints = addMeandering(riverCells); + const meanderedPoints = addMeandering(pack, riverCells); const discharge = cells.fl[mouth]; // m3 in second const length = getApproximateLength(meanderedPoints); @@ -704,7 +704,7 @@ function addRiverOnClick() { riversG.append("path").attr("id", id).attr("d", path); if (d3.event.shiftKey === false) { - Lakes.cleanupLakeData(); + Lakes.cleanupLakeData(pack); unpressClickToAddButton(); document.getElementById("addNewRiver").classList.remove("pressed"); if (addNewRiver.offsetParent) riversOverviewRefresh.click(); diff --git a/src/modules/ui/world-configurator.js b/src/modules/ui/world-configurator.js index ba4d42ac..3854eaad 100644 --- a/src/modules/ui/world-configurator.js +++ b/src/modules/ui/world-configurator.js @@ -7,8 +7,7 @@ import {renderLayer} from "layers"; let isLoaded = false; -export function editWorld() { - if (customization) return; +export function open() { $("#worldConfigurator").dialog({ title: "Configure World", resizable: false, @@ -63,7 +62,7 @@ export function editWorld() { calculateTemperatures(grid); generatePrecipitation(grid); const heights = new Uint8Array(pack.cells.h); - Rivers.generate(); + Rivers.generate(pack, grid); Lakes.defineGroup(); Rivers.specify(); pack.cells.h = new Float32Array(heights); @@ -73,7 +72,7 @@ export function editWorld() { if (layerIsOn("togglePrec")) renderLayer("precipitation"); if (layerIsOn("toggleBiomes")) renderLayer("biomes"); if (layerIsOn("toggleCoordinates")) drawCoordinates(); - if (layerIsOn("toggleRivers")) renderLayer("rivers"); + if (layerIsOn("toggleRivers")) renderLayer("rivers", pack); if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500); } diff --git a/src/scripts/findAll.js b/src/scripts/findAll.js new file mode 100644 index 00000000..58e70333 --- /dev/null +++ b/src/scripts/findAll.js @@ -0,0 +1,84 @@ +import * as d3 from "d3"; + +// findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e +export function addFindAll() { + const Quad = function (node, x0, y0, x1, y1) { + this.node = node; + this.x0 = x0; + this.y0 = y0; + this.x1 = x1; + this.y1 = y1; + }; + + const tree_filter = function (x, y, radius) { + var t = {x, y, x0: this._x0, y0: this._y0, x3: this._x1, y3: this._y1, quads: [], node: this._root}; + if (t.node) { + t.quads.push(new Quad(t.node, t.x0, t.y0, t.x3, t.y3)); + } + radiusSearchInit(t, radius); + + var i = 0; + while ((t.q = t.quads.pop())) { + i++; + + // Stop searching if this quadrant can’t contain a closer node. + if ( + !(t.node = t.q.node) || + (t.x1 = t.q.x0) > t.x3 || + (t.y1 = t.q.y0) > t.y3 || + (t.x2 = t.q.x1) < t.x0 || + (t.y2 = t.q.y1) < t.y0 + ) + continue; + + // Bisect the current quadrant. + if (t.node.length) { + t.node.explored = true; + var xm = (t.x1 + t.x2) / 2, + ym = (t.y1 + t.y2) / 2; + + t.quads.push( + new Quad(t.node[3], xm, ym, t.x2, t.y2), + new Quad(t.node[2], t.x1, ym, xm, t.y2), + new Quad(t.node[1], xm, t.y1, t.x2, ym), + new Quad(t.node[0], t.x1, t.y1, xm, ym) + ); + + // Visit the closest quadrant first. + if ((t.i = ((y >= ym) << 1) | (x >= xm))) { + t.q = t.quads[t.quads.length - 1]; + t.quads[t.quads.length - 1] = t.quads[t.quads.length - 1 - t.i]; + t.quads[t.quads.length - 1 - t.i] = t.q; + } + } + + // Visit this point. (Visiting coincident points isn’t necessary!) + else { + var dx = x - +this._x.call(null, t.node.data), + dy = y - +this._y.call(null, t.node.data), + d2 = dx * dx + dy * dy; + radiusSearchVisit(t, d2); + } + } + return t.result; + }; + + d3.quadtree.prototype.findAll = tree_filter; + + var radiusSearchInit = function (t, radius) { + t.result = []; + (t.x0 = t.x - radius), (t.y0 = t.y - radius); + (t.x3 = t.x + radius), (t.y3 = t.y + radius); + t.radius = radius * radius; + }; + + var radiusSearchVisit = function (t, d2) { + t.node.data.scanned = true; + if (d2 < t.radius) { + do { + t.result.push(t.node.data); + t.node.data.selected = true; + } while ((t.node = t.node.next)); + } + }; +} diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index f7cfd1c4..6359772a 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -2,6 +2,7 @@ import * as d3 from "d3"; import {ERROR, INFO, WARN} from "config/logging"; import {closeDialogs} from "dialogs/utils"; +import {openDialog} from "dialogs"; import {initLayers, renderLayer, restoreLayers} from "layers"; // @ts-expect-error js module import {drawCoastline} from "modules/coastline"; @@ -29,11 +30,29 @@ import {rankCells} from "../rankCells"; import {showStatistics} from "../statistics"; import {createGrid} from "./grid"; import {reGraph} from "./reGraph"; +import {getInputValue, setInputValue} from "utils/nodeUtils"; -const {Zoom, Lakes, OceanLayers, Rivers, Biomes, Cultures, BurgsAndStates, Religions, Military, Markers, Names} = - window; +const { + Zoom, + Lakes, + OceanLayers, + Rivers, + Biomes, + Cultures, + BurgsAndStates, + Religions, + Military, + Markers, + Names, + ThreeD +} = window; -async function generate(options?: {seed: string; graph: IGrid}) { +interface IGenerationOptions { + seed: string; + graph: IGrid; +} + +async function generate(options?: IGenerationOptions) { try { const timeStart = performance.now(); const {seed: precreatedSeed, graph: precreatedGraph} = options || {}; @@ -47,17 +66,17 @@ async function generate(options?: {seed: string; graph: IGrid}) { randomizeOptions(); const newGrid = await createGrid(grid, precreatedGraph); + const newPack = reGraph(newGrid); - const pack = reGraph(newGrid); - reMarkFeatures(pack, newGrid); - drawCoastline(); + reMarkFeatures(newPack, newGrid); + drawCoastline(newPack); - Rivers.generate(); - renderLayer("rivers"); - Lakes.defineGroup(); - Biomes.define(); + Rivers.generate(newPack, newGrid); + renderLayer("rivers", newPack); + Lakes.defineGroup(newPack); + Biomes.define(newPack, newGrid); - rankCells(); + rankCells(newPack); Cultures.generate(); Cultures.expand(); BurgsAndStates.generate(); @@ -82,6 +101,9 @@ async function generate(options?: {seed: string; graph: IGrid}) { drawScaleBar(window.scale); Names.getMapName(); + // @ts-expect-error redefine global + pack = newPack; + WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); showStatistics(); INFO && console.groupEnd(); @@ -142,10 +164,10 @@ export function undraw() { unfog(); } -export const regenerateMap = debounce(async function (options) { +export const regenerateMap = debounce(async function (options: IGenerationOptions) { WARN && console.warn("Generate new random map"); - const cellsDesired = +byId("pointsInput").dataset.cells; + const cellsDesired = Number(byId("pointsInput")?.dataset.cells); const shouldShowLoading = cellsDesired > 10000; shouldShowLoading && showLoading(); @@ -156,7 +178,7 @@ export const regenerateMap = debounce(async function (options) { await generate(options); restoreLayers(); if (ThreeD.options.isOn) ThreeD.redraw(); - if ($("#worldConfigurator").is(":visible")) editWorld(); + if ($("#worldConfigurator").is(":visible")) openDialog("worldConfigurator"); shouldShowLoading && hideLoading(); clearMainTip(); @@ -164,14 +186,13 @@ export const regenerateMap = debounce(async function (options) { // focus on coordinates, cell or burg provided in searchParams function focusOn() { - const url = new URL(window.location.href); - const params = url.searchParams; + const params = new URL(window.location.href).searchParams; const fromMGCG = params.get("from") === "MFCG" && document.referrer; if (fromMGCG) { - if (params.get("seed").length === 13) { + if (params.get("seed")?.length === 13) { // show back burg from MFCG - const burgSeed = params.get("seed").slice(-4); + const burgSeed = params.get("seed")!.slice(-4); params.set("burg", burgSeed); } else { // select burg for MFCG @@ -185,10 +206,10 @@ function focusOn() { const burgParam = params.get("burg"); if (scaleParam || cellParam || burgParam) { - const scale = +scaleParam || 8; + const scale = scaleParam ? Number(scaleParam) : 8; if (cellParam) { - const cell = +params.get("cell"); + const cell = Number(scaleParam); const [x, y] = pack.cells.p[cell]; Zoom.to(x, y, scale, 1600); return; @@ -203,14 +224,14 @@ function focusOn() { return; } - const x = +params.get("x") || graphWidth / 2; - const y = +params.get("y") || graphHeight / 2; + const x = params.get("x") ? Number(params.get("x")) : graphWidth / 2; + const y = params.get("y") ? Number(params.get("y")) : graphHeight / 2; Zoom.to(x, y, scale, 1600); } } // find burg for MFCG and focus on it -function findBurgForMFCG(params) { +function findBurgForMFCG(params: URLSearchParams) { const {cells, burgs} = pack; if (pack.burgs.length < 2) { @@ -219,17 +240,17 @@ function findBurgForMFCG(params) { } // used for selection - const size = +params.get("size"); - const coast = +params.get("coast"); - const port = +params.get("port"); - const river = +params.get("river"); + const size = params.get("size") ? Number(params.get("size")) : 10; + const coast = Boolean(params.get("coast")); + const port = Boolean(params.get("port")); + const river = Boolean(params.get("river")); let selection = defineSelection(coast, port, river); if (!selection.length) selection = defineSelection(coast, !port, !river); - if (!selection.length) selection = defineSelection(!coast, 0, !river); + if (!selection.length) selection = defineSelection(!coast, false, !river); if (!selection.length) selection = [burgs[1]]; // select first if nothing is found - function defineSelection(coast, port, river) { + function defineSelection(coast: boolean, port: boolean, river: boolean) { if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]); if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]); if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]); @@ -240,29 +261,27 @@ function findBurgForMFCG(params) { // select a burg with closest population from selection const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size)); - const burgId = selection[selected].i; - if (!burgId) { - ERROR && console.error("Cannot select a burg for MFCG"); - return; - } + const burgId = selected && selection[selected].i; + if (!burgId) return ERROR && console.error("Cannot select a burg for MFCG"); const b = burgs[burgId]; - const referrer = new URL(document.referrer); - for (let p of referrer.searchParams) { - if (p[0] === "name") b.name = p[1]; - else if (p[0] === "size") b.population = +p[1]; - else if (p[0] === "seed") b.MFCG = +p[1]; - else if (p[0] === "shantytown") b.shanty = +p[1]; - else b[p[0]] = +p[1]; // other parameters + const searchParams = new URL(document.referrer).searchParams; + for (let [param, value] of searchParams) { + if (param === "name") b.name = value; + else if (param === "size") b.population = +value; + else if (param === "seed") b.MFCG = +value; + else if (param === "shantytown") b.shanty = +value; } - if (params.get("name") && params.get("name") != "null") b.name = params.get("name"); + + const nameParam = params.get("name"); + if (nameParam && nameParam !== "null") b.name = nameParam; const label = burgLabels.select("[data-id='" + burgId + "']"); if (label.size()) { label .text(b.name) .classed("drag", true) - .on("mouseover", function () { + .on("mouseover", function (this: Element) { d3.select(this).classed("drag", false); label.on("mouseover", null); }); @@ -270,24 +289,27 @@ function findBurgForMFCG(params) { Zoom.to(b.x, b.y, 8, 1600); Zoom.invoke(); + tip("Here stands the glorious city of " + b.name, true, "success", 15000); } // set map seed (string!) -function setSeed(precreatedSeed) { +function setSeed(precreatedSeed?: string) { 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); + + const params = new URL(window.location.href).searchParams; + const urlSeed = params.get("seed"); + const optionsSeed = getInputValue("optionsSeed"); + + 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 if (optionsSeed && optionsSeed !== seed) seed = optionsSeed; else seed = generateSeed(); } else { seed = precreatedSeed; } - byId("optionsSeed").value = seed; + setInputValue("optionsSeed", seed); Math.random = aleaPRNG(seed); } diff --git a/src/scripts/generation/graph.ts b/src/scripts/generation/graph.ts new file mode 100644 index 00000000..fa486338 --- /dev/null +++ b/src/scripts/generation/graph.ts @@ -0,0 +1,87 @@ +import Delaunator from "delaunator"; + +import {Voronoi} from "modules/voronoi"; +import {TIME} from "config/logging"; +// @ts-expect-error js module +import {aleaPRNG} from "scripts/aleaPRNG"; +import {createTypedArray} from "utils/arrayUtils"; +import {rn} from "utils/numberUtils"; +import {byId} from "utils/shorthands"; + +export function generateGrid() { + Math.random = aleaPRNG(seed); // reset PRNG + const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints(); + const {cells, vertices} = calculateVoronoi(points, boundary); + return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices}; +} + +// place random points to calculate Voronoi diagram +function placePoints() { + TIME && console.time("placePoints"); + 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); + 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}; +} + +// calculate Delaunay and then Voronoi diagram +export function calculateVoronoi(points: TPoints, boundary: TPoints): IGraph { + TIME && console.time("calculateDelaunay"); + const allPoints: TPoints = points.concat(boundary); + const delaunay = Delaunator.from(allPoints); + TIME && console.timeEnd("calculateDelaunay"); + + TIME && console.time("calculateVoronoi"); + const {cells, vertices} = new Voronoi(delaunay, allPoints, points.length); + const i = createTypedArray({maxValue: points.length, length: points.length}).map((_, i) => i); // array of indexes + + TIME && console.timeEnd("calculateVoronoi"); + return {cells: {...cells, i}, vertices}; +} + +// add points along map edge to pseudo-clip voronoi cells +function getBoundaryPoints(width: number, height: number, spacing: number) { + 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: TPoints = []; + + 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: number, height: number, spacing: number) { + const radius = spacing / 2; // square radius + const jittering = radius * 0.9; // max deviation + const doubleJittering = jittering * 2; + const jitter = () => Math.random() * doubleJittering - jittering; + + const points: TPoints = []; + 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; +} diff --git a/src/scripts/generation/grid.ts b/src/scripts/generation/grid.ts index 22e54dfa..0beecad0 100644 --- a/src/scripts/generation/grid.ts +++ b/src/scripts/generation/grid.ts @@ -1,5 +1,5 @@ import {calculateTemperatures} from "modules/temperature"; -import {generateGrid} from "utils/graphUtils"; +import {generateGrid} from "scripts/generation/graph"; import {calculateMapCoordinates, defineMapSize} from "modules/coordinates"; import {markupGridFeatures} from "modules/markup"; // @ts-expect-error js module diff --git a/src/scripts/generation/reGraph.ts b/src/scripts/generation/reGraph.ts index 1c64bc64..8f8f5d4c 100644 --- a/src/scripts/generation/reGraph.ts +++ b/src/scripts/generation/reGraph.ts @@ -3,7 +3,7 @@ import * as d3 from "d3"; import {TIME} from "config/logging"; import {UINT16_MAX} from "constants"; import {createTypedArray} from "utils/arrayUtils"; -import {calculateVoronoi, getPackPolygon} from "utils/graphUtils"; +import {calculateVoronoi} from "scripts/generation/graph"; import {rn} from "utils/numberUtils"; import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; @@ -51,20 +51,21 @@ export function reGraph(grid: IGrid): IPackBase { newCells.h.push(height); } + const {cells, vertices} = calculateVoronoi(newCells.p, grid.boundary); + function getCellArea(i: number) { - const area = Math.abs(d3.polygonArea(getPackPolygon(i))); + const polygon = cells.v[i].map(v => vertices.p[v]); + const area = Math.abs(d3.polygonArea(polygon)); return Math.min(area, UINT16_MAX); } - const {cells, vertices} = calculateVoronoi(newCells.p, grid.boundary); - const pack: IPackBase = { vertices, cells: { ...cells, p: newCells.p, g: createTypedArray({maxValue: grid.points.length, from: newCells.g}), - q: d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i])), + q: d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i])) as unknown as Quadtree, h: new Uint8Array(newCells.h), area: createTypedArray({maxValue: UINT16_MAX, from: cells.i}).map(getCellArea) } diff --git a/src/scripts/listeners.ts b/src/scripts/listeners.ts index fad0d0c9..c28b09f3 100644 --- a/src/scripts/listeners.ts +++ b/src/scripts/listeners.ts @@ -8,6 +8,8 @@ import {addResizeListener} from "modules/ui/options"; // @ts-ignore import {addDragToUpload} from "modules/io/load"; import {addHotkeyListeners} from "scripts/hotkeys"; +// @ts-ignore +import {addFindAll} from "scripts/findAll"; export function addGlobalListeners() { if (PRODUCTION) { @@ -22,6 +24,7 @@ export function addGlobalListeners() { addHotkeyListeners(); assignSpeakerBehavior(); addDragToUpload(); + addFindAll(); } function registerServiceWorker() { diff --git a/src/scripts/rankCells.ts b/src/scripts/rankCells.ts index b2ee91fa..447d3aad 100644 --- a/src/scripts/rankCells.ts +++ b/src/scripts/rankCells.ts @@ -8,7 +8,7 @@ const FLUX_MAX_BONUS = 250; const SUITABILITY_FACTOR = 5; // assess cells suitability for population and rank cells for culture centers and burgs placement -export function rankCells() { +export function rankCells(pack: IPack) { TIME && console.time("rankCells"); const {cells, features} = pack; diff --git a/src/types/overrides.d.ts b/src/types/overrides.d.ts index bbf58204..ea68331e 100644 --- a/src/types/overrides.d.ts +++ b/src/types/overrides.d.ts @@ -33,3 +33,8 @@ interface Node { on: (name: string, fn: EventListenerOrEventListenerObject, options?: AddEventListenerOptions) => void; off: (name: string, fn: EventListenerOrEventListenerObject) => void; } + +interface Quadtree extends d3.Quadtree { + find: (x: number, y: number, radius: number) => [x: number, y: number, cellId: number]; + findAll: (x: number, y: number, radius: number) => [x: number, y: number, cellId: number][]; +} diff --git a/src/types/pack.d.ts b/src/types/pack.d.ts index e2a291cf..cdf18077 100644 --- a/src/types/pack.d.ts +++ b/src/types/pack.d.ts @@ -29,7 +29,7 @@ interface IPackCells { burg: UintArray; haven: UintArray; harbor: UintArray; - q: d3.Quadtree; + q: Quadtree; } interface IPackBase extends IGraph { @@ -95,6 +95,9 @@ interface IBurg { x: number; y: number; population: number; + port: number; + shanty: number; + MFCG?: string | number; removed?: boolean; } @@ -119,4 +122,5 @@ interface IRiver { length: number; discharge: number; cells: number[]; + points?: number[]; } diff --git a/src/utils/graphUtils.ts b/src/utils/graphUtils.ts index fc3910eb..1a8a9f47 100644 --- a/src/utils/graphUtils.ts +++ b/src/utils/graphUtils.ts @@ -1,92 +1,6 @@ -import * as d3 from "d3"; -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"; - -export function generateGrid() { - Math.random = aleaPRNG(seed); // reset PRNG - const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints(); - const {cells, vertices} = calculateVoronoi(points, boundary); - return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices}; -} - -// place random points to calculate Voronoi diagram -function placePoints() { - TIME && console.time("placePoints"); - 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); - 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}; -} - -// calculate Delaunay and then Voronoi diagram -export function calculateVoronoi(points: TPoints, boundary: TPoints): IGraph { - TIME && console.time("calculateDelaunay"); - const allPoints: TPoints = points.concat(boundary); - const delaunay = Delaunator.from(allPoints); - TIME && console.timeEnd("calculateDelaunay"); - - TIME && console.time("calculateVoronoi"); - const {cells, vertices} = new Voronoi(delaunay, allPoints, points.length); - const i = createTypedArray({maxValue: points.length, length: points.length}).map((_, i) => i); // array of indexes - - TIME && console.timeEnd("calculateVoronoi"); - return {cells: {...cells, i}, vertices}; -} - -// add points along map edge to pseudo-clip voronoi cells -function getBoundaryPoints(width: number, height: number, spacing: number) { - 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: TPoints = []; - - 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: number, height: number, spacing: number) { - const radius = spacing / 2; // square radius - const jittering = radius * 0.9; // max deviation - const doubleJittering = jittering * 2; - const jitter = () => Math.random() * doubleJittering - jittering; - - const points: TPoints = []; - 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 export function findGridCell(x: number, y: number, grid: IGrid) { @@ -125,7 +39,7 @@ export function findGridAll(x: number, y: number, radius: number) { // return array of cell indexes in radius export function findAll(x: number, y: number, radius: number) { const found = pack.cells.q.findAll(x, y, radius); - return found.map(r => r[2]); + return found.map(data => data[2]); } // get polygon points for packed cells knowing cell id @@ -157,85 +71,3 @@ export function isWater(cellId: number) { export function isCoastal(i: number) { return pack.cells.t[i] === DISTANCE_FIELD.LAND_COAST; } - -// findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e -function addFindAll() { - const Quad = function (node, x0, y0, x1, y1) { - this.node = node; - this.x0 = x0; - this.y0 = y0; - this.x1 = x1; - this.y1 = y1; - }; - - const tree_filter = function (x, y, radius) { - var t = {x, y, x0: this._x0, y0: this._y0, x3: this._x1, y3: this._y1, quads: [], node: this._root}; - if (t.node) { - t.quads.push(new Quad(t.node, t.x0, t.y0, t.x3, t.y3)); - } - radiusSearchInit(t, radius); - - var i = 0; - while ((t.q = t.quads.pop())) { - i++; - - // Stop searching if this quadrant can’t contain a closer node. - if ( - !(t.node = t.q.node) || - (t.x1 = t.q.x0) > t.x3 || - (t.y1 = t.q.y0) > t.y3 || - (t.x2 = t.q.x1) < t.x0 || - (t.y2 = t.q.y1) < t.y0 - ) - continue; - - // Bisect the current quadrant. - if (t.node.length) { - t.node.explored = true; - var xm = (t.x1 + t.x2) / 2, - ym = (t.y1 + t.y2) / 2; - - t.quads.push( - new Quad(t.node[3], xm, ym, t.x2, t.y2), - new Quad(t.node[2], t.x1, ym, xm, t.y2), - new Quad(t.node[1], xm, t.y1, t.x2, ym), - new Quad(t.node[0], t.x1, t.y1, xm, ym) - ); - - // Visit the closest quadrant first. - if ((t.i = ((y >= ym) << 1) | (x >= xm))) { - t.q = t.quads[t.quads.length - 1]; - t.quads[t.quads.length - 1] = t.quads[t.quads.length - 1 - t.i]; - t.quads[t.quads.length - 1 - t.i] = t.q; - } - } - - // Visit this point. (Visiting coincident points isn’t necessary!) - else { - var dx = x - +this._x.call(null, t.node.data), - dy = y - +this._y.call(null, t.node.data), - d2 = dx * dx + dy * dy; - radiusSearchVisit(t, d2); - } - } - return t.result; - }; - d3.quadtree.prototype.findAll = tree_filter; - - var radiusSearchInit = function (t, radius) { - t.result = []; - (t.x0 = t.x - radius), (t.y0 = t.y - radius); - (t.x3 = t.x + radius), (t.y3 = t.y + radius); - t.radius = radius * radius; - }; - - var radiusSearchVisit = function (t, d2) { - t.node.data.scanned = true; - if (d2 < t.radius) { - do { - t.result.push(t.node.data); - t.node.data.selected = true; - } while ((t.node = t.node.next)); - } - }; -}