From d1208b12ec24959af5dd7fb6cb43d99d3227da11 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 10 Jul 2022 17:48:02 +0300 Subject: [PATCH] refactor: rankCells --- src/scripts/rankCells.ts | 90 +++++++++++++++++++++++++++------------- src/types/pack.d.ts | 4 ++ src/utils/graphUtils.ts | 14 ++++--- 3 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/scripts/rankCells.ts b/src/scripts/rankCells.ts index b76c7501..8b581870 100644 --- a/src/scripts/rankCells.ts +++ b/src/scripts/rankCells.ts @@ -1,9 +1,13 @@ import * as d3 from "d3"; import {TIME} from "config/logging"; -import {normalize} from "utils/numberUtils"; +import {normalize, rn} from "utils/numberUtils"; +import {isWater, isCoastal} from "utils/graphUtils"; -// assess cells suitability to calculate population and rand cells for culture center and burgs placement +const FLUX_MAX_BONUS = 250; +const SUITABILITY_FACTOR = 5; + +// assess cells suitability for population and rank cells for culture centers and burgs placement export function rankCells() { TIME && console.time("rankCells"); const {cells, features} = pack; @@ -11,36 +15,66 @@ export function rankCells() { cells.s = new Int16Array(cells.i.length); // cell suitability array cells.pop = new Float32Array(cells.i.length); // cell population array - const flMean = d3.median(cells.fl.filter(f => f)) || 0; - const flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux - const areaMean = d3.mean(cells.area); // to adjust population by cell area + 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 meanArea = d3.mean(cells.area) || 0; // to adjust population by cell area - for (const i of cells.i) { - if (cells.h[i] < 20) continue; // no population in water - let s = +biomesData.habitability[cells.biome[i]]; // base suitability derived from biome habitability - if (!s) continue; // uninhabitable biomes has 0 suitability - if (flMean) s += normalize(cells.fl[i] + cells.conf[i], flMean, flMax) * 250; // big rivers and confluences are valued - s -= (cells.h[i] - 50) / 5; // low elevation is valued, high is not; + for (const cellId of cells.i) { + if (isWater(cellId)) continue; // no population in water - if (cells.t[i] === 1) { - if (cells.r[i]) s += 15; // estuary is valued - const feature = features[cells.f[cells.haven[i]]]; - if (feature.type === "lake") { - if (feature.group === "freshwater") s += 30; - else if (feature.group == "salt") s += 10; - else if (feature.group == "frozen") s += 1; - else if (feature.group == "dry") s -= 5; - else if (feature.group == "sinkhole") s -= 5; - else if (feature.group == "lava") s -= 30; - } else { - s += 5; // ocean coast is valued - if (cells.harbor[i] === 1) s += 20; // safe sea harbor is valued - } - } + const habitabilityBonus = getHabitabilityBonus(cellId); // [0, 100] + if (!habitabilityBonus) continue; // uninhabitable biomes are excluded + + const riverBonus = getFluxBonus(cellId); // [0, 250] + const elevationBonus = getElevationBonus(cellId); // [-10, 6] + const coastBonus = getCoastBonus(cellId); // [-30, 30] + const estuaryBonus = getEstuaryBonus(cellId); // [0, 15] + + const suitability = + (habitabilityBonus + riverBonus + elevationBonus + coastBonus + estuaryBonus) / SUITABILITY_FACTOR; // [-30, 311] - cells.s[i] = s / 5; // general population rate // cell rural population is suitability adjusted by cell area - cells.pop[i] = cells.s[i] > 0 ? (cells.s[i] * cells.area[i]) / areaMean : 0; + const population = suitability > 0 ? suitability * (cells.area[cellId] / meanArea) : 0; + + cells.pop[cellId] = population; + cells.s[cellId] = suitability; + } + + function getHabitabilityBonus(cellId: number) { + return biomesData.habitability[cells.biome[cellId]]; + } + + function getFluxBonus(cellId: number) { + if (!cells.fl[cellId]) return 0; + return normalize(cells.fl[cellId] + cells.conf[cellId], meanFlux, maxFlux) * FLUX_MAX_BONUS; + } + + function getElevationBonus(cellId: number) { + return (50 - cells.h[cellId]) / 5; + } + + function getCoastBonus(cellId: number) { + if (!isCoastal(cellId)) return 0; + + const havenCell = cells.haven[cellId]; + const {group} = features[cells.f[havenCell]]; + + // lake coast + if (group === "freshwater") return 30; + if (group == "salt") return 10; + if (group == "frozen") return 1; + if (group == "dry") return 1; + if (group == "sinkhole") return 3; + if (group == "lava") return -30; + + // ocean coast + if (cells.harbor[cellId] === 1) return 25; // safe harbor + return 5; // unsafe harbor + } + + // estuary bonus is [0, 15] + function getEstuaryBonus(cellId: number) { + return cells.r[cellId] && isCoastal(cellId) ? 15 : 0; } TIME && console.timeEnd("rankCells"); diff --git a/src/types/pack.d.ts b/src/types/pack.d.ts index 34c48b84..4470f3b7 100644 --- a/src/types/pack.d.ts +++ b/src/types/pack.d.ts @@ -17,6 +17,8 @@ interface IPack { s: IntArray; pop: Float32Array; fl: UintArray; + conf: UintArray; + r: UintArray; biome: UintArray; area: UintArray; state: UintArray; @@ -24,6 +26,8 @@ interface IPack { religion: UintArray; province: UintArray; burg: UintArray; + haven: UintArray; + harbor: UintArray; q: d3.Quadtree; }; states: IState[]; diff --git a/src/utils/graphUtils.ts b/src/utils/graphUtils.ts index 5f2e5ef3..2cf36b05 100644 --- a/src/utils/graphUtils.ts +++ b/src/utils/graphUtils.ts @@ -160,14 +160,16 @@ export function getGridPolygon(i: number): TPoints { return grid.cells.v[i].map(v => grid.vertices.p[v]); } -// filter land cells -export function isLand(i: number) { - return pack.cells.h[i] >= 20; +export function isLand(cellId: number) { + return pack.cells.h[cellId] >= 20; } -// filter water cells -export function isWater(i: number) { - return pack.cells.h[i] < 20; +export function isWater(cellId: number) { + return pack.cells.h[cellId] < 20; +} + +export function isCoastal(i: number) { + return pack.cells.t[i] === 1; } // findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e