Spaces:
Runtime error
Runtime error
File size: 4,808 Bytes
dd96d2f 29a88f8 dd96d2f 29a88f8 dd96d2f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | """
Compile walkable polygons into a single figure: one exterior boundary + holes (internal non-walkable zones).
Produces compiled_map.json used by pathfinding for walkable test and optional nav points.
"""
from __future__ import annotations
import json
from pathlib import Path
def _signed_area(polygon: list[tuple[float, float]]) -> float:
"""Signed area (positive = CCW)."""
n = len(polygon)
if n < 3:
return 0.0
area = 0.0
for i in range(n):
j = (i + 1) % n
area += polygon[i][0] * polygon[j][1]
area -= polygon[j][0] * polygon[i][1]
return area / 2.0
def _centroid(polygon: list[tuple[float, float]]) -> tuple[float, float]:
n = len(polygon)
if n == 0:
return (0.0, 0.0)
cx = sum(p[0] for p in polygon) / n
cy = sum(p[1] for p in polygon) / n
return (cx, cy)
def _point_in_polygon(x: float, y: float, polygon: list[tuple[float, float]]) -> bool:
n = len(polygon)
inside = False
j = n - 1
for i in range(n):
xi, yi = polygon[i]
xj, yj = polygon[j]
if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi):
inside = not inside
j = i
return inside
def compile_walkable(
polygons: list[list[tuple[float, float]]],
) -> tuple[list[tuple[float, float]], list[list[tuple[float, float]]]]:
"""
From a list of polygons (each list of (x,y) in 0-100 space),
return (exterior, holes).
Exterior = polygon with largest absolute area.
Holes = polygons whose centroid is inside the exterior.
"""
if not polygons:
return ([], [])
# Filter to valid polygons
valid = [p for p in polygons if len(p) >= 3]
if not valid:
return ([], [])
# Exterior = largest area
by_area = [(abs(_signed_area(p)), p) for p in valid]
by_area.sort(key=lambda x: -x[0])
exterior = by_area[0][1]
others = [p for _, p in by_area[1:]]
holes: list[list[tuple[float, float]]] = []
for poly in others:
cx, cy = _centroid(poly)
if _point_in_polygon(cx, cy, exterior):
holes.append(poly)
return (exterior, holes)
def build_nav_points(
polygons: list[list[tuple[float, float]]],
scale_x: float,
scale_y: float,
step: float = 2.0,
) -> list[list[float]]:
"""
Generate navigation points (in game coordinates) for ALL walkable polygons.
Used for pathfinding graph: units can path between these points.
"""
if not polygons:
return []
all_xs = [p[0] for poly in polygons for p in poly]
all_ys = [p[1] for poly in polygons for p in poly]
min_x, max_x = min(all_xs), max(all_xs)
min_y, max_y = min(all_ys), max(all_ys)
def is_walkable_100(x: float, y: float) -> bool:
return any(_point_in_polygon(x, y, poly) for poly in polygons)
points: list[list[float]] = []
x = min_x
while x <= max_x:
y = min_y
while y <= max_y:
if is_walkable_100(x, y):
points.append([x * scale_x / 100.0, y * scale_y / 100.0])
y += step
x += step
return points
def run_compiler(
walkable_path: Path,
output_path: Path,
map_width: float = 80.0,
map_height: float = 80.0,
nav_step: float = 1.5,
) -> None:
"""Load walkable.json, compile to exterior + holes, write compiled_map.json."""
scale_x = map_width / 100.0
scale_y = map_height / 100.0
if not walkable_path.exists():
raise FileNotFoundError(f"Walkable file not found: {walkable_path}")
with open(walkable_path, encoding="utf-8") as f:
data = json.load(f)
raw = data.get("polygons", [])
if not raw and data.get("polygon"):
raw = [data["polygon"]]
polygons: list[list[tuple[float, float]]] = []
for poly in raw:
if len(poly) < 3:
continue
polygons.append([(float(p[0]), float(p[1])) for p in poly])
exterior, holes = compile_walkable(polygons)
nav_points_game = build_nav_points(polygons, map_width, map_height, step=nav_step)
out = {
"exterior": [[round(x, 4), round(y, 4)] for x, y in exterior],
"holes": [[[round(x, 4), round(y, 4)] for x, y in h] for h in holes],
"nav_points": [[round(x, 4), round(y, 4)] for x, y in nav_points_game],
# All walkable zones in 0-100 space (for visualization and multi-zone support)
"walkable_zones": [[[round(p[0], 4), round(p[1], 4)] for p in poly] for poly in polygons],
}
with open(output_path, "w", encoding="utf-8") as f:
json.dump(out, f, indent=2)
if __name__ == "__main__":
static_dir = Path(__file__).resolve().parent.parent / "static"
run_compiler(
static_dir / "walkable.json",
static_dir / "compiled_map.json",
)
|