Files
gta6/tools/extract/leonida/emit.py
T

112 lines
4.5 KiB
Python

import json
from pathlib import Path
from shapely.geometry import Polygon, box
from .coords import CHUNK_SIZE, SLICE
N = (SLICE["x1"] - SLICE["x0"]) // CHUNK_SIZE # 12
def chunk_box(i: int, j: int) -> Polygon:
x0 = SLICE["x0"] + i * CHUNK_SIZE
y1 = SLICE["y1"] - j * CHUNK_SIZE
return box(x0, y1 - CHUNK_SIZE, x0 + CHUNK_SIZE, y1)
def split_by_chunk(poly: Polygon) -> dict[tuple[int, int], Polygon]:
out = {}
for j in range(N):
for i in range(N):
part = poly.intersection(chunk_box(i, j))
if not part.is_empty and part.area > 0.5:
out[(i, j)] = part
return out
def _rings(poly: Polygon, i: int, j: int) -> list[list[list[float]]]:
"""Aussenring zuerst, danach jedes Loch, in derselben Transformation.
Ruling 34: ein Loch (z.B. ein von Strassen umschlossener Block, ein Innenhof, ein Teich
im Park) ist genauso echte Flaeche wie der Aussenring und darf beim Zuschneiden nicht
verschwinden - sonst wird der Block zu Asphalt, der Innenhof zu Gebaeudemasse, der Teich
zu Wiese.
"""
ox = SLICE["x0"] + i * CHUNK_SIZE
oy = SLICE["y1"] - j * CHUNK_SIZE
# Welt (X, Y) -> three (x, z) -> relativ zur Chunk-Ecke
return [[[round(x - ox, 2), round(-y - (-oy), 2)] for x, y in ring.coords]
for ring in (poly.exterior, *poly.interiors)]
def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms, paths=()) -> dict:
ox = SLICE["x0"] + i * CHUNK_SIZE
oy = SLICE["y1"] - j * CHUNK_SIZE
return {
"chunk": [i, j],
"origin": [ox, -oy],
"land": [_rings(p, i, j) for p in land],
"surfaces": [{"poly": _rings(p, i, j), "kind": k} for p, k in surfaces],
"roads": [{"poly": _rings(p, i, j), "bridge": b} for p, b in roads],
"centerlines": [[[round(x - ox, 2), round(-y + oy, 2)] for x, y in ln.coords] for ln in centerlines],
"buildings": [{"poly": _rings(p, i, j), "height": h["height"],
"source": h["source"], "speculative": s} for p, h, s in buildings],
"palms": [[round(x - ox, 2), round(-y + oy, 2)] for x, y in palms],
# Parkwege (Flamingo Park, docs/superpowers/specs/2026-09-15-flamingo-park-design.md), aufgebaut wie roads.
"paths": [{"poly": _rings(p, i, j)} for p in paths],
}
def chunk_box_of(i: int, j: int) -> Polygon:
return chunk_box(i, j)
def split_by_chunk_all(polys, i: int, j: int):
"""Alle Anteile der übergebenen Polygone, die in Chunk (i, j) liegen."""
cell = chunk_box(i, j)
for poly in polys:
part = poly.intersection(cell)
if not part.is_empty and part.area > 0.5:
if part.geom_type == "Polygon":
yield part
else:
yield from (g for g in part.geoms if g.geom_type == "Polygon" and g.area > 0.5)
def in_chunk(point, i: int, j: int) -> bool:
x0 = SLICE["x0"] + i * CHUNK_SIZE
y1 = SLICE["y1"] - j * CHUNK_SIZE
return x0 <= point[0] < x0 + CHUNK_SIZE and y1 - CHUNK_SIZE <= point[1] < y1
def write_debug_png(out_dir: Path, mosaic, payloads: list[dict]) -> None:
"""Ein Bild je Chunk: die extrahierten Vektoren über der Karte, zur Sichtprüfung."""
from PIL import Image, ImageDraw
out_dir.mkdir(parents=True, exist_ok=True)
for payload in payloads:
i, j = payload["chunk"]
x0, y0 = i * CHUNK_SIZE, j * CHUNK_SIZE
crop = Image.fromarray(mosaic[y0:y0 + CHUNK_SIZE, x0:x0 + CHUNK_SIZE]).convert("RGB")
draw = ImageDraw.Draw(crop)
for rings in payload["land"]:
ring = rings[0] # nur der Aussenring; Loecher werden nicht gezeichnet
draw.line([tuple(pt) for pt in ring] + [tuple(ring[0])], fill=(0, 255, 255), width=1)
for road in payload["roads"]:
ring = road["poly"][0]
draw.line([tuple(pt) for pt in ring], fill=(255, 0, 255), width=1)
for b in payload["buildings"]:
ring = b["poly"][0]
if len(ring) >= 3:
draw.polygon([tuple(pt) for pt in ring], outline=(255, 220, 0))
for x, z in payload["palms"]:
draw.ellipse([x - 2, z - 2, x + 2, z + 2], outline=(0, 200, 0))
crop.save(out_dir / f"chunk_{i}_{j}.png")
def write_chunks(out_dir: Path, payloads: list[dict], meta: dict) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
for payload in payloads:
i, j = payload["chunk"]
(out_dir / f"chunk_{i}_{j}.json").write_text(json.dumps(payload, separators=(",", ":")))
(out_dir / "manifest.json").write_text(json.dumps(meta, indent=1))