not good but not bad
This commit is contained in:
parent
5c82bfcab7
commit
4f0df3f6c5
11 changed files with 1284 additions and 622 deletions
|
|
@ -474,20 +474,21 @@ export function generateRegionalPrefectures(seed, sea, elevation, slope, river,
|
|||
}
|
||||
for (let i = 0; i < SIZE; i++) if (anchorMask[i] && !sea[i]) regionId[i] = 0;
|
||||
for (let pass = 0; pass < 4; pass++) repairRegionalTopology(regionId, sea, seeded.centers, anchorMask, 260);
|
||||
|
||||
const displayRegionId = new Int16Array(beforeRegionId);
|
||||
for (let pass = 0; pass < 3; pass++) repairRegionalTopology(displayRegionId, sea, seeded.centers, anchorMask, 200);
|
||||
repairDisconnectedRegionalPrefectures(regionId, sea, seeded.centers, anchorMask);
|
||||
mergeTinyRegionalPrefectures(regionId, sea, seeded.centers, anchorMask, 720);
|
||||
rebalanceOversizedRegionalPrefectures(regionId, sea, seeded.centers, anchorMask, naturalBarrierScore);
|
||||
snapRegionalBoundariesToNaturalFeatures(regionId, sea, anchorMask, naturalBarrierScore, 2);
|
||||
repairFinalRegionalTopology(regionId, sea, seeded.centers, anchorMask, naturalBarrierScore);
|
||||
|
||||
let changed = 0;
|
||||
for (let i = 0; i < SIZE; i++) if (!sea[i] && beforeRegionId[i] !== regionId[i]) changed++;
|
||||
const afterBorderCount = countRegionBorderEdges(regionId, sea);
|
||||
const measuredAfterNaturalAverage = averageRegionBorderBarrier(regionId, sea, naturalBarrierScore);
|
||||
const afterNaturalAverage = Math.max(measuredAfterNaturalAverage, beforeNaturalAverage);
|
||||
const afterNaturalAverage = measuredAfterNaturalAverage;
|
||||
const afterVoronoiLikeRate = regionalVoronoiLikeRate(regionId, seeded.centers, sea, naturalBarrierScore);
|
||||
|
||||
return {
|
||||
regionId,
|
||||
displayRegionId,
|
||||
centers: seeded.centers,
|
||||
naturalBarrierScore,
|
||||
debug: {
|
||||
|
|
@ -498,13 +499,16 @@ export function generateRegionalPrefectures(seed, sea, elevation, slope, river,
|
|||
regionalVoronoiLikeRateAfter: afterVoronoiLikeRate,
|
||||
regionalNaturalBarrierAverageBefore: beforeNaturalAverage,
|
||||
regionalNaturalBarrierAverageAfter: afterNaturalAverage,
|
||||
regionalDisplayBorderCount: countRegionBorderEdges(displayRegionId, sea),
|
||||
regionalDisplayNaturalBarrierAverage: averageRegionBorderBarrier(displayRegionId, sea, naturalBarrierScore),
|
||||
regionalDisplayBorderCount: countRegionBorderEdges(regionId, sea),
|
||||
regionalDisplayNaturalBarrierAverage: averageRegionBorderBarrier(regionId, sea, naturalBarrierScore),
|
||||
regionalCompartmentCount: compartments.filter((unit) => unit.area > 0).length,
|
||||
compartmentCount: compartments.filter((unit) => unit.area > 0).length,
|
||||
changedAfterCompartmentAssignment: changed,
|
||||
borderNaturalBarrierAverage: afterNaturalAverage,
|
||||
voronoiLikeRate: afterVoronoiLikeRate,
|
||||
finalRegionConnectivityMaxComponents: Math.max(0, ...[...new Set([...regionId].filter((id, i) => id >= 0 && !sea[i]))].map((id) => collectRegionComponents(regionId, sea, id).length)),
|
||||
finalRegionalEnclaveCount: countRegionalEnclaves(regionId, sea),
|
||||
finalRegionalMaxAreaShare: maxRegionalAreaShare(regionId, sea),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -537,9 +541,9 @@ function generateRegionalPrefecturesSeedGrowth(seed, sea, elevation, slope, rive
|
|||
}
|
||||
}
|
||||
centers.push(...pickEntities(candidates, {
|
||||
max: 9 + Math.floor(rand(seed, 6101) * 6),
|
||||
minDistance: 22,
|
||||
threshold: 0.38,
|
||||
max: 6 + Math.floor(rand(seed, 6101) * 4),
|
||||
minDistance: 31,
|
||||
threshold: 0.42,
|
||||
seed: seed + 6102,
|
||||
jitter: 0.02,
|
||||
}));
|
||||
|
|
@ -583,6 +587,357 @@ function generateRegionalPrefecturesSeedGrowth(seed, sea, elevation, slope, rive
|
|||
return { regionId, centers };
|
||||
}
|
||||
|
||||
function collectRegionComponents(regionId, sea, id) {
|
||||
const seen = new Uint8Array(SIZE);
|
||||
const components = [];
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (seen[i] || sea[i] || regionId[i] !== id) continue;
|
||||
const queue = [i];
|
||||
const cells = [];
|
||||
seen[i] = 1;
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
cells.push(cur);
|
||||
const [x, y] = xyOf(cur);
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (seen[ni] || sea[ni] || regionId[ni] !== id) continue;
|
||||
seen[ni] = 1;
|
||||
queue.push(ni);
|
||||
}
|
||||
}
|
||||
components.push(cells);
|
||||
}
|
||||
return components.sort((a, b) => b.length - a.length);
|
||||
}
|
||||
|
||||
function chooseRegionalReassignment(cells, regionId, sea, centers, forbiddenId = -1) {
|
||||
const adjacent = new Map();
|
||||
let sx = 0, sy = 0;
|
||||
for (const i of cells) {
|
||||
const [x, y] = xyOf(i);
|
||||
sx += x; sy += y;
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
const id = regionId[ni];
|
||||
if (sea[ni] || id < 0 || id === forbiddenId) continue;
|
||||
adjacent.set(id, (adjacent.get(id) || 0) + 1);
|
||||
}
|
||||
}
|
||||
let bestId = -1;
|
||||
let bestScore = -INF;
|
||||
const cx = sx / Math.max(1, cells.length);
|
||||
const cy = sy / Math.max(1, cells.length);
|
||||
for (const [id, edge] of adjacent) {
|
||||
const center = centers[id];
|
||||
const d = center ? Math.hypot(center.x - cx, center.y - cy) : 0;
|
||||
const score = edge * 4 - d * 0.04 + (id === 0 ? -1.5 : 0);
|
||||
if (score > bestScore) { bestScore = score; bestId = id; }
|
||||
}
|
||||
if (bestId >= 0) return bestId;
|
||||
for (let id = 0; id < centers.length; id++) {
|
||||
if (id === forbiddenId || !centers[id]) continue;
|
||||
const d = Math.hypot(centers[id].x - cx, centers[id].y - cy);
|
||||
const score = -d + (id === 0 ? -8 : 0);
|
||||
if (score > bestScore) { bestScore = score; bestId = id; }
|
||||
}
|
||||
return bestId;
|
||||
}
|
||||
|
||||
function nearestRegionalReassignmentByLand(cells, regionId, sea, forbiddenId = -1) {
|
||||
const seen = new Uint8Array(SIZE);
|
||||
const queue = [];
|
||||
for (const i of cells) {
|
||||
seen[i] = 1;
|
||||
queue.push(i);
|
||||
}
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
const [x, y] = xyOf(cur);
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni] || seen[ni]) continue;
|
||||
const id = regionId[ni];
|
||||
if (id >= 0 && id !== forbiddenId) return id;
|
||||
seen[ni] = 1;
|
||||
queue.push(ni);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function repairDisconnectedRegionalPrefectures(regionId, sea, centers, anchorMask) {
|
||||
let totalChanged = 0;
|
||||
for (let pass = 0; pass < 10; pass++) {
|
||||
let changedThisPass = 0;
|
||||
const ids = [...new Set([...regionId].filter((id, i) => id >= 0 && !sea[i]))].sort((a, b) => a - b);
|
||||
for (const id of ids) {
|
||||
const components = collectRegionComponents(regionId, sea, id);
|
||||
if (components.length <= 1) continue;
|
||||
let keepIndex = 0;
|
||||
if (id === 0) {
|
||||
const anchorIndex = components.findIndex((cells) => cells.some((i) => anchorMask[i]));
|
||||
if (anchorIndex >= 0) keepIndex = anchorIndex;
|
||||
}
|
||||
for (let c = 0; c < components.length; c++) {
|
||||
if (c === keepIndex) continue;
|
||||
const replacement =
|
||||
chooseRegionalReassignment(components[c], regionId, sea, centers, id) ??
|
||||
nearestRegionalReassignmentByLand(components[c], regionId, sea, id);
|
||||
const target = replacement >= 0 ? replacement : nearestRegionalReassignmentByLand(components[c], regionId, sea, id);
|
||||
if (target < 0) continue;
|
||||
for (const i of components[c]) {
|
||||
if (anchorMask[i]) continue;
|
||||
regionId[i] = target;
|
||||
changedThisPass++;
|
||||
}
|
||||
}
|
||||
}
|
||||
totalChanged += changedThisPass;
|
||||
for (let i = 0; i < SIZE; i++) if (anchorMask[i] && !sea[i]) regionId[i] = 0;
|
||||
if (changedThisPass === 0) break;
|
||||
}
|
||||
return totalChanged;
|
||||
}
|
||||
|
||||
function componentTouchesOutside(cells, sea) {
|
||||
for (const i of cells) {
|
||||
const [x, y] = xyOf(i);
|
||||
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) return true;
|
||||
for (const [nx, ny] of neighbors4(x, y)) if (sea[indexOf(nx, ny)]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function boundaryNeighborIds(cells, regionId, sea, ownId) {
|
||||
const ids = new Set();
|
||||
for (const i of cells) {
|
||||
const [x, y] = xyOf(i);
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
const id = regionId[ni];
|
||||
if (!sea[ni] && id >= 0 && id !== ownId) ids.add(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function countRegionalEnclaves(regionId, sea) {
|
||||
let count = 0;
|
||||
const ids = [...new Set([...regionId].filter((id, i) => id >= 0 && !sea[i]))].sort((a, b) => a - b);
|
||||
for (const id of ids) {
|
||||
for (const cells of collectRegionComponents(regionId, sea, id)) {
|
||||
if (componentTouchesOutside(cells, sea)) continue;
|
||||
if (boundaryNeighborIds(cells, regionId, sea, id).size === 1) count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function carveRegionalCorridor(regionId, sea, cells, ownId, enclosingId, naturalBarrierScore) {
|
||||
const best = new Float32Array(SIZE);
|
||||
const cameFrom = new Int32Array(SIZE);
|
||||
best.fill(INF);
|
||||
cameFrom.fill(-1);
|
||||
const heap = new MinHeap();
|
||||
const source = new Uint8Array(SIZE);
|
||||
for (const i of cells) {
|
||||
source[i] = 1;
|
||||
best[i] = 0;
|
||||
heap.push({ i, f: 0 });
|
||||
}
|
||||
let target = -1;
|
||||
while (heap.length) {
|
||||
const cur = heap.pop();
|
||||
if (!cur || cur.f > best[cur.i] + 1e-5) continue;
|
||||
const [x, y] = xyOf(cur.i);
|
||||
if (!source[cur.i]) {
|
||||
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) { target = cur.i; break; }
|
||||
let seaAdjacent = false;
|
||||
let otherAdjacent = false;
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni]) seaAdjacent = true;
|
||||
else if (regionId[ni] >= 0 && regionId[ni] !== ownId && regionId[ni] !== enclosingId) otherAdjacent = true;
|
||||
}
|
||||
if (seaAdjacent || otherAdjacent) { target = cur.i; break; }
|
||||
}
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni] || regionId[ni] === ownId) continue;
|
||||
const id = regionId[ni];
|
||||
const regionPenalty = id === enclosingId ? 0 : 8;
|
||||
const barrier = naturalBarrierScore?.[ni] || 0;
|
||||
const nd = cur.f + 1 + barrier * 3.2 + regionPenalty;
|
||||
if (nd < best[ni]) {
|
||||
best[ni] = nd;
|
||||
cameFrom[ni] = cur.i;
|
||||
heap.push({ i: ni, f: nd });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target < 0) return 0;
|
||||
let changed = 0;
|
||||
for (let i = target; i >= 0 && regionId[i] !== ownId; i = cameFrom[i]) {
|
||||
if (!sea[i] && regionId[i] !== ownId) {
|
||||
regionId[i] = ownId;
|
||||
changed++;
|
||||
}
|
||||
if (cameFrom[i] < 0) break;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function repairRegionalEnclaves(regionId, sea, centers, anchorMask, naturalBarrierScore) {
|
||||
let changed = 0;
|
||||
const ids = [...new Set([...regionId].filter((id, i) => id >= 0 && !sea[i]))].sort((a, b) => a - b);
|
||||
for (const id of ids) {
|
||||
const components = collectRegionComponents(regionId, sea, id);
|
||||
for (const cells of components) {
|
||||
if (componentTouchesOutside(cells, sea)) continue;
|
||||
const neighbors = boundaryNeighborIds(cells, regionId, sea, id);
|
||||
if (neighbors.size !== 1) continue;
|
||||
const enclosingId = [...neighbors][0];
|
||||
const protectedAnchor = id === 0 && cells.some((i) => anchorMask[i]);
|
||||
if (protectedAnchor) {
|
||||
changed += carveRegionalCorridor(regionId, sea, cells, id, enclosingId, naturalBarrierScore);
|
||||
} else {
|
||||
for (const i of cells) {
|
||||
if (anchorMask[i]) continue;
|
||||
regionId[i] = enclosingId;
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < SIZE; i++) if (anchorMask[i] && !sea[i]) regionId[i] = 0;
|
||||
return changed;
|
||||
}
|
||||
|
||||
function repairFinalRegionalTopology(regionId, sea, centers, anchorMask, naturalBarrierScore) {
|
||||
let totalChanged = 0;
|
||||
for (let pass = 0; pass < 12; pass++) {
|
||||
const disconnected = repairDisconnectedRegionalPrefectures(regionId, sea, centers, anchorMask);
|
||||
const enclaves = repairRegionalEnclaves(regionId, sea, centers, anchorMask, naturalBarrierScore);
|
||||
totalChanged += disconnected + enclaves;
|
||||
if (disconnected + enclaves === 0) break;
|
||||
}
|
||||
return totalChanged;
|
||||
}
|
||||
|
||||
function regionalAreaById(regionId, sea) {
|
||||
const area = new Map();
|
||||
for (let i = 0; i < SIZE; i++) if (!sea[i] && regionId[i] >= 0) area.set(regionId[i], (area.get(regionId[i]) || 0) + 1);
|
||||
return area;
|
||||
}
|
||||
|
||||
function maxRegionalAreaShare(regionId, sea) {
|
||||
const area = regionalAreaById(regionId, sea);
|
||||
const land = [...area.values()].reduce((sum, value) => sum + value, 0);
|
||||
return land ? Math.max(0, ...area.values()) / land : 0;
|
||||
}
|
||||
|
||||
function canMoveRegionalBoundaryCell(regionId, sea, i, ownId) {
|
||||
const [x, y] = xyOf(i);
|
||||
let ownNeighbors = 0;
|
||||
let otherNeighbors = 0;
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni]) continue;
|
||||
if (regionId[ni] === ownId) ownNeighbors++;
|
||||
else if (regionId[ni] >= 0) otherNeighbors++;
|
||||
}
|
||||
return ownNeighbors >= 2 && otherNeighbors > 0;
|
||||
}
|
||||
|
||||
function rebalanceOversizedRegionalPrefectures(regionId, sea, centers, anchorMask, naturalBarrierScore) {
|
||||
const minArea = 720;
|
||||
for (let pass = 0; pass < 4; pass++) {
|
||||
const area = regionalAreaById(regionId, sea);
|
||||
const land = [...area.values()].reduce((sum, value) => sum + value, 0);
|
||||
const maxArea = Math.max(minArea * 2, Math.floor(land * 0.32));
|
||||
let changed = 0;
|
||||
const oversized = [...area.entries()].filter(([, value]) => value > maxArea).sort((a, b) => b[1] - a[1]);
|
||||
for (const [id] of oversized) {
|
||||
const candidates = [];
|
||||
for (let y = 1; y < MAP_H - 1; y++) {
|
||||
for (let x = 1; x < MAP_W - 1; x++) {
|
||||
const i = indexOf(x, y);
|
||||
if (sea[i] || regionId[i] !== id || anchorMask[i] || !canMoveRegionalBoundaryCell(regionId, sea, i, id)) continue;
|
||||
const counts = new Map();
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
const other = regionId[ni];
|
||||
if (!sea[ni] && other >= 0 && other !== id && (area.get(other) || 0) < maxArea) counts.set(other, (counts.get(other) || 0) + 1);
|
||||
}
|
||||
for (const [other, edge] of counts) {
|
||||
const center = centers[other];
|
||||
const d = center ? Math.hypot(center.x - x, center.y - y) : 0;
|
||||
candidates.push({ i, other, score: edge * 2.0 + (naturalBarrierScore?.[i] || 0) * 1.4 - d * 0.006 });
|
||||
}
|
||||
}
|
||||
}
|
||||
candidates.sort((a, b) => b.score - a.score || a.i - b.i);
|
||||
for (const candidate of candidates) {
|
||||
if ((area.get(id) || 0) <= maxArea) break;
|
||||
if ((area.get(candidate.other) || 0) >= maxArea || regionId[candidate.i] !== id) continue;
|
||||
regionId[candidate.i] = candidate.other;
|
||||
area.set(id, (area.get(id) || 0) - 1);
|
||||
area.set(candidate.other, (area.get(candidate.other) || 0) + 1);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
repairDisconnectedRegionalPrefectures(regionId, sea, centers, anchorMask);
|
||||
if (!changed) break;
|
||||
}
|
||||
}
|
||||
|
||||
function snapRegionalBoundariesToNaturalFeatures(regionId, sea, anchorMask, naturalBarrierScore, passes = 2) {
|
||||
for (let pass = 0; pass < passes; pass++) {
|
||||
const before = new Int16Array(regionId);
|
||||
let changed = 0;
|
||||
for (let y = 1; y < MAP_H - 1; y++) {
|
||||
for (let x = 1; x < MAP_W - 1; x++) {
|
||||
const i = indexOf(x, y);
|
||||
const own = before[i];
|
||||
if (sea[i] || own < 0 || anchorMask[i]) continue;
|
||||
const hereBarrier = naturalBarrierScore?.[i] || 0;
|
||||
if (hereBarrier > 0.54 || !canMoveRegionalBoundaryCell(before, sea, i, own)) continue;
|
||||
const counts = new Map();
|
||||
let bestNeighborBarrier = 0;
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
const other = before[ni];
|
||||
if (sea[ni] || other < 0) continue;
|
||||
bestNeighborBarrier = Math.max(bestNeighborBarrier, naturalBarrierScore?.[ni] || 0);
|
||||
if (other !== own) counts.set(other, (counts.get(other) || 0) + 1);
|
||||
}
|
||||
if (counts.size === 0 || bestNeighborBarrier < hereBarrier + 0.18) continue;
|
||||
let target = -1, best = -1;
|
||||
for (const [other, count] of counts) if (count > best) { best = count; target = other; }
|
||||
if (target >= 0 && best >= 2) {
|
||||
regionId[i] = target;
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!changed) break;
|
||||
}
|
||||
}
|
||||
|
||||
function mergeTinyRegionalPrefectures(regionId, sea, centers, anchorMask, minArea) {
|
||||
const ids = [...new Set([...regionId].filter((id, i) => id >= 0 && !sea[i]))].sort((a, b) => a - b);
|
||||
for (const id of ids) {
|
||||
if (id === 0) continue;
|
||||
const cells = [];
|
||||
for (let i = 0; i < SIZE; i++) if (!sea[i] && regionId[i] === id) cells.push(i);
|
||||
if (cells.length === 0 || cells.length >= minArea) continue;
|
||||
const replacement = chooseRegionalReassignment(cells, regionId, sea, centers, id);
|
||||
if (replacement < 0) continue;
|
||||
for (const i of cells) if (!anchorMask[i]) regionId[i] = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
function buildRegionalNaturalBarrierScore(sea, elevation, slope, river, ridgeField, flowAccum) {
|
||||
const score = new Float32Array(SIZE);
|
||||
for (let y = 0; y < MAP_H; y++) {
|
||||
|
|
@ -811,20 +1166,50 @@ function repairRegionalTopology(regionId, sea, centers, anchorMask, maxIslandCel
|
|||
for (let i = 0; i < SIZE; i++) if (anchorMask[i] && !sea[i]) regionId[i] = 0;
|
||||
}
|
||||
|
||||
export function extractRegionBorderSegments(regionId, sea) {
|
||||
export function extractRegionBorderSegments(regionId, sea, options = {}) {
|
||||
const segments = [];
|
||||
const nameById = options.prefectureRegions
|
||||
? new Map(options.prefectureRegions.map((region) => [region.id, region.name]))
|
||||
: null;
|
||||
const fail = (message) => {
|
||||
if (options.throwOnInvalid) throw new Error(message);
|
||||
if (options.logInvalid) console.error(message);
|
||||
};
|
||||
for (let y = 0; y < MAP_H; y++) {
|
||||
for (let x = 0; x < MAP_W; x++) {
|
||||
const i = indexOf(x, y);
|
||||
if (sea[i] || regionId[i] < 0) continue;
|
||||
const a = regionId[i];
|
||||
if (x + 1 < MAP_W && !sea[indexOf(x + 1, y)]) {
|
||||
const b = regionId[indexOf(x + 1, y)];
|
||||
const ni = indexOf(x + 1, y);
|
||||
const b = regionId[ni];
|
||||
if (b >= 0 && a !== b) segments.push([[x + 1, y], [x + 1, y + 1]]);
|
||||
else if (options.validateSameId && b >= 0 && a === b) fail(`Invalid prefecture border candidate at (${x},${y})/(${x + 1},${y}): both id=${a}, name=${nameById?.get(a) || "-"}`);
|
||||
}
|
||||
if (y + 1 < MAP_H && !sea[indexOf(x, y + 1)]) {
|
||||
const b = regionId[indexOf(x, y + 1)];
|
||||
const ni = indexOf(x, y + 1);
|
||||
const b = regionId[ni];
|
||||
if (b >= 0 && a !== b) segments.push([[x, y + 1], [x + 1, y + 1]]);
|
||||
else if (options.validateSameId && b >= 0 && a === b) fail(`Invalid prefecture border candidate at (${x},${y})/(${x},${y + 1}): both id=${a}, name=${nameById?.get(a) || "-"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.throwOnInvalid) {
|
||||
for (const segment of segments) {
|
||||
const [[x1, y1], [x2, y2]] = segment;
|
||||
let ax = -1, ay = -1, bx = -1, by = -1;
|
||||
if (x1 === x2) {
|
||||
ax = x1 - 1; bx = x1; ay = by = Math.min(y1, y2);
|
||||
} else if (y1 === y2) {
|
||||
ax = bx = Math.min(x1, x2); ay = y1 - 1; by = y1;
|
||||
}
|
||||
if (!inside(ax, ay) || !inside(bx, by)) continue;
|
||||
const ai = indexOf(ax, ay);
|
||||
const bi = indexOf(bx, by);
|
||||
const a = regionId[ai];
|
||||
const b = regionId[bi];
|
||||
if (sea[ai] || sea[bi] || a < 0 || b < 0 || a === b) {
|
||||
throw new Error(`Invalid emitted prefecture border ${JSON.stringify(segment)} between (${ax},${ay}) id=${a} name=${nameById?.get(a) || "-"} and (${bx},${by}) id=${b} name=${nameById?.get(b) || "-"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue