191 lines
7.7 KiB
JavaScript
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 };
|
|
}
|
|
|