From 707cdd77ac122a601c202c8b417ecd9e2d480ea3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 27 Aug 2022 00:51:29 +0300 Subject: [PATCH] refactor(religions): expand religions --- src/scripts/generation/generation.ts | 3 ++ src/scripts/generation/pack/pack.ts | 10 ++-- .../pack/religions/expandReligions.ts | 39 ++++++++++++++- .../pack/religions/generateReligions.ts | 4 +- .../pack/religions/specifyReligions.ts | 2 +- src/types/common.d.ts | 8 ++-- src/types/pack/pack.d.ts | 28 +---------- src/types/pack/provinces.d.ts | 8 ++++ src/types/pack/rivers.d.ts | 18 +++++++ src/utils/colorUtils.ts | 11 +++-- src/utils/debugUtils.ts | 47 +++++++++++++++++++ 11 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 src/types/pack/provinces.d.ts create mode 100644 src/types/pack/rivers.d.ts diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index 256210e9..a201620a 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -26,6 +26,7 @@ import {createGrid} from "./grid/grid"; import {createPack} from "./pack/pack"; import {getInputValue, setInputValue} from "utils/nodeUtils"; import {calculateMapCoordinates} from "modules/coordinates"; +import {drawPolygons} from "utils/debugUtils"; const {Zoom, ThreeD} = window; @@ -70,6 +71,8 @@ async function generate(options?: IGenerationOptions) { renderLayer("burgs"); renderLayer("routes"); + drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true}); + WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); INFO && console.groupEnd(); diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 70f7e0fe..16005c6f 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -128,7 +128,7 @@ export function createPack(grid: IGrid): IPack { burg: burgIds }); - const {religionIds} = generateReligions({ + const {religionIds, religions} = generateReligions({ states, cultures, burgs, @@ -143,7 +143,8 @@ export function createPack(grid: IGrid): IPack { pop: population, culture: cultureIds, burg: burgIds, - state: stateIds + state: stateIds, + route: cellRoutes } }); @@ -191,11 +192,12 @@ export function createPack(grid: IGrid): IPack { // province }, features: mergedFeatures, - rivers: rawRivers, // "name" | "basin" | "type" + // rivers: rawRivers, // "name" | "basin" | "type" cultures, states, burgs, - routes + routes, + religions }; return pack; diff --git a/src/scripts/generation/pack/religions/expandReligions.ts b/src/scripts/generation/pack/religions/expandReligions.ts index 12d1207b..286bca0c 100644 --- a/src/scripts/generation/pack/religions/expandReligions.ts +++ b/src/scripts/generation/pack/religions/expandReligions.ts @@ -1,10 +1,12 @@ import FlatQueue from "flatqueue"; + +import {ROUTES} from "config/generation"; import {getInputNumber} from "utils/nodeUtils"; import {gauss} from "utils/probabilityUtils"; import {isReligion} from "utils/typeUtils"; type TReligionData = Pick; -type TCellsData = Pick; +type TCellsData = Pick; export function expandReligions(religions: TReligionData[], cells: TCellsData) { const religionIds = spreadFolkReligions(religions, cells); @@ -15,6 +17,10 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { const neutralInput = getInputNumber("neutralInput"); const maxExpansionCost = (cells.i.length / 25) * gauss(1, 0.3, 0.2, 2, 2) * neutralInput; + const biomePassageCost = (cellId: number) => biomesData.cost[cells.biome[cellId]]; + const isMainRoad = (cellId: number) => cells.route[cellId] === ROUTES.MAIN_ROAD; + const isSeaRoute = (cellId: number) => cells.route[cellId] === ROUTES.SEA_ROUTE; + for (const religion of religions) { if (!isReligion(religion as IReligion) || (religion as IReligion).type === "Folk") continue; @@ -24,6 +30,37 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { queue.push({cellId, religionId}, 0); } + const religionsMap = new Map(religions.map(religion => [religion.i, religion])); + + while (queue.length) { + const priority = queue.peekValue()!; + const {cellId, religionId} = queue.pop()!; + + const {culture, center, expansion, expansionism} = religionsMap.get(religionId)!; + + cells.c[cellId].forEach(neibCellId => { + // if (neibCellId === center && religionIds[neibCellId]) return; // do not overwrite center cells + if (expansion === "culture" && culture !== cells.culture[neibCellId]) return; + if (expansion === "state" && cells.state[center] !== cells.state[neibCellId]) return; + + const cultureCost = culture !== cells.culture[neibCellId] ? 50 : 0; + const stateCost = cells.state[center] !== cells.state[neibCellId] ? 50 : 0; + const passageCost = isMainRoad(neibCellId) ? 1 : biomePassageCost(neibCellId); // [1, 5000] + const waterCost = isSeaRoute(neibCellId) ? 50 : 1000; + + const cellCost = Math.max(cultureCost + stateCost + passageCost + waterCost, 0); + const totalCost = priority + 10 + cellCost / expansionism; + if (totalCost > maxExpansionCost) return; + + if (!cost[neibCellId] || totalCost < cost[neibCellId]) { + if (cells.culture[neibCellId]) religionIds[neibCellId] = religionId; // assign religion to cell + cost[neibCellId] = totalCost; + + queue.push({cellId: neibCellId, religionId}, totalCost); + } + }); + } + return religionIds; } diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index fb073b42..8a92c224 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -6,7 +6,7 @@ import {specifyReligions} from "./specifyReligions"; type TCellsData = Pick< IPack["cells"], - "i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "culture" | "burg" | "state" + "i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "culture" | "burg" | "state" | "route" >; export function generateReligions({ @@ -29,7 +29,7 @@ export function generateReligions({ cultures, states, burgs, - pick(cells, "i", "c", "culture", "burg", "state") + pick(cells, "i", "c", "biome", "culture", "burg", "state", "route") ); console.log(religions); diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts index b3c10ea8..2054503f 100644 --- a/src/scripts/generation/pack/religions/specifyReligions.ts +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -17,7 +17,7 @@ export function specifyReligions( cultures: TCultures, states: TStates, burgs: TBurgs, - cells: Pick + cells: Pick ): {religions: TReligions; religionIds: Uint16Array} { const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => { const supreme = getDeityName(cultures, cultureId); diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 8148e129..6cc35379 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -2,20 +2,22 @@ type Logical = number & (1 | 0); // data type for logical numbers type UnknownObject = {[key: string]: unknown}; -// extract element from array -type Entry = T[number]; - type noop = () => void; interface Dict { [key: string]: T; } +// extract element from array +type Entry = T[number]; + // element of Object.entries type ObjectEntry = [string, T]; type UintArray = Uint8Array | Uint16Array | Uint32Array; type IntArray = Int8Array | Int16Array | Int32Array; +type FloatArray = Float32Array | Float64Array; +type TypedArray = UintArray | IntArray | FloatArray; type RGB = `rgb(${number}, ${number}, ${number})`; type Hex = `#${string}`; diff --git a/src/types/pack/pack.d.ts b/src/types/pack/pack.d.ts index 6c205904..71bbe374 100644 --- a/src/types/pack/pack.d.ts +++ b/src/types/pack/pack.d.ts @@ -3,9 +3,9 @@ interface IPack extends IGraph { features: TPackFeatures; states: TStates; cultures: TCultures; - provinces: IProvince[]; + provinces: TProvinces; burgs: TBurgs; - rivers: IRiver[]; + rivers: TRivers; religions: TReligions; routes: TRoutes; } @@ -38,27 +38,3 @@ interface IPackBase extends IGraph { cells: IGraphCells & Partial; features?: TPackFeatures; } - -interface IProvince { - i: number; - name: string; - fullName: string; - removed?: boolean; -} - -interface IRiver { - i: number; - name: string; - basin: number; - parent: number; - type: string; - source: number; - mouth: number; - sourceWidth: number; - width: number; - widthFactor: number; - length: number; - discharge: number; - cells: number[]; - points?: number[]; -} diff --git a/src/types/pack/provinces.d.ts b/src/types/pack/provinces.d.ts new file mode 100644 index 00000000..502b5b80 --- /dev/null +++ b/src/types/pack/provinces.d.ts @@ -0,0 +1,8 @@ +interface IProvince { + i: number; + name: string; + fullName: string; + removed?: boolean; +} + +type TProvinces = IProvince[]; diff --git a/src/types/pack/rivers.d.ts b/src/types/pack/rivers.d.ts new file mode 100644 index 00000000..c682fcf5 --- /dev/null +++ b/src/types/pack/rivers.d.ts @@ -0,0 +1,18 @@ +interface IRiver { + i: number; + name: string; + basin: number; + parent: number; + type: string; + source: number; + mouth: number; + sourceWidth: number; + width: number; + widthFactor: number; + length: number; + discharge: number; + cells: number[]; + points?: number[]; +} + +type TRivers = IRiver[]; diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index bdd05c93..36f4a604 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -15,8 +15,9 @@ const cardinal12: Hex[] = [ "#eb8de7" ]; -type ColorScheme = d3.ScaleSequential; -const colorSchemeMap: Dict = { +export type TColorScheme = "default" | "bright" | "light" | "green" | "rainbow" | "monochrome"; +const colorSchemeMap: {[key in TColorScheme]: d3.ScaleSequential} = { + default: d3.scaleSequential(d3.interpolateSpectral), bright: d3.scaleSequential(d3.interpolateSpectral), light: d3.scaleSequential(d3.interpolateRdYlGn), green: d3.scaleSequential(d3.interpolateGreens), @@ -27,7 +28,7 @@ const colorSchemeMap: Dict = { export function getColors(number: number) { if (number <= cardinal12.length) return d3.shuffle(cardinal12.slice(0, number)); - const scheme = colorSchemeMap.bright; + const scheme = colorSchemeMap.default; const colors = d3.range(number).map(index => { if (index < 12) return cardinal12[index]; @@ -39,7 +40,7 @@ export function getColors(number: number) { } export function getRandomColor(): Hex { - const scheme = colorSchemeMap.bright; + const scheme = colorSchemeMap.default; const rgb = scheme(Math.random())!; return d3.color(rgb)?.formatHex() as Hex; } @@ -54,7 +55,7 @@ export function getMixedColor(hexColor: string, mixation = 0.2, bright = 0.3) { return d3.color(mixedColor)!.brighter(bright).hex(); } -export function getColorScheme(schemeName: string) { +export function getColorScheme(schemeName: TColorScheme) { return colorSchemeMap[schemeName] || colorSchemeMap.bright; } diff --git a/src/utils/debugUtils.ts b/src/utils/debugUtils.ts index 623d66a5..e17c98c3 100644 --- a/src/utils/debugUtils.ts +++ b/src/utils/debugUtils.ts @@ -1,5 +1,6 @@ // utils to be used for debugging (not in PROD) +import {getColorScheme, TColorScheme} from "./colorUtils"; import {getNormal} from "./lineUtils"; export function drawPoint([x, y]: TPoint, {radius = 1, color = "red"} = {}) { @@ -55,3 +56,49 @@ export function drawText(text: string | number, [x, y]: TPoint, {size = 6, color .attr("stroke", "none") .text(text); } + +export function drawPolygons( + values: TypedArray, + cellVertices: number[][], + vertexPoints: TPoints, + { + fillOpacity = 0.3, + stroke = "#222", + strokeWidth = 0.2, + colorScheme = "default", + excludeZeroes = false + }: { + fillOpacity?: number; + stroke?: string; + strokeWidth?: number; + colorScheme?: TColorScheme; + excludeZeroes?: boolean; + } = {} +) { + const cellIds = [...Array(values.length).keys()]; + const data = excludeZeroes ? cellIds.filter(id => values[id] !== 0) : cellIds; + + const getPolygon = (id: number) => { + const vertices = cellVertices[id]; + const points = vertices.map(id => vertexPoints[id]); + return `${points.join(" ")} ${points[0].join(",")}`; + }; + + // get fill from normalizing and interpolating values to color scheme + const min = Math.min(...values); + const max = Math.max(...values); + const normalized = Array.from(values).map(value => (value - min) / (max - min)); + const scheme = getColorScheme(colorScheme); + const getFill = (id: number) => scheme(normalized[id])!; + + debug + .selectAll("polyline") + .data(data) + .enter() + .append("polyline") + .attr("points", getPolygon) + .attr("fill", getFill) + .attr("fill-opacity", fillOpacity) + .attr("stroke", stroke) + .attr("stroke-width", strokeWidth); +}