diff --git a/index.html b/index.html
index cf6eb2ca..fc388af6 100644
--- a/index.html
+++ b/index.html
@@ -603,6 +603,7 @@
id="toggleBorders"
data-tip="State borders: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
data-shortcut="D"
+ class="buttonoff"
onclick="toggleBorders(event)"
>
Borders
@@ -8030,7 +8031,8 @@
-
+
+
diff --git a/main.js b/main.js
index dae6925a..8120e591 100644
--- a/main.js
+++ b/main.js
@@ -654,7 +654,6 @@ async function generate(options) {
Provinces.getPoles();
BurgsAndStates.defineBurgFeatures();
- drawBorders();
drawStateLabels();
Rivers.specify();
diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js
index 12ac1e5e..f7d78376 100644
--- a/modules/dynamic/auto-update.js
+++ b/modules/dynamic/auto-update.js
@@ -53,7 +53,6 @@ export function resolveVersionConflicts(mapVersion) {
BurgsAndStates.defineStateForms();
Provinces.generate();
Provinces.getPoles();
- drawBorders();
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
diff --git a/modules/dynamic/editors/states-editor.js b/modules/dynamic/editors/states-editor.js
index 3067d1b8..dbca9fff 100644
--- a/modules/dynamic/editors/states-editor.js
+++ b/modules/dynamic/editors/states-editor.js
@@ -643,13 +643,10 @@ function stateRemove(stateId) {
debug.selectAll(".highlight").remove();
- if (!layerIsOn("toggleStates")) toggleStates();
- else drawStates();
-
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
-
+ if (layerIsOn("toggleStates")) drawStates();
+ if (layerIsOn("toggleBorders")) drawBorders();
if (layerIsOn("toggleProvinces")) drawProvinces();
+
refreshStatesEditor();
}
@@ -844,13 +841,13 @@ function recalculateStates(must) {
BurgsAndStates.expandStates();
Provinces.generate();
Provinces.getPoles();
- if (!layerIsOn("toggleStates")) toggleStates();
- else drawStates();
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
- if (layerIsOn("toggleProvinces")) drawProvinces();
BurgsAndStates.getPoles();
+
+ if (layerIsOn("toggleStates")) drawStates();
+ if (layerIsOn("toggleBorders")) drawBorders();
+ if (layerIsOn("toggleProvinces")) drawProvinces();
if (adjustLabels.checked) drawStateLabels();
+
refreshStatesEditor();
}
diff --git a/modules/renderers/draw-borders.js b/modules/renderers/draw-borders.js
new file mode 100644
index 00000000..f0f3006e
--- /dev/null
+++ b/modules/renderers/draw-borders.js
@@ -0,0 +1,120 @@
+"use strict";
+
+function drawBorders() {
+ TIME && console.time("drawBorders");
+ const {cells, vertices} = pack;
+
+ const statePath = [];
+ const provincePath = [];
+ const checked = {};
+
+ const isLand = cellId => cells.h[cellId] >= 20;
+
+ for (let cellId = 0; cellId < cells.i.length; cellId++) {
+ if (!cells.state[cellId]) continue;
+ const provinceId = cells.province[cellId];
+ const stateId = cells.state[cellId];
+
+ // bordering cell of another province
+ if (provinceId) {
+ const provToCell = cells.c[cellId].find(neibId => {
+ const neibProvinceId = cells.province[neibId];
+ return (
+ neibProvinceId &&
+ provinceId > neibProvinceId &&
+ !checked[`prov-${provinceId}-${neibProvinceId}-${cellId}`] &&
+ cells.state[neibId] === stateId
+ );
+ });
+
+ if (provToCell !== undefined) {
+ const addToChecked = cellId => (checked[`prov-${provinceId}-${cells.province[provToCell]}-${cellId}`] = true);
+ const border = getBorder({type: "province", fromCell: cellId, toCell: provToCell, addToChecked});
+
+ if (border) {
+ provincePath.push(border);
+ cellId--; // check the same cell again
+ continue;
+ }
+ }
+ }
+
+ // if cell is on state border
+ const stateToCell = cells.c[cellId].find(neibId => {
+ const neibStateId = cells.state[neibId];
+ return isLand(neibId) && stateId > neibStateId && !checked[`state-${stateId}-${neibStateId}-${cellId}`];
+ });
+
+ if (stateToCell !== undefined) {
+ const addToChecked = cellId => (checked[`state-${stateId}-${cells.state[stateToCell]}-${cellId}`] = true);
+ const border = getBorder({type: "state", fromCell: cellId, toCell: stateToCell, addToChecked});
+
+ if (border) {
+ statePath.push(border);
+ cellId--; // check the same cell again
+ continue;
+ }
+ }
+ }
+
+ svg.select("#borders").selectAll("path").remove();
+ svg.select("#stateBorders").append("path").attr("d", statePath.join(" "));
+ svg.select("#provinceBorders").append("path").attr("d", provincePath.join(" "));
+
+ function getBorder({type, fromCell, toCell, addToChecked}) {
+ const getType = cellId => cells[type][cellId];
+ const isTypeFrom = cellId => cellId < cells.i.length && getType(cellId) === getType(fromCell);
+ const isTypeTo = cellId => cellId < cells.i.length && getType(cellId) === getType(toCell);
+
+ addToChecked(fromCell);
+ const startingVertex = cells.v[fromCell].find(v => vertices.c[v].some(i => isLand(i) && isTypeTo(i)));
+ if (startingVertex === undefined) return null;
+
+ const checkVertex = vertex =>
+ vertices.c[vertex].some(isTypeFrom) && vertices.c[vertex].some(c => isLand(c) && isTypeTo(c));
+ const chain = getVerticesLine({vertices, startingVertex, checkCell: isTypeFrom, checkVertex, addToChecked});
+ if (chain.length > 1) return "M" + chain.map(cellId => vertices.p[cellId]).join(" ");
+
+ return null;
+ }
+
+ // connect vertices to chain to form a border
+ function getVerticesLine({vertices, startingVertex, checkCell, checkVertex, addToChecked}) {
+ let chain = []; // vertices chain to form a path
+ let next = startingVertex;
+ const MAX_ITERATIONS = vertices.c.length;
+
+ for (let run = 0; run < 2; run++) {
+ // first run: from any vertex to a border edge
+ // second run: from found border edge to another edge
+ chain = [];
+
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
+ const previous = chain.at(-1);
+ const current = next;
+ chain.push(current);
+
+ const neibCells = vertices.c[current];
+ neibCells.map(addToChecked);
+
+ const [c1, c2, c3] = neibCells.map(checkCell);
+ const [v1, v2, v3] = vertices.v[current].map(checkVertex);
+ const [vertex1, vertex2, vertex3] = vertices.v[current];
+
+ if (v1 && vertex1 !== previous && c1 !== c2) next = vertex1;
+ else if (v2 && vertex2 !== previous && c2 !== c3) next = vertex2;
+ else if (v3 && vertex3 !== previous && c1 !== c3) next = vertex3;
+
+ if (next === current || next === startingVertex) {
+ if (next === startingVertex) chain.push(startingVertex);
+ startingVertex = next;
+ break;
+ }
+ }
+ }
+
+ return chain;
+ }
+
+ TIME && console.timeEnd("drawBorders");
+}
diff --git a/modules/renderers/state-labels.js b/modules/renderers/draw-state-labels.js
similarity index 99%
rename from modules/renderers/state-labels.js
rename to modules/renderers/draw-state-labels.js
index b0cf3001..15bb7ea5 100644
--- a/modules/renderers/state-labels.js
+++ b/modules/renderers/draw-state-labels.js
@@ -2,7 +2,7 @@
// list - an optional array of stateIds to regenerate
function drawStateLabels(list) {
- console.time("drawStateLabels");
+ TIME && console.time("drawStateLabels");
// temporary make the labels visible
const layerDisplay = labels.style("display");
@@ -289,5 +289,5 @@ function drawStateLabels(list) {
return false;
}
- console.timeEnd("drawStateLabels");
+ TIME && console.timeEnd("drawStateLabels");
}
diff --git a/modules/submap.js b/modules/submap.js
index c4028d6e..5389bcaa 100644
--- a/modules/submap.js
+++ b/modules/submap.js
@@ -273,7 +273,6 @@ window.Submap = (function () {
stage("Regenerating routes network.");
regenerateRoutes();
- drawBorders();
drawStateLabels();
Rivers.specify();
diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js
index 3f684bdc..fa1427f3 100644
--- a/modules/ui/heightmap-editor.js
+++ b/modules/ui/heightmap-editor.js
@@ -253,7 +253,6 @@ function editHeightmap(options) {
Provinces.getPoles();
BurgsAndStates.defineBurgFeatures();
- drawBorders();
drawStateLabels();
Rivers.specify();
@@ -440,7 +439,6 @@ function editHeightmap(options) {
}
drawStateLabels();
- drawBorders();
if (erosionAllowed) {
Rivers.specify();
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index f8a9542b..acb8736a 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -182,10 +182,10 @@ function restoreLayers() {
if (layerIsOn("toggleEmblems")) drawEmblems();
if (layerIsOn("toggleMarkers")) drawMarkers();
if (layerIsOn("toggleZones")) drawZones();
+ if (layerIsOn("toggleBorders")) drawBorders();
+ if (layerIsOn("toggleStates")) drawStates();
// some layers are rendered each time, remove them if they are not on
- if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove();
- if (!layerIsOn("toggleStates")) regions.selectAll("path").remove();
if (!layerIsOn("toggleRivers")) rivers.selectAll("*").remove();
}
@@ -871,114 +871,6 @@ function toggleBorders(event) {
}
}
-// draw state and province borders
-function drawBorders() {
- TIME && console.time("drawBorders");
- borders.selectAll("path").remove();
-
- const {cells, vertices} = pack;
- const n = cells.i.length;
-
- const sPath = [];
- const pPath = [];
-
- const sUsed = new Array(pack.states.length).fill("").map(_ => []);
- const pUsed = new Array(pack.provinces.length).fill("").map(_ => []);
-
- for (let i = 0; i < cells.i.length; i++) {
- if (!cells.state[i]) continue;
- const p = cells.province[i];
- const s = cells.state[i];
-
- // if cell is on province border
- const provToCell = cells.c[i].find(
- n => cells.state[n] === s && p > cells.province[n] && pUsed[p][n] !== cells.province[n]
- );
-
- if (provToCell) {
- const provTo = cells.province[provToCell];
- pUsed[p][provToCell] = provTo;
- const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.province[i] === provTo));
- const chain = connectVertices(vertex, p, cells.province, provTo, pUsed);
-
- if (chain.length > 1) {
- pPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
- i--;
- continue;
- }
- }
-
- // if cell is on state border
- const stateToCell = cells.c[i].find(n => cells.h[n] >= 20 && s > cells.state[n] && sUsed[s][n] !== cells.state[n]);
- if (stateToCell !== undefined) {
- const stateTo = cells.state[stateToCell];
- sUsed[s][stateToCell] = stateTo;
- const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] >= 20 && cells.state[i] === stateTo));
- const chain = connectVertices(vertex, s, cells.state, stateTo, sUsed);
-
- if (chain.length > 1) {
- sPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
- i--;
- continue;
- }
- }
- }
-
- stateBorders.append("path").attr("d", sPath.join(" "));
- provinceBorders.append("path").attr("d", pPath.join(" "));
-
- // connect vertices to chain
- function connectVertices(current, f, array, t, used) {
- let chain = [];
- const checkCell = c => c >= n || array[c] !== f;
- const checkVertex = v =>
- vertices.c[v].some(c => array[c] === f) && vertices.c[v].some(c => array[c] === t && cells.h[c] >= 20);
-
- // find starting vertex
- for (let i = 0; i < 1000; i++) {
- if (i === 999) ERROR && console.error("Find starting vertex: limit is reached", current, f, t);
- const p = chain[chain.length - 2] || -1; // previous vertex
- const v = vertices.v[current],
- c = vertices.c[current];
-
- const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
- const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
- const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
- if (v0 + v1 + v2 === 1) break;
- current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
-
- if (current === chain[0]) break;
- if (current === p) return [];
- chain.push(current);
- }
-
- chain = [current]; // vertices chain to form a path
- // find path
- for (let i = 0; i < 1000; i++) {
- if (i === 999) ERROR && console.error("Find path: limit is reached", current, f, t);
- const p = chain[chain.length - 2] || -1; // previous vertex
- const v = vertices.v[current],
- c = vertices.c[current];
- c.filter(c => array[c] === t).forEach(c => (used[f][c] = t));
-
- const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
- const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
- const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
- current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
-
- if (current === p) break;
- if (current === chain[chain.length - 1]) break;
- if (chain.length > 1 && v0 + v1 + v2 < 2) break;
- chain.push(current);
- if (current === chain[0]) break;
- }
-
- return chain;
- }
-
- TIME && console.timeEnd("drawBorders");
-}
-
function toggleProvinces(event) {
if (!layerIsOn("toggleProvinces")) {
turnButtonOn("toggleProvinces");
diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js
index 2846a054..dba4a741 100644
--- a/modules/ui/provinces-editor.js
+++ b/modules/ui/provinces-editor.js
@@ -490,8 +490,7 @@ function editProvinces() {
const g = provs.select("#provincesBody");
g.select("#province" + p).remove();
g.select("#province-gap" + p).remove();
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
+ if (layerIsOn("toggleBorders")) drawBorders();
refreshProvincesEditor();
$(this).dialog("close");
},
@@ -950,12 +949,9 @@ function editProvinces() {
pack.cells.province[i] = +this.dataset.province;
});
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
-
Provinces.getPoles();
- if (!layerIsOn("toggleProvinces")) toggleProvinces();
- else drawProvinces();
+ if (layerIsOn("toggleBorders")) drawBorders();
+ if (layerIsOn("toggleProvinces")) drawProvinces();
exitProvincesManualAssignment();
refreshProvincesEditor();
@@ -1047,10 +1043,9 @@ function editProvinces() {
cells.province[c] = province;
});
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
- if (!layerIsOn("toggleProvinces")) toggleProvinces();
- else drawProvinces();
+ if (layerIsOn("toggleBorders")) drawBorders();
+ if (layerIsOn("toggleProvinces")) drawProvinces();
+
collectStatistics();
byId("provincesFilterState").value = state;
provincesEditorAddLines();
@@ -1123,8 +1118,7 @@ function editProvinces() {
pack.states.forEach(s => (s.provinces = []));
unfog();
- if (!layerIsOn("toggleBorders")) toggleBorders();
- else drawBorders();
+ if (layerIsOn("toggleBorders")) drawBorders();
provs.select("#provincesBody").remove();
turnButtonOff("toggleProvinces");
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index aa3b831a..33bff1aa 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -336,7 +336,8 @@ function regenerateProvinces() {
Provinces.generate(true, true);
Provinces.getPoles();
- drawBorders();
+
+ if (layerIsOn("toggleBorders")) drawBorders();
if (layerIsOn("toggleProvinces")) drawProvinces();
// remove emblems