mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-02-04 17:41:23 +01:00
refactor: clean up global variable declarations and improve type definitions
This commit is contained in:
parent
613e826133
commit
3ed3d0dbd8
8 changed files with 105 additions and 177 deletions
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
33
src/types/global.ts
Normal 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[];
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue