v1.2.1: 修复飞地检测——按 dynmap even-odd 规则解析领土多边形,内圈孔洞(国中飞地边界)不再被并集填充掩盖

这个提交包含在:
lyizai
2026-08-19 13:09:42 +08:00
父节点 448dad6cc7
当前提交 c55e4eaf8b
共修改 4 个文件,包含 38 行新增和 23 行删除
@@ -269,9 +269,9 @@ public final class TerritoryDataService {
return;
}
Set chunks = claims.computeIfAbsent(name, ignored -> new HashSet());
for (List<int[]> polygon : TerritoryDataService.extractPolygons(marker.get("points"))) {
TerritoryDataService.fillPolygon(chunks, polygon);
}
// 一个 marker 的多个多边形遵循 dynmap 的 even-odd 填充规则:
// 外圈为正,内圈多边形(国中飞地的边界)作为孔洞被减去,而非作为独立领土并集填充。
TerritoryDataService.fillPolygonEvenOdd(chunks, TerritoryDataService.extractPolygons(marker.get("points")));
colors.putIfAbsent(name, TerritoryDataService.parseColor(TerritoryDataService.stringOr(marker, "color", "#75bd20")));
}
@@ -337,31 +337,46 @@ public final class TerritoryDataService {
}
/**
* 将多边形覆盖的区块填充进集合:
* 先算出多边形外接矩形(换算为区块坐标),再逐区块用"点在多边形内"算法判断,
* 以区块中心点(cx*16+8, cz*16+8)是否落在多边形内为准。
* 面积超过 600 万区块时直接放弃,防止异常数据导致卡死。
* 将多边形组覆盖的区块按 even-odd 规则填充进集合。
*
* <p>一个 marker 里的多个多边形遵循 dynmap 的奇偶填充:区块落入<b>奇数个</b>多边形内时才视为领土;
* 这样内圈多边形(国中飞地/孔洞的边界)会被正确减去,而不是像并集那样把孔洞也填成领土。</p>
*
* @param output 输出集合(该邦国的区块集合)
* @param polygons 该 marker 的全部多边形,第一个为外圈、可为多个内圈孔洞
*/
private static void fillPolygon(Set<ChunkPos> output, List<int[]> polygon) {
int area;
private static void fillPolygonEvenOdd(Set<ChunkPos> output, List<List<int[]>> polygons) {
if (polygons.isEmpty()) {
return;
}
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int minZ = Integer.MAX_VALUE;
int maxZ = Integer.MIN_VALUE;
for (int[] point : polygon) {
minX = Math.min(minX, Math.floorDiv(point[0], 16));
maxX = Math.max(maxX, Math.floorDiv(point[0], 16));
minZ = Math.min(minZ, Math.floorDiv(point[1], 16));
maxZ = Math.max(maxZ, Math.floorDiv(point[1], 16));
for (List<int[]> polygon : polygons) {
for (int[] point : polygon) {
minX = Math.min(minX, Math.floorDiv(point[0], 16));
maxX = Math.max(maxX, Math.floorDiv(point[0], 16));
minZ = Math.min(minZ, Math.floorDiv(point[1], 16));
maxZ = Math.max(maxZ, Math.floorDiv(point[1], 16));
}
}
int n = area = ((maxX - minX) + 1L) * ((maxZ - minZ) + 1L) > Integer.MAX_VALUE ? Integer.MAX_VALUE : (maxX - minX + 1) * (maxZ - minZ + 1);
if (area > 6000000) {
// 面积超过 600 万区块时直接放弃,防止异常数据导致卡死
if ((long)(maxX - minX + 1) * (maxZ - minZ + 1) > 6000000L) {
return;
}
for (int cx = minX; cx <= maxX; ++cx) {
for (int cz = minZ; cz <= maxZ; ++cz) {
if (!TerritoryDataService.pointInPolygon(cx * 16 + 8, cz * 16 + 8, polygon)) continue;
output.add(new ChunkPos(cx, cz));
// 奇偶填充:落在奇数个多边形内则为领土;否则(含恰好落在偶数个内圈孔洞中)忽略
boolean inside = false;
for (List<int[]> polygon : polygons) {
if (TerritoryDataService.pointInPolygon(cx * 16 + 8, cz * 16 + 8, polygon)) {
inside = !inside;
}
}
if (inside) {
output.add(new ChunkPos(cx, cz));
}
}
}
}