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;