mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor: river generation start
This commit is contained in:
parent
4833a8ab35
commit
4e65616dbc
11 changed files with 285 additions and 265 deletions
|
|
@ -13,18 +13,19 @@ export function drawCoastline(vertices: IGraphVertices, features: TPackFeatures)
|
||||||
|
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
if (!feature) continue;
|
if (!feature) continue;
|
||||||
|
if (feature.type === "ocean") continue;
|
||||||
|
|
||||||
const points = clipPoly(feature.vertices.map(vertex => vertices.p[vertex]));
|
const points = clipPoly(feature.vertices.map(vertex => vertices.p[vertex]));
|
||||||
const simplifiedPoints = simplify(points, SIMPLIFICATION_TOLERANCE);
|
const simplifiedPoints = simplify(points, SIMPLIFICATION_TOLERANCE);
|
||||||
const path = round(lineGen(simplifiedPoints)!);
|
const path = round(lineGen(simplifiedPoints)!);
|
||||||
|
|
||||||
landMask
|
|
||||||
.append("path")
|
|
||||||
.attr("d", path)
|
|
||||||
.attr("fill", "black")
|
|
||||||
.attr("id", "land_" + feature.i);
|
|
||||||
|
|
||||||
if (feature.type === "lake") {
|
if (feature.type === "lake") {
|
||||||
|
landMask
|
||||||
|
.append("path")
|
||||||
|
.attr("d", path)
|
||||||
|
.attr("fill", "black")
|
||||||
|
.attr("id", "land_" + feature.i);
|
||||||
|
|
||||||
lakes
|
lakes
|
||||||
.select("#freshwater")
|
.select("#freshwater")
|
||||||
.append("path")
|
.append("path")
|
||||||
|
|
@ -32,6 +33,12 @@ export function drawCoastline(vertices: IGraphVertices, features: TPackFeatures)
|
||||||
.attr("id", "lake_" + feature.i)
|
.attr("id", "lake_" + feature.i)
|
||||||
.attr("data-f", feature.i);
|
.attr("data-f", feature.i);
|
||||||
} else {
|
} else {
|
||||||
|
landMask
|
||||||
|
.append("path")
|
||||||
|
.attr("d", path)
|
||||||
|
.attr("fill", "white")
|
||||||
|
.attr("id", "land_" + feature.i);
|
||||||
|
|
||||||
waterMask
|
waterMask
|
||||||
.append("path")
|
.append("path")
|
||||||
.attr("d", path)
|
.attr("d", path)
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ export function drawStates() {
|
||||||
// define inner-state lakes to omit on border render
|
// define inner-state lakes to omit on border render
|
||||||
const innerLakes = features.map(feature => {
|
const innerLakes = features.map(feature => {
|
||||||
if (feature.type !== "lake") return false;
|
if (feature.type !== "lake") return false;
|
||||||
if (!feature.shoreline) Lakes.getShoreline(feature);
|
|
||||||
|
|
||||||
const states = feature.shoreline.map(i => cells.state[i]);
|
const shoreline = feature.shoreline || [];
|
||||||
|
const states = shoreline.map(i => cells.state[i]);
|
||||||
return new Set(states).size > 1 ? false : true;
|
return new Set(states).size > 1 ? false : true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
// @ts-nocheckd
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
import {TIME} from "config/logging";
|
import {TIME} from "config/logging";
|
||||||
import {rn} from "utils/numberUtils";
|
import {rn} from "utils/numberUtils";
|
||||||
// @ts-expect-error js module
|
|
||||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||||
import {getInputNumber, getInputValue} from "utils/nodeUtils";
|
import {getInputNumber, getInputValue} from "utils/nodeUtils";
|
||||||
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
||||||
|
|
@ -39,69 +39,6 @@ window.Lakes = (function () {
|
||||||
return lakeOutCells;
|
return lakeOutCells;
|
||||||
};
|
};
|
||||||
|
|
||||||
// get array of land cells aroound lake
|
|
||||||
const getShoreline = function (lake: IPackFeatureLake, pack: IPack) {
|
|
||||||
const uniqueCells = new Set();
|
|
||||||
lake.vertices.forEach(v =>
|
|
||||||
pack.vertices.c[v].forEach(c => pack.cells.h[c] >= MIN_LAND_HEIGHT && uniqueCells.add(c))
|
|
||||||
);
|
|
||||||
lake.shoreline = [...uniqueCells];
|
|
||||||
};
|
|
||||||
|
|
||||||
const prepareLakeData = (h: Uint8Array, pack: IPack) => {
|
|
||||||
const cells = pack.cells;
|
|
||||||
const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput");
|
|
||||||
|
|
||||||
pack.features.forEach(feature => {
|
|
||||||
if (!feature || feature.type !== "lake") return;
|
|
||||||
delete feature.flux;
|
|
||||||
delete feature.inlets;
|
|
||||||
delete feature.outlet;
|
|
||||||
delete feature.height;
|
|
||||||
delete feature.closed;
|
|
||||||
!feature.shoreline && getShoreline(feature, pack);
|
|
||||||
|
|
||||||
// lake surface height is as lowest land cells around
|
|
||||||
const min = feature.shoreline.sort((a, b) => h[a] - h[b])[0];
|
|
||||||
feature.height = h[min] - 0.1;
|
|
||||||
|
|
||||||
// check if lake can be open (not in deep depression)
|
|
||||||
if (ELEVATION_LIMIT === 80) {
|
|
||||||
feature.closed = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let deep = true;
|
|
||||||
const threshold = feature.height + ELEVATION_LIMIT;
|
|
||||||
const queue = [min];
|
|
||||||
const checked = [];
|
|
||||||
checked[min] = true;
|
|
||||||
|
|
||||||
// check if elevated lake can potentially pour to another water body
|
|
||||||
while (deep && queue.length) {
|
|
||||||
const q = queue.pop();
|
|
||||||
|
|
||||||
for (const n of cells.c[q]) {
|
|
||||||
if (checked[n]) continue;
|
|
||||||
if (h[n] >= threshold) continue;
|
|
||||||
|
|
||||||
if (h[n] < 20) {
|
|
||||||
const nFeature = pack.features[cells.f[n]];
|
|
||||||
if ((nFeature && nFeature.type === "ocean") || feature.height > nFeature.height) {
|
|
||||||
deep = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checked[n] = true;
|
|
||||||
queue.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.closed = deep;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanupLakeData = function (pack: IPack) {
|
const cleanupLakeData = function (pack: IPack) {
|
||||||
for (const feature of pack.features) {
|
for (const feature of pack.features) {
|
||||||
if (feature.type !== "lake") continue;
|
if (feature.type !== "lake") continue;
|
||||||
|
|
@ -277,11 +214,9 @@ window.Lakes = (function () {
|
||||||
return {
|
return {
|
||||||
setClimateData,
|
setClimateData,
|
||||||
cleanupLakeData,
|
cleanupLakeData,
|
||||||
prepareLakeData,
|
|
||||||
defineGroup,
|
defineGroup,
|
||||||
generateName,
|
generateName,
|
||||||
getName,
|
getName,
|
||||||
getShoreline,
|
|
||||||
addLakesInDeepDepressions,
|
addLakesInDeepDepressions,
|
||||||
openNearSeaLakes
|
openNearSeaLakes
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import {MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
|
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
||||||
import {TIME} from "config/logging";
|
import {TIME} from "config/logging";
|
||||||
import {INT8_MAX} from "constants";
|
import {INT8_MAX} from "constants";
|
||||||
// @ts-expect-error js module
|
|
||||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||||
import {createTypedArray} from "utils/arrayUtils";
|
|
||||||
import {dist2} from "utils/functionUtils";
|
|
||||||
import {getFeatureVertices} from "scripts/connectVertices";
|
import {getFeatureVertices} from "scripts/connectVertices";
|
||||||
|
import {createTypedArray, unique} from "utils/arrayUtils";
|
||||||
|
import {dist2} from "utils/functionUtils";
|
||||||
|
import {clipPoly} from "utils/lineUtils";
|
||||||
|
import {rn} from "utils/numberUtils";
|
||||||
|
|
||||||
const {UNMARKED, LAND_COAST, WATER_COAST, LANDLOCKED, DEEPER_WATER} = DISTANCE_FIELD;
|
const {UNMARKED, LAND_COAST, WATER_COAST, LANDLOCKED, DEEPER_WATER} = DISTANCE_FIELD;
|
||||||
|
|
||||||
|
|
@ -132,23 +135,22 @@ export function markupPackFeatures(
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureVertices = getFeatureVertices({firstCell, vertices, cells, featureIds, featureId});
|
const featureVertices = getFeatureVertices({firstCell, vertices, cells, featureIds, featureId});
|
||||||
|
const points = clipPoly(featureVertices.map(vertex => vertices.p[vertex]));
|
||||||
// let points = clipPoly(vchain.map(v => vertices.p[v]));
|
const area = d3.polygonArea(points); // feature perimiter area
|
||||||
// const area = d3.polygonArea(points); // area with lakes/islands
|
|
||||||
// if (area > 0 && features[f].type === "lake") {
|
|
||||||
// points = points.reverse();
|
|
||||||
// vchain = vchain.reverse();
|
|
||||||
// }
|
|
||||||
|
|
||||||
const feature = addFeature({
|
const feature = addFeature({
|
||||||
|
vertices,
|
||||||
|
heights: cells.h,
|
||||||
features,
|
features,
|
||||||
|
featureIds,
|
||||||
firstCell,
|
firstCell,
|
||||||
land,
|
land,
|
||||||
border,
|
border,
|
||||||
featureVertices,
|
featureVertices,
|
||||||
featureId,
|
featureId,
|
||||||
cellNumber,
|
cellNumber,
|
||||||
gridCellsNumber
|
gridCellsNumber,
|
||||||
|
area
|
||||||
});
|
});
|
||||||
features.push(feature);
|
features.push(feature);
|
||||||
|
|
||||||
|
|
@ -164,16 +166,23 @@ export function markupPackFeatures(
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFeature({
|
function addFeature({
|
||||||
|
vertices,
|
||||||
|
heights,
|
||||||
features,
|
features,
|
||||||
|
featureIds,
|
||||||
firstCell,
|
firstCell,
|
||||||
land,
|
land,
|
||||||
border,
|
border,
|
||||||
featureVertices,
|
featureVertices,
|
||||||
featureId,
|
featureId,
|
||||||
cellNumber,
|
cellNumber,
|
||||||
gridCellsNumber
|
gridCellsNumber,
|
||||||
|
area
|
||||||
}: {
|
}: {
|
||||||
|
vertices: IGraphVertices;
|
||||||
|
heights: Uint8Array;
|
||||||
features: TPackFeatures;
|
features: TPackFeatures;
|
||||||
|
featureIds: Uint16Array;
|
||||||
firstCell: number;
|
firstCell: number;
|
||||||
land: boolean;
|
land: boolean;
|
||||||
border: boolean;
|
border: boolean;
|
||||||
|
|
@ -181,12 +190,15 @@ function addFeature({
|
||||||
featureId: number;
|
featureId: number;
|
||||||
cellNumber: number;
|
cellNumber: number;
|
||||||
gridCellsNumber: number;
|
gridCellsNumber: number;
|
||||||
|
area: number;
|
||||||
}) {
|
}) {
|
||||||
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 absArea = Math.abs(rn(area));
|
||||||
|
|
||||||
if (land) return addIsland();
|
if (land) return addIsland();
|
||||||
if (border) return addOcean();
|
if (border) return addOcean();
|
||||||
return addLake();
|
return addLake();
|
||||||
|
|
@ -201,7 +213,8 @@ function addFeature({
|
||||||
border,
|
border,
|
||||||
cells: cellNumber,
|
cells: cellNumber,
|
||||||
firstCell,
|
firstCell,
|
||||||
vertices: featureVertices
|
vertices: featureVertices,
|
||||||
|
area: absArea
|
||||||
};
|
};
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +229,8 @@ function addFeature({
|
||||||
border: false,
|
border: false,
|
||||||
cells: cellNumber,
|
cells: cellNumber,
|
||||||
firstCell,
|
firstCell,
|
||||||
vertices: featureVertices
|
vertices: featureVertices,
|
||||||
|
area: absArea
|
||||||
};
|
};
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +238,25 @@ function addFeature({
|
||||||
function addLake() {
|
function addLake() {
|
||||||
const group = "freshwater"; // temp, to be defined later
|
const group = "freshwater"; // temp, to be defined later
|
||||||
const name = ""; // temp, to be defined later
|
const name = ""; // temp, to be defined later
|
||||||
|
|
||||||
|
// ensure lake ring is clockwise (to form a hole)
|
||||||
|
const lakeVertices = area > 0 ? featureVertices.reverse() : featureVertices;
|
||||||
|
|
||||||
|
const shoreline = getShoreline(); // land cells around lake
|
||||||
|
const height = getLakeElevation();
|
||||||
|
|
||||||
|
function getShoreline() {
|
||||||
|
const isLand = (cellId: number) => heights[cellId] >= MIN_LAND_HEIGHT;
|
||||||
|
const cellsAround = lakeVertices.map(vertex => vertices.c[vertex].filter(isLand)).flat();
|
||||||
|
return unique(cellsAround);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLakeElevation() {
|
||||||
|
const MIN_ELEVATION_DELTA = 0.1;
|
||||||
|
const minShoreHeight = d3.min(shoreline.map(cellId => heights[cellId])) || MIN_LAND_HEIGHT;
|
||||||
|
return minShoreHeight - MIN_ELEVATION_DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
const feature: IPackFeatureLake = {
|
const feature: IPackFeatureLake = {
|
||||||
i: featureId,
|
i: featureId,
|
||||||
type: "lake",
|
type: "lake",
|
||||||
|
|
@ -233,7 +266,10 @@ function addFeature({
|
||||||
border: false,
|
border: false,
|
||||||
cells: cellNumber,
|
cells: cellNumber,
|
||||||
firstCell,
|
firstCell,
|
||||||
vertices: featureVertices
|
vertices: lakeVertices,
|
||||||
|
shoreline: shoreline,
|
||||||
|
height,
|
||||||
|
area: absArea
|
||||||
};
|
};
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
@ -245,7 +281,7 @@ function addFeature({
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineIslandGroup() {
|
function defineIslandGroup() {
|
||||||
const prevFeature = features.at(-1);
|
const prevFeature = features[featureIds[firstCell - 1]];
|
||||||
|
|
||||||
if (prevFeature && prevFeature.type === "lake") return "lake_island";
|
if (prevFeature && prevFeature.type === "lake") return "lake_island";
|
||||||
if (cellNumber > CONTINENT_MIN_SIZE) return "continent";
|
if (cellNumber > CONTINENT_MIN_SIZE) return "continent";
|
||||||
|
|
|
||||||
|
|
@ -6,28 +6,36 @@ import {rn} from "utils/numberUtils";
|
||||||
import {round} from "utils/stringUtils";
|
import {round} from "utils/stringUtils";
|
||||||
import {rw, each} from "utils/probabilityUtils";
|
import {rw, each} from "utils/probabilityUtils";
|
||||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||||
|
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
||||||
|
import {getInputNumber} from "utils/nodeUtils";
|
||||||
|
|
||||||
|
const {Lakes} = window;
|
||||||
|
const {LAND_COAST} = DISTANCE_FIELD;
|
||||||
|
|
||||||
|
interface IRiverPackData {
|
||||||
|
cells: Pick<IPack["cells"], "i" | "h" | "c" | "t">;
|
||||||
|
features: TPackFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
window.Rivers = (function () {
|
window.Rivers = (function () {
|
||||||
const generate = function (pack, grid, allowErosion = true) {
|
const generate = function (grid: IGrid, {cells, features}: IRiverPackData, allowErosion = true) {
|
||||||
TIME && console.time("generateRivers");
|
TIME && console.time("generateRivers");
|
||||||
|
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
const {cells, features} = pack;
|
|
||||||
|
|
||||||
const riversData = {}; // rivers data
|
const riversData = {}; // rivers data
|
||||||
const riverParents = {};
|
const riverParents = {};
|
||||||
const addCellToRiver = function (cell, river) {
|
|
||||||
if (!riversData[river]) riversData[river] = [cell];
|
|
||||||
else riversData[river].push(cell);
|
|
||||||
};
|
|
||||||
|
|
||||||
cells.fl = new Uint16Array(cells.i.length); // water flux array
|
const cellsNumber = cells.i.length;
|
||||||
cells.r = new Uint16Array(cells.i.length); // rivers array
|
const flux = new Uint16Array(cellsNumber);
|
||||||
cells.conf = new Uint8Array(cells.i.length); // confluences array
|
const riverIds = new Uint16Array(cellsNumber);
|
||||||
let riverNext = 1; // first river id is 1
|
const confluence = new Uint8Array(cellsNumber);
|
||||||
|
|
||||||
const h = alterHeights(pack.cells);
|
let nextRiverId = 1; // starts with 1
|
||||||
Lakes.prepareLakeData(h, pack);
|
|
||||||
resolveDepressions(pack, h);
|
const alteredHeights = alterHeights({h: cells.h, c: cells.c, t: cells.t});
|
||||||
|
|
||||||
|
resolveDepressions(pack, alteredHeights);
|
||||||
drainWater();
|
drainWater();
|
||||||
defineRivers();
|
defineRivers();
|
||||||
|
|
||||||
|
|
@ -35,7 +43,7 @@ window.Rivers = (function () {
|
||||||
Lakes.cleanupLakeData(pack);
|
Lakes.cleanupLakeData(pack);
|
||||||
|
|
||||||
if (allowErosion) {
|
if (allowErosion) {
|
||||||
cells.h = Uint8Array.from(h); // apply gradient
|
cells.h = Uint8Array.from(alteredHeights); // apply gradient
|
||||||
downcutRivers(); // downcut river beds
|
downcutRivers(); // downcut river beds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,36 +54,36 @@ window.Rivers = (function () {
|
||||||
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
|
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
|
||||||
|
|
||||||
const prec = grid.cells.prec;
|
const prec = grid.cells.prec;
|
||||||
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
const land = cells.i.filter(i => alteredHeights[i] >= 20).sort((a, b) => alteredHeights[b] - alteredHeights[a]);
|
||||||
const lakeOutCells = Lakes.setClimateData(h, pack, grid);
|
const lakeOutCells = Lakes.setClimateData(alteredHeights, pack, grid);
|
||||||
|
|
||||||
land.forEach(function (i) {
|
land.forEach(function (i) {
|
||||||
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
|
flux[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
|
||||||
|
|
||||||
// create lake outlet if lake is not in deep depression and flux > evaporation
|
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||||
const lakes = lakeOutCells[i]
|
const lakes = lakeOutCells[i]
|
||||||
? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation)
|
? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation)
|
||||||
: [];
|
: [];
|
||||||
for (const lake of lakes) {
|
for (const lake of lakes) {
|
||||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
const lakeCell = cells.c[i].find(c => alteredHeights[c] < 20 && cells.f[c] === lake.i);
|
||||||
cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
|
flux[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
|
||||||
|
|
||||||
// allow chain lakes to retain identity
|
// allow chain lakes to retain identity
|
||||||
if (cells.r[lakeCell] !== lake.river) {
|
if (riverIds[lakeCell] !== lake.river) {
|
||||||
const sameRiver = cells.c[lakeCell].some(c => cells.r[c] === lake.river);
|
const sameRiver = cells.c[lakeCell].some(c => riverIds[c] === lake.river);
|
||||||
|
|
||||||
if (sameRiver) {
|
if (sameRiver) {
|
||||||
cells.r[lakeCell] = lake.river;
|
riverIds[lakeCell] = lake.river;
|
||||||
addCellToRiver(lakeCell, lake.river);
|
addCellToRiver(lakeCell, lake.river);
|
||||||
} else {
|
} else {
|
||||||
cells.r[lakeCell] = riverNext;
|
riverIds[lakeCell] = nextRiverId;
|
||||||
addCellToRiver(lakeCell, riverNext);
|
addCellToRiver(lakeCell, nextRiverId);
|
||||||
riverNext++;
|
nextRiverId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lake.outlet = cells.r[lakeCell];
|
lake.outlet = riverIds[lakeCell];
|
||||||
flowDown(i, cells.fl[lakeCell], lake.outlet);
|
flowDown(i, flux[lakeCell], lake.outlet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign all tributary rivers to outlet basin
|
// assign all tributary rivers to outlet basin
|
||||||
|
|
@ -88,21 +96,21 @@ window.Rivers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// near-border cell: pour water out of the screen
|
// near-border cell: pour water out of the screen
|
||||||
if (cells.b[i] && cells.r[i]) return addCellToRiver(-1, cells.r[i]);
|
if (cells.b[i] && riverIds[i]) return addCellToRiver(-1, riverIds[i]);
|
||||||
|
|
||||||
// downhill cell (make sure it's not in the source lake)
|
// downhill cell (make sure it's not in the source lake)
|
||||||
let min = null;
|
let min = null;
|
||||||
if (lakeOutCells[i]) {
|
if (lakeOutCells[i]) {
|
||||||
const filtered = cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c]));
|
const filtered = cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c]));
|
||||||
min = filtered.sort((a, b) => h[a] - h[b])[0];
|
min = filtered.sort((a, b) => alteredHeights[a] - alteredHeights[b])[0];
|
||||||
} else if (cells.haven[i]) {
|
} else if (cells.haven[i]) {
|
||||||
min = cells.haven[i];
|
min = cells.haven[i];
|
||||||
} else {
|
} else {
|
||||||
min = cells.c[i].sort((a, b) => h[a] - h[b])[0];
|
min = cells.c[i].sort((a, b) => alteredHeights[a] - alteredHeights[b])[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// cells is depressed
|
// cells is depressed
|
||||||
if (h[i] <= h[min]) return;
|
if (alteredHeights[i] <= alteredHeights[min]) return;
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
// .append("line")
|
// .append("line")
|
||||||
|
|
@ -113,40 +121,45 @@ window.Rivers = (function () {
|
||||||
// .attr("stroke", "#333")
|
// .attr("stroke", "#333")
|
||||||
// .attr("stroke-width", 0.2);
|
// .attr("stroke-width", 0.2);
|
||||||
|
|
||||||
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
|
if (flux[i] < MIN_FLUX_TO_FORM_RIVER) {
|
||||||
// flux is too small to operate as a river
|
// flux is too small to operate as a river
|
||||||
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
|
if (alteredHeights[min] >= 20) flux[min] += flux[i];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// proclaim a new river
|
// proclaim a new river
|
||||||
if (!cells.r[i]) {
|
if (!riverIds[i]) {
|
||||||
cells.r[i] = riverNext;
|
riverIds[i] = nextRiverId;
|
||||||
addCellToRiver(i, riverNext);
|
addCellToRiver(i, nextRiverId);
|
||||||
riverNext++;
|
nextRiverId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
flowDown(min, cells.fl[i], cells.r[i]);
|
flowDown(min, flux[i], riverIds[i]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCellToRiver(cellId: number, riverId: number) {
|
||||||
|
if (!riversData[riverId]) riversData[riverId] = [cellId];
|
||||||
|
else riversData[riverId].push(cellId);
|
||||||
|
}
|
||||||
|
|
||||||
function flowDown(toCell, fromFlux, river) {
|
function flowDown(toCell, fromFlux, river) {
|
||||||
const toFlux = cells.fl[toCell] - cells.conf[toCell];
|
const toFlux = flux[toCell] - confluence[toCell];
|
||||||
const toRiver = cells.r[toCell];
|
const toRiver = riverIds[toCell];
|
||||||
|
|
||||||
if (toRiver) {
|
if (toRiver) {
|
||||||
// downhill cell already has river assigned
|
// downhill cell already has river assigned
|
||||||
if (fromFlux > toFlux) {
|
if (fromFlux > toFlux) {
|
||||||
cells.conf[toCell] += cells.fl[toCell]; // mark confluence
|
confluence[toCell] += flux[toCell]; // mark confluence
|
||||||
if (h[toCell] >= 20) riverParents[toRiver] = river; // min river is a tributary of current river
|
if (alteredHeights[toCell] >= 20) riverParents[toRiver] = river; // min river is a tributary of current river
|
||||||
cells.r[toCell] = river; // re-assign river if downhill part has less flux
|
riverIds[toCell] = river; // re-assign river if downhill part has less flux
|
||||||
} else {
|
} else {
|
||||||
cells.conf[toCell] += fromFlux; // mark confluence
|
confluence[toCell] += fromFlux; // mark confluence
|
||||||
if (h[toCell] >= 20) riverParents[river] = toRiver; // current river is a tributary of min river
|
if (alteredHeights[toCell] >= 20) riverParents[river] = toRiver; // current river is a tributary of min river
|
||||||
}
|
}
|
||||||
} else cells.r[toCell] = river; // assign the river to the downhill cell
|
} else riverIds[toCell] = river; // assign the river to the downhill cell
|
||||||
|
|
||||||
if (h[toCell] < 20) {
|
if (alteredHeights[toCell] < 20) {
|
||||||
// pour water to the water body
|
// pour water to the water body
|
||||||
const waterBody = features[cells.f[toCell]];
|
const waterBody = features[cells.f[toCell]];
|
||||||
if (waterBody.type === "lake") {
|
if (waterBody.type === "lake") {
|
||||||
|
|
@ -160,7 +173,7 @@ window.Rivers = (function () {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// propagate flux and add next river segment
|
// propagate flux and add next river segment
|
||||||
cells.fl[toCell] += fromFlux;
|
flux[toCell] += fromFlux;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCellToRiver(toCell, river);
|
addCellToRiver(toCell, river);
|
||||||
|
|
@ -168,8 +181,8 @@ window.Rivers = (function () {
|
||||||
|
|
||||||
function defineRivers() {
|
function defineRivers() {
|
||||||
// re-initialize rivers and confluence arrays
|
// re-initialize rivers and confluence arrays
|
||||||
cells.r = new Uint16Array(cells.i.length);
|
riverIds = new Uint16Array(cellsNumber);
|
||||||
cells.conf = new Uint16Array(cells.i.length);
|
confluence = new Uint16Array(cellsNumber);
|
||||||
pack.rivers = [];
|
pack.rivers = [];
|
||||||
|
|
||||||
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
||||||
|
|
@ -184,8 +197,8 @@ window.Rivers = (function () {
|
||||||
if (cell < 0 || cells.h[cell] < 20) continue;
|
if (cell < 0 || cells.h[cell] < 20) continue;
|
||||||
|
|
||||||
// mark real confluences and assign river to cells
|
// mark real confluences and assign river to cells
|
||||||
if (cells.r[cell]) cells.conf[cell] = 1;
|
if (riverIds[cell]) confluence[cell] = 1;
|
||||||
else cells.r[cell] = riverId;
|
else riverIds[cell] = riverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = riverCells[0];
|
const source = riverCells[0];
|
||||||
|
|
@ -194,7 +207,7 @@ window.Rivers = (function () {
|
||||||
|
|
||||||
const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor;
|
const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor;
|
||||||
const meanderedPoints = addMeandering(pack, riverCells);
|
const meanderedPoints = addMeandering(pack, riverCells);
|
||||||
const discharge = cells.fl[mouth]; // m3 in second
|
const discharge = flux[mouth]; // m3 in second
|
||||||
const length = getApproximateLength(meanderedPoints);
|
const length = getApproximateLength(meanderedPoints);
|
||||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
||||||
|
|
||||||
|
|
@ -218,48 +231,94 @@ window.Rivers = (function () {
|
||||||
|
|
||||||
for (const i of 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 (!flux[i]) continue;
|
||||||
|
|
||||||
const higherCells = cells.c[i].filter(c => cells.h[c] > cells.h[i]);
|
const higherCells = cells.c[i].filter(c => cells.h[c] > cells.h[i]);
|
||||||
const higherFlux = higherCells.reduce((acc, c) => acc + cells.fl[c], 0) / higherCells.length;
|
const higherFlux = higherCells.reduce((acc, c) => acc + flux[c], 0) / higherCells.length;
|
||||||
if (!higherFlux) continue;
|
if (!higherFlux) continue;
|
||||||
|
|
||||||
const downcut = Math.floor(cells.fl[i] / higherFlux);
|
const downcut = Math.floor(flux[i] / higherFlux);
|
||||||
if (downcut) cells.h[i] -= Math.min(downcut, MAX_DOWNCUT);
|
if (downcut) cells.h[i] -= Math.min(downcut, MAX_DOWNCUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateConfluenceFlux() {
|
function calculateConfluenceFlux() {
|
||||||
for (const i of cells.i) {
|
for (const i of cells.i) {
|
||||||
if (!cells.conf[i]) continue;
|
if (!confluence[i]) continue;
|
||||||
|
|
||||||
const sortedInflux = cells.c[i]
|
const sortedInflux = cells.c[i]
|
||||||
.filter(c => cells.r[c] && h[c] > h[i])
|
.filter(c => riverIds[c] && alteredHeights[c] > alteredHeights[i])
|
||||||
.map(c => cells.fl[c])
|
.map(c => flux[c])
|
||||||
.sort((a, b) => b - a);
|
.sort((a, b) => b - a);
|
||||||
cells.conf[i] = sortedInflux.reduce((acc, flux, index) => (index ? acc + flux : acc), 0);
|
confluence[i] = sortedInflux.reduce((acc, flux, index) => (index ? acc + flux : acc), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// add distance to water value to land cells to make map less depressed
|
// add distance to water value to land cells to make map less depressed
|
||||||
const alterHeights = ({h, c, t}) => {
|
const alterHeights = ({h, c, t}: Pick<IPack["cells"], "h" | "c" | "t">) => {
|
||||||
return Array.from(h).map((h, i) => {
|
return Array.from(h).map((height, index) => {
|
||||||
if (h < 20 || t[i] < 1) return h;
|
if (height < MIN_LAND_HEIGHT || t[index] < LAND_COAST) return height;
|
||||||
return h + t[i] / 100 + d3.mean(c[i].map(c => t[c])) / 10000;
|
const mean = d3.mean(c[index].map(c => t[c])) || 0;
|
||||||
|
return height + t[index] / 100 + mean / 10000;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// depression filling algorithm (for a correct water flux modeling)
|
// depression filling algorithm (for a correct water flux modeling)
|
||||||
const resolveDepressions = function (pack, h) {
|
const resolveDepressions = function (pack, h) {
|
||||||
const {cells, features} = pack;
|
const {cells, features} = pack;
|
||||||
const maxIterations = +document.getElementById("resolveDepressionsStepsOutput").value;
|
const maxIterations = getInputNumber("resolveDepressionsStepsOutput");
|
||||||
const checkLakeMaxIteration = maxIterations * 0.85;
|
const checkLakeMaxIteration = maxIterations * 0.85;
|
||||||
const elevateLakeMaxIteration = maxIterations * 0.75;
|
const elevateLakeMaxIteration = maxIterations * 0.75;
|
||||||
|
|
||||||
const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell
|
const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell
|
||||||
|
|
||||||
const lakes = features.filter(f => f.type === "lake");
|
const lakes = features.filter(feature => feature.type === "lake");
|
||||||
|
const canBePoured = () => {
|
||||||
|
const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput");
|
||||||
|
|
||||||
|
const lakes = features.filter(feature => feature && feature.type === "lake") as IPackFeatureLake[];
|
||||||
|
const lakeData = lakes.map(feature => {
|
||||||
|
const minShoreHeight = d3.min(feature.shoreline.map(cellId => heights[cellId])) || MIN_LAND_HEIGHT;
|
||||||
|
const minHeightCell =
|
||||||
|
feature.shoreline.find(cellId => heights[cellId] === minShoreHeight) || feature.shoreline[0];
|
||||||
|
|
||||||
|
if (ELEVATION_LIMIT === 80) return {...feature, closed: false};
|
||||||
|
|
||||||
|
// check if lake can be open (not in deep depression)
|
||||||
|
let deep = true;
|
||||||
|
|
||||||
|
const threshold = feature.height + ELEVATION_LIMIT;
|
||||||
|
const queue = [minHeightCell];
|
||||||
|
const checked = [];
|
||||||
|
checked[minHeightCell] = true;
|
||||||
|
|
||||||
|
// check if elevated lake can potentially pour to another water body
|
||||||
|
while (deep && queue.length) {
|
||||||
|
const cellId = queue.pop()!;
|
||||||
|
|
||||||
|
for (const neibCellId of cells.c[cellId]) {
|
||||||
|
if (checked[neibCellId]) continue;
|
||||||
|
if (heights[neibCellId] >= threshold) continue;
|
||||||
|
|
||||||
|
if (heights[neibCellId] < MIN_LAND_HEIGHT) {
|
||||||
|
const waterFeatureMet = features[cells.f[neibCellId]];
|
||||||
|
|
||||||
|
if ((waterFeatureMet && waterFeatureMet.type === "ocean") || feature.height > waterFeatureMet.height) {
|
||||||
|
deep = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checked[neibCellId] = true;
|
||||||
|
queue.push(neibCellId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...feature, closed: deep};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
||||||
land.sort((a, b) => h[a] - h[b]); // lowest cells go first
|
land.sort((a, b) => h[a] - h[b]); // lowest cells go first
|
||||||
|
|
||||||
|
|
@ -476,10 +535,10 @@ window.Rivers = (function () {
|
||||||
|
|
||||||
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
||||||
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
||||||
const getWidth = offset => rn((offset / 1.5) ** 1.8, 2); // mouth width in km
|
const getWidth = (offset: number) => rn((offset / 1.5) ** 1.8, 2); // mouth width in km
|
||||||
|
|
||||||
// remove river and all its tributaries
|
// remove river and all its tributaries
|
||||||
const remove = function (id) {
|
const remove = function (id: number) {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
const riversToRemove = 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());
|
||||||
|
|
@ -492,9 +551,9 @@ window.Rivers = (function () {
|
||||||
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBasin = function (r) {
|
const getBasin = function (riverId: number) {
|
||||||
const parent = pack.rivers.find(river => river.i === r)?.parent;
|
const parent = pack.rivers.find(river => river.i === riverId)?.parent;
|
||||||
if (!parent || r === parent) return r;
|
if (!parent || riverId === parent) return riverId;
|
||||||
return getBasin(parent);
|
return getBasin(parent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1,20 +1,16 @@
|
||||||
/*////////////////////////////////////////////////////////////////
|
// @ts-nocheck
|
||||||
aleaPRNG 1.1
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
https://github.com/macmcmeans/aleaPRNG/blob/master/aleaPRNG-1.1.js
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
Original work copyright © 2010 Johannes Baagøe, under MIT license
|
|
||||||
This is a derivative work copyright (c) 2017-2020, W. Mac" McMeans, under BSD license.
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
////////////////////////////////////////////////////////////////*/
|
|
||||||
export function aleaPRNG() {
|
|
||||||
return (function (args) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
|
// aleaPRNG 1.1: https://github.com/macmcmeans/aleaPRNG/blob/master/aleaPRNG-1.1.js
|
||||||
|
// Original work copyright © 2010 Johannes Baagøe, under MIT license
|
||||||
|
// This is a derivative work copyright (c) 2017-2020, W. Mac" McMeans, under BSD license.
|
||||||
|
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
export function aleaPRNG(args) {
|
||||||
|
return (function (args) {
|
||||||
const version = "aleaPRNG 1.1.0";
|
const version = "aleaPRNG 1.1.0";
|
||||||
|
|
||||||
var s0,
|
var s0,
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import * as d3 from "d3";
|
|
||||||
|
|
||||||
import {ERROR} from "config/logging";
|
import {ERROR} from "config/logging";
|
||||||
import {clipPoly} from "utils/lineUtils";
|
|
||||||
|
|
||||||
export function getFeatureVertices({
|
export function getFeatureVertices({
|
||||||
firstCell,
|
firstCell,
|
||||||
|
|
@ -22,26 +19,6 @@ export function getFeatureVertices({
|
||||||
const startingVertex = findStartingVertex({startingCell, featureIds, featureId, vertices, cells, packCellsNumber});
|
const startingVertex = findStartingVertex({startingCell, featureIds, featureId, vertices, cells, packCellsNumber});
|
||||||
const featureVertices = connectVertices({vertices, startingVertex, featureIds, featureId});
|
const featureVertices = connectVertices({vertices, startingVertex, featureIds, featureId});
|
||||||
|
|
||||||
// temp: draw feature vertices
|
|
||||||
cells.v[firstCell]
|
|
||||||
.map(v => vertices.p[v])
|
|
||||||
.forEach(([x, y]) => {
|
|
||||||
d3.select("#debug").append("circle").attr("cx", x).attr("cy", y).attr("r", 0.2).attr("fill", "yellow");
|
|
||||||
});
|
|
||||||
|
|
||||||
const [cx, cy] = vertices.p[startingVertex];
|
|
||||||
d3.select("#debug").append("circle").attr("cx", cx).attr("cy", cy).attr("r", 1.5).attr("fill", "red");
|
|
||||||
|
|
||||||
const lineGen = d3.line();
|
|
||||||
const points = clipPoly(featureVertices.map(v => vertices.p[v]));
|
|
||||||
const path = lineGen(points)!;
|
|
||||||
d3.select("#debug")
|
|
||||||
.attr("fill", "none")
|
|
||||||
.attr("stroke", "black")
|
|
||||||
.attr("stroke-width", 0.1)
|
|
||||||
.append("path")
|
|
||||||
.attr("d", path);
|
|
||||||
|
|
||||||
return featureVertices;
|
return featureVertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +83,7 @@ function findStartingVertex({
|
||||||
throw new Error(`Markup: firstCell ${startingCell} of feature ${featureId} has no neighbors of other features`);
|
throw new Error(`Markup: firstCell ${startingCell} of feature ${featureId} has no neighbors of other features`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = neibCells.indexOf(d3.min(otherFeatureNeibs)!);
|
const index = neibCells.indexOf(Math.min(...otherFeatureNeibs)!);
|
||||||
return cellVertices[index];
|
return cellVertices[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import * as d3 from "d3";
|
||||||
import {ERROR, INFO, WARN} from "config/logging";
|
import {ERROR, INFO, WARN} from "config/logging";
|
||||||
import {closeDialogs} from "dialogs/utils";
|
import {closeDialogs} from "dialogs/utils";
|
||||||
import {openDialog} from "dialogs";
|
import {openDialog} from "dialogs";
|
||||||
import {initLayers, restoreLayers} from "layers";
|
import {initLayers, renderLayer, restoreLayers} from "layers";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
import {drawCoastline} from "layers/renderers/drawCoastline";
|
import {drawCoastline} from "layers/renderers/drawCoastline";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
|
|
@ -16,7 +16,6 @@ import {applyMapSize, randomizeOptions} from "modules/ui/options";
|
||||||
import {applyStyleOnLoad} from "modules/ui/stylePresets";
|
import {applyStyleOnLoad} from "modules/ui/stylePresets";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
import {addZones} from "modules/zones";
|
import {addZones} from "modules/zones";
|
||||||
// @ts-expect-error js module
|
|
||||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||||
import {hideLoading, showLoading} from "scripts/loading";
|
import {hideLoading, showLoading} from "scripts/loading";
|
||||||
import {clearMainTip, tip} from "scripts/tooltips";
|
import {clearMainTip, tip} from "scripts/tooltips";
|
||||||
|
|
@ -63,6 +62,11 @@ async function generate(options?: IGenerationOptions) {
|
||||||
grid = newGrid;
|
grid = newGrid;
|
||||||
pack = newPack;
|
pack = newPack;
|
||||||
|
|
||||||
|
// temp rendering for debug
|
||||||
|
renderLayer("coastline", pack.vertices, pack.features);
|
||||||
|
renderLayer("heightmap");
|
||||||
|
renderLayer("rivers", pack);
|
||||||
|
|
||||||
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
||||||
showStatistics();
|
showStatistics();
|
||||||
INFO && console.groupEnd();
|
INFO && console.groupEnd();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
import {renderLayer} from "layers";
|
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
import {drawCoastline} from "layers/renderers/drawCoastline";
|
import {drawCoastline} from "layers/renderers/drawCoastline";
|
||||||
import {markupPackFeatures} from "modules/markup";
|
import {markupPackFeatures} from "modules/markup";
|
||||||
|
|
@ -23,49 +22,54 @@ export function createPack(grid: IGrid): IPack {
|
||||||
const {vertices, cells} = repackGrid(grid);
|
const {vertices, cells} = repackGrid(grid);
|
||||||
|
|
||||||
const markup = markupPackFeatures(grid, vertices, pick(cells, "v", "c", "b", "p", "h"));
|
const markup = markupPackFeatures(grid, vertices, pick(cells, "v", "c", "b", "p", "h"));
|
||||||
|
const {features, featureIds, distanceField, haven, harbor} = markup;
|
||||||
|
|
||||||
renderLayer("coastline", vertices, markup.features);
|
const riverCells = {...cells, f: featureIds, t: distanceField, haven};
|
||||||
|
Rivers.generate(grid, {cells: riverCells, features}, true);
|
||||||
|
|
||||||
// drawCoastline({vertices, cells}); // split into vertices definition and rendering
|
|
||||||
|
|
||||||
// Rivers.generate(newPack, grid);
|
|
||||||
// renderLayer("rivers", newPack);
|
|
||||||
// Lakes.defineGroup(newPack);
|
// Lakes.defineGroup(newPack);
|
||||||
// Biomes.define(newPack, grid);
|
// Biomes.define(newPack, grid);
|
||||||
|
|
||||||
// const rankCellsData = pick(newPack.cells, "i", "f", "fl", "conf", "r", "h", "area", "biome", "haven", "harbor");
|
// const rankCellsData = pick(newPack.cells, "i", "f", "fl", "conf", "r", "h", "area", "biome", "haven", "harbor");
|
||||||
// rankCells(newPack.features!, rankCellsData);
|
// rankCells(newPack.features!, rankCellsData);
|
||||||
|
|
||||||
Cultures.generate();
|
// Cultures.generate();
|
||||||
Cultures.expand();
|
// Cultures.expand();
|
||||||
BurgsAndStates.generate();
|
// BurgsAndStates.generate();
|
||||||
Religions.generate();
|
// Religions.generate();
|
||||||
BurgsAndStates.defineStateForms();
|
// BurgsAndStates.defineStateForms();
|
||||||
BurgsAndStates.generateProvinces();
|
// BurgsAndStates.generateProvinces();
|
||||||
BurgsAndStates.defineBurgFeatures();
|
// BurgsAndStates.defineBurgFeatures();
|
||||||
|
|
||||||
renderLayer("states");
|
// renderLayer("states");
|
||||||
renderLayer("borders");
|
// renderLayer("borders");
|
||||||
BurgsAndStates.drawStateLabels();
|
// BurgsAndStates.drawStateLabels();
|
||||||
|
|
||||||
Rivers.specify();
|
// Rivers.specify();
|
||||||
Lakes.generateName();
|
// Lakes.generateName();
|
||||||
|
|
||||||
Military.generate();
|
// Military.generate();
|
||||||
Markers.generate();
|
// Markers.generate();
|
||||||
addZones();
|
// addZones();
|
||||||
|
|
||||||
OceanLayers(newGrid);
|
// OceanLayers(newGrid);
|
||||||
|
|
||||||
drawScaleBar(window.scale);
|
// drawScaleBar(window.scale);
|
||||||
Names.getMapName();
|
// Names.getMapName();
|
||||||
|
|
||||||
const pack = {
|
const pack: IPack = {
|
||||||
vertices,
|
vertices,
|
||||||
cells
|
cells: {
|
||||||
|
...cells,
|
||||||
|
f: featureIds,
|
||||||
|
t: distanceField,
|
||||||
|
haven,
|
||||||
|
harbor
|
||||||
|
},
|
||||||
|
features
|
||||||
};
|
};
|
||||||
|
|
||||||
return pack as IPack;
|
return pack;
|
||||||
}
|
}
|
||||||
|
|
||||||
// repack grid cells: discart deep water cells, add land cells along the coast
|
// repack grid cells: discart deep water cells, add land cells along the coast
|
||||||
|
|
@ -133,6 +137,3 @@ function repackGrid(grid: IGrid) {
|
||||||
TIME && console.timeEnd("repackGrid");
|
TIME && console.timeEnd("repackGrid");
|
||||||
return pack;
|
return pack;
|
||||||
}
|
}
|
||||||
function drawLayer(arg0: string, vertices: IGraphVertices, features: TPackFeatures) {
|
|
||||||
throw new Error("Function not implemented.");
|
|
||||||
}
|
|
||||||
|
|
|
||||||
36
src/types/pack/feature.d.ts
vendored
Normal file
36
src/types/pack/feature.d.ts
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
interface IPackFeatureBase {
|
||||||
|
i: number; // feature id starting from 1
|
||||||
|
border: boolean; // if touches map border
|
||||||
|
cells: number; // number of cells
|
||||||
|
firstCell: number; // index of the top left cell
|
||||||
|
vertices: number[]; // indexes of perimetric vertices
|
||||||
|
area: number; // area of the feature perimetric polygon
|
||||||
|
}
|
||||||
|
3;
|
||||||
|
|
||||||
|
interface IPackFeatureOcean extends IPackFeatureBase {
|
||||||
|
land: false;
|
||||||
|
type: "ocean";
|
||||||
|
group: "ocean" | "sea" | "gulf";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPackFeatureIsland extends IPackFeatureBase {
|
||||||
|
land: true;
|
||||||
|
type: "island";
|
||||||
|
group: "continent" | "island" | "isle" | "lake_island";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPackFeatureLake extends IPackFeatureBase {
|
||||||
|
land: false;
|
||||||
|
type: "lake";
|
||||||
|
group: "freshwater" | "salt" | "frozen" | "dry" | "sinkhole" | "lava";
|
||||||
|
name: string;
|
||||||
|
shoreline: number[];
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TPackFeature = IPackFeatureOcean | IPackFeatureIsland | IPackFeatureLake;
|
||||||
|
|
||||||
|
type FirstElement = 0;
|
||||||
|
|
||||||
|
type TPackFeatures = [FirstElement, ...TPackFeature[]];
|
||||||
31
src/types/pack.d.ts → src/types/pack/pack.d.ts
vendored
31
src/types/pack.d.ts → src/types/pack/pack.d.ts
vendored
|
|
@ -37,37 +37,6 @@ interface IPackBase extends IGraph {
|
||||||
features?: TPackFeatures;
|
features?: TPackFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPackFeatureBase {
|
|
||||||
i: number; // feature id starting from 1
|
|
||||||
border: boolean; // if touches map border
|
|
||||||
cells: number; // number of cells
|
|
||||||
firstCell: number; // index of the top left cell
|
|
||||||
vertices: number[]; // indexes of perimetric vertices
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPackFeatureOcean extends IPackFeatureBase {
|
|
||||||
land: false;
|
|
||||||
type: "ocean";
|
|
||||||
group: "ocean" | "sea" | "gulf";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPackFeatureIsland extends IPackFeatureBase {
|
|
||||||
land: true;
|
|
||||||
type: "island";
|
|
||||||
group: "continent" | "island" | "isle" | "lake_island";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPackFeatureLake extends IPackFeatureBase {
|
|
||||||
land: false;
|
|
||||||
type: "lake";
|
|
||||||
group: "freshwater" | "salt" | "frozen" | "dry" | "sinkhole" | "lava";
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type TPackFeature = IPackFeatureOcean | IPackFeatureIsland | IPackFeatureLake;
|
|
||||||
|
|
||||||
type TPackFeatures = [0, ...TPackFeature[]];
|
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
i: number;
|
i: number;
|
||||||
name: string;
|
name: string;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue