mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor(religions): expand religions
This commit is contained in:
parent
0a77845ebc
commit
707cdd77ac
11 changed files with 136 additions and 42 deletions
|
|
@ -26,6 +26,7 @@ import {createGrid} from "./grid/grid";
|
||||||
import {createPack} from "./pack/pack";
|
import {createPack} from "./pack/pack";
|
||||||
import {getInputValue, setInputValue} from "utils/nodeUtils";
|
import {getInputValue, setInputValue} from "utils/nodeUtils";
|
||||||
import {calculateMapCoordinates} from "modules/coordinates";
|
import {calculateMapCoordinates} from "modules/coordinates";
|
||||||
|
import {drawPolygons} from "utils/debugUtils";
|
||||||
|
|
||||||
const {Zoom, ThreeD} = window;
|
const {Zoom, ThreeD} = window;
|
||||||
|
|
||||||
|
|
@ -70,6 +71,8 @@ async function generate(options?: IGenerationOptions) {
|
||||||
renderLayer("burgs");
|
renderLayer("burgs");
|
||||||
renderLayer("routes");
|
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`);
|
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
||||||
// showStatistics();
|
// showStatistics();
|
||||||
INFO && console.groupEnd();
|
INFO && console.groupEnd();
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export function createPack(grid: IGrid): IPack {
|
||||||
burg: burgIds
|
burg: burgIds
|
||||||
});
|
});
|
||||||
|
|
||||||
const {religionIds} = generateReligions({
|
const {religionIds, religions} = generateReligions({
|
||||||
states,
|
states,
|
||||||
cultures,
|
cultures,
|
||||||
burgs,
|
burgs,
|
||||||
|
|
@ -143,7 +143,8 @@ export function createPack(grid: IGrid): IPack {
|
||||||
pop: population,
|
pop: population,
|
||||||
culture: cultureIds,
|
culture: cultureIds,
|
||||||
burg: burgIds,
|
burg: burgIds,
|
||||||
state: stateIds
|
state: stateIds,
|
||||||
|
route: cellRoutes
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -191,11 +192,12 @@ export function createPack(grid: IGrid): IPack {
|
||||||
// province
|
// province
|
||||||
},
|
},
|
||||||
features: mergedFeatures,
|
features: mergedFeatures,
|
||||||
rivers: rawRivers, // "name" | "basin" | "type"
|
// rivers: rawRivers, // "name" | "basin" | "type"
|
||||||
cultures,
|
cultures,
|
||||||
states,
|
states,
|
||||||
burgs,
|
burgs,
|
||||||
routes
|
routes,
|
||||||
|
religions
|
||||||
};
|
};
|
||||||
|
|
||||||
return pack;
|
return pack;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import FlatQueue from "flatqueue";
|
import FlatQueue from "flatqueue";
|
||||||
|
|
||||||
|
import {ROUTES} from "config/generation";
|
||||||
import {getInputNumber} from "utils/nodeUtils";
|
import {getInputNumber} from "utils/nodeUtils";
|
||||||
import {gauss} from "utils/probabilityUtils";
|
import {gauss} from "utils/probabilityUtils";
|
||||||
import {isReligion} from "utils/typeUtils";
|
import {isReligion} from "utils/typeUtils";
|
||||||
|
|
||||||
type TReligionData = Pick<IReligion, "i" | "type" | "center" | "culture" | "expansion" | "expansionism">;
|
type TReligionData = Pick<IReligion, "i" | "type" | "center" | "culture" | "expansion" | "expansionism">;
|
||||||
type TCellsData = Pick<IPack["cells"], "i" | "c" | "culture">;
|
type TCellsData = Pick<IPack["cells"], "i" | "c" | "biome" | "culture" | "state" | "route">;
|
||||||
|
|
||||||
export function expandReligions(religions: TReligionData[], cells: TCellsData) {
|
export function expandReligions(religions: TReligionData[], cells: TCellsData) {
|
||||||
const religionIds = spreadFolkReligions(religions, cells);
|
const religionIds = spreadFolkReligions(religions, cells);
|
||||||
|
|
@ -15,6 +17,10 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) {
|
||||||
const neutralInput = getInputNumber("neutralInput");
|
const neutralInput = getInputNumber("neutralInput");
|
||||||
const maxExpansionCost = (cells.i.length / 25) * gauss(1, 0.3, 0.2, 2, 2) * 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) {
|
for (const religion of religions) {
|
||||||
if (!isReligion(religion as IReligion) || (religion as IReligion).type === "Folk") continue;
|
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);
|
queue.push({cellId, religionId}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const religionsMap = new Map<number, TReligionData>(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;
|
return religionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {specifyReligions} from "./specifyReligions";
|
||||||
|
|
||||||
type TCellsData = Pick<
|
type TCellsData = Pick<
|
||||||
IPack["cells"],
|
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({
|
export function generateReligions({
|
||||||
|
|
@ -29,7 +29,7 @@ export function generateReligions({
|
||||||
cultures,
|
cultures,
|
||||||
states,
|
states,
|
||||||
burgs,
|
burgs,
|
||||||
pick(cells, "i", "c", "culture", "burg", "state")
|
pick(cells, "i", "c", "biome", "culture", "burg", "state", "route")
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(religions);
|
console.log(religions);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function specifyReligions(
|
||||||
cultures: TCultures,
|
cultures: TCultures,
|
||||||
states: TStates,
|
states: TStates,
|
||||||
burgs: TBurgs,
|
burgs: TBurgs,
|
||||||
cells: Pick<IPack["cells"], "i" | "c" | "culture" | "burg" | "state">
|
cells: Pick<IPack["cells"], "i" | "c" | "biome" | "culture" | "burg" | "state" | "route">
|
||||||
): {religions: TReligions; religionIds: Uint16Array} {
|
): {religions: TReligions; religionIds: Uint16Array} {
|
||||||
const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => {
|
const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => {
|
||||||
const supreme = getDeityName(cultures, cultureId);
|
const supreme = getDeityName(cultures, cultureId);
|
||||||
|
|
|
||||||
8
src/types/common.d.ts
vendored
8
src/types/common.d.ts
vendored
|
|
@ -2,20 +2,22 @@ type Logical = number & (1 | 0); // data type for logical numbers
|
||||||
|
|
||||||
type UnknownObject = {[key: string]: unknown};
|
type UnknownObject = {[key: string]: unknown};
|
||||||
|
|
||||||
// extract element from array
|
|
||||||
type Entry<T> = T[number];
|
|
||||||
|
|
||||||
type noop = () => void;
|
type noop = () => void;
|
||||||
|
|
||||||
interface Dict<T> {
|
interface Dict<T> {
|
||||||
[key: string]: T;
|
[key: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extract element from array
|
||||||
|
type Entry<T> = T[number];
|
||||||
|
|
||||||
// element of Object.entries
|
// element of Object.entries
|
||||||
type ObjectEntry<T> = [string, T];
|
type ObjectEntry<T> = [string, T];
|
||||||
|
|
||||||
type UintArray = Uint8Array | Uint16Array | Uint32Array;
|
type UintArray = Uint8Array | Uint16Array | Uint32Array;
|
||||||
type IntArray = Int8Array | Int16Array | Int32Array;
|
type IntArray = Int8Array | Int16Array | Int32Array;
|
||||||
|
type FloatArray = Float32Array | Float64Array;
|
||||||
|
type TypedArray = UintArray | IntArray | FloatArray;
|
||||||
|
|
||||||
type RGB = `rgb(${number}, ${number}, ${number})`;
|
type RGB = `rgb(${number}, ${number}, ${number})`;
|
||||||
type Hex = `#${string}`;
|
type Hex = `#${string}`;
|
||||||
|
|
|
||||||
28
src/types/pack/pack.d.ts
vendored
28
src/types/pack/pack.d.ts
vendored
|
|
@ -3,9 +3,9 @@ interface IPack extends IGraph {
|
||||||
features: TPackFeatures;
|
features: TPackFeatures;
|
||||||
states: TStates;
|
states: TStates;
|
||||||
cultures: TCultures;
|
cultures: TCultures;
|
||||||
provinces: IProvince[];
|
provinces: TProvinces;
|
||||||
burgs: TBurgs;
|
burgs: TBurgs;
|
||||||
rivers: IRiver[];
|
rivers: TRivers;
|
||||||
religions: TReligions;
|
religions: TReligions;
|
||||||
routes: TRoutes;
|
routes: TRoutes;
|
||||||
}
|
}
|
||||||
|
|
@ -38,27 +38,3 @@ interface IPackBase extends IGraph {
|
||||||
cells: IGraphCells & Partial<IPackCells>;
|
cells: IGraphCells & Partial<IPackCells>;
|
||||||
features?: TPackFeatures;
|
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[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
8
src/types/pack/provinces.d.ts
vendored
Normal file
8
src/types/pack/provinces.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
interface IProvince {
|
||||||
|
i: number;
|
||||||
|
name: string;
|
||||||
|
fullName: string;
|
||||||
|
removed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TProvinces = IProvince[];
|
||||||
18
src/types/pack/rivers.d.ts
vendored
Normal file
18
src/types/pack/rivers.d.ts
vendored
Normal file
|
|
@ -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[];
|
||||||
|
|
@ -15,8 +15,9 @@ const cardinal12: Hex[] = [
|
||||||
"#eb8de7"
|
"#eb8de7"
|
||||||
];
|
];
|
||||||
|
|
||||||
type ColorScheme = d3.ScaleSequential<string>;
|
export type TColorScheme = "default" | "bright" | "light" | "green" | "rainbow" | "monochrome";
|
||||||
const colorSchemeMap: Dict<ColorScheme> = {
|
const colorSchemeMap: {[key in TColorScheme]: d3.ScaleSequential<string>} = {
|
||||||
|
default: d3.scaleSequential(d3.interpolateSpectral),
|
||||||
bright: d3.scaleSequential(d3.interpolateSpectral),
|
bright: d3.scaleSequential(d3.interpolateSpectral),
|
||||||
light: d3.scaleSequential(d3.interpolateRdYlGn),
|
light: d3.scaleSequential(d3.interpolateRdYlGn),
|
||||||
green: d3.scaleSequential(d3.interpolateGreens),
|
green: d3.scaleSequential(d3.interpolateGreens),
|
||||||
|
|
@ -27,7 +28,7 @@ const colorSchemeMap: Dict<ColorScheme> = {
|
||||||
export function getColors(number: number) {
|
export function getColors(number: number) {
|
||||||
if (number <= cardinal12.length) return d3.shuffle(cardinal12.slice(0, 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 => {
|
const colors = d3.range(number).map(index => {
|
||||||
if (index < 12) return cardinal12[index];
|
if (index < 12) return cardinal12[index];
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ export function getColors(number: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomColor(): Hex {
|
export function getRandomColor(): Hex {
|
||||||
const scheme = colorSchemeMap.bright;
|
const scheme = colorSchemeMap.default;
|
||||||
const rgb = scheme(Math.random())!;
|
const rgb = scheme(Math.random())!;
|
||||||
return d3.color(rgb)?.formatHex() as Hex;
|
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();
|
return d3.color(mixedColor)!.brighter(bright).hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorScheme(schemeName: string) {
|
export function getColorScheme(schemeName: TColorScheme) {
|
||||||
return colorSchemeMap[schemeName] || colorSchemeMap.bright;
|
return colorSchemeMap[schemeName] || colorSchemeMap.bright;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// utils to be used for debugging (not in PROD)
|
// utils to be used for debugging (not in PROD)
|
||||||
|
|
||||||
|
import {getColorScheme, TColorScheme} from "./colorUtils";
|
||||||
import {getNormal} from "./lineUtils";
|
import {getNormal} from "./lineUtils";
|
||||||
|
|
||||||
export function drawPoint([x, y]: TPoint, {radius = 1, color = "red"} = {}) {
|
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")
|
.attr("stroke", "none")
|
||||||
.text(text);
|
.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);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue