mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
feat: routes - create route dialog
This commit is contained in:
parent
a3a858d609
commit
f6f7beb793
8 changed files with 318 additions and 124 deletions
|
|
@ -549,6 +549,58 @@ window.Routes = (function () {
|
|||
}
|
||||
}
|
||||
|
||||
function preparePointsArray() {
|
||||
const {cells, burgs} = pack;
|
||||
return cells.p.map(([x, y], cellId) => {
|
||||
const burgId = cells.burg[cellId];
|
||||
if (burgId) return [burgs[burgId].x, burgs[burgId].y];
|
||||
return [x, y];
|
||||
});
|
||||
}
|
||||
|
||||
function getPoints(route, points) {
|
||||
if (route.points) return route.points;
|
||||
const routePoints = route.cells.map(cellId => points[cellId]);
|
||||
|
||||
if (route.group !== "searoutes2") {
|
||||
for (let i = 1; i < route.cells.length - 1; i++) {
|
||||
const cellId = route.cells[i];
|
||||
if (pack.cells.burg[cellId]) continue;
|
||||
|
||||
const [prevX, prevY] = routePoints[i - 1];
|
||||
const [currX, currY] = routePoints[i];
|
||||
const [nextX, nextY] = routePoints[i + 1];
|
||||
|
||||
const dAx = prevX - currX;
|
||||
const dAy = prevY - currY;
|
||||
const dBx = nextX - currX;
|
||||
const dBy = nextY - currY;
|
||||
const angle = Math.abs((Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI);
|
||||
|
||||
if (angle < ROUTES_SHARP_ANGLE) {
|
||||
const middleX = (prevX + nextX) / 2;
|
||||
const middleY = (prevY + nextY) / 2;
|
||||
let newX, newY;
|
||||
|
||||
if (angle < ROUTES_VERY_SHARP_ANGLE) {
|
||||
newX = rn((currX + middleX * 2) / 3, 2);
|
||||
newY = rn((currY + middleY * 2) / 3, 2);
|
||||
} else {
|
||||
newX = rn((currX + middleX) / 2, 2);
|
||||
newY = rn((currY + middleY) / 2, 2);
|
||||
}
|
||||
|
||||
if (findCell(newX, newY) === cellId) {
|
||||
routePoints[i] = [newX, newY];
|
||||
points[cellId] = routePoints[i]; // change cell coordinate for all routes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routePoints;
|
||||
}
|
||||
|
||||
function remove(route) {
|
||||
const routes = pack.cells.routes;
|
||||
|
||||
|
|
@ -568,5 +620,16 @@ window.Routes = (function () {
|
|||
.remove();
|
||||
}
|
||||
|
||||
return {generate, isConnected, areConnected, getRoute, hasRoad, isCrossroad, generateName, remove};
|
||||
return {
|
||||
generate,
|
||||
isConnected,
|
||||
areConnected,
|
||||
getRoute,
|
||||
hasRoad,
|
||||
isCrossroad,
|
||||
generateName,
|
||||
preparePointsArray,
|
||||
getPoints,
|
||||
remove
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function handleKeyup(event) {
|
|||
else if (key === "!") toggleAddBurg();
|
||||
else if (key === "@") toggleAddLabel();
|
||||
else if (key === "#") toggleAddRiver();
|
||||
else if (key === "$") toggleAddRoute();
|
||||
else if (key === "$") createRoute();
|
||||
else if (key === "%") toggleAddMarker();
|
||||
else if (alt && code === "KeyB") console.table(pack.burgs);
|
||||
else if (alt && code === "KeyS") console.table(pack.states);
|
||||
|
|
|
|||
|
|
@ -1645,17 +1645,12 @@ function drawRoutes() {
|
|||
const routePaths = {};
|
||||
const lineGen = d3.line();
|
||||
|
||||
const {cells, burgs} = pack;
|
||||
let points = cells.p.map(([x, y], cellId) => {
|
||||
const burgId = cells.burg[cellId];
|
||||
if (burgId) return [burgs[burgId].x, burgs[burgId].y];
|
||||
return [x, y];
|
||||
});
|
||||
let points = Routes.preparePointsArray();
|
||||
|
||||
for (const route of pack.routes) {
|
||||
const {i, group} = route;
|
||||
lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
|
||||
const routePoints = getRoutePoints(route, points);
|
||||
const routePoints = Routes.getPoints(route, points);
|
||||
const path = round(lineGen(routePoints), 1);
|
||||
|
||||
if (!routePaths[group]) routePaths[group] = [];
|
||||
|
|
@ -1673,49 +1668,6 @@ function drawRoutes() {
|
|||
const ROUTES_SHARP_ANGLE = 135;
|
||||
const ROUTES_VERY_SHARP_ANGLE = 115;
|
||||
|
||||
function getRoutePoints(route, points) {
|
||||
if (route.points) return route.points;
|
||||
const routePoints = route.cells.map(cellId => points[cellId]);
|
||||
|
||||
if (route.group !== "searoutes2") {
|
||||
for (let i = 1; i < route.cells.length - 1; i++) {
|
||||
const cellId = route.cells[i];
|
||||
if (pack.cells.burg[cellId]) continue;
|
||||
|
||||
const [prevX, prevY] = routePoints[i - 1];
|
||||
const [currX, currY] = routePoints[i];
|
||||
const [nextX, nextY] = routePoints[i + 1];
|
||||
|
||||
const dAx = prevX - currX;
|
||||
const dAy = prevY - currY;
|
||||
const dBx = nextX - currX;
|
||||
const dBy = nextY - currY;
|
||||
const angle = Math.abs((Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI);
|
||||
|
||||
if (angle < ROUTES_SHARP_ANGLE) {
|
||||
const middleX = (prevX + nextX) / 2;
|
||||
const middleY = (prevY + nextY) / 2;
|
||||
let newX, newY;
|
||||
|
||||
if (angle < ROUTES_VERY_SHARP_ANGLE) {
|
||||
newX = rn((currX + middleX * 2) / 3, 2);
|
||||
newY = rn((currY + middleY * 2) / 3, 2);
|
||||
} else {
|
||||
newX = rn((currX + middleX) / 2, 2);
|
||||
newY = rn((currY + middleY) / 2, 2);
|
||||
}
|
||||
|
||||
if (findCell(newX, newY) === cellId) {
|
||||
routePoints[i] = [newX, newY];
|
||||
points[cellId] = routePoints[i]; // change cell coordinate for all routes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routePoints;
|
||||
}
|
||||
|
||||
function drawRoute() {}
|
||||
|
||||
function toggleMilitary() {
|
||||
|
|
|
|||
83
modules/ui/route-group-editor.js
Normal file
83
modules/ui/route-group-editor.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
"use strict";
|
||||
|
||||
function editRouteGroups() {
|
||||
if (customization) return;
|
||||
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||
|
||||
addLines();
|
||||
|
||||
$("#routeGroupsEditor").dialog({
|
||||
title: "Edit Route groups",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+140", of: "#map"}
|
||||
});
|
||||
|
||||
if (modules.editRouteGroups) return;
|
||||
modules.editRouteGroups = true;
|
||||
|
||||
// add listeners
|
||||
byId("routeGroupsEditorAdd").addEventListener("click", addGroup);
|
||||
byId("routeGroupsEditorBody").on("click", ev => {
|
||||
const group = ev.target.parentNode.dataset.id;
|
||||
if (ev.target.classList.contains("editStyle")) editStyle("routes", group);
|
||||
else if (ev.target.classList.contains("removeGroup")) removeGroup(group);
|
||||
});
|
||||
|
||||
function addLines() {
|
||||
byId("routeGroupsEditorBody").innerHTML = "";
|
||||
|
||||
const lines = Array.from(routes.selectAll("g")._groups[0]).map(el => {
|
||||
const count = el.children.length;
|
||||
return /* html */ `<div data-id="${el.id}" class="states" style="display: flex; justify-content: space-between;">
|
||||
<span>${el.id} (${count})</span>
|
||||
<div style="width: auto; display: flex; gap: 0.4em;">
|
||||
<span data-tip="Edit style" class="editStyle icon-brush pointer" style="font-size: smaller;"></span>
|
||||
<span data-tip="Remove group" class="removeGroup icon-trash pointer"></span>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
byId("routeGroupsEditorBody").innerHTML = lines.join("");
|
||||
}
|
||||
|
||||
const DEFAULT_GROUPS = ["roads", "trails", "searoutes"];
|
||||
|
||||
function addGroup() {
|
||||
prompt("Type group name", {default: "route-group-new"}, v => {
|
||||
let group = v
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (!group) return tip("Invalid group name", false, "error");
|
||||
if (!group.startsWith("route-")) group = "route-" + group;
|
||||
if (byId(group)) return tip("Element with this name already exists. Provide a unique name", false, "error");
|
||||
if (Number.isFinite(+group.charAt(0))) return tip("Group name should start with a letter", false, "error");
|
||||
|
||||
routes
|
||||
.append("g")
|
||||
.attr("id", group)
|
||||
.attr("stroke", "#000000")
|
||||
.attr("stroke-width", 0.5)
|
||||
.attr("stroke-dasharray", "1 0.5")
|
||||
.attr("stroke-linecap", "butt");
|
||||
byId("routeGroup")?.options.add(new Option(group, group));
|
||||
addLines();
|
||||
});
|
||||
}
|
||||
|
||||
function removeGroup(group) {
|
||||
confirmationDialog({
|
||||
title: "Remove route group",
|
||||
message:
|
||||
"Are you sure you want to remove the entire route group? All routes in this group will be removed. This action can't be reverted.",
|
||||
confirm: "Remove",
|
||||
onConfirm: () => {
|
||||
const routes = pack.routes.filter(r => r.group === group);
|
||||
routes.forEach(r => Routes.remove(r));
|
||||
if (DEFAULT_GROUPS.includes(group)) routes.select(`#${group}`).remove();
|
||||
addLines();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
120
modules/ui/routes-creator.js
Normal file
120
modules/ui/routes-creator.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"use strict";
|
||||
|
||||
function createRoute(defaultGroup) {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||
|
||||
byId("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||
if (!layerIsOn("toggleCells")) toggleCells();
|
||||
|
||||
tip("Click to add route point, click again to remove", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
viewbox.style("cursor", "crosshair").on("click", onCellClick);
|
||||
|
||||
createRoute.cells = [];
|
||||
const body = byId("routeCreatorBody");
|
||||
|
||||
// update route groups
|
||||
byId("routeCreatorGroupSelect").innerHTML = Array.from(routes.selectAll("g")._groups[0]).map(el => {
|
||||
return `<option value="${el.id}" ${el.id === defaultGroup ? "selected" : ""}>${el.id}</option>`;
|
||||
});
|
||||
|
||||
$("#routeCreator").dialog({
|
||||
title: "Create Route",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
close: closeRouteCreator
|
||||
});
|
||||
|
||||
if (modules.createRoute) return;
|
||||
modules.createRoute = true;
|
||||
|
||||
// add listeners
|
||||
byId("routeCreatorGroupEdit").on("click", editRouteGroups);
|
||||
byId("routeCreatorComplete").on("click", completeCreation);
|
||||
byId("routeCreatorCancel").on("click", () => $("#routeCreator").dialog("close"));
|
||||
body.on("click", ev => {
|
||||
if (ev.target.classList.contains("icon-trash-empty")) removeCell(+ev.target.parentNode.dataset.cell);
|
||||
});
|
||||
|
||||
function onCellClick() {
|
||||
const cell = findCell(...d3.mouse(this));
|
||||
|
||||
if (createRoute.cells.includes(cell)) removeCell(cell);
|
||||
else addCell(cell);
|
||||
}
|
||||
|
||||
function addCell(cell) {
|
||||
createRoute.cells.push(cell);
|
||||
drawCells(createRoute.cells);
|
||||
|
||||
body.innerHTML += `<li class="editorLine" data-cell="${cell}">
|
||||
<span>Cell ${cell}</span>
|
||||
<span data-tip="Remove the cell" class="icon-trash-empty pointer"></span>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
function removeCell(cell) {
|
||||
createRoute.cells = createRoute.cells.filter(c => c !== cell);
|
||||
drawCells(createRoute.cells);
|
||||
body.querySelector(`[data-cell='${cell}']`)?.remove();
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
debug
|
||||
.select("#controlCells")
|
||||
.selectAll("polygon")
|
||||
.data(cells)
|
||||
.join("polygon")
|
||||
.attr("points", getPackPolygon)
|
||||
.attr("class", "current");
|
||||
}
|
||||
|
||||
function completeCreation() {
|
||||
const routeCells = createRoute.cells;
|
||||
if (routeCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
|
||||
const routeId = Math.max(...pack.routes.map(route => route.i)) + 1;
|
||||
const group = byId("routeCreatorGroupSelect").value;
|
||||
const feature = pack.cells.f[routeCells[0]];
|
||||
const route = {cells: routeCells, group, feature, i: routeId};
|
||||
pack.routes.push(route);
|
||||
|
||||
const links = pack.cells.routes;
|
||||
for (let i = 0; i < routeCells.length; i++) {
|
||||
const cellId = routeCells[i];
|
||||
const nextCellId = routeCells[i + 1];
|
||||
if (nextCellId) {
|
||||
if (!links[cellId]) links[cellId] = {};
|
||||
links[cellId][nextCellId] = routeId;
|
||||
|
||||
if (!links[nextCellId]) links[nextCellId] = {};
|
||||
links[nextCellId][cellId] = routeId;
|
||||
}
|
||||
}
|
||||
|
||||
const lineGen = d3.line();
|
||||
lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
|
||||
const routePoints = Routes.getPoints(route, Routes.preparePointsArray());
|
||||
const path = round(lineGen(routePoints), 1);
|
||||
routes
|
||||
.select("#" + group)
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "route" + routeId);
|
||||
|
||||
editRoute("route" + routeId);
|
||||
}
|
||||
|
||||
function closeRouteCreator() {
|
||||
body.innerHTML = "";
|
||||
debug.select("#controlCells").remove();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +byId("toggleCells").dataset.forced;
|
||||
byId("toggleCells").dataset.forced = 0;
|
||||
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ function editRoute(id) {
|
|||
|
||||
updateRouteData();
|
||||
|
||||
drawControlPoints(getPoints());
|
||||
const route = getRoute();
|
||||
drawControlPoints(Routes.getPoints(route, Routes.preparePointsArray()));
|
||||
drawCells();
|
||||
|
||||
$("#routeEditor").dialog({
|
||||
|
|
@ -34,13 +35,14 @@ function editRoute(id) {
|
|||
modules.editRoute = true;
|
||||
|
||||
// add listeners
|
||||
byId("routeCreateSelectingCells").on("click", createRoute);
|
||||
byId("routeEditStyle").on("click", editRouteGroupStyle);
|
||||
byId("routeCreateSelectingCells").on("click", showCreationDialog);
|
||||
byId("routeElevationProfile").on("click", showRouteElevationProfile);
|
||||
byId("routeLegend").on("click", editRouteLegend);
|
||||
byId("routeRemove").on("click", removeRoute);
|
||||
byId("routeName").on("input", changeName);
|
||||
byId("routeGroup").on("input", changeGroup);
|
||||
byId("routeGroupEdit").on("click", editRouteGroups);
|
||||
byId("routeEditStyle").on("click", editRouteGroupStyle);
|
||||
byId("routeGenerateName").on("click", generateName);
|
||||
|
||||
function getRoute() {
|
||||
|
|
@ -49,17 +51,6 @@ function editRoute(id) {
|
|||
return route;
|
||||
}
|
||||
|
||||
function getPoints() {
|
||||
const {cells, burgs} = pack;
|
||||
let points = cells.p.map(([x, y], cellId) => {
|
||||
const burgId = cells.burg[cellId];
|
||||
if (burgId) return [burgs[burgId].x, burgs[burgId].y];
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
return getRoutePoints(getRoute(), points);
|
||||
}
|
||||
|
||||
function updateRouteData() {
|
||||
const route = getRoute();
|
||||
|
||||
|
|
@ -181,23 +172,9 @@ function editRoute(id) {
|
|||
redrawRoute();
|
||||
}
|
||||
|
||||
function drawConnections() {
|
||||
debug.selectAll("line").remove();
|
||||
for (const [fromCellId, connections] of Object.entries(pack.cells.routes)) {
|
||||
const from = pack.cells.p[fromCellId];
|
||||
for (const toCellId of Object.keys(connections)) {
|
||||
const to = pack.cells.p[toCellId];
|
||||
debug
|
||||
.append("line")
|
||||
.attr("x1", from[0])
|
||||
.attr("y1", from[1])
|
||||
.attr("x2", to[0])
|
||||
.attr("y2", to[1])
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 0.4)
|
||||
.attr("opacity", 0.5);
|
||||
}
|
||||
}
|
||||
function showCreationDialog() {
|
||||
const route = getRoute();
|
||||
createRoute(route.group);
|
||||
}
|
||||
|
||||
function removeConnection(from, to) {
|
||||
|
|
@ -254,10 +231,6 @@ function editRoute(id) {
|
|||
editStyle("routes", group);
|
||||
}
|
||||
|
||||
function createRoute() {
|
||||
// TODO: white the code :)
|
||||
}
|
||||
|
||||
function removeRoute() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the route";
|
||||
$("#alert").dialog({
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ toolsContent.addEventListener("click", function (event) {
|
|||
if (button === "addLabel") toggleAddLabel();
|
||||
else if (button === "addBurgTool") toggleAddBurg();
|
||||
else if (button === "addRiver") toggleAddRiver();
|
||||
else if (button === "addRoute") toggleAddRoute();
|
||||
else if (button === "addRoute") createRoute();
|
||||
else if (button === "addMarker") toggleAddMarker();
|
||||
// click to create a new map buttons
|
||||
else if (button === "openSubmapMenu") UISubmap.openSubmapMenu();
|
||||
|
|
@ -775,30 +775,6 @@ function addRiverOnClick() {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleAddRoute() {
|
||||
const pressed = document.getElementById("addRoute").classList.contains("pressed");
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addRoute.classList.add("pressed");
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addRouteOnClick);
|
||||
tip("Click on map to add a first control point", true);
|
||||
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||
}
|
||||
|
||||
function addRouteOnClick() {
|
||||
unpressClickToAddButton();
|
||||
const [x, y] = d3.mouse(this);
|
||||
|
||||
const id = getNextId("route");
|
||||
routes.select("g").append("path").attr("id", id).attr("data-new", 1).attr("d", `M${x},${y}`);
|
||||
editRoute(id);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById("addMarker")?.classList.contains("pressed");
|
||||
if (pressed) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue