mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 18:11:24 +01:00
refactor: submap - don't add middle points, unified findPath fn
This commit is contained in:
parent
3d79a527e2
commit
41a710302b
8 changed files with 103 additions and 238 deletions
|
|
@ -10,6 +10,7 @@ window.Resample = (function () {
|
||||||
*/
|
*/
|
||||||
function process({projection, inverse, scale}) {
|
function process({projection, inverse, scale}) {
|
||||||
const parentMap = {grid: deepCopy(grid), pack: deepCopy(pack), notes: deepCopy(notes)};
|
const parentMap = {grid: deepCopy(grid), pack: deepCopy(pack), notes: deepCopy(notes)};
|
||||||
|
const riversData = saveRiversData(pack.rivers);
|
||||||
|
|
||||||
grid = generateGrid();
|
grid = generateGrid();
|
||||||
pack = {};
|
pack = {};
|
||||||
|
|
@ -30,7 +31,7 @@ window.Resample = (function () {
|
||||||
createDefaultRuler();
|
createDefaultRuler();
|
||||||
|
|
||||||
restoreCellData(parentMap, inverse, scale);
|
restoreCellData(parentMap, inverse, scale);
|
||||||
restoreRivers(parentMap, projection, scale);
|
restoreRivers(riversData, projection, scale);
|
||||||
restoreCultures(parentMap, projection);
|
restoreCultures(parentMap, projection);
|
||||||
restoreBurgs(parentMap, projection, scale);
|
restoreBurgs(parentMap, projection, scale);
|
||||||
restoreStates(parentMap, projection);
|
restoreStates(parentMap, projection);
|
||||||
|
|
@ -104,28 +105,30 @@ window.Resample = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreRivers(parentMap, projection, scale) {
|
function saveRiversData(parentRivers) {
|
||||||
|
return parentRivers.map(river => {
|
||||||
|
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||||
|
return {...river, meanderedPoints};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreRivers(riversData, projection, scale) {
|
||||||
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
||||||
pack.cells.conf = new Uint8Array(pack.cells.i.length);
|
pack.cells.conf = new Uint8Array(pack.cells.i.length);
|
||||||
const offset = grid.spacing * 2;
|
|
||||||
|
|
||||||
const getCellCost = cellId => {
|
pack.rivers = riversData
|
||||||
if (pack.cells.h[cellId] < 20) return Infinity;
|
|
||||||
return pack.cells.h[cellId];
|
|
||||||
};
|
|
||||||
|
|
||||||
pack.rivers = parentMap.pack.rivers
|
|
||||||
.map(river => {
|
.map(river => {
|
||||||
const parentPoints = river.points || river.cells.map(cellId => parentMap.pack.cells.p[cellId]);
|
let wasInMap = true;
|
||||||
const newPoints = parentPoints
|
const points = [];
|
||||||
.map(([parentX, parentY]) => {
|
|
||||||
const [x, y] = projection(parentX, parentY);
|
river.meanderedPoints.forEach(([parentX, parentY]) => {
|
||||||
return isInMap(x, y, offset) ? [rn(x, 2), rn(y, 2)] : null;
|
const [x, y] = projection(parentX, parentY);
|
||||||
})
|
const inMap = isInMap(x, y);
|
||||||
.filter(Boolean);
|
if (inMap || wasInMap) points.push([rn(x, 2), rn(y, 2)]);
|
||||||
if (newPoints.length < 2) return null;
|
wasInMap = inMap;
|
||||||
|
});
|
||||||
|
if (points.length < 2) return null;
|
||||||
|
|
||||||
const points = addIntermidiatePoints(newPoints, getCellCost);
|
|
||||||
const cells = points.map(point => findCell(...point));
|
const cells = points.map(point => findCell(...point));
|
||||||
cells.forEach(cellId => {
|
cells.forEach(cellId => {
|
||||||
if (pack.cells.r[cellId]) pack.cells.conf[cellId] = 1;
|
if (pack.cells.r[cellId]) pack.cells.conf[cellId] = 1;
|
||||||
|
|
@ -218,20 +221,17 @@ window.Resample = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreRoutes(parentMap, projection) {
|
function restoreRoutes(parentMap, projection) {
|
||||||
const offset = grid.spacing * 2;
|
|
||||||
|
|
||||||
pack.routes = parentMap.pack.routes
|
pack.routes = parentMap.pack.routes
|
||||||
.map(route => {
|
.map(route => {
|
||||||
const points = route.points
|
let wasInMap = true;
|
||||||
.map(([parentX, parentY]) => {
|
const points = [];
|
||||||
const [x, y] = projection(parentX, parentY);
|
|
||||||
if (!isInMap(x, y, offset)) return null;
|
|
||||||
|
|
||||||
const cell = findCell(x, y);
|
|
||||||
return [rn(x, 2), rn(y, 2), cell];
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
|
route.points.forEach(([parentX, parentY]) => {
|
||||||
|
const [x, y] = projection(parentX, parentY);
|
||||||
|
const inMap = isInMap(x, y);
|
||||||
|
if (inMap || wasInMap) points.push([rn(x, 2), rn(y, 2)]);
|
||||||
|
wasInMap = inMap;
|
||||||
|
});
|
||||||
if (points.length < 2) return null;
|
if (points.length < 2) return null;
|
||||||
|
|
||||||
const firstCell = points[0][2];
|
const firstCell = points[0][2];
|
||||||
|
|
@ -333,29 +333,12 @@ window.Resample = (function () {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill gaps in points array with intermidiate points
|
|
||||||
function addIntermidiatePoints(points, getCellCost) {
|
|
||||||
const newPoints = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < points.length; i++) {
|
|
||||||
newPoints.push(points[i]);
|
|
||||||
if (points[i + 1]) {
|
|
||||||
const start = findCell(...points[i]);
|
|
||||||
const exit = findCell(...points[i + 1]);
|
|
||||||
const pathCells = findPath(start, exit, getCellCost);
|
|
||||||
if (pathCells) newPoints.push(...pathCells.map(cellId => pack.cells.p[cellId]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isWater(graph, cellId) {
|
function isWater(graph, cellId) {
|
||||||
return graph.cells.h[cellId] < 20;
|
return graph.cells.h[cellId] < 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInMap(x, y, offset = 0) {
|
function isInMap(x, y) {
|
||||||
return x + offset >= 0 && x - offset <= graphWidth && y + offset >= 0 && y - offset <= graphHeight;
|
return x >= 0 && x <= graphWidth && y >= 0 && y <= graphHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {process};
|
return {process};
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,7 @@ window.Rivers = (function () {
|
||||||
|
|
||||||
// build polygon from a list of points and calculated offset (width)
|
// build polygon from a list of points and calculated offset (width)
|
||||||
const getRiverPath = (points, widthFactor, startingWidth) => {
|
const getRiverPath = (points, widthFactor, startingWidth) => {
|
||||||
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
const riverPointsLeft = [];
|
const riverPointsLeft = [];
|
||||||
const riverPointsRight = [];
|
const riverPointsRight = [];
|
||||||
let flux = 0;
|
let flux = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
const ROUTES_SHARP_ANGLE = 135;
|
const ROUTES_SHARP_ANGLE = 135;
|
||||||
const ROUTES_VERY_SHARP_ANGLE = 115;
|
const ROUTES_VERY_SHARP_ANGLE = 115;
|
||||||
|
|
||||||
|
const MIN_PASSABLE_SEA_TEMP = -4;
|
||||||
|
const ROUTE_TYPE_MODIFIERS = {
|
||||||
|
"-1": 1, // coastline
|
||||||
|
"-2": 1.8, // sea
|
||||||
|
"-3": 4, // open sea
|
||||||
|
"-4": 6, // ocean
|
||||||
|
default: 8 // far ocean
|
||||||
|
};
|
||||||
|
|
||||||
window.Routes = (function () {
|
window.Routes = (function () {
|
||||||
function generate(lockedRoutes = []) {
|
function generate(lockedRoutes = []) {
|
||||||
const {capitalsByFeature, burgsByFeature, portsByFeature} = sortBurgsByFeature(pack.burgs);
|
const {capitalsByFeature, burgsByFeature, portsByFeature} = sortBurgsByFeature(pack.burgs);
|
||||||
|
|
@ -118,10 +127,8 @@ window.Routes = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPathSegments({isWater, connections, start, exit}) {
|
function findPathSegments({isWater, connections, start, exit}) {
|
||||||
const from = findPath(isWater, start, exit, connections);
|
const getCost = createCostEvaluator({isWater, connections});
|
||||||
if (!from) return [];
|
const pathCells = findPath(start, current => current === exit, getCost);
|
||||||
|
|
||||||
const pathCells = restorePath(start, exit, from);
|
|
||||||
const segments = getRouteSegments(pathCells, connections);
|
const segments = getRouteSegments(pathCells, connections);
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +181,38 @@ window.Routes = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createCostEvaluator({isWater, connections}) {
|
||||||
|
return isWater ? getWaterPathCost : getLandPathCost;
|
||||||
|
|
||||||
|
function getLandPathCost(current, next) {
|
||||||
|
if (pack.cells.h[next] < 20) return Infinity; // ignore water cells
|
||||||
|
|
||||||
|
const habitability = biomesData.habitability[pack.cells.biome[next]];
|
||||||
|
if (!habitability) return Infinity; // inhabitable cells are not passable (e.g. glacier)
|
||||||
|
|
||||||
|
const distanceCost = dist2(pack.cells.p[current], pack.cells.p[next]);
|
||||||
|
const habitabilityModifier = 1 + Math.max(100 - habitability, 0) / 1000; // [1, 1.1];
|
||||||
|
const heightModifier = 1 + Math.max(pack.cells.h[next] - 25, 25) / 25; // [1, 3];
|
||||||
|
const connectionModifier = connections.has(`${current}-${next}`) ? 0.5 : 1;
|
||||||
|
const burgModifier = pack.cells.burg[next] ? 1 : 3;
|
||||||
|
|
||||||
|
const pathCost = distanceCost * habitabilityModifier * heightModifier * connectionModifier * burgModifier;
|
||||||
|
return pathCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWaterPathCost(current, next) {
|
||||||
|
if (pack.cells.h[next] >= 20) return Infinity; // ignore land cells
|
||||||
|
if (grid.cells.temp[pack.cells.g[next]] < MIN_PASSABLE_SEA_TEMP) return Infinity; // ignore too cold cells
|
||||||
|
|
||||||
|
const distanceCost = dist2(pack.cells.p[current], pack.cells.p[next]);
|
||||||
|
const typeModifier = ROUTE_TYPE_MODIFIERS[pack.cells.t[next]] || ROUTE_TYPE_MODIFIERS.default;
|
||||||
|
const connectionModifier = connections.has(`${current}-${next}`) ? 0.5 : 1;
|
||||||
|
|
||||||
|
const pathCost = distanceCost * typeModifier * connectionModifier;
|
||||||
|
return pathCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildLinks(routes) {
|
function buildLinks(routes) {
|
||||||
const links = {};
|
const links = {};
|
||||||
|
|
||||||
|
|
@ -249,109 +288,6 @@ window.Routes = (function () {
|
||||||
return data; // [[x, y, cell], [x, y, cell]];
|
return data; // [[x, y, cell], [x, y, cell]];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_PASSABLE_SEA_TEMP = -4;
|
|
||||||
const TYPE_MODIFIERS = {
|
|
||||||
"-1": 1, // coastline
|
|
||||||
"-2": 1.8, // sea
|
|
||||||
"-3": 4, // open sea
|
|
||||||
"-4": 6, // ocean
|
|
||||||
default: 8 // far ocean
|
|
||||||
};
|
|
||||||
|
|
||||||
function findPath(isWater, start, exit, connections) {
|
|
||||||
const {temp} = grid.cells;
|
|
||||||
const {cells} = pack;
|
|
||||||
|
|
||||||
const from = [];
|
|
||||||
const cost = [];
|
|
||||||
const queue = new FlatQueue();
|
|
||||||
queue.push(start, 0);
|
|
||||||
|
|
||||||
return isWater ? findWaterPath() : findLandPath();
|
|
||||||
|
|
||||||
function findLandPath() {
|
|
||||||
while (queue.length) {
|
|
||||||
const priority = queue.peekValue();
|
|
||||||
const next = queue.pop();
|
|
||||||
|
|
||||||
for (const neibCellId of cells.c[next]) {
|
|
||||||
if (neibCellId === exit) {
|
|
||||||
from[neibCellId] = next;
|
|
||||||
return 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 connectionModifier = connections.has(`${next}-${neibCellId}`) ? 1 : 2;
|
|
||||||
const burgModifier = cells.burg[neibCellId] ? 1 : 3;
|
|
||||||
|
|
||||||
const cellCost = distanceCost * habitabilityModifier * heightModifier * connectionModifier * burgModifier;
|
|
||||||
const totalCost = priority + cellCost;
|
|
||||||
|
|
||||||
if (totalCost >= cost[neibCellId]) continue;
|
|
||||||
from[neibCellId] = next;
|
|
||||||
cost[neibCellId] = totalCost;
|
|
||||||
queue.push(neibCellId, totalCost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // path is not found
|
|
||||||
}
|
|
||||||
|
|
||||||
function findWaterPath() {
|
|
||||||
while (queue.length) {
|
|
||||||
const priority = queue.peekValue();
|
|
||||||
const next = queue.pop();
|
|
||||||
|
|
||||||
for (const neibCellId of cells.c[next]) {
|
|
||||||
if (neibCellId === exit) {
|
|
||||||
from[neibCellId] = next;
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cells.h[neibCellId] >= 20) continue; // ignore land cells
|
|
||||||
if (temp[cells.g[neibCellId]] < MIN_PASSABLE_SEA_TEMP) continue; // ignore too cold cells
|
|
||||||
|
|
||||||
const distanceCost = dist2(cells.p[next], cells.p[neibCellId]);
|
|
||||||
const typeModifier = TYPE_MODIFIERS[cells.t[neibCellId]] || TYPE_MODIFIERS.default;
|
|
||||||
const connectionModifier = connections.has(`${next}-${neibCellId}`) ? 1 : 2;
|
|
||||||
|
|
||||||
const cellsCost = distanceCost * typeModifier * connectionModifier;
|
|
||||||
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 restorePath(start, end, from) {
|
|
||||||
const cells = [];
|
|
||||||
|
|
||||||
let current = end;
|
|
||||||
let prev = end;
|
|
||||||
|
|
||||||
while (current !== start) {
|
|
||||||
cells.push(current);
|
|
||||||
prev = from[current];
|
|
||||||
current = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
cells.push(current);
|
|
||||||
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRouteSegments(pathCells, connections) {
|
function getRouteSegments(pathCells, connections) {
|
||||||
const segments = [];
|
const segments = [];
|
||||||
let segment = [];
|
let segment = [];
|
||||||
|
|
@ -422,21 +358,16 @@ window.Routes = (function () {
|
||||||
|
|
||||||
// connect cell with routes system by land
|
// connect cell with routes system by land
|
||||||
function connect(cellId) {
|
function connect(cellId) {
|
||||||
if (isConnected(cellId)) return;
|
const getCost = createCostEvaluator({isWater: false, connections: new Map()});
|
||||||
|
const pathCells = findPath(cellId, isConnected, getCost);
|
||||||
|
if (!pathCells) return;
|
||||||
|
|
||||||
const {cells, routes} = pack;
|
|
||||||
|
|
||||||
const path = findConnectionPath(cellId);
|
|
||||||
if (!path) return;
|
|
||||||
|
|
||||||
const pathCells = restorePath(...path);
|
|
||||||
const pointsArray = preparePointsArray();
|
const pointsArray = preparePointsArray();
|
||||||
const points = getPoints("trails", pathCells, pointsArray);
|
const points = getPoints("trails", pathCells, pointsArray);
|
||||||
const feature = cells.f[cellId];
|
const feature = pack.cells.f[cellId];
|
||||||
|
|
||||||
const routeId = getNextId();
|
const routeId = getNextId();
|
||||||
const newRoute = {i: routeId, group: "trails", feature, points};
|
const newRoute = {i: routeId, group: "trails", feature, points};
|
||||||
routes.push(newRoute);
|
pack.routes.push(newRoute);
|
||||||
|
|
||||||
for (let i = 0; i < pathCells.length; i++) {
|
for (let i = 0; i < pathCells.length; i++) {
|
||||||
const cellId = pathCells[i];
|
const cellId = pathCells[i];
|
||||||
|
|
@ -446,43 +377,6 @@ window.Routes = (function () {
|
||||||
|
|
||||||
return newRoute;
|
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) {
|
function addConnection(from, to, routeId) {
|
||||||
const routes = pack.cells.routes;
|
const routes = pack.cells.routes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -796,14 +796,12 @@ function drawRivers() {
|
||||||
TIME && console.time("drawRivers");
|
TIME && console.time("drawRivers");
|
||||||
rivers.selectAll("*").remove();
|
rivers.selectAll("*").remove();
|
||||||
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
||||||
if (!cells || cells.length < 2) return;
|
if (!cells || cells.length < 2) return;
|
||||||
|
|
||||||
if (points && points.length !== cells.length) {
|
if (points && points.length !== cells.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`River ${i} has ${cells.length} cells, but only ${points.length} points defined.`,
|
`River ${i} has ${cells.length} cells, but only ${points.length} points defined. Resetting points data`
|
||||||
"Resetting points data"
|
|
||||||
);
|
);
|
||||||
points = undefined;
|
points = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,6 @@ function createRiver() {
|
||||||
});
|
});
|
||||||
const id = "river" + riverId;
|
const id = "river" + riverId;
|
||||||
|
|
||||||
// render river
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
viewbox
|
viewbox
|
||||||
.select("#rivers")
|
.select("#rivers")
|
||||||
.append("path")
|
.append("path")
|
||||||
|
|
|
||||||
|
|
@ -164,11 +164,9 @@ function editRiver(id) {
|
||||||
river.points = debug.selectAll("#controlPoints > *").data();
|
river.points = debug.selectAll("#controlPoints > *").data();
|
||||||
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||||
|
|
||||||
const {widthFactor, sourceWidth} = river;
|
|
||||||
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
|
||||||
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||||
|
const path = Rivers.getRiverPath(meanderedPoints, river.widthFactor, river.sourceWidth);
|
||||||
elSelected.attr("d", path);
|
elSelected.attr("d", path);
|
||||||
|
|
||||||
updateRiverLength(river);
|
updateRiverLength(river);
|
||||||
|
|
|
||||||
|
|
@ -793,7 +793,6 @@ function addRiverOnClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// render river
|
// render river
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||||
const id = "river" + riverId;
|
const id = "river" + riverId;
|
||||||
const riversG = viewbox.select("#rivers");
|
const riversG = viewbox.select("#rivers");
|
||||||
|
|
|
||||||
|
|
@ -180,42 +180,36 @@ function connectVertices({vertices, startingVertex, ofSameType, addToChecked, cl
|
||||||
/**
|
/**
|
||||||
* Finds the shortest path between two cells using a cost-based pathfinding algorithm.
|
* Finds the shortest path between two cells using a cost-based pathfinding algorithm.
|
||||||
* @param {number} start - The ID of the starting cell.
|
* @param {number} start - The ID of the starting cell.
|
||||||
* @param {number} exit - The ID of the destination cell.
|
* @param {(id: number) => boolean} isExit - A function that returns true if the cell is the exit cell.
|
||||||
* @param {function} getCellCost - A function that returns the cost of a cell. Should return Infinity for impassable cells.
|
* @param {(current: number, next: number) => number} getCost - A function that returns the path cost from current cell to the next cell. Must return `Infinity` for impassable connections.
|
||||||
* @returns {number[]|null} An array of cell IDs of the path from start to exit, or null if no path is found or start and exit are the same.
|
* @returns {number[] | null} An array of cell IDs of the path from start to exit, or null if no path is found or start and exit are the same.
|
||||||
*/
|
*/
|
||||||
function findPath(start, exit, getCellCost) {
|
function findPath(start, isExit, getCost) {
|
||||||
if (start === exit) return null;
|
if (isExit(start)) return null;
|
||||||
|
|
||||||
const from = [];
|
const from = [];
|
||||||
const cost = [];
|
const cost = [];
|
||||||
const queue = new FlatQueue();
|
const queue = new FlatQueue();
|
||||||
queue.push(start, 0);
|
queue.push(start, 0);
|
||||||
|
|
||||||
let iteration = 0;
|
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const priority = queue.peekValue();
|
const currentCost = queue.peekValue();
|
||||||
const next = queue.pop();
|
const current = queue.pop();
|
||||||
iteration++;
|
|
||||||
console.log(iteration, next);
|
|
||||||
|
|
||||||
for (const neibCellId of pack.cells.c[next]) {
|
for (const next of pack.cells.c[current]) {
|
||||||
if (neibCellId === exit) {
|
if (isExit(next)) {
|
||||||
from[neibCellId] = next;
|
from[next] = current;
|
||||||
return restorePath(start, exit, from);
|
return restorePath(next, start, from);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellCost = getCellCost(neibCellId);
|
const nextCost = getCost(current, next);
|
||||||
if (cellCost === Infinity) continue; // impassable cell
|
if (nextCost === Infinity) continue; // impassable cell
|
||||||
|
const totalCost = currentCost + nextCost;
|
||||||
|
|
||||||
const distanceCost = dist2(pack.cells.p[next], pack.cells.p[neibCellId]);
|
if (totalCost >= cost[next]) continue; // has cheaper path
|
||||||
const totalCost = priority + distanceCost + getCellCost(neibCellId);
|
from[next] = current;
|
||||||
|
cost[next] = totalCost;
|
||||||
if (totalCost >= cost[neibCellId]) continue;
|
queue.push(next, totalCost);
|
||||||
from[neibCellId] = next;
|
|
||||||
cost[neibCellId] = totalCost;
|
|
||||||
queue.push(neibCellId, totalCost);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +217,7 @@ function findPath(start, exit, getCellCost) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// supplementary function for findPath
|
// supplementary function for findPath
|
||||||
function restorePath(start, exit, from) {
|
function restorePath(exit, start, from) {
|
||||||
const pathCells = [];
|
const pathCells = [];
|
||||||
|
|
||||||
let current = exit;
|
let current = exit;
|
||||||
|
|
@ -237,5 +231,5 @@ function restorePath(start, exit, from) {
|
||||||
|
|
||||||
pathCells.push(current);
|
pathCells.push(current);
|
||||||
|
|
||||||
return pathCells;
|
return pathCells.reverse();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue