map/mapFeatureTransportTools.js
2026-05-28 02:47:33 +09:00

191 lines
7.7 KiB
JavaScript

import { INF, MAP_H, MAP_W, SIZE, clamp, hash2, indexOf, inside } from "./mapUtils.js";
export function buildFeatureTransportCostFields(ctx) {
const {
seed,
sea, elevation, slope, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland,
portSuitability, passSuitability, crossingSuitability, naturalBarrierScore,
settlementDemand, urbanEdge, logisticsPreSuitability,
preliminaryTownInfluence, preliminaryVillageInfluence,
valleySettlement, coastalSettlement, developable,
} = ctx;
const expressway = new Float32Array(SIZE);
const rail = new Float32Array(SIZE);
const national = new Float32Array(SIZE);
const local = new Float32Array(SIZE);
const expresswayPotential = new Float32Array(SIZE);
const railPotential = new Float32Array(SIZE);
const nationalPotential = new Float32Array(SIZE);
const localPotential = new Float32Array(SIZE);
function seaAdjacency(x, y, radius = 1) {
let sum = 0;
let total = 0;
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
if (!dx && !dy) continue;
const nx = x + dx;
const ny = y + dy;
if (!inside(nx, ny)) continue;
total++;
if (sea[indexOf(nx, ny)]) sum += 1;
}
}
return total > 0 ? sum / total : 0;
}
function highAltitudeTransportClosed(i) {
// Above this contour the generator should treat mountains as no-road
// terrain. A strong mapped pass is the exception, so genuine saddle
// crossings can still exist without roads drilling through entire ranges.
return elevation[i] >= 0.70;
}
for (let y = 0; y < MAP_H; y++) {
for (let x = 0; x < MAP_W; x++) {
const i = indexOf(x, y);
if (sea[i] || highAltitudeTransportClosed(i)) {
expressway[i] = rail[i] = national[i] = local[i] = INF;
expresswayPotential[i] = railPotential[i] = nationalPotential[i] = localPotential[i] = 0;
continue;
}
const density = settlementDemand[i];
const mediumDensity = clamp(1 - Math.abs(density - 0.42) / 0.30);
const highDensity = clamp((density - 0.32) / 0.50);
const lowland = clamp(plain[i] * 0.48 + basinField[i] * 0.28 + valleyField[i] * 0.24 + coastalLowland[i] * 0.26 + agriculture[i] * 0.16);
const pass = passSuitability?.[i] || 0;
const crossing = crossingSuitability?.[i] || 0;
const waterCrossingPenalty = river[i] > 0.18 ? (1 - crossing) * (0.72 + river[i] * 1.35) : 0;
const seaNear = seaAdjacency(x, y, 1);
const seaBroad = seaAdjacency(x, y, 3);
const seaWide = seaAdjacency(x, y, 5);
// Roads should use coastal lowlands when there is a settlement/port reason,
// but should not casually trace beaches or hop over small bays.
const coastalTraversePenalty = clamp(seaBroad * 1.72 + seaWide * 0.82 - coastalLowland[i] * 0.48 - (portSuitability?.[i] || 0) * 0.30);
const highMountain = clamp((elevation[i] - 0.52) * 3.6 + slope[i] * 0.95 + ridgeField[i] * 1.05 - pass * 0.55 - valleyField[i] * 0.12);
const extremeMountain = clamp((elevation[i] - 0.64) * 4.8 + slope[i] * 1.55 + ridgeField[i] * 1.45 - pass * 0.80);
const denseCorePenalty = clamp((density - 0.66) / 0.28);
const openPlainParallelPenalty = clamp(plain[i] * 0.48 + agriculture[i] * 0.26 - density * 0.20 - valleyField[i] * 0.20 - coastalLowland[i] * 0.12);
const boundaryRidgePenalty = Math.pow(naturalBarrierScore?.[i] || 0, 2);
if (extremeMountain > 0.92 && pass < 0.34) {
expressway[i] = rail[i] = INF;
national[i] = elevation[i] >= 0.68 ? INF : 2.9 + extremeMountain * 2.8 + waterCrossingPenalty;
local[i] = elevation[i] >= 0.69 ? INF : 2.2 + extremeMountain * 2.3 + waterCrossingPenalty * 0.55;
expresswayPotential[i] = 0;
railPotential[i] = 0;
nationalPotential[i] = clamp(lowland * 0.18 + pass * 0.24 - extremeMountain * 0.55);
localPotential[i] = clamp(valleySettlement[i] * 0.14 + pass * 0.18 - extremeMountain * 0.28);
continue;
}
expresswayPotential[i] = clamp(
mediumDensity * 0.62 +
urbanEdge[i] * 0.38 +
logisticsPreSuitability[i] * 0.54 +
lowland * 0.36 +
agriculture[i] * 0.16 -
denseCorePenalty * 0.54 -
slope[i] * 0.82 -
highMountain * 0.92 -
coastalTraversePenalty * 0.32 -
river[i] * 0.14
);
railPotential[i] = clamp(
highDensity * 0.80 +
preliminaryTownInfluence[i] * 0.22 +
lowland * 0.46 +
valleyField[i] * 0.22 +
coastalLowland[i] * 0.22 -
slope[i] * 1.28 -
highMountain * 1.10 -
coastalTraversePenalty * 0.26 -
ridgeField[i] * 0.34
);
nationalPotential[i] = clamp(
density * 0.46 +
preliminaryTownInfluence[i] * 0.32 +
preliminaryVillageInfluence[i] * 0.20 +
agriculture[i] * 0.24 +
valleyField[i] * 0.28 +
coastalLowland[i] * 0.26 +
pass * 0.18 +
crossing * 0.18 -
slope[i] * 0.48 -
highMountain * 0.24 -
coastalTraversePenalty * 0.16 -
ridgeField[i] * 0.18
);
localPotential[i] = clamp(
preliminaryVillageInfluence[i] * 0.52 +
agriculture[i] * 0.38 +
coastalSettlement[i] * 0.30 +
valleySettlement[i] * 0.30 +
developable[i] * 0.18 -
slope[i] * 0.34 -
coastalTraversePenalty * 0.08 -
ridgeField[i] * 0.10
);
expressway[i] = Math.max(0.18,
1.62 - expresswayPotential[i] * 0.96 +
denseCorePenalty * 1.30 +
slope[i] * 5.4 +
highMountain * 5.8 +
extremeMountain * 4.2 +
boundaryRidgePenalty * 4.2 +
waterCrossingPenalty * 2.1 +
coastalTraversePenalty * 2.65 +
seaNear * 2.35 +
seaWide * 1.10 +
openPlainParallelPenalty * 0.12 +
hash2(x, y, seed + 13301) * 0.04
);
rail[i] = Math.max(0.16,
1.48 - railPotential[i] * 1.02 +
slope[i] * 7.2 +
highMountain * 7.0 +
extremeMountain * 4.8 +
boundaryRidgePenalty * 2.4 +
waterCrossingPenalty * 1.7 +
coastalTraversePenalty * 1.75 +
seaNear * 1.50 +
seaWide * 0.70 +
hash2(x, y, seed + 13302) * 0.03
);
national[i] = Math.max(0.16,
1.28 - nationalPotential[i] * 0.84 +
slope[i] * 2.8 +
ridgeField[i] * 1.18 +
Math.max(0, elevation[i] - 0.62) * 3.0 +
highMountain * 2.9 +
boundaryRidgePenalty * 1.8 +
waterCrossingPenalty * 1.25 -
valleyField[i] * 0.18 -
coastalLowland[i] * 0.08 +
coastalTraversePenalty * 1.55 +
seaNear * 0.84 +
seaWide * 0.48 -
pass * 0.42 +
hash2(x, y, seed + 13303) * 0.05
);
local[i] = Math.max(0.14,
1.12 - localPotential[i] * 0.86 +
slope[i] * 1.72 +
ridgeField[i] * 0.82 +
Math.max(0, elevation[i] - 0.68) * 1.9 +
highMountain * 1.24 +
boundaryRidgePenalty * 0.72 +
waterCrossingPenalty * 0.65 -
valleyField[i] * 0.22 -
coastalLowland[i] * 0.10 +
coastalTraversePenalty * 0.82 +
seaNear * 0.48 +
seaWide * 0.26 +
hash2(x, y, seed + 13304) * 0.07
);
}
}
return { expressway, rail, national, local, expresswayPotential, railPotential, nationalPotential, localPotential };
}