mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-21 19:41:23 +01:00
feat: routes - add route on burg creation
This commit is contained in:
parent
16b441c5cf
commit
6907c9dcac
7 changed files with 191 additions and 95 deletions
|
|
@ -253,11 +253,11 @@ window.BurgsAndStates = (() => {
|
||||||
return "Generic";
|
return "Generic";
|
||||||
};
|
};
|
||||||
|
|
||||||
const defineBurgFeatures = newburg => {
|
const defineBurgFeatures = burg => {
|
||||||
const {cells} = pack;
|
const {cells} = pack;
|
||||||
|
|
||||||
pack.burgs
|
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 => {
|
.forEach(b => {
|
||||||
const pop = b.population;
|
const pop = b.population;
|
||||||
b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1));
|
b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1));
|
||||||
|
|
|
||||||
|
|
@ -172,58 +172,6 @@ window.Routes = (function () {
|
||||||
return routesMerged > 1 ? mergeRoutes(routes) : routes;
|
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) {
|
function buildLinks(routes) {
|
||||||
const links = {};
|
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 MIN_PASSABLE_SEA_TEMP = -4;
|
||||||
const TYPE_MODIFIERS = {
|
const TYPE_MODIFIERS = {
|
||||||
"-1": 1, // coastline
|
"-1": 1, // coastline
|
||||||
|
|
@ -418,6 +418,80 @@ window.Routes = (function () {
|
||||||
return edges;
|
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
|
// utility functions
|
||||||
function isConnected(cellId) {
|
function isConnected(cellId) {
|
||||||
const {routes} = pack.cells;
|
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) {
|
function getLength(routeId) {
|
||||||
const path = routes.select("#route" + routeId).node();
|
const path = routes.select("#route" + routeId).node();
|
||||||
return path.getTotalLength();
|
return path.getTotalLength();
|
||||||
|
|
@ -638,12 +726,14 @@ window.Routes = (function () {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generate,
|
generate,
|
||||||
|
connect,
|
||||||
isConnected,
|
isConnected,
|
||||||
areConnected,
|
areConnected,
|
||||||
getRoute,
|
getRoute,
|
||||||
hasRoad,
|
hasRoad,
|
||||||
isCrossroad,
|
isCrossroad,
|
||||||
generateName,
|
generateName,
|
||||||
|
getPath,
|
||||||
getLength,
|
getLength,
|
||||||
remove
|
remove
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,8 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
|
||||||
|
|
||||||
function addBurgOnClick() {
|
function addBurgOnClick() {
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
const cell = findCell(point[0], point[1]);
|
const cell = findCell(...point);
|
||||||
|
|
||||||
if (pack.cells.h[cell] < 20)
|
if (pack.cells.h[cell] < 20)
|
||||||
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||||
if (pack.cells.burg[cell])
|
if (pack.cells.burg[cell])
|
||||||
|
|
|
||||||
|
|
@ -132,27 +132,43 @@ function applySorting(headers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBurg(point) {
|
function addBurg(point) {
|
||||||
const cells = pack.cells;
|
const {cells, states} = pack;
|
||||||
const x = rn(point[0], 2),
|
const x = rn(point[0], 2);
|
||||||
y = rn(point[1], 2);
|
const 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 temple = pack.states[state].form === "Theocracy";
|
const cellId = findCell(x, y);
|
||||||
const population = Math.max(cells.s[cell] / 3 + i / 1000 + (cell % 100) / 1000, 0.1);
|
const i = pack.burgs.length;
|
||||||
const type = BurgsAndStates.getType(cell, false);
|
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
|
// 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);
|
coa.shield = COA.getShield(culture, state);
|
||||||
COArenderer.add("burg", i, coa, x, y);
|
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});
|
const burg = {
|
||||||
cells.burg[cell] = i;
|
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;
|
const townSize = burgIcons.select("#towns").attr("size") || 0.5;
|
||||||
burgIcons
|
burgIcons
|
||||||
|
|
@ -173,7 +189,17 @@ function addBurg(point) {
|
||||||
.attr("dy", `${townSize * -1.5}px`)
|
.attr("dy", `${townSize * -1.5}px`)
|
||||||
.text(name);
|
.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;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
function drawRoutes() {
|
||||||
TIME && console.time("drawRoutes");
|
TIME && console.time("drawRoutes");
|
||||||
const routePaths = {};
|
const routePaths = {};
|
||||||
const lineGen = d3.line();
|
|
||||||
|
|
||||||
for (const route of pack.routes) {
|
for (const route of pack.routes) {
|
||||||
const {i, group} = route;
|
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] = [];
|
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();
|
routes.selectAll("path").remove();
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,12 @@ function createRoute(defaultGroup) {
|
||||||
.attr("r", 0.6);
|
.attr("r", 0.6);
|
||||||
|
|
||||||
const group = byId("routeCreatorGroupSelect").value;
|
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("#routeTemp").remove();
|
||||||
routes
|
routes
|
||||||
.select("#" + group)
|
.select("#" + group)
|
||||||
.append("path")
|
.append("path")
|
||||||
.attr("d", path)
|
.attr("d", Routes.getPath({group, points}))
|
||||||
.attr("id", "routeTemp");
|
.attr("id", "routeTemp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,13 +134,7 @@ function editRoute(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function redrawRoute(route) {
|
function redrawRoute(route) {
|
||||||
const lineGen = d3.line();
|
elSelected.attr("d", Routes.getPath(route));
|
||||||
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);
|
|
||||||
|
|
||||||
updateRouteLength(route);
|
updateRouteLength(route);
|
||||||
if (byId("elevationProfile").offsetParent) showRouteElevationProfile();
|
if (byId("elevationProfile").offsetParent) showRouteElevationProfile();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue