splittintting
This commit is contained in:
parent
b17be0e0d2
commit
d2e7a80e72
11 changed files with 1341 additions and 1349 deletions
|
|
@ -155,7 +155,7 @@ export function finalizeAdminAwareTransport({ seed, terrain, features, admin, ge
|
|||
...(features.villages || []).filter((p) => (p.population || 0) >= 5000),
|
||||
...(features.ports || []).filter((p) => (p.population || 0) >= 5000 || p.portClass === "major" || p.portClass === "regional" || p.portClass === "fishing"),
|
||||
];
|
||||
const debug = { order: "settlements -> administration -> transport", adminCentersChecked: 0, adminLocalRoadsAdded: 0, nationalTownSpursAdded: 0, nationalTownChainsAdded: 0, nationalTownChainTownsCovered: 0, expresswayEndpointInterchangesAdded: 0, expresswaysSmoothed: 0 };
|
||||
const debug = { order: "settlements -> administration -> transport", adminCentersChecked: 0, adminLocalRoadsAdded: 0, nationalTownSpursAdded: 0, nationalTownChainsAdded: 0, nationalTownChainTownsCovered: 0, expresswayEndpointInterchangesAdded: 0, expresswaysSmoothed: 0, expresswayMajorCityLinksAdded: 0, railCityChainsAdded: 0, railCityChainCitiesCovered: 0 };
|
||||
|
||||
// Local roads after admin: every municipal office cell should lie on a road.
|
||||
const settlementTargets = [...(features.modernCities || []), ...(features.markets || []), ...(features.villages || []), ...(features.ports || [])];
|
||||
|
|
@ -230,23 +230,23 @@ export function finalizeAdminAwareTransport({ seed, terrain, features, admin, ge
|
|||
const parts = [];
|
||||
const before = nearestTrunkOrHub(chain[0], 80);
|
||||
if (before) {
|
||||
const p = directPath(before, chain[0], { maxLength: 90, terrain, maxSeaRun: 10, maxTunnelRun: 0 });
|
||||
const p = directPath(before, chain[0], { maxLength: 90, terrain, maxSeaRun: 10, maxTunnelRun: 10 });
|
||||
if (p.length) parts.push(p);
|
||||
}
|
||||
for (let i = 1; i < chain.length; i++) {
|
||||
const d = Math.hypot(chain[i - 1].x - chain[i].x, chain[i - 1].y - chain[i].y);
|
||||
const p = directPath(chain[i - 1], chain[i], { maxLength: Math.max(28, d * 1.35 + 8), terrain, maxSeaRun: 10, maxTunnelRun: 0 });
|
||||
const p = directPath(chain[i - 1], chain[i], { maxLength: Math.max(28, d * 1.35 + 8), terrain, maxSeaRun: 10, maxTunnelRun: 10 });
|
||||
if (p.length) parts.push(p);
|
||||
}
|
||||
const after = chain.length >= 2 ? nearestTrunkOrHub(chain[chain.length - 1], 80) : null;
|
||||
if (after) {
|
||||
const p = directPath(chain[chain.length - 1], after, { maxLength: 90, terrain, maxSeaRun: 10, maxTunnelRun: 0 });
|
||||
const p = directPath(chain[chain.length - 1], after, { maxLength: 90, terrain, maxSeaRun: 10, maxTunnelRun: 10 });
|
||||
if (p.length) parts.push(p);
|
||||
}
|
||||
let path = concatPaths(parts);
|
||||
if (path.length < 2) {
|
||||
const target = nearestTrunkOrHub(chain[0], 90);
|
||||
path = target ? directPath(chain[0], target, { maxLength: 100, terrain, maxSeaRun: 10, maxTunnelRun: 0 }) : [];
|
||||
path = target ? directPath(chain[0], target, { maxLength: 100, terrain, maxSeaRun: 10, maxTunnelRun: 10 }) : [];
|
||||
}
|
||||
if (path.length >= 2 && chain.some((town) => pathTouchesCell(path, town.x, town.y, 0.65))) {
|
||||
nationalRoads.push(path);
|
||||
|
|
@ -261,6 +261,106 @@ export function finalizeAdminAwareTransport({ seed, terrain, features, admin, ge
|
|||
debug.nationalTownChainTownsCovered = chainDebug.townsCovered;
|
||||
debug.nationalTownSpursAdded = chainDebug.chainsAdded;
|
||||
|
||||
function majorCityKey(city) { return city?.name || `${Math.round(city.x)},${Math.round(city.y)}`; }
|
||||
function expresswayCityComponents(cities, radius = 8.0) {
|
||||
const parent = new Map();
|
||||
function find(k) {
|
||||
const p = parent.get(k);
|
||||
if (p === k) return k;
|
||||
const r = find(p);
|
||||
parent.set(k, r);
|
||||
return r;
|
||||
}
|
||||
function unite(a, b) {
|
||||
const ra = find(a), rb = find(b);
|
||||
if (ra !== rb) parent.set(ra, rb);
|
||||
}
|
||||
for (const city of cities) parent.set(majorCityKey(city), majorCityKey(city));
|
||||
for (const path of [...expressways, ...externalExpressways]) {
|
||||
const near = cities.filter((city) => pathTouchesCell(path, city.x, city.y, radius));
|
||||
if (near.length >= 2) {
|
||||
const first = majorCityKey(near[0]);
|
||||
for (const city of near.slice(1)) unite(first, majorCityKey(city));
|
||||
}
|
||||
}
|
||||
return new Map(cities.map((city) => [majorCityKey(city), find(majorCityKey(city))]));
|
||||
}
|
||||
|
||||
function ensureMajorCityExpresswayLinks(minPopulation = 100000) {
|
||||
const cities = (features.modernCities || [])
|
||||
.filter((city) => (city.population || 0) >= minPopulation && inside(city.x, city.y))
|
||||
.sort((a, b) => (b.population || 0) - (a.population || 0));
|
||||
const result = { minPopulation, checked: cities.length, added: 0 };
|
||||
if (cities.length < 2) return result;
|
||||
for (let iter = 0; iter < cities.length * 2; iter++) {
|
||||
const comps = expresswayCityComponents(cities);
|
||||
if (new Set(comps.values()).size <= 1) break;
|
||||
let best = null;
|
||||
for (const a of cities) {
|
||||
for (const b of cities) {
|
||||
if (a === b || comps.get(majorCityKey(a)) === comps.get(majorCityKey(b))) continue;
|
||||
const d = Math.hypot(a.x - b.x, a.y - b.y);
|
||||
const score = d / Math.max(1, Math.log2(Math.sqrt((a.population || 1) * (b.population || 1))));
|
||||
if (!best || score < best.score) best = { a, b, d, score };
|
||||
}
|
||||
}
|
||||
if (!best) break;
|
||||
let path = directPath(best.a, best.b, { maxLength: best.d * 1.18 + 12, terrain, maxSeaRun: 20 });
|
||||
if (!path.length) path = directPath(best.a, best.b, { maxLength: best.d * 1.38 + 28, terrain });
|
||||
if (!path.length) break;
|
||||
path = smoothPath(path, 2);
|
||||
expressways.push(path);
|
||||
result.added++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cityRailChain(minPopulation = 50000) {
|
||||
const cities = (features.modernCities || [])
|
||||
.filter((city) => (city.population || 0) >= minPopulation && inside(city.x, city.y))
|
||||
.sort((a, b) => a.x - b.x || a.y - b.y);
|
||||
const result = { minPopulation, checked: cities.length, chainsAdded: 0, citiesCovered: 0 };
|
||||
if (cities.length < 2) return result;
|
||||
const existingRail = [...(features.railways || []), ...(features.branchRailways || []), ...(features.externalRailways || [])];
|
||||
const uncovered = cities.filter((city) => !anyPathTouches(existingRail, city, 1.2));
|
||||
if (!uncovered.length) return result;
|
||||
const remaining = uncovered.slice();
|
||||
let chain = [remaining.shift()];
|
||||
while (remaining.length) {
|
||||
const cur = chain[chain.length - 1];
|
||||
let bestIndex = 0;
|
||||
let bestD = Infinity;
|
||||
for (let i = 0; i < remaining.length; i++) {
|
||||
const d = Math.hypot(cur.x - remaining[i].x, cur.y - remaining[i].y);
|
||||
if (d < bestD) { bestD = d; bestIndex = i; }
|
||||
}
|
||||
chain.push(remaining.splice(bestIndex, 1)[0]);
|
||||
}
|
||||
const parts = [];
|
||||
for (let i = 1; i < chain.length; i++) {
|
||||
const a = chain[i - 1], b = chain[i];
|
||||
const d = Math.hypot(a.x - b.x, a.y - b.y);
|
||||
let p = directPath(a, b, { maxLength: d * 1.55 + 20, terrain, maxSeaRun: 10, maxTunnelRun: 10 });
|
||||
if (!p.length) p = directPath(a, b, { maxLength: d * 1.85 + 40, terrain });
|
||||
if (p.length) parts.push(p);
|
||||
}
|
||||
const path = concatPaths(parts);
|
||||
if (path.length >= 2) {
|
||||
features.railways = features.railways || [];
|
||||
features.railways.push(smoothPath(path, 1));
|
||||
result.chainsAdded = 1;
|
||||
result.citiesCovered = chain.filter((city) => pathTouchesCell(path, city.x, city.y, 1.2)).length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const railDebug = cityRailChain(50000);
|
||||
debug.railCityChainsAdded = railDebug.chainsAdded;
|
||||
debug.railCityChainCitiesCovered = railDebug.citiesCovered;
|
||||
|
||||
const expressDebug = ensureMajorCityExpresswayLinks(100000);
|
||||
debug.expresswayMajorCityLinksAdded = expressDebug.added;
|
||||
|
||||
// Expressway finalization after administration: smooth and ensure both endpoints are ICs.
|
||||
for (let i = 0; i < expressways.length; i++) {
|
||||
const smoothed = smoothPath(expressways[i], 2);
|
||||
|
|
@ -282,6 +382,7 @@ export function finalizeAdminAwareTransport({ seed, terrain, features, admin, ge
|
|||
features.externalRoads = dedupePaths(externalRoads, 1);
|
||||
features.expressways = dedupePaths(expressways, 2);
|
||||
features.externalExpressways = dedupePaths(externalExpressways, 2);
|
||||
features.railways = dedupePaths(features.railways || [], 2);
|
||||
features.interchanges = interchanges;
|
||||
|
||||
// Final invariant: after all dedupe passes, every municipal office cell has at least a local road cell on it.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue