mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-02-04 17:41:23 +01:00
refactor: Migrate lakes functionality to lakes.ts and update related interfaces
This commit is contained in:
parent
49e7e5c533
commit
371c775578
8 changed files with 136 additions and 110 deletions
|
|
@ -8469,7 +8469,6 @@
|
|||
|
||||
<script defer src="config/heightmap-templates.js"></script>
|
||||
<script defer src="config/precreated-heightmaps.js"></script>
|
||||
<script defer src="modules/lakes.js?v=1.99.00"></script>
|
||||
<script defer src="modules/names-generator.js?v=1.106.0"></script>
|
||||
<script defer src="modules/cultures-generator.js?v=1.106.0"></script>
|
||||
<script defer src="modules/burgs-generator.js?v=1.109.5"></script>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
import { PackedGraphFeature } from "./features";
|
||||
import { River } from "./river-generator";
|
||||
|
||||
|
||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int8Array | Int16Array | Float32Array | Float64Array;
|
||||
|
||||
export interface PackedGraph {
|
||||
cells: {
|
||||
i: number[]; // cell indices
|
||||
c: number[][]; // neighboring cells
|
||||
v: number[][]; // neighboring vertices
|
||||
p: [number, number][]; // cell polygon points
|
||||
b: boolean[]; // cell is on border
|
||||
h: Uint8Array; // cell heights
|
||||
t: Uint8Array; // cell terrain types
|
||||
h: TypedArray; // cell heights
|
||||
t: TypedArray; // cell terrain types
|
||||
r: Uint16Array; // river id passing through cell
|
||||
f: Uint16Array; // feature id occupying cell
|
||||
fl: Uint16Array | Uint8Array; // flux presence in cell
|
||||
conf: Uint16Array | Uint8Array; // cell water confidence
|
||||
haven: Uint8Array; // cell is a haven
|
||||
fl: TypedArray; // flux presence in cell
|
||||
conf: TypedArray; // cell water confidence
|
||||
haven: TypedArray; // cell is a haven
|
||||
g: number[]; // cell ground type
|
||||
culture: number[]; // cell culture id
|
||||
p: [number, number][]; // cell polygon points
|
||||
biome: TypedArray; // cell biome id
|
||||
harbor: TypedArray; // cell harbour presence
|
||||
};
|
||||
vertices: {
|
||||
i: number[]; // vertex indices
|
||||
|
|
@ -24,6 +29,7 @@ export interface PackedGraph {
|
|||
v: number[][]; // neighboring vertices
|
||||
x: number[]; // x coordinates
|
||||
y: number[]; // y coordinates
|
||||
p: [number, number][]; // vertex points
|
||||
};
|
||||
rivers: River[];
|
||||
features: PackedGraphFeature[];
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { range, mean } from "d3";
|
||||
import { rn } from "../utils";
|
||||
import { PackedGraph } from "./PackedGraph";
|
||||
|
||||
declare global {
|
||||
var Biomes: BiomesModule;
|
||||
|
||||
var pack: any;
|
||||
var pack: PackedGraph;
|
||||
var grid: any;
|
||||
var TIME: boolean;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import { clipPoly, connectVertices, createTypedArray, distanceSquared, isLand, isWater, rn, TYPED_ARRAY_MAX_VALUES,unique } from "../utils";
|
||||
import Alea from "alea";
|
||||
import { polygonArea } from "d3";
|
||||
import { LakesModule } from "./lakes";
|
||||
import { PackedGraph } from "./PackedGraph";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Features: any;
|
||||
}
|
||||
var TIME: boolean;
|
||||
var Lakes: any;
|
||||
var Lakes: LakesModule;
|
||||
var grid: any;
|
||||
var pack: any;
|
||||
var pack: PackedGraph;
|
||||
var seed: string;
|
||||
}
|
||||
|
||||
|
|
@ -30,11 +32,15 @@ export interface PackedGraphFeature {
|
|||
temp: number;
|
||||
flux: number;
|
||||
evaporation: number;
|
||||
inlets: number[];
|
||||
outlet: number;
|
||||
river: number;
|
||||
enteringFlux: number;
|
||||
closed: boolean;
|
||||
name: string;
|
||||
|
||||
// River related
|
||||
inlets?: number[];
|
||||
outlet?: number;
|
||||
river?: number;
|
||||
enteringFlux?: number;
|
||||
closed?: boolean;
|
||||
outCell?: number;
|
||||
}
|
||||
|
||||
export interface GridFeature {
|
||||
|
|
@ -210,7 +216,7 @@ class FeatureModule {
|
|||
if (type === "lake") {
|
||||
if (area > 0) feature.vertices = (feature.vertices as number[]).reverse();
|
||||
feature.shoreline = unique((feature.vertices as number[]).map(vertex => vertices.c[vertex].filter((index: number) => isLand(index, this.packedGraph))).flat() || []);
|
||||
feature.height = Lakes.getHeight(feature);
|
||||
feature.height = Lakes.getHeight(feature as PackedGraphFeature);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -278,7 +284,7 @@ class FeatureModule {
|
|||
this.packedGraph.cells.f = featureIds;
|
||||
this.packedGraph.cells.haven = haven;
|
||||
this.packedGraph.cells.harbor = harbor;
|
||||
this.packedGraph.features = [0, ...features];
|
||||
this.packedGraph.features = [0 as unknown as PackedGraphFeature, ...features];
|
||||
|
||||
TIME && console.timeEnd("markupPack");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import "./voronoi";
|
||||
import "./heightmap-generator";
|
||||
import "./features";
|
||||
import "./lakes";
|
||||
import "./ocean-layers";
|
||||
import "./river-generator";
|
||||
import "./biomes"
|
||||
|
|
@ -1,12 +1,98 @@
|
|||
"use strict";
|
||||
import { PackedGraphFeature } from "./features";
|
||||
import { min, mean } from "d3";
|
||||
import { byId,
|
||||
rn } from "../utils";
|
||||
import { PackedGraph } from "./PackedGraph";
|
||||
|
||||
window.Lakes = (function () {
|
||||
const LAKE_ELEVATION_DELTA = 0.1;
|
||||
declare global {
|
||||
var Lakes: LakesModule;
|
||||
var pack: PackedGraph;
|
||||
var Names: any;
|
||||
|
||||
var heightExponentInput: HTMLInputElement;
|
||||
}
|
||||
|
||||
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)
|
||||
const detectCloseLakes = h => {
|
||||
detectCloseLakes(h: Uint8Array) {
|
||||
const {cells} = pack;
|
||||
const ELEVATION_LIMIT = +byId("lakeElevationLimitOutput").value;
|
||||
const ELEVATION_LIMIT = +(byId("lakeElevationLimitOutput") as HTMLInputElement)?.value;
|
||||
|
||||
pack.features.forEach(feature => {
|
||||
if (feature.type !== "lake") return;
|
||||
|
|
@ -25,7 +111,7 @@ window.Lakes = (function () {
|
|||
checked[lowestShorelineCell] = true;
|
||||
|
||||
while (queue.length && isDeep) {
|
||||
const cellId = queue.pop();
|
||||
const cellId: number = queue.pop() as number;
|
||||
|
||||
for (const neibCellId of cells.c[cellId]) {
|
||||
if (checked[neibCellId]) continue;
|
||||
|
|
@ -44,80 +130,6 @@ window.Lakes = (function () {
|
|||
feature.closed = isDeep;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const defineClimateData = function (heights) {
|
||||
const {cells, features} = pack;
|
||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||
|
||||
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] = feature.i;
|
||||
});
|
||||
|
||||
return lakeOutCells;
|
||||
|
||||
function getFlux(lake) {
|
||||
return lake.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||
}
|
||||
|
||||
function getLakeTemp(lake) {
|
||||
if (lake.cells < 6) return grid.cells.temp[cells.g[lake.firstCell]];
|
||||
return rn(d3.mean(lake.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
||||
}
|
||||
|
||||
function getLakeEvaporation(lake) {
|
||||
const height = (lake.height - 18) ** 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);
|
||||
}
|
||||
|
||||
function getLowestShoreCell(lake) {
|
||||
return lake.shoreline.sort((a, b) => heights[a] - heights[b])[0];
|
||||
}
|
||||
};
|
||||
|
||||
const 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;
|
||||
}
|
||||
};
|
||||
|
||||
const getHeight = function (feature) {
|
||||
const heights = pack.cells.h;
|
||||
const minShoreHeight = d3.min(feature.shoreline.map(cellId => heights[cellId])) || 20;
|
||||
return rn(minShoreHeight - LAKE_ELEVATION_DELTA, 2);
|
||||
};
|
||||
|
||||
const defineNames = function () {
|
||||
pack.features.forEach(feature => {
|
||||
if (feature.type !== "lake") return;
|
||||
feature.name = getName(feature);
|
||||
});
|
||||
};
|
||||
|
||||
const getName = function (feature) {
|
||||
const landCell = feature.shoreline[0];
|
||||
const culture = pack.cells.culture[landCell];
|
||||
return Names.getCulture(culture);
|
||||
};
|
||||
|
||||
return {defineClimateData, cleanupLakeData, detectCloseLakes, getHeight, defineNames, getName};
|
||||
})();
|
||||
window.Lakes = new LakesModule();
|
||||
|
|
@ -7,6 +7,7 @@ rn,round,
|
|||
rw} from "../utils";
|
||||
import { PackedGraphFeature } from "./features";
|
||||
import { PackedGraph } from "./PackedGraph";
|
||||
import { LakesModule } from "./lakes";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -16,7 +17,7 @@ declare global {
|
|||
var WARN: boolean;
|
||||
var graphHeight: number;
|
||||
var graphWidth: number;
|
||||
var pack: any;
|
||||
var pack: PackedGraph;
|
||||
|
||||
var rivers: Selection<SVGElement, unknown, null, undefined>;
|
||||
var pointsInput: HTMLInputElement;
|
||||
|
|
@ -25,7 +26,7 @@ declare global {
|
|||
var TIME: boolean;
|
||||
|
||||
var Names: any;
|
||||
var Lakes: any;
|
||||
var Lakes: LakesModule;
|
||||
}
|
||||
|
||||
export interface River {
|
||||
|
|
@ -114,8 +115,8 @@ class RiverModule {
|
|||
const sameRiver = cells.c[lakeCell].some((c: number) => cells.r[c] === lake.river);
|
||||
|
||||
if (sameRiver) {
|
||||
cells.r[lakeCell] = lake.river;
|
||||
addCellToRiver(lakeCell, lake.river);
|
||||
cells.r[lakeCell] = lake.river as number;
|
||||
addCellToRiver(lakeCell, lake.river as number);
|
||||
} else {
|
||||
cells.r[lakeCell] = riverNext;
|
||||
addCellToRiver(lakeCell, riverNext);
|
||||
|
|
@ -132,7 +133,7 @@ class RiverModule {
|
|||
for (const lake of lakes) {
|
||||
if (!Array.isArray(lake.inlets)) continue;
|
||||
for (const inlet of lake.inlets) {
|
||||
riverParents[inlet] = outlet;
|
||||
riverParents[inlet] = outlet as number;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +200,7 @@ class RiverModule {
|
|||
// pour water to the water body
|
||||
const waterBody = features[cells.f[toCell]];
|
||||
if (waterBody.type === "lake") {
|
||||
if (!waterBody.river || fromFlux > waterBody.enteringFlux) {
|
||||
if (!waterBody.river || fromFlux > (waterBody.enteringFlux as number)) {
|
||||
waterBody.river = river;
|
||||
waterBody.enteringFlux = fromFlux;
|
||||
}
|
||||
|
|
@ -320,16 +321,16 @@ class RiverModule {
|
|||
TIME && console.timeEnd("generateRivers");
|
||||
};
|
||||
|
||||
alterHeights() {
|
||||
alterHeights(): Uint8Array {
|
||||
const {h, c, t} = this.pack.cells as {h: Uint8Array, c: number[][], t: Uint8Array};
|
||||
return Array.from(h).map((h, i) => {
|
||||
return Uint8Array.from(Array.from(h).map((h, i) => {
|
||||
if (h < 20 || t[i] < 1) return h;
|
||||
return h + t[i] / 100 + (mean(c[i].map(c => t[c])) || 0) / 10000;
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
// depression filling algorithm (for a correct water flux modeling)
|
||||
resolveDepressions(h: number[]) {
|
||||
resolveDepressions(h: Uint8Array) {
|
||||
const {cells, features} = this.pack;
|
||||
const maxIterations = +(document.getElementById("resolveDepressionsStepsOutput") as HTMLInputElement)?.value;
|
||||
const checkLakeMaxIteration = maxIterations * 0.85;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ declare global {
|
|||
}
|
||||
|
||||
interface Array<T> {
|
||||
flat(depth?: number): T[];
|
||||
flat(depth?: number): T;
|
||||
at(index: number): T | undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue