town tweak

This commit is contained in:
33333-33333 2026-05-24 17:38:51 +09:00
commit 1ea8ba1701
12 changed files with 5005 additions and 379 deletions

View file

@ -358,42 +358,45 @@ function terrainColorContinuous(map, fx, fy, mode) {
}
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);
const step = 0.50;
const eC = fieldSample(map.elevation, fx, fy);
const eL = fieldSample(map.elevation, fx - step, fy);
const eR = fieldSample(map.elevation, fx + step, fy);
const eU = fieldSample(map.elevation, fx, fy - step);
const eD = fieldSample(map.elevation, fx, fy + step);
// 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;
// 描画では地形生成上の差分をやや誇張し、粗いDEMでも山腹の起伏を読ませる
const dzdx = (eR - eL) / (step * 2);
const dzdy = (eD - eU) / (step * 2);
const nx = -dzdx * 4.4;
const ny = -dzdy * 4.4;
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 hill = clamp((nx * lx + ny * ly + nz * lz) / nLen * 0.58 + 0.48);
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 rvL = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx - 0.90, fy) : 0;
const rvR = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx + 0.90, fy) : 0;
const rvU = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx, fy - 0.90) : 0;
const rvD = map.visibleRavineField ? fieldSample(map.visibleRavineField, fx, fy + 0.90) : 0;
const ravineRelief = (rvL - rvR) * 0.26 + (rvU - rvD) * 0.20;
const concavity = clamp(((eL + eR + eU + eD) * 0.25 - eC) * 9.0, -0.18, 0.18);
// 谷底の低傾斜面では陰影を少し圧縮し、標高色がそのまま見えるようにする。
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);
// 谷底の色保持は少し残すが、以前より圧縮を弱めて陰影の振幅を大きくする。
const valleyFloor = clamp((valley - 0.18) * 1.45) * clamp((0.24 - slope) * 3.6);
let shade = 0.66 + hill * 0.50 + ravineRelief - ravine * 0.12 - tex * 0.042 - concavity * 0.16 + slope * 0.030;
if (shade < 1) shade = 1 - (1 - shade) * (1 - valleyFloor * 0.26);
else shade = 1 + (shade - 1) * (1 - valleyFloor * 0.12);
return clamp(shade, 0.66, 1.13);
return clamp(shade, 0.54, 1.26);
}
function discreteColor(map, x, y, mode) {
@ -404,16 +407,16 @@ function discreteColor(map, x, y, mode) {
color = [160, 205, 239];
} else if (mode === "landuse") {
const colors = {
0: [242, 248, 238],
1: [248, 250, 245],
2: [240, 238, 232],
3: [245, 230, 220],
4: [250, 248, 245],
5: [235, 235, 240],
6: [240, 245, 240],
7: [245, 248, 252],
8: [250, 248, 240],
9: [240, 245, 238],
0: [242, 248, 238], // rural / natural land
1: [238, 246, 222], // farmland
2: [240, 238, 232], // old urban
3: [245, 230, 220], // CBD / DID core
4: [250, 248, 245], // suburb
5: [235, 235, 240], // industrial
6: [240, 245, 240], // logistics
7: [245, 248, 252], // new town
8: [250, 248, 240], // roadside
9: [225, 238, 220], // forest / mountain land
};
color = colors[map.landuse[i]] || colors[0];
} else if (mode === "admin") {
@ -606,7 +609,8 @@ function drawDebugCells(ctx, map, field, color) {
for (let y = 0; y < MAP_H; y++) {
for (let x = 0; x < MAP_W; x++) {
const i = indexOf(x, y);
if (!map.prefectureMask[i] || map.sea[i]) continue;
const debugMask = map.humanRegionMask || map.prefectureMask;
if (!debugMask[i] || map.sea[i]) continue;
const v = clamp(field[i] || 0, 0, 1);
if (v <= 0.12) continue;
ctx.fillStyle = color(v);
@ -756,8 +760,10 @@ export function drawMap(canvas, map, options) {
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);
// 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 (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 });
}
@ -818,6 +824,7 @@ export function drawMap(canvas, map, options) {
const popRadius = p.population ? Math.min(8.5, 3.5 + Math.sqrt(p.population) / 400) : 4.5;
dot(ctx, p, popRadius, "rgba(240, 110, 110, 0.95)", "rgba(255,255,255,0.9)");
if (p.isPrefecturalCapital) dot(ctx, p, popRadius + 3.0, "transparent", "rgba(200,80,80,0.9)");
else if (p.isRegionalCapital) dot(ctx, p, popRadius + 2.2, "transparent", "rgba(190,95,95,0.62)");
}
for (const p of map.interchanges || []) dot(ctx, p, 2.7, "rgba(250, 250, 245, 0.95)", "rgba(95, 125, 95, 0.95)");
if (mode === "admin-debug" || mode === "borders-debug") {
@ -832,14 +839,14 @@ export function drawMap(canvas, map, options) {
return;
}
if (mode === "admin-debug" || mode === "borders-debug") {
drawLabels(ctx, [...(map.adminCenters || []), ...(map.externalGateways || [])], Infinity);
drawLabels(ctx, map.adminCenters || [], Infinity);
return;
}
const important = [
...map.modernCities,
...map.ports,
...(map.satelliteCities || []),
].filter((p) => p.insidePrefecture || p.kind === "External Gateway");
].filter((p) => p.insidePrefecture || p.isRegionalCapital || p.kind === "External Gateway");
drawLabels(ctx, important, 60);
}
}