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