not bad
This commit is contained in:
parent
27ceb6568a
commit
4a8aff90b2
7 changed files with 3806 additions and 83 deletions
97
app.js
97
app.js
|
|
@ -70,6 +70,10 @@ const dragState = {
|
||||||
startClientY: 0,
|
startClientY: 0,
|
||||||
startCameraX: 0,
|
startCameraX: 0,
|
||||||
startCameraY: 0,
|
startCameraY: 0,
|
||||||
|
lastClientX: 0,
|
||||||
|
lastClientY: 0,
|
||||||
|
panRemainderX: 0,
|
||||||
|
panRemainderY: 0,
|
||||||
selectStart: null,
|
selectStart: null,
|
||||||
selectEnd: null,
|
selectEnd: null,
|
||||||
selectPath: null,
|
selectPath: null,
|
||||||
|
|
@ -241,15 +245,34 @@ function selectionPathToShape(points) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncSelectionSvgToCanvas() {
|
||||||
|
if (!selectionSvgEl || !canvas) return null;
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const width = Math.max(1, rect.width || canvas.clientWidth || canvas.width || 1);
|
||||||
|
const height = Math.max(1, rect.height || canvas.clientHeight || canvas.height || 1);
|
||||||
|
selectionSvgEl.style.left = `${canvas.offsetLeft}px`;
|
||||||
|
selectionSvgEl.style.top = `${canvas.offsetTop}px`;
|
||||||
|
selectionSvgEl.style.width = `${width}px`;
|
||||||
|
selectionSvgEl.style.height = `${height}px`;
|
||||||
|
selectionSvgEl.setAttribute("width", String(width));
|
||||||
|
selectionSvgEl.setAttribute("height", String(height));
|
||||||
|
selectionSvgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
function drawSelectionSvg(points, invalid = false) {
|
function drawSelectionSvg(points, invalid = false) {
|
||||||
if (!selectionSvgEl) return;
|
if (!selectionSvgEl) return;
|
||||||
if (!points || points.length < 3) {
|
const bounds = syncSelectionSvgToCanvas();
|
||||||
|
if (!bounds || !points || points.length < 3) {
|
||||||
selectionSvgEl.style.display = "none";
|
selectionSvgEl.style.display = "none";
|
||||||
selectionSvgEl.innerHTML = "";
|
selectionSvgEl.innerHTML = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pts = points.map((p) => `${p.x},${p.y}`).join(" ");
|
const pts = points.map((p) => {
|
||||||
selectionSvgEl.setAttribute("viewBox", `0 0 ${canvas.clientWidth || canvas.width || 1} ${canvas.clientHeight || canvas.height || 1}`);
|
const x = Math.min(Math.max(p.x, 0), bounds.width);
|
||||||
|
const y = Math.min(Math.max(p.y, 0), bounds.height);
|
||||||
|
return `${x},${y}`;
|
||||||
|
}).join(" ");
|
||||||
selectionSvgEl.innerHTML = `<polygon points="${pts}" />`;
|
selectionSvgEl.innerHTML = `<polygon points="${pts}" />`;
|
||||||
selectionSvgEl.style.display = "block";
|
selectionSvgEl.style.display = "block";
|
||||||
selectionSvgEl.classList.toggle("invalid", !!invalid);
|
selectionSvgEl.classList.toggle("invalid", !!invalid);
|
||||||
|
|
@ -375,6 +398,8 @@ function clearDragMode() {
|
||||||
dragState.mode = null;
|
dragState.mode = null;
|
||||||
dragState.pointerId = null;
|
dragState.pointerId = null;
|
||||||
dragState.pendingCamera = null;
|
dragState.pendingCamera = null;
|
||||||
|
dragState.panRemainderX = 0;
|
||||||
|
dragState.panRemainderY = 0;
|
||||||
if (dragState.panRaf != null) {
|
if (dragState.panRaf != null) {
|
||||||
cancelAnimationFrame(dragState.panRaf);
|
cancelAnimationFrame(dragState.panRaf);
|
||||||
dragState.panRaf = null;
|
dragState.panRaf = null;
|
||||||
|
|
@ -392,7 +417,10 @@ function schedulePanRedraw(camera) {
|
||||||
dragState.pendingCamera = null;
|
dragState.pendingCamera = null;
|
||||||
if (next.x === state.camera.x && next.y === state.camera.y) return;
|
if (next.x === state.camera.x && next.y === state.camera.y) return;
|
||||||
state.camera = next;
|
state.camera = next;
|
||||||
redraw({ fastTerrain: true });
|
// Do not auto-expand the backing world while a pointer drag is active.
|
||||||
|
// Expansion shifts world coordinates; doing it mid-drag invalidates the
|
||||||
|
// pointer-to-camera baseline and can make the viewport appear to jump.
|
||||||
|
redraw({ fastTerrain: true, allowWorldExpand: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,6 +460,10 @@ function handleMapPointerDown(event) {
|
||||||
dragState.startClientY = event.clientY;
|
dragState.startClientY = event.clientY;
|
||||||
dragState.startCameraX = state.camera.x;
|
dragState.startCameraX = state.camera.x;
|
||||||
dragState.startCameraY = state.camera.y;
|
dragState.startCameraY = state.camera.y;
|
||||||
|
dragState.lastClientX = event.clientX;
|
||||||
|
dragState.lastClientY = event.clientY;
|
||||||
|
dragState.panRemainderX = 0;
|
||||||
|
dragState.panRemainderY = 0;
|
||||||
tooltipEl?.classList.remove("visible");
|
tooltipEl?.classList.remove("visible");
|
||||||
|
|
||||||
if (event.button === 0) {
|
if (event.button === 0) {
|
||||||
|
|
@ -455,14 +487,34 @@ function handleMapPointerMove(event) {
|
||||||
tooltipEl?.classList.remove("visible");
|
tooltipEl?.classList.remove("visible");
|
||||||
|
|
||||||
if (dragState.mode === "pan") {
|
if (dragState.mode === "pan") {
|
||||||
const cellSize = Math.max(1, displayedCellSize());
|
const rect = canvas.getBoundingClientRect();
|
||||||
const dxCells = Math.round((event.clientX - dragState.startClientX) / cellSize);
|
const maxDeltaX = Math.max(320, rect.width * 0.72);
|
||||||
const dyCells = Math.round((event.clientY - dragState.startClientY) / cellSize);
|
const maxDeltaY = Math.max(240, rect.height * 0.72);
|
||||||
const nextCamera = clampCameraForView({
|
const rawDx = event.clientX - dragState.lastClientX;
|
||||||
x: dragState.startCameraX - dxCells,
|
const rawDy = event.clientY - dragState.lastClientY;
|
||||||
y: dragState.startCameraY - dyCells,
|
dragState.lastClientX = event.clientX;
|
||||||
}, viewportSizeForZoom(state.zoom));
|
dragState.lastClientY = event.clientY;
|
||||||
schedulePanRedraw(nextCamera);
|
|
||||||
|
// Pointer capture can occasionally deliver a stale/outlier coordinate after
|
||||||
|
// a tab switch, resize, context-menu gesture, or OS-level event hiccup. A
|
||||||
|
// single implausibly large delta would otherwise become a large camera jump.
|
||||||
|
if (Math.abs(rawDx) <= maxDeltaX && Math.abs(rawDy) <= maxDeltaY) {
|
||||||
|
const cellSize = Math.max(1, displayedCellSize());
|
||||||
|
const totalX = dragState.panRemainderX + rawDx / cellSize;
|
||||||
|
const totalY = dragState.panRemainderY + rawDy / cellSize;
|
||||||
|
const dxCells = totalX < 0 ? Math.ceil(totalX) : Math.floor(totalX);
|
||||||
|
const dyCells = totalY < 0 ? Math.ceil(totalY) : Math.floor(totalY);
|
||||||
|
dragState.panRemainderX = totalX - dxCells;
|
||||||
|
dragState.panRemainderY = totalY - dyCells;
|
||||||
|
if (dxCells || dyCells) {
|
||||||
|
const base = dragState.pendingCamera || state.camera;
|
||||||
|
const nextCamera = clampCameraForView({
|
||||||
|
x: base.x - dxCells,
|
||||||
|
y: base.y - dyCells,
|
||||||
|
}, viewportSizeForZoom(state.zoom));
|
||||||
|
schedulePanRedraw(nextCamera);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (dragState.mode === "select") {
|
} else if (dragState.mode === "select") {
|
||||||
dragState.selectEnd = clampCanvasPoint(event);
|
dragState.selectEnd = clampCanvasPoint(event);
|
||||||
if (!dragState.selectPath || Math.hypot(dragState.selectEnd.x - dragState.selectPath[dragState.selectPath.length - 1].x, dragState.selectEnd.y - dragState.selectPath[dragState.selectPath.length - 1].y) >= 3) {
|
if (!dragState.selectPath || Math.hypot(dragState.selectEnd.x - dragState.selectPath[dragState.selectPath.length - 1].x, dragState.selectEnd.y - dragState.selectPath[dragState.selectPath.length - 1].y) >= 3) {
|
||||||
|
|
@ -954,17 +1006,22 @@ function redraw(options = {}) {
|
||||||
const viewSize = syncViewportSize();
|
const viewSize = syncViewportSize();
|
||||||
const expansion = options.allowWorldExpand === false ? null : ensureWorldPaddingForCamera(state.world, state.camera, viewSize.width, viewSize.height);
|
const expansion = options.allowWorldExpand === false ? null : ensureWorldPaddingForCamera(state.world, state.camera, viewSize.width, viewSize.height);
|
||||||
if (expansion?.expanded) {
|
if (expansion?.expanded) {
|
||||||
state.camera = { x: (state.camera?.x || 0) + (expansion.dx || 0), y: (state.camera?.y || 0) + (expansion.dy || 0) };
|
const ex = expansion.dx || 0;
|
||||||
|
const ey = expansion.dy || 0;
|
||||||
|
state.camera = { x: (state.camera?.x || 0) + ex, y: (state.camera?.y || 0) + ey };
|
||||||
|
if (dragState.mode === "pan") {
|
||||||
|
dragState.startCameraX += ex;
|
||||||
|
dragState.startCameraY += ey;
|
||||||
|
if (dragState.pendingCamera) dragState.pendingCamera = { x: dragState.pendingCamera.x + ex, y: dragState.pendingCamera.y + ey };
|
||||||
|
}
|
||||||
if (state.selectionRect) {
|
if (state.selectionRect) {
|
||||||
const dx = expansion.dx || 0;
|
|
||||||
const dy = expansion.dy || 0;
|
|
||||||
state.selectionRect = {
|
state.selectionRect = {
|
||||||
...state.selectionRect,
|
...state.selectionRect,
|
||||||
x0: state.selectionRect.x0 + dx,
|
x0: state.selectionRect.x0 + ex,
|
||||||
y0: state.selectionRect.y0 + dy,
|
y0: state.selectionRect.y0 + ey,
|
||||||
x1: state.selectionRect.x1 + dx,
|
x1: state.selectionRect.x1 + ex,
|
||||||
y1: state.selectionRect.y1 + dy,
|
y1: state.selectionRect.y1 + ey,
|
||||||
polygon: Array.isArray(state.selectionRect.polygon) ? state.selectionRect.polygon.map((p) => ({ x: p.x + dx, y: p.y + dy })) : state.selectionRect.polygon,
|
polygon: Array.isArray(state.selectionRect.polygon) ? state.selectionRect.polygon.map((p) => ({ x: p.x + ex, y: p.y + ey })) : state.selectionRect.polygon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
<option value="tohoku_spine">Tohoku spine</option>
|
<option value="tohoku_spine">Tohoku spine</option>
|
||||||
<option value="chubu_mountain">Chubu mountain</option>
|
<option value="chubu_mountain">Chubu mountain</option>
|
||||||
<option value="setouchi_inland_sea">Setouchi inland sea</option>
|
<option value="setouchi_inland_sea">Setouchi inland sea</option>
|
||||||
<option value="oceanic_archipelago">Oceanic archipelago</option>
|
<option value="oceanic_archipelago">Ocean</option>
|
||||||
<option value="kanto_alluvial">Kanto alluvial plain</option>
|
<option value="kanto_alluvial">Kanto alluvial plain</option>
|
||||||
<option value="mixed_archipelago">Mixed archipelago</option>
|
<option value="mixed_archipelago">Mixed archipelago</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
<option value="tohoku_spine">Tohoku spine</option>
|
<option value="tohoku_spine">Tohoku spine</option>
|
||||||
<option value="chubu_mountain">Chubu mountain</option>
|
<option value="chubu_mountain">Chubu mountain</option>
|
||||||
<option value="setouchi_inland_sea">Setouchi inland sea</option>
|
<option value="setouchi_inland_sea">Setouchi inland sea</option>
|
||||||
<option value="oceanic_archipelago">Oceanic archipelago</option>
|
<option value="oceanic_archipelago">Ocean</option>
|
||||||
<option value="kanto_alluvial">Kanto alluvial plain</option>
|
<option value="kanto_alluvial">Kanto alluvial plain</option>
|
||||||
<option value="mixed_archipelago">Mixed archipelago</option>
|
<option value="mixed_archipelago">Mixed archipelago</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
1038
mapPatch.js
1038
mapPatch.js
File diff suppressed because it is too large
Load diff
2660
mapPatch.js.bak
Normal file
2660
mapPatch.js.bak
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -209,15 +209,16 @@ const TERRAIN_TYPES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "oceanic_archipelago",
|
id: "oceanic_archipelago",
|
||||||
label: "海洋型・多島海",
|
label: "Ocean",
|
||||||
weight: 0.16,
|
weight: 0.00,
|
||||||
|
autoSelectable: false,
|
||||||
coastStyle: "oceanic_archipelago",
|
coastStyle: "oceanic_archipelago",
|
||||||
mountainMode: "mixed",
|
mountainMode: "mixed",
|
||||||
massifnessRange: [0.04, 0.28],
|
massifnessRange: [0.04, 0.28],
|
||||||
seaRatioRange: [0.72, 0.90],
|
seaRatioRange: [0.955, 0.985],
|
||||||
twoSidedChance: 1.0,
|
twoSidedChance: 1.0,
|
||||||
mountainOffsetRange: [0.25, 0.55],
|
mountainOffsetRange: [0.25, 0.55],
|
||||||
baseHeightRange: [0.30, 0.62],
|
baseHeightRange: [0.20, 0.46],
|
||||||
primaryLengthRange: [0.20, 0.52],
|
primaryLengthRange: [0.20, 0.52],
|
||||||
primaryWidthRange: [0.08, 0.24],
|
primaryWidthRange: [0.08, 0.24],
|
||||||
systemCountRange: [7, 14],
|
systemCountRange: [7, 14],
|
||||||
|
|
@ -227,7 +228,7 @@ const TERRAIN_TYPES = [
|
||||||
lengthScale: 0.78,
|
lengthScale: 0.78,
|
||||||
widthScale: 0.82,
|
widthScale: 0.82,
|
||||||
heightScale: 0.58,
|
heightScale: 0.58,
|
||||||
coastStrength: 1.55,
|
coastStrength: 2.10,
|
||||||
plainBiasRange: [0.12, 0.34],
|
plainBiasRange: [0.12, 0.34],
|
||||||
riverRichnessRange: [0.18, 0.52],
|
riverRichnessRange: [0.18, 0.52],
|
||||||
bigRiverChanceRange: [0.02, 0.12],
|
bigRiverChanceRange: [0.02, 0.12],
|
||||||
|
|
@ -315,11 +316,11 @@ function pickTerrainType(seed, requestedType = "auto") {
|
||||||
const selected = TERRAIN_TYPES.find((type) => type.id === normalizedType);
|
const selected = TERRAIN_TYPES.find((type) => type.id === normalizedType);
|
||||||
if (selected) return selected;
|
if (selected) return selected;
|
||||||
}
|
}
|
||||||
// Terrain type selection is intentionally uniform. Individual terrain
|
// Auto excludes explicitly manual/special-purpose templates such as Ocean.
|
||||||
// templates still contain their own parameter ranges, but there is no
|
// The selection remains uniform over ordinary land-bearing templates.
|
||||||
// terrain-type appearance weighting.
|
const autoTypes = TERRAIN_TYPES.filter((type) => type.autoSelectable !== false);
|
||||||
const index = Math.floor(rand(seed, 10001) * TERRAIN_TYPES.length) % TERRAIN_TYPES.length;
|
const index = Math.floor(rand(seed, 10001) * autoTypes.length) % autoTypes.length;
|
||||||
return TERRAIN_TYPES[index];
|
return autoTypes[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
function rangeValue(seed, salt, [lo, hi]) {
|
function rangeValue(seed, salt, [lo, hi]) {
|
||||||
|
|
@ -1174,8 +1175,8 @@ function enforceLandGradient(elevation, sea, seaLevel) {
|
||||||
function rectTerrainProfile(template) {
|
function rectTerrainProfile(template) {
|
||||||
const id = String(template?.terrainType || "auto");
|
const id = String(template?.terrainType || "auto");
|
||||||
if (id.includes("oceanic")) return {
|
if (id.includes("oceanic")) return {
|
||||||
base: 0.36, relief: 0.19, ridge: 0.30, ridgeWidth: 22, ridgeSpacing: 76, coast: 0.34, archipelago: 0.28,
|
base: 0.28, relief: 0.11, ridge: 0.20, ridgeWidth: 18, ridgeSpacing: 70, coast: 0.52, archipelago: 0.18,
|
||||||
seaQuantile: Math.max(0.68, template.seaRatio ?? 0.76), plain: 0.20, moisture: 0.60, capStart: 0.64, capMax: 0.86,
|
seaQuantile: Math.max(0.95, template.seaRatio ?? 0.965), plain: 0.12, moisture: 0.66, capStart: 0.56, capMax: 0.74,
|
||||||
};
|
};
|
||||||
if (id.includes("setouchi") || id.includes("archipelago")) return {
|
if (id.includes("setouchi") || id.includes("archipelago")) return {
|
||||||
base: 0.43, relief: 0.18, ridge: 0.34, ridgeWidth: 30, ridgeSpacing: 94, coast: 0.25, archipelago: 0.20,
|
base: 0.43, relief: 0.18, ridge: 0.34, ridgeWidth: 30, ridgeSpacing: 94, coast: 0.25, archipelago: 0.20,
|
||||||
|
|
@ -1697,7 +1698,11 @@ export function generateTerrainRect(options = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seaLevel = clamp(Number.isFinite(options.seaLevel) ? options.seaLevel : rectQuantile(elevation, profile.seaQuantile), 0.13, 0.50);
|
const seaLevel = clamp(
|
||||||
|
Number.isFinite(options.seaLevel) ? options.seaLevel : rectQuantile(elevation, profile.seaQuantile),
|
||||||
|
0.13,
|
||||||
|
terrainTemplate.terrainType === "oceanic_archipelago" ? 0.72 : 0.50
|
||||||
|
);
|
||||||
const oceanCells = classifyRectWater(ctx, fields, seaLevel);
|
const oceanCells = classifyRectWater(ctx, fields, seaLevel);
|
||||||
recomputeRectSlope(ctx, fields);
|
recomputeRectSlope(ctx, fields);
|
||||||
const filled = priorityFloodRect(ctx, fields);
|
const filled = priorityFloodRect(ctx, fields);
|
||||||
|
|
@ -1922,7 +1927,7 @@ export function generateTerrainAndRivers(seed, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let seaLevel = quantile(elevation, terrainTemplate.seaRatio);
|
let seaLevel = quantile(elevation, terrainTemplate.seaRatio);
|
||||||
seaLevel = clamp(seaLevel, 0.14, 0.47);
|
seaLevel = clamp(seaLevel, 0.14, terrainTemplate.terrainType === "oceanic_archipelago" ? 0.72 : 0.47);
|
||||||
classifyWater(elevation, seaLevel, sea, ocean, lake);
|
classifyWater(elevation, seaLevel, sea, ocean, lake);
|
||||||
recomputeSlope(elevation, sea, slope);
|
recomputeSlope(elevation, sea, slope);
|
||||||
|
|
||||||
|
|
|
||||||
55
renderer.js
55
renderer.js
|
|
@ -1,4 +1,4 @@
|
||||||
import { CELL_SIZE, MAP_H, MAP_W, clamp } from "./mapUtils.js";
|
import { CELL_SIZE, MAP_H, MAP_W, clamp, valueNoise } from "./mapUtils.js";
|
||||||
|
|
||||||
|
|
||||||
const segmentVectorCache = new WeakMap();
|
const segmentVectorCache = new WeakMap();
|
||||||
|
|
@ -296,6 +296,38 @@ function isWaterSample(map, fx, fy) {
|
||||||
return seaCoverageSample(map, fx, fy) >= 0.50;
|
return seaCoverageSample(map, fx, fy) >= 0.50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function terrainWorldPoint(map, fx, fy) {
|
||||||
|
return {
|
||||||
|
x: fx + (map?.worldCamera?.x || 0),
|
||||||
|
y: fy + (map?.worldCamera?.y || 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function waterVisualTone(map, fx, fy) {
|
||||||
|
const p = terrainWorldPoint(map, fx, fy);
|
||||||
|
const seed = (map?.seed || 0) >>> 0;
|
||||||
|
const broad = valueNoise(p.x, p.y, seed ^ 0x5d2a9b31, 74);
|
||||||
|
const mid = valueNoise(p.x, p.y, seed ^ 0x8f4c2d19, 29);
|
||||||
|
return clamp(broad * 0.72 + mid * 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
function waterVisualColor(map, fx, fy, waterCoverage = null) {
|
||||||
|
const coverage = waterCoverage ?? seaCoverageSample(map, fx, fy);
|
||||||
|
const tone = waterVisualTone(map, fx, fy) - 0.5;
|
||||||
|
const offshore = clamp((coverage - 0.42) / 0.58);
|
||||||
|
const depth = clamp(0.48 + offshore * 0.22 + tone * 0.10);
|
||||||
|
return [
|
||||||
|
Math.round(174 - depth * 6),
|
||||||
|
Math.round(203 + depth * 2),
|
||||||
|
Math.round(224 + depth * 4),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function waterVisualShade(map, fx, fy) {
|
||||||
|
const tone = waterVisualTone(map, fx * 0.85 + 11.7, fy * 0.85 - 3.1) - 0.5;
|
||||||
|
return clamp(0.992 + tone * 0.018, 0.976, 1.012);
|
||||||
|
}
|
||||||
|
|
||||||
function mixRgb(a, b, t) {
|
function mixRgb(a, b, t) {
|
||||||
return [
|
return [
|
||||||
Math.round(a[0] + (b[0] - a[0]) * t),
|
Math.round(a[0] + (b[0] - a[0]) * t),
|
||||||
|
|
@ -363,8 +395,10 @@ function terrainColorContinuous(map, fx, fy, mode) {
|
||||||
let color;
|
let color;
|
||||||
|
|
||||||
const waterCoverage = seaCoverageSample(map, fx, fy);
|
const waterCoverage = seaCoverageSample(map, fx, fy);
|
||||||
const depth = clamp(((map.seaLevel ?? 0.285) + 0.065 - fieldSample(map, map.elevation, fx, fy)) * 2.4);
|
// Water must not inherit terrain DEM hill bands. Use a stable world-coordinate
|
||||||
const waterColor = [Math.round(174 - depth * 7), Math.round(203 + depth * 2), Math.round(224 + depth * 5)];
|
// visual water tone instead of elevation-derived depth; otherwise patched sea
|
||||||
|
// areas expose rectangular/horizontal generation artefacts.
|
||||||
|
const waterColor = waterVisualColor(map, fx, fy, waterCoverage);
|
||||||
|
|
||||||
let landColor;
|
let landColor;
|
||||||
if (mode === "development") {
|
if (mode === "development") {
|
||||||
|
|
@ -405,6 +439,9 @@ function terrainColorContinuous(map, fx, fy, mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function terrainShadeContinuous(map, fx, fy) {
|
function terrainShadeContinuous(map, fx, fy) {
|
||||||
|
const waterCoverage = seaCoverageSample(map, fx, fy);
|
||||||
|
if (waterCoverage >= 0.50) return waterVisualShade(map, fx, fy);
|
||||||
|
|
||||||
const step = 0.50;
|
const step = 0.50;
|
||||||
const eC = fieldSample(map, map.elevation, fx, fy);
|
const eC = fieldSample(map, map.elevation, fx, fy);
|
||||||
const eL = fieldSample(map, map.elevation, fx - step, fy);
|
const eL = fieldSample(map, map.elevation, fx - step, fy);
|
||||||
|
|
@ -589,6 +626,14 @@ function getRasterBorderSegments(map, fieldName) {
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDisplayBorderSegments(map, fieldName, precomputedSegments, mode) {
|
||||||
|
// Raw raster borders expose every one-cell ID fragment and also reveal patch
|
||||||
|
// ownership seams as long rectangular prefecture/municipality lines. Keep
|
||||||
|
// that extractor debug-only; normal modes must use prepared vector layers.
|
||||||
|
if (mode === "borders-debug") return getRasterBorderSegments(map, fieldName);
|
||||||
|
return Array.isArray(precomputedSegments) ? precomputedSegments : [];
|
||||||
|
}
|
||||||
|
|
||||||
function drawRiverPath(ctx, map, path, color, widthFn, alpha = 1) {
|
function drawRiverPath(ctx, map, path, color, widthFn, alpha = 1) {
|
||||||
if (!path || path.length < 2) return;
|
if (!path || path.length < 2) return;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
@ -1067,8 +1112,8 @@ export function drawMap(canvas, map, options) {
|
||||||
const showPrefectureRegions = ["all", "admin", "borders-debug"].includes(mode);
|
const showPrefectureRegions = ["all", "admin", "borders-debug"].includes(mode);
|
||||||
if (showPrefectureRegions) drawPrefectureRegionFill(ctx, map, mode);
|
if (showPrefectureRegions) drawPrefectureRegionFill(ctx, map, mode);
|
||||||
|
|
||||||
const adminBorderSegments = map.adminId ? getRasterBorderSegments(map, "adminId") : map.adminBorders;
|
const adminBorderSegments = getDisplayBorderSegments(map, "adminId", map.adminBorders, mode);
|
||||||
const prefectureBorderSegments = map.prefectureRegionId ? getRasterBorderSegments(map, "prefectureRegionId") : map.regionalPrefectureBorders;
|
const prefectureBorderSegments = getDisplayBorderSegments(map, "prefectureRegionId", map.regionalPrefectureBorders, mode);
|
||||||
if (showAdmin && adminBorderSegments?.length) {
|
if (showAdmin && adminBorderSegments?.length) {
|
||||||
drawVectorSegments(ctx, adminBorderSegments, "rgba(255, 255, 255, 0.8)", 2.8, false, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
drawVectorSegments(ctx, adminBorderSegments, "rgba(255, 255, 255, 0.8)", 2.8, false, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
||||||
drawVectorSegments(ctx, adminBorderSegments, "rgba(150, 140, 150, 0.9)", 1.2, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
drawVectorSegments(ctx, adminBorderSegments, "rgba(150, 140, 150, 0.9)", 1.2, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ code{background:#e8e8e8;border-radius:4px;padding:1px 4px}
|
||||||
.progress-timing-row{display:flex;justify-content:space-between;gap:16px;border-top:1px solid rgba(0,0,0,0.06);padding-top:4px}
|
.progress-timing-row{display:flex;justify-content:space-between;gap:16px;border-top:1px solid rgba(0,0,0,0.06);padding-top:4px}
|
||||||
|
|
||||||
.canvas-shell.panning{cursor:grabbing}
|
.canvas-shell.panning{cursor:grabbing}
|
||||||
.map-selection-svg{position:absolute;inset:12px;z-index:18;display:none;pointer-events:none;overflow:visible}
|
.map-selection-svg{position:absolute;left:0;top:0;width:0;height:0;z-index:18;display:none;pointer-events:none;overflow:visible}
|
||||||
.map-selection-svg polygon{fill:rgba(26,115,232,0.16);stroke:rgba(26,115,232,0.88);stroke-width:2;vector-effect:non-scaling-stroke;stroke-linejoin:round}
|
.map-selection-svg polygon{fill:rgba(26,115,232,0.16);stroke:rgba(26,115,232,0.88);stroke-width:2;vector-effect:non-scaling-stroke;stroke-linejoin:round}
|
||||||
.map-selection-svg.invalid polygon{fill:rgba(179,38,30,0.14);stroke:rgba(179,38,30,0.88)}
|
.map-selection-svg.invalid polygon{fill:rgba(179,38,30,0.14);stroke:rgba(179,38,30,0.88)}
|
||||||
.canvas-shell.selecting{cursor:crosshair}
|
.canvas-shell.selecting{cursor:crosshair}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue