From 70e8296c48170fa125854b7bc29781334bcde190 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 24 Aug 2022 00:09:40 +0300 Subject: [PATCH 01/12] refactor: religions start --- src/config/religionsData.ts | 339 ++++++++++++++++++ .../generation/pack/generateReligions.ts | 70 ++++ src/scripts/generation/pack/generateRoutes.ts | 2 +- src/scripts/generation/pack/pack.ts | 17 +- src/types/pack/pack.d.ts | 13 +- src/types/pack/religions.d.ts | 19 + src/utils/probabilityUtils.ts | 7 +- 7 files changed, 449 insertions(+), 18 deletions(-) create mode 100644 src/config/religionsData.ts create mode 100644 src/scripts/generation/pack/generateReligions.ts create mode 100644 src/types/pack/religions.d.ts diff --git a/src/config/religionsData.ts b/src/config/religionsData.ts new file mode 100644 index 00000000..8efaed90 --- /dev/null +++ b/src/config/religionsData.ts @@ -0,0 +1,339 @@ +// name generation approach and relative chance to be selected +const approach = { + Number: 1, + Being: 3, + Adjective: 5, + "Color + Animal": 5, + "Adjective + Animal": 5, + "Adjective + Being": 5, + "Adjective + Genitive": 1, + "Color + Being": 3, + "Color + Genitive": 3, + "Being + of + Genitive": 2, + "Being + of the + Genitive": 1, + "Animal + of + Genitive": 1, + "Adjective + Being + of + Genitive": 2, + "Adjective + Animal + of + Genitive": 2 +}; + +// turn weighted data into a flat array +const approaches: string[] = Object.entries(approach) + .map(([approach, weight]) => new Array(weight).fill(approach)) + .flat(); + +const base = { + number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"], + being: [ + "Ancestor", + "Ancient", + "Brother", + "Chief", + "Council", + "Creator", + "Deity", + "Elder", + "Father", + "Forebear", + "Forefather", + "Giver", + "God", + "Goddess", + "Guardian", + "Lady", + "Lord", + "Maker", + "Master", + "Mother", + "Numen", + "Overlord", + "Reaper", + "Ruler", + "Sister", + "Spirit", + "Virgin" + ], + animal: [ + "Antelope", + "Ape", + "Badger", + "Basilisk", + "Bear", + "Beaver", + "Bison", + "Boar", + "Buffalo", + "Camel", + "Cat", + "Centaur", + "Chimera", + "Cobra", + "Crane", + "Crocodile", + "Crow", + "Cyclope", + "Deer", + "Dog", + "Dragon", + "Eagle", + "Elk", + "Falcon", + "Fox", + "Goat", + "Goose", + "Hare", + "Hawk", + "Heron", + "Horse", + "Hound", + "Hyena", + "Ibis", + "Jackal", + "Jaguar", + "Kraken", + "Lark", + "Leopard", + "Lion", + "Mantis", + "Marten", + "Moose", + "Mule", + "Narwhal", + "Owl", + "Ox", + "Panther", + "Pegasus", + "Phoenix", + "Rat", + "Raven", + "Rook", + "Scorpion", + "Serpent", + "Shark", + "Sheep", + "Snake", + "Sphinx", + "Spider", + "Swan", + "Tiger", + "Turtle", + "Unicorn", + "Viper", + "Vulture", + "Walrus", + "Wolf", + "Wolverine", + "Worm", + "Wyvern" + ], + adjective: [ + "Aggressive", + "Almighty", + "Ancient", + "Beautiful", + "Benevolent", + "Big", + "Blind", + "Blond", + "Bloody", + "Brave", + "Broken", + "Brutal", + "Burning", + "Calm", + "Cheerful", + "Crazy", + "Cruel", + "Dead", + "Deadly", + "Devastating", + "Distant", + "Disturbing", + "Divine", + "Dying", + "Eternal", + "Evil", + "Explicit", + "Fair", + "Far", + "Fat", + "Fatal", + "Favorable", + "Flying", + "Friendly", + "Frozen", + "Giant", + "Good", + "Grateful", + "Great", + "Happy", + "High", + "Holy", + "Honest", + "Huge", + "Hungry", + "Immutable", + "Infallible", + "Inherent", + "Last", + "Latter", + "Lost", + "Loud", + "Lucky", + "Mad", + "Magical", + "Main", + "Major", + "Marine", + "Naval", + "New", + "Old", + "Patient", + "Peaceful", + "Pregnant", + "Prime", + "Proud", + "Pure", + "Sacred", + "Sad", + "Scary", + "Secret", + "Selected", + "Severe", + "Silent", + "Sleeping", + "Slumbering", + "Strong", + "Sunny", + "Superior", + "Sustainable", + "Troubled", + "Unhappy", + "Unknown", + "Waking", + "Wild", + "Wise", + "Worried", + "Young" + ], + genitive: [ + "Cold", + "Day", + "Death", + "Doom", + "Fate", + "Fire", + "Fog", + "Frost", + "Gates", + "Heaven", + "Home", + "Ice", + "Justice", + "Life", + "Light", + "Lightning", + "Love", + "Nature", + "Night", + "Pain", + "Snow", + "Springs", + "Summer", + "Thunder", + "Time", + "Victory", + "War", + "Winter" + ], + theGenitive: [ + "Abyss", + "Blood", + "Dawn", + "Earth", + "East", + "Eclipse", + "Fall", + "Harvest", + "Moon", + "North", + "Peak", + "Rainbow", + "Sea", + "Sky", + "South", + "Stars", + "Storm", + "Sun", + "Tree", + "Underworld", + "West", + "Wild", + "Word", + "World" + ], + color: [ + "Amber", + "Black", + "Blue", + "Bright", + "Brown", + "Dark", + "Golden", + "Green", + "Grey", + "Light", + "Orange", + "Pink", + "Purple", + "Red", + "White", + "Yellow" + ] +}; + +const forms = { + Folk: {Shamanism: 2, Animism: 2, "Ancestor worship": 1, Polytheism: 2}, + Organized: {Polytheism: 5, Dualism: 1, Monotheism: 4, "Non-theism": 1}, + Cult: {Cult: 1, "Dark Cult": 1}, + Heresy: {Heresy: 1} +}; + +const methods = { + "Random + type": 3, + "Random + ism": 1, + "Supreme + ism": 5, + "Faith of + Supreme": 5, + "Place + ism": 1, + "Culture + ism": 2, + "Place + ian + type": 6, + "Culture + type": 4 +}; + +export const types = { + Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1}, + Animism: {Spirits: 1, Beliefs: 1}, + "Ancestor worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2}, + Polytheism: {Deities: 3, Faith: 1, Gods: 1, Pantheon: 1}, + + Dualism: {Religion: 3, Faith: 1, Cult: 1}, + Monotheism: {Religion: 1, Church: 1}, + "Non-theism": {Beliefs: 3, Spirits: 1}, + + Cult: {Cult: 4, Sect: 4, Arcanum: 1, Coterie: 1, Order: 1, Worship: 1}, + "Dark Cult": {Cult: 2, Sect: 2, Blasphemy: 1, Circle: 1, Coven: 1, Idols: 1, Occultism: 1}, + + Heresy: { + Heresy: 3, + Sect: 2, + Apostates: 1, + Brotherhood: 1, + Circle: 1, + Dissent: 1, + Dissenters: 1, + Iconoclasm: 1, + Schism: 1, + Society: 1 + } +}; + +export const religionsData = {approaches, base, forms, methods, types}; diff --git a/src/scripts/generation/pack/generateReligions.ts b/src/scripts/generation/pack/generateReligions.ts new file mode 100644 index 00000000..7fd1414c --- /dev/null +++ b/src/scripts/generation/pack/generateReligions.ts @@ -0,0 +1,70 @@ +import {TIME} from "config/logging"; +import {religionsData} from "config/religionsData"; +import {getMixedColor} from "utils/colorUtils"; +import {ra, rw} from "utils/probabilityUtils"; + +type TCellsData = Pick; + +const {Names} = window; +const {approaches, base, forms, methods, types} = religionsData; + +export function generateReligions(states: TStates, cultures: TCultures, cells: TCellsData) { + TIME && console.time("generateReligions"); + + const religionIds = new Uint16Array(cells.c.length); + + const folkReligions = generateFolkReligions(cultures, cells); + console.log(folkReligions); + + TIME && console.timeEnd("generateReligions"); + return {religionIds}; +} + +function generateFolkReligions(cultures: TCultures, cells: TCellsData) { + const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => + culture.i !== 0 && !(culture as ICulture).removed; + + return cultures.filter(isValidCulture).map((culture, index) => { + const {i: cultureId, name: cultureName, center} = culture; + const id = index + 1; + const form = rw(forms.Folk); + const type: {[key: string]: number} = types[form]; + const name = cultureName + " " + rw(type); + const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); + const color = getMixedColor(culture.color, 0.1, 0); + + return {i: id, name, color, culture: cultureId, type: "Folk", form, deity, center: center, origins: [0]}; + }); +} + +function getDeityName(cultures: TCultures, cultureId: number) { + if (cultureId === undefined) throw "CultureId is undefined"; + + const meaning = generateMeaning(); + + const base = cultures[cultureId].base; + const cultureName = Names.getBase(base); + return cultureName + ", The " + meaning; +} + +function generateMeaning() { + const approach = ra(approaches); + if (approach === "Number") return ra(base.number); + if (approach === "Being") return ra(base.being); + if (approach === "Adjective") return ra(base.adjective); + if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`; + if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`; + if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`; + if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`; + if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`; + if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`; + if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`; + if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Being + of + Genitive") + return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Animal + of + Genitive") + return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`; + + throw "Unknown generation approach"; +} diff --git a/src/scripts/generation/pack/generateRoutes.ts b/src/scripts/generation/pack/generateRoutes.ts index 94ca3adc..d80b6ad1 100644 --- a/src/scripts/generation/pack/generateRoutes.ts +++ b/src/scripts/generation/pack/generateRoutes.ts @@ -5,7 +5,7 @@ import {TIME} from "config/logging"; import {ELEVATION, MIN_LAND_HEIGHT, ROUTES} from "config/generation"; import {dist2} from "utils/functionUtils"; -type TCellsData = Pick; +type TCellsData = Pick; export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData) { const cellRoutes = new Uint8Array(cells.h.length); diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 0b42466d..b6fd80d7 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -13,6 +13,7 @@ import {generateCultures, expandCultures} from "./cultures"; import {generateRivers} from "./rivers"; import {generateBurgsAndStates} from "./burgsAndStates/generateBurgsAndStates"; import {generateRoutes} from "./generateRoutes"; +import {generateReligions} from "./generateReligions"; const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD; const {Biomes} = window; @@ -124,7 +125,16 @@ export function createPack(grid: IGrid): IPack { h: heights, t: distanceField, biome, - state: stateIds, + burg: burgIds + }); + + const {religionIds} = generateReligions(states, cultures, { + c: cells.c, + p: cells.p, + g: cells.g, + h: heights, + t: distanceField, + biome, burg: burgIds }); @@ -167,8 +177,9 @@ export function createPack(grid: IGrid): IPack { culture: cultureIds, burg: burgIds, state: stateIds, - route: cellRoutes - // religion, province + route: cellRoutes, + religion: religionIds + // province }, features: mergedFeatures, rivers: rawRivers, // "name" | "basin" | "type" diff --git a/src/types/pack/pack.d.ts b/src/types/pack/pack.d.ts index d1a3302a..6c205904 100644 --- a/src/types/pack/pack.d.ts +++ b/src/types/pack/pack.d.ts @@ -6,7 +6,7 @@ interface IPack extends IGraph { provinces: IProvince[]; burgs: TBurgs; rivers: IRiver[]; - religions: IReligion[]; + religions: TReligions; routes: TRoutes; } @@ -23,9 +23,9 @@ interface IPackCells { conf: Uint16Array; // conluence, defined by defineRivers() in river-generator.ts biome: Uint8Array; area: UintArray; - state: UintArray; + state: Uint16Array; culture: Uint16Array; - religion: UintArray; + religion: Uint16Array; province: UintArray; burg: UintArray; haven: UintArray; @@ -46,13 +46,6 @@ interface IProvince { removed?: boolean; } -interface IReligion { - i: number; - name: string; - type: "Folk" | "Orgamized" | "Cult" | "Heresy"; - removed?: boolean; -} - interface IRiver { i: number; name: string; diff --git a/src/types/pack/religions.d.ts b/src/types/pack/religions.d.ts new file mode 100644 index 00000000..215395a1 --- /dev/null +++ b/src/types/pack/religions.d.ts @@ -0,0 +1,19 @@ +interface IReligion { + i: number; + name: string; + type: "Folk" | "Orgamized" | "Cult" | "Heresy"; + color: string; + culture: number; + form: any; + deity: string | null; + center: number; + origins: number[]; + removed?: boolean; +} + +type TNoReligion = { + i: 0; + name: string; +}; + +type TReligions = [TNoReligion, ...IReligion[]]; diff --git a/src/utils/probabilityUtils.ts b/src/utils/probabilityUtils.ts index 08695bdc..95cffd7d 100644 --- a/src/utils/probabilityUtils.ts +++ b/src/utils/probabilityUtils.ts @@ -42,10 +42,9 @@ export function ra(array: T[]) { } // return random value from weighted array -export function rw(object: {[key: string]: number}) { - const weightedArray = Object.entries(object) - .map(([choise, weight]) => new Array(weight).fill(choise)) - .flat(); +export function rw(object: {[key in T]: number}) { + const entries = Object.entries(object); + const weightedArray: T[] = entries.map(([choise, weight]) => new Array(weight).fill(choise)).flat(); return ra(weightedArray); } From cce374da247237c7aad8ab1b83e81ed9525a4b5e Mon Sep 17 00:00:00 2001 From: max Date: Thu, 25 Aug 2022 00:01:42 +0300 Subject: [PATCH 02/12] refactor: generate organized religions --- .../pack/burgsAndStates/createTowns.ts | 4 +- .../pack/burgsAndStates/expandStates.ts | 7 +- src/scripts/generation/pack/cultures.ts | 7 +- .../generation/pack/generateReligions.ts | 70 ------- src/scripts/generation/pack/pack.ts | 28 ++- .../pack/religions/generateReligionName.ts | 112 ++++++++++ .../pack/religions/generateReligions.ts | 198 ++++++++++++++++++ src/utils/typeUtils.ts | 6 + 8 files changed, 342 insertions(+), 90 deletions(-) delete mode 100644 src/scripts/generation/pack/generateReligions.ts create mode 100644 src/scripts/generation/pack/religions/generateReligionName.ts create mode 100644 src/scripts/generation/pack/religions/generateReligions.ts create mode 100644 src/utils/typeUtils.ts diff --git a/src/scripts/generation/pack/burgsAndStates/createTowns.ts b/src/scripts/generation/pack/burgsAndStates/createTowns.ts index c5e28243..a6b6dbf9 100644 --- a/src/scripts/generation/pack/burgsAndStates/createTowns.ts +++ b/src/scripts/generation/pack/burgsAndStates/createTowns.ts @@ -53,13 +53,13 @@ function placeTowns(townsNumber: number, scoredCellIds: UintArray, points: TPoin const townCells: number[] = []; const townsQuadtree = d3.quadtree(); - const randomizeScaping = (spacing: number) => spacing * gauss(1, 0.3, 0.2, 2, 2); + const randomizeSpacing = (spacing: number) => spacing * gauss(1, 0.3, 0.2, 2, 2); for (const cellId of scoredCellIds) { const [x, y] = points[cellId]; // randomize min spacing a bit to make placement not that uniform - const currentSpacing = randomizeScaping(spacing); + const currentSpacing = randomizeSpacing(spacing); if (townsQuadtree.find(x, y, currentSpacing) === undefined) { townCells.push(cellId); diff --git a/src/scripts/generation/pack/burgsAndStates/expandStates.ts b/src/scripts/generation/pack/burgsAndStates/expandStates.ts index 85630262..98f1237d 100644 --- a/src/scripts/generation/pack/burgsAndStates/expandStates.ts +++ b/src/scripts/generation/pack/burgsAndStates/expandStates.ts @@ -6,6 +6,7 @@ import {minmax} from "utils/numberUtils"; import type {createCapitals} from "./createCapitals"; import type {createStates} from "./createStates"; import {ELEVATION, FOREST_BIOMES, MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation"; +import {isState} from "utils/typeUtils"; type TCapitals = ReturnType; type TStates = ReturnType; @@ -104,13 +105,9 @@ export function expandStates( return normalizeStates(stateIds, capitalCells, cells.c, cells.h); - function isNeutrals(state: Entry): state is TNeutrals { - return state.i === 0; - } - function getState(stateId: number) { const state = states[stateId]; - if (isNeutrals(state)) throw new Error("Neutrals cannot expand"); + if (!isState(state)) throw new Error("Neutrals cannot expand"); return state; } diff --git a/src/scripts/generation/pack/cultures.ts b/src/scripts/generation/pack/cultures.ts index d26c197f..bf598782 100644 --- a/src/scripts/generation/pack/cultures.ts +++ b/src/scripts/generation/pack/cultures.ts @@ -18,6 +18,7 @@ import {minmax, rn} from "utils/numberUtils"; import {biased, P, rand} from "utils/probabilityUtils"; import {byId} from "utils/shorthands"; import {defaultNameBases} from "config/namebases"; +import {isCulture} from "utils/typeUtils"; const {COA} = window; @@ -284,9 +285,7 @@ export const expandCultures = function ( const cultureIds = new Uint16Array(cells.h.length); // cell cultures const queue = new FlatQueue<{cellId: number; cultureId: number}>(); - const isWilderness = (culture: ICulture | TWilderness): culture is TWilderness => culture.i === 0; - cultures.forEach(culture => { - if (isWilderness(culture) || culture.removed) return; + cultures.filter(isCulture).forEach(culture => { queue.push({cellId: culture.center, cultureId: culture.i}, 0); }); @@ -323,7 +322,7 @@ export const expandCultures = function ( function getCulture(cultureId: number) { const culture = cultures[cultureId]; - if (isWilderness(culture)) throw new Error("Wilderness culture cannot expand"); + if (!isCulture(culture)) throw new Error("Wilderness cannot expand"); return culture; } diff --git a/src/scripts/generation/pack/generateReligions.ts b/src/scripts/generation/pack/generateReligions.ts deleted file mode 100644 index 7fd1414c..00000000 --- a/src/scripts/generation/pack/generateReligions.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {TIME} from "config/logging"; -import {religionsData} from "config/religionsData"; -import {getMixedColor} from "utils/colorUtils"; -import {ra, rw} from "utils/probabilityUtils"; - -type TCellsData = Pick; - -const {Names} = window; -const {approaches, base, forms, methods, types} = religionsData; - -export function generateReligions(states: TStates, cultures: TCultures, cells: TCellsData) { - TIME && console.time("generateReligions"); - - const religionIds = new Uint16Array(cells.c.length); - - const folkReligions = generateFolkReligions(cultures, cells); - console.log(folkReligions); - - TIME && console.timeEnd("generateReligions"); - return {religionIds}; -} - -function generateFolkReligions(cultures: TCultures, cells: TCellsData) { - const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => - culture.i !== 0 && !(culture as ICulture).removed; - - return cultures.filter(isValidCulture).map((culture, index) => { - const {i: cultureId, name: cultureName, center} = culture; - const id = index + 1; - const form = rw(forms.Folk); - const type: {[key: string]: number} = types[form]; - const name = cultureName + " " + rw(type); - const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); - const color = getMixedColor(culture.color, 0.1, 0); - - return {i: id, name, color, culture: cultureId, type: "Folk", form, deity, center: center, origins: [0]}; - }); -} - -function getDeityName(cultures: TCultures, cultureId: number) { - if (cultureId === undefined) throw "CultureId is undefined"; - - const meaning = generateMeaning(); - - const base = cultures[cultureId].base; - const cultureName = Names.getBase(base); - return cultureName + ", The " + meaning; -} - -function generateMeaning() { - const approach = ra(approaches); - if (approach === "Number") return ra(base.number); - if (approach === "Being") return ra(base.being); - if (approach === "Adjective") return ra(base.adjective); - if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`; - if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`; - if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`; - if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`; - if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`; - if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`; - if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`; - if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`; - if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`; - if (approach === "Adjective + Being + of + Genitive") - return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`; - if (approach === "Adjective + Animal + of + Genitive") - return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`; - - throw "Unknown generation approach"; -} diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index b6fd80d7..1746c41f 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -13,7 +13,7 @@ import {generateCultures, expandCultures} from "./cultures"; import {generateRivers} from "./rivers"; import {generateBurgsAndStates} from "./burgsAndStates/generateBurgsAndStates"; import {generateRoutes} from "./generateRoutes"; -import {generateReligions} from "./generateReligions"; +import {generateReligions} from "./religions/generateReligions"; const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD; const {Biomes} = window; @@ -128,14 +128,24 @@ export function createPack(grid: IGrid): IPack { burg: burgIds }); - const {religionIds} = generateReligions(states, cultures, { - c: cells.c, - p: cells.p, - g: cells.g, - h: heights, - t: distanceField, - biome, - burg: burgIds + const {religionIds} = generateReligions({ + states, + cultures, + burgs, + cultureIds, + stateIds, + burgIds, + cells: { + i: cells.i, + c: cells.c, + p: cells.p, + g: cells.g, + h: heights, + t: distanceField, + biome, + pop: population, + burg: burgIds + } }); // Religions.generate(); diff --git a/src/scripts/generation/pack/religions/generateReligionName.ts b/src/scripts/generation/pack/religions/generateReligionName.ts new file mode 100644 index 00000000..5abe3e0d --- /dev/null +++ b/src/scripts/generation/pack/religions/generateReligionName.ts @@ -0,0 +1,112 @@ +import {religionsData} from "config/religionsData"; +import {trimVowels, getAdjective} from "utils/languageUtils"; +import {rw, ra} from "utils/probabilityUtils"; +import {isCulture, isState} from "utils/typeUtils"; + +const {Names} = window; +const {methods, types} = religionsData; + +interface IContext { + cultureId: number; + stateId: number; + burgId: number; + cultures: TCultures; + states: TStates; + burgs: TBurgs; + center: number; + form: keyof typeof types; + deity: string; +} + +const context = { + data: {} as IContext, + + // data setter + set current(data: IContext) { + this.data = data; + }, + + // data getters + get culture() { + return this.data.cultures[this.data.cultureId]; + }, + + get state() { + return this.data.states[this.data.stateId]; + }, + + get burg() { + return this.data.burgs[this.data.burgId]; + }, + + get form() { + return this.data.form; + }, + + get deity() { + return this.data.deity; + }, + + // generation methods + get random() { + return Names.getBase(this.culture.base); + }, + + get type() { + return rw(types[this.form] as {[key: string]: number}); + }, + + get supreme() { + return this.deity.split(/[ ,]+/)[0]; + }, + + get cultureName() { + return this.culture.name; + }, + + get place() { + const base = this.burg.name || this.state.name; + return trimVowels(base.split(/[ ,]+/)[0]); + } +}; + +const nameMethodsMap = { + "Random + type": {getName: () => `${context.random} ${context.type}`, expansion: "global"}, + "Random + ism": {getName: () => `${trimVowels(context.random)}ism`, expansion: "global"}, + "Supreme + ism": {getName: () => `${trimVowels(context.supreme)}ism`, expansion: "global"}, + "Faith of + Supreme": { + getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supreme}`, + expansion: "global" + }, + "Place + ism": {getName: () => `${context.place}ism`, expansion: "state"}, + "Culture + ism": {getName: () => `${trimVowels(context.cultureName)}ism`, expansion: "culture"}, + "Place + ian + type": { + getName: () => `${getAdjective(context.place)} ${context.type}`, + expansion: "state" + }, + "Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"} +}; + +export function generateReligionName(data: IContext) { + context.current = data; + const {stateId, cultureId, states, cultures, center} = data; + + const method = nameMethodsMap[rw(methods)]; + const name = method.getName(); + + let expansion = method.expansion; + if (expansion === "state" && !stateId) expansion = "global"; + if (expansion === "culture" && !cultureId) expansion = "global"; + + if (expansion === "state" && Math.random() > 0.5) { + const state = states[stateId]; + if (isState(state)) return {name, expansion, center: state.center}; + } + + if (expansion === "culture" && Math.random() > 0.5) { + const culture = cultures[cultureId]; + if (isCulture(culture)) return {name, expansion, center: culture.center}; + } + + return {name, expansion, center}; +} diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts new file mode 100644 index 00000000..8fea9270 --- /dev/null +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -0,0 +1,198 @@ +import * as d3 from "d3"; + +import {TIME, WARN} from "config/logging"; +import {religionsData} from "config/religionsData"; +import {unique} from "utils/arrayUtils"; +import {getMixedColor, getRandomColor} from "utils/colorUtils"; +import {findAll} from "utils/graphUtils"; +import {getInputNumber} from "utils/nodeUtils"; +import {ra, rand, rw} from "utils/probabilityUtils"; +import {isBurg} from "utils/typeUtils"; +import {generateReligionName} from "./generateReligionName"; + +const {Names} = window; +const {approaches, base, forms, types} = religionsData; + +type TCellsData = Pick; + +export function generateReligions({ + states, + cultures, + burgs, + cultureIds, + stateIds, + burgIds, + cells +}: { + states: TStates; + cultures: TCultures; + burgs: TBurgs; + cultureIds: Uint16Array; + stateIds: Uint16Array; + burgIds: Uint16Array; + cells: TCellsData; +}) { + TIME && console.time("generateReligions"); + + const religionIds = new Uint16Array(cells.c.length); + + const folkReligions = generateFolkReligions(cultures); + const basicReligions = generateOrganizedReligionsAndCults( + states, + cultures, + burgs, + cultureIds, + stateIds, + burgIds, + folkReligions, + { + i: cells.i, + p: cells.p, + pop: cells.pop + } + ); + + console.log(folkReligions, basicReligions); + + TIME && console.timeEnd("generateReligions"); + return {religionIds}; +} + +function generateFolkReligions(cultures: TCultures) { + const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => + culture.i !== 0 && !(culture as ICulture).removed; + + return cultures.filter(isValidCulture).map(culture => { + const {i: cultureId, name: cultureName, center} = culture; + const form = rw(forms.Folk); + const type: {[key: string]: number} = types[form]; + const name = cultureName + " " + rw(type); + const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); + const color = getMixedColor(culture.color, 0.1, 0); + + return {name, type: "Folk", form, deity, color, culture: cultureId, center, origins: [0]}; + }); +} + +function generateOrganizedReligionsAndCults( + states: TStates, + cultures: TCultures, + burgs: TBurgs, + cultureIds: Uint16Array, + stateIds: Uint16Array, + burgIds: Uint16Array, + folkReligions: ReturnType, + cells: Pick +) { + const religionsNumber = getInputNumber("religionsInput"); + if (religionsNumber === 0) return []; + + const cultsNumber = Math.floor((rand(1, 4) / 10) * religionsNumber); // 10-40% + const organizedNumber = religionsNumber - cultsNumber; + + const canditateCells = getCandidateCells(); + const religionCells = placeReligions(); + + return religionCells.map((cellId, index) => { + const cultureId = cultureIds[cellId]; + const stateId = stateIds[cellId]; + const burgId = burgIds[cellId]; + + const type = index < organizedNumber ? "Organized" : "Cult"; + + const form = rw(forms[type] as {[key in keyof typeof types]: number}); + const deityName = getDeityName(cultures, cultureId); + const deity = form === "Non-theism" ? null : deityName; + + const {name, expansion, center} = generateReligionName({ + cultureId, + stateId, + burgId, + cultures, + states, + burgs, + center: cellId, + form, + deity: deityName + }); + + const folkReligion = folkReligions.find(({culture}) => culture === cultureId); + const baseColor = folkReligion?.color || getRandomColor(); + const color = getMixedColor(baseColor, 0.3, 0); + + return {name, type, form, deity, color, culture: cultureId, center, expansion}; + }); + + function placeReligions() { + const religionCells = []; + const religionsTree = d3.quadtree(); + + // initial min distance between religions + let spacing = (graphWidth + graphHeight) / 4 / religionsNumber; + + for (const cellId of canditateCells) { + const [x, y] = cells.p[cellId]; + + if (religionsTree.find(x, y, spacing) === undefined) { + religionCells.push(cellId); + religionsTree.add([x, y]); + + if (religionCells.length === religionsNumber) return religionCells; + } + } + + WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`); + return religionCells; + } + + function getCandidateCells() { + const validBurgs = burgs.filter(isBurg); + + if (validBurgs.length >= religionsNumber) + return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell); + + return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]); + } +} + +function getDeityName(cultures: TCultures, cultureId: number) { + if (cultureId === undefined) throw "CultureId is undefined"; + + const meaning = generateMeaning(); + + const base = cultures[cultureId].base; + const cultureName = Names.getBase(base); + return cultureName + ", The " + meaning; +} + +function generateMeaning() { + const approach = ra(approaches); + if (approach === "Number") return ra(base.number); + if (approach === "Being") return ra(base.being); + if (approach === "Adjective") return ra(base.adjective); + if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`; + if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`; + if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`; + if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`; + if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`; + if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`; + if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`; + if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Being + of + Genitive") + return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Animal + of + Genitive") + return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`; + + throw "Unknown generation approach"; +} + +function getReligionsInRadius( + religionIds: Uint16Array, + {x, y, r, max}: {x: number; y: number; r: number; max: number} +) { + if (max === 0) return [0]; + const cellsInRadius = findAll(x, y, r); + const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r)); + return religions.length ? religions.slice(0, max) : [0]; +} diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts new file mode 100644 index 00000000..ef1ebf1b --- /dev/null +++ b/src/utils/typeUtils.ts @@ -0,0 +1,6 @@ +export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed; + +export const isCulture = (culture: TWilderness | ICulture): culture is ICulture => + culture.i !== 0 && !(culture as ICulture).removed; + +export const isBurg = (burg: TNoBurg | IBurg): burg is IBurg => burg.i !== 0 && !(burg as IBurg).removed; From 17e96524fbbb3c94878aaa3cd07aa0142453fb7c Mon Sep 17 00:00:00 2001 From: max Date: Thu, 25 Aug 2022 00:20:43 +0300 Subject: [PATCH 03/12] refactor: split to several files --- src/config/religionsData.ts | 2 +- .../pack/religions/generateDeityName.ts | 38 +++++ .../pack/religions/generateFolkReligions.ts | 22 +++ .../generateOrganizedReligionsAndCults.ts | 94 ++++++++++++ .../pack/religions/generateReligions.ts | 144 +----------------- 5 files changed, 158 insertions(+), 142 deletions(-) create mode 100644 src/scripts/generation/pack/religions/generateDeityName.ts create mode 100644 src/scripts/generation/pack/religions/generateFolkReligions.ts create mode 100644 src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts diff --git a/src/config/religionsData.ts b/src/config/religionsData.ts index 8efaed90..c655136a 100644 --- a/src/config/religionsData.ts +++ b/src/config/religionsData.ts @@ -309,7 +309,7 @@ const methods = { "Culture + type": 4 }; -export const types = { +const types = { Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1}, Animism: {Spirits: 1, Beliefs: 1}, "Ancestor worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2}, diff --git a/src/scripts/generation/pack/religions/generateDeityName.ts b/src/scripts/generation/pack/religions/generateDeityName.ts new file mode 100644 index 00000000..f58c7ce2 --- /dev/null +++ b/src/scripts/generation/pack/religions/generateDeityName.ts @@ -0,0 +1,38 @@ +import {religionsData} from "config/religionsData"; +import {ra} from "utils/probabilityUtils"; + +const {Names} = window; + +const {base, approaches} = religionsData; + +export function getDeityName(cultures: TCultures, cultureId: number) { + if (cultureId === undefined) throw "CultureId is undefined"; + + const meaning = generateMeaning(); + + const base = cultures[cultureId].base; + const cultureName = Names.getBase(base); + return cultureName + ", The " + meaning; +} + +function generateMeaning() { + const approach = ra(approaches); + if (approach === "Number") return ra(base.number); + if (approach === "Being") return ra(base.being); + if (approach === "Adjective") return ra(base.adjective); + if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`; + if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`; + if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`; + if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`; + if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`; + if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`; + if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`; + if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Being + of + Genitive") + return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`; + if (approach === "Adjective + Animal + of + Genitive") + return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`; + + throw "Unknown generation approach"; +} diff --git a/src/scripts/generation/pack/religions/generateFolkReligions.ts b/src/scripts/generation/pack/religions/generateFolkReligions.ts new file mode 100644 index 00000000..59d99910 --- /dev/null +++ b/src/scripts/generation/pack/religions/generateFolkReligions.ts @@ -0,0 +1,22 @@ +import {religionsData} from "config/religionsData"; +import {getMixedColor} from "utils/colorUtils"; +import {rw} from "utils/probabilityUtils"; +import {getDeityName} from "./generateDeityName"; + +const {forms, types} = religionsData; + +export function generateFolkReligions(cultures: TCultures) { + const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => + culture.i !== 0 && !(culture as ICulture).removed; + + return cultures.filter(isValidCulture).map(culture => { + const {i: cultureId, name: cultureName, center} = culture; + const form = rw(forms.Folk); + const type: {[key: string]: number} = types[form]; + const name = cultureName + " " + rw(type); + const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); + const color = getMixedColor(culture.color, 0.1, 0); + + return {name, type: "Folk", form, deity, color, culture: cultureId, center, origins: [0]}; + }); +} diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts b/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts new file mode 100644 index 00000000..44e74be9 --- /dev/null +++ b/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts @@ -0,0 +1,94 @@ +import * as d3 from "d3"; + +import {WARN} from "config/logging"; +import {religionsData} from "config/religionsData"; +import {getRandomColor, getMixedColor} from "utils/colorUtils"; +import {getInputNumber} from "utils/nodeUtils"; +import {rand, rw} from "utils/probabilityUtils"; +import {isBurg} from "utils/typeUtils"; +import {getDeityName} from "./generateDeityName"; +import {generateFolkReligions} from "./generateFolkReligions"; +import {generateReligionName} from "./generateReligionName"; + +const {forms, types} = religionsData; + +export function generateOrganizedReligionsAndCults( + states: TStates, + cultures: TCultures, + burgs: TBurgs, + cultureIds: Uint16Array, + stateIds: Uint16Array, + burgIds: Uint16Array, + folkReligions: ReturnType, + cells: Pick +) { + const religionsNumber = getInputNumber("religionsInput"); + if (religionsNumber === 0) return []; + + const cultsNumber = Math.floor((rand(1, 4) / 10) * religionsNumber); // 10-40% + const organizedNumber = religionsNumber - cultsNumber; + + const canditateCells = getCandidateCells(); + const religionCells = placeReligions(); + + return religionCells.map((cellId, index) => { + const cultureId = cultureIds[cellId]; + const stateId = stateIds[cellId]; + const burgId = burgIds[cellId]; + + const type = index < organizedNumber ? "Organized" : "Cult"; + + const form = rw(forms[type] as {[key in keyof typeof types]: number}); + const deityName = getDeityName(cultures, cultureId); + const deity = form === "Non-theism" ? null : deityName; + + const {name, expansion, center} = generateReligionName({ + cultureId, + stateId, + burgId, + cultures, + states, + burgs, + center: cellId, + form, + deity: deityName + }); + + const folkReligion = folkReligions.find(({culture}) => culture === cultureId); + const baseColor = folkReligion?.color || getRandomColor(); + const color = getMixedColor(baseColor, 0.3, 0); + + return {name, type, form, deity, color, culture: cultureId, center, expansion}; + }); + + function placeReligions() { + const religionCells = []; + const religionsTree = d3.quadtree(); + + // initial min distance between religions + let spacing = (graphWidth + graphHeight) / 4 / religionsNumber; + + for (const cellId of canditateCells) { + const [x, y] = cells.p[cellId]; + + if (religionsTree.find(x, y, spacing) === undefined) { + religionCells.push(cellId); + religionsTree.add([x, y]); + + if (religionCells.length === religionsNumber) return religionCells; + } + } + + WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`); + return religionCells; + } + + function getCandidateCells() { + const validBurgs = burgs.filter(isBurg); + + if (validBurgs.length >= religionsNumber) + return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell); + + return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]); + } +} diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 8fea9270..59eb65a1 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -1,17 +1,8 @@ -import * as d3 from "d3"; - -import {TIME, WARN} from "config/logging"; -import {religionsData} from "config/religionsData"; +import {TIME} from "config/logging"; import {unique} from "utils/arrayUtils"; -import {getMixedColor, getRandomColor} from "utils/colorUtils"; import {findAll} from "utils/graphUtils"; -import {getInputNumber} from "utils/nodeUtils"; -import {ra, rand, rw} from "utils/probabilityUtils"; -import {isBurg} from "utils/typeUtils"; -import {generateReligionName} from "./generateReligionName"; - -const {Names} = window; -const {approaches, base, forms, types} = religionsData; +import {generateFolkReligions} from "./generateFolkReligions"; +import {generateOrganizedReligionsAndCults} from "./generateOrganizedReligionsAndCults"; type TCellsData = Pick; @@ -58,135 +49,6 @@ export function generateReligions({ return {religionIds}; } -function generateFolkReligions(cultures: TCultures) { - const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => - culture.i !== 0 && !(culture as ICulture).removed; - - return cultures.filter(isValidCulture).map(culture => { - const {i: cultureId, name: cultureName, center} = culture; - const form = rw(forms.Folk); - const type: {[key: string]: number} = types[form]; - const name = cultureName + " " + rw(type); - const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); - const color = getMixedColor(culture.color, 0.1, 0); - - return {name, type: "Folk", form, deity, color, culture: cultureId, center, origins: [0]}; - }); -} - -function generateOrganizedReligionsAndCults( - states: TStates, - cultures: TCultures, - burgs: TBurgs, - cultureIds: Uint16Array, - stateIds: Uint16Array, - burgIds: Uint16Array, - folkReligions: ReturnType, - cells: Pick -) { - const religionsNumber = getInputNumber("religionsInput"); - if (religionsNumber === 0) return []; - - const cultsNumber = Math.floor((rand(1, 4) / 10) * religionsNumber); // 10-40% - const organizedNumber = religionsNumber - cultsNumber; - - const canditateCells = getCandidateCells(); - const religionCells = placeReligions(); - - return religionCells.map((cellId, index) => { - const cultureId = cultureIds[cellId]; - const stateId = stateIds[cellId]; - const burgId = burgIds[cellId]; - - const type = index < organizedNumber ? "Organized" : "Cult"; - - const form = rw(forms[type] as {[key in keyof typeof types]: number}); - const deityName = getDeityName(cultures, cultureId); - const deity = form === "Non-theism" ? null : deityName; - - const {name, expansion, center} = generateReligionName({ - cultureId, - stateId, - burgId, - cultures, - states, - burgs, - center: cellId, - form, - deity: deityName - }); - - const folkReligion = folkReligions.find(({culture}) => culture === cultureId); - const baseColor = folkReligion?.color || getRandomColor(); - const color = getMixedColor(baseColor, 0.3, 0); - - return {name, type, form, deity, color, culture: cultureId, center, expansion}; - }); - - function placeReligions() { - const religionCells = []; - const religionsTree = d3.quadtree(); - - // initial min distance between religions - let spacing = (graphWidth + graphHeight) / 4 / religionsNumber; - - for (const cellId of canditateCells) { - const [x, y] = cells.p[cellId]; - - if (religionsTree.find(x, y, spacing) === undefined) { - religionCells.push(cellId); - religionsTree.add([x, y]); - - if (religionCells.length === religionsNumber) return religionCells; - } - } - - WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`); - return religionCells; - } - - function getCandidateCells() { - const validBurgs = burgs.filter(isBurg); - - if (validBurgs.length >= religionsNumber) - return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell); - - return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]); - } -} - -function getDeityName(cultures: TCultures, cultureId: number) { - if (cultureId === undefined) throw "CultureId is undefined"; - - const meaning = generateMeaning(); - - const base = cultures[cultureId].base; - const cultureName = Names.getBase(base); - return cultureName + ", The " + meaning; -} - -function generateMeaning() { - const approach = ra(approaches); - if (approach === "Number") return ra(base.number); - if (approach === "Being") return ra(base.being); - if (approach === "Adjective") return ra(base.adjective); - if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`; - if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`; - if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`; - if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`; - if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`; - if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`; - if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`; - if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`; - if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`; - if (approach === "Adjective + Being + of + Genitive") - return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`; - if (approach === "Adjective + Animal + of + Genitive") - return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`; - - throw "Unknown generation approach"; -} - function getReligionsInRadius( religionIds: Uint16Array, {x, y, r, max}: {x: number; y: number; r: number; max: number} From 901120401f1b17442e24afe6a1342321d00a4711 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 26 Aug 2022 01:28:19 +0300 Subject: [PATCH 04/12] refactor(religions): define basic data --- src/config/religionsData.ts | 39 ++++-- src/scripts/generation/pack/generateRoutes.ts | 2 +- .../pack/religions/expandReligions.ts | 7 ++ .../pack/religions/generateDeityName.ts | 2 +- .../pack/religions/generateFolkReligions.ts | 22 ++-- .../religions/generateOrganizedReligions.ts | 70 +++++++++++ .../generateOrganizedReligionsAndCults.ts | 94 --------------- .../pack/religions/generateReligionName.ts | 68 ++++++----- .../pack/religions/generateReligions.ts | 40 ++----- .../pack/religions/specifyReligions.ts | 111 ++++++++++++++++++ src/types/pack/religions.d.ts | 4 +- 11 files changed, 280 insertions(+), 179 deletions(-) create mode 100644 src/scripts/generation/pack/religions/expandReligions.ts create mode 100644 src/scripts/generation/pack/religions/generateOrganizedReligions.ts delete mode 100644 src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts create mode 100644 src/scripts/generation/pack/religions/specifyReligions.ts diff --git a/src/config/religionsData.ts b/src/config/religionsData.ts index c655136a..b7d70665 100644 --- a/src/config/religionsData.ts +++ b/src/config/religionsData.ts @@ -298,15 +298,34 @@ const forms = { Heresy: {Heresy: 1} }; -const methods = { - "Random + type": 3, - "Random + ism": 1, - "Supreme + ism": 5, - "Faith of + Supreme": 5, - "Place + ism": 1, - "Culture + ism": 2, - "Place + ian + type": 6, - "Culture + type": 4 +const namingMethods = { + Folk: { + "Culture + type": 1 + }, + + Organized: { + "Random + type": 3, + "Random + ism": 1, + "Supreme + ism": 5, + "Faith of + Supreme": 5, + "Place + ism": 1, + "Culture + ism": 2, + "Place + ian + type": 6, + "Culture + type": 4 + }, + + Cult: { + "Burg + ian + type": 2, + "Random + ian + type": 1, + "Type + of the + meaning": 2 + }, + + Heresy: { + "Burg + ian + type": 3, + "Random + ism": 3, + "Random + ian + type": 2, + "Type + of the + meaning": 1 + } }; const types = { @@ -336,4 +355,4 @@ const types = { } }; -export const religionsData = {approaches, base, forms, methods, types}; +export const religionsData = {approaches, base, forms, namingMethods, types}; diff --git a/src/scripts/generation/pack/generateRoutes.ts b/src/scripts/generation/pack/generateRoutes.ts index d80b6ad1..da280f9a 100644 --- a/src/scripts/generation/pack/generateRoutes.ts +++ b/src/scripts/generation/pack/generateRoutes.ts @@ -4,6 +4,7 @@ import FlatQueue from "flatqueue"; import {TIME} from "config/logging"; import {ELEVATION, MIN_LAND_HEIGHT, ROUTES} from "config/generation"; import {dist2} from "utils/functionUtils"; +import {isBurg} from "utils/typeUtils"; type TCellsData = Pick; @@ -25,7 +26,6 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const capitalsByFeature: Dict = {}; const portsByFeature: Dict = {}; - const isBurg = (burg: IBurg | TNoBurg): burg is IBurg => burg.i !== 0; const addBurg = (object: Dict, feature: number, burg: IBurg) => { if (!object[feature]) object[feature] = []; object[feature].push(burg); diff --git a/src/scripts/generation/pack/religions/expandReligions.ts b/src/scripts/generation/pack/religions/expandReligions.ts new file mode 100644 index 00000000..314e1a59 --- /dev/null +++ b/src/scripts/generation/pack/religions/expandReligions.ts @@ -0,0 +1,7 @@ +export function expandReligions( + religions: Pick[] +) { + const religionIds = new Uint16Array(); + + return religionIds; +} diff --git a/src/scripts/generation/pack/religions/generateDeityName.ts b/src/scripts/generation/pack/religions/generateDeityName.ts index f58c7ce2..d1342ec9 100644 --- a/src/scripts/generation/pack/religions/generateDeityName.ts +++ b/src/scripts/generation/pack/religions/generateDeityName.ts @@ -15,7 +15,7 @@ export function getDeityName(cultures: TCultures, cultureId: number) { return cultureName + ", The " + meaning; } -function generateMeaning() { +export function generateMeaning() { const approach = ra(approaches); if (approach === "Number") return ra(base.number); if (approach === "Being") return ra(base.being); diff --git a/src/scripts/generation/pack/religions/generateFolkReligions.ts b/src/scripts/generation/pack/religions/generateFolkReligions.ts index 59d99910..37f05c72 100644 --- a/src/scripts/generation/pack/religions/generateFolkReligions.ts +++ b/src/scripts/generation/pack/religions/generateFolkReligions.ts @@ -1,22 +1,14 @@ import {religionsData} from "config/religionsData"; -import {getMixedColor} from "utils/colorUtils"; import {rw} from "utils/probabilityUtils"; -import {getDeityName} from "./generateDeityName"; +import {isCulture} from "utils/typeUtils"; -const {forms, types} = religionsData; +const {forms} = religionsData; -export function generateFolkReligions(cultures: TCultures) { - const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => - culture.i !== 0 && !(culture as ICulture).removed; +export function generateFolkReligions(cultures: TCultures): Pick[] { + return cultures.filter(isCulture).map(culture => { + const {i: cultureId, center} = culture; + const form = rw(forms.Folk); - return cultures.filter(isValidCulture).map(culture => { - const {i: cultureId, name: cultureName, center} = culture; - const form = rw(forms.Folk); - const type: {[key: string]: number} = types[form]; - const name = cultureName + " " + rw(type); - const deity = form === "Animism" ? null : getDeityName(cultures, cultureId); - const color = getMixedColor(culture.color, 0.1, 0); - - return {name, type: "Folk", form, deity, color, culture: cultureId, center, origins: [0]}; + return {type: "Folk", form, culture: cultureId, center}; }); } diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligions.ts b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts new file mode 100644 index 00000000..6df0e5be --- /dev/null +++ b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts @@ -0,0 +1,70 @@ +import * as d3 from "d3"; + +import {WARN} from "config/logging"; +import {religionsData} from "config/religionsData"; +import {getInputNumber} from "utils/nodeUtils"; +import {rand, rw} from "utils/probabilityUtils"; +import {isBurg} from "utils/typeUtils"; + +const {forms} = religionsData; + +export function generateOrganizedReligions( + burgs: TBurgs, + cultureIds: Uint16Array, + cells: Pick +): Pick[] { + const religionsNumber = getInputNumber("religionsInput"); + if (religionsNumber === 0) return []; + + const canditateCells = getCandidateCells(); + const religionCells = placeReligions(); + + const cultsNumber = Math.floor((rand(1, 4) / 10) * religionCells.length); // 10-40% + const heresiesNumber = Math.floor((rand(0, 2) / 10) * religionCells.length); // 0-20% + const organizedNumber = religionCells.length - cultsNumber - heresiesNumber; + + const getType = (index: number) => { + if (index < organizedNumber) return "Organized"; + if (index < organizedNumber + cultsNumber) return "Cult"; + return "Heresy"; + }; + + return religionCells.map((cellId, index) => { + const type = getType(index); + const form = rw(forms[type]); + const cultureId = cultureIds[cellId]; + + return {type, form, culture: cultureId, center: cellId}; + }); + + function placeReligions() { + const religionCells = []; + const religionsTree = d3.quadtree(); + + // min distance between religions + const spacing = (graphWidth + graphHeight) / 2 / religionsNumber; + + for (const cellId of canditateCells) { + const [x, y] = cells.p[cellId]; + + if (religionsTree.find(x, y, spacing) === undefined) { + religionCells.push(cellId); + religionsTree.add([x, y]); + + if (religionCells.length === religionsNumber) return religionCells; + } + } + + WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`); + return religionCells; + } + + function getCandidateCells() { + const validBurgs = burgs.filter(isBurg); + + if (validBurgs.length >= religionsNumber) + return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell); + + return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]); + } +} diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts b/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts deleted file mode 100644 index 44e74be9..00000000 --- a/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as d3 from "d3"; - -import {WARN} from "config/logging"; -import {religionsData} from "config/religionsData"; -import {getRandomColor, getMixedColor} from "utils/colorUtils"; -import {getInputNumber} from "utils/nodeUtils"; -import {rand, rw} from "utils/probabilityUtils"; -import {isBurg} from "utils/typeUtils"; -import {getDeityName} from "./generateDeityName"; -import {generateFolkReligions} from "./generateFolkReligions"; -import {generateReligionName} from "./generateReligionName"; - -const {forms, types} = religionsData; - -export function generateOrganizedReligionsAndCults( - states: TStates, - cultures: TCultures, - burgs: TBurgs, - cultureIds: Uint16Array, - stateIds: Uint16Array, - burgIds: Uint16Array, - folkReligions: ReturnType, - cells: Pick -) { - const religionsNumber = getInputNumber("religionsInput"); - if (religionsNumber === 0) return []; - - const cultsNumber = Math.floor((rand(1, 4) / 10) * religionsNumber); // 10-40% - const organizedNumber = religionsNumber - cultsNumber; - - const canditateCells = getCandidateCells(); - const religionCells = placeReligions(); - - return religionCells.map((cellId, index) => { - const cultureId = cultureIds[cellId]; - const stateId = stateIds[cellId]; - const burgId = burgIds[cellId]; - - const type = index < organizedNumber ? "Organized" : "Cult"; - - const form = rw(forms[type] as {[key in keyof typeof types]: number}); - const deityName = getDeityName(cultures, cultureId); - const deity = form === "Non-theism" ? null : deityName; - - const {name, expansion, center} = generateReligionName({ - cultureId, - stateId, - burgId, - cultures, - states, - burgs, - center: cellId, - form, - deity: deityName - }); - - const folkReligion = folkReligions.find(({culture}) => culture === cultureId); - const baseColor = folkReligion?.color || getRandomColor(); - const color = getMixedColor(baseColor, 0.3, 0); - - return {name, type, form, deity, color, culture: cultureId, center, expansion}; - }); - - function placeReligions() { - const religionCells = []; - const religionsTree = d3.quadtree(); - - // initial min distance between religions - let spacing = (graphWidth + graphHeight) / 4 / religionsNumber; - - for (const cellId of canditateCells) { - const [x, y] = cells.p[cellId]; - - if (religionsTree.find(x, y, spacing) === undefined) { - religionCells.push(cellId); - religionsTree.add([x, y]); - - if (religionCells.length === religionsNumber) return religionCells; - } - } - - WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`); - return religionCells; - } - - function getCandidateCells() { - const validBurgs = burgs.filter(isBurg); - - if (validBurgs.length >= religionsNumber) - return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell); - - return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]); - } -} diff --git a/src/scripts/generation/pack/religions/generateReligionName.ts b/src/scripts/generation/pack/religions/generateReligionName.ts index 5abe3e0d..330277ec 100644 --- a/src/scripts/generation/pack/religions/generateReligionName.ts +++ b/src/scripts/generation/pack/religions/generateReligionName.ts @@ -1,10 +1,10 @@ import {religionsData} from "config/religionsData"; import {trimVowels, getAdjective} from "utils/languageUtils"; import {rw, ra} from "utils/probabilityUtils"; -import {isCulture, isState} from "utils/typeUtils"; +import {generateMeaning} from "./generateDeityName"; const {Names} = window; -const {methods, types} = religionsData; +const {namingMethods, types} = religionsData; interface IContext { cultureId: number; @@ -13,9 +13,8 @@ interface IContext { cultures: TCultures; states: TStates; burgs: TBurgs; - center: number; - form: keyof typeof types; - deity: string; + form: string; + supreme: string; } const context = { @@ -43,8 +42,8 @@ const context = { return this.data.form; }, - get deity() { - return this.data.deity; + get supreme() { + return this.data.supreme; }, // generation methods @@ -53,11 +52,11 @@ const context = { }, get type() { - return rw(types[this.form] as {[key: string]: number}); + return rw(types[this.form as keyof typeof types]); }, - get supreme() { - return this.deity.split(/[ ,]+/)[0]; + get supremeName() { + return this.supreme.split(/[ ,]+/)[0]; }, get cultureName() { @@ -67,15 +66,19 @@ const context = { get place() { const base = this.burg.name || this.state.name; return trimVowels(base.split(/[ ,]+/)[0]); + }, + + get meaning() { + return generateMeaning(); } }; const nameMethodsMap = { "Random + type": {getName: () => `${context.random} ${context.type}`, expansion: "global"}, "Random + ism": {getName: () => `${trimVowels(context.random)}ism`, expansion: "global"}, - "Supreme + ism": {getName: () => `${trimVowels(context.supreme)}ism`, expansion: "global"}, + "Supreme + ism": {getName: () => `${trimVowels(context.supremeName)}ism`, expansion: "global"}, "Faith of + Supreme": { - getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supreme}`, + getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supremeName}`, expansion: "global" }, "Place + ism": {getName: () => `${context.place}ism`, expansion: "state"}, @@ -84,29 +87,36 @@ const nameMethodsMap = { getName: () => `${getAdjective(context.place)} ${context.type}`, expansion: "state" }, - "Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"} + "Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"}, + "Burg + ian + type": { + getName: () => context.burg.name && `${getAdjective(context.burg.name)} ${context.type}`, + expansion: "global" + }, + "Random + ian + type": {getName: () => `${getAdjective(context.random)} ${context.type}`, expansion: "global"}, + "Type + of the + meaning": {getName: () => `${context.type} of the ${context.meaning}`, expansion: "global"} }; -export function generateReligionName(data: IContext) { +const fallbackMethod = nameMethodsMap["Random + type"]; + +function getMethod(type: IReligion["type"]) { + const methods: {[key in string]: number} = namingMethods[type]; + const method = rw(methods); + return nameMethodsMap[method as keyof typeof nameMethodsMap]; +} + +export function generateReligionName( + type: IReligion["type"], + data: IContext +): {name: string; expansion: IReligion["expansion"]} { context.current = data; - const {stateId, cultureId, states, cultures, center} = data; + const {stateId, cultureId} = data; - const method = nameMethodsMap[rw(methods)]; - const name = method.getName(); + const method = getMethod(type); + const name = method.getName() || fallbackMethod.getName(); - let expansion = method.expansion; + let expansion = method.expansion as IReligion["expansion"]; if (expansion === "state" && !stateId) expansion = "global"; if (expansion === "culture" && !cultureId) expansion = "global"; - if (expansion === "state" && Math.random() > 0.5) { - const state = states[stateId]; - if (isState(state)) return {name, expansion, center: state.center}; - } - - if (expansion === "culture" && Math.random() > 0.5) { - const culture = cultures[cultureId]; - if (isCulture(culture)) return {name, expansion, center: culture.center}; - } - - return {name, expansion, center}; + return {name, expansion}; } diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 59eb65a1..91a6eb75 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -1,8 +1,8 @@ import {TIME} from "config/logging"; -import {unique} from "utils/arrayUtils"; -import {findAll} from "utils/graphUtils"; +import {pick} from "utils/functionUtils"; import {generateFolkReligions} from "./generateFolkReligions"; -import {generateOrganizedReligionsAndCults} from "./generateOrganizedReligionsAndCults"; +import {generateOrganizedReligions} from "./generateOrganizedReligions"; +import {specifyReligions} from "./specifyReligions"; type TCellsData = Pick; @@ -25,36 +25,20 @@ export function generateReligions({ }) { TIME && console.time("generateReligions"); - const religionIds = new Uint16Array(cells.c.length); - const folkReligions = generateFolkReligions(cultures); - const basicReligions = generateOrganizedReligionsAndCults( - states, - cultures, - burgs, - cultureIds, + const basicReligions = generateOrganizedReligions(burgs, cultureIds, pick(cells, "i", "p", "pop")); + const {religions, religionIds} = specifyReligions( + [...folkReligions, ...basicReligions], stateIds, burgIds, - folkReligions, - { - i: cells.i, - p: cells.p, - pop: cells.pop - } + cultures, + states, + burgs, + cells.p ); - console.log(folkReligions, basicReligions); + console.log(religions); TIME && console.timeEnd("generateReligions"); - return {religionIds}; -} - -function getReligionsInRadius( - religionIds: Uint16Array, - {x, y, r, max}: {x: number; y: number; r: number; max: number} -) { - if (max === 0) return [0]; - const cellsInRadius = findAll(x, y, r); - const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r)); - return religions.length ? religions.slice(0, max) : [0]; + return {religionIds, religions}; } diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts new file mode 100644 index 00000000..786c7081 --- /dev/null +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -0,0 +1,111 @@ +import {unique} from "utils/arrayUtils"; +import {getMixedColor, getRandomColor} from "utils/colorUtils"; +import {findAll} from "utils/graphUtils"; +import {each, gauss, rand} from "utils/probabilityUtils"; +import {isCulture} from "utils/typeUtils"; +import {expandReligions} from "./expandReligions"; +import {getDeityName} from "./generateDeityName"; +import {generateReligionName} from "./generateReligionName"; + +const expansionismMap = { + Folk: () => 0, + Organized: () => rand(3, 8), + Cult: () => gauss(1.1, 0.5, 0, 5), + Heresy: () => gauss(1.2, 0.5, 0, 5) +}; + +export function specifyReligions( + religionsData: Pick[], + stateIds: Uint16Array, + burgIds: Uint16Array, + cultures: TCultures, + states: TStates, + burgs: TBurgs, + points: TPoints +) { + const religions = religionsData.map(({type, form, culture: cultureId, center}, index) => { + const supreme = getDeityName(cultures, cultureId); + const deity = form === "Non-theism" || form === "Animism" ? null : supreme; + + const stateId = stateIds[center]; + const burgId = burgIds[center]; + + const {name, expansion} = generateReligionName(type, { + cultureId, + stateId, + burgId, + cultures, + states, + burgs, + form, + supreme + }); + + const expansionism = expansionismMap[type](); + + const culture = cultures[cultureId]; + const color = isCulture(culture) ? getMixedColor(culture.color, 0.1, 0) : getRandomColor(); + + const origins: number[] = []; // define after religions expansion + return {i: index + 1, name, type, form, culture: cultureId, center, deity, expansion, expansionism, color, origins}; + }); + + const religionIds = expandReligions(religions); + + const names = renameOldReligions(religions); + const origins = defineOrigins(religionIds, religions, points); + + return { + religions: religions.map((religion, index) => ({name: names[index], religion, origins: origins[index]})), + religionIds + }; +} + +// add 'Old' to names of folk religions which have organized competitors +function renameOldReligions(religions: Pick[]) { + return religions.map(({name, type, culture: cultureId}) => { + if (type !== "Folk") return name; + + const haveOrganized = religions.some( + ({type, culture, expansion}) => culture === cultureId && type === "Organized" && expansion === "culture" + ); + if (haveOrganized && name.slice(0, 3) !== "Old") return `Old ${name}`; + return name; + }); +} + +function defineOrigins( + religionIds: Uint16Array, + religions: Pick[], + points: TPoints +) { + return religions.map(religion => { + if (religion.type === "Folk") return [0]; + + const {type, culture: cultureId, expansion, center} = religion; + const [x, y] = points[center]; + + const folkReligion = religions.find(({culture, type}) => type === "Folk" || culture === cultureId); + const isFolkBased = folkReligion && cultureId && expansion === "culture" && each(2)(center); + + if (isFolkBased) return [folkReligion.i]; + + if (type === "Organized") { + const origins = getReligionsInRadius(religionIds, {x, y, r: 150 / religions.length, max: 2}); + return origins; + } + + const origins = getReligionsInRadius(religionIds, {x, y, r: 300 / religions.length, max: rand(0, 4)}); + return origins; + }); +} + +function getReligionsInRadius( + religionIds: Uint16Array, + {x, y, r, max}: {x: number; y: number; r: number; max: number} +) { + if (max === 0) return [0]; + const cellsInRadius: number[] = []; // findAll(x, y, r); + const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r)); + return religions.length ? religions.slice(0, max) : [0]; +} diff --git a/src/types/pack/religions.d.ts b/src/types/pack/religions.d.ts index 215395a1..eb4c5bb1 100644 --- a/src/types/pack/religions.d.ts +++ b/src/types/pack/religions.d.ts @@ -1,13 +1,15 @@ interface IReligion { i: number; name: string; - type: "Folk" | "Orgamized" | "Cult" | "Heresy"; + type: "Folk" | "Organized" | "Cult" | "Heresy"; color: string; culture: number; form: any; deity: string | null; center: number; origins: number[]; + expansion?: "global" | "culture" | "state"; + expansionism: number; removed?: boolean; } From 0a77845ebc65007dac5531710b10b20a94096643 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 26 Aug 2022 23:24:37 +0300 Subject: [PATCH 05/12] refactor(religions): spread folk religion --- .../pack/burgsAndStates/createStates.ts | 4 +- .../pack/burgsAndStates/expandStates.ts | 9 +- src/scripts/generation/pack/pack.ts | 7 +- .../pack/religions/expandReligions.ts | 44 +++++++++- .../religions/generateOrganizedReligions.ts | 5 +- .../pack/religions/generateReligionName.ts | 6 +- .../pack/religions/generateReligions.ts | 17 ++-- .../pack/religions/specifyReligions.ts | 87 ++++++++++++------- src/types/pack/religions.d.ts | 2 +- src/utils/typeUtils.ts | 5 ++ 10 files changed, 118 insertions(+), 68 deletions(-) diff --git a/src/scripts/generation/pack/burgsAndStates/createStates.ts b/src/scripts/generation/pack/burgsAndStates/createStates.ts index 8f232734..73c5591b 100644 --- a/src/scripts/generation/pack/burgsAndStates/createStates.ts +++ b/src/scripts/generation/pack/burgsAndStates/createStates.ts @@ -10,7 +10,7 @@ const {Names, COA} = window; type TCapitals = ReturnType; -export function createStates(capitals: TCapitals, cultures: TCultures) { +export function createStates(capitals: TCapitals, cultures: TCultures): TStates { TIME && console.time("createStates"); const colors = getColors(capitals.length); @@ -28,7 +28,7 @@ export function createStates(capitals: TCapitals, cultures: TCultures) { const shield = COA.getShield(cultureShield, null); const coa: ICoa = {...COA.generate(null, null, null, type), shield}; - return {i: id, name, type, center: cellId, color, expansionism, capital: id, culture: cultureId, coa}; + return {i: id, name, type, center: cellId, color, expansionism, capital: id, culture: cultureId, coa} as IState; }); TIME && console.timeEnd("createStates"); diff --git a/src/scripts/generation/pack/burgsAndStates/expandStates.ts b/src/scripts/generation/pack/burgsAndStates/expandStates.ts index 98f1237d..1de4dcf8 100644 --- a/src/scripts/generation/pack/burgsAndStates/expandStates.ts +++ b/src/scripts/generation/pack/burgsAndStates/expandStates.ts @@ -3,13 +3,8 @@ import FlatQueue from "flatqueue"; import {TIME} from "config/logging"; import {getInputNumber} from "utils/nodeUtils"; import {minmax} from "utils/numberUtils"; -import type {createCapitals} from "./createCapitals"; -import type {createStates} from "./createStates"; import {ELEVATION, FOREST_BIOMES, MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation"; -import {isState} from "utils/typeUtils"; - -type TCapitals = ReturnType; -type TStates = ReturnType; +import {isNeutals} from "utils/typeUtils"; // growth algorithm to assign cells to states export function expandStates( @@ -107,7 +102,7 @@ export function expandStates( function getState(stateId: number) { const state = states[stateId]; - if (!isState(state)) throw new Error("Neutrals cannot expand"); + if (isNeutals(state)) throw new Error("Neutrals cannot expand"); return state; } diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 1746c41f..70f7e0fe 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -132,9 +132,6 @@ export function createPack(grid: IGrid): IPack { states, cultures, burgs, - cultureIds, - stateIds, - burgIds, cells: { i: cells.i, c: cells.c, @@ -144,7 +141,9 @@ export function createPack(grid: IGrid): IPack { t: distanceField, biome, pop: population, - burg: burgIds + culture: cultureIds, + burg: burgIds, + state: stateIds } }); diff --git a/src/scripts/generation/pack/religions/expandReligions.ts b/src/scripts/generation/pack/religions/expandReligions.ts index 314e1a59..12d1207b 100644 --- a/src/scripts/generation/pack/religions/expandReligions.ts +++ b/src/scripts/generation/pack/religions/expandReligions.ts @@ -1,7 +1,43 @@ -export function expandReligions( - religions: Pick[] -) { - const religionIds = new Uint16Array(); +import FlatQueue from "flatqueue"; +import {getInputNumber} from "utils/nodeUtils"; +import {gauss} from "utils/probabilityUtils"; +import {isReligion} from "utils/typeUtils"; + +type TReligionData = Pick; +type TCellsData = Pick; + +export function expandReligions(religions: TReligionData[], cells: TCellsData) { + const religionIds = spreadFolkReligions(religions, cells); + + const queue = new FlatQueue<{cellId: number; religionId: number}>(); + const cost: number[] = []; + + const neutralInput = getInputNumber("neutralInput"); + const maxExpansionCost = (cells.i.length / 25) * gauss(1, 0.3, 0.2, 2, 2) * neutralInput; + + for (const religion of religions) { + if (!isReligion(religion as IReligion) || (religion as IReligion).type === "Folk") continue; + + const {i: religionId, center: cellId} = religion; + religionIds[cellId] = religionId; + cost[cellId] = 1; + queue.push({cellId, religionId}, 0); + } + + return religionIds; +} + +// folk religions initially get all cells of their culture +function spreadFolkReligions(religions: TReligionData[], cells: TCellsData) { + const religionIds = new Uint16Array(cells.i.length); + + const folkReligions = religions.filter(({type}) => type === "Folk"); + const cultureToReligionMap = new Map(folkReligions.map(({i, culture}) => [culture, i])); + + for (const cellId of cells.i) { + const cultureId = cells.culture[cellId]; + religionIds[cellId] = cultureToReligionMap.get(cultureId) || 0; + } return religionIds; } diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligions.ts b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts index 6df0e5be..9049a0be 100644 --- a/src/scripts/generation/pack/religions/generateOrganizedReligions.ts +++ b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts @@ -10,8 +10,7 @@ const {forms} = religionsData; export function generateOrganizedReligions( burgs: TBurgs, - cultureIds: Uint16Array, - cells: Pick + cells: Pick ): Pick[] { const religionsNumber = getInputNumber("religionsInput"); if (religionsNumber === 0) return []; @@ -32,7 +31,7 @@ export function generateOrganizedReligions( return religionCells.map((cellId, index) => { const type = getType(index); const form = rw(forms[type]); - const cultureId = cultureIds[cellId]; + const cultureId = cells.culture[cellId]; return {type, form, culture: cultureId, center: cellId}; }); diff --git a/src/scripts/generation/pack/religions/generateReligionName.ts b/src/scripts/generation/pack/religions/generateReligionName.ts index 330277ec..967bd820 100644 --- a/src/scripts/generation/pack/religions/generateReligionName.ts +++ b/src/scripts/generation/pack/religions/generateReligionName.ts @@ -109,14 +109,12 @@ export function generateReligionName( data: IContext ): {name: string; expansion: IReligion["expansion"]} { context.current = data; - const {stateId, cultureId} = data; - const method = getMethod(type); const name = method.getName() || fallbackMethod.getName(); let expansion = method.expansion as IReligion["expansion"]; - if (expansion === "state" && !stateId) expansion = "global"; - if (expansion === "culture" && !cultureId) expansion = "global"; + if (expansion === "state" && !data.stateId) expansion = "global"; + else if (expansion === "culture" && !data.cultureId) expansion = "global"; return {name, expansion}; } diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 91a6eb75..fb073b42 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -4,37 +4,32 @@ import {generateFolkReligions} from "./generateFolkReligions"; import {generateOrganizedReligions} from "./generateOrganizedReligions"; import {specifyReligions} from "./specifyReligions"; -type TCellsData = Pick; +type TCellsData = Pick< + IPack["cells"], + "i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "culture" | "burg" | "state" +>; export function generateReligions({ states, cultures, burgs, - cultureIds, - stateIds, - burgIds, cells }: { states: TStates; cultures: TCultures; burgs: TBurgs; - cultureIds: Uint16Array; - stateIds: Uint16Array; - burgIds: Uint16Array; cells: TCellsData; }) { TIME && console.time("generateReligions"); const folkReligions = generateFolkReligions(cultures); - const basicReligions = generateOrganizedReligions(burgs, cultureIds, pick(cells, "i", "p", "pop")); + const basicReligions = generateOrganizedReligions(burgs, pick(cells, "i", "p", "pop", "culture")); const {religions, religionIds} = specifyReligions( [...folkReligions, ...basicReligions], - stateIds, - burgIds, cultures, states, burgs, - cells.p + pick(cells, "i", "c", "culture", "burg", "state") ); console.log(religions); diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts index 786c7081..b3c10ea8 100644 --- a/src/scripts/generation/pack/religions/specifyReligions.ts +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -1,6 +1,4 @@ -import {unique} from "utils/arrayUtils"; import {getMixedColor, getRandomColor} from "utils/colorUtils"; -import {findAll} from "utils/graphUtils"; import {each, gauss, rand} from "utils/probabilityUtils"; import {isCulture} from "utils/typeUtils"; import {expandReligions} from "./expandReligions"; @@ -16,19 +14,17 @@ const expansionismMap = { export function specifyReligions( religionsData: Pick[], - stateIds: Uint16Array, - burgIds: Uint16Array, cultures: TCultures, states: TStates, burgs: TBurgs, - points: TPoints -) { - const religions = religionsData.map(({type, form, culture: cultureId, center}, index) => { + cells: Pick +): {religions: TReligions; religionIds: Uint16Array} { + const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => { const supreme = getDeityName(cultures, cultureId); const deity = form === "Non-theism" || form === "Animism" ? null : supreme; - const stateId = stateIds[center]; - const burgId = burgIds[center]; + const stateId = cells.state[center]; + const burgId = cells.burg[center]; const {name, expansion} = generateReligionName(type, { cultureId, @@ -46,19 +42,26 @@ export function specifyReligions( const culture = cultures[cultureId]; const color = isCulture(culture) ? getMixedColor(culture.color, 0.1, 0) : getRandomColor(); - const origins: number[] = []; // define after religions expansion - return {i: index + 1, name, type, form, culture: cultureId, center, deity, expansion, expansionism, color, origins}; + return {i: index + 1, name, type, form, culture: cultureId, center, deity, expansion, expansionism, color}; }); - const religionIds = expandReligions(religions); + const religionIds = expandReligions(rawReligions, cells); + const names = renameOldReligions(rawReligions); + const origins = defineOrigins(religionIds, rawReligions, cells.c); - const names = renameOldReligions(religions); - const origins = defineOrigins(religionIds, religions, points); + return {religions: combineReligionsData(), religionIds}; - return { - religions: religions.map((religion, index) => ({name: names[index], religion, origins: origins[index]})), - religionIds - }; + function combineReligionsData(): TReligions { + const noReligion: TNoReligion = {i: 0, name: "No religion"}; + + const religions = rawReligions.map((religion, index) => ({ + ...religion, + name: names[index], + origins: origins[index] + })); + + return [noReligion, ...religions]; + } } // add 'Old' to names of folk religions which have organized competitors @@ -74,38 +77,58 @@ function renameOldReligions(religions: Pick[], - points: TPoints + neighbors: number[][] ) { return religions.map(religion => { if (religion.type === "Folk") return [0]; - const {type, culture: cultureId, expansion, center} = religion; - const [x, y] = points[center]; + const {i, type, culture: cultureId, expansion, center} = religion; const folkReligion = religions.find(({culture, type}) => type === "Folk" || culture === cultureId); const isFolkBased = folkReligion && cultureId && expansion === "culture" && each(2)(center); if (isFolkBased) return [folkReligion.i]; - if (type === "Organized") { - const origins = getReligionsInRadius(religionIds, {x, y, r: 150 / religions.length, max: 2}); - return origins; - } - - const origins = getReligionsInRadius(religionIds, {x, y, r: 300 / religions.length, max: rand(0, 4)}); + const {clusterSize, maxReligions} = religionOriginsParamsMap[type]; + const origins = getReligionsInRadius(neighbors, center, religionIds, i, clusterSize, maxReligions); return origins; }); } function getReligionsInRadius( + neighbors: number[][], + center: number, religionIds: Uint16Array, - {x, y, r, max}: {x: number; y: number; r: number; max: number} + religionId: number, + clusterSize: number, + maxReligions: number ) { - if (max === 0) return [0]; - const cellsInRadius: number[] = []; // findAll(x, y, r); - const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r)); - return religions.length ? religions.slice(0, max) : [0]; + const religions = new Set(); + const queue = [center]; + const checked = <{[key: number]: true}>{}; + + for (let size = 0; queue.length && size < clusterSize; size++) { + const cellId = queue.pop()!; + checked[center] = true; + + for (const neibId of neighbors[cellId]) { + if (checked[neibId]) continue; + checked[neibId] = true; + + const neibReligion = religionIds[neibId]; + if (neibReligion && neibReligion !== religionId) religions.add(neibReligion); + queue.push(neibId); + } + } + + return religions.size ? [...religions].slice(0, maxReligions) : [0]; } diff --git a/src/types/pack/religions.d.ts b/src/types/pack/religions.d.ts index eb4c5bb1..d99b652c 100644 --- a/src/types/pack/religions.d.ts +++ b/src/types/pack/religions.d.ts @@ -15,7 +15,7 @@ interface IReligion { type TNoReligion = { i: 0; - name: string; + name: "No religion"; }; type TReligions = [TNoReligion, ...IReligion[]]; diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index ef1ebf1b..94ce34aa 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -1,6 +1,11 @@ export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed; +export const isNeutals = (neutrals: TNeutrals | IState): neutrals is TNeutrals => neutrals.i === 0; + export const isCulture = (culture: TWilderness | ICulture): culture is ICulture => culture.i !== 0 && !(culture as ICulture).removed; export const isBurg = (burg: TNoBurg | IBurg): burg is IBurg => burg.i !== 0 && !(burg as IBurg).removed; + +export const isReligion = (religion: TNoReligion | IReligion): religion is IReligion => + religion.i !== 0 && !(religion as IReligion).removed; From 707cdd77ac122a601c202c8b417ecd9e2d480ea3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 27 Aug 2022 00:51:29 +0300 Subject: [PATCH 06/12] 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); +} From 7a3a87e935f9a9fc8978d55e1abc44ebb5b53482 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 29 Aug 2022 00:42:37 +0300 Subject: [PATCH 07/12] refactor(religions): render religions --- src/index.css | 5 +- src/layers/renderers/drawCultures.ts | 34 ++++++-- src/layers/renderers/drawReligions.js | 4 +- src/layers/renderers/drawRoutes.ts | 2 +- src/layers/renderers/utilts.ts | 107 ++++++++++++++++++++------ src/scripts/connectVertices.ts | 21 ++--- src/scripts/generation/generation.ts | 4 +- src/types/pack/features.d.ts | 4 +- src/utils/lineUtils.ts | 6 +- src/utils/typeUtils.ts | 5 ++ 10 files changed, 137 insertions(+), 55 deletions(-) diff --git a/src/index.css b/src/index.css index 9653262e..b5180a25 100644 --- a/src/index.css +++ b/src/index.css @@ -137,7 +137,8 @@ a { stroke-linejoin: round; } -t, +/* TODO: turn on after debugging */ +/* t, #regions, #cults, #relig, @@ -150,7 +151,7 @@ t, #landmass, #fogging { pointer-events: none; -} +} */ #armies text { pointer-events: none; diff --git a/src/layers/renderers/drawCultures.ts b/src/layers/renderers/drawCultures.ts index 7090f417..aa9189ef 100644 --- a/src/layers/renderers/drawCultures.ts +++ b/src/layers/renderers/drawCultures.ts @@ -1,22 +1,44 @@ import * as d3 from "d3"; import {getPaths} from "./utilts"; +import {pick} from "utils/functionUtils"; export function drawCultures() { - /* uses */ const {cells, vertices, cultures} = pack; + d3.select("#cults").selectAll("g").remove(); - const getType = (cellId: number) => cells.culture[cellId]; - const paths = getPaths(cells.c, cells.v, vertices, getType); + /* uses */ const {cells, vertices, features, cultures} = pack; - const getColor = (i: number) => i && (cultures[i] as ICulture).color; + const paths = getPaths({ + getType: (cellId: number) => cells.culture[cellId], + cells: pick(cells, "c", "v", "b", "h", "f"), + vertices, + features + }); + + const getColor = (i: number) => (cultures[i] as ICulture).color; d3.select("#cults") + .append("g") + .attr("fill", "none") + .attr("stroke-width", 3) .selectAll("path") .remove() - .data(Object.entries(paths)) + .data(paths) .enter() .append("path") - .attr("d", ([, path]) => path) + .attr("d", ([, path]) => path.waterGap) + .attr("stroke", ([i]) => getColor(Number(i))) + .attr("id", ([i]) => "culture-gap" + i); + + d3.select("#cults") + .append("g") + .attr("stroke", "none") + .selectAll("path") + .remove() + .data(paths) + .enter() + .append("path") + .attr("d", ([, path]) => path.fill) .attr("fill", ([i]) => getColor(Number(i))) .attr("id", ([i]) => "culture" + i); } diff --git a/src/layers/renderers/drawReligions.js b/src/layers/renderers/drawReligions.js index a8df70ae..e81810b4 100644 --- a/src/layers/renderers/drawReligions.js +++ b/src/layers/renderers/drawReligions.js @@ -4,7 +4,6 @@ export function drawReligions() { const n = cells.i.length; const used = new Uint8Array(cells.i.length); - const vArray = new Array(religions.length); // store vertices array const body = new Array(religions.length).fill(""); // store path around each religion const gap = new Array(religions.length).fill(""); // store path along water for each religion to fill the gaps @@ -20,8 +19,7 @@ export function drawReligions() { const chain = connectVertices(vertex, r, borderWith); if (chain.length < 3) continue; const points = chain.map(v => vertices.p[v[0]]); - if (!vArray[r]) vArray[r] = []; - vArray[r].push(points); + body[r] += "M" + points.join("L") + "Z"; gap[r] += "M" + diff --git a/src/layers/renderers/drawRoutes.ts b/src/layers/renderers/drawRoutes.ts index 1518dd8d..f6362490 100644 --- a/src/layers/renderers/drawRoutes.ts +++ b/src/layers/renderers/drawRoutes.ts @@ -12,7 +12,7 @@ const lineGenTypeMap: {[key in IRoute["type"]]: d3.CurveFactory | d3.CurveFactor export function drawRoutes() { routes.selectAll("path").remove(); - const {cells, burgs} = pack; + /* uses */ const {cells, burgs} = pack; const lineGen = d3.line(); const SHARP_ANGLE = 135; diff --git a/src/layers/renderers/utilts.ts b/src/layers/renderers/utilts.ts index cf454606..7931004a 100644 --- a/src/layers/renderers/utilts.ts +++ b/src/layers/renderers/utilts.ts @@ -1,40 +1,99 @@ +import {MIN_LAND_HEIGHT} from "config/generation"; import {connectVertices} from "scripts/connectVertices"; +import {isLake} from "utils/typeUtils"; -export function getPaths( - cellNeighbors: number[][], - cellVertices: number[][], - vertices: IGraphVertices, - getType: (cellId: number) => number -) { - const paths: Dict = {}; +type TPath = {fill: string; waterGap: string; halo: string}; - function addPath(index: number, points: TPoints) { - if (!paths[index]) paths[index] = ""; - paths[index] += "M" + points.join("L") + "Z"; - } +export function getPaths({ + vertices, + getType, + features, + cells +}: { + vertices: IGraphVertices; + getType: (cellId: number) => number; + features: TPackFeatures; + cells: Pick; +}) { + const paths: Dict = {}; - const checkedCells = new Uint8Array(cellNeighbors.length); - for (let cellId = 0; cellId < cellNeighbors.length; cellId++) { - if (checkedCells[cellId]) continue; - if (!getType(cellId)) continue; + const checkedCells = new Uint8Array(cells.c.length); + const addToChecked = (cellId: number) => { checkedCells[cellId] = 1; + }; + const isChecked = (cellId: number) => checkedCells[cellId] === 1; + + for (let cellId = 0; cellId < cells.c.length; cellId++) { + if (isChecked(cellId) || getType(cellId) === 0) continue; + addToChecked(cellId); const type = getType(cellId); const ofSameType = (cellId: number) => getType(cellId) === type; + const ofDifferentType = (cellId: number) => getType(cellId) !== type; - const isOnborder = cellNeighbors[cellId].some(cellId => !ofSameType(cellId)); - if (!isOnborder) continue; + const onborderCell = cells.c[cellId].find(ofDifferentType); + if (onborderCell === undefined) continue; - const startingVertex = cellVertices[cellId].find(v => vertices.c[v].some(cellId => !ofSameType(cellId))); - if (startingVertex === undefined) throw new Error(`getPath: starting vertex for cell ${cellId} is not found`); + const feature = features[cells.f[onborderCell]]; + if (isInnerLake(feature, ofSameType)) continue; - const chain = connectVertices({vertices, startingVertex, ofSameType, checkedCellsMutable: checkedCells}); + const startingVertex = cells.v[cellId].find(v => vertices.c[v].some(ofDifferentType)); + if (startingVertex === undefined) throw new Error(`Starting vertex for cell ${cellId} is not found`); - if (chain.length < 3) continue; - const points = chain.map(v => vertices.p[v]); + const vertexChain = connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing: true}); + if (vertexChain.length < 3) continue; - addPath(type, points); + addPath(type, vertexChain); } - return paths; + return Object.entries(paths); + + function getVertexPoint(vertex: number) { + return vertices.p[vertex]; + } + + function getFillPath(vertexChain: number[]) { + const points: TPoints = vertexChain.map(getVertexPoint); + const firstPoint = points.shift(); + return `M${firstPoint} L${points.join(" ")} Z`; + } + + function getBorderPath(vertexChain: number[], discontinue: (vertex: number) => boolean) { + let discontinued = true; + const path = vertexChain.map(vertex => { + if (discontinue(vertex)) { + discontinued = true; + return ""; + } + + const operation = discontinued ? "M" : "L"; + discontinued = false; + return ` ${operation}${getVertexPoint(vertex)}`; + }); + + return path.join("").trim(); + } + + function isBorderVertex(vertex: number) { + const adjacentCells = vertices.c[vertex]; + return adjacentCells.some(i => cells.b[i]); + } + + function isLandVertex(vertex: number) { + const adjacentCells = vertices.c[vertex]; + return adjacentCells.every(i => cells.h[i] >= MIN_LAND_HEIGHT); + } + + function addPath(index: number, vertexChain: number[]) { + if (!paths[index]) paths[index] = {fill: "", waterGap: "", halo: ""}; + + paths[index].fill += getFillPath(vertexChain); + paths[index].halo += getBorderPath(vertexChain, isBorderVertex); + paths[index].waterGap += getBorderPath(vertexChain, isLandVertex); + } +} + +function isInnerLake(feature: 0 | TPackFeature, ofSameType: (cellId: number) => boolean) { + if (!isLake(feature)) return false; + return feature.shoreline.every(ofSameType); } diff --git a/src/scripts/connectVertices.ts b/src/scripts/connectVertices.ts index cec69aba..afb8052b 100644 --- a/src/scripts/connectVertices.ts +++ b/src/scripts/connectVertices.ts @@ -88,38 +88,32 @@ function findStartingVertex({ throw new Error(`Markup: firstCell of feature ${featureId} has no neighbors of other features or external vertices`); } -const CONNECT_VERTICES_MAX_ITERATIONS = 50000; +const MAX_ITERATIONS = 50000; // connect vertices around feature export function connectVertices({ vertices, startingVertex, ofSameType, - checkedCellsMutable + addToChecked, + closeRing }: { vertices: IGraphVertices; startingVertex: number; ofSameType: (cellId: number) => boolean; - checkedCellsMutable?: Uint8Array; + addToChecked?: (cellId: number) => void; + closeRing?: boolean; }) { const chain: number[] = []; // vertices chain to form a path - const addToChecked = (cellIds: number[]) => { - if (checkedCellsMutable) { - cellIds.forEach(cellId => { - checkedCellsMutable[cellId] = 1; - }); - } - }; - let next = startingVertex; - for (let i = 0; i === 0 || (next !== startingVertex && i < CONNECT_VERTICES_MAX_ITERATIONS); i++) { + for (let i = 0; i === 0 || (next !== startingVertex && i < MAX_ITERATIONS); i++) { const previous = chain.at(-1); const current = next; chain.push(current); const neibCells = vertices.c[current]; - addToChecked(neibCells); + if (addToChecked) neibCells.forEach(addToChecked); const [c1, c2, c3] = neibCells.map(ofSameType); const [v1, v2, v3] = vertices.v[current]; @@ -134,5 +128,6 @@ export function connectVertices({ } } + if (closeRing) chain.push(startingVertex); return chain; } diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index a201620a..6d776f82 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -70,8 +70,10 @@ async function generate(options?: IGenerationOptions) { // renderLayer("biomes"); renderLayer("burgs"); renderLayer("routes"); + renderLayer("cultures"); + //renderLayer("religions"); - drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true}); + // 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(); diff --git a/src/types/pack/features.d.ts b/src/types/pack/features.d.ts index 2ae8f95a..13cd5b22 100644 --- a/src/types/pack/features.d.ts +++ b/src/types/pack/features.d.ts @@ -35,6 +35,6 @@ interface IPackFeatureLake extends IPackFeatureBase { type TPackFeature = IPackFeatureOcean | IPackFeatureIsland | IPackFeatureLake; -type FirstElement = 0; +type TNoFeature = 0; -type TPackFeatures = [FirstElement, ...TPackFeature[]]; +type TPackFeatures = [TNoFeature, ...TPackFeature[]]; diff --git a/src/utils/lineUtils.ts b/src/utils/lineUtils.ts index 0d837f12..9674a7ad 100644 --- a/src/utils/lineUtils.ts +++ b/src/utils/lineUtils.ts @@ -63,12 +63,12 @@ function getPointOffCanvasSide([x, y]: TPoint) { // remove intermediate out-of-canvas points from polyline export function filterOutOfCanvasPoints(points: TPoints) { - const pointsOutSide = points.map(getPointOffCanvasSide); + const pointsOutside = points.map(getPointOffCanvasSide); const SAFE_ZONE = 3; - const fragment = (i: number) => sliceFragment(pointsOutSide, i, SAFE_ZONE); + const fragment = (i: number) => sliceFragment(pointsOutside, i, SAFE_ZONE); const filterOutCanvasPoint = (i: number) => { - const pointSide = pointsOutSide[i]; + const pointSide = pointsOutside[i]; return !pointSide || fragment(i).some(side => !side || side !== pointSide); }; diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index 94ce34aa..8248144b 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -1,3 +1,8 @@ +export const isFeature = (feature: TNoFeature | TPackFeature): feature is TPackFeature => feature !== 0; + +export const isLake = (feature: TNoFeature | TPackFeature): feature is IPackFeatureLake => + isFeature(feature) && feature.type === "lake"; + export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed; export const isNeutals = (neutrals: TNeutrals | IState): neutrals is TNeutrals => neutrals.i === 0; From 538ad3512e77527debe01df8097aa4296a1cc804 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 29 Aug 2022 00:59:30 +0300 Subject: [PATCH 08/12] fix(burgs): don't put burgs on border cells --- .../pack/burgsAndStates/createTowns.ts | 5 +---- .../burgsAndStates/generateBurgsAndStates.ts | 17 +++++++++++------ src/scripts/generation/pack/pack.ts | 2 +- src/utils/debugUtils.ts | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/scripts/generation/pack/burgsAndStates/createTowns.ts b/src/scripts/generation/pack/burgsAndStates/createTowns.ts index a6b6dbf9..fd13a14c 100644 --- a/src/scripts/generation/pack/burgsAndStates/createTowns.ts +++ b/src/scripts/generation/pack/burgsAndStates/createTowns.ts @@ -8,8 +8,8 @@ import {gauss} from "utils/probabilityUtils"; const {Names} = window; export function createTowns( - capitalCells: Map, cultures: TCultures, + scoredCellIds: UintArray, cells: Pick ) { TIME && console.time("createTowns"); @@ -17,9 +17,6 @@ export function createTowns( // randomize cells score a bit for more natural towns placement const randomizeScore = (suitability: number) => suitability * gauss(1, 3, 0, 20, 3); const scores = new Int16Array(cells.s.map(randomizeScore)); - - // take populated cells without capitals - const scoredCellIds = cells.i.filter(i => scores[i] > 0 && cells.culture[i] && !capitalCells.has(i)); scoredCellIds.sort((a, b) => scores[b] - scores[a]); // sort by randomized suitability score const townsNumber = getTownsNumber(); diff --git a/src/scripts/generation/pack/burgsAndStates/generateBurgsAndStates.ts b/src/scripts/generation/pack/burgsAndStates/generateBurgsAndStates.ts index 061d804e..eeefba07 100644 --- a/src/scripts/generation/pack/burgsAndStates/generateBurgsAndStates.ts +++ b/src/scripts/generation/pack/burgsAndStates/generateBurgsAndStates.ts @@ -16,7 +16,7 @@ export function generateBurgsAndStates( vertices: IGraphVertices, cells: Pick< IPack["cells"], - "v" | "c" | "p" | "i" | "g" | "h" | "f" | "t" | "haven" | "harbor" | "r" | "fl" | "biome" | "s" | "culture" + "v" | "c" | "p" | "b" | "i" | "g" | "h" | "f" | "t" | "haven" | "harbor" | "r" | "fl" | "biome" | "s" | "culture" > ): {burgIds: Uint16Array; stateIds: Uint16Array; burgs: TBurgs; states: TStates} { const cellsNumber = cells.i.length; @@ -34,9 +34,13 @@ export function generateBurgsAndStates( const capitals = createCapitals(statesNumber, scoredCellIds, cultures, pick(cells, "p", "f", "culture")); const capitalCells = new Map(capitals.map(({cell}) => [cell, true])); - const states = createStates(capitals, cultures); - const towns = createTowns(capitalCells, cultures, pick(cells, "p", "i", "f", "s", "culture")); + + const towns = createTowns( + cultures, + scoredCellIds.filter(i => !capitalCells.has(i)), + pick(cells, "p", "i", "f", "s", "culture") + ); const stateIds = expandStates( capitalCells, @@ -63,11 +67,12 @@ export function generateBurgsAndStates( return {burgIds, stateIds, burgs, states}; function getScoredCellIds() { - // cell score for capitals placement const score = new Int16Array(cells.s.map(s => s * Math.random())); - // filtered and sorted array of indexes - const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); + // filtered and sorted array of indexes: only populated cells not on map edge + const sorted = cells.i + .filter(i => !cells.b[i] && score[i] > 0 && cells.culture[i]) + .sort((a, b) => score[b] - score[a]); return sorted; } diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 16005c6f..915f3a63 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -104,7 +104,7 @@ export function createPack(grid: IGrid): IPack { rawRivers, vertices, { - ...pick(cells, "v", "c", "p", "i", "g"), + ...pick(cells, "v", "c", "p", "b", "i", "g"), h: heights, f: featureIds, t: distanceField, diff --git a/src/utils/debugUtils.ts b/src/utils/debugUtils.ts index e17c98c3..f72c614f 100644 --- a/src/utils/debugUtils.ts +++ b/src/utils/debugUtils.ts @@ -58,7 +58,7 @@ export function drawText(text: string | number, [x, y]: TPoint, {size = 6, color } export function drawPolygons( - values: TypedArray, + values: TypedArray | number[], cellVertices: number[][], vertexPoints: TPoints, { From 8abf443f700cd785d61bc6043f22ebe2b3557337 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 29 Aug 2022 22:40:06 +0300 Subject: [PATCH 09/12] refactor: render covering layers --- package.json | 1 + src/assets/styles/ancient.json | 4 +- src/assets/styles/atlas.json | 4 +- src/assets/styles/clean.json | 4 +- src/assets/styles/cyberpunk.json | 4 +- src/assets/styles/default.json | 4 +- src/assets/styles/gloom.json | 4 +- src/assets/styles/light.json | 4 +- src/assets/styles/monochrome.json | 4 +- src/assets/styles/watercolor.json | 4 +- src/index.css | 2 +- src/layers/renderers/drawBiomes.js | 61 ------- src/layers/renderers/drawBiomes.ts | 29 ++++ src/layers/renderers/drawCultures.ts | 46 ++---- src/layers/renderers/drawFeatures.ts | 2 +- src/layers/renderers/drawReligions.js | 91 ----------- src/layers/renderers/drawReligions.ts | 28 ++++ src/layers/renderers/drawRoutes.ts | 5 +- src/layers/renderers/drawStates.js | 149 ------------------ src/layers/renderers/drawStates.ts | 48 ++++++ .../{utilts.ts => utils/getVertexPaths.ts} | 19 ++- src/scripts/generation/generation.ts | 12 +- .../pack/religions/generateReligions.ts | 2 - src/types/pack/states.d.ts | 1 + yarn.lock | 5 + 25 files changed, 171 insertions(+), 366 deletions(-) delete mode 100644 src/layers/renderers/drawBiomes.js create mode 100644 src/layers/renderers/drawBiomes.ts delete mode 100644 src/layers/renderers/drawReligions.js create mode 100644 src/layers/renderers/drawReligions.ts delete mode 100644 src/layers/renderers/drawStates.js create mode 100644 src/layers/renderers/drawStates.ts rename src/layers/renderers/{utilts.ts => utils/getVertexPaths.ts} (84%) diff --git a/package.json b/package.json index 4ee3b0c0..5754d5fc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@types/delaunator": "^5.0.0", "@types/jquery": "^3.5.14", "@types/jqueryui": "^1.12.16", + "@types/polylabel": "^1.0.5", "c8": "^7.12.0", "happy-dom": "^6.0.4", "rollup": "^2.75.7", diff --git a/src/assets/styles/ancient.json b/src/assets/styles/ancient.json index ffc3e775..3f5b3281 100644 --- a/src/assets/styles/ancient.json +++ b/src/assets/styles/ancient.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.7, "stroke": "#404040", - "stroke-width": 0.7, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/atlas.json b/src/assets/styles/atlas.json index c26848a0..6a6917c3 100644 --- a/src/assets/styles/atlas.json +++ b/src/assets/styles/atlas.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.7, "stroke": "#777777", - "stroke-width": 0, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/clean.json b/src/assets/styles/clean.json index fd00c5ad..4fed8dea 100644 --- a/src/assets/styles/clean.json +++ b/src/assets/styles/clean.json @@ -79,13 +79,13 @@ "#relig": { "opacity": 0.7, "stroke": "#404040", - "stroke-width": 0.7, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/cyberpunk.json b/src/assets/styles/cyberpunk.json index 579d093b..31c493f2 100644 --- a/src/assets/styles/cyberpunk.json +++ b/src/assets/styles/cyberpunk.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.5, "stroke": "#404040", - "stroke-width": 2, + "stroke-width": 3, "filter": "url(#splotch)" }, "#cults": { "opacity": 0.35, "stroke": "#777777", - "stroke-width": 2, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": "url(#splotch)" diff --git a/src/assets/styles/default.json b/src/assets/styles/default.json index 37a52b33..775bdbd2 100644 --- a/src/assets/styles/default.json +++ b/src/assets/styles/default.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.7, "stroke": "#777777", - "stroke-width": 0, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/gloom.json b/src/assets/styles/gloom.json index 95dee382..b5096a95 100644 --- a/src/assets/styles/gloom.json +++ b/src/assets/styles/gloom.json @@ -79,13 +79,13 @@ "#relig": { "opacity": 0.7, "stroke": "#404040", - "stroke-width": 1, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.7, "stroke": "#777777", - "stroke-width": 1.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/light.json b/src/assets/styles/light.json index 6a225e86..b6dd72ed 100644 --- a/src/assets/styles/light.json +++ b/src/assets/styles/light.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.5, "stroke": null, - "stroke-width": 0, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.5, "stroke": "#777777", - "stroke-width": 0, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/monochrome.json b/src/assets/styles/monochrome.json index e94f5a7b..0c843f0c 100644 --- a/src/assets/styles/monochrome.json +++ b/src/assets/styles/monochrome.json @@ -79,13 +79,13 @@ "#relig": { "opacity": 0.7, "stroke": "#404040", - "stroke-width": 0.7, + "stroke-width": 3, "filter": null }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": null diff --git a/src/assets/styles/watercolor.json b/src/assets/styles/watercolor.json index 99908126..24d7787b 100644 --- a/src/assets/styles/watercolor.json +++ b/src/assets/styles/watercolor.json @@ -78,13 +78,13 @@ "#relig": { "opacity": 0.7, "stroke": "#777777", - "stroke-width": 0, + "stroke-width": 3, "filter": "url(#bluredSplotch)" }, "#cults": { "opacity": 0.6, "stroke": "#777777", - "stroke-width": 0.5, + "stroke-width": 3, "stroke-dasharray": null, "stroke-linecap": null, "filter": "url(#splotch)" diff --git a/src/index.css b/src/index.css index b5180a25..a626de1c 100644 --- a/src/index.css +++ b/src/index.css @@ -102,7 +102,7 @@ a { } #biomes { - stroke-width: 0.7; + stroke-width: 3; } #landmass { diff --git a/src/layers/renderers/drawBiomes.js b/src/layers/renderers/drawBiomes.js deleted file mode 100644 index 5e406cb5..00000000 --- a/src/layers/renderers/drawBiomes.js +++ /dev/null @@ -1,61 +0,0 @@ -import {clipPoly} from "utils/lineUtils"; -import {TIME} from "config/logging"; - -export function drawBiomes() { - TIME && console.time("drawBiomes"); - biomes.selectAll("path").remove(); - - const {cells, vertices} = pack; - const n = cells.i.length; - - const used = new Uint8Array(cells.i.length); - const paths = new Array(biomesData.i.length).fill(""); - - for (const i of cells.i) { - if (!cells.biome[i]) continue; // no need to mark marine biome (liquid water) - if (used[i]) continue; // already marked - const b = cells.biome[i]; - const onborder = cells.c[i].some(n => cells.biome[n] !== b); - if (!onborder) continue; - const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b)); - const chain = connectVertices(edgeVerticle, b); - if (chain.length < 3) continue; - const points = clipPoly(chain.map(v => vertices.p[v])); - paths[b] += "M" + points.join("L") + "Z"; - } - - paths.forEach(function (d, i) { - if (d.length < 10) return; - biomes - .append("path") - .attr("d", d) - .attr("fill", biomesData.color[i]) - .attr("stroke", biomesData.color[i]) - .attr("id", "biome" + i); - }); - - // connect vertices to chain - function connectVertices(start, b) { - const chain = []; // vertices chain to form a path - for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { - const prev = chain[chain.length - 1]; // previous vertex in chain - chain.push(current); // add current vertex to sequence - const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.biome[c] === b).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.biome[c[0]] !== b; - const c1 = c[1] >= n || cells.biome[c[1]] !== b; - const c2 = c[2] >= n || cells.biome[c[2]] !== b; - const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) current = v[0]; - else if (v[1] !== prev && c1 !== c2) current = v[1]; - else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) { - ERROR && console.error("Next vertex is not found"); - break; - } - } - return chain; - } - - TIME && console.timeEnd("drawBiomes"); -} diff --git a/src/layers/renderers/drawBiomes.ts b/src/layers/renderers/drawBiomes.ts new file mode 100644 index 00000000..382f858f --- /dev/null +++ b/src/layers/renderers/drawBiomes.ts @@ -0,0 +1,29 @@ +import {pick} from "utils/functionUtils"; +import {byId} from "utils/shorthands"; +import {getPaths} from "./utils/getVertexPaths"; + +export function drawBiomes() { + /* global */ const {cells, vertices, features} = pack; + /* global */ const colors = biomesData.color; + + const paths = getPaths({ + getType: (cellId: number) => cells.biome[cellId], + cells: pick(cells, "c", "v", "b", "h", "f"), + vertices, + features, + options: {fill: true, waterGap: true, halo: false} + }); + + console.log(paths); + + const htmlPaths = paths.map(([index, {fill, waterGap}]) => { + const color = colors[Number(index)]; + + return /* html */ ` + + + `; + }); + + byId("biomes")!.innerHTML = htmlPaths.join(""); +} diff --git a/src/layers/renderers/drawCultures.ts b/src/layers/renderers/drawCultures.ts index aa9189ef..9d3cdeae 100644 --- a/src/layers/renderers/drawCultures.ts +++ b/src/layers/renderers/drawCultures.ts @@ -1,44 +1,28 @@ -import * as d3 from "d3"; - -import {getPaths} from "./utilts"; import {pick} from "utils/functionUtils"; +import {byId} from "utils/shorthands"; +import {getPaths} from "./utils/getVertexPaths"; export function drawCultures() { - d3.select("#cults").selectAll("g").remove(); - - /* uses */ const {cells, vertices, features, cultures} = pack; + /* global */ const {cells, vertices, features, cultures} = pack; const paths = getPaths({ getType: (cellId: number) => cells.culture[cellId], cells: pick(cells, "c", "v", "b", "h", "f"), vertices, - features + features, + options: {fill: true, waterGap: true, halo: false} }); - const getColor = (i: number) => (cultures[i] as ICulture).color; + const getColor = (i: string) => (cultures[Number(i)] as ICulture).color; - d3.select("#cults") - .append("g") - .attr("fill", "none") - .attr("stroke-width", 3) - .selectAll("path") - .remove() - .data(paths) - .enter() - .append("path") - .attr("d", ([, path]) => path.waterGap) - .attr("stroke", ([i]) => getColor(Number(i))) - .attr("id", ([i]) => "culture-gap" + i); + const htmlPaths = paths.map(([index, {fill, waterGap}]) => { + const color = getColor(index); - d3.select("#cults") - .append("g") - .attr("stroke", "none") - .selectAll("path") - .remove() - .data(paths) - .enter() - .append("path") - .attr("d", ([, path]) => path.fill) - .attr("fill", ([i]) => getColor(Number(i))) - .attr("id", ([i]) => "culture" + i); + return /* html */ ` + + + `; + }); + + byId("cults")!.innerHTML = htmlPaths.join(""); } diff --git a/src/layers/renderers/drawFeatures.ts b/src/layers/renderers/drawFeatures.ts index 7832e84a..41ccba46 100644 --- a/src/layers/renderers/drawFeatures.ts +++ b/src/layers/renderers/drawFeatures.ts @@ -5,7 +5,7 @@ import {filterOutOfCanvasPoints} from "utils/lineUtils"; import {round} from "utils/stringUtils"; export function drawFeatures() { - /* uses */ const {vertices, features} = pack; + /* global */ const {vertices, features} = pack; const landMask = defs.select("#land"); const waterMask = defs.select("#water"); diff --git a/src/layers/renderers/drawReligions.js b/src/layers/renderers/drawReligions.js deleted file mode 100644 index e81810b4..00000000 --- a/src/layers/renderers/drawReligions.js +++ /dev/null @@ -1,91 +0,0 @@ -export function drawReligions() { - relig.selectAll("path").remove(); - const {cells, vertices, religions} = pack; - const n = cells.i.length; - - const used = new Uint8Array(cells.i.length); - const body = new Array(religions.length).fill(""); // store path around each religion - const gap = new Array(religions.length).fill(""); // store path along water for each religion to fill the gaps - - for (const i of cells.i) { - if (!cells.religion[i]) continue; - if (used[i]) continue; - used[i] = 1; - const r = cells.religion[i]; - const onborder = cells.c[i].filter(n => cells.religion[n] !== r); - if (!onborder.length) continue; - const borderWith = cells.c[i].map(c => cells.religion[c]).find(n => n !== r); - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.religion[i] === borderWith)); - const chain = connectVertices(vertex, r, borderWith); - if (chain.length < 3) continue; - const points = chain.map(v => vertices.p[v[0]]); - - body[r] += "M" + points.join("L") + "Z"; - gap[r] += - "M" + - vertices.p[chain[0][0]] + - chain.reduce( - (r2, v, i, d) => - !i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2, - "" - ); - } - - const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]); - relig - .selectAll("path") - .data(bodyData) - .enter() - .append("path") - .attr("d", d => d[0]) - .attr("fill", d => d[2]) - .attr("id", d => "religion" + d[1]); - - const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]); - relig - .selectAll(".path") - .data(gapData) - .enter() - .append("path") - .attr("d", d => d[0]) - .attr("fill", "none") - .attr("stroke", d => d[2]) - .attr("id", d => "religion-gap" + d[1]) - .attr("stroke-width", "10px"); - - // connect vertices to chain - function connectVertices(start, t, religion) { - const chain = []; // vertices chain to form a path - let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.religion[c] !== t); - function check(i) { - religion = cells.religion[i]; - land = cells.h[i] >= 20; - } - - for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { - const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain - chain.push([current, religion, land]); // add current vertex to sequence - const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.religion[c] === t).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.religion[c[0]] !== t; - const c1 = c[1] >= n || cells.religion[c[1]] !== t; - const c2 = c[2] >= n || cells.religion[c[2]] !== t; - const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) { - current = v[0]; - check(c0 ? c[0] : c[1]); - } else if (v[1] !== prev && c1 !== c2) { - current = v[1]; - check(c1 ? c[1] : c[2]); - } else if (v[2] !== prev && c0 !== c2) { - current = v[2]; - check(c2 ? c[2] : c[0]); - } - if (current === chain[chain.length - 1][0]) { - ERROR && console.error("Next vertex is not found"); - break; - } - } - return chain; - } -} diff --git a/src/layers/renderers/drawReligions.ts b/src/layers/renderers/drawReligions.ts new file mode 100644 index 00000000..19c63354 --- /dev/null +++ b/src/layers/renderers/drawReligions.ts @@ -0,0 +1,28 @@ +import {pick} from "utils/functionUtils"; +import {byId} from "utils/shorthands"; +import {getPaths} from "./utils/getVertexPaths"; + +export function drawReligions() { + /* global */ const {cells, vertices, features, religions} = pack; + + const paths = getPaths({ + getType: (cellId: number) => cells.religion[cellId], + cells: pick(cells, "c", "v", "b", "h", "f"), + vertices, + features, + options: {fill: true, waterGap: true, halo: false} + }); + + const getColor = (i: string) => (religions[Number(i)] as IReligion).color; + + const htmlPaths = paths.map(([index, {fill, waterGap}]) => { + const color = getColor(index); + + return /* html */ ` + + + `; + }); + + byId("relig")!.innerHTML = htmlPaths.join(""); +} diff --git a/src/layers/renderers/drawRoutes.ts b/src/layers/renderers/drawRoutes.ts index f6362490..dc269981 100644 --- a/src/layers/renderers/drawRoutes.ts +++ b/src/layers/renderers/drawRoutes.ts @@ -10,9 +10,7 @@ const lineGenTypeMap: {[key in IRoute["type"]]: d3.CurveFactory | d3.CurveFactor }; export function drawRoutes() { - routes.selectAll("path").remove(); - - /* uses */ const {cells, burgs} = pack; + /* global */ const {cells, burgs} = pack; const lineGen = d3.line(); const SHARP_ANGLE = 135; @@ -32,6 +30,7 @@ export function drawRoutes() { routePaths[type].push(``); } + routes.selectAll("path").remove(); for (const type in routePaths) { routes.select(`[data-type=${type}]`).html(routePaths[type].join("")); } diff --git a/src/layers/renderers/drawStates.js b/src/layers/renderers/drawStates.js deleted file mode 100644 index 2c7e1cd7..00000000 --- a/src/layers/renderers/drawStates.js +++ /dev/null @@ -1,149 +0,0 @@ -import * as d3 from "d3"; - -import polylabel from "polylabel"; - -export function drawStates() { - regions.selectAll("path").remove(); - - const {cells, vertices, features} = pack; - const states = pack.states; - const n = cells.i.length; - - const used = new Uint8Array(cells.i.length); - const vArray = new Array(states.length); // store vertices array - const body = new Array(states.length).fill(""); // path around each state - const gap = new Array(states.length).fill(""); // path along water for each state to fill the gaps - const halo = new Array(states.length).fill(""); // path around states, but not lakes - - const getStringPoint = v => vertices.p[v[0]].join(","); - - // define inner-state lakes to omit on border render - const innerLakes = features.map(feature => { - if (feature.type !== "lake") return false; - - const shoreline = feature.shoreline || []; - const states = shoreline.map(i => cells.state[i]); - return new Set(states).size > 1 ? false : true; - }); - - for (const i of cells.i) { - if (!cells.state[i] || used[i]) continue; - const state = cells.state[i]; - - const onborder = cells.c[i].some(n => cells.state[n] !== state); - if (!onborder) continue; - - const borderWith = cells.c[i].map(c => cells.state[c]).find(n => n !== state); - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.state[i] === borderWith)); - const chain = connectVertices(vertex, state); - - const noInnerLakes = chain.filter(v => v[1] !== "innerLake"); - if (noInnerLakes.length < 3) continue; - - // get path around the state - if (!vArray[state]) vArray[state] = []; - const points = noInnerLakes.map(v => vertices.p[v[0]]); - vArray[state].push(points); - body[state] += "M" + points.join("L"); - - // connect path for halo - let discontinued = true; - halo[state] += noInnerLakes - .map(v => { - if (v[1] === "border") { - discontinued = true; - return ""; - } - - const operation = discontinued ? "M" : "L"; - discontinued = false; - return `${operation}${getStringPoint(v)}`; - }) - .join(""); - - // connect gaps between state and water into a single path - discontinued = true; - gap[state] += chain - .map(v => { - if (v[1] === "land") { - discontinued = true; - return ""; - } - - const operation = discontinued ? "M" : "L"; - discontinued = false; - return `${operation}${getStringPoint(v)}`; - }) - .join(""); - } - - // find state visual center - vArray.forEach((ar, i) => { - const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number - states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility - }); - - const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - - const bodyString = bodyData.map(d => ``).join(""); - const gapString = gapData.map(d => ``).join(""); - const clipString = bodyData - .map(d => ``) - .join(""); - const haloString = haloData - .map( - d => - `` - ) - .join(""); - - statesBody.html(bodyString + gapString); - defs.select("#statePaths").html(clipString); - statesHalo.html(haloString); - - // connect vertices to chain - function connectVertices(start, state) { - const chain = []; // vertices chain to form a path - const getType = c => { - const borderCell = c.find(i => cells.b[i]); - if (borderCell) return "border"; - - const waterCell = c.find(i => cells.h[i] < 20); - if (!waterCell) return "land"; - if (innerLakes[cells.f[waterCell]]) return "innerLake"; - return features[cells.f[waterCell]].type; - }; - - for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { - const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain - - const c = vertices.c[current]; // cells adjacent to vertex - chain.push([current, getType(c)]); // add current vertex to sequence - - c.filter(c => cells.state[c] === state).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.state[c[0]] !== state; - const c1 = c[1] >= n || cells.state[c[1]] !== state; - const c2 = c[2] >= n || cells.state[c[2]] !== state; - - const v = vertices.v[current]; // neighboring vertices - - if (v[0] !== prev && c0 !== c1) current = v[0]; - else if (v[1] !== prev && c1 !== c2) current = v[1]; - else if (v[2] !== prev && c0 !== c2) current = v[2]; - - if (current === prev) { - ERROR && console.error("Next vertex is not found"); - break; - } - } - - if (chain.length) chain.push(chain[0]); - return chain; - } - - Zoom.invoke(); -} diff --git a/src/layers/renderers/drawStates.ts b/src/layers/renderers/drawStates.ts new file mode 100644 index 00000000..1c5f65b2 --- /dev/null +++ b/src/layers/renderers/drawStates.ts @@ -0,0 +1,48 @@ +import * as d3 from "d3"; + +import {pick} from "utils/functionUtils"; +import {byId} from "utils/shorthands"; +import {getPaths} from "./utils/getVertexPaths"; + +export function drawStates() { + /* global */ const {cells, vertices, features, states} = pack; + + const paths = getPaths({ + getType: (cellId: number) => cells.state[cellId], + cells: pick(cells, "c", "v", "b", "h", "f"), + vertices, + features, + options: {fill: true, waterGap: true, halo: true} + }); + + const getColor = (i: number) => (states[i] as IState).color; + + const maxLength = states.length - 1; + const bodyPaths = new Array(maxLength); + const clipPaths = new Array(maxLength); + const haloPaths = new Array(maxLength); + + for (const [index, {fill, waterGap, halo}] of paths) { + const color = getColor(Number(index)); + const haloColor = d3.color(color)?.darker().formatHex() || "#666666"; + + bodyPaths.push(/* html */ ` + + + `); + + clipPaths.push(/* html */ ` + + `); + + haloPaths.push(/* html */ ` + + `); + } + + byId("statesBody")!.innerHTML = bodyPaths.join(""); + byId("statePaths")!.innerHTML = clipPaths.join(""); + byId("statesHalo")!.innerHTML = haloPaths.join(""); + + /* global */ window.Zoom.invoke(); +} diff --git a/src/layers/renderers/utilts.ts b/src/layers/renderers/utils/getVertexPaths.ts similarity index 84% rename from src/layers/renderers/utilts.ts rename to src/layers/renderers/utils/getVertexPaths.ts index 7931004a..6231c359 100644 --- a/src/layers/renderers/utilts.ts +++ b/src/layers/renderers/utils/getVertexPaths.ts @@ -8,12 +8,14 @@ export function getPaths({ vertices, getType, features, - cells + cells, + options }: { vertices: IGraphVertices; getType: (cellId: number) => number; features: TPackFeatures; cells: Pick; + options: {[key in keyof TPath]: boolean}; }) { const paths: Dict = {}; @@ -55,11 +57,12 @@ export function getPaths({ function getFillPath(vertexChain: number[]) { const points: TPoints = vertexChain.map(getVertexPoint); const firstPoint = points.shift(); - return `M${firstPoint} L${points.join(" ")} Z`; + return `M${firstPoint} L${points.join(" ")}`; } function getBorderPath(vertexChain: number[], discontinue: (vertex: number) => boolean) { let discontinued = true; + let lastOperation = ""; const path = vertexChain.map(vertex => { if (discontinue(vertex)) { discontinued = true; @@ -67,8 +70,12 @@ export function getPaths({ } const operation = discontinued ? "M" : "L"; + const command = operation === lastOperation ? "" : operation; + discontinued = false; - return ` ${operation}${getVertexPoint(vertex)}`; + lastOperation = operation; + + return ` ${command}${getVertexPoint(vertex)}`; }); return path.join("").trim(); @@ -87,9 +94,9 @@ export function getPaths({ function addPath(index: number, vertexChain: number[]) { if (!paths[index]) paths[index] = {fill: "", waterGap: "", halo: ""}; - paths[index].fill += getFillPath(vertexChain); - paths[index].halo += getBorderPath(vertexChain, isBorderVertex); - paths[index].waterGap += getBorderPath(vertexChain, isLandVertex); + if (options.fill) paths[index].fill += getFillPath(vertexChain); + if (options.halo) paths[index].halo += getBorderPath(vertexChain, isBorderVertex); + if (options.waterGap) paths[index].waterGap += getBorderPath(vertexChain, isLandVertex); } } diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index 6d776f82..0b83ef0f 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -26,7 +26,8 @@ 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"; +import {drawPoint, drawPolygons} from "utils/debugUtils"; +import {isReligion} from "utils/typeUtils"; const {Zoom, ThreeD} = window; @@ -70,10 +71,15 @@ async function generate(options?: IGenerationOptions) { // renderLayer("biomes"); renderLayer("burgs"); renderLayer("routes"); - renderLayer("cultures"); - //renderLayer("religions"); + // renderLayer("states"); + renderLayer("religions"); // drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true}); + pack.religions.filter(isReligion).forEach(({center}) => + drawPoint(pack.cells.p[center], { + radius: 5 + }) + ); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 8a92c224..c0dfe85a 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -32,8 +32,6 @@ export function generateReligions({ pick(cells, "i", "c", "biome", "culture", "burg", "state", "route") ); - console.log(religions); - TIME && console.timeEnd("generateReligions"); return {religionIds, religions}; } diff --git a/src/types/pack/states.d.ts b/src/types/pack/states.d.ts index 01c216e8..1a104e4b 100644 --- a/src/types/pack/states.d.ts +++ b/src/types/pack/states.d.ts @@ -9,6 +9,7 @@ interface IState { fullName: string; capital: Logical; coa: ICoa | string; + // pole: TPoint ? removed?: boolean; } diff --git a/yarn.lock b/yarn.lock index b0f6d4ff..e5e80b0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -422,6 +422,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@types/polylabel@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/polylabel/-/polylabel-1.0.5.tgz#9262f269de36f1e9248aeb9dee0ee9d10065e043" + integrity sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w== + "@types/qs@^6.2.31": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" From 5361565cd72db888b185b672347ccde4b7a794a7 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 31 Aug 2022 00:58:08 +0300 Subject: [PATCH 10/12] refactor: render covering layers fix --- src/scripts/connectVertices.ts | 2 +- src/scripts/generation/generation.ts | 9 -------- .../pack/religions/generateReligions.ts | 4 ++++ .../pack/religions/specifyReligions.ts | 17 ++++++++++---- src/utils/colorUtils.ts | 23 +++++++++++++------ 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/scripts/connectVertices.ts b/src/scripts/connectVertices.ts index afb8052b..f493e257 100644 --- a/src/scripts/connectVertices.ts +++ b/src/scripts/connectVertices.ts @@ -113,7 +113,7 @@ export function connectVertices({ chain.push(current); const neibCells = vertices.c[current]; - if (addToChecked) neibCells.forEach(addToChecked); + if (addToChecked) neibCells.filter(ofSameType).forEach(addToChecked); const [c1, c2, c3] = neibCells.map(ofSameType); const [v1, v2, v3] = vertices.v[current]; diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index 0b83ef0f..a6c6ef1c 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -26,8 +26,6 @@ import {createGrid} from "./grid/grid"; import {createPack} from "./pack/pack"; import {getInputValue, setInputValue} from "utils/nodeUtils"; import {calculateMapCoordinates} from "modules/coordinates"; -import {drawPoint, drawPolygons} from "utils/debugUtils"; -import {isReligion} from "utils/typeUtils"; const {Zoom, ThreeD} = window; @@ -74,13 +72,6 @@ async function generate(options?: IGenerationOptions) { // renderLayer("states"); renderLayer("religions"); - // drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true}); - pack.religions.filter(isReligion).forEach(({center}) => - drawPoint(pack.cells.p[center], { - radius: 5 - }) - ); - WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); INFO && console.groupEnd(); diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index c0dfe85a..299d184e 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -1,4 +1,5 @@ import {TIME} from "config/logging"; +import {drawPoint} from "utils/debugUtils"; import {pick} from "utils/functionUtils"; import {generateFolkReligions} from "./generateFolkReligions"; import {generateOrganizedReligions} from "./generateOrganizedReligions"; @@ -32,6 +33,9 @@ export function generateReligions({ pick(cells, "i", "c", "biome", "culture", "burg", "state", "route") ); + folkReligions.forEach(({center}) => drawPoint(cells.p[center], {radius: 3, color: "blue"})); + basicReligions.forEach(({center}) => drawPoint(cells.p[center], {radius: 3, color: "red"})); + TIME && console.timeEnd("generateReligions"); return {religionIds, religions}; } diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts index 2054503f..f9fa9061 100644 --- a/src/scripts/generation/pack/religions/specifyReligions.ts +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -1,4 +1,4 @@ -import {getMixedColor, getRandomColor} from "utils/colorUtils"; +import {brighter, getMixedColor} from "utils/colorUtils"; import {each, gauss, rand} from "utils/probabilityUtils"; import {isCulture} from "utils/typeUtils"; import {expandReligions} from "./expandReligions"; @@ -12,8 +12,10 @@ const expansionismMap = { Heresy: () => gauss(1.2, 0.5, 0, 5) }; +type TReligionData = Pick; + export function specifyReligions( - religionsData: Pick[], + religionsData: TReligionData[], cultures: TCultures, states: TStates, burgs: TBurgs, @@ -39,8 +41,7 @@ export function specifyReligions( const expansionism = expansionismMap[type](); - const culture = cultures[cultureId]; - const color = isCulture(culture) ? getMixedColor(culture.color, 0.1, 0) : getRandomColor(); + const color = getReligionColor(cultureId, type); return {i: index + 1, name, type, form, culture: cultureId, center, deity, expansion, expansionism, color}; }); @@ -51,6 +52,14 @@ export function specifyReligions( return {religions: combineReligionsData(), religionIds}; + function getReligionColor(cultureId: number, type: IReligion["type"]) { + const culture = cultures[cultureId]; + if (!isCulture(culture)) throw new Error(`Culture ${cultureId} is not a valid culture`); + + if (type === "Folk") return brighter(culture.color, 0.2); + return getMixedColor(culture.color, 0.2, 0); + } + function combineReligionsData(): TReligions { const noReligion: TNoReligion = {i: 0, name: "No religion"}; diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index 36f4a604..94f5f2ff 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -25,7 +25,7 @@ const colorSchemeMap: {[key in TColorScheme]: d3.ScaleSequential} = { monochrome: d3.scaleSequential(d3.interpolateGreys) }; -export function getColors(number: number) { +export function getColors(number: number): Hex[] { if (number <= cardinal12.length) return d3.shuffle(cardinal12.slice(0, number)); const scheme = colorSchemeMap.default; @@ -40,19 +40,28 @@ export function getColors(number: number) { } export function getRandomColor(): Hex { - const scheme = colorSchemeMap.default; + const scheme = d3.scaleSequential(d3.interpolateSpectral); const rgb = scheme(Math.random())!; return d3.color(rgb)?.formatHex() as Hex; } -// mix a color with a random color -export function getMixedColor(hexColor: string, mixation = 0.2, bright = 0.3) { - // if provided color is not hex (e.g. harching), generate random one - const color1 = hexColor && hexColor[0] === "#" ? hexColor : getRandomColor(); +// mix a color with a random color. TODO: refactor without interpolation +export function getMixedColor(color: Hex | CssUrl, mixation = 0.2, bright = 0.3) { + const color1 = color.startsWith("#") ? color : getRandomColor(); const color2 = getRandomColor(); const mixedColor = d3.interpolate(color1, color2)(mixation); - return d3.color(mixedColor)!.brighter(bright).hex(); + return d3.color(mixedColor)!.brighter(bright).formatHex() as Hex; +} + +export function darker(color: Hex | CssUrl, amount = 1) { + if (color.startsWith("#") === false) return color; + return d3.color(color)!.darker(amount).formatHex() as Hex; +} + +export function brighter(color: Hex | CssUrl, amount = 1) { + if (color.startsWith("#") === false) return color; + return d3.color(color)!.brighter(amount).formatHex() as Hex; } export function getColorScheme(schemeName: TColorScheme) { From b9ff6a652c0edd0ef171087623a254e649964a26 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 1 Sep 2022 00:22:46 +0300 Subject: [PATCH 11/12] refactor: refactor religions expand --- index.html | 2 +- src/dialogs/dialogs/states-editor.js | 4 +-- src/modules/burgs-and-states.js | 6 ++-- src/modules/ui/options.js | 2 +- src/scripts/events/index.ts | 8 +++-- src/scripts/events/onhover.ts | 5 +--- src/scripts/generation/generation.ts | 5 ++++ src/scripts/generation/pack/generateRoutes.ts | 4 +-- src/scripts/generation/pack/pack.ts | 6 ++-- .../pack/religions/expandReligions.ts | 30 ++++++++++++------- .../pack/religions/generateReligions.ts | 2 +- .../pack/religions/specifyReligions.ts | 17 ++++++----- src/utils/colorUtils.ts | 4 +-- 13 files changed, 55 insertions(+), 40 deletions(-) diff --git a/index.html b/index.html index c4280dd9..4806603e 100644 --- a/index.html +++ b/index.html @@ -1639,7 +1639,7 @@ Religions number - + diff --git a/src/dialogs/dialogs/states-editor.js b/src/dialogs/dialogs/states-editor.js index a4f96fd5..c5d797a7 100644 --- a/src/dialogs/dialogs/states-editor.js +++ b/src/dialogs/dialogs/states-editor.js @@ -1119,7 +1119,7 @@ function adjustProvinces(affectedProvinces) { // reassign province ownership to province center owner prevOwner.provinces = prevOwner.provinces.filter(province => province !== provinceId); province.state = stateId; - province.color = getMixedColor(states[stateId].color); + province.color = brighter(getMixedColor(states[stateId].color, 0.2), 0.3); states[stateId].provinces.push(provinceId); return; } @@ -1163,7 +1163,7 @@ function adjustProvinces(affectedProvinces) { const formOptions = ["Zone", "Area", "Territory", "Province"]; const formName = burgCell && oldProvince.formName ? oldProvince.formName : ra(formOptions); - const color = getMixedColor(states[stateId].color); + const color = brighter(getMixedColor(states[stateId].color, 0.2), 0.3); const kinship = nameByBurg ? 0.8 : 0.4; const type = BurgsAndStates.getType(center, burg?.port); diff --git a/src/modules/burgs-and-states.js b/src/modules/burgs-and-states.js index 5d6eb086..be4b359e 100644 --- a/src/modules/burgs-and-states.js +++ b/src/modules/burgs-and-states.js @@ -780,7 +780,7 @@ window.BurgsAndStates = (function () { const sameColored = pack.states.filter(s => s.color === c); sameColored.forEach((s, d) => { if (!d) return; - s.color = getMixedColor(s.color); + s.color = brighter(getMixedColor(s.color, 0.2), 0.3); }); }); @@ -1209,7 +1209,7 @@ window.BurgsAndStates = (function () { const formName = rw(form); form[formName] += 10; const fullName = name + " " + formName; - const color = getMixedColor(s.color); + const color = brighter(getMixedColor(s.color, 0.2), 0.3); const kinship = nameByBurg ? 0.8 : 0.4; const type = getType(center, burg.port); const coa = COA.generate(stateBurgs[i].coa, kinship, null, type); @@ -1317,7 +1317,7 @@ window.BurgsAndStates = (function () { // generate "wild" province name const cultureId = cells.culture[center]; const f = pack.features[cells.f[center]]; - const color = getMixedColor(s.color); + const color = brighter(getMixedColor(s.color, 0.2), 0.3); const provCells = stateNoProvince.filter(i => cells.province[i] === province); const singleIsle = provCells.length === f.cells && !provCells.find(i => cells.f[i] !== f.i); diff --git a/src/modules/ui/options.js b/src/modules/ui/options.js index 5c864778..fcb5dd6c 100644 --- a/src/modules/ui/options.js +++ b/src/modules/ui/options.js @@ -566,7 +566,7 @@ export function randomizeOptions() { manorsInput.value = 1000; manorsOutput.value = "auto"; } - if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(5, 2, 2, 10); + if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(6, 3, 2, 10); if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(4, 2, 0, 10, 2); if (randomize || !locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1); if (randomize || !locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30); diff --git a/src/scripts/events/index.ts b/src/scripts/events/index.ts index 0d3d7c8c..977cfb3f 100644 --- a/src/scripts/events/index.ts +++ b/src/scripts/events/index.ts @@ -4,14 +4,16 @@ import {openDialog} from "dialogs"; import {tip} from "scripts/tooltips"; import {handleMapClick} from "./onclick"; import {onMouseMove} from "./onhover"; -// @ts-expect-error js module import {clearLegend, dragLegendBox} from "modules/legend"; export function setDefaultEventHandlers() { window.Zoom.setZoomBehavior(); - viewbox.style("cursor", "default").on(".drag", null).on("click", handleMapClick); - //.on("touchmove mousemove", onMouseMove); + viewbox + .style("cursor", "default") + .on(".drag", null) + .on("click", handleMapClick) + .on("touchmove mousemove", onMouseMove); scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => openDialog("unitsEditor")); diff --git a/src/scripts/events/onhover.ts b/src/scripts/events/onhover.ts index e0b2693c..ac1ce961 100644 --- a/src/scripts/events/onhover.ts +++ b/src/scripts/events/onhover.ts @@ -1,9 +1,6 @@ import * as d3 from "d3"; import {layerIsOn} from "layers"; -// @ts-expect-error js module -import {clearLegend, dragLegendBox} from "modules/legend"; -// @ts-expect-error js module import {updateCellInfo} from "modules/ui/cell-info"; import {debounce} from "utils/functionUtils"; import {findCell, findGridCell, isLand} from "utils/graphUtils"; @@ -78,7 +75,7 @@ const getHoveredElement = (tagName: string, group: string, subgroup: string, isL if (layerIsOn("togglePopulation")) return "populationLayer"; if (layerIsOn("toggleTemp")) return "temperatureLayer"; if (layerIsOn("toggleBiomes") && biome[cellId]) return "biomesLayer"; - if (layerIsOn("toggleReligions") && religion[cellId]) return "religionsLayer"; + if (religion[cellId]) return "religionsLayer"; // layerIsOn("toggleReligions") && if (layerIsOn("toggleProvinces") || (layerIsOn("toggleStates") && state[cellId])) return "statesLayer"; if (layerIsOn("toggleCultures") && culture[cellId]) return "culturesLayer"; if (layerIsOn("toggleHeight")) return "heightLayer"; diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index a6c6ef1c..614840b4 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 {drawPoint} from "utils/debugUtils"; const {Zoom, ThreeD} = window; @@ -72,6 +73,10 @@ async function generate(options?: IGenerationOptions) { // renderLayer("states"); renderLayer("religions"); + // pack.cells.route.forEach((route, index) => { + // if (route === 2) drawPoint(pack.cells.p[index], {color: "black"}); + // }); + WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); INFO && console.groupEnd(); diff --git a/src/scripts/generation/pack/generateRoutes.ts b/src/scripts/generation/pack/generateRoutes.ts index da280f9a..b7f3e76a 100644 --- a/src/scripts/generation/pack/generateRoutes.ts +++ b/src/scripts/generation/pack/generateRoutes.ts @@ -103,7 +103,7 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const segments = findPathSegments({isWater: true, cellRoutes, connections, start, exit}); for (const segment of segments) { - addConnections(segment, ROUTES.MAIN_ROAD); + addConnections(segment, ROUTES.SEA_ROUTE); mainRoads.push({feature: Number(key), cells: segment}); } }); @@ -118,7 +118,7 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const cellId = segment[i]; const nextCellId = segment[i + 1]; if (nextCellId) connections.set(`${cellId}-${nextCellId}`, true); - cellRoutes[cellId] = roadTypeId; + if (!cellRoutes[cellId]) cellRoutes[cellId] = roadTypeId; } } diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 915f3a63..d110e75d 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -188,11 +188,11 @@ export function createPack(grid: IGrid): IPack { burg: burgIds, state: stateIds, route: cellRoutes, - religion: religionIds - // province + religion: religionIds, + province: new Uint16Array(cells.i.length) }, features: mergedFeatures, - // rivers: rawRivers, // "name" | "basin" | "type" + rivers: rawRivers, // "name" | "basin" | "type" cultures, states, burgs, diff --git a/src/scripts/generation/pack/religions/expandReligions.ts b/src/scripts/generation/pack/religions/expandReligions.ts index 286bca0c..9b0809bf 100644 --- a/src/scripts/generation/pack/religions/expandReligions.ts +++ b/src/scripts/generation/pack/religions/expandReligions.ts @@ -1,12 +1,12 @@ import FlatQueue from "flatqueue"; -import {ROUTES} from "config/generation"; +import {MIN_LAND_HEIGHT, 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,11 +15,9 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { const cost: number[] = []; const neutralInput = getInputNumber("neutralInput"); - const maxExpansionCost = (cells.i.length / 25) * gauss(1, 0.3, 0.2, 2, 2) * neutralInput; + const maxExpansionCost = (cells.i.length / 20) * 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; @@ -32,6 +30,11 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { const religionsMap = new Map(religions.map(religion => [religion.i, religion])); + const isMainRoad = (cellId: number) => cells.route[cellId] === ROUTES.MAIN_ROAD; + const isTrail = (cellId: number) => cells.route[cellId] === ROUTES.TRAIL; + const isSeaRoute = (cellId: number) => cells.route[cellId] === ROUTES.SEA_ROUTE; + const isWater = (cellId: number) => cells.h[cellId] < MIN_LAND_HEIGHT; + while (queue.length) { const priority = queue.peekValue()!; const {cellId, religionId} = queue.pop()!; @@ -39,16 +42,14 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { 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 cultureCost = culture !== cells.culture[neibCellId] ? 10 : 0; + const stateCost = cells.state[center] !== cells.state[neibCellId] ? 10 : 0; + const passageCost = getPassageCost(neibCellId); - const cellCost = Math.max(cultureCost + stateCost + passageCost + waterCost, 0); + const cellCost = cultureCost + stateCost + passageCost; const totalCost = priority + 10 + cellCost / expansionism; if (totalCost > maxExpansionCost) return; @@ -62,6 +63,13 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) { } return religionIds; + + function getPassageCost(cellId: number) { + if (isWater(cellId)) return isSeaRoute(cellId) ? 50 : 500; + if (isMainRoad(cellId)) return 1; + const biomeCost = biomePassageCost(cellId); // [1, 5000] + return isTrail(cellId) ? biomeCost / 1.5 : biomeCost; + } } // folk religions initially get all cells of their culture diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 299d184e..83c23975 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -30,7 +30,7 @@ export function generateReligions({ cultures, states, burgs, - pick(cells, "i", "c", "biome", "culture", "burg", "state", "route") + pick(cells, "i", "c", "h", "biome", "culture", "burg", "state", "route") ); folkReligions.forEach(({center}) => drawPoint(cells.p[center], {radius: 3, color: "blue"})); diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts index f9fa9061..937cd2b0 100644 --- a/src/scripts/generation/pack/religions/specifyReligions.ts +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -1,4 +1,4 @@ -import {brighter, getMixedColor} from "utils/colorUtils"; +import {brighter, darker, getMixedColor} from "utils/colorUtils"; import {each, gauss, rand} from "utils/probabilityUtils"; import {isCulture} from "utils/typeUtils"; import {expandReligions} from "./expandReligions"; @@ -7,19 +7,20 @@ import {generateReligionName} from "./generateReligionName"; const expansionismMap = { Folk: () => 0, - Organized: () => rand(3, 8), - Cult: () => gauss(1.1, 0.5, 0, 5), - Heresy: () => gauss(1.2, 0.5, 0, 5) + Organized: () => gauss(5, 3, 0, 10, 1), + Cult: () => gauss(0.5, 0.5, 0, 5, 1), + Heresy: () => gauss(1, 0.5, 0, 5, 1) }; type TReligionData = Pick; +type TCellsData = Pick; export function specifyReligions( religionsData: TReligionData[], cultures: TCultures, states: TStates, burgs: TBurgs, - cells: Pick + cells: TCellsData ): {religions: TReligions; religionIds: Uint16Array} { const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => { const supreme = getDeityName(cultures, cultureId); @@ -56,8 +57,10 @@ export function specifyReligions( const culture = cultures[cultureId]; if (!isCulture(culture)) throw new Error(`Culture ${cultureId} is not a valid culture`); - if (type === "Folk") return brighter(culture.color, 0.2); - return getMixedColor(culture.color, 0.2, 0); + if (type === "Folk") return culture.color; + if (type === "Heresy") return darker(getMixedColor(culture.color, 0.35), 0.3); + if (type === "Cult") return darker(getMixedColor(culture.color, 0.5), 0.8); + return brighter(getMixedColor(culture.color, 0.25), 0.3); } function combineReligionsData(): TReligions { diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index 94f5f2ff..4d1834a8 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -46,12 +46,12 @@ export function getRandomColor(): Hex { } // mix a color with a random color. TODO: refactor without interpolation -export function getMixedColor(color: Hex | CssUrl, mixation = 0.2, bright = 0.3) { +export function getMixedColor(color: Hex | CssUrl, mixation: number) { const color1 = color.startsWith("#") ? color : getRandomColor(); const color2 = getRandomColor(); const mixedColor = d3.interpolate(color1, color2)(mixation); - return d3.color(mixedColor)!.brighter(bright).formatHex() as Hex; + return d3.color(mixedColor)!.formatHex() as Hex; } export function darker(color: Hex | CssUrl, amount = 1) { From 3f6fca72e60d9ceda751bb30993e642407314e8d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 1 Sep 2022 22:06:09 +0300 Subject: [PATCH 12/12] refactor: religions generation --- src/scripts/generation/pack/religions/generateReligions.ts | 3 --- src/scripts/generation/pack/religions/specifyReligions.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 83c23975..aafa2d27 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -33,9 +33,6 @@ export function generateReligions({ pick(cells, "i", "c", "h", "biome", "culture", "burg", "state", "route") ); - folkReligions.forEach(({center}) => drawPoint(cells.p[center], {radius: 3, color: "blue"})); - basicReligions.forEach(({center}) => drawPoint(cells.p[center], {radius: 3, color: "red"})); - TIME && console.timeEnd("generateReligions"); return {religionIds, religions}; } diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts index 937cd2b0..d35be07e 100644 --- a/src/scripts/generation/pack/religions/specifyReligions.ts +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -1,5 +1,5 @@ import {brighter, darker, getMixedColor} from "utils/colorUtils"; -import {each, gauss, rand} from "utils/probabilityUtils"; +import {each, gauss} from "utils/probabilityUtils"; import {isCulture} from "utils/typeUtils"; import {expandReligions} from "./expandReligions"; import {getDeityName} from "./generateDeityName";