diff --git a/src/index.html b/src/index.html
index 5d9cf35a..8896a2e6 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8469,7 +8469,6 @@
-
diff --git a/src/modules/PackedGraph.ts b/src/modules/PackedGraph.ts
index 1430c860..d6295430 100644
--- a/src/modules/PackedGraph.ts
+++ b/src/modules/PackedGraph.ts
@@ -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[];
diff --git a/src/modules/biomes.ts b/src/modules/biomes.ts
index 49bc24b8..ba6e58a9 100644
--- a/src/modules/biomes.ts
+++ b/src/modules/biomes.ts
@@ -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;
diff --git a/src/modules/features.ts b/src/modules/features.ts
index 75367278..e6b935c6 100644
--- a/src/modules/features.ts
+++ b/src/modules/features.ts
@@ -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");
}
diff --git a/src/modules/index.ts b/src/modules/index.ts
index 21836080..c15b421a 100644
--- a/src/modules/index.ts
+++ b/src/modules/index.ts
@@ -1,6 +1,7 @@
import "./voronoi";
import "./heightmap-generator";
import "./features";
+import "./lakes";
import "./ocean-layers";
import "./river-generator";
import "./biomes"
\ No newline at end of file
diff --git a/public/modules/lakes.js b/src/modules/lakes.ts
similarity index 65%
rename from public/modules/lakes.js
rename to src/modules/lakes.ts
index 8ce18793..1665a3ab 100644
--- a/public/modules/lakes.js
+++ b/src/modules/lakes.ts
@@ -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();
\ No newline at end of file
diff --git a/src/modules/river-generator.ts b/src/modules/river-generator.ts
index 98a567fd..7a6ed447 100644
--- a/src/modules/river-generator.ts
+++ b/src/modules/river-generator.ts
@@ -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;
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;
diff --git a/src/utils/polyfills.ts b/src/utils/polyfills.ts
index 18f5f1bd..89a0d2d9 100644
--- a/src/utils/polyfills.ts
+++ b/src/utils/polyfills.ts
@@ -44,7 +44,7 @@ declare global {
}
interface Array {
- flat(depth?: number): T[];
+ flat(depth?: number): T;
at(index: number): T | undefined;
}