diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index 6facd2d9..dfd734cb 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -253,11 +253,11 @@ window.BurgsAndStates = (() => {
return "Generic";
};
- const defineBurgFeatures = newburg => {
+ const defineBurgFeatures = burg => {
const {cells} = pack;
pack.burgs
- .filter(b => (newburg ? b.i == newburg.i : b.i && !b.removed && !b.lock))
+ .filter(b => (burg ? b.i == burg.i : b.i && !b.removed && !b.lock))
.forEach(b => {
const pop = b.population;
b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1));
diff --git a/modules/routes-generator.js b/modules/routes-generator.js
index 96315e11..62f6a8c7 100644
--- a/modules/routes-generator.js
+++ b/modules/routes-generator.js
@@ -172,58 +172,6 @@ window.Routes = (function () {
return routesMerged > 1 ? mergeRoutes(routes) : routes;
}
- 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(group, cells, points) {
- const data = cells.map(cellId => [...points[cellId], cellId]);
-
- // resolve sharp angles
- if (group !== "searoutes") {
- for (let i = 1; i < cells.length - 1; i++) {
- const cellId = cells[i];
- if (pack.cells.burg[cellId]) continue;
-
- const [prevX, prevY] = data[i - 1];
- const [currX, currY] = data[i];
- const [nextX, nextY] = data[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) {
- data[i] = [newX, newY, cellId];
- points[cellId] = [data[i][0], data[i][1]]; // change cell coordinate for all routes
- }
- }
- }
- }
-
- return data; // [[x, y, cell], [x, y, cell]];
- }
-
function buildLinks(routes) {
const links = {};
@@ -247,6 +195,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(group, cells, points) {
+ const data = cells.map(cellId => [...points[cellId], cellId]);
+
+ // resolve sharp angles
+ if (group !== "searoutes") {
+ for (let i = 1; i < cells.length - 1; i++) {
+ const cellId = cells[i];
+ if (pack.cells.burg[cellId]) continue;
+
+ const [prevX, prevY] = data[i - 1];
+ const [currX, currY] = data[i];
+ const [nextX, nextY] = data[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) {
+ data[i] = [newX, newY, cellId];
+ points[cellId] = [data[i][0], data[i][1]]; // change cell coordinate for all routes
+ }
+ }
+ }
+ }
+
+ return data; // [[x, y, cell], [x, y, cell]];
+ }
+
const MIN_PASSABLE_SEA_TEMP = -4;
const TYPE_MODIFIERS = {
"-1": 1, // coastline
@@ -418,6 +418,80 @@ window.Routes = (function () {
return edges;
}
+ // connect cell with routes system by land
+ function connect(cellId) {
+ if (isConnected(cellId)) return;
+
+ const {cells, routes} = pack;
+
+ const path = findConnectionPath(cellId);
+ if (!path) return;
+
+ const pathCells = restorePath(...path);
+ const pointsArray = preparePointsArray();
+ const points = getPoints("trails", pathCells, pointsArray);
+ const feature = cells.f[cellId];
+
+ const routeId = Math.max(...routes.map(route => route.i)) + 1;
+ const newRoute = {i: routeId, group: "trails", feature, points};
+ routes.push(newRoute);
+
+ for (let i = 0; i < pathCells.length; i++) {
+ const cellId = pathCells[i];
+ const nextCellId = pathCells[i + 1];
+ if (nextCellId) addConnection(cellId, nextCellId, routeId);
+ }
+
+ return newRoute;
+
+ function findConnectionPath(start) {
+ const from = [];
+ const cost = [];
+ const queue = new FlatQueue();
+ queue.push(start, 0);
+
+ while (queue.length) {
+ const priority = queue.peekValue();
+ const next = queue.pop();
+
+ for (const neibCellId of cells.c[next]) {
+ if (isConnected(neibCellId)) {
+ from[neibCellId] = next;
+ return [start, neibCellId, from];
+ }
+
+ if (cells.h[neibCellId] < 20) continue; // ignore water cells
+ const habitability = biomesData.habitability[cells.biome[neibCellId]];
+ if (!habitability) continue; // inhabitable cells are not passable (eg. lava, glacier)
+
+ const distanceCost = dist2(cells.p[next], cells.p[neibCellId]);
+ const habitabilityModifier = 1 + Math.max(100 - habitability, 0) / 1000; // [1, 1.1];
+ const heightModifier = 1 + Math.max(cells.h[neibCellId] - 25, 25) / 25; // [1, 3];
+
+ const cellsCost = distanceCost * habitabilityModifier * heightModifier;
+ const totalCost = priority + cellsCost;
+
+ if (totalCost >= cost[neibCellId]) continue;
+ from[neibCellId] = next;
+ cost[neibCellId] = totalCost;
+ queue.push(neibCellId, totalCost);
+ }
+ }
+
+ return null; // path is not found
+ }
+
+ function addConnection(from, to, routeId) {
+ const routes = pack.cells.routes;
+
+ if (!routes[from]) routes[from] = {};
+ routes[from][to] = routeId;
+
+ if (!routes[to]) routes[to] = {};
+ routes[to][from] = routeId;
+ }
+ }
+
// utility functions
function isConnected(cellId) {
const {routes} = pack.cells;
@@ -610,6 +684,20 @@ window.Routes = (function () {
}
}
+ const ROUTE_CURVES = {
+ roads: d3.curveCatmullRom.alpha(0.1),
+ trails: d3.curveCatmullRom.alpha(0.1),
+ searoutes: d3.curveCatmullRom.alpha(0.5),
+ default: d3.curveCatmullRom.alpha(0.1)
+ };
+
+ function getPath({group, points}) {
+ const lineGen = d3.line();
+ lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
+ const path = round(lineGen(points.map(p => [p[0], p[1]])), 1);
+ return path;
+ }
+
function getLength(routeId) {
const path = routes.select("#route" + routeId).node();
return path.getTotalLength();
@@ -638,12 +726,14 @@ window.Routes = (function () {
return {
generate,
+ connect,
isConnected,
areConnected,
getRoute,
hasRoad,
isCrossroad,
generateName,
+ getPath,
getLength,
remove
};
diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js
index 561722ce..8f68cbee 100644
--- a/modules/ui/burgs-overview.js
+++ b/modules/ui/burgs-overview.js
@@ -279,7 +279,8 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
function addBurgOnClick() {
const point = d3.mouse(this);
- const cell = findCell(point[0], point[1]);
+ const cell = findCell(...point);
+
if (pack.cells.h[cell] < 20)
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
if (pack.cells.burg[cell])
diff --git a/modules/ui/editors.js b/modules/ui/editors.js
index 79dcef4e..dfaeb9a2 100644
--- a/modules/ui/editors.js
+++ b/modules/ui/editors.js
@@ -132,27 +132,43 @@ function applySorting(headers) {
}
function addBurg(point) {
- const cells = pack.cells;
- const x = rn(point[0], 2),
- y = rn(point[1], 2);
- const cell = findCell(x, point[1]);
- const i = pack.burgs.length;
- const culture = cells.culture[cell];
- const name = Names.getCulture(culture);
- const state = cells.state[cell];
- const feature = cells.f[cell];
+ const {cells, states} = pack;
+ const x = rn(point[0], 2);
+ const y = rn(point[1], 2);
- const temple = pack.states[state].form === "Theocracy";
- const population = Math.max(cells.s[cell] / 3 + i / 1000 + (cell % 100) / 1000, 0.1);
- const type = BurgsAndStates.getType(cell, false);
+ const cellId = findCell(x, y);
+ const i = pack.burgs.length;
+ const culture = cells.culture[cellId];
+ const name = Names.getCulture(culture);
+ const state = cells.state[cellId];
+ const feature = cells.f[cellId];
+
+ const population = Math.max(cells.s[cellId] / 3 + i / 1000 + (cellId % 100) / 1000, 0.1);
+ const type = BurgsAndStates.getType(cellId, false);
// generate emblem
- const coa = COA.generate(pack.states[state].coa, 0.25, null, type);
+ const coa = COA.generate(states[state].coa, 0.25, null, type);
coa.shield = COA.getShield(culture, state);
COArenderer.add("burg", i, coa, x, y);
- pack.burgs.push({name, cell, x, y, state, i, culture, feature, capital: 0, port: 0, temple, population, coa, type});
- cells.burg[cell] = i;
+ const burg = {
+ name,
+ cell: cellId,
+ x,
+ y,
+ state,
+ i,
+ culture,
+ feature,
+ capital: 0,
+ port: 0,
+ temple: 0,
+ population,
+ coa,
+ type
+ };
+ pack.burgs.push(burg);
+ cells.burg[cellId] = i;
const townSize = burgIcons.select("#towns").attr("size") || 0.5;
burgIcons
@@ -173,7 +189,17 @@ function addBurg(point) {
.attr("dy", `${townSize * -1.5}px`)
.text(name);
- BurgsAndStates.defineBurgFeatures(pack.burgs[i]);
+ BurgsAndStates.defineBurgFeatures(burg);
+
+ const newRoute = Routes.connect(cellId);
+ if (newRoute && layerIsOn("toggleRoutes")) {
+ routes
+ .select("#" + newRoute.group)
+ .append("path")
+ .attr("d", Routes.getPath(newRoute))
+ .attr("id", "route" + newRoute.i);
+ }
+
return i;
}
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index 4bae98f7..0b85aa0c 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -1631,26 +1631,14 @@ function toggleRoutes(event) {
}
}
-const ROUTE_CURVES = {
- roads: d3.curveCatmullRom.alpha(0.1),
- trails: d3.curveCatmullRom.alpha(0.1),
- searoutes: d3.curveCatmullRom.alpha(0.5),
- default: d3.curveCatmullRom.alpha(0.1)
-};
-
function drawRoutes() {
TIME && console.time("drawRoutes");
const routePaths = {};
- const lineGen = d3.line();
for (const route of pack.routes) {
const {i, group} = route;
- lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
- const points = route.points.map(p => [p[0], p[1]]);
- const path = round(lineGen(points), 1);
-
if (!routePaths[group]) routePaths[group] = [];
- routePaths[group].push(``);
+ routePaths[group].push(``);
}
routes.selectAll("path").remove();
diff --git a/modules/ui/routes-creator.js b/modules/ui/routes-creator.js
index 0be3f277..cd9d853f 100644
--- a/modules/ui/routes-creator.js
+++ b/modules/ui/routes-creator.js
@@ -84,15 +84,12 @@ function createRoute(defaultGroup) {
.attr("r", 0.6);
const group = byId("routeCreatorGroupSelect").value;
- const lineGen = d3.line();
- lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
- const path = round(lineGen(points), 1);
routes.select("#routeTemp").remove();
routes
.select("#" + group)
.append("path")
- .attr("d", path)
+ .attr("d", Routes.getPath({group, points}))
.attr("id", "routeTemp");
}
diff --git a/modules/ui/routes-editor.js b/modules/ui/routes-editor.js
index 9e05242a..067213e8 100644
--- a/modules/ui/routes-editor.js
+++ b/modules/ui/routes-editor.js
@@ -134,13 +134,7 @@ function editRoute(id) {
}
function redrawRoute(route) {
- const lineGen = d3.line();
- lineGen.curve(ROUTE_CURVES[route.group] || ROUTE_CURVES.default);
-
- const points = route.points.map(p => [p[0], p[1]]);
- const path = round(lineGen(points), 1);
- elSelected.attr("d", path);
-
+ elSelected.attr("d", Routes.getPath(route));
updateRouteLength(route);
if (byId("elevationProfile").offsetParent) showRouteElevationProfile();
}