mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51: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 {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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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) {
|
||||
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<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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export function specifyReligions(
|
|||
cultures: TCultures,
|
||||
states: TStates,
|
||||
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} {
|
||||
const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => {
|
||||
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};
|
||||
|
||||
// extract element from array
|
||||
type Entry<T> = T[number];
|
||||
|
||||
type noop = () => void;
|
||||
|
||||
interface Dict<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
// extract element from array
|
||||
type Entry<T> = T[number];
|
||||
|
||||
// element of Object.entries
|
||||
type ObjectEntry<T> = [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}`;
|
||||
|
|
|
|||
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;
|
||||
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<IPackCells>;
|
||||
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"
|
||||
];
|
||||
|
||||
type ColorScheme = d3.ScaleSequential<string>;
|
||||
const colorSchemeMap: Dict<ColorScheme> = {
|
||||
export type TColorScheme = "default" | "bright" | "light" | "green" | "rainbow" | "monochrome";
|
||||
const colorSchemeMap: {[key in TColorScheme]: d3.ScaleSequential<string>} = {
|
||||
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<ColorScheme> = {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue