mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
[Draft] Submap refactoring (#1153)
* refactor: submap - start * refactor: submap - continue * Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into submap-refactoring * refactor: submap - relocate burgs * refactor: submap - restore routes * refactor: submap - restore lake names * refactor: submap - UI update * refactor: submap - restore river and biome data * refactor: submap - simplify options * refactor: submap - restore rivers * refactor: submap - recalculateMapSize * refactor: submap - add middle points * refactor: submap - don't add middle points, unified findPath fn * chore: update version * feat: submap - relocate out of map regiments * feat: submap - fix route gen * feat: submap - allow custom number of cells * feat: submap - add checkbox submapRescaleBurgStyles * feat: submap - update version hash * chore: supporters update --------- Co-authored-by: Azgaar <azgaar.fmg@yandex.com>
This commit is contained in:
parent
23f36c3210
commit
66d22f26c0
18 changed files with 1043 additions and 745 deletions
|
|
@ -1,6 +1,15 @@
|
|||
const ROUTES_SHARP_ANGLE = 135;
|
||||
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 () {
|
||||
function generate(lockedRoutes = []) {
|
||||
const {capitalsByFeature, burgsByFeature, portsByFeature} = sortBurgsByFeature(pack.burgs);
|
||||
|
|
@ -118,10 +127,9 @@ window.Routes = (function () {
|
|||
}
|
||||
|
||||
function findPathSegments({isWater, connections, start, exit}) {
|
||||
const from = findPath(isWater, start, exit, connections);
|
||||
if (!from) return [];
|
||||
|
||||
const pathCells = restorePath(start, exit, from);
|
||||
const getCost = createCostEvaluator({isWater, connections});
|
||||
const pathCells = findPath(start, current => current === exit, getCost);
|
||||
if (!pathCells) return [];
|
||||
const segments = getRouteSegments(pathCells, connections);
|
||||
return segments;
|
||||
}
|
||||
|
|
@ -172,29 +180,61 @@ window.Routes = (function () {
|
|||
|
||||
return routesMerged > 1 ? mergeRoutes(routes) : routes;
|
||||
}
|
||||
}
|
||||
|
||||
function buildLinks(routes) {
|
||||
const links = {};
|
||||
function createCostEvaluator({isWater, connections}) {
|
||||
return isWater ? getWaterPathCost : getLandPathCost;
|
||||
|
||||
for (const {points, i: routeId} of routes) {
|
||||
const cells = points.map(p => p[2]);
|
||||
function getLandPathCost(current, next) {
|
||||
if (pack.cells.h[next] < 20) return Infinity; // ignore water cells
|
||||
|
||||
for (let i = 0; i < cells.length - 1; i++) {
|
||||
const cellId = cells[i];
|
||||
const nextCellId = cells[i + 1];
|
||||
const habitability = biomesData.habitability[pack.cells.biome[next]];
|
||||
if (!habitability) return Infinity; // inhabitable cells are not passable (e.g. glacier)
|
||||
|
||||
if (cellId !== nextCellId) {
|
||||
if (!links[cellId]) links[cellId] = {};
|
||||
links[cellId][nextCellId] = routeId;
|
||||
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;
|
||||
|
||||
if (!links[nextCellId]) links[nextCellId] = {};
|
||||
links[nextCellId][cellId] = routeId;
|
||||
}
|
||||
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) {
|
||||
const links = {};
|
||||
|
||||
for (const {points, i: routeId} of routes) {
|
||||
const cells = points.map(p => p[2]);
|
||||
|
||||
for (let i = 0; i < cells.length - 1; i++) {
|
||||
const cellId = cells[i];
|
||||
const nextCellId = cells[i + 1];
|
||||
|
||||
if (cellId !== nextCellId) {
|
||||
if (!links[cellId]) links[cellId] = {};
|
||||
links[cellId][nextCellId] = routeId;
|
||||
|
||||
if (!links[nextCellId]) links[nextCellId] = {};
|
||||
links[nextCellId][cellId] = routeId;
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
function preparePointsArray() {
|
||||
|
|
@ -249,109 +289,6 @@ window.Routes = (function () {
|
|||
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 cellsCost = distanceCost * habitabilityModifier * heightModifier * connectionModifier * burgModifier;
|
||||
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 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) {
|
||||
const segments = [];
|
||||
let segment = [];
|
||||
|
|
@ -422,21 +359,16 @@ window.Routes = (function () {
|
|||
|
||||
// connect cell with routes system by land
|
||||
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 points = getPoints("trails", pathCells, pointsArray);
|
||||
const feature = cells.f[cellId];
|
||||
|
||||
const feature = pack.cells.f[cellId];
|
||||
const routeId = getNextId();
|
||||
const newRoute = {i: routeId, group: "trails", feature, points};
|
||||
routes.push(newRoute);
|
||||
pack.routes.push(newRoute);
|
||||
|
||||
for (let i = 0; i < pathCells.length; i++) {
|
||||
const cellId = pathCells[i];
|
||||
|
|
@ -446,43 +378,6 @@ window.Routes = (function () {
|
|||
|
||||
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;
|
||||
|
||||
|
|
@ -743,6 +638,7 @@ window.Routes = (function () {
|
|||
|
||||
return {
|
||||
generate,
|
||||
buildLinks,
|
||||
connect,
|
||||
isConnected,
|
||||
areConnected,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue