From 3c6da6585e29f9e2d5deab893a18e63b84aa64b2 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 24 Jul 2022 15:18:47 +0300 Subject: [PATCH] refactor: addLakesInDeepDepressions --- index.html | 1 - src/dialogs/dialogs/heightmap-editor.js | 8 +- src/modules/heightmap-generator.js | 6 +- src/modules/lakes.ts | 126 ------------------ src/modules/submap.js | 8 +- src/scripts/generation/generation.ts | 12 +- src/scripts/generation/graph.ts | 1 - src/scripts/generation/{ => grid}/grid.ts | 66 +++++---- src/scripts/generation/grid/lakes.ts | 117 ++++++++++++++++ .../generation/grid/precipitation.ts} | 115 ++++++++-------- .../generation/grid}/temperature.ts | 5 +- src/{modules => scripts/generation}/markup.ts | 27 +--- src/scripts/generation/pack/pack.ts | 2 +- src/scripts/generation/pack/rivers.ts | 4 +- src/types/grid.d.ts | 10 -- src/utils/graphUtils.ts | 8 +- 16 files changed, 250 insertions(+), 266 deletions(-) delete mode 100644 src/modules/lakes.ts rename src/scripts/generation/{ => grid}/grid.ts (52%) create mode 100644 src/scripts/generation/grid/lakes.ts rename src/{modules/precipitation.js => scripts/generation/grid/precipitation.ts} (67%) rename src/{modules => scripts/generation/grid}/temperature.ts (91%) rename src/{modules => scripts/generation}/markup.ts (93%) diff --git a/index.html b/index.html index b8d31638..b5cc670a 100644 --- a/index.html +++ b/index.html @@ -7642,7 +7642,6 @@ - diff --git a/src/dialogs/dialogs/heightmap-editor.js b/src/dialogs/dialogs/heightmap-editor.js index 5a9d9f71..214189b4 100644 --- a/src/dialogs/dialogs/heightmap-editor.js +++ b/src/dialogs/dialogs/heightmap-editor.js @@ -215,10 +215,10 @@ export function open(options) { const erosionAllowed = allowErosion.checked; markupGridFeatures(); - if (erosionAllowed) { - Lakes.addLakesInDeepDepressions(grid); - Lakes.openNearSeaLakes(grid); - } + // if (erosionAllowed) { + // Lakes.addLakesInDeepDepressions(grid); + // Lakes.openNearSeaLakes(grid); + // } OceanLayers(grid); calculateTemperatures(grid); generatePrecipitation(grid); diff --git a/src/modules/heightmap-generator.js b/src/modules/heightmap-generator.js index ec2235c2..f792f8c7 100644 --- a/src/modules/heightmap-generator.js +++ b/src/modules/heightmap-generator.js @@ -9,6 +9,7 @@ import {byId} from "utils/shorthands"; import {ERROR} from "../config/logging"; import {lim, minmax} from "../utils/numberUtils"; import {aleaPRNG} from "scripts/aleaPRNG"; +import {addLakesInDeepDepressions} from "scripts/generation/grid/lakes"; window.HeightmapGenerator = (function () { let grid = null; @@ -77,7 +78,10 @@ window.HeightmapGenerator = (function () { Math.random = aleaPRNG(seed); const isTemplate = id in heightmapTemplates; - const heights = isTemplate ? fromTemplate(graph, id) : await fromPrecreated(graph, id); + const rawHeights = isTemplate ? fromTemplate(graph, id) : await fromPrecreated(graph, id); + + const heights = addLakesInDeepDepressions(rawHeights, graph.cells.c, graph.cells.v, graph.vertices, graph.cells.i); + TIME && console.timeEnd("defineHeightmap"); clearData(); diff --git a/src/modules/lakes.ts b/src/modules/lakes.ts deleted file mode 100644 index 55ccb5ff..00000000 --- a/src/modules/lakes.ts +++ /dev/null @@ -1,126 +0,0 @@ -// @ts-nocheck -import * as d3 from "d3"; - -import {TIME} from "config/logging"; -import {aleaPRNG} from "scripts/aleaPRNG"; -import {getInputNumber, getInputValue} from "utils/nodeUtils"; -import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; -import {byId} from "utils/shorthands"; - -window.Lakes = (function () { - const {LAND_COAST, WATER_COAST} = DISTANCE_FIELD; - - function addLakesInDeepDepressions(grid: IGraph & Partial) { - const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput"); - if (ELEVATION_LIMIT === 80) return; - - TIME && console.time("addLakesInDeepDepressions"); - const {cells, features} = grid; - if (!features) throw new Error("addLakesInDeepDepressions: features are not defined"); - const {c, h, b} = cells; - - for (const i of cells.i) { - if (b[i] || h[i] < MIN_LAND_HEIGHT) continue; - - const minHeight = d3.min(c[i].map(c => h[c])) || 0; - if (h[i] > minHeight) continue; - - let deep = true; - const threshold = h[i] + ELEVATION_LIMIT; - const queue = [i]; - const checked = []; - checked[i] = true; - - // check if elevated cell can potentially pour to water - while (deep && queue.length) { - const q = queue.pop()!; - - for (const n of c[q]) { - if (checked[n]) continue; - if (h[n] >= threshold) continue; - if (h[n] < MIN_LAND_HEIGHT) { - deep = false; - break; - } - - checked[n] = true; - queue.push(n); - } - } - - // if not, add a lake - if (deep) { - const lakeCells = [i].concat(c[i].filter(n => h[n] === h[i])); - addLake(lakeCells); - } - } - - function addLake(lakeCells: number[]) { - const featureId = features!.length; - - for (const lakeCellId of lakeCells) { - cells.h[lakeCellId] = MIN_LAND_HEIGHT - 1; - cells.t[lakeCellId] = WATER_COAST; - cells.f[lakeCellId] = featureId; - - for (const neibCellId of c[lakeCellId]) { - if (!lakeCells.includes(neibCellId)) cells.t[neibCellId] = LAND_COAST; - } - } - - features!.push({i: featureId, land: false, border: false, type: "lake"}); - } - - TIME && console.timeEnd("addLakesInDeepDepressions"); - } - - // near sea lakes usually get a lot of water inflow, most of them should brake threshold and flow out to sea (see Ancylus Lake) - function openNearSeaLakes(grid: IGraph & Partial) { - if (getInputValue("templateInput") === "Atoll") return; // no need for Atolls - - const {cells, features} = grid; - if (!features?.find(f => f && f.type === "lake")) return; // no lakes - - TIME && console.time("openLakes"); - const LIMIT = 22; // max height that can be breached by water - - const isLake = (featureId: number) => featureId && (features[featureId] as IGridFeature).type === "lake"; - const isOcean = (featureId: number) => featureId && (features[featureId] as IGridFeature).type === "ocean"; - - for (const cellId of cells.i) { - const featureId = cells.f[cellId]; - if (!isLake(featureId)) continue; // not a lake cell - - check_neighbours: for (const neibCellId of cells.c[cellId]) { - // water cannot brake the barrier - if (cells.t[neibCellId] !== WATER_COAST || cells.h[neibCellId] > LIMIT) continue; - - for (const neibOfNeibCellId of cells.c[neibCellId]) { - const neibOfNeibFeatureId = cells.f[neibOfNeibCellId]; - if (!isOcean(neibOfNeibFeatureId)) continue; // not an ocean - removeLake(neibCellId, featureId, neibOfNeibFeatureId); - break check_neighbours; - } - } - } - - function removeLake(barrierCellId: number, lakeFeatureId: number, oceanFeatureId: number) { - cells.h[barrierCellId] = MIN_LAND_HEIGHT - 1; - cells.t[barrierCellId] = WATER_COAST; - cells.f[barrierCellId] = oceanFeatureId; - - for (const neibCellId of cells.c[barrierCellId]) { - if (cells.h[neibCellId] >= MIN_LAND_HEIGHT) cells.t[neibCellId] = LAND_COAST; - } - - if (features && lakeFeatureId) { - // mark former lake as ocean - (features[lakeFeatureId] as IGridFeature).type = "ocean"; - } - } - - TIME && console.timeEnd("openLakes"); - } - - return {generateName, getName, addLakesInDeepDepressions, openNearSeaLakes}; -})(); diff --git a/src/modules/submap.js b/src/modules/submap.js index e81ee0fe..75dc0daf 100644 --- a/src/modules/submap.js +++ b/src/modules/submap.js @@ -118,10 +118,10 @@ window.Submap = (function () { markupGridFeatures(); // Warning: addLakesInDeepDepressions can be very slow! - if (options.addLakesInDepressions) { - Lakes.addLakesInDeepDepressions(grid); - Lakes.openNearSeaLakes(grid); - } + // if (options.addLakesInDepressions) { + // Lakes.addLakesInDeepDepressions(grid); + // Lakes.openNearSeaLakes(grid); + // } OceanLayers(grid); diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index 838cc44a..b3de2a66 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -22,10 +22,10 @@ import {debounce} from "utils/functionUtils"; import {rn} from "utils/numberUtils"; import {generateSeed} from "utils/probabilityUtils"; import {byId} from "utils/shorthands"; -import {createGrid} from "./grid"; +import {createGrid} from "./grid/grid"; import {createPack} from "./pack/pack"; import {getInputValue, setInputValue} from "utils/nodeUtils"; -// import {Ruler} from "modules/measurers"; +import {calculateMapCoordinates} from "modules/coordinates"; const {Zoom, ThreeD} = window; @@ -50,6 +50,8 @@ async function generate(options?: IGenerationOptions) { applyMapSize(); randomizeOptions(); + window.mapCoordinates = calculateMapCoordinates(); + const newGrid = await createGrid(grid, precreatedGraph); const newPack = createPack(newGrid); @@ -60,10 +62,10 @@ async function generate(options?: IGenerationOptions) { pack = newPack; // temp rendering for debug - renderLayer("cells"); + // renderLayer("cells"); renderLayer("features"); - renderLayer("heightmap"); - renderLayer("rivers", pack); + // renderLayer("heightmap"); + // renderLayer("rivers", pack); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); diff --git a/src/scripts/generation/graph.ts b/src/scripts/generation/graph.ts index 2dd72193..a0cfd557 100644 --- a/src/scripts/generation/graph.ts +++ b/src/scripts/generation/graph.ts @@ -2,7 +2,6 @@ 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"; diff --git a/src/scripts/generation/grid.ts b/src/scripts/generation/grid/grid.ts similarity index 52% rename from src/scripts/generation/grid.ts rename to src/scripts/generation/grid/grid.ts index 0beecad0..1c8ba4e4 100644 --- a/src/scripts/generation/grid.ts +++ b/src/scripts/generation/grid/grid.ts @@ -1,41 +1,59 @@ -import {calculateTemperatures} from "modules/temperature"; +import {defineMapSize} from "modules/coordinates"; import {generateGrid} from "scripts/generation/graph"; -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 {markupGridFeatures} from "scripts/generation/markup"; import {rn} from "utils/numberUtils"; +import {byId} from "utils/shorthands"; +import {generatePrecipitation} from "./precipitation"; +import {calculateTemperatures} from "./temperature"; -const {Lakes, HeightmapGenerator} = window; +const {HeightmapGenerator} = window; -export async function createGrid(globalGrid: IGrid, precreatedGraph?: IGrid): Promise { - const baseGrid: IGridBase = shouldRegenerateGridPoints(globalGrid) - ? (precreatedGraph && undressGrid(precreatedGraph)) || generateGrid() - : undressGrid(globalGrid); +export async function createGrid(globalGrid: IGrid, precreatedGrid?: IGrid): Promise { + const shouldRegenerate = shouldRegenerateGridPoints(globalGrid); + const {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices} = shouldRegenerate + ? (precreatedGrid && undressPrecreatedGrid(precreatedGrid)) || generateGrid() + : undressPrecreatedGrid(globalGrid); - const heights: Uint8Array = await HeightmapGenerator.generate(baseGrid); + const heights: Uint8Array = await HeightmapGenerator.generate({ + vertices, + points, + cells, + cellsDesired, + spacing, + cellsX, + cellsY + }); 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 {featureIds, distanceField, features} = markupGridFeatures(cells.c, cells.b, heights); const touchesEdges = features.some(feature => feature && feature.land && feature.border); defineMapSize(touchesEdges); - window.mapCoordinates = calculateMapCoordinates(); - Lakes.addLakesInDeepDepressions(markedGrid); - Lakes.openNearSeaLakes(markedGrid); + const temp = calculateTemperatures(heights, cellsX, points); + const prec = generatePrecipitation(heights, temp, cellsX, cellsY); - const temperature = calculateTemperatures(markedGrid); - const temperatureGrid = {...markedGrid, cells: {...markedGrid.cells, temp: temperature}}; - - const prec = generatePrecipitation(temperatureGrid); - return {...temperatureGrid, cells: {...temperatureGrid.cells, prec}}; + return { + cellsDesired, + cellsX, + cellsY, + spacing, + boundary, + points, + vertices, + cells: { + ...cells, + h: heights, + f: featureIds, + t: distanceField, + prec, + temp + }, + features + }; } -function undressGrid(extendedGrid: IGrid): IGridBase { +function undressPrecreatedGrid(extendedGrid: IGrid) { 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}; diff --git a/src/scripts/generation/grid/lakes.ts b/src/scripts/generation/grid/lakes.ts new file mode 100644 index 00000000..49cd0c36 --- /dev/null +++ b/src/scripts/generation/grid/lakes.ts @@ -0,0 +1,117 @@ +import {TIME} from "config/logging"; +import {getInputNumber, getInputValue} from "utils/nodeUtils"; +import {DISTANCE_FIELD, MAX_HEIGHT, MIN_LAND_HEIGHT} from "config/generation"; +import {drawPolygon} from "utils/debugUtils"; + +const {LAND_COAST, WATER_COAST} = DISTANCE_FIELD; + +// near sea lakes usually get a lot of water inflow +// most of them would brake threshold and flow out to sea (see Ancylus Lake) +// connect these type of lakes to the main water body to improve the heightmap +export function openNearSeaLakes(grid: IGraph & Partial) { + if (getInputValue("templateInput") === "Atoll") return; // no need for Atolls + + const {cells, features} = grid; + if (!features?.find(f => f && f.type === "lake")) return; // no lakes + + TIME && console.time("openNearSeaLakes"); + const LIMIT = 22; // max height that can be breached by water + + const isLake = (featureId: number) => featureId && (features[featureId] as IGridFeature).type === "lake"; + const isOcean = (featureId: number) => featureId && (features[featureId] as IGridFeature).type === "ocean"; + + for (const cellId of cells.i) { + const featureId = cells.f[cellId]; + if (!isLake(featureId)) continue; // not a lake cell + + check_neighbours: for (const neibCellId of cells.c[cellId]) { + // water cannot brake the barrier + if (cells.t[neibCellId] !== WATER_COAST || cells.h[neibCellId] > LIMIT) continue; + + for (const neibOfNeibCellId of cells.c[neibCellId]) { + const neibOfNeibFeatureId = cells.f[neibOfNeibCellId]; + if (!isOcean(neibOfNeibFeatureId)) continue; // not an ocean + removeLake(neibCellId, featureId, neibOfNeibFeatureId); + break check_neighbours; + } + } + } + + function removeLake(barrierCellId: number, lakeFeatureId: number, oceanFeatureId: number) { + cells.h[barrierCellId] = MIN_LAND_HEIGHT - 1; + cells.t[barrierCellId] = WATER_COAST; + cells.f[barrierCellId] = oceanFeatureId; + + for (const neibCellId of cells.c[barrierCellId]) { + if (cells.h[neibCellId] >= MIN_LAND_HEIGHT) cells.t[neibCellId] = LAND_COAST; + } + + if (features && lakeFeatureId) { + // mark former lake as ocean + (features[lakeFeatureId] as IGridFeature).type = "ocean"; + } + } + + TIME && console.timeEnd("openNearSeaLakes"); +} + +// some deeply depressed areas may not be resolved on river generation +// this areas tend to collect precipitation, so we can add a lake there to help the resolver +export function addLakesInDeepDepressions( + heights: Uint8Array, + neighbours: number[][], + cellVertices: number[][], + vertices: IGraphVertices, + indexes: UintArray +) { + const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput"); + if (ELEVATION_LIMIT === MAX_HEIGHT - MIN_LAND_HEIGHT) return heights; // any depression can be resolved + + TIME && console.time("addLakesInDeepDepressions"); + + const landCells = indexes.filter(i => heights[i] >= MIN_LAND_HEIGHT); + landCells.sort((a, b) => heights[a] - heights[b]); // lower elevation first + + const currentHeights = new Uint8Array(heights); + const checkedCells: Dict = {[landCells[0]]: true}; + + for (const cellId of landCells) { + if (checkedCells[cellId]) continue; + + const THESHOLD_HEIGHT = currentHeights[cellId] + ELEVATION_LIMIT; + + let inDeepDepression = true; + + const queue = [cellId]; + const checkedPaths: Dict = {[cellId]: true}; + + while (queue.length) { + const nextCellId = queue.pop()!; + + if (currentHeights[nextCellId] < MIN_LAND_HEIGHT) { + inDeepDepression = false; + break; + } + + for (const neibCellId of neighbours[nextCellId]) { + if (checkedPaths[neibCellId]) continue; + + checkedPaths[neibCellId] = true; + checkedCells[neibCellId] = true; + + if (currentHeights[neibCellId] < THESHOLD_HEIGHT) queue.push(neibCellId); + } + } + + if (inDeepDepression) { + currentHeights[cellId] = MIN_LAND_HEIGHT - 1; + console.log(`ⓘ Added lake at deep depression. Cell: ${cellId}`); + + const polygon = cellVertices[cellId].map(vertex => vertices.p[vertex]); + drawPolygon(polygon, {stroke: "red", strokeWidth: 1, fill: "none"}); + } + } + + TIME && console.timeEnd("addLakesInDeepDepressions"); + return currentHeights; +} diff --git a/src/modules/precipitation.js b/src/scripts/generation/grid/precipitation.ts similarity index 67% rename from src/modules/precipitation.js rename to src/scripts/generation/grid/precipitation.ts index aea8c8fe..4b9bfda8 100644 --- a/src/modules/precipitation.js +++ b/src/scripts/generation/grid/precipitation.ts @@ -1,19 +1,21 @@ +// @ts-nocheck import * as d3 from "d3"; import {TIME} from "config/logging"; import {minmax} from "utils/numberUtils"; import {rand} from "utils/probabilityUtils"; +import {getInputNumber, getInputValue} from "utils/nodeUtils"; +import {byId} from "utils/shorthands"; // simplest precipitation model -export function generatePrecipitation(grid) { +export function generatePrecipitation(heights: Uint8Array, temperatures: Int8Array, cellsX: number, cellsY: number) { TIME && console.time("generatePrecipitation"); prec.selectAll("*").remove(); - const {cells, cellsX, cellsY} = grid; - const precipitation = new Uint8Array(cells.i.length); // precipitation array + const precipitation = new Uint8Array(heights.length); // precipitation array - const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25; - const precInputModifier = precInput.value / 100; + const cellsNumberModifier = (byId("pointsInput").dataset.cells / 10000) ** 0.25; + const precInputModifier = getInputNumber("precInput") / 100; const modifier = cellsNumberModifier * precInputModifier; const westerly = []; @@ -34,7 +36,7 @@ export function generatePrecipitation(grid) { 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, heights.length, cellsX).forEach(function (c, i) { const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT; const latBand = ((Math.abs(lat) - 1) / 5) | 0; const latMod = latitudeModifier[latBand]; @@ -63,7 +65,7 @@ export function generatePrecipitation(grid) { const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0; const latModS = mapCoordinates.latT > 60 ? d3.mean(latitudeModifier) : latitudeModifier[bandS]; const maxPrecS = (southerly / vertT) * 60 * modifier * latModS; - passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY); + passWind(d3.range(heights.length - cellsX, heights.length, 1), maxPrecS, -cellsX, cellsY); } function getWindDirections(tier) { @@ -86,15 +88,15 @@ export function generatePrecipitation(grid) { first = first[0]; } - let humidity = maxPrec - cells.h[first]; // initial water amount + let humidity = maxPrec - heights[first]; // initial water amount if (humidity <= 0) continue; // if first cell in row is too elevated consider wind dry for (let s = 0, current = first; s < steps; s++, current += next) { - if (cells.temp[current] < -5) continue; // no flux in permafrost + if (temperatures[current] < -5) continue; // no flux in permafrost - if (cells.h[current] < 20) { + if (heights[current] < 20) { // water cell - if (cells.h[current + next] >= 20) { + if (heights[current + next] >= 20) { precipitation[current + next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation } else { humidity = Math.min(humidity + 5 * modifier, maxPrec); // wind gets more humidity passing water cell @@ -104,7 +106,7 @@ export function generatePrecipitation(grid) { } // land cell - const isPassable = cells.h[current + next] <= MAX_PASSABLE_ELEVATION; + const isPassable = heights[current + next] <= MAX_PASSABLE_ELEVATION; const cellPrec = isPassable ? getPrecipitation(humidity, current, next) : humidity; precipitation[current] += cellPrec; const evaporation = cellPrec > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere @@ -115,53 +117,54 @@ export function generatePrecipitation(grid) { function getPrecipitation(humidity, i, n) { const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions - const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height - const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains + const diff = Math.max(heights[i + n] - heights[i], 0); // difference in height + const mod = (heights[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains return minmax(normalLoss + diff * mod, 1, humidity); } - void (function drawWindDirection() { - const wind = prec.append("g").attr("id", "wind"); - - d3.range(0, 6).forEach(function (t) { - if (westerly.length > 1) { - const west = westerly.filter(w => w[2] === t); - if (west && west.length > 3) { - const from = west[0][0], - to = west[west.length - 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); - if (east && east.length > 3) { - const from = east[0][0], - to = east[east.length - 1][0]; - const y = (grid.points[from][1] + grid.points[to][1]) / 2; - wind - .append("text") - .attr("x", graphWidth - 52) - .attr("y", y) - .text("\u21C7"); - } - } - }); - - if (northerly) - wind - .append("text") - .attr("x", graphWidth / 2) - .attr("y", 42) - .text("\u21CA"); - if (southerly) - wind - .append("text") - .attr("x", graphWidth / 2) - .attr("y", graphHeight - 20) - .text("\u21C8"); - })(); - TIME && console.timeEnd("generatePrecipitation"); return precipitation; } + +// TODO: move to renderer +function drawWindDirection() { + const wind = prec.append("g").attr("id", "wind"); + + d3.range(0, 6).forEach(function (t) { + if (westerly.length > 1) { + const west = westerly.filter(w => w[2] === t); + if (west && west.length > 3) { + const from = west[0][0]; + const to = west[west.length - 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); + if (east && east.length > 3) { + const from = east[0][0]; + const to = east[east.length - 1][0]; + const y = (grid.points[from][1] + grid.points[to][1]) / 2; + wind + .append("text") + .attr("x", graphWidth - 52) + .attr("y", y) + .text("\u21C7"); + } + } + }); + + if (northerly) + wind + .append("text") + .attr("x", graphWidth / 2) + .attr("y", 42) + .text("\u21CA"); + if (southerly) + wind + .append("text") + .attr("x", graphWidth / 2) + .attr("y", graphHeight - 20) + .text("\u21C8"); +} diff --git a/src/modules/temperature.ts b/src/scripts/generation/grid/temperature.ts similarity index 91% rename from src/modules/temperature.ts rename to src/scripts/generation/grid/temperature.ts index 5a5d3c41..b620db24 100644 --- a/src/modules/temperature.ts +++ b/src/scripts/generation/grid/temperature.ts @@ -7,12 +7,9 @@ import {MIN_LAND_HEIGHT} from "config/generation"; const interpolate = d3.easePolyInOut.exponent(0.5); // interpolation function -export function calculateTemperatures(grid: IGridWithHeights) { +export function calculateTemperatures(heights: Uint8Array, cellsX: number, points: TPoints) { TIME && console.time("calculateTemperatures"); - const {cells, cellsX, points} = grid; - const heights = cells.h; - const temperatures = new Int8Array(heights.length); // temperature array // temperature decreases by 6.5 Celsius per kilometer diff --git a/src/modules/markup.ts b/src/scripts/generation/markup.ts similarity index 93% rename from src/modules/markup.ts rename to src/scripts/generation/markup.ts index f7c01d87..3fc9dcd3 100644 --- a/src/modules/markup.ts +++ b/src/scripts/generation/markup.ts @@ -13,20 +13,13 @@ import {rn} from "utils/numberUtils"; const {UNMARKED, LAND_COAST, WATER_COAST, LANDLOCKED, DEEPER_WATER} = DISTANCE_FIELD; // define features (oceans, lakes, islands) -export function markupGridFeatures(grid: IGridWithHeights) { +export function markupGridFeatures(neighbors: IGraphCells["c"], borderCells: IGraphCells["b"], heights: Uint8Array) { TIME && console.time("markupGridFeatures"); Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode - if (!grid.cells || !grid.cells.h) { - throw new Error("markupGridFeatures: grid.cells.h is required"); - } - - const cells = grid.cells; - const heights = cells.h; - const n = cells.i.length; - - const featureIds = new Uint16Array(n); // starts from 1 - const distanceField = new Int8Array(n); + const gridCellsNumber = borderCells.length; + const featureIds = new Uint16Array(gridCellsNumber); // starts from 1 + const distanceField = new Int8Array(gridCellsNumber); const features: TGridFeatures = [0]; const queue = [0]; @@ -39,9 +32,9 @@ export function markupGridFeatures(grid: IGridWithHeights) { while (queue.length) { const cellId = queue.pop()!; - if (cells.b[cellId]) border = true; + if (borderCells[cellId]) border = true; - for (const neighborId of cells.c[cellId]) { + for (const neighborId of neighbors[cellId]) { const isNeibLand = heights[neighborId] >= MIN_LAND_HEIGHT; if (land === isNeibLand && featureIds[neighborId] === UNMARKED) { @@ -61,13 +54,7 @@ export function markupGridFeatures(grid: IGridWithHeights) { } // markup deep ocean cells - const dfOceanMarked = markup({ - distanceField, - neighbors: grid.cells.c, - start: DEEPER_WATER, - increment: -1, - limit: -10 - }); + const dfOceanMarked = markup({distanceField, neighbors, start: DEEPER_WATER, increment: -1, limit: -10}); TIME && console.timeEnd("markupGridFeatures"); return {featureIds, distanceField: dfOceanMarked, features}; diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index cf9b206d..b8cb4b0f 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -1,6 +1,6 @@ import * as d3 from "d3"; -import {markupPackFeatures} from "modules/markup"; +import {markupPackFeatures} from "scripts/generation/markup"; // @ts-expect-error js module import {drawScaleBar} from "modules/measurers"; // @ts-expect-error js module diff --git a/src/scripts/generation/pack/rivers.ts b/src/scripts/generation/pack/rivers.ts index 3ca29192..c0d39a02 100644 --- a/src/scripts/generation/pack/rivers.ts +++ b/src/scripts/generation/pack/rivers.ts @@ -8,7 +8,6 @@ import {getInputNumber} from "utils/nodeUtils"; import {pick} from "utils/functionUtils"; import {byId} from "utils/shorthands"; import {mergeLakeData, getClimateData, ILakeClimateData} from "./lakes"; -import {drawArrow} from "utils/debugUtils"; const {Rivers} = window; const {LAND_COAST} = DISTANCE_FIELD; @@ -376,8 +375,7 @@ const resolveDepressions = function ( return [initialCellHeights, {}]; } - INFO && - console.info(`ⓘ Resolved all depressions. Depressions: ${depressions[0]}. Interations: ${depressions.length}`); + INFO && console.info(`ⓘ Resolved all depressions. Depressions: ${depressions[0]}. Iterations: ${depressions.length}`); return [currentCellHeights, currentDrainableLakes]; // define lakes that potentially can be open (drained into another water body) diff --git a/src/types/grid.d.ts b/src/types/grid.d.ts index c6417078..99b06d9d 100644 --- a/src/types/grid.d.ts +++ b/src/types/grid.d.ts @@ -17,16 +17,6 @@ interface IGridCells { prec: Uint8Array; // precipitation in inner units } -interface IGridBase extends IGrid { - cells: IGraphCells & Partial; - features?: TGridFeatures; -} - -interface IGridWithHeights extends IGrid { - cells: IGraphCells & Partial & {h: Uint8Array}; - features?: TGridFeatures; -} - type TGridFeatures = [0, ...IGridFeature[]]; interface IGridFeature { diff --git a/src/utils/graphUtils.ts b/src/utils/graphUtils.ts index 1a8a9f47..963fcb1b 100644 --- a/src/utils/graphUtils.ts +++ b/src/utils/graphUtils.ts @@ -1,13 +1,9 @@ import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; -// @ts-expect-error js module -import {aleaPRNG} from "scripts/aleaPRNG"; // return cell index on a regular square 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)) - ); + /* use */ const {spacing, cellsX, cellsY} = grid; + return Math.floor(Math.min(y / spacing, cellsY - 1)) * cellsX + Math.floor(Math.min(x / spacing, cellsX - 1)); } // return array of cell indexes in radius on a regular square grid