terrain update. broken but ok

This commit is contained in:
33333-33333 2026-05-22 19:28:39 +09:00
commit 1be3d86da0
7 changed files with 2208 additions and 254 deletions

View file

@ -261,12 +261,10 @@ function distToNearest(points, x, y, fallback = 999) {
}
function blendOutside(color, isInside) {
if (isInside) return color;
return [
Math.round(color[0] * 0.8 + 50),
Math.round(color[1] * 0.8 + 50),
Math.round(color[2] * 0.8 + 50),
];
// Seamless-map mode: do not dim cells outside the currently named prefecture.
// The prefecture mask is still kept for statistics/admin debug, but the base
// terrain should read as one continuous region when the map is later panned.
return color;
}
function fieldSample(field, fx, fy) {
@ -316,14 +314,28 @@ function terrainColorContinuous(map, fx, fy, mode) {
Math.round(230 + density * 10),
];
} else {
// 地形色を少し濃く(暗く)調整
// 地形色は「細い山脈線」を直接塗らず、標高+広域山地性で読む。
// 狭い arcSpineField を強く発色させると人工的な波線になるため、寄与を抑える。
const e = fieldSample(map.elevation, fx, fy);
if (e > 0.82) color = [210, 205, 195];
else if (e > 0.68) color = [218, 215, 205];
else if (e > 0.52) color = [220, 225, 210];
else if (e > 0.34) color = [225, 230, 215];
else if (e > 0.24) color = [230, 235, 220];
else color = [238, 242, 228];
const spine = map.arcSpineField ? fieldSample(map.arcSpineField, fx, fy) : 0;
const ridge = map.ridgeField ? fieldSample(map.ridgeField, fx, fy) : 0;
const rel = clamp((e - (map.seaLevel ?? 0.285) + 0.035) / 0.46);
const mountainRead = clamp(rel * 0.92 + ridge * 0.16 + spine * 0.045);
if (mountainRead > 0.86) color = [176, 166, 150];
else if (mountainRead > 0.76) color = [192, 183, 166];
else if (mountainRead > 0.62) color = [207, 199, 181];
else if (mountainRead > 0.46) color = [221, 225, 204];
else if (mountainRead > 0.29) color = [232, 238, 222];
else color = [240, 244, 230];
const highRugged = clamp((ridge - 0.42) * 0.36 + (rel - 0.66) * 0.22);
if (highRugged > 0) {
color = [
Math.round(color[0] - highRugged * 18),
Math.round(color[1] - highRugged * 15),
Math.round(color[2] - highRugged * 12),
];
}
}
return blendOutside(color, isInside);
@ -379,7 +391,7 @@ function drawBase(ctx, map, mode, continuousTerrain) {
const eR = fieldSample(map.elevation, fx + 0.6, fy);
const eU = fieldSample(map.elevation, fx, fy - 0.6);
const eD = fieldSample(map.elevation, fx, fy + 0.6);
const shade = clamp(0.95 + (eR - eL) * 0.6 + (eD - eU) * 0.4, 0.85, 1.08);
const shade = clamp(0.965 + (eR - eL) * 1.24 + (eD - eU) * 0.86, 0.78, 1.17);
const ii = (py * width + px) * 4;
img.data[ii] = Math.round(r * shade);
@ -590,6 +602,136 @@ function drawLabels(ctx, points, limit = Infinity) {
for (const p of prioritized.slice(0, limit)) labelWithCollision(ctx, p, occupied);
}
function niceScaleKm(maxKm) {
const candidates = [1, 2, 5, 10, 20, 25, 50, 100, 200];
let best = candidates[0];
for (const c of candidates) {
if (c <= maxKm) best = c;
}
return best;
}
function drawScaleBar(ctx, map) {
const kmPerCell = map.scaleKmPerCell || 1;
const width = MAP_W * CELL_SIZE;
const height = MAP_H * CELL_SIZE;
const maxPx = Math.min(150, width * 0.18);
const scaleKm = niceScaleKm(Math.max(1, maxPx / CELL_SIZE * kmPerCell));
const barPx = Math.max(24, (scaleKm / kmPerCell) * CELL_SIZE);
const x = 18;
const y = height - 22;
const h = 6;
ctx.save();
ctx.font = '11px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
ctx.textBaseline = "bottom";
ctx.fillStyle = "rgba(255, 255, 255, 0.72)";
ctx.strokeStyle = "rgba(80, 80, 80, 0.34)";
ctx.lineWidth = 1;
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(x - 8, y - 22, barPx + 42, 29, 5);
else ctx.rect(x - 8, y - 22, barPx + 42, 29);
ctx.fill();
ctx.stroke();
ctx.fillStyle = "rgba(60, 60, 60, 0.95)";
ctx.fillRect(x, y - h, barPx / 2, h);
ctx.fillStyle = "rgba(245, 245, 245, 0.95)";
ctx.fillRect(x + barPx / 2, y - h, barPx / 2, h);
ctx.strokeStyle = "rgba(55, 55, 55, 0.95)";
ctx.lineWidth = 1.2;
ctx.strokeRect(x, y - h, barPx, h);
ctx.beginPath();
ctx.moveTo(x, y - h - 3);
ctx.lineTo(x, y + 3);
ctx.moveTo(x + barPx / 2, y - h);
ctx.lineTo(x + barPx / 2, y + 2);
ctx.moveTo(x + barPx, y - h - 3);
ctx.lineTo(x + barPx, y + 3);
ctx.stroke();
ctx.fillStyle = "rgba(45, 45, 45, 0.95)";
ctx.fillText(`${scaleKm} km`, x + barPx + 8, y + 2);
ctx.restore();
}
function drawNeighborPrefectureDetails(ctx, map, mode = "all") {
const details = map?.neighborPrefectureDetails;
if (!details || !details.prefectures?.length) return;
const showRoads = ["roads", "all", "development"].includes(mode);
const showModern = ["modern", "all", "development", "landuse", "roads", "admin", "admin-debug", "borders-debug"].includes(mode);
const showAdmin = ["admin", "all", "admin-debug", "borders-debug"].includes(mode);
if (!showRoads && !showModern && !showAdmin) return;
ctx.save();
ctx.globalAlpha = 1.0;
if (showModern) {
for (const city of details.cities || []) {
const r = Math.max(6, Math.min(22, (city.urbanRadius || 8) * CELL_SIZE * 0.48));
ctx.beginPath();
ctx.arc(city.x * CELL_SIZE + CELL_SIZE / 2, city.y * CELL_SIZE + CELL_SIZE / 2, r, 0, Math.PI * 2);
ctx.fillStyle = "rgba(226, 216, 202, 0.52)";
ctx.fill();
}
for (const cbd of details.centralBusinessDistricts || []) {
ctx.fillStyle = "rgba(222, 168, 145, 0.68)";
ctx.fillRect(cbd.x * CELL_SIZE - 1.5, cbd.y * CELL_SIZE - 1.5, CELL_SIZE + 3, CELL_SIZE + 3);
}
}
if (showRoads) {
for (const path of details.roads || []) drawPath(ctx, path, "rgba(190, 174, 118, 0.92)", 3.2);
for (const path of details.roads || []) drawPath(ctx, path, "rgba(248, 226, 122, 0.96)", 1.65);
}
if (showModern || showRoads) {
for (const path of details.railways || []) drawPath(ctx, path, "rgba(255,255,255,0.74)", 2.8);
for (const path of details.railways || []) drawRailway(ctx, path, "rgba(76, 76, 76, 0.86)", 1.15, 4.0, 6.0);
}
if (showModern || showAdmin) {
for (const p of details.adminCenters || []) dot(ctx, p, 2.0, "rgba(250,250,245,0.82)", "rgba(90,90,90,0.66)");
for (const p of details.cities || []) {
const r = p.rank === "Neighbor Prefectural Capital" ? 5.4 : 3.8;
dot(ctx, p, r, "rgba(226, 104, 104, 0.86)", "rgba(255,255,255,0.78)");
if (p.rank === "Neighbor Prefectural Capital") dot(ctx, p, r + 4.6, "rgba(255,255,255,0.05)", "rgba(225,95,95,0.55)");
}
}
if (["all", "admin", "modern", "roads", "admin-debug", "borders-debug"].includes(mode)) {
ctx.globalAlpha = 0.72;
drawLabels(ctx, [...(details.cities || []), ...(showAdmin ? (details.adminCenters || []) : [])], showAdmin ? 64 : 36);
}
ctx.restore();
}
function drawPrefectureLabels(ctx, map, mode = "all") {
if (!map?.prefectureLabel && !(map?.neighborPrefectures || []).length) return;
const width = MAP_W * CELL_SIZE;
const height = MAP_H * CELL_SIZE;
const shouldShow = ["all", "modern", "roads", "development", "landuse", "admin", "admin-debug", "borders-debug"].includes(mode);
if (!shouldShow) return;
ctx.save();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.lineJoin = "round";
const drawPrefLabel = (label, isMain = false) => {
if (!label?.name) return;
const x = clamp(label.x * CELL_SIZE, 44, width - 44);
const y = clamp(label.y * CELL_SIZE - (isMain ? 14 : 8), 28, height - 34);
ctx.font = `${isMain ? 700 : 700} ${isMain ? 22 : 20}px "Hiragino Sans", "Yu Gothic", system-ui, sans-serif`;
ctx.lineWidth = isMain ? 5.2 : 4.8;
ctx.strokeStyle = "rgba(255, 255, 255, 0.72)";
ctx.fillStyle = "rgba(75, 62, 90, 0.64)";
ctx.strokeText(label.name, x, y);
ctx.fillText(label.name, x, y);
};
drawPrefLabel(map.prefectureLabel, true);
for (const label of map.neighborPrefectures || []) drawPrefLabel(label, false);
ctx.restore();
}
export function drawMap(canvas, map, options) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
@ -607,8 +749,8 @@ export function drawMap(canvas, map, options) {
drawBase(ctx, map, mode, true);
drawUrbanAreas(ctx, map, mode);
const coastSegments = getCoastlineSegments(map);
drawVectorSegments(ctx, coastSegments, "rgba(120, 175, 210, 0.22)", 2.2, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, coastSegments, "rgba(248, 250, 242, 0.68)", 1.1, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, coastSegments, "rgba(120, 175, 210, 0.22)", 2.2, false, { iterations: 2, tolerance: 0.06 });
drawVectorSegments(ctx, coastSegments, "rgba(248, 250, 242, 0.68)", 1.1, false, { iterations: 2, tolerance: 0.06 });
// 2. Rivers
const waterBlue = "rgba(160, 205, 240, 1)";
@ -621,22 +763,36 @@ export function drawMap(canvas, map, options) {
const showMinorRoads = ["roads", "all", "modern", "development"].includes(mode);
const showAdmin = ["admin", "all", "admin-debug", "borders-debug"].includes(mode);
drawNeighborPrefectureDetails(ctx, map, mode);
// 3. Borders
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 });
drawVectorSegments(ctx, map.adminBorders, "rgba(145, 145, 145, 0.82)", 1.15, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
}
if (mode === "admin-debug" || mode === "borders-debug") {
drawDebugCells(ctx, map, map.naturalBarrierScore, (v) => `rgba(255, 120, 40, ${0.06 + v * 0.18})`);
if (map.adminDebug?.compartmentBorders) drawSegments(ctx, map.adminDebug.compartmentBorders, "rgba(60, 110, 170, 0.42)", 0.8, 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 (map.regionalPrefectureBorders) drawVectorSegments(ctx, map.regionalPrefectureBorders, "rgba(70, 55, 95, 0.95)", 2.4, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
}
drawVectorSegments(ctx, map.prefectureBorder, "rgba(255, 255, 255, 0.95)", 5.0, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, map.prefectureBorder, "rgba(110, 90, 110, 1)", 2.2, true, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
if (!showFeatures) return;
// Draw all generated prefecture borders with the same weight. The old main
// prefecture border is shown only in debug modes so the normal view no longer
// reads as a single highlighted prefecture surrounded by context.
const prefectureBorderSegments = map.regionalPrefectureBorders?.length ? map.regionalPrefectureBorders : map.prefectureBorder;
if (prefectureBorderSegments?.length) {
drawVectorSegments(ctx, prefectureBorderSegments, "rgba(255, 255, 255, 0.82)", 4.0, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, prefectureBorderSegments, "rgba(110, 90, 120, 0.86)", 1.7, true, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
}
if ((mode === "admin-debug" || mode === "borders-debug") && map.prefectureBorder?.length) {
drawVectorSegments(ctx, map.prefectureBorder, "rgba(255, 255, 255, 0.95)", 5.0, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
drawVectorSegments(ctx, map.prefectureBorder, "rgba(110, 90, 110, 1)", 2.2, true, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
}
if (showLabels) drawPrefectureLabels(ctx, map, mode);
if (!showFeatures) {
drawScaleBar(ctx, map);
return;
}
// 4. Transport casings. Layer order: local roads, trunk roads, railways, expressways.
if (showHistory) {
@ -646,8 +802,8 @@ export function drawMap(canvas, map, options) {
for (const path of map.minorRoads || []) drawPath(ctx, path, "rgba(205, 205, 205, 0.60)", 2.35);
}
if (showRoads) {
for (const path of map.ringRoads || []) drawPath(ctx, path, "rgba(205, 205, 205, 0.62)", 2.8);
for (const path of map.nationalRoads) drawPath(ctx, path, "rgba(190, 175, 140, 1)", 3.8);
for (const path of map.ringRoads || []) drawPath(ctx, path, "rgba(190, 175, 140, 1)", 3.8);
for (const path of map.externalRoads) drawPath(ctx, path, "rgba(190, 175, 140, 1)", 3.8);
}
if (showModern || showRoads) {
@ -668,8 +824,8 @@ export function drawMap(canvas, map, options) {
for (const path of map.minorRoads || []) drawPath(ctx, path, "rgba(255, 255, 255, 0.94)", 1.1, false);
}
if (showRoads) {
for (const path of map.ringRoads || []) drawPath(ctx, path, "rgba(255, 255, 255, 0.96)", 1.25, false);
for (const path of map.nationalRoads) drawPath(ctx, path, "rgba(245, 225, 130, 1)", 2.0);
for (const path of map.ringRoads || []) drawPath(ctx, path, "rgba(245, 225, 130, 1)", 2.0);
for (const path of map.externalRoads) drawPath(ctx, path, "rgba(245, 225, 130, 1)", 2.0);
}
if (showModern || showRoads) {
@ -700,10 +856,12 @@ export function drawMap(canvas, map, options) {
if (showLabels) {
if (mode === "admin") {
drawLabels(ctx, map.adminCenters || [], Infinity);
drawScaleBar(ctx, map);
return;
}
if (mode === "admin-debug" || mode === "borders-debug") {
drawLabels(ctx, [...(map.adminCenters || []), ...(map.externalGateways || [])], Infinity);
drawScaleBar(ctx, map);
return;
}
const important = [
@ -713,4 +871,5 @@ export function drawMap(canvas, map, options) {
].filter((p) => p.insidePrefecture || p.kind === "External Gateway");
drawLabels(ctx, important, 60);
}
drawScaleBar(ctx, map);
}