From b2ab69984389210e7fdc38c99a2df135ce2c738e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 10 Sep 2022 22:50:45 +0300 Subject: [PATCH] refactor: render provinces --- src/index.css | 5 +- src/layers/renderers/drawEmblems.js | 3 +- src/layers/renderers/drawProvinces.ts | 151 +++++------------- src/scripts/generation/pack/pack.ts | 3 +- .../pack/provinces/generateCoreProvinces.ts | 3 +- .../pack/provinces/generateProvinces.ts | 6 +- .../pack/provinces/generateWildProvinces.ts | 2 +- .../pack/provinces/specifyProvinces.ts | 20 +++ src/scripts/getPolesOfInaccessibility.ts | 64 ++++++++ src/types/pack/provinces.d.ts | 1 + src/types/pack/states.d.ts | 2 +- src/utils/typeUtils.ts | 3 + 12 files changed, 136 insertions(+), 127 deletions(-) create mode 100644 src/scripts/generation/pack/provinces/specifyProvinces.ts create mode 100644 src/scripts/getPolesOfInaccessibility.ts diff --git a/src/index.css b/src/index.css index a626de1c..39b660e4 100644 --- a/src/index.css +++ b/src/index.css @@ -169,6 +169,7 @@ a { font-size: 0.8em; } +#provincesBody, #statesBody { stroke-width: 3; } @@ -179,10 +180,6 @@ a { stroke-linejoin: round; } -#provincesBody { - stroke-width: 0.2; -} - #statesBody, #provincesBody, #relig, diff --git a/src/layers/renderers/drawEmblems.js b/src/layers/renderers/drawEmblems.js index d571c681..06f1ca1e 100644 --- a/src/layers/renderers/drawEmblems.js +++ b/src/layers/renderers/drawEmblems.js @@ -1,6 +1,5 @@ import * as d3 from "d3"; -import {getProvincesVertices} from "./drawProvinces"; import {minmax, rn} from "utils/numberUtils"; import {byId} from "utils/shorthands"; @@ -42,7 +41,7 @@ export function drawEmblems() { const sizeProvinces = getProvinceEmblemsSize(); const provinceCOAs = validProvinces.map(province => { - if (!province.pole) getProvincesVertices(); + if (!province.pole) throw "Pole is not defined"; const [x, y] = province.pole || pack.cells.p[province.center]; const size = province.coaSize || 1; const shift = (sizeProvinces * size) / 2; diff --git a/src/layers/renderers/drawProvinces.ts b/src/layers/renderers/drawProvinces.ts index 10b4de42..f6c884fc 100644 --- a/src/layers/renderers/drawProvinces.ts +++ b/src/layers/renderers/drawProvinces.ts @@ -1,122 +1,45 @@ -import polylabel from "polylabel"; +import {pick} from "utils/functionUtils"; +import {byId} from "utils/shorthands"; +import {isProvince} from "utils/typeUtils"; +import {getPaths} from "./utils/getVertexPaths"; export function drawProvinces() { - const labelsOn = provs.attr("data-labels") == 1; - provs.selectAll("*").remove(); + /* global */ const {cells, vertices, features, provinces} = pack; - const provinces = pack.provinces; - const {body, gap} = getProvincesVertices(); - - const g = provs.append("g").attr("id", "provincesBody"); - const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll("path") - .data(bodyData) - .enter() - .append("path") - .attr("d", d => d[0]) - .attr("fill", d => d[2]) - .attr("stroke", "none") - .attr("id", d => "province" + d[1]); - const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll(".path") - .data(gapData) - .enter() - .append("path") - .attr("d", d => d[0]) - .attr("fill", "none") - .attr("stroke", d => d[2]) - .attr("id", d => "province-gap" + d[1]); - - const labels = provs.append("g").attr("id", "provinceLabels"); - labels.style("display", `${labelsOn ? "block" : "none"}`); - const labelData = provinces.filter(p => p.i && !p.removed && p.pole); - labels - .selectAll(".path") - .data(labelData) - .enter() - .append("text") - .attr("x", d => d.pole[0]) - .attr("y", d => d.pole[1]) - .attr("id", d => "provinceLabel" + d.i) - .text(d => d.name); -} - -export function getProvincesVertices() { - const cells = pack.cells, - vertices = pack.vertices, - provinces = pack.provinces, - n = cells.i.length; - const used = new Uint8Array(cells.i.length); - const vArray = new Array(provinces.length); // store vertices array - const body = new Array(provinces.length).fill(""); // store path around each province - const gap = new Array(provinces.length).fill(""); // store path along water for each province to fill the gaps - - for (const i of cells.i) { - if (!cells.province[i] || used[i]) continue; - const p = cells.province[i]; - const onborder = cells.c[i].some(n => cells.province[n] !== p); - if (!onborder) continue; - - const borderWith = cells.c[i].map(c => cells.province[c]).find(n => n !== p); - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.province[i] === borderWith)); - const chain = connectVertices(vertex, p, borderWith); - if (chain.length < 3) continue; - const points = chain.map(v => vertices.p[v[0]]); - if (!vArray[p]) vArray[p] = []; - vArray[p].push(points); - body[p] += "M" + points.join("L"); - gap[p] += - "M" + - vertices.p[chain[0][0]] + - chain.reduce( - (r, v, i, d) => - !i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r, - "" - ); - } - - // find province visual center - vArray.forEach((ar, i) => { - const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number - provinces[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility + const paths = getPaths({ + getType: (cellId: number) => cells.province[cellId], + cells: pick(cells, "c", "v", "b", "h", "f"), + vertices, + features, + options: {fill: true, waterGap: true, halo: false} }); - return {body, gap}; + const getColor = (i: string) => (provinces[Number(i)] as IProvince).color; - // connect vertices to chain - function connectVertices(start, t, province) { - const chain = []; // vertices chain to form a path - let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.province[c] !== t); - function check(i) { - province = cells.province[i]; - land = cells.h[i] >= 20; - } + const getLabels = () => { + const renderLabels = byId("provs")!.getAttribute("data-labels") === "1"; + if (!renderLabels) return []; - 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, province, land]); // add current vertex to sequence - const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.province[c] === t).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.province[c[0]] !== t; - const c1 = c[1] >= n || cells.province[c[1]] !== t; - const c2 = c[2] >= n || cells.province[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; - } - } - chain.push([start, province, land]); // add starting vertex to sequence to close the path - return chain; - } + return provinces.filter(isProvince).map(({i, pole: [x, y], name}) => { + return `${name}`; + }); + }; + + const htmlPaths = paths.map(([index, {fill, waterGap}]) => { + const color = getColor(index); + + return /* html */ ` + + + `; + }); + + byId("provs")!.innerHTML = /* html*/ ` + + ${htmlPaths.join("")} + + + ${getLabels().join("")} + + `; } diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index b2917308..e2000597 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -143,9 +143,10 @@ export function createPack(grid: IGrid): IPack { } }); - const {provinceIds, provinces} = generateProvinces(states, burgs, cultures, mergedFeatures, { + const {provinceIds, provinces} = generateProvinces(states, burgs, cultures, mergedFeatures, vertices, { i: cells.i, c: cells.c, + v: cells.v, h: heights, t: distanceField, f: featureIds, diff --git a/src/scripts/generation/pack/provinces/generateCoreProvinces.ts b/src/scripts/generation/pack/provinces/generateCoreProvinces.ts index bcebc5c7..84c804f9 100644 --- a/src/scripts/generation/pack/provinces/generateCoreProvinces.ts +++ b/src/scripts/generation/pack/provinces/generateCoreProvinces.ts @@ -4,7 +4,6 @@ import {brighter, getMixedColor} from "utils/colorUtils"; import {gauss, P, rw} from "utils/probabilityUtils"; import {isBurg, isState} from "utils/typeUtils"; import {provinceForms} from "./config"; -import {generateProvinceName, generateProvinceEmblem} from "./utils"; const {COA, Names} = window; @@ -37,7 +36,7 @@ export function generateCoreProvinces(states: TStates, burgs: TBurgs, cultures: const color = brighter(getMixedColor(state.color, 0.2), 0.3); const coa = generateEmblem(nameByBurg, burgEmblem, type, cultures, cultureId, state); - provinces.push({i: provinces.length, name, formName, center, burg, state: state.i, fullName, color, coa}); + provinces.push({i: provinces.length + 1, name, formName, center, burg, state: state.i, fullName, color, coa}); } }); diff --git a/src/scripts/generation/pack/provinces/generateProvinces.ts b/src/scripts/generation/pack/provinces/generateProvinces.ts index 906ccfaa..d9c79c62 100644 --- a/src/scripts/generation/pack/provinces/generateProvinces.ts +++ b/src/scripts/generation/pack/provinces/generateProvinces.ts @@ -3,13 +3,15 @@ import {getInputNumber} from "utils/nodeUtils"; import {expandProvinces} from "./expandProvinces"; import {generateCoreProvinces} from "./generateCoreProvinces"; import {generateWildProvinces} from "./generateWildProvinces"; +import {specifyProvinces} from "./specifyProvinces"; export function generateProvinces( states: TStates, burgs: TBurgs, cultures: TCultures, features: TPackFeatures, - cells: Pick + vertices: IGraphVertices, + cells: Pick ): {provinceIds: Uint16Array; provinces: TProvinces} { TIME && console.time("generateProvinces"); @@ -30,7 +32,7 @@ export function generateProvinces( cells }); // mutates provinceIds - const provinces: TProvinces = [0, ...coreProvinces, ...wildProvinces]; + const provinces = specifyProvinces(provinceIds, coreProvinces, wildProvinces, vertices, cells.c, cells.v); TIME && console.timeEnd("generateProvinces"); return {provinceIds, provinces}; diff --git a/src/scripts/generation/pack/provinces/generateWildProvinces.ts b/src/scripts/generation/pack/provinces/generateWildProvinces.ts index 851969e8..f9f64c5e 100644 --- a/src/scripts/generation/pack/provinces/generateWildProvinces.ts +++ b/src/scripts/generation/pack/provinces/generateWildProvinces.ts @@ -39,7 +39,7 @@ export function generateWildProvinces({ let noProvinceCellsInState = noProvinceCells.filter(i => cells.state[i] === state.i); while (noProvinceCellsInState.length) { - const provinceId = coreProvinces.length + wildProvinces.length; + const provinceId = coreProvinces.length + wildProvinces.length + 1; const burgCell = noProvinceCellsInState.find(i => cells.burg[i]); const center = burgCell || noProvinceCellsInState[0]; const cultureId = cells.culture[center]; diff --git a/src/scripts/generation/pack/provinces/specifyProvinces.ts b/src/scripts/generation/pack/provinces/specifyProvinces.ts new file mode 100644 index 00000000..96d88dc2 --- /dev/null +++ b/src/scripts/generation/pack/provinces/specifyProvinces.ts @@ -0,0 +1,20 @@ +import {getPolesOfInaccessibility} from "scripts/getPolesOfInaccessibility"; + +export function specifyProvinces( + provinceIds: Uint16Array, + coreProvinces: IProvince[], + wildProvinces: IProvince[], + vertices: IGraphVertices, + cellNeighbors: number[][], + cellVertices: number[][] +): TProvinces { + const getType = (cellId: number) => provinceIds[cellId]; + const poles = getPolesOfInaccessibility({vertices, getType, cellNeighbors, cellVertices}); + + const provinces = [...coreProvinces, ...wildProvinces].map(province => { + const pole = poles[province.i]; + return {...province, pole}; + }); + + return [0, ...provinces]; +} diff --git a/src/scripts/getPolesOfInaccessibility.ts b/src/scripts/getPolesOfInaccessibility.ts new file mode 100644 index 00000000..dd40f43d --- /dev/null +++ b/src/scripts/getPolesOfInaccessibility.ts @@ -0,0 +1,64 @@ +import polylabel from "polylabel"; + +import {TIME} from "config/logging"; +import {connectVertices} from "./connectVertices"; + +interface IGetPolesProps { + vertices: IGraphVertices; + cellNeighbors: number[][]; + cellVertices: number[][]; + getType: (cellId: number) => number; +} + +export function getPolesOfInaccessibility(props: IGetPolesProps) { + TIME && console.time("getPolesOfInaccessibility"); + const multiPolygons = getMultiPolygons(props); + + const poles: Dict = Object.fromEntries( + Object.entries(multiPolygons).map(([id, multiPolygon]) => { + const [x, y] = polylabel(multiPolygon, 20); + return [id, [x, y]]; + }) + ); + + TIME && console.timeEnd("getPolesOfInaccessibility"); + return poles; +} + +function getMultiPolygons({vertices, getType, cellNeighbors, cellVertices}: IGetPolesProps) { + const multiPolygons: Dict = {}; + + const checkedCells = new Uint8Array(cellNeighbors.length); + const addToChecked = (cellId: number) => { + checkedCells[cellId] = 1; + }; + const isChecked = (cellId: number) => checkedCells[cellId] === 1; + + for (let cellId = 0; cellId < cellNeighbors.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 onborderCell = cellNeighbors[cellId].find(ofDifferentType); + if (onborderCell === undefined) continue; + + const startingVertex = cellVertices[cellId].find(v => vertices.c[v].some(ofDifferentType)); + if (startingVertex === undefined) throw new Error(`Starting vertex for cell ${cellId} is not found`); + + const vertexChain = connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing: true}); + if (vertexChain.length < 3) continue; + + addPolygon(type, vertexChain); + } + + return multiPolygons; + + function addPolygon(id: number, vertexChain: number[]) { + if (!multiPolygons[id]) multiPolygons[id] = []; + const polygon = vertexChain.map(vertex => vertices.p[vertex]); + multiPolygons[id].push(polygon); + } +} diff --git a/src/types/pack/provinces.d.ts b/src/types/pack/provinces.d.ts index a9b202b7..39f7ec8e 100644 --- a/src/types/pack/provinces.d.ts +++ b/src/types/pack/provinces.d.ts @@ -7,6 +7,7 @@ interface IProvince { color: Hex | CssUrls; state: number; center: number; + pole: TPoint; coa: ICoa | string; removed?: boolean; } diff --git a/src/types/pack/states.d.ts b/src/types/pack/states.d.ts index f001f711..79dd27cd 100644 --- a/src/types/pack/states.d.ts +++ b/src/types/pack/states.d.ts @@ -10,8 +10,8 @@ interface IState { form: TStateForm; formName: string; fullName: string; + pole: TPoint; coa: ICoa | string; - // pole: TPoint ? area: number; cells: number; burgs: number; diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index 8248144b..f355f139 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -14,3 +14,6 @@ export const isBurg = (burg: TNoBurg | IBurg): burg is IBurg => burg.i !== 0 && export const isReligion = (religion: TNoReligion | IReligion): religion is IReligion => religion.i !== 0 && !(religion as IReligion).removed; + +export const isProvince = (province: TNoProvince | IProvince): province is IProvince => + province !== 0 && !(province as IProvince).removed;