mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-21 19:41:23 +01:00
feat: route - continue
This commit is contained in:
parent
6936f37f9f
commit
29e7ab0953
7 changed files with 258 additions and 21 deletions
38
index.html
38
index.html
|
|
@ -2107,6 +2107,9 @@
|
||||||
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
|
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
|
||||||
Burgs
|
Burgs
|
||||||
</button>
|
</button>
|
||||||
|
<button id="overviewRoutesButton" data-tip="Click to open Routes Overview" data-shortcut="Shift + U">
|
||||||
|
Routes
|
||||||
|
</button>
|
||||||
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V">
|
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V">
|
||||||
Rivers
|
Rivers
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -5407,6 +5410,40 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="routesOverview" class="dialog stable" style="display: none">
|
||||||
|
<div id="routesHeader" class="header" style="grid-template-columns: 17em 8em 8em">
|
||||||
|
<div data-tip="Click to sort by route name" class="sortable alphabetically" data-sortby="name">
|
||||||
|
Route
|
||||||
|
</div>
|
||||||
|
<div data-tip="Click to sort by route group" class="sortable alphabetically" data-sortby="group">
|
||||||
|
Group
|
||||||
|
</div>
|
||||||
|
<div data-tip="Click to sort by route length" class="sortable" data-sortby="length">Length </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="routesBody" class="table"></div>
|
||||||
|
|
||||||
|
<div id="routesFooter" class="totalLine">
|
||||||
|
<div data-tip="Routes number" style="margin-left: 4px">
|
||||||
|
Total routes: <span id="routesFooterNumber">0</span>
|
||||||
|
</div>
|
||||||
|
<div data-tip="Average length" style="margin-left: 12px">
|
||||||
|
Average length: <span id="routesFooterLength">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="routesBottom">
|
||||||
|
<button id="routesOverviewRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||||
|
<button id="routeCreateNew" data-tip="Create new route selecting route cells" class="icon-map-pin"></button>
|
||||||
|
<button
|
||||||
|
id="routesExport"
|
||||||
|
data-tip="Save routes-related data as a text file (.csv)"
|
||||||
|
class="icon-download"
|
||||||
|
></button>
|
||||||
|
<button id="routesRemoveAll" data-tip="Remove all routes" class="icon-trash"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="riversOverview" class="dialog stable" style="display: none">
|
<div id="riversOverview" class="dialog stable" style="display: none">
|
||||||
<div id="riversHeader" class="header" style="grid-template-columns: 9em 4em 6em 6em 5em 9em">
|
<div id="riversHeader" class="header" style="grid-template-columns: 9em 4em 6em 6em 5em 9em">
|
||||||
<div data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">
|
<div data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">
|
||||||
|
|
@ -8092,6 +8129,7 @@
|
||||||
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
|
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js"></script>
|
<script defer src="modules/ui/zones-editor.js"></script>
|
||||||
<script defer src="modules/ui/burgs-overview.js?v=1.97.00"></script>
|
<script defer src="modules/ui/burgs-overview.js?v=1.97.00"></script>
|
||||||
|
<script defer src="modules/ui/routes-overview.js"></script>
|
||||||
<script defer src="modules/ui/rivers-overview.js"></script>
|
<script defer src="modules/ui/rivers-overview.js"></script>
|
||||||
<script defer src="modules/ui/military-overview.js?v=1.96.07"></script>
|
<script defer src="modules/ui/military-overview.js?v=1.96.07"></script>
|
||||||
<script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script>
|
<script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script>
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,21 @@ window.Routes = (function () {
|
||||||
routes.push({i: routes.length, group: "roads", feature, cells});
|
routes.push({i: routes.length, group: "roads", feature, cells});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const {feature, cells} of trails) {
|
// merge routes so that the last cells of one route are the first cells of the next route
|
||||||
|
for (let i = 0; i < trails.length; i++) {
|
||||||
|
const {cells, feature, merged} = trails[i];
|
||||||
|
if (merged) continue;
|
||||||
|
|
||||||
|
for (let j = i + 1; j < trails.length; j++) {
|
||||||
|
const nextTrail = trails[j];
|
||||||
|
if (nextTrail.merged) continue;
|
||||||
|
|
||||||
|
if (nextTrail.cells[0] === cells.at(-1)) {
|
||||||
|
cells.push(...nextTrail.cells.slice(1));
|
||||||
|
nextTrail.merged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
routes.push({i: routes.length, group: "trails", feature, cells});
|
routes.push({i: routes.length, group: "trails", feature, cells});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,6 +516,8 @@ window.Routes = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateName({group, cells}) {
|
function generateName({group, cells}) {
|
||||||
|
if (cells.length < 4) return "Unnamed route segment";
|
||||||
|
|
||||||
const model = rw(models[group]);
|
const model = rw(models[group]);
|
||||||
const suffix = rw(suffixes[group]);
|
const suffix = rw(suffixes[group]);
|
||||||
|
|
||||||
|
|
@ -521,5 +537,24 @@ window.Routes = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {generate, isConnected, areConnected, getRoute, hasRoad, isCrossroad, generateName};
|
function remove(route) {
|
||||||
|
const routes = pack.cells.routes;
|
||||||
|
|
||||||
|
for (const from of route.cells) {
|
||||||
|
for (const [to, routeId] of Object.entries(routes[from])) {
|
||||||
|
if (routeId === route.i) {
|
||||||
|
delete routes[from][to];
|
||||||
|
delete routes[to][from];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pack.routes = pack.routes.filter(r => r.i !== route.i);
|
||||||
|
viewbox
|
||||||
|
.select("#routes")
|
||||||
|
.select("#route" + route.i)
|
||||||
|
.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {generate, isConnected, areConnected, getRoute, hasRoad, isCrossroad, generateName, remove};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ function handleKeyup(event) {
|
||||||
else if (shift && code === "KeyO") editNotes();
|
else if (shift && code === "KeyO") editNotes();
|
||||||
else if (shift && code === "KeyA") overviewCharts();
|
else if (shift && code === "KeyA") overviewCharts();
|
||||||
else if (shift && code === "KeyT") overviewBurgs();
|
else if (shift && code === "KeyT") overviewBurgs();
|
||||||
|
else if (shift && code === "KeyU") overviewRoutes();
|
||||||
else if (shift && code === "KeyV") overviewRivers();
|
else if (shift && code === "KeyV") overviewRivers();
|
||||||
else if (shift && code === "KeyM") overviewMilitary();
|
else if (shift && code === "KeyM") overviewMilitary();
|
||||||
else if (shift && code === "KeyK") overviewMarkers();
|
else if (shift && code === "KeyK") overviewMarkers();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function overviewRivers() {
|
function overviewRivers() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
closeDialogs("#riversOverview, .stable");
|
closeDialogs("#riversOverview, .stable");
|
||||||
|
|
@ -49,7 +50,7 @@ function overviewRivers() {
|
||||||
data-basin="${basin}"
|
data-basin="${basin}"
|
||||||
>
|
>
|
||||||
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
|
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
|
||||||
<div data-tip="River name" class="riverName">${r.name}</div>
|
<div data-tip="River name" style="margin-left: 0.4em;" class="riverName">${r.name}</div>
|
||||||
<div data-tip="River type name" class="riverType">${r.type}</div>
|
<div data-tip="River type name" class="riverType">${r.type}</div>
|
||||||
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
|
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
|
||||||
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
|
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
|
||||||
|
|
@ -75,7 +76,9 @@ function overviewRivers() {
|
||||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
|
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
|
||||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
|
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
|
||||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
|
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
|
||||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove));
|
body
|
||||||
|
.querySelectorAll("div > span.icon-trash-empty")
|
||||||
|
.forEach(el => el.addEventListener("click", triggerRiverRemove));
|
||||||
|
|
||||||
applySorting(riversHeader);
|
applySorting(riversHeader);
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +113,18 @@ function overviewRivers() {
|
||||||
} else {
|
} else {
|
||||||
rivers.attr("data-basin", "hightlighted");
|
rivers.attr("data-basin", "hightlighted");
|
||||||
const basins = [...new Set(pack.rivers.map(r => r.basin))];
|
const basins = [...new Set(pack.rivers.map(r => r.basin))];
|
||||||
const colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
|
const colors = [
|
||||||
|
"#1f77b4",
|
||||||
|
"#ff7f0e",
|
||||||
|
"#2ca02c",
|
||||||
|
"#d62728",
|
||||||
|
"#9467bd",
|
||||||
|
"#8c564b",
|
||||||
|
"#e377c2",
|
||||||
|
"#7f7f7f",
|
||||||
|
"#bcbd22",
|
||||||
|
"#17becf"
|
||||||
|
];
|
||||||
|
|
||||||
basins.forEach((b, i) => {
|
basins.forEach((b, i) => {
|
||||||
const color = colors[i % colors.length];
|
const color = colors[i % colors.length];
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ function editRoute(id) {
|
||||||
|
|
||||||
function updateRouteLength(route) {
|
function updateRouteLength(route) {
|
||||||
route.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
route.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||||
const lengthUI = `${rn(route.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
const lengthUI = `${rn(route.length * distanceScale)} ${distanceUnitInput.value}`;
|
||||||
byId("routeLength").value = lengthUI;
|
byId("routeLength").value = lengthUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,22 +255,8 @@ function editRoute(id) {
|
||||||
title: "Remove route",
|
title: "Remove route",
|
||||||
buttons: {
|
buttons: {
|
||||||
Remove: function () {
|
Remove: function () {
|
||||||
const route = getRoute();
|
Routes.remove(getRoute());
|
||||||
const routes = pack.cells.routes;
|
|
||||||
|
|
||||||
for (const from of route.cells) {
|
|
||||||
for (const [to, routeId] of Object.entries(routes[from])) {
|
|
||||||
if (routeId === route.i) {
|
|
||||||
delete routes[from][to];
|
|
||||||
delete routes[to][from];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pack.routes = pack.routes.filter(r => r.i !== route.i);
|
|
||||||
|
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
elSelected.remove();
|
|
||||||
$("#routeEditor").dialog("close");
|
$("#routeEditor").dialog("close");
|
||||||
},
|
},
|
||||||
Cancel: function () {
|
Cancel: function () {
|
||||||
|
|
|
||||||
162
modules/ui/routes-overview.js
Normal file
162
modules/ui/routes-overview.js
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function overviewRoutes() {
|
||||||
|
if (customization) return;
|
||||||
|
closeDialogs("#routesOverview, .stable");
|
||||||
|
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||||
|
|
||||||
|
const body = byId("routesBody");
|
||||||
|
routesOverviewAddLines();
|
||||||
|
$("#routesOverview").dialog();
|
||||||
|
|
||||||
|
if (modules.overviewRoutes) return;
|
||||||
|
modules.overviewRoutes = true;
|
||||||
|
|
||||||
|
$("#routesOverview").dialog({
|
||||||
|
title: "Routes Overview",
|
||||||
|
resizable: false,
|
||||||
|
width: fitContent(),
|
||||||
|
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||||
|
});
|
||||||
|
|
||||||
|
// add listeners
|
||||||
|
byId("routesOverviewRefresh").on("click", routesOverviewAddLines);
|
||||||
|
// byId("routeCreateNew").on("click", createRoute);
|
||||||
|
byId("routesExport").on("click", downloadRoutesData);
|
||||||
|
byId("routesRemoveAll").on("click", triggerAllRoutesRemove);
|
||||||
|
|
||||||
|
// add line for each route
|
||||||
|
function routesOverviewAddLines() {
|
||||||
|
body.innerHTML = "";
|
||||||
|
let lines = "";
|
||||||
|
const unit = distanceUnitInput.value;
|
||||||
|
|
||||||
|
for (const route of pack.routes) {
|
||||||
|
route.name = route.name || Routes.generateName(route);
|
||||||
|
route.length = route.length || getRouteLength(route.i);
|
||||||
|
const length = rn(route.length * distanceScale) + " " + unit;
|
||||||
|
|
||||||
|
lines += /* html */ `<div
|
||||||
|
class="states"
|
||||||
|
data-id="${route.i}"
|
||||||
|
data-name="${route.name}"
|
||||||
|
data-group="${route.group}"
|
||||||
|
data-length="${route.length}"
|
||||||
|
>
|
||||||
|
<span data-tip="Click to focus on route" class="icon-dot-circled pointer"></span>
|
||||||
|
<div data-tip="Route name" style="width: 15em; margin-left: 0.4em;">${route.name}</div>
|
||||||
|
<div data-tip="Route group" style="width: 8em;">${route.group}</div>
|
||||||
|
<div data-tip="Route length" style="width: 6em;">${length}</div>
|
||||||
|
<span data-tip="Edit route" class="icon-pencil"></span>
|
||||||
|
<span data-tip="Remove route" class="icon-trash-empty"></span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
body.insertAdjacentHTML("beforeend", lines);
|
||||||
|
|
||||||
|
// update footer
|
||||||
|
routesFooterNumber.innerHTML = pack.routes.length;
|
||||||
|
const averageLength = rn(d3.mean(pack.routes.map(r => r.length)));
|
||||||
|
routesFooterLength.innerHTML = averageLength * distanceScale + " " + unit;
|
||||||
|
|
||||||
|
// add listeners
|
||||||
|
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn));
|
||||||
|
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", routeHighlightOff));
|
||||||
|
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.on("click", zoomToRoute));
|
||||||
|
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRouteEditor));
|
||||||
|
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRouteRemove));
|
||||||
|
|
||||||
|
applySorting(routesHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRouteLength(routeId) {
|
||||||
|
const path = routes.select("#route" + routeId).node();
|
||||||
|
return rn(path.getTotalLength() / 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeHighlightOn(event) {
|
||||||
|
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||||
|
const routeId = +event.target.dataset.id;
|
||||||
|
routes
|
||||||
|
.select("#route" + routeId)
|
||||||
|
.attr("stroke", "red")
|
||||||
|
.attr("stroke-width", 3)
|
||||||
|
.attr("stroke-dasharray", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeHighlightOff(e) {
|
||||||
|
const routeId = +e.target.dataset.id;
|
||||||
|
routes
|
||||||
|
.select("#route" + routeId)
|
||||||
|
.attr("stroke", null)
|
||||||
|
.attr("stroke-width", null)
|
||||||
|
.attr("stroke-dasharray", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomToRoute() {
|
||||||
|
const r = +this.parentNode.dataset.id;
|
||||||
|
const route = routes.select("#route" + r).node();
|
||||||
|
highlightElement(route, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadRoutesData() {
|
||||||
|
let data = "Id,Route,Group,Length\n"; // headers
|
||||||
|
|
||||||
|
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||||
|
const d = el.dataset;
|
||||||
|
const length = rn(d.length * distanceScale) + " " + distanceUnitInput.value;
|
||||||
|
data += [d.id, d.name, d.group, length].join(",") + "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
const name = getFileName("Routes") + ".csv";
|
||||||
|
downloadFile(data, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRouteEditor() {
|
||||||
|
const id = "route" + this.parentNode.dataset.id;
|
||||||
|
editRoute(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerRouteRemove() {
|
||||||
|
const routeId = +this.parentNode.dataset.id;
|
||||||
|
|
||||||
|
alertMessage.innerHTML = /* html */ `Are you sure you want to remove the route? All tributaries will be auto-removed`;
|
||||||
|
|
||||||
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
width: "22em",
|
||||||
|
title: "Remove route",
|
||||||
|
buttons: {
|
||||||
|
Remove: function () {
|
||||||
|
const route = pack.routes.find(r => r.i === routeId);
|
||||||
|
Routes.remove(route);
|
||||||
|
routesOverviewAddLines();
|
||||||
|
$(this).dialog("close");
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerAllRoutesRemove() {
|
||||||
|
alertMessage.innerHTML = /* html */ `Are you sure you want to remove all routes? This action can't be undone`;
|
||||||
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Remove all routes",
|
||||||
|
buttons: {
|
||||||
|
Remove: function () {
|
||||||
|
pack.cells.routes = {};
|
||||||
|
pack.routes = [];
|
||||||
|
routes.selectAll("path").remove();
|
||||||
|
|
||||||
|
$(this).dialog("close");
|
||||||
|
$("#routesOverview").dialog("close");
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ toolsContent.addEventListener("click", function (event) {
|
||||||
else if (button === "editZonesButton") editZones();
|
else if (button === "editZonesButton") editZones();
|
||||||
else if (button === "overviewChartsButton") overviewCharts();
|
else if (button === "overviewChartsButton") overviewCharts();
|
||||||
else if (button === "overviewBurgsButton") overviewBurgs();
|
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||||
|
else if (button === "overviewRoutesButton") overviewRoutes();
|
||||||
else if (button === "overviewRiversButton") overviewRivers();
|
else if (button === "overviewRiversButton") overviewRivers();
|
||||||
else if (button === "overviewMilitaryButton") overviewMilitary();
|
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||||
else if (button === "overviewMarkersButton") overviewMarkers();
|
else if (button === "overviewMarkersButton") overviewMarkers();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue