terrain tweak?
This commit is contained in:
parent
1be3d86da0
commit
8e1ec7b9ac
7 changed files with 3423 additions and 1328 deletions
356
renderer.js
356
renderer.js
|
|
@ -1,4 +1,4 @@
|
|||
import { CELL_SIZE, MAP_H, MAP_W, clamp, indexOf } from "./mapUtils.js";
|
||||
import { CELL_SIZE, MAP_H, MAP_W, clamp, fbm, indexOf, valueNoise } from "./mapUtils.js";
|
||||
|
||||
|
||||
const segmentVectorCache = new WeakMap();
|
||||
|
|
@ -261,10 +261,12 @@ function distToNearest(points, x, y, fallback = 999) {
|
|||
}
|
||||
|
||||
function blendOutside(color, isInside) {
|
||||
// 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;
|
||||
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),
|
||||
];
|
||||
}
|
||||
|
||||
function fieldSample(field, fx, fy) {
|
||||
|
|
@ -285,6 +287,23 @@ function fieldSample(field, fx, fy) {
|
|||
return ((a * (1 - tx) + b * tx) * (1 - ty)) + ((c * (1 - tx) + d * tx) * ty);
|
||||
}
|
||||
|
||||
function interpolateColorStops(value, stops) {
|
||||
if (value <= stops[0][0]) return stops[0][1].slice();
|
||||
for (let i = 1; i < stops.length; i++) {
|
||||
const [v, c] = stops[i];
|
||||
const [pv, pc] = stops[i - 1];
|
||||
if (value <= v) {
|
||||
const t = clamp((value - pv) / Math.max(0.0001, v - pv));
|
||||
return [
|
||||
Math.round(pc[0] + (c[0] - pc[0]) * t),
|
||||
Math.round(pc[1] + (c[1] - pc[1]) * t),
|
||||
Math.round(pc[2] + (c[2] - pc[2]) * t),
|
||||
];
|
||||
}
|
||||
}
|
||||
return stops[stops.length - 1][1].slice();
|
||||
}
|
||||
|
||||
function terrainColorContinuous(map, fx, fy, mode) {
|
||||
const i = sampleCellIndex(fx, fy);
|
||||
const isInside = Boolean(map.prefectureMask[i]);
|
||||
|
|
@ -314,39 +333,75 @@ function terrainColorContinuous(map, fx, fy, mode) {
|
|||
Math.round(230 + density * 10),
|
||||
];
|
||||
} else {
|
||||
// 地形色は「細い山脈線」を直接塗らず、標高+広域山地性で読む。
|
||||
// 狭い arcSpineField を強く発色させると人工的な波線になるため、寄与を抑える。
|
||||
// 地形の基底色は標高のみに従わせる。
|
||||
// 谷や微地形の見え方は陰影側で制御し、谷底だけが不自然に茶色化しないようにする。
|
||||
const e = fieldSample(map.elevation, fx, fy);
|
||||
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),
|
||||
];
|
||||
}
|
||||
color = interpolateColorStops(clamp(e), [
|
||||
[0.20, [231, 236, 223]],
|
||||
[0.30, [223, 231, 214]],
|
||||
[0.40, [213, 223, 201]],
|
||||
[0.50, [204, 215, 188]],
|
||||
[0.58, [195, 207, 173]],
|
||||
[0.65, [185, 196, 158]],
|
||||
[0.71, [177, 181, 141]],
|
||||
[0.76, [169, 164, 125]],
|
||||
[0.81, [157, 145, 105]],
|
||||
[0.86, [144, 128, 89]],
|
||||
[0.91, [130, 111, 79]],
|
||||
[0.95, [118, 103, 89]],
|
||||
[0.985, [146, 141, 133]],
|
||||
[1.00, [183, 179, 171]],
|
||||
]);
|
||||
}
|
||||
|
||||
return blendOutside(color, isInside);
|
||||
}
|
||||
|
||||
function terrainShadeContinuous(map, fx, fy) {
|
||||
const eL = fieldSample(map.elevation, fx - 0.6, fy);
|
||||
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);
|
||||
|
||||
// x は東向き, y は南向き。法線は (-dz/dx, -dz/dy, 1)。
|
||||
// 光源は北西上空(日本の地形表現で一般的な見え方)。
|
||||
const dzdx = (eR - eL) / 1.2;
|
||||
const dzdy = (eD - eU) / 1.2;
|
||||
const nx = -dzdx * 2.5;
|
||||
const ny = -dzdy * 2.5;
|
||||
const nz = 1.0;
|
||||
const nLen = Math.hypot(nx, ny, nz) || 1;
|
||||
|
||||
const lx = -0.5;
|
||||
const ly = -0.5;
|
||||
const lz = 0.7071067811865476;
|
||||
const hill = clamp((nx * lx + ny * ly + nz * lz) / nLen * 0.5 + 0.5);
|
||||
|
||||
const slope = map.slope ? fieldSample(map.slope, fx, fy) : 0;
|
||||
const valley = map.valleyField ? fieldSample(map.valleyField, fx, fy) : 0;
|
||||
const ravine = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx, fy) : 0;
|
||||
const tex = map.surfaceTextureField ? fieldSample(map.surfaceTextureField, fx, fy) : 0;
|
||||
const rvL = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx - 0.75, fy) : 0;
|
||||
const rvR = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx + 0.75, fy) : 0;
|
||||
const rvU = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx, fy - 0.75) : 0;
|
||||
const rvD = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx, fy + 0.75) : 0;
|
||||
const ravineRelief = (rvL - rvR) * 0.16 + (rvU - rvD) * 0.12;
|
||||
|
||||
// 谷底の低傾斜面では陰影を少し圧縮し、標高色がそのまま見えるようにする。
|
||||
const valleyFloor = clamp((valley - 0.16) * 1.8) * clamp((0.28 - slope) * 4.5);
|
||||
let shade = 0.76 + hill * 0.32 + ravineRelief - ravine * 0.08 - tex * 0.028;
|
||||
if (shade < 1) shade = 1 - (1 - shade) * (1 - valleyFloor * 0.52);
|
||||
else shade = 1 + (shade - 1) * (1 - valleyFloor * 0.20);
|
||||
|
||||
return clamp(shade, 0.66, 1.13);
|
||||
}
|
||||
|
||||
function discreteColor(map, x, y, mode) {
|
||||
const i = indexOf(x, y);
|
||||
let color;
|
||||
|
||||
if (map.sea[i]) {
|
||||
color = [170, 218, 255];
|
||||
color = [160, 205, 239];
|
||||
} else if (mode === "landuse") {
|
||||
const colors = {
|
||||
0: [242, 248, 238],
|
||||
|
|
@ -386,12 +441,7 @@ function drawBase(ctx, map, mode, continuousTerrain) {
|
|||
for (let px = 0; px < width; px++) {
|
||||
const fx = px / CELL_SIZE;
|
||||
const [r, g, b] = terrainColorContinuous(map, fx, fy, mode === "all" ? "terrain" : mode);
|
||||
|
||||
const eL = fieldSample(map.elevation, fx - 0.6, fy);
|
||||
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.965 + (eR - eL) * 1.24 + (eD - eU) * 0.86, 0.78, 1.17);
|
||||
const shade = terrainShadeContinuous(map, fx, fy);
|
||||
|
||||
const ii = (py * width + px) * 4;
|
||||
img.data[ii] = Math.round(r * shade);
|
||||
|
|
@ -419,6 +469,28 @@ function drawBase(ctx, map, mode, continuousTerrain) {
|
|||
ctx.putImageData(img, 0, 0);
|
||||
}
|
||||
|
||||
function drawRiverPath(ctx, map, path, color, widthFn, alpha = 1) {
|
||||
if (!path || path.length < 2) return;
|
||||
ctx.save();
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
for (let k = 0; k < path.length - 1; k++) {
|
||||
const [x1, y1] = path[k];
|
||||
const [x2, y2] = path[k + 1];
|
||||
const i1 = indexOf(x1, y1);
|
||||
const i2 = indexOf(x2, y2);
|
||||
const strength = Math.max((map.river?.[i1] || 0) + (map.flowAccum?.[i1] || 0) * 0.95, (map.river?.[i2] || 0) + (map.flowAccum?.[i2] || 0) * 0.95);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.lineWidth = widthFn(strength, k / Math.max(1, path.length - 1));
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1 * CELL_SIZE + CELL_SIZE / 2, y1 * CELL_SIZE + CELL_SIZE / 2);
|
||||
ctx.lineTo(x2 * CELL_SIZE + CELL_SIZE / 2, y2 * CELL_SIZE + CELL_SIZE / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawPath(ctx, path, color, width, dashed = false) {
|
||||
const points = vectorPath(path);
|
||||
if (points.length < 2) return;
|
||||
|
|
@ -602,136 +674,6 @@ 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;
|
||||
|
|
@ -749,13 +691,58 @@ 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 });
|
||||
drawVectorSegments(ctx, coastSegments, "rgba(248, 250, 242, 0.68)", 1.1, false, { iterations: 2, tolerance: 0.06 });
|
||||
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 });
|
||||
|
||||
// 2. Rivers
|
||||
const waterBlue = "rgba(160, 205, 240, 1)";
|
||||
for (const path of map.tributaryRivers || map.riverPaths || []) drawPath(ctx, path, "rgba(160, 205, 240, 0.8)", 1.5);
|
||||
for (const path of map.mainRivers) drawPath(ctx, path, waterBlue, 3.5);
|
||||
const mediumBlue = "rgba(160, 205, 240, 0.88)";
|
||||
const riverStrengthForPath = (path) => {
|
||||
if (!path || path.length === 0) return 0;
|
||||
let peak = 0;
|
||||
let tail = 0;
|
||||
const tailStart = Math.max(0, path.length - Math.min(path.length, 8));
|
||||
let tailCount = 0;
|
||||
for (let k = 0; k < path.length; k++) {
|
||||
const [x, y] = path[k];
|
||||
const i = indexOf(x, y);
|
||||
const strength = (map.river?.[i] || 0) + (map.flowAccum?.[i] || 0) * 0.75;
|
||||
peak = Math.max(peak, strength);
|
||||
if (k >= tailStart) {
|
||||
tail += strength;
|
||||
tailCount++;
|
||||
}
|
||||
}
|
||||
return Math.max(peak, tail / Math.max(1, tailCount));
|
||||
};
|
||||
// Draw a dendritic river network. Width is intentionally separated by
|
||||
// river order: small streams are hairline/low-alpha, tributaries are thin,
|
||||
// and only trunk rivers get a modestly wider stroke.
|
||||
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);
|
||||
}
|
||||
for (const path of map.tributaryRivers || []) {
|
||||
const strength = riverStrengthForPath(path);
|
||||
if ((path?.length || 0) < 9 || strength < 0.45) continue;
|
||||
drawRiverPath(ctx, map, path, mediumBlue, (s, t) => {
|
||||
const downstreamBoost = 0.92 + t * 0.18;
|
||||
if (s > 1.65) return 1.35 * downstreamBoost;
|
||||
if (s > 0.95) return 1.12 * downstreamBoost;
|
||||
return 0.94 * downstreamBoost;
|
||||
}, 0.92);
|
||||
}
|
||||
for (const path of map.mainRivers || []) {
|
||||
const strength = riverStrengthForPath(path);
|
||||
if ((path?.length || 0) < 9) continue;
|
||||
drawRiverPath(ctx, map, path, waterBlue, (s, t) => {
|
||||
const downstreamBoost = 0.96 + t * 0.24;
|
||||
if (s > 2.35) return 2.15 * downstreamBoost;
|
||||
if (s > 1.45) return 1.86 * downstreamBoost;
|
||||
return 1.55 * downstreamBoost;
|
||||
}, 1.0);
|
||||
}
|
||||
|
||||
const showHistory = ["history", "all", "terrain"].includes(mode);
|
||||
const showModern = ["modern", "all", "development", "landuse", "roads", "admin-debug", "borders-debug"].includes(mode);
|
||||
|
|
@ -763,36 +750,22 @@ 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(145, 145, 145, 0.82)", 1.15, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
||||
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") {
|
||||
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 });
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (!showFeatures) return;
|
||||
|
||||
// 4. Transport casings. Layer order: local roads, trunk roads, railways, expressways.
|
||||
if (showHistory) {
|
||||
|
|
@ -802,8 +775,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) {
|
||||
|
|
@ -824,8 +797,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) {
|
||||
|
|
@ -856,12 +829,10 @@ 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 = [
|
||||
|
|
@ -871,5 +842,4 @@ export function drawMap(canvas, map, options) {
|
|||
].filter((p) => p.insidePrefecture || p.kind === "External Gateway");
|
||||
drawLabels(ctx, important, 60);
|
||||
}
|
||||
drawScaleBar(ctx, map);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue