road update

This commit is contained in:
33333-33333 2026-05-26 21:14:37 +09:00
commit a6e66f2838
9 changed files with 2174 additions and 258 deletions

View file

@ -314,7 +314,8 @@ function terrainColorContinuous(map, fx, fy, mode) {
if (isWaterSample(map, fx, fy)) {
const depth = clamp(((map.seaLevel ?? 0.285) + 0.065 - fieldSample(map.elevation, fx, fy)) * 2.4);
color = [Math.round(170 + depth * 5), Math.round(218 + depth * 10), Math.round(255 - depth * 5)];
// Calm sky-blue water; less saturated than the previous bright cyan.
color = [Math.round(174 - depth * 7), Math.round(203 + depth * 2), Math.round(224 + depth * 5)];
} else if (mode === "development") {
const dCity = distToNearest(map.modernCities, fx, fy);
const urban = clamp(1 - dCity / 25);
@ -644,6 +645,36 @@ function drawDebugCells(ctx, map, field, color) {
ctx.restore();
}
function drawTransportDebug(ctx, map) {
const layers = map.transportDebug?.layers;
if (!layers) return;
drawDebugCells(ctx, map, layers.expresswayPotential, (v) => v < 0.16 ? "rgba(0,0,0,0)" : `rgba(80, 190, 110, ${0.035 + v * 0.13})`);
drawDebugCells(ctx, map, layers.railPotential, (v) => v < 0.16 ? "rgba(0,0,0,0)" : `rgba(70, 120, 230, ${0.035 + v * 0.13})`);
drawDebugCells(ctx, map, layers.nationalRoadPotential, (v) => v < 0.16 ? "rgba(0,0,0,0)" : `rgba(245, 205, 65, ${0.030 + v * 0.12})`);
drawDebugCells(ctx, map, layers.slopeSeaPenalty, (v) => v < 0.45 ? "rgba(0,0,0,0)" : `rgba(80, 30, 30, ${0.025 + v * 0.10})`);
const componentColors = {
expressway: "rgba(60, 165, 80, 0.42)",
rail: "rgba(65, 95, 210, 0.42)",
national: "rgba(210, 155, 20, 0.42)",
};
ctx.save();
for (const comp of layers.components || []) {
ctx.fillStyle = componentColors[comp.mode] || "rgba(150,150,150,0.35)";
for (const [x, y] of comp.cells || []) ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}
ctx.restore();
for (const repair of layers.repairedSegments || []) {
const color = repair.mode === "expressway" ? "rgba(0, 115, 40, 0.95)"
: repair.mode === "rail" ? "rgba(35, 70, 210, 0.95)"
: repair.mode === "national" ? "rgba(180, 120, 0, 0.95)"
: "rgba(210, 35, 155, 0.92)";
drawPath(ctx, repair.path, "rgba(255,255,255,0.92)", 5.0);
drawPath(ctx, repair.path, color, 2.4);
}
}
function prefectureRegionColor(id) {
const palette = [
[234, 220, 214], [218, 232, 218], [218, 224, 238], [238, 232, 208],
@ -653,10 +684,10 @@ function prefectureRegionColor(id) {
}
function drawPrefectureRegionFill(ctx, map, mode) {
if (!["all", "admin", "admin-debug", "borders-debug"].includes(mode)) return;
if (!["all", "admin", "borders-debug"].includes(mode)) return;
const ids = map.prefectureRegionId;
if (!ids) return;
const alpha = mode === "borders-debug" ? 0.34 : mode === "admin-debug" ? 0.26 : 0.18;
const alpha = mode === "borders-debug" ? 0.34 : 0.18;
ctx.save();
ctx.globalAlpha = alpha;
for (let y = 0; y < MAP_H; y++) {
@ -688,30 +719,38 @@ function boxesOverlap(a, b, pad = 3) {
function labelWithCollision(ctx, p, occupied) {
if (!p.name) return false;
const isPrefectureLabel = p.kind === "Prefecture" || p.kind === "Current Prefecture" || p.isPrefectureLabel;
ctx.save();
ctx.font = "600 11.5px ui-sans-serif, system-ui, -apple-system, sans-serif";
ctx.font = isPrefectureLabel
? "900 20px ui-sans-serif, system-ui, -apple-system, sans-serif"
: "600 11.5px ui-sans-serif, system-ui, -apple-system, sans-serif";
const baseX = p.x * CELL_SIZE + CELL_SIZE / 2;
const baseY = p.y * CELL_SIZE + CELL_SIZE / 2;
const textW = ctx.measureText(p.name).width;
const textH = 12;
const candidates = [
[7, -4], [7, 13], [-textW - 7, -4], [-textW - 7, 13],
[-textW / 2, -12], [-textW / 2, 20], [12, 4], [-textW - 12, 4],
];
const textH = isPrefectureLabel ? 22 : 12;
const candidates = isPrefectureLabel
? [
[-textW / 2, 6], [-textW / 2, -12], [-textW / 2, 24],
[10, 6], [-textW - 10, 6],
]
: [
[7, -4], [7, 13], [-textW - 7, -4], [-textW - 7, 13],
[-textW / 2, -12], [-textW / 2, 20], [12, 4], [-textW - 12, 4],
];
for (const [ox, oy] of candidates) {
const x = baseX + ox;
const y = baseY + oy;
const box = { x1: x - 2, y1: y - textH, x2: x + textW + 2, y2: y + 4 };
const box = { x1: x - 3, y1: y - textH, x2: x + textW + 3, y2: y + 5 };
if (box.x1 < 0 || box.y1 < 0 || box.x2 > MAP_W * CELL_SIZE || box.y2 > MAP_H * CELL_SIZE) continue;
if (occupied.some((b) => boxesOverlap(box, b))) continue;
if (occupied.some((b) => boxesOverlap(box, b, isPrefectureLabel ? 5 : 3))) continue;
ctx.lineJoin = "round";
ctx.lineWidth = 3.5;
ctx.strokeStyle = "rgba(255, 255, 255, 0.95)";
ctx.lineWidth = isPrefectureLabel ? 6.2 : 3.5;
ctx.strokeStyle = isPrefectureLabel ? "rgba(255, 255, 255, 0.98)" : "rgba(255, 255, 255, 0.95)";
ctx.strokeText(p.name, x, y);
ctx.fillStyle = p.isPrefecturalCapital ? "#111111" : "#333333";
ctx.fillStyle = isPrefectureLabel ? "rgba(62, 38, 112, 0.98)" : p.isPrefecturalCapital ? "#111111" : "#333333";
ctx.fillText(p.name, x, y);
occupied.push(box);
ctx.restore();
@ -787,8 +826,8 @@ export function drawMap(canvas, map, options) {
drawVectorSegments(ctx, coastSegments, "rgba(248, 250, 242, 0.68)", 1.1, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
// 2. Rivers
const waterBlue = "rgba(160, 205, 240, 1)";
const mediumBlue = "rgba(160, 205, 240, 0.88)";
const waterBlue = "rgba(116, 165, 202, 0.92)";
const mediumBlue = "rgba(132, 184, 220, 0.78)";
const riverStrengthForPath = (path) => {
if (!path || path.length === 0) return 0;
let peak = 0;
@ -813,7 +852,7 @@ export function drawMap(canvas, map, options) {
for (const path of map.smallStreams || []) {
const strength = riverStrengthForPath(path);
if ((path?.length || 0) < 5 || strength < 0.045) continue;
drawRiverPath(ctx, map, path, "rgba(150, 198, 235, 1)", (s) => s > 0.45 ? 0.58 : s > 0.22 ? 0.48 : 0.36, 0.34);
drawRiverPath(ctx, map, path, "rgba(140, 190, 224, 0.82)", (s) => s > 0.45 ? 0.52 : s > 0.22 ? 0.42 : 0.32, 0.28);
}
for (const path of map.tributaryRivers || []) {
const strength = riverStrengthForPath(path);
@ -837,26 +876,28 @@ export function drawMap(canvas, map, options) {
}
const showHistory = ["history", "all", "terrain"].includes(mode);
const showModern = ["modern", "all", "development", "landuse", "admin-debug", "borders-debug"].includes(mode);
const showRoads = ["all", "development"].includes(mode);
const showMinorRoads = ["all", "modern", "development"].includes(mode);
const showAdmin = ["admin", "all", "admin-debug", "borders-debug"].includes(mode);
const showTransportDebug = mode === "transport-debug";
const showModern = ["modern", "all", "development", "landuse", "borders-debug", "transport-debug"].includes(mode);
const showRoads = ["all", "development", "transport-debug"].includes(mode);
const showMinorRoads = ["all", "modern", "development", "transport-debug"].includes(mode);
const showAdmin = ["admin", "all", "borders-debug"].includes(mode);
// 3. Borders
const showPrefectureRegions = ["all", "admin", "admin-debug", "borders-debug"].includes(mode);
const showPrefectureRegions = ["all", "admin", "borders-debug"].includes(mode);
if (showPrefectureRegions) drawPrefectureRegionFill(ctx, map, mode);
if (showAdmin && map.adminBorders) {
drawVectorSegments(ctx, map.adminBorders, "rgba(255, 255, 255, 0.8)", 2.8, false, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, map.adminBorders, "rgba(150, 140, 150, 0.9)", 1.2, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
}
if (mode === "admin-debug" || mode === "borders-debug") {
if (mode === "borders-debug") {
// Keep the natural barrier heatmap subtle. A dense cell fill can look like
// artificial horizontal hatching, so only strong terrain dividers are shown.
drawDebugCells(ctx, map, map.naturalBarrierScore, (v) => v < 0.42 ? "rgba(0,0,0,0)" : `rgba(255, 120, 40, ${0.025 + v * 0.075})`);
if (map.adminDebug?.compartmentBorders) drawSegments(ctx, map.adminDebug.compartmentBorders, "rgba(45, 95, 160, 0.72)", 1.0, true);
for (const p of map.adminDebug?.lowlandAdminSeeds || []) dot(ctx, p, 3.2, "rgba(255,255,255,0.9)", "rgba(40,150,95,0.95)");
}
if (showTransportDebug) drawTransportDebug(ctx, map);
if (showPrefectureRegions && map.regionalPrefectureBorders) {
drawVectorSegments(ctx, map.regionalPrefectureBorders, "rgba(255, 255, 255, 0.90)", 4.2, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
@ -916,7 +957,7 @@ export function drawMap(canvas, map, options) {
}
// 6. Icons & Labels
if (["admin", "admin-debug", "borders-debug"].includes(mode)) {
if (["admin", "borders-debug"].includes(mode)) {
for (const p of map.adminCenters || []) dot(ctx, p, 2.8, "rgba(255,255,255,0.96)", "rgba(75,60,90,0.95)");
}
@ -937,20 +978,25 @@ export function drawMap(canvas, map, options) {
}
for (const p of map.interchanges || []) dot(ctx, p, 2.7, "rgba(250, 250, 245, 0.95)", "rgba(95, 125, 95, 0.95)");
for (const p of map.logisticsParks || []) dot(ctx, p, 2.4, "rgba(235, 238, 230, 0.95)", "rgba(105, 125, 105, 0.88)");
if (mode === "admin-debug" || mode === "borders-debug") {
if (mode === "borders-debug") {
for (const p of map.externalGateways || []) dot(ctx, p, 5.5, "rgba(255,255,255,0.95)", "rgba(40,80,170,0.95)");
for (const p of map.transportDebug?.missingRequiredNodes || []) dot(ctx, p, 6.5, "rgba(255,80,80,0.95)", "rgba(80,20,20,0.95)");
}
if (showTransportDebug) {
for (const p of map.transportDebug?.layers?.unservedSettlements || []) {
dot(ctx, p, p.repaired ? 3.4 : 4.8, p.repaired ? "rgba(255,255,255,0.92)" : "rgba(255,80,140,0.95)", "rgba(125,35,105,0.95)");
}
}
}
if (showLabels) {
const prefectureLabels = showPrefectureRegions ? (map.prefectureRegions || []).map((p) => ({ ...p, labelPriorityBase: p.labelPriorityBase || 900 })) : [];
const prefectureLabels = (map.prefectureRegions || []).map((p) => ({ ...p, isPrefectureLabel: true, labelPriorityBase: p.labelPriorityBase || 1700 }));
if (mode === "admin") {
drawLabels(ctx, [...prefectureLabels, ...(map.adminCenters || [])], Infinity);
drawScaleBar(ctx);
return;
}
if (mode === "admin-debug" || mode === "borders-debug") {
if (mode === "borders-debug") {
drawLabels(ctx, [...prefectureLabels, ...(map.adminCenters || [])], Infinity);
drawScaleBar(ctx);
return;
@ -970,7 +1016,7 @@ export function drawMap(canvas, map, options) {
...(map.logisticsParks || []).map((p) => ({ ...p, labelPriorityBase: 60 })),
...(map.satelliteCities || []),
...allLayerTowns,
].filter((p) => p.forceAllLayerTownLabel || p.insidePrefecture || p.isRegionalCapital || p.kind === "External Gateway" || (p.population || 0) >= 5000);
].filter((p) => p.isPrefectureLabel || p.forceAllLayerTownLabel || p.insidePrefecture || p.isRegionalCapital || p.kind === "External Gateway" || (p.population || 0) >= 5000);
drawLabels(ctx, important, mode === "all" ? 85 : 60);
}
drawScaleBar(ctx);