refactor: clean up global variable declarations and improve type definitions

This commit is contained in:
Marc Emmanuel 2026-01-25 19:13:02 +01:00
parent 613e826133
commit 3ed3d0dbd8
8 changed files with 105 additions and 177 deletions

View file

@ -1,24 +1,8 @@
import { range, mean } from "d3"; import { range, mean } from "d3";
import { rn } from "../utils"; import { rn } from "../utils";
import { PackedGraph } from "./PackedGraph";
declare global { declare global {
var Biomes: BiomesModule; var Biomes: BiomesModule;
var pack: PackedGraph;
var grid: any;
var TIME: boolean;
var biomesData: {
i: number[];
name: string[];
color: string[];
biomesMartix: Uint8Array[];
habitability: number[];
iconsDensity: number[];
icons: string[][];
cost: number[];
};
} }
class BiomesModule { class BiomesModule {
@ -74,7 +58,7 @@ class BiomesModule {
{swamp: 1} {swamp: 1}
]; ];
const cost: number[] = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost const cost: number[] = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
const biomesMartix: Uint8Array[] = [ const biomesMatrix: Uint8Array[] = [
// hot ↔ cold [>19°C; <-4°C]; dry ↕ wet // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]), new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]),
new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]), new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]),
@ -95,7 +79,7 @@ class BiomesModule {
parsedIcons[i] = parsed; parsedIcons[i] = parsed;
} }
return {i: range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons: parsedIcons, cost}; return {i: range(0, name.length), name, color, biomesMatrix, habitability, iconsDensity, icons: parsedIcons, cost};
}; };
define() { define() {
@ -135,7 +119,7 @@ class BiomesModule {
// in other cases use biome matrix // in other cases use biome matrix
const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4] const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25] const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
return biomesData.biomesMartix[moistureBand][temperatureBand]; return biomesData.biomesMatrix[moistureBand][temperatureBand];
} }
private isWetland(moisture: number, temperature: number, height: number) { private isWetland(moisture: number, temperature: number, height: number) {

View file

@ -1,18 +1,9 @@
import { clipPoly, connectVertices, createTypedArray, distanceSquared, isLand, isWater, rn, TYPED_ARRAY_MAX_VALUES,unique } from "../utils"; import { clipPoly, connectVertices, createTypedArray, distanceSquared, isLand, isWater, rn, TYPED_ARRAY_MAX_VALUES,unique } from "../utils";
import Alea from "alea"; import Alea from "alea";
import { polygonArea } from "d3"; import { polygonArea } from "d3";
import { LakesModule } from "./lakes";
import { PackedGraph } from "./PackedGraph";
declare global { declare global {
interface Window { var Features: FeatureModule;
Features: any;
}
var TIME: boolean;
var Lakes: LakesModule;
var grid: any;
var pack: PackedGraph;
var seed: string;
} }
type FeatureType = "ocean" | "lake" | "island"; type FeatureType = "ocean" | "lake" | "island";
@ -58,18 +49,6 @@ class FeatureModule {
private WATER_COAST = -1; private WATER_COAST = -1;
private DEEP_WATER = -2; private DEEP_WATER = -2;
private get grid() {
return grid;
}
private get packedGraph() {
return pack;
}
private get seed() {
return seed;
}
/** /**
* calculate distance to coast for every cell * calculate distance to coast for every cell
*/ */
@ -100,9 +79,9 @@ class FeatureModule {
*/ */
markupGrid() { markupGrid() {
TIME && console.time("markupGrid"); TIME && console.time("markupGrid");
Math.random = Alea(this.seed); // get the same result on heightmap edit in Erase mode Math.random = Alea(seed); // get the same result on heightmap edit in Erase mode
const { h: heights, c: neighbors, b: borderCells, i } = this.grid.cells; const { h: heights, c: neighbors, b: borderCells, i } = grid.cells;
const cellsNumber = i.length; const cellsNumber = i.length;
const distanceField = new Int8Array(cellsNumber); // gird.cells.t const distanceField = new Int8Array(cellsNumber); // gird.cells.t
const featureIds = new Uint16Array(cellsNumber); // gird.cells.f const featureIds = new Uint16Array(cellsNumber); // gird.cells.f
@ -141,9 +120,9 @@ class FeatureModule {
// markup deep ocean cells // markup deep ocean cells
this.markup({ distanceField, neighbors, start: this.DEEP_WATER, increment: -1, limit: -10 }); this.markup({ distanceField, neighbors, start: this.DEEP_WATER, increment: -1, limit: -10 });
this.grid.cells.t = distanceField; grid.cells.t = distanceField;
this.grid.cells.f = featureIds; grid.cells.f = featureIds;
this.grid.features = [0, ...features]; grid.features = [0, ...features];
TIME && console.timeEnd("markupGrid"); TIME && console.timeEnd("markupGrid");
} }
@ -153,7 +132,7 @@ class FeatureModule {
*/ */
markupPack() { markupPack() {
const defineHaven = (cellId: number) => { const defineHaven = (cellId: number) => {
const waterCells = neighbors[cellId].filter((index: number) => isWater(index, this.packedGraph)); const waterCells = neighbors[cellId].filter((index: number) => isWater(index, pack));
const distances = waterCells.map((neibCellId: number) => distanceSquared(cells.p[cellId], cells.p[neibCellId])); const distances = waterCells.map((neibCellId: number) => distanceSquared(cells.p[cellId], cells.p[neibCellId]));
const closest = distances.indexOf(Math.min.apply(Math, distances)); const closest = distances.indexOf(Math.min.apply(Math, distances));
@ -215,7 +194,7 @@ class FeatureModule {
if (type === "lake") { if (type === "lake") {
if (area > 0) feature.vertices = (feature.vertices as number[]).reverse(); 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.shoreline = unique((feature.vertices as number[]).map(vertex => vertices.c[vertex].filter((index: number) => isLand(index, pack))).flat() || []);
feature.height = Lakes.getHeight(feature as PackedGraphFeature); feature.height = Lakes.getHeight(feature as PackedGraphFeature);
} }
@ -226,7 +205,7 @@ class FeatureModule {
TIME && console.time("markupPack"); TIME && console.time("markupPack");
const { cells, vertices } = this.packedGraph; const { cells, vertices } = pack;
const { c: neighbors, b: borderCells, i } = cells; const { c: neighbors, b: borderCells, i } = cells;
const packCellsNumber = i.length; const packCellsNumber = i.length;
if (!packCellsNumber) return; // no cells -> there is nothing to do if (!packCellsNumber) return; // no cells -> there is nothing to do
@ -242,7 +221,7 @@ class FeatureModule {
const firstCell = queue[0]; const firstCell = queue[0];
featureIds[firstCell] = featureId; featureIds[firstCell] = featureId;
const land = isLand(firstCell, this.packedGraph); const land = isLand(firstCell, pack);
let border = Boolean(borderCells[firstCell]); // true if feature touches map border let border = Boolean(borderCells[firstCell]); // true if feature touches map border
let totalCells = 1; // count cells in a feature let totalCells = 1; // count cells in a feature
@ -252,7 +231,7 @@ class FeatureModule {
if (!border && borderCells[cellId]) border = true; if (!border && borderCells[cellId]) border = true;
for (const neighborId of neighbors[cellId]) { for (const neighborId of neighbors[cellId]) {
const isNeibLand = isLand(neighborId, this.packedGraph); const isNeibLand = isLand(neighborId, pack);
if (land && !isNeibLand) { if (land && !isNeibLand) {
distanceField[cellId] = this.LAND_COAST; distanceField[cellId] = this.LAND_COAST;
@ -280,12 +259,11 @@ class FeatureModule {
this.markup({ distanceField, neighbors, start: this.DEEPER_LAND, increment: 1 }); // markup pack land this.markup({ distanceField, neighbors, start: this.DEEPER_LAND, increment: 1 }); // markup pack land
this.markup({ distanceField, neighbors, start: this.DEEP_WATER, increment: -1, limit: -10 }); // markup pack water this.markup({ distanceField, neighbors, start: this.DEEP_WATER, increment: -1, limit: -10 }); // markup pack water
this.packedGraph.cells.t = distanceField; pack.cells.t = distanceField;
this.packedGraph.cells.f = featureIds; pack.cells.f = featureIds;
this.packedGraph.cells.haven = haven; pack.cells.haven = haven;
this.packedGraph.cells.harbor = harbor; pack.cells.harbor = harbor;
this.packedGraph.features = [0 as unknown as PackedGraphFeature, ...features]; pack.features = [0 as unknown as PackedGraphFeature, ...features];
TIME && console.timeEnd("markupPack"); TIME && console.timeEnd("markupPack");
} }
@ -293,14 +271,14 @@ class FeatureModule {
* define feature groups (ocean, sea, gulf, continent, island, isle, freshwater lake, salt lake, etc.) * define feature groups (ocean, sea, gulf, continent, island, isle, freshwater lake, salt lake, etc.)
*/ */
defineGroups() { defineGroups() {
const gridCellsNumber = this.grid.cells.i.length; const gridCellsNumber = grid.cells.i.length;
const OCEAN_MIN_SIZE = gridCellsNumber / 25; const OCEAN_MIN_SIZE = gridCellsNumber / 25;
const SEA_MIN_SIZE = gridCellsNumber / 1000; const SEA_MIN_SIZE = gridCellsNumber / 1000;
const CONTINENT_MIN_SIZE = gridCellsNumber / 10; const CONTINENT_MIN_SIZE = gridCellsNumber / 10;
const ISLAND_MIN_SIZE = gridCellsNumber / 1000; const ISLAND_MIN_SIZE = gridCellsNumber / 1000;
const defineIslandGroup = (feature: PackedGraphFeature) => { const defineIslandGroup = (feature: PackedGraphFeature) => {
const prevFeature = this.packedGraph.features[this.packedGraph.cells.f[feature.firstCell - 1]]; const prevFeature = pack.features[pack.cells.f[feature.firstCell - 1]];
if (prevFeature && prevFeature.type === "lake") return "lake_island"; if (prevFeature && prevFeature.type === "lake") return "lake_island";
if (feature.cells > CONTINENT_MIN_SIZE) return "continent"; if (feature.cells > CONTINENT_MIN_SIZE) return "continent";
if (feature.cells > ISLAND_MIN_SIZE) return "island"; if (feature.cells > ISLAND_MIN_SIZE) return "island";
@ -334,7 +312,7 @@ class FeatureModule {
throw new Error(`Markup: unknown feature type ${feature.type}`); throw new Error(`Markup: unknown feature type ${feature.type}`);
} }
for (const feature of this.packedGraph.features) { for (const feature of pack.features) {
if (!feature || feature.type === "ocean") continue; if (!feature || feature.type === "ocean") continue;
if (feature.type === "lake") feature.height = Lakes.getHeight(feature); if (feature.type === "lake") feature.height = Lakes.getHeight(feature);

View file

@ -3,12 +3,7 @@ import { range as d3Range, leastIndex, mean } from "d3";
import { createTypedArray, byId, findGridCell, getNumberInRange, lim, minmax, P, rand } from "../utils"; import { createTypedArray, byId, findGridCell, getNumberInRange, lim, minmax, P, rand } from "../utils";
declare global { declare global {
interface Window { var HeightmapGenerator: HeightmapGenerator;
HeightmapGenerator: HeightmapGenerator;
}
var heightmapTemplates: any;
var TIME: boolean;
var ERROR: boolean;
} }
type Tool = "Hill" | "Pit" | "Range" | "Trough" | "Strait" | "Mask" | "Invert" | "Add" | "Multiply" | "Smooth"; type Tool = "Hill" | "Pit" | "Range" | "Trough" | "Strait" | "Mask" | "Invert" | "Add" | "Multiply" | "Smooth";
@ -19,21 +14,6 @@ class HeightmapGenerator {
blobPower: number = 0; blobPower: number = 0;
linePower: number = 0; linePower: number = 0;
// TODO: remove after migration to TS and use param in constructor
get seed() {
return (window as any).seed;
}
get graphWidth() {
return (window as any).graphWidth;
}
get graphHeight() {
return (window as any).graphHeight;
}
constructor() {
}
private clearData() { private clearData() {
this.heights = null; this.heights = null;
this.grid = null; this.grid = null;
@ -107,8 +87,8 @@ class HeightmapGenerator {
let h = lim(getNumberInRange(height)); let h = lim(getNumberInRange(height));
do { do {
const x = this.getPointInRange(rangeX, this.graphWidth); const x = this.getPointInRange(rangeX, graphWidth);
const y = this.getPointInRange(rangeY, this.graphHeight); const y = this.getPointInRange(rangeY, graphHeight);
if (x === undefined || y === undefined) return; if (x === undefined || y === undefined) return;
start = findGridCell(x, y, this.grid); start = findGridCell(x, y, this.grid);
limit++; limit++;
@ -143,8 +123,8 @@ class HeightmapGenerator {
let h = lim(getNumberInRange(height)); let h = lim(getNumberInRange(height));
do { do {
const x = this.getPointInRange(rangeX, this.graphWidth); const x = this.getPointInRange(rangeX, graphWidth);
const y = this.getPointInRange(rangeY, this.graphHeight); const y = this.getPointInRange(rangeY, graphHeight);
if (x === undefined || y === undefined) return; if (x === undefined || y === undefined) return;
start = findGridCell(x, y, this.grid); start = findGridCell(x, y, this.grid);
limit++; limit++;
@ -207,8 +187,8 @@ class HeightmapGenerator {
if (rangeX && rangeY) { if (rangeX && rangeY) {
// find start and end points // find start and end points
const startX = this.getPointInRange(rangeX, this.graphWidth) as number; const startX = this.getPointInRange(rangeX, graphWidth) as number;
const startY = this.getPointInRange(rangeY, this.graphHeight) as number; const startY = this.getPointInRange(rangeY, graphHeight) as number;
let dist = 0; let dist = 0;
let limit = 0; let limit = 0;
@ -216,11 +196,11 @@ class HeightmapGenerator {
let endX; let endX;
do { do {
endX = Math.random() * this.graphWidth * 0.8 + this.graphWidth * 0.1; endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
endY = Math.random() * this.graphHeight * 0.7 + this.graphHeight * 0.15; endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
dist = Math.abs(endY - startY) + Math.abs(endX - startX); dist = Math.abs(endY - startY) + Math.abs(endX - startX);
limit++; limit++;
} while ((dist < this.graphWidth / 8 || dist > this.graphWidth / 3) && limit < 50); } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50);
startCellId = findGridCell(startX, startY, this.grid); startCellId = findGridCell(startX, startY, this.grid);
endCellId = findGridCell(endX, endY, this.grid); endCellId = findGridCell(endX, endY, this.grid);
@ -311,19 +291,19 @@ class HeightmapGenerator {
let endX: number; let endX: number;
let endY: number; let endY: number;
do { do {
startX = this.getPointInRange(rangeX, this.graphWidth) as number; startX = this.getPointInRange(rangeX, graphWidth) as number;
startY = this.getPointInRange(rangeY, this.graphHeight) as number; startY = this.getPointInRange(rangeY, graphHeight) as number;
startCellId = findGridCell(startX, startY, this.grid); startCellId = findGridCell(startX, startY, this.grid);
limit++; limit++;
} while (this.heights[startCellId] < 20 && limit < 50); } while (this.heights[startCellId] < 20 && limit < 50);
limit = 0; limit = 0;
do { do {
endX = Math.random() * this.graphWidth * 0.8 + this.graphWidth * 0.1; endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
endY = Math.random() * this.graphHeight * 0.7 + this.graphHeight * 0.15; endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
dist = Math.abs(endY - startY) + Math.abs(endX - startX); dist = Math.abs(endY - startY) + Math.abs(endX - startX);
limit++; limit++;
} while ((dist < this.graphWidth / 8 || dist > this.graphWidth / 2) && limit < 50); } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50);
endCellId = findGridCell(endX, endY, this.grid); endCellId = findGridCell(endX, endY, this.grid);
} }
@ -378,14 +358,14 @@ class HeightmapGenerator {
if (desiredWidth < 1 && P(desiredWidth)) return; if (desiredWidth < 1 && P(desiredWidth)) return;
const used = new Uint8Array(this.heights.length); const used = new Uint8Array(this.heights.length);
const vert = direction === "vertical"; const vert = direction === "vertical";
const startX = vert ? Math.floor(Math.random() * this.graphWidth * 0.4 + this.graphWidth * 0.3) : 5; const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5;
const startY = vert ? 5 : Math.floor(Math.random() * this.graphHeight * 0.4 + this.graphHeight * 0.3); const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3);
const endX = vert const endX = vert
? Math.floor(this.graphWidth - startX - this.graphWidth * 0.1 + Math.random() * this.graphWidth * 0.2) ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2)
: this.graphWidth - 5; : graphWidth - 5;
const endY = vert const endY = vert
? this.graphHeight - 5 ? graphHeight - 5
: Math.floor(this.graphHeight - startY - this.graphHeight * 0.1 + Math.random() * this.graphHeight * 0.2); : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
const start = findGridCell(startX, startY, this.grid); const start = findGridCell(startX, startY, this.grid);
const end = findGridCell(endX, endY, this.grid); const end = findGridCell(endX, endY, this.grid);
@ -462,8 +442,8 @@ class HeightmapGenerator {
this.heights = this.heights.map((h, i) => { this.heights = this.heights.map((h, i) => {
const [x, y] = this.grid.points[i]; const [x, y] = this.grid.points[i];
const nx = (2 * x) / this.graphWidth - 1; // [-1, 1], 0 is center const nx = (2 * x) / graphWidth - 1; // [-1, 1], 0 is center
const ny = (2 * y) / this.graphHeight - 1; // [-1, 1], 0 is center const ny = (2 * y) / graphHeight - 1; // [-1, 1], 0 is center
let distance = (1 - nx ** 2) * (1 - ny ** 2); // 1 is center, 0 is edge let distance = (1 - nx ** 2) * (1 - ny ** 2); // 1 is center, 0 is edge
if (power < 0) distance = 1 - distance; // inverted, 0 is center, 1 is edge if (power < 0) distance = 1 - distance; // inverted, 0 is center, 1 is edge
const masked = h * distance; const masked = h * distance;
@ -509,7 +489,7 @@ class HeightmapGenerator {
TIME && console.time("defineHeightmap"); TIME && console.time("defineHeightmap");
const id = (byId("templateInput")! as HTMLInputElement).value; const id = (byId("templateInput")! as HTMLInputElement).value;
Math.random = Alea(this.seed); Math.random = Alea(seed);
const isTemplate = id in heightmapTemplates; const isTemplate = id in heightmapTemplates;
const heights = isTemplate ? this.fromTemplate(graph, id) : await this.fromPrecreated(graph, id); const heights = isTemplate ? this.fromTemplate(graph, id) : await this.fromPrecreated(graph, id);

View file

@ -2,14 +2,9 @@ import { PackedGraphFeature } from "./features";
import { min, mean } from "d3"; import { min, mean } from "d3";
import { byId, import { byId,
rn } from "../utils"; rn } from "../utils";
import { PackedGraph } from "./PackedGraph";
declare global { declare global {
var Lakes: LakesModule; var Lakes: LakesModule;
var pack: PackedGraph;
var Names: any;
var heightExponentInput: HTMLInputElement;
} }
export class LakesModule { export class LakesModule {

View file

@ -3,14 +3,7 @@ import type { Selection } from 'd3';
import { clipPoly,P,rn,round } from '../utils'; import { clipPoly,P,rn,round } from '../utils';
declare global { declare global {
interface Window { var OceanLayers: typeof OceanModule.prototype.draw;
OceanLayers: any;
}
var TIME: boolean;
var ERROR: boolean;
var grid: any;
var oceanLayers: Selection<SVGGElement, unknown, null, undefined>;
} }
class OceanModule { class OceanModule {
private cells: any; private cells: any;
@ -25,10 +18,6 @@ class OceanModule {
this.oceanLayers = oceanLayers; this.oceanLayers = oceanLayers;
} }
get grid(): any {
return grid;
}
randomizeOutline() { randomizeOutline() {
const limits = []; const limits = [];
let odd = 0.2; let odd = 0.2;

View file

@ -1,32 +1,13 @@
import Alea from "alea"; import Alea from "alea";
import { curveBasis, import { curveBasis,
line, line,
mean, min, sum, curveCatmullRom, Selection } from "d3"; mean, min, sum, curveCatmullRom } from "d3";
import { each, import { each,
rn,round, rn,round,
rw} from "../utils"; rw} from "../utils";
import { PackedGraphFeature } from "./features";
import { PackedGraph } from "./PackedGraph";
import { LakesModule } from "./lakes";
declare global { declare global {
interface Window { var Rivers: RiverModule;
Rivers: any;
}
var WARN: boolean;
var graphHeight: number;
var graphWidth: number;
var pack: PackedGraph;
var rivers: Selection<SVGElement, unknown, null, undefined>;
var pointsInput: HTMLInputElement;
var grid: any;
var seed: string;
var TIME: boolean;
var Names: any;
var Lakes: LakesModule;
} }
export interface River { export interface River {
@ -66,22 +47,10 @@ class RiverModule {
smallLength: number | null = null; smallLength: number | null = null;
get graphHeight() {
return graphHeight;
}
get graphWidth() {
return graphWidth;
}
get pack(): PackedGraph {
return pack;
}
generate(allowErosion = true) { generate(allowErosion = true) {
TIME && console.time("generateRivers"); TIME && console.time("generateRivers");
Math.random = Alea(seed); Math.random = Alea(seed);
const {cells, features} = this.pack; const {cells, features} = pack;
const riversData: {[riverId: number]: number[]} = {}; const riversData: {[riverId: number]: number[]} = {};
const riverParents: {[key: number]: number} = {}; const riverParents: {[key: number]: number} = {};
@ -220,7 +189,7 @@ class RiverModule {
// re-initialize rivers and confluence arrays // re-initialize rivers and confluence arrays
cells.r = new Uint16Array(cells.i.length); cells.r = new Uint16Array(cells.i.length);
cells.conf = new Uint16Array(cells.i.length); cells.conf = new Uint16Array(cells.i.length);
this.pack.rivers = []; pack.rivers = [];
const defaultWidthFactor = rn(1 / (parseInt(pointsInput.dataset.cells || "10000") / 10000) ** 0.25, 2); const defaultWidthFactor = rn(1 / (parseInt(pointsInput.dataset.cells || "10000") / 10000) ** 0.25, 2);
const mainStemWidthFactor = defaultWidthFactor * 1.2; const mainStemWidthFactor = defaultWidthFactor * 1.2;
@ -256,7 +225,7 @@ class RiverModule {
}) })
); );
this.pack.rivers.push({ pack.rivers.push({
i: riverId, i: riverId,
source, source,
mouth, mouth,
@ -274,7 +243,7 @@ class RiverModule {
const downcutRivers = () => { const downcutRivers = () => {
const MAX_DOWNCUT = 5; const MAX_DOWNCUT = 5;
for (const i of this.pack.cells.i) { for (const i of pack.cells.i) {
if (cells.h[i] < 35) continue; // don't donwcut lowlands if (cells.h[i] < 35) continue; // don't donwcut lowlands
if (!cells.fl[i]) continue; if (!cells.fl[i]) continue;
@ -322,7 +291,7 @@ class RiverModule {
}; };
alterHeights(): Uint8Array { alterHeights(): Uint8Array {
const {h, c, t} = this.pack.cells as {h: Uint8Array, c: number[][], t: Uint8Array}; const {h, c, t} = pack.cells as {h: Uint8Array, c: number[][], t: Uint8Array};
return Uint8Array.from(Array.from(h).map((h, i) => { return Uint8Array.from(Array.from(h).map((h, i) => {
if (h < 20 || t[i] < 1) return h; if (h < 20 || t[i] < 1) return h;
return h + t[i] / 100 + (mean(c[i].map(c => t[c])) || 0) / 10000; return h + t[i] / 100 + (mean(c[i].map(c => t[c])) || 0) / 10000;
@ -331,14 +300,14 @@ class RiverModule {
// depression filling algorithm (for a correct water flux modeling) // depression filling algorithm (for a correct water flux modeling)
resolveDepressions(h: Uint8Array) { resolveDepressions(h: Uint8Array) {
const {cells, features} = this.pack; const {cells, features} = pack;
const maxIterations = +(document.getElementById("resolveDepressionsStepsOutput") as HTMLInputElement)?.value; const maxIterations = +(document.getElementById("resolveDepressionsStepsOutput") as HTMLInputElement)?.value;
const checkLakeMaxIteration = maxIterations * 0.85; const checkLakeMaxIteration = maxIterations * 0.85;
const elevateLakeMaxIteration = maxIterations * 0.75; const elevateLakeMaxIteration = maxIterations * 0.75;
const height = (i: number) => features[cells.f[i]].height || h[i]; // height of lake or specific cell const height = (i: number) => features[cells.f[i]].height || h[i]; // height of lake or specific cell
const lakes = features.filter((feature: PackedGraphFeature) => feature.type === "lake"); const lakes = features.filter((feature) => feature.type === "lake");
const land = cells.i.filter((i: number) => h[i] >= 20 && !cells.b[i]); // exclude near-border cells const land = cells.i.filter((i: number) => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
land.sort((a: number, b: number) => h[a] - h[b]); // lowest cells go first land.sort((a: number, b: number) => h[a] - h[b]); // lowest cells go first
@ -389,7 +358,7 @@ class RiverModule {
}; };
addMeandering(riverCells: number[], riverPoints = null, meandering = 0.5): [number, number, number][] { addMeandering(riverCells: number[], riverPoints = null, meandering = 0.5): [number, number, number][] {
const {fl, h} = this.pack.cells; const {fl, h} = pack.cells;
const meandered = []; const meandered = [];
const lastStep = riverCells.length - 1; const lastStep = riverCells.length - 1;
const points = this.getRiverPoints(riverCells, riverPoints); const points = this.getRiverPoints(riverCells, riverPoints);
@ -441,7 +410,7 @@ class RiverModule {
getRiverPoints(riverCells: number[], riverPoints: [number, number][] | null) { getRiverPoints(riverCells: number[], riverPoints: [number, number][] | null) {
if (riverPoints) return riverPoints; if (riverPoints) return riverPoints;
const {p} = this.pack.cells; const {p} = pack.cells;
return riverCells.map((cell, i) => { return riverCells.map((cell, i) => {
if (cell === -1) return this.getBorderPoint(riverCells[i - 1]); if (cell === -1) return this.getBorderPoint(riverCells[i - 1]);
return p[cell]; return p[cell];
@ -449,12 +418,12 @@ class RiverModule {
}; };
getBorderPoint(i: number) { getBorderPoint(i: number) {
const [x, y] = this.pack.cells.p[i]; const [x, y] = pack.cells.p[i];
const min = Math.min(y, this.graphHeight - y, x, this.graphWidth - x); const min = Math.min(y, graphHeight - y, x, graphWidth - x);
if (min === y) return [x, 0]; if (min === y) return [x, 0];
else if (min === this.graphHeight - y) return [x, this.graphHeight]; else if (min === graphHeight - y) return [x, graphHeight];
else if (min === x) return [0, y]; else if (min === x) return [0, y];
return [this.graphWidth, y]; return [graphWidth, y];
}; };
getOffset({flux, pointIndex, widthFactor, startingWidth}: {flux: number, pointIndex: number, widthFactor: number, startingWidth: number}) { getOffset({flux, pointIndex, widthFactor, startingWidth}: {flux: number, pointIndex: number, widthFactor: number, startingWidth: number}) {
@ -499,7 +468,7 @@ class RiverModule {
}; };
specify() { specify() {
const rivers = this.pack.rivers; const rivers = pack.rivers;
if (!rivers.length) return; if (!rivers.length) return;
for (const river of rivers) { for (const river of rivers) {
@ -510,13 +479,13 @@ class RiverModule {
}; };
getName(cell: number) { getName(cell: number) {
return Names.getCulture(this.pack.cells.culture[cell]); return Names.getCulture(pack.cells.culture[cell]);
}; };
getType({i, length, parent}: River) { getType({i, length, parent}: River) {
if (this.smallLength === null) { if (this.smallLength === null) {
const threshold = Math.ceil(this.pack.rivers.length * 0.15); const threshold = Math.ceil(pack.rivers.length * 0.15);
this.smallLength = this.pack.rivers.map(r => r.length || 0).sort((a: number, b: number) => a - b)[threshold]; this.smallLength = pack.rivers.map(r => r.length || 0).sort((a: number, b: number) => a - b)[threshold];
} }
const isSmall: boolean = length < (this.smallLength as number); const isSmall: boolean = length < (this.smallLength as number);
@ -537,8 +506,8 @@ class RiverModule {
// remove river and all its tributaries // remove river and all its tributaries
remove(id: number) { remove(id: number) {
const cells = this.pack.cells; const cells = pack.cells;
const riversToRemove = this.pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i); const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i);
riversToRemove.forEach(r => rivers.select("#river" + r).remove()); riversToRemove.forEach(r => rivers.select("#river" + r).remove());
cells.r.forEach((r, i) => { cells.r.forEach((r, i) => {
if (!r || !riversToRemove.includes(r)) return; if (!r || !riversToRemove.includes(r)) return;
@ -546,11 +515,11 @@ class RiverModule {
cells.fl[i] = grid.cells.prec[cells.g[i]]; cells.fl[i] = grid.cells.prec[cells.g[i]];
cells.conf[i] = 0; cells.conf[i] = 0;
}); });
this.pack.rivers = this.pack.rivers.filter(r => !riversToRemove.includes(r.i)); pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
}; };
getBasin(r: number): number { getBasin(r: number): number {
const parent = this.pack.rivers.find(river => river.i === r)?.parent; const parent = pack.rivers.find(river => river.i === r)?.parent;
if (!parent || r === parent) return r; if (!parent || r === parent) return r;
return this.getBasin(parent); return this.getBasin(parent);
}; };

View file

@ -1,5 +1,5 @@
import { PackedGraphFeature } from "./features"; import { PackedGraphFeature } from "../modules/features";
import { River } from "./river-generator"; import { River } from "../modules/river-generator";
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int8Array | Int16Array | Float32Array | Float64Array; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int8Array | Int16Array | Float32Array | Float64Array;

33
src/types/global.ts Normal file
View file

@ -0,0 +1,33 @@
import type { Selection } from 'd3';
import { PackedGraph } from "./PackedGraph";
declare global {
var seed: string;
var pack: PackedGraph;
var grid: any;
var graphHeight: number;
var graphWidth: number;
var TIME: boolean;
var WARN: boolean;
var ERROR: boolean;
var heightmapTemplates: any;
var Names: any;
var pointsInput: HTMLInputElement;
var heightExponentInput: HTMLInputElement;
var rivers: Selection<SVGElement, unknown, null, undefined>;
var oceanLayers: Selection<SVGGElement, unknown, null, undefined>;
var biomesData: {
i: number[];
name: string[];
color: string[];
biomesMatrix: Uint8Array[];
habitability: number[];
iconsDensity: number[];
icons: string[][];
cost: number[];
};
}