Fantasy-Map-Generator/src/modules/lakes.ts

130 lines
No EOL
4.1 KiB
TypeScript

import { PackedGraphFeature } from "./features";
import { min, mean } from "d3";
import { byId,
rn } from "../utils";
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 = function () {
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: 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: 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();