before tweaking
This commit is contained in:
parent
da9d8ef904
commit
e48fae5509
8 changed files with 980 additions and 246 deletions
266
mapFeatures.js
266
mapFeatures.js
|
|
@ -440,8 +440,8 @@ export function generateMapFeatures(seed, terrain) {
|
|||
|
||||
function transportAccessPoint(node, mode = "road", salt = 0) {
|
||||
if (!node || nearMapEdge(node.x, node.y, 1) || node.kind === "External Gateway") return node;
|
||||
const minR = mode === "express" ? 10 : mode === "rail" ? 2 : 4;
|
||||
const maxR = mode === "express" ? 20 : mode === "rail" ? 6 : 10;
|
||||
const minR = mode === "express" ? 6 : mode === "rail" ? 2 : 4;
|
||||
const maxR = mode === "express" ? 16 : mode === "rail" ? 6 : 10;
|
||||
let best = null;
|
||||
let bestScore = -INF;
|
||||
for (let dy = -maxR; dy <= maxR; dy++) {
|
||||
|
|
@ -460,7 +460,7 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const ring = -Math.abs(d - targetD) * 0.08;
|
||||
const riverPenalty = river[i] > 0.5 ? 0.45 : river[i] * 0.12;
|
||||
const density = densityValue(x, y);
|
||||
const densityAffinity = mode === "rail" ? density * 0.9 : mode === "express" ? midDensityAffinity(x, y) * 0.52 - Math.max(0, density - 0.72) * 0.9 : density * 0.24;
|
||||
const densityAffinity = mode === "rail" ? density * 0.9 : mode === "express" ? density * 0.70 + midDensityAffinity(x, y) * 0.18 - Math.max(0, density - 0.92) * 0.35 : density * 0.24;
|
||||
const noise = hash2(x, y, seed + salt + (mode === "rail" ? 6000 : mode === "express" ? 7000 : 5000)) * 0.12;
|
||||
const score = flatness + densityAffinity + ring - riverPenalty - barrier * 0.012 + noise;
|
||||
if (score > bestScore) {
|
||||
|
|
@ -601,10 +601,12 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const density = densityValue(x, y);
|
||||
const midDensity = midDensityAffinity(x, y);
|
||||
const cityDistance = distanceToNearest(modernCities, x, y);
|
||||
const cityAvoid = cityDistance < 5 ? 22.0 : cityDistance < 9 ? 11.0 : cityDistance < 13 ? 4.0 : distanceToNearest(markets, x, y) < 4 ? 3.2 : 0;
|
||||
const densityPenalty = density > 0.66 ? (density - 0.66) * 9.5 : density < 0.08 ? (0.08 - density) * 2.4 : 0;
|
||||
const highPenalty = barrier + (elevation[i] > 0.72 ? 26 : elevation[i] > 0.62 ? 8.5 : 0);
|
||||
return Math.max(0.42, 1 + slope[i] * 23.0 + highPenalty + cityAvoid + densityPenalty + (river[i] > 0.45 ? 1.0 : 0) - midDensity * 0.82 - plain[i] * 0.16 - valleyField[i] * 0.16 - coastalLowland[i] * 0.18 + ridgeField[i] * 1.20 + normalEdgePenalty(x, y) + hash2(x, y, seed + 444) * 0.015);
|
||||
const coreAvoid = cityDistance < 2.2 ? 18.0 : cityDistance < 4.5 ? 7.0 : cityDistance < 7.5 ? 2.0 : 0;
|
||||
const marketAvoid = distanceToNearest(markets, x, y) < 2.5 ? 1.8 : 0;
|
||||
const lowDensityPenalty = density < 0.10 ? (0.10 - density) * 4.8 : 0;
|
||||
const urbanCorridorBonus = density * 1.18 + midDensity * 0.28 + (cityDistance >= 4 && cityDistance <= 16 ? 0.40 : 0);
|
||||
const constructionCost = 0.58 + slope[i] * 28.0 + barrier * 1.10 + Math.max(0, elevation[i] - 0.60) * 16.0 + ridgeField[i] * 1.45 + (river[i] > 0.45 ? 1.15 : river[i] * 0.45);
|
||||
return Math.max(0.50, 1.18 + constructionCost + coreAvoid + marketAvoid + lowDensityPenalty - urbanCorridorBonus - plain[i] * 0.12 - valleyField[i] * 0.12 - coastalLowland[i] * 0.12 + normalEdgePenalty(x, y) + hash2(x, y, seed + 444) * 0.015);
|
||||
}
|
||||
|
||||
const nationalRoads = [];
|
||||
|
|
@ -648,8 +650,8 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const path = aStar(start, goal, makeTransportCost(roadCost, existing, roadHubs, [start, goal], 3, 5.8, townAvoidNodes, 3.2, 5.4));
|
||||
const direct = pathEndpointDistance(path);
|
||||
const urbanPasses = modernCities.filter((city) => path.some(([x, y]) => Math.hypot(x - city.x, y - city.y) <= Math.max(6, Math.min(13, (city.urbanRadius || 8) * 0.78)))).length;
|
||||
const passBonusOk = urbanPasses >= 2 || direct >= 24;
|
||||
if (path.length > 3 && direct >= 16 && pathLength(path) >= 20 && pathCompactness(path) < 3.35 && pathOverlapRatio(path, existing, 2) < 0.48 && passBonusOk) {
|
||||
const passBonusOk = urbanPasses >= 1 || direct >= 18;
|
||||
if (path.length > 3 && direct >= 12 && pathLength(path) >= 14 && pathCompactness(path) < 4.20 && pathOverlapRatio(path, existing, 2) < 0.78 && passBonusOk) {
|
||||
nationalRoads.push(path);
|
||||
incrementDegree(roadDegree, a);
|
||||
incrementDegree(roadDegree, b);
|
||||
|
|
@ -706,12 +708,37 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const expressDegree = new Map();
|
||||
const expressCore = [capital];
|
||||
|
||||
function snapPathToExistingExpressways(path, existingPaths, radius = 2.4) {
|
||||
if (!path?.length || !existingPaths?.length) return path || [];
|
||||
const snapped = [];
|
||||
const skipEnd = Math.min(5, Math.floor(path.length / 5));
|
||||
for (let pi = 0; pi < path.length; pi++) {
|
||||
const [x, y] = path[pi];
|
||||
let best = null;
|
||||
let bestD = radius;
|
||||
if (pi >= skipEnd && pi < path.length - skipEnd) {
|
||||
for (const existing of existingPaths) {
|
||||
for (const [ex, ey] of existing) {
|
||||
const d = Math.hypot(x - ex, y - ey);
|
||||
if (d < bestD) { bestD = d; best = [ex, ey]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
const next = best || [x, y];
|
||||
const last = snapped[snapped.length - 1];
|
||||
if (!last || last[0] !== next[0] || last[1] !== next[1]) snapped.push(next);
|
||||
}
|
||||
return snapped;
|
||||
}
|
||||
|
||||
|
||||
function addExpressway(a, b, bucket = expressways) {
|
||||
const start = routePoint(a, "express", a.x * 41 + a.y * 43);
|
||||
const goal = routePoint(b, "express", b.x * 41 + b.y * 43 + 29);
|
||||
const existing = [...expressways, ...nationalRoads, ...railways, ...branchRailways];
|
||||
let path = aStar(start, goal, makeTransportCost(expresswayCost, existing, roadHubs, [start, goal], 5, 10.8, townAvoidNodes, 8.5, 14.0));
|
||||
path = smoothPathByLineOfSight(path, (x, y) => expresswayCost(x, y, x, y) < INF && slope[indexOf(x, y)] < 0.54 && elevation[indexOf(x, y)] < 0.78, 10);
|
||||
path = snapPathToExistingExpressways(path, expressways, 2.6);
|
||||
const direct = pathEndpointDistance(path);
|
||||
if (path.length > 8 && direct >= 26 && pathLength(path) >= 30 && pathCompactness(path) < 2.35 && pathOverlapRatio(path, existing, 2) < 0.30) {
|
||||
bucket.push(path);
|
||||
|
|
@ -888,6 +915,16 @@ export function generateMapFeatures(seed, terrain) {
|
|||
threshold: 0.4,
|
||||
seed: seed + 1201,
|
||||
}).map((p) => ({ ...p, kind: "External Gateway" }));
|
||||
if (externalGateways.length < 2) {
|
||||
const fallbackGateways = gatewayCandidates
|
||||
.slice()
|
||||
.sort((a, b) => b.score - a.score);
|
||||
for (const gate of fallbackGateways) {
|
||||
if (externalGateways.some((p) => Math.hypot(p.x - gate.x, p.y - gate.y) < 30)) continue;
|
||||
externalGateways.push({ ...gate, kind: "External Gateway" });
|
||||
if (externalGateways.length >= 2) break;
|
||||
}
|
||||
}
|
||||
|
||||
function externalRoadCost(goal) {
|
||||
return (x, y) => {
|
||||
|
|
@ -910,9 +947,11 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const borderPenalty = nearMapEdge(x, y, 1) && !(Math.abs(x - goal.x) <= 2 && Math.abs(y - goal.y) <= 2) ? 7 : nearMapEdge(x, y, 3) ? 1.5 : 0;
|
||||
const density = densityValue(x, y);
|
||||
const cityDistance = distanceToNearest(modernCities, x, y);
|
||||
const cityAvoid = cityDistance < 5 ? 22.0 : cityDistance < 9 ? 11.0 : cityDistance < 13 ? 4.0 : distanceToNearest(markets, x, y) < 4 ? 3.2 : 0;
|
||||
const densityPenalty = density > 0.66 ? (density - 0.66) * 9.5 : density < 0.08 ? (0.08 - density) * 2.4 : 0;
|
||||
return Math.max(0.42, 1 + slope[i] * 19 + barrier + cityAvoid + densityPenalty + (river[i] > 0.45 ? 1 : 0) + floodplain[i] * 0.2 - midDensityAffinity(x, y) * 0.7 - plain[i] * 0.12 + borderPenalty + hash2(x, y, seed + 444) * 0.05);
|
||||
const coreAvoid = cityDistance < 2.2 ? 16.0 : cityDistance < 4.5 ? 6.0 : cityDistance < 7.5 ? 1.8 : 0;
|
||||
const lowDensityPenalty = density < 0.10 ? (0.10 - density) * 4.2 : 0;
|
||||
const urbanCorridorBonus = density * 1.04 + midDensityAffinity(x, y) * 0.24 + (cityDistance >= 4 && cityDistance <= 16 ? 0.32 : 0);
|
||||
const constructionCost = 0.55 + slope[i] * 23.0 + barrier * 1.06 + Math.max(0, elevation[i] - 0.60) * 12.0 + ridgeField[i] * 1.20 + (river[i] > 0.45 ? 1 : river[i] * 0.38);
|
||||
return Math.max(0.50, 1.14 + constructionCost + coreAvoid + lowDensityPenalty + floodplain[i] * 0.18 + borderPenalty - urbanCorridorBonus - plain[i] * 0.10 + hash2(x, y, seed + 444) * 0.04);
|
||||
};
|
||||
}
|
||||
function externalRailCost(goal) {
|
||||
|
|
@ -945,7 +984,8 @@ export function generateMapFeatures(seed, terrain) {
|
|||
const roadStart = routePoint(roadStartRaw, makeExpressLink ? "express" : "road", gate.x * 53 + gate.y * 59);
|
||||
const roadExisting = [...nationalRoads, ...expressways, ...externalRoads, ...externalExpressways, ...railways, ...branchRailways];
|
||||
const roadBaseCost = makeExpressLink ? externalExpresswayCost(gate) : externalRoadCost(gate);
|
||||
const roadPath = aStar(roadStart, gate, makeTransportCost(roadBaseCost, roadExisting, roadHubs, [roadStart, gate], makeExpressLink ? 4 : 3, makeExpressLink ? 8.2 : 6.2, townAvoidNodes, makeExpressLink ? 5.4 : 3.2, makeExpressLink ? 7.8 : 5.6));
|
||||
let roadPath = aStar(roadStart, gate, makeTransportCost(roadBaseCost, roadExisting, roadHubs, [roadStart, gate], makeExpressLink ? 4 : 3, makeExpressLink ? 8.2 : 6.2, townAvoidNodes, makeExpressLink ? 5.4 : 3.2, makeExpressLink ? 7.8 : 5.6));
|
||||
if (makeExpressLink) roadPath = snapPathToExistingExpressways(roadPath, [...expressways, ...externalExpressways], 2.6);
|
||||
if (roadPath.length > 6) {
|
||||
if (makeExpressLink) {
|
||||
externalExpressways.push(roadPath);
|
||||
|
|
@ -971,68 +1011,113 @@ export function generateMapFeatures(seed, terrain) {
|
|||
}
|
||||
});
|
||||
|
||||
const requiredTransportNodes = [];
|
||||
function addRequiredTransportNode(node, reason) {
|
||||
if (!node || !inside(node.x, node.y) || sea[indexOf(node.x, node.y)]) return;
|
||||
const key = `${node.x},${node.y}`;
|
||||
if (requiredTransportNodes.some((p) => `${p.x},${p.y}` === key)) return;
|
||||
requiredTransportNodes.push({ ...node, requiredTransportReason: reason });
|
||||
let throughExpresswayAdded = false;
|
||||
function throughExpresswayCost(a, b) {
|
||||
return (x, y) => {
|
||||
const i = indexOf(x, y);
|
||||
if (sea[i]) return INF;
|
||||
const barrier = mountainBarrierPenalty(x, y, "express");
|
||||
if (barrier >= INF) return INF;
|
||||
const nearEndpoint = Math.min(Math.hypot(x - a.x, y - a.y), Math.hypot(x - b.x, y - b.y)) <= 3;
|
||||
const borderPenalty = nearEndpoint ? 0 : nearMapEdge(x, y, 3) ? 2.2 : 0;
|
||||
const density = densityValue(x, y);
|
||||
const cityDistance = distanceToNearest(modernCities, x, y);
|
||||
const coreAvoid = cityDistance < 2.0 ? 12.0 : cityDistance < 4.5 ? 4.8 : cityDistance < 7.5 ? 1.4 : 0;
|
||||
const lowDensityPenalty = density < 0.08 ? (0.08 - density) * 4.0 : 0;
|
||||
const urbanCorridorBonus = density * 1.00 + midDensityAffinity(x, y) * 0.22 + (cityDistance >= 4 && cityDistance <= 18 ? 0.34 : 0);
|
||||
return Math.max(0.48, 1.16 + slope[i] * 21.0 + barrier * 1.08 + Math.max(0, elevation[i] - 0.62) * 13.0 + coreAvoid + lowDensityPenalty + (river[i] > 0.45 ? 1.0 : 0) + borderPenalty - urbanCorridorBonus - plain[i] * 0.14 - coastalLowland[i] * 0.12 + hash2(x, y, seed + 9101) * 0.03);
|
||||
};
|
||||
}
|
||||
addRequiredTransportNode(capital, "capital");
|
||||
for (const gate of externalGateways) addRequiredTransportNode(gate, "externalGateway");
|
||||
for (const city of modernCities) if ((city.population || 0) >= 120000 || city.isPrefecturalCapital) addRequiredTransportNode(city, "majorCity");
|
||||
for (const port of majorPorts) addRequiredTransportNode(port, "majorPort");
|
||||
|
||||
const backboneAccess = new Map();
|
||||
function nodeKey(p) {
|
||||
return `${p.x},${p.y}`;
|
||||
function permissiveThroughExpresswayCost(a, b) {
|
||||
return (x, y) => {
|
||||
const i = indexOf(x, y);
|
||||
if (sea[i]) return 24 + nearMapEdge(x, y, 2) * 2 + hash2(x, y, seed + 9202) * 0.2;
|
||||
const rawBarrier = mountainBarrierPenalty(x, y, "express");
|
||||
const tunnelBarrier = rawBarrier >= INF ? 120 + Math.max(0, elevation[i] - 0.66) * 260 + slope[i] * 55 : rawBarrier;
|
||||
const nearEndpoint = Math.min(Math.hypot(x - a.x, y - a.y), Math.hypot(x - b.x, y - b.y)) <= 3;
|
||||
const borderPenalty = nearEndpoint ? 0 : nearMapEdge(x, y, 3) ? 2.0 : 0;
|
||||
const cityDistance = distanceToNearest(modernCities, x, y);
|
||||
const coreAvoid = cityDistance < 2.0 ? 9.0 : cityDistance < 4.5 ? 3.5 : 0;
|
||||
const density = densityValue(x, y);
|
||||
const urbanCorridorBonus = density * 0.85 + midDensityAffinity(x, y) * 0.18 + (cityDistance >= 4 && cityDistance <= 18 ? 0.24 : 0);
|
||||
return Math.max(0.52, 1.18 + slope[i] * 14.0 + tunnelBarrier * 0.42 + Math.max(0, elevation[i] - 0.66) * 18.0 + coreAvoid + borderPenalty - urbanCorridorBonus - plain[i] * 0.10 + hash2(x, y, seed + 9201) * 0.035);
|
||||
};
|
||||
}
|
||||
function backbonePoint(node) {
|
||||
const key = nodeKey(node);
|
||||
if (!backboneAccess.has(key)) {
|
||||
const mode = node.requiredTransportReason === "externalGateway" ? "road" : "road";
|
||||
backboneAccess.set(key, routePoint(node, mode, 18000 + node.x * 97 + node.y * 101));
|
||||
|
||||
function pointToSegmentDistance(p, a, b) {
|
||||
const vx = b.x - a.x;
|
||||
const vy = b.y - a.y;
|
||||
const len2 = vx * vx + vy * vy;
|
||||
if (len2 <= 0.0001) return Math.hypot(p.x - a.x, p.y - a.y);
|
||||
const t = clamp(((p.x - a.x) * vx + (p.y - a.y) * vy) / len2, 0, 1);
|
||||
return Math.hypot(p.x - (a.x + vx * t), p.y - (a.y + vy * t));
|
||||
}
|
||||
|
||||
function chooseThroughExpresswayVia(a, b) {
|
||||
const candidates = [capital, ...modernCities.filter((city) => (city.population || 0) >= 90000)];
|
||||
let best = null;
|
||||
let bestScore = -INF;
|
||||
for (const city of candidates) {
|
||||
if (!city || !prefectureMask[indexOf(city.x, city.y)] || sea[indexOf(city.x, city.y)]) continue;
|
||||
const access = routePoint(city, "express", city.x * 73 + city.y * 79 + 9301);
|
||||
const lineD = pointToSegmentDistance(access, a, b);
|
||||
const density = densityValue(access.x, access.y);
|
||||
const popScore = Math.sqrt(Math.max(0, city.population || 0)) / 520;
|
||||
const score = density * 2.8 + popScore + (city.isPrefecturalCapital ? 0.9 : 0) - lineD / 38 - mountainBarrierPenalty(access.x, access.y, "express") * 0.004;
|
||||
if (score > bestScore) { bestScore = score; best = access; }
|
||||
}
|
||||
return backboneAccess.get(key);
|
||||
return best;
|
||||
}
|
||||
function addBackboneRoad(a, b) {
|
||||
const start = backbonePoint(a);
|
||||
const goal = backbonePoint(b);
|
||||
const existing = [...nationalRoads, ...externalRoads, ...externalExpressways, ...railways, ...branchRailways];
|
||||
const path = aStar(start, goal, makeTransportCost(roadCost, existing, roadHubs, [start, goal], 2, 4.2, townAvoidNodes, 2.6, 4.2));
|
||||
if (path.length <= 3 || pathCompactness(path) > 3.8 || path.some(([x, y]) => elevation[indexOf(x, y)] > 0.72)) return false;
|
||||
nationalRoads.push(path);
|
||||
incrementDegree(roadDegree, a);
|
||||
incrementDegree(roadDegree, b);
|
||||
return true;
|
||||
}
|
||||
const connectedBackboneNodes = requiredTransportNodes.length ? [requiredTransportNodes[0]] : [];
|
||||
const pendingBackboneNodes = requiredTransportNodes.slice(1);
|
||||
let backboneEdgeCount = 0;
|
||||
while (pendingBackboneNodes.length && connectedBackboneNodes.length) {
|
||||
let bestIndex = -1;
|
||||
let bestAnchor = null;
|
||||
let bestScore = INF;
|
||||
for (let i = 0; i < pendingBackboneNodes.length; i++) {
|
||||
const node = pendingBackboneNodes[i];
|
||||
for (const anchor of connectedBackboneNodes) {
|
||||
const d = Math.hypot(node.x - anchor.x, node.y - anchor.y);
|
||||
const ai = indexOf(anchor.x, anchor.y);
|
||||
const bi = indexOf(node.x, node.y);
|
||||
const corridor = sameCorridorAffinity(anchor, node);
|
||||
const score = d * (1.0 - corridor * 0.22) + Math.max(elevation[ai], elevation[bi]) * 8 - Math.max(passSuitability[ai], passSuitability[bi]) * 4;
|
||||
if (score < bestScore) {
|
||||
|
||||
function addThroughExpressway() {
|
||||
if (externalGateways.length < 2) return false;
|
||||
let bestPair = null;
|
||||
let bestScore = -INF;
|
||||
for (let i = 0; i < externalGateways.length; i++) {
|
||||
for (let j = i + 1; j < externalGateways.length; j++) {
|
||||
const a = externalGateways[i];
|
||||
const b = externalGateways[j];
|
||||
const d = Math.hypot(a.x - b.x, a.y - b.y);
|
||||
const opposite = (a.side === "N" && b.side === "S") || (a.side === "S" && b.side === "N") || (a.side === "W" && b.side === "E") || (a.side === "E" && b.side === "W");
|
||||
const score = d + (opposite ? 42 : 0) - Math.abs((a.score || 0) - (b.score || 0)) * 3;
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestIndex = i;
|
||||
bestAnchor = anchor;
|
||||
bestPair = [a, b];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestIndex < 0 || !bestAnchor) break;
|
||||
const node = pendingBackboneNodes.splice(bestIndex, 1)[0];
|
||||
if (addBackboneRoad(bestAnchor, node)) backboneEdgeCount++;
|
||||
connectedBackboneNodes.push(node);
|
||||
if (!bestPair) return false;
|
||||
const [a, b] = bestPair;
|
||||
const existing = [...externalExpressways, ...expressways, ...nationalRoads, ...railways, ...branchRailways];
|
||||
const via = chooseThroughExpresswayVia(a, b);
|
||||
let path = [];
|
||||
let viaUsed = false;
|
||||
if (via) {
|
||||
let first = aStar(a, via, makeTransportCost(throughExpresswayCost(a, via), existing, roadHubs, [a, via], 5, 9.6, townAvoidNodes, 4.2, 6.0));
|
||||
first = smoothPathByLineOfSight(first, (x, y) => throughExpresswayCost(a, via)(x, y, x, y) < INF && elevation[indexOf(x, y)] < 0.88, 11);
|
||||
let second = aStar(via, b, makeTransportCost(throughExpresswayCost(via, b), [...existing, first], roadHubs, [via, b], 5, 9.6, townAvoidNodes, 4.2, 6.0));
|
||||
second = smoothPathByLineOfSight(second, (x, y) => throughExpresswayCost(via, b)(x, y, x, y) < INF && elevation[indexOf(x, y)] < 0.88, 11);
|
||||
if (first.length > 6 && second.length > 6) { path = [...first, ...second.slice(1)]; viaUsed = true; }
|
||||
}
|
||||
if (path.length < 12) {
|
||||
path = aStar(a, b, makeTransportCost(throughExpresswayCost(a, b), existing, roadHubs, [a, b], 5, 9.4, townAvoidNodes, 6.0, 9.0));
|
||||
path = smoothPathByLineOfSight(path, (x, y) => throughExpresswayCost(a, b)(x, y, x, y) < INF && elevation[indexOf(x, y)] < 0.88, 11);
|
||||
}
|
||||
path = snapPathToExistingExpressways(path, [...externalExpressways, ...expressways], 2.8);
|
||||
if (!viaUsed && (path.length < 12 || pathEndpointDistance(path) < Math.min(MAP_W, MAP_H) * 0.45 || pathCompactness(path) > 3.25)) {
|
||||
path = aStar(a, b, makeTransportCost(permissiveThroughExpresswayCost(a, b), existing, roadHubs, [a, b], 4, 7.2, townAvoidNodes, 4.0, 6.5));
|
||||
path = smoothPathByLineOfSight(path, (x, y) => permissiveThroughExpresswayCost(a, b)(x, y, x, y) < INF, 12);
|
||||
path = snapPathToExistingExpressways(path, [...externalExpressways, ...expressways], 2.8);
|
||||
}
|
||||
if (path.length < 12 || pathEndpointDistance(path) < Math.min(MAP_W, MAP_H) * 0.42 || pathCompactness(path) > (viaUsed ? 5.6 : 3.45)) return false;
|
||||
externalExpressways.push(path);
|
||||
incrementDegree(expressDegree, a);
|
||||
incrementDegree(expressDegree, b);
|
||||
throughExpresswayAdded = true;
|
||||
return true;
|
||||
}
|
||||
addThroughExpressway();
|
||||
|
||||
function nearestPathCellDistance(node, paths) {
|
||||
let best = INF;
|
||||
|
|
@ -1041,24 +1126,13 @@ export function generateMapFeatures(seed, terrain) {
|
|||
}
|
||||
return best;
|
||||
}
|
||||
const combinedModernBackbone = [...nationalRoads, ...externalRoads, ...externalExpressways, ...railways, ...branchRailways, ...externalRailways, ...expressways];
|
||||
const connectedRequiredNodeCount = requiredTransportNodes.filter((node) => nearestPathCellDistance(node, combinedModernBackbone) <= 7).length;
|
||||
const missingRequiredNodes = requiredTransportNodes
|
||||
.filter((node) => nearestPathCellDistance(node, combinedModernBackbone) > 7)
|
||||
.map((node) => ({ x: node.x, y: node.y, kind: node.kind, reason: node.requiredTransportReason }));
|
||||
const transportDebug = {
|
||||
requiredNodeCount: requiredTransportNodes.length,
|
||||
connectedRequiredNodeCount,
|
||||
missingRequiredNodes,
|
||||
backboneEdgeCount,
|
||||
};
|
||||
|
||||
function pruneHighMountainTransport(paths, threshold = 0.82) {
|
||||
for (let i = paths.length - 1; i >= 0; i--) {
|
||||
if (paths[i].some(([x, y]) => elevation[indexOf(x, y)] > threshold)) paths.splice(i, 1);
|
||||
}
|
||||
}
|
||||
for (const paths of [railways, branchRailways, ringRailways, externalRailways, expressways, externalExpressways]) pruneHighMountainTransport(paths, 0.82);
|
||||
for (const paths of [railways, branchRailways, ringRailways, externalRailways, expressways]) pruneHighMountainTransport(paths, 0.82);
|
||||
|
||||
const expressInfluence = influenceFromPaths([...expressways, ...externalExpressways], 6);
|
||||
const roadInfluence = influenceFromPaths([...nationalRoads, ...ringRoads, ...expressways, ...externalRoads, ...externalExpressways], 4);
|
||||
|
|
@ -1197,19 +1271,51 @@ export function generateMapFeatures(seed, terrain) {
|
|||
}
|
||||
|
||||
for (const village of villages) {
|
||||
if (rand(seed, village.x * 13 + village.y * 17) < 0.42) {
|
||||
if (rand(seed, village.x * 13 + village.y * 17) < 0.90) {
|
||||
const target = pickEntities(trunkNodes.map((p) => ({ ...p, score: 1 / (1 + Math.hypot(p.x - village.x, p.y - village.y)) })), { max: 1, minDistance: 1, threshold: 0 })[0];
|
||||
if (target && Math.hypot(target.x - village.x, target.y - village.y) < 28) addMinorRoad(village, target);
|
||||
if (target && Math.hypot(target.x - village.x, target.y - village.y) < 34) addMinorRoad(village, target);
|
||||
}
|
||||
}
|
||||
for (const market of markets) {
|
||||
const localVillages = pickEntities(villages.map((v) => ({ ...v, score: 1 / (1 + Math.hypot(v.x - market.x, v.y - market.y)) })), { max: 3, minDistance: 1, threshold: 0 });
|
||||
const localVillages = pickEntities(villages.map((v) => ({ ...v, score: 1 / (1 + Math.hypot(v.x - market.x, v.y - market.y)) })), { max: 4, minDistance: 1, threshold: 0 });
|
||||
for (const v of localVillages) addMinorRoad(market, v);
|
||||
}
|
||||
for (const pass of passes.slice(0, 8)) {
|
||||
const target = pickEntities([...markets, ...villages].map((p) => ({ ...p, score: 1 / (1 + Math.hypot(p.x - pass.x, p.y - pass.y)) })), { max: 1, minDistance: 1, threshold: 0 })[0];
|
||||
if (target) addMinorRoad(pass, target);
|
||||
}
|
||||
for (const port of ports) {
|
||||
const target = pickEntities([...markets, ...villages, ...stations.slice(0, 18)].map((p) => ({ ...p, score: 1 / (1 + Math.hypot(p.x - port.x, p.y - port.y)) })), { max: 1, minDistance: 1, threshold: 0 })[0];
|
||||
if (target && Math.hypot(target.x - port.x, target.y - port.y) < 24) addMinorRoad(port, target);
|
||||
}
|
||||
for (const localCenter of [...satelliteCities, ...newTowns]) {
|
||||
const target = pickEntities([...stations, ...markets, ...modernCities].map((p) => ({ ...p, score: 1 / (1 + Math.hypot(p.x - localCenter.x, p.y - localCenter.y)) })), { max: 1, minDistance: 1, threshold: 0 })[0];
|
||||
if (target && Math.hypot(target.x - localCenter.x, target.y - localCenter.y) < 30) addMinorRoad(localCenter, target);
|
||||
}
|
||||
for (const station of stations.slice(0, 28)) {
|
||||
const locals = pickEntities([...villages, ...markets, ...ports].map((p) => ({ ...p, score: 1 / (1 + Math.hypot(p.x - station.x, p.y - station.y)) })), { max: 2, minDistance: 1, threshold: 0 });
|
||||
for (const local of locals) if (Math.hypot(local.x - station.x, local.y - station.y) < 22) addMinorRoad(station, local);
|
||||
}
|
||||
for (const village of villages.slice(0, 42)) {
|
||||
const neighbor = pickEntities(villages.filter((v) => v !== village).map((v) => ({ ...v, score: 1 / (1 + Math.hypot(v.x - village.x, v.y - village.y)) })), { max: 1, minDistance: 1, threshold: 0 })[0];
|
||||
if (neighbor && Math.hypot(neighbor.x - village.x, neighbor.y - village.y) < 14) addMinorRoad(village, neighbor);
|
||||
}
|
||||
|
||||
const combinedModernTransport = [...nationalRoads, ...externalRoads, ...externalExpressways, ...railways, ...branchRailways, ...externalRailways, ...expressways];
|
||||
const requiredTransportNodes = [capital, ...externalGateways, ...modernCities.filter((city) => (city.population || 0) >= 120000 || city.isPrefecturalCapital)];
|
||||
const connectedRequiredNodeCount = requiredTransportNodes.filter((node) => nearestPathCellDistance(node, combinedModernTransport) <= 7).length;
|
||||
const allExpresswayPaths = [...expressways, ...externalExpressways];
|
||||
const expresswayCells = allExpresswayPaths.flat();
|
||||
const expresswayAverageDensity = expresswayCells.length ? expresswayCells.reduce((sum, [x, y]) => sum + densityValue(x, y), 0) / expresswayCells.length : 0;
|
||||
const transportDebug = {
|
||||
requiredNodeCount: requiredTransportNodes.length,
|
||||
connectedRequiredNodeCount,
|
||||
throughExpresswayAdded,
|
||||
expresswayAverageDensity: Number(expresswayAverageDensity.toFixed(3)),
|
||||
expresswayPathCount: allExpresswayPaths.length,
|
||||
minorRoadCount: minorRoads.length,
|
||||
minorRoadTotalLength: Math.round(minorRoads.reduce((sum, path) => sum + pathLength(path), 0)),
|
||||
};
|
||||
|
||||
const newTownInfluence = influenceFromPoints(newTowns, 8, () => 1);
|
||||
const landuse = new Uint8Array(SIZE);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue