refactor: markupPackFeatures

This commit is contained in:
Azgaar 2022-07-14 01:38:45 +03:00
parent 87d8c1024d
commit f82fcbae0f
8 changed files with 271 additions and 178 deletions

View file

@ -3,8 +3,9 @@ export const MIN_LAND_HEIGHT = 20;
export const MAX_HEIGHT = 100; export const MAX_HEIGHT = 100;
export enum DISTANCE_FIELD { export enum DISTANCE_FIELD {
LAND_COAST = 1,
UNMARKED = 0, UNMARKED = 0,
WATER_COAST = -1, WATER_COAST = -1,
DEEPER_WATER = -2 LAND_COAST = 1,
DEEPER_WATER = -2,
LANDLOCKED = 2
} }

View file

@ -17,7 +17,7 @@ import {setDefaultEventHandlers} from "scripts/events";
import {undraw} from "scripts/generation/generation"; import {undraw} from "scripts/generation/generation";
import {prompt} from "scripts/prompt"; import {prompt} from "scripts/prompt";
import {rankCells} from "scripts/rankCells"; import {rankCells} from "scripts/rankCells";
import {reGraph} from "scripts/reGraph"; import {repackGrid, markupPackFeatures} from "scripts/reGraph";
import {clearMainTip, showMainTip, tip} from "scripts/tooltips"; import {clearMainTip, showMainTip, tip} from "scripts/tooltips";
import {createTypedArray, last} from "utils/arrayUtils"; import {createTypedArray, last} from "utils/arrayUtils";
import {getColorScheme, getHeightColor} from "utils/colorUtils"; import {getColorScheme, getHeightColor} from "utils/colorUtils";
@ -223,8 +223,8 @@ export function open(options) {
OceanLayers(grid); OceanLayers(grid);
calculateTemperatures(grid); calculateTemperatures(grid);
generatePrecipitation(grid); generatePrecipitation(grid);
reGraph(grid); repackGrid(grid);
reMarkFeatures(pack, newGrid); markupPackFeatures(pack, newGrid);
drawCoastline(pack); drawCoastline(pack);
Rivers.generate(pack, grid, erosionAllowed); Rivers.generate(pack, grid, erosionAllowed);

View file

@ -3,7 +3,6 @@ import * as d3 from "d3";
import {INFO} from "config/logging"; import {INFO} from "config/logging";
import {closeDialogs} from "dialogs/utils"; import {closeDialogs} from "dialogs/utils";
import {updatePresetInput} from "layers"; import {updatePresetInput} from "layers";
import {reMarkFeatures} from "modules/markup";
import {setDefaultEventHandlers} from "scripts/events"; import {setDefaultEventHandlers} from "scripts/events";
import {regenerateMap} from "scripts/generation/generation"; import {regenerateMap} from "scripts/generation/generation";
import {calculateVoronoi} from "scripts/generation/graph"; import {calculateVoronoi} from "scripts/generation/graph";

View file

@ -3,10 +3,12 @@ import {TIME} from "config/logging";
import {INT8_MAX} from "constants"; import {INT8_MAX} from "constants";
// @ts-expect-error js module // @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";
const {UNMARKED, LAND_COAST, WATER_COAST} = DISTANCE_FIELD; const {UNMARKED, LAND_COAST, WATER_COAST, LANDLOCKED, DEEPER_WATER} = DISTANCE_FIELD;
// define features (grid.features: ocean, lakes, islands) and calculate distance field (cells.t) // define features (oceans, lakes, islands)
export function markupGridFeatures(grid: IGridWithHeights) { export function markupGridFeatures(grid: IGridWithHeights) {
TIME && console.time("markupGridFeatures"); TIME && console.time("markupGridFeatures");
Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode
@ -20,7 +22,7 @@ export function markupGridFeatures(grid: IGridWithHeights) {
const n = cells.i.length; const n = cells.i.length;
const featureIds = new Uint16Array(n); // starts from 1 const featureIds = new Uint16Array(n); // starts from 1
let distanceField = new Int8Array(n); const distanceField = new Int8Array(n);
const features: TGridFeatures = [0]; const features: TGridFeatures = [0];
const queue = [0]; const queue = [0];
@ -55,33 +57,166 @@ export function markupGridFeatures(grid: IGridWithHeights) {
} }
// markup deep ocean cells // markup deep ocean cells
distanceField = markup({graph: grid, distanceField, start: -2, increment: -1, limit: -10}); const dfOceanMarked = markup({
distanceField,
neighbors: grid.cells.c,
start: DEEPER_WATER,
increment: -1,
limit: -10
});
TIME && console.timeEnd("markupGridFeatures"); TIME && console.timeEnd("markupGridFeatures");
return {featureIds, distanceField, features}; return {featureIds, distanceField: dfOceanMarked, features};
}
// define features (oceans, lakes, islands) add related details
export function markupPackFeatures(grid: IGrid, cells: Pick<IPack["cells"], "c" | "b" | "p" | "h">) {
TIME && console.time("markupPackFeatures");
const packCellsNumber = cells.h.length;
const gridCellsNumber = grid.cells.h.length;
const features: TPackFeatures = [0];
const featureIds = new Uint16Array(packCellsNumber); // ids of features, starts from 1
const distanceField = new Int8Array(packCellsNumber); // distance from coast; 1 = land along coast; -1 = water along coast
const haven = createTypedArray({maxValue: packCellsNumber, length: packCellsNumber}); // haven (opposite water cell)
const harbor = new Uint8Array(packCellsNumber); // harbor (number of adjacent water cells)
const defineHaven = (cellId: number) => {
const waterCells = cells.c[cellId].filter(c => cells.h[c] < MIN_LAND_HEIGHT);
const distances = waterCells.map(c => dist2(cells.p[cellId], cells.p[c]));
const closest = distances.indexOf(Math.min.apply(Math, distances));
haven[cellId] = waterCells[closest];
harbor[cellId] = waterCells.length;
};
const OCEAN_MIN_SIZE = gridCellsNumber / 25;
const SEA_MIN_SIZE = gridCellsNumber / 1000;
const CONTINENT_MIN_SIZE = gridCellsNumber / 10;
const ISLAND_MIN_SIZE = gridCellsNumber / 1000;
function defineOceanGroup(cellsNumber: number) {
if (cellsNumber > OCEAN_MIN_SIZE) return "ocean";
if (cellsNumber > SEA_MIN_SIZE) return "sea";
return "gulf";
}
function defineIslandGroup(firstCell: number, cellsNumber: number) {
const prevCellFeature = features[featureIds[firstCell - 1]];
if (prevCellFeature && prevCellFeature.type === "lake") return "lake_island";
if (cellsNumber > CONTINENT_MIN_SIZE) return "continent";
if (cellsNumber > ISLAND_MIN_SIZE) return "island";
return "isle";
}
function addIsland(featureId: number, border: boolean, firstCell: number, cells: number) {
const group = defineIslandGroup(firstCell, cells);
const feature: IPackFeatureIsland = {i: featureId, type: "island", group, land: true, border, cells, firstCell};
features.push(feature);
}
function addOcean(featureId: number, firstCell: number, cells: number) {
const group = defineOceanGroup(cells);
const feature: IPackFeatureOcean = {
i: featureId,
type: "ocean",
group,
land: false,
border: false,
cells,
firstCell
};
features.push(feature);
}
function addLake(featureId: number, firstCell: number, cells: number) {
const group = "freshwater"; // temp, to be defined later
const name = ""; // temp, to be defined later
const feature: IPackFeatureLake = {
i: featureId,
type: "lake",
group,
name,
land: false,
border: false,
cells,
firstCell
};
features.push(feature);
}
const queue = [0];
for (let featureId = 1; queue[0] !== -1; featureId++) {
const firstCell = queue[0];
featureIds[firstCell] = featureId; // assign feature number
const land = cells.h[firstCell] >= MIN_LAND_HEIGHT;
let border = false; // true if feature touches map border
let cellNumber = 1; // count cells in a feature
while (queue.length) {
const cellId = queue.pop()!;
if (cells.b[cellId]) border = true;
for (const neighborId of cells.c[cellId]) {
const isNeibLand = cells.h[neighborId] >= MIN_LAND_HEIGHT;
if (land && !isNeibLand) {
distanceField[cellId] = LAND_COAST;
distanceField[neighborId] = WATER_COAST;
if (!haven[cellId]) defineHaven(cellId);
} else if (land && isNeibLand) {
if (distanceField[neighborId] === UNMARKED && distanceField[cellId] === LAND_COAST)
distanceField[neighborId] = LANDLOCKED;
else if (distanceField[cellId] === UNMARKED && distanceField[neighborId] === LAND_COAST)
distanceField[cellId] = LANDLOCKED;
}
if (!featureIds[neighborId] && land === isNeibLand) {
queue.push(neighborId);
featureIds[neighborId] = featureId;
cellNumber++;
}
}
}
// const vertices = detectFeatureVertices(cells, firstCell);
if (land) addIsland(featureId, border, firstCell, cellNumber);
else if (border) addOcean(featureId, firstCell, cellNumber);
else addLake(featureId, firstCell, cellNumber);
queue[0] = featureIds.findIndex(f => f === UNMARKED); // find unmarked cell
}
// markup pack land cells
const dfLandMarked = markup({distanceField, neighbors: cells.c, start: LANDLOCKED + 1, increment: 1});
TIME && console.timeEnd("markupPackFeatures");
return {features, featureIds, distanceField: dfLandMarked, haven, harbor};
} }
// calculate distance to coast for every cell // calculate distance to coast for every cell
function markup({ function markup({
graph,
distanceField, distanceField,
neighbors,
start, start,
increment, increment,
limit limit = INT8_MAX
}: { }: {
graph: IGraph;
distanceField: Int8Array; distanceField: Int8Array;
neighbors: number[][];
start: number; start: number;
increment: number; increment: number;
limit: number; limit?: number;
}) { }) {
const cellsLength = graph.cells.i.length;
const neighbors = graph.cells.c;
for (let distance = start, marked = Infinity; marked > 0 && distance > limit; distance += increment) { for (let distance = start, marked = Infinity; marked > 0 && distance > limit; distance += increment) {
marked = 0; marked = 0;
const prevDistance = distance - increment; const prevDistance = distance - increment;
for (let cellId = 0; cellId < cellsLength; cellId++) { for (let cellId = 0; cellId < neighbors.length; cellId++) {
if (distanceField[cellId] !== prevDistance) continue; if (distanceField[cellId] !== prevDistance) continue;
for (const neighborId of neighbors[cellId]) { for (const neighborId of neighbors[cellId]) {
@ -95,94 +230,24 @@ function markup({
return distanceField; return distanceField;
} }
// Re-mark features (ocean, lakes, islands) // connect vertices to chain
export function reMarkFeatures(pack: IPackBase, grid: IGrid) { function connectVertices(start, t) {
TIME && console.time("reMarkFeatures"); const chain = []; // vertices chain to form a path
const {cells} = pack; for (let i = 0, current = start; i === 0 || (current !== start && i < 50000); i++) {
const features: TPackFeatures = [0]; const prev = chain[chain.length - 1]; // previous vertex in chain
const n = cells.i.length; chain.push(current); // add current vertex to sequence
const c = vertices.c[current]; // cells adjacent to vertex
cells.f = new Uint16Array(n); // cell feature number const v = vertices.v[current]; // neighboring vertices
cells.t = new Int8Array(n); // cell type: 1 = land along coast; -1 = water along coast; const c0 = c[0] >= n || cells.t[c[0]] === t;
cells.haven = n < 65535 ? new Uint16Array(n) : new Uint32Array(n); // cell haven (opposite water cell); const c1 = c[1] >= n || cells.t[c[1]] === t;
cells.harbor = new Uint8Array(n); // cell harbor (number of adjacent water cells); const c2 = c[2] >= n || cells.t[c[2]] === t;
if (v[0] !== prev && c0 !== c1) current = v[0];
const defineHaven = (i: number) => { else if (v[1] !== prev && c1 !== c2) current = v[1];
const water = cells.c[i].filter(c => cells.h[c] < 20); else if (v[2] !== prev && c0 !== c2) current = v[2];
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2); if (current === chain[chain.length - 1]) {
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))]; ERROR && console.error("Next vertex is not found");
break;
cells.haven[i] = closest;
cells.harbor[i] = water.length;
};
for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
const start = queue[0]; // first cell
cells.f[start] = i; // assign feature number
const land = cells.h[start] >= 20;
let border = false; // true if feature touches map border
let cellNumber = 1; // to count cells number in a feature
while (queue.length) {
const firstCellId = queue.pop()!;
if (cells.b[firstCellId]) border = true;
cells.c[firstCellId].forEach(function (e) {
const eLand = cells.h[e] >= 20;
if (land && !eLand) {
cells.t[firstCellId] = 1;
cells.t[e] = -1;
if (!cells.haven[firstCellId]) defineHaven(firstCellId);
} else if (land && eLand) {
if (!cells.t[e] && cells.t[firstCellId] === 1) cells.t[e] = 2;
else if (!cells.t[firstCellId] && cells.t[e] === 1) cells.t[firstCellId] = 2;
}
if (!cells.f[e] && land === eLand) {
queue.push(e);
cells.f[e] = i;
cellNumber++;
}
});
} }
if (land) {
const group = defineIslandGroup(start, cellNumber);
const feature: IPackFeatureIsland = {i, type: "island", group, land, border, cells: cellNumber, firstCell: start};
features.push(feature);
} else if (border) {
const group = defineOceanGroup(cellNumber);
const feature: IPackFeatureOcean = {i, type: "ocean", group, land, border, cells: cellNumber, firstCell: start};
features.push(feature);
} else {
const group = "freshwater"; // temp, to be defined later
const name = ""; // temp, to be defined later
const cells = cellNumber;
const feature: IPackFeatureLake = {i, type: "lake", group, name, land, border, cells, firstCell: start};
features.push(feature);
}
queue[0] = cells.f.findIndex(f => f === UNMARKED); // find unmarked cell
} }
return chain;
// markupPackLand
markup({graph: pack, distanceField: cells.t, start: 3, increment: 1, limit: INT8_MAX});
function defineOceanGroup(number: number) {
if (number > grid.cells.i.length / 25) return "ocean";
if (number > grid.cells.i.length / 100) return "sea";
return "gulf";
}
function defineIslandGroup(cellId: number, number: number) {
const prevCellFeature = features[cells.f[cellId - 1]];
if (cellId && prevCellFeature && prevCellFeature.type === "lake") return "lake_island";
if (number > grid.cells.i.length / 10) return "continent";
if (number > grid.cells.i.length / 1000) return "island";
return "isle";
}
pack.features = features;
TIME && console.timeEnd("reMarkFeatures");
} }

View file

@ -3,10 +3,9 @@ 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, renderLayer, restoreLayers} from "layers"; import {initLayers, restoreLayers} from "layers";
// @ts-expect-error js module // @ts-expect-error js module
import {drawCoastline} from "modules/coastline"; import {drawCoastline} from "modules/coastline";
import {reMarkFeatures} from "modules/markup";
// @ts-expect-error js module // @ts-expect-error js module
import {drawScaleBar, Rulers} from "modules/measurers"; import {drawScaleBar, Rulers} from "modules/measurers";
// @ts-expect-error js module // @ts-expect-error js module
@ -26,26 +25,12 @@ import {debounce} from "utils/functionUtils";
import {rn} from "utils/numberUtils"; import {rn} from "utils/numberUtils";
import {generateSeed} from "utils/probabilityUtils"; import {generateSeed} from "utils/probabilityUtils";
import {byId} from "utils/shorthands"; import {byId} from "utils/shorthands";
import {rankCells} from "../rankCells";
import {showStatistics} from "../statistics"; import {showStatistics} from "../statistics";
import {createGrid} from "./grid"; import {createGrid} from "./grid";
import {reGraph} from "./reGraph"; import {createPack} from "./pack";
import {getInputValue, setInputValue} from "utils/nodeUtils"; import {getInputValue, setInputValue} from "utils/nodeUtils";
const { const {Zoom, ThreeD} = window;
Zoom,
Lakes,
OceanLayers,
Rivers,
Biomes,
Cultures,
BurgsAndStates,
Religions,
Military,
Markers,
Names,
ThreeD
} = window;
interface IGenerationOptions { interface IGenerationOptions {
seed: string; seed: string;
@ -66,42 +51,10 @@ async function generate(options?: IGenerationOptions) {
randomizeOptions(); randomizeOptions();
const newGrid = await createGrid(grid, precreatedGraph); const newGrid = await createGrid(grid, precreatedGraph);
const newPack = reGraph(newGrid); const newPack = createPack(newGrid);
reMarkFeatures(newPack, newGrid); // redefine global grid and pack
drawCoastline(newPack); grid = newGrid;
Rivers.generate(newPack, newGrid);
renderLayer("rivers", newPack);
Lakes.defineGroup(newPack);
Biomes.define(newPack, newGrid);
rankCells(newPack);
Cultures.generate();
Cultures.expand();
BurgsAndStates.generate();
Religions.generate();
BurgsAndStates.defineStateForms();
BurgsAndStates.generateProvinces();
BurgsAndStates.defineBurgFeatures();
renderLayer("states");
renderLayer("borders");
BurgsAndStates.drawStateLabels();
Rivers.specify();
Lakes.generateName();
Military.generate();
Markers.generate();
addZones();
OceanLayers(newGrid);
drawScaleBar(window.scale);
Names.getMapName();
// @ts-expect-error redefine global
pack = newPack; pack = newPack;
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);

View file

@ -1,17 +1,75 @@
import * as d3 from "d3"; import * as d3 from "d3";
import {renderLayer} from "layers";
// @ts-expect-error js module
import {drawCoastline} from "modules/coastline";
import {markupPackFeatures} from "modules/markup";
// @ts-expect-error js module
import {drawScaleBar} from "modules/measurers";
// @ts-expect-error js module
import {addZones} from "modules/zones";
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {UINT16_MAX} from "constants"; import {UINT16_MAX} from "constants";
import {createTypedArray} from "utils/arrayUtils";
import {calculateVoronoi} from "scripts/generation/graph"; import {calculateVoronoi} from "scripts/generation/graph";
import {createTypedArray} from "utils/arrayUtils";
import {pick} from "utils/functionUtils";
import {rn} from "utils/numberUtils"; import {rn} from "utils/numberUtils";
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation"; import {rankCells} from "../rankCells";
const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD; const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD;
const {Lakes, OceanLayers, Rivers, Biomes, Cultures, BurgsAndStates, Religions, Military, Markers, Names} = window;
// recalculate Voronoi Graph to pack cells export function createPack(grid: IGrid): IPack {
export function reGraph(grid: IGrid): IPackBase { const {vertices, cells} = repackGrid(grid);
TIME && console.time("reGraph");
const markup = markupPackFeatures(grid, pick(cells, "c", "b", "p", "h"));
drawCoastline({vertices, cells}); // split into vertices definition and rendering
// Rivers.generate(newPack, grid);
// renderLayer("rivers", newPack);
// Lakes.defineGroup(newPack);
// Biomes.define(newPack, grid);
// const rankCellsData = pick(newPack.cells, "i", "f", "fl", "conf", "r", "h", "area", "biome", "haven", "harbor");
// rankCells(newPack.features!, rankCellsData);
Cultures.generate();
Cultures.expand();
BurgsAndStates.generate();
Religions.generate();
BurgsAndStates.defineStateForms();
BurgsAndStates.generateProvinces();
BurgsAndStates.defineBurgFeatures();
renderLayer("states");
renderLayer("borders");
BurgsAndStates.drawStateLabels();
Rivers.specify();
Lakes.generateName();
Military.generate();
Markers.generate();
addZones();
OceanLayers(newGrid);
drawScaleBar(window.scale);
Names.getMapName();
const pack = {
vertices,
cells
};
return pack as IPack;
}
// repack grid cells: discart deep water cells, add land cells along the coast
function repackGrid(grid: IGrid) {
TIME && console.time("repackGrid");
const {cells: gridCells, points, features} = grid; const {cells: gridCells, points, features} = grid;
const newCells: {p: TPoints; g: number[]; h: number[]} = {p: [], g: [], h: []}; // store new data const newCells: {p: TPoints; g: number[]; h: number[]} = {p: [], g: [], h: []}; // store new data
const spacing2 = grid.spacing ** 2; const spacing2 = grid.spacing ** 2;
@ -59,7 +117,7 @@ export function reGraph(grid: IGrid): IPackBase {
return Math.min(area, UINT16_MAX); return Math.min(area, UINT16_MAX);
} }
const pack: IPackBase = { const pack = {
vertices, vertices,
cells: { cells: {
...cells, ...cells,
@ -71,6 +129,6 @@ export function reGraph(grid: IGrid): IPackBase {
} }
}; };
TIME && console.timeEnd("reGraph"); TIME && console.timeEnd("repackGrid");
return pack; return pack;
} }

View file

@ -8,12 +8,14 @@ const FLUX_MAX_BONUS = 250;
const SUITABILITY_FACTOR = 5; const SUITABILITY_FACTOR = 5;
// assess cells suitability for population and rank cells for culture centers and burgs placement // assess cells suitability for population and rank cells for culture centers and burgs placement
export function rankCells(pack: IPack) { export function rankCells(
features: TPackFeatures,
cells: Pick<IPack["cells"], "i" | "f" | "fl" | "conf" | "r" | "h" | "area" | "biome" | "haven" | "harbor">
) {
TIME && console.time("rankCells"); TIME && console.time("rankCells");
const {cells, features} = pack;
cells.s = new Int16Array(cells.i.length); // cell suitability array const suitability = new Int16Array(cells.i.length); // cell suitability array
cells.pop = new Float32Array(cells.i.length); // cell population array const population = new Float32Array(cells.i.length); // cell population array
const meanFlux = d3.median(cells.fl.filter(f => f)) || 0; const meanFlux = d3.median(cells.fl.filter(f => f)) || 0;
const maxFlux = (d3.max(cells.fl) || 0) + (d3.max(cells.conf) || 0); // to normalize flux const maxFlux = (d3.max(cells.fl) || 0) + (d3.max(cells.conf) || 0); // to normalize flux
@ -30,14 +32,12 @@ export function rankCells(pack: IPack) {
const coastBonus = getCoastBonus(cellId); // [-30, 30] const coastBonus = getCoastBonus(cellId); // [-30, 30]
const estuaryBonus = getEstuaryBonus(cellId); // [0, 15] const estuaryBonus = getEstuaryBonus(cellId); // [0, 15]
const suitability = const bonuses = [habitabilityBonus, riverBonus, elevationBonus, coastBonus, estuaryBonus];
(habitabilityBonus + riverBonus + elevationBonus + coastBonus + estuaryBonus) / SUITABILITY_FACTOR; // [-30, 311] const total = d3.sum(bonuses) / SUITABILITY_FACTOR; // [-30, 311]
suitability[cellId] = total;
// cell rural population is suitability adjusted by cell area // cell rural population is suitability adjusted by cell area
const population = suitability > 0 ? suitability * (cells.area[cellId] / meanArea) : 0; population[cellId] = total > 0 ? total * (cells.area[cellId] / meanArea) : 0;
cells.pop[cellId] = population;
cells.s[cellId] = suitability;
} }
function getHabitabilityBonus(cellId: number) { function getHabitabilityBonus(cellId: number) {
@ -57,7 +57,10 @@ export function rankCells(pack: IPack) {
if (!isCoastal(cellId)) return 0; if (!isCoastal(cellId)) return 0;
const havenCell = cells.haven[cellId]; const havenCell = cells.haven[cellId];
const {group} = features[cells.f[havenCell]]; const feature = features[cells.f[havenCell]];
if (!feature) return 0;
const {group} = feature;
// lake coast // lake coast
if (group === "freshwater") return 30; if (group === "freshwater") return 30;
@ -78,4 +81,6 @@ export function rankCells(pack: IPack) {
} }
TIME && console.timeEnd("rankCells"); TIME && console.timeEnd("rankCells");
return {s: suitability, pop: population};
} }

View file

@ -84,3 +84,15 @@ export function getBase64(url: string, callback: (base64: string | ArrayBuffer |
xhr.responseType = "blob"; xhr.responseType = "blob";
xhr.send(); xhr.send();
} }
export function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
const ret = {} as Pick<T, K>;
keys.forEach(key => {
ret[key] = obj[key];
});
return ret;
}
export function dist2([x1, y1]: [number, number], [x2, y2]: [number, number]) {
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}