mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
feat: routes rendering
This commit is contained in:
parent
6776e5b867
commit
71e53bd34f
4 changed files with 138 additions and 9 deletions
|
|
@ -612,6 +612,7 @@
|
||||||
id="toggleRoutes"
|
id="toggleRoutes"
|
||||||
data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||||
data-shortcut="U"
|
data-shortcut="U"
|
||||||
|
class="buttonoff"
|
||||||
onclick="toggleRoutes(event)"
|
onclick="toggleRoutes(event)"
|
||||||
>
|
>
|
||||||
Ro<u>u</u>tes
|
Ro<u>u</u>tes
|
||||||
|
|
|
||||||
|
|
@ -843,4 +843,12 @@ export function resolveVersionConflicts(version) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version < 1.98) {
|
||||||
|
// v1.98.00 changed routes generation algorithm and data format
|
||||||
|
// 1. cells.road => cells.route; 1 = MAIN; 2 = TRAIL; 3 = SEA;
|
||||||
|
// 2. cells.crossroad is removed
|
||||||
|
// 3. pack.routes is added
|
||||||
|
// 4. rendering is changed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,15 +134,15 @@ window.Routes = (function () {
|
||||||
const routes = [];
|
const routes = [];
|
||||||
|
|
||||||
for (const {feature, cells} of mainRoads) {
|
for (const {feature, cells} of mainRoads) {
|
||||||
routes.push({i: routes.length, type: "road", feature, cells});
|
routes.push({i: routes.length, group: "roads", feature, cells});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const {feature, cells} of trails) {
|
for (const {feature, cells} of trails) {
|
||||||
routes.push({i: routes.length, type: "trail", feature, cells});
|
routes.push({i: routes.length, group: "trails", feature, cells});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const {feature, cells} of seaRoutes) {
|
for (const {feature, cells} of seaRoutes) {
|
||||||
routes.push({i: routes.length, type: "sea", feature, cells});
|
routes.push({i: routes.length, group: "searoutes", feature, cells});
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ function restoreLayers() {
|
||||||
if (layerIsOn("toggleGrid")) drawGrid();
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||||
if (layerIsOn("toggleCompass")) compass.style("display", "block");
|
if (layerIsOn("toggleCompass")) compass.style("display", "block");
|
||||||
|
if (layerIsOn("toggleRoutes")) drawRoutes();
|
||||||
if (layerIsOn("toggleTemp")) drawTemp();
|
if (layerIsOn("toggleTemp")) drawTemp();
|
||||||
if (layerIsOn("togglePrec")) drawPrec();
|
if (layerIsOn("togglePrec")) drawPrec();
|
||||||
if (layerIsOn("togglePopulation")) drawPopulation();
|
if (layerIsOn("togglePopulation")) drawPopulation();
|
||||||
|
|
@ -1624,18 +1625,137 @@ function drawRivers() {
|
||||||
function toggleRoutes(event) {
|
function toggleRoutes(event) {
|
||||||
if (!layerIsOn("toggleRoutes")) {
|
if (!layerIsOn("toggleRoutes")) {
|
||||||
turnButtonOn("toggleRoutes");
|
turnButtonOn("toggleRoutes");
|
||||||
$("#routes").fadeIn();
|
drawRoutes();
|
||||||
if (event && isCtrlClick(event)) editStyle("routes");
|
if (event && isCtrlClick(event)) editStyle("routes");
|
||||||
} else {
|
} else {
|
||||||
if (event && isCtrlClick(event)) {
|
if (event && isCtrlClick(event)) return editStyle("routes");
|
||||||
editStyle("routes");
|
routes.selectAll("path").remove();
|
||||||
return;
|
|
||||||
}
|
|
||||||
$("#routes").fadeOut();
|
|
||||||
turnButtonOff("toggleRoutes");
|
turnButtonOff("toggleRoutes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawRoutes() {
|
||||||
|
TIME && console.time("drawRoutes");
|
||||||
|
const {cells, burgs} = pack;
|
||||||
|
const lineGen = d3.line();
|
||||||
|
|
||||||
|
const SHARP_ANGLE = 135;
|
||||||
|
const VERY_SHARP_ANGLE = 115;
|
||||||
|
|
||||||
|
const points = adjustBurgPoints(); // mutable array of points
|
||||||
|
const routePaths = {};
|
||||||
|
|
||||||
|
const lineGenMap = {
|
||||||
|
roads: d3.curveCatmullRom.alpha(0.1),
|
||||||
|
trails: d3.curveCatmullRom.alpha(0.1),
|
||||||
|
searoutes: d3.curveBasis,
|
||||||
|
default: d3.curveCatmullRom.alpha(0.1)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const {i, group, cells} of pack.routes) {
|
||||||
|
if (group !== "searoutes") straightenPathAngles(cells); // mutates points
|
||||||
|
const pathPoints = getPathPoints(cells);
|
||||||
|
|
||||||
|
lineGen.curve(lineGenMap[group] || lineGenMap.default);
|
||||||
|
const path = round(lineGen(pathPoints), 1);
|
||||||
|
|
||||||
|
if (!routePaths[group]) routePaths[group] = [];
|
||||||
|
routePaths[group].push(`<path id="route${i}" d="${path}"/>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.selectAll("path").remove();
|
||||||
|
for (const group in routePaths) {
|
||||||
|
routes.select("#" + group).html(routePaths[group].join(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TIME && console.timeEnd("drawRoutes");
|
||||||
|
|
||||||
|
function adjustBurgPoints() {
|
||||||
|
const points = Array.from(cells.p);
|
||||||
|
|
||||||
|
for (const burg of burgs) {
|
||||||
|
if (burg.i === 0) continue;
|
||||||
|
const {cell, x, y} = burg;
|
||||||
|
points[cell] = [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
function straightenPathAngles(cellIds) {
|
||||||
|
for (let i = 1; i < cellIds.length - 1; i++) {
|
||||||
|
const cellId = cellIds[i];
|
||||||
|
if (cells.burg[cellId]) continue;
|
||||||
|
|
||||||
|
const prev = points[cellIds[i - 1]];
|
||||||
|
const that = points[cellId];
|
||||||
|
const next = points[cellIds[i + 1]];
|
||||||
|
|
||||||
|
const dAx = prev[0] - that[0];
|
||||||
|
const dAy = prev[1] - that[1];
|
||||||
|
const dBx = next[0] - that[0];
|
||||||
|
const dBy = next[1] - that[1];
|
||||||
|
const angle = (Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI;
|
||||||
|
|
||||||
|
if (Math.abs(angle) < SHARP_ANGLE) {
|
||||||
|
const middleX = (prev[0] + next[0]) / 2;
|
||||||
|
const middleY = (prev[1] + next[1]) / 2;
|
||||||
|
|
||||||
|
if (Math.abs(angle) < VERY_SHARP_ANGLE) {
|
||||||
|
const newX = (that[0] + middleX * 2) / 3;
|
||||||
|
const newY = (that[1] + middleY * 2) / 3;
|
||||||
|
points[cellId] = [newX, newY];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newX = (that[0] + middleX) / 2;
|
||||||
|
const newY = (that[1] + middleY) / 2;
|
||||||
|
points[cellId] = [newX, newY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathPoints(cellIds) {
|
||||||
|
const pathPoints = cellIds.map(cellId => points[cellId]);
|
||||||
|
|
||||||
|
if (pathPoints.length === 2) {
|
||||||
|
// curve and shorten 2-points line
|
||||||
|
const [[x1, y1], [x2, y2]] = pathPoints;
|
||||||
|
|
||||||
|
const middleX = (x1 + x2) / 2;
|
||||||
|
const middleY = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
// add shifted point at the middle to curve the line a bit
|
||||||
|
const NORMAL_LENGTH = 0.3;
|
||||||
|
const normal = getNormal([x1, y1], [x2, y2]);
|
||||||
|
const sign = cellIds[0] % 2 ? 1 : -1;
|
||||||
|
const normalX = middleX + NORMAL_LENGTH * Math.cos(normal) * sign;
|
||||||
|
const normalY = middleY + NORMAL_LENGTH * Math.sin(normal) * sign;
|
||||||
|
|
||||||
|
// make line shorter to avoid overlapping with other lines
|
||||||
|
const SHORT_LINE_LENGTH_MODIFIER = 0.8;
|
||||||
|
const distX = x2 - x1;
|
||||||
|
const distY = y2 - y1;
|
||||||
|
const nx1 = x1 + distX * SHORT_LINE_LENGTH_MODIFIER;
|
||||||
|
const ny1 = y1 + distY * SHORT_LINE_LENGTH_MODIFIER;
|
||||||
|
const nx2 = x2 - distX * SHORT_LINE_LENGTH_MODIFIER;
|
||||||
|
const ny2 = y2 - distY * SHORT_LINE_LENGTH_MODIFIER;
|
||||||
|
|
||||||
|
return [
|
||||||
|
[nx1, ny1],
|
||||||
|
[normalX, normalY],
|
||||||
|
[nx2, ny2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormal([x1, y1], [x2, y2]) {
|
||||||
|
return Math.atan2(y1 - y2, x1 - x2) + Math.PI / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleMilitary() {
|
function toggleMilitary() {
|
||||||
if (!layerIsOn("toggleMilitary")) {
|
if (!layerIsOn("toggleMilitary")) {
|
||||||
turnButtonOn("toggleMilitary");
|
turnButtonOn("toggleMilitary");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue