feat: routes - add route on burg creation

This commit is contained in:
Azgaar 2024-08-11 15:47:15 +02:00
parent 16b441c5cf
commit 6907c9dcac
7 changed files with 191 additions and 95 deletions

View file

@ -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));

View file

@ -172,6 +172,29 @@ window.Routes = (function () {
return routesMerged > 1 ? mergeRoutes(routes) : routes;
}
function buildLinks(routes) {
const links = {};
for (const {points, i: routeId} of routes) {
const cells = points.map(p => p[2]);
for (let i = 0; i < cells.length; i++) {
const cellId = cells[i];
const nextCellId = cells[i + 1];
if (nextCellId && cellId !== nextCellId) {
if (!links[cellId]) links[cellId] = {};
links[cellId][nextCellId] = routeId;
if (!links[nextCellId]) links[nextCellId] = {};
links[nextCellId][cellId] = routeId;
}
}
}
return links;
}
}
function preparePointsArray() {
const {cells, burgs} = pack;
return cells.p.map(([x, y], cellId) => {
@ -224,29 +247,6 @@ window.Routes = (function () {
return data; // [[x, y, cell], [x, y, cell]];
}
function buildLinks(routes) {
const links = {};
for (const {points, i: routeId} of routes) {
const cells = points.map(p => p[2]);
for (let i = 0; i < cells.length; i++) {
const cellId = cells[i];
const nextCellId = cells[i + 1];
if (nextCellId && cellId !== nextCellId) {
if (!links[cellId]) links[cellId] = {};
links[cellId][nextCellId] = routeId;
if (!links[nextCellId]) links[nextCellId] = {};
links[nextCellId][cellId] = routeId;
}
}
}
return links;
}
}
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
};

View file

@ -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])

View file

@ -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;
}

View file

@ -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(`<path id="route${i}" d="${path}"/>`);
routePaths[group].push(`<path id="route${i}" d="${Routes.getPath(route)}"/>`);
}
routes.selectAll("path").remove();

View file

@ -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");
}

View file

@ -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();
}