Fantasy-Map-Generator/src/modules/lakes.ts
Marc Emmanuel 9db40a5230
Some checks are pending
Deploy static content to Pages / deploy (push) Waiting to run
Code quality / quality (push) Waiting to run
chore: add biome for linting/formatting + CI action for linting in SRC folder (#1284)
* chore: add npm + vite for progressive enhancement

* fix: update Dockerfile to copy only the dist folder contents

* fix: update Dockerfile to use multi-stage build for optimized production image

* fix: correct nginx config file copy command in Dockerfile

* chore: add netlify configuration for build and redirects

* fix: add NODE_VERSION to environment in Netlify configuration

* remove wrong dist folder

* Update package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: split public and src

* migrating all util files from js to ts

* feat: Implement HeightmapGenerator and Voronoi module

- Added HeightmapGenerator class for generating heightmaps with various tools (Hill, Pit, Range, Trough, Strait, etc.).
- Introduced Voronoi class for creating Voronoi diagrams using Delaunator.
- Updated index.html to include new modules.
- Created index.ts to manage module imports.
- Enhanced arrayUtils and graphUtils with type definitions and improved functionality.
- Added utility functions for generating grids and calculating Voronoi cells.

* chore: add GitHub Actions workflow for deploying to GitHub Pages

* fix: update branch name in GitHub Actions workflow from 'main' to 'master'

* chore: update package.json to specify Node.js engine version and remove unused launch.json

* Initial plan

* Update copilot guidelines to reflect NPM/Vite/TypeScript migration

Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>

* Update src/modules/heightmap-generator.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/utils/graphUtils.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/heightmap-generator.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: Add TIME and ERROR variables to global scope in HeightmapGenerator

* fix: Update base path in vite.config.ts for Netlify deployment

* refactor: Migrate features to a new module and remove legacy script reference

* refactor: Update feature interfaces and improve type safety in FeatureModule

* refactor: Add documentation for markupPack and defineGroups methods in FeatureModule

* refactor: Remove legacy ocean-layers.js and migrate functionality to ocean-layers.ts

* refactor: Remove river-generator.js script reference and migrate river generation logic to river-generator.ts

* refactor: Remove river-generator.js reference and add biomes module

* refactor: Migrate lakes functionality to lakes.ts and update related interfaces

* refactor: clean up global variable declarations and improve type definitions

* refactor: update shoreline calculation and improve type imports in PackedGraph

* fix: e2e tests

* chore: add biome for linting/formatting

* chore: add linting workflow using Biome

* refactor: improve code readability by standardizing string quotes and simplifying function calls

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Azgaar <maxganiev@yandex.com>
Co-authored-by: Azgaar <azgaar.fmg@yandex.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>
2026-01-26 22:30:28 +01:00

146 lines
4.3 KiB
TypeScript

import { mean, min } from "d3";
import { byId, rn } from "../utils";
import type { PackedGraphFeature } from "./features";
declare global {
var Lakes: LakesModule;
}
export class LakesModule {
private LAKE_ELEVATION_DELTA = 0.1;
getHeight(feature: PackedGraphFeature) {
const heights = pack.cells.h;
const minShoreHeight =
min(feature.shoreline.map((cellId) => heights[cellId])) || 20;
return rn(minShoreHeight - this.LAKE_ELEVATION_DELTA, 2);
}
defineNames() {
pack.features.forEach((feature: PackedGraphFeature) => {
if (feature.type !== "lake") return;
feature.name = this.getName(feature);
});
}
getName(feature: PackedGraphFeature): string {
const landCell = feature.shoreline[0];
const culture = pack.cells.culture[landCell];
return Names.getCulture(culture);
}
cleanupLakeData = () => {
for (const feature of pack.features) {
if (feature.type !== "lake") continue;
delete feature.river;
delete feature.enteringFlux;
delete feature.outCell;
delete feature.closed;
feature.height = rn(feature.height, 3);
const inlets = feature.inlets?.filter((r) =>
pack.rivers.find((river) => river.i === r),
);
if (!inlets || !inlets.length) delete feature.inlets;
else feature.inlets = inlets;
const outlet =
feature.outlet &&
pack.rivers.find((river) => river.i === feature.outlet);
if (!outlet) delete feature.outlet;
}
};
defineClimateData(heights: number[] | Uint8Array) {
const { cells, features } = pack;
const lakeOutCells = new Uint16Array(cells.i.length);
const getFlux = (lake: PackedGraphFeature) => {
return lake.shoreline.reduce(
(acc, c) => acc + grid.cells.prec[cells.g[c]],
0,
);
};
const getLakeTemp = (lake: PackedGraphFeature) => {
if (lake.cells < 6) return grid.cells.temp[cells.g[lake.firstCell]];
return rn(
mean(lake.shoreline.map((c) => grid.cells.temp[cells.g[c]])) as number,
1,
);
};
const getLakeEvaporation = (lake: PackedGraphFeature) => {
const height = (lake.height - 18) ** Number(heightExponentInput.value); // height in meters
const evaporation =
((700 * (lake.temp + 0.006 * height)) / 50 + 75) / (80 - lake.temp); // based on Penman formula, [1-11]
return rn(evaporation * lake.cells);
};
const getLowestShoreCell = (lake: PackedGraphFeature) => {
return lake.shoreline.sort((a, b) => heights[a] - heights[b])[0];
};
features.forEach((feature) => {
if (feature.type !== "lake") return;
feature.flux = getFlux(feature);
feature.temp = getLakeTemp(feature);
feature.evaporation = getLakeEvaporation(feature);
if (feature.closed) return; // no outlet for lakes in depressed areas
feature.outCell = getLowestShoreCell(feature);
lakeOutCells[feature.outCell as number] = feature.i;
});
return lakeOutCells;
}
// check if lake can be potentially open (not in deep depression)
detectCloseLakes(h: number[] | Uint8Array) {
const { cells } = pack;
const ELEVATION_LIMIT = +(
byId("lakeElevationLimitOutput") as HTMLInputElement
)?.value;
pack.features.forEach((feature) => {
if (feature.type !== "lake") return;
delete feature.closed;
const MAX_ELEVATION = feature.height + ELEVATION_LIMIT;
if (MAX_ELEVATION > 99) {
feature.closed = false;
return;
}
let isDeep = true;
const lowestShorelineCell = feature.shoreline.sort(
(a, b) => h[a] - h[b],
)[0];
const queue = [lowestShorelineCell];
const checked = [];
checked[lowestShorelineCell] = true;
while (queue.length && isDeep) {
const cellId: number = queue.pop() as number;
for (const neibCellId of cells.c[cellId]) {
if (checked[neibCellId]) continue;
if (h[neibCellId] >= MAX_ELEVATION) continue;
if (h[neibCellId] < 20) {
const nFeature = pack.features[cells.f[neibCellId]];
if (nFeature.type === "ocean" || feature.height > nFeature.height)
isDeep = false;
}
checked[neibCellId] = true;
queue.push(neibCellId);
}
}
feature.closed = isDeep;
});
}
}
window.Lakes = new LakesModule();