Files
gta6/tools/extract/leonida/marking.py
T
knsoandClaude Sonnet 5 e92d5b5ce8 fix(extract): Kommentarfehler doppelte Mitte, Rueckgabetyp, zwei Lagetests
Der Kommentar zu gelb_doppelt nannte den Achsabstand (2*WIDTH = 20 cm) als "Abstand der
Mitten", meinte damit aber die Luecke zwischen den gemalten Linien - die entsteht erst
nach der Pufferung um je WIDTH/2 und betraegt WIDTH = 10 cm, wie vom Entwurf verlangt.
Die Geometrie war immer richtig, nur der Satz dazu nicht.

longitudinal() versprach list[tuple[object, str]] statt der zugesagten
list[tuple[Polygon, str]] - Polygon war nicht einmal importiert.

Dazu zwei Tests, die die schon richtige Lage festhalten, damit ein Kommentarfehler wie
der obige naechstes Mal auffiele: gelb_doppelt liegt symmetrisch um die Achse mit 20 cm
Achsabstand und 10 cm Luecke, der Boulevard-Spurtrenner liegt bei genau ±LANE. Kein
TDD-Fall - beide liefen beim ersten Lauf gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 08:03:31 +02:00

79 lines
3.5 KiB
Python

"""Amerikanische Fahrbahnmarkierung als Geometrie.
Das Schema ist das Gegenteil des europäischen: Gelb trennt Gegenrichtungen und steht deshalb in
der Mitte, Weiß trennt gleichgerichtete Spuren und begrenzt die Fahrbahn. Die mitgelieferten
Texturen von modular_city_road_pack__game_ready zeigen das europäische Schema und sind hier
deshalb unbrauchbar (Entwurf, Abschnitt "Die beiden Kits").
Markierungen sind flache Vierecke, keine Textur: An Kreuzungen liegen Haltelinie und
Zebrastreifen quer zur Fahrtrichtung und ließen sich in einer längs laufenden Kachel gar nicht
unterbringen, und aus der Nähe bleibt Geometrie scharf, wo eine Textur verwischt.
"""
from __future__ import annotations
from shapely.geometry import LineString, Polygon
from shapely.ops import substring
from .roads import Graph
from .section import LANE, SECTION, trimmed_line
WIDTH = 0.10 # 4 in
DASH = 3.0
GAP = 9.0
KINDS = ("gelb_gestrichelt", "gelb_doppelt", "weiss_gestrichelt", "weiss_rand",
"haltelinie", "zebra")
def longitudinal(graph: Graph) -> list[tuple[Polygon, str]]:
"""Mittellinie, Spurtrenner und Randlinien je Kante, zwischen den Kreuzungsrändern."""
out = []
for k, edge in enumerate(graph.edges):
section = SECTION[edge.klass]
line = trimmed_line(graph, k)
if line is None or section.lanes < 2:
continue # die Gasse hat nur eine Fahrgasse und bleibt blank
if section.parking == 0.0:
# Wohnstraße: eine gestrichelte gelbe Mitte, Überholen erlaubt.
out += [(p, "gelb_gestrichelt") for p in _dashed(line, 0.0)]
else:
# Doppelt durchgezogen: Die Achsen liegen bei ±WIDTH, also 20 cm auseinander. Jede
# Linie ist WIDTH breit (Pufferung um WIDTH/2 je Seite), sodass von den 20 cm genau
# WIDTH = 10 cm als Lücke zwischen den gemalten Linien übrig bleiben - das ist der im
# Entwurf verlangte Abstand, nicht der Achsabstand selbst.
out += [(_solid(line, off), "gelb_doppelt") for off in (-WIDTH, WIDTH)]
rand = section.carriageway / 2 - section.parking
out += [(_solid(line, off), "weiss_rand") for off in (-rand, rand)]
if section.lanes >= 4:
# Zwischen den gleichgerichteten Spuren, je Richtung einer.
out += [(p, "weiss_gestrichelt") for off in (-LANE, LANE) for p in _dashed(line, off)]
return out
def _offset(line: LineString, distance: float) -> LineString:
if distance == 0.0:
return line
# offset_curve versetzt nach links; negative Werte nach rechts. Bei geraden Straßen ist das
# exakt, bei Knicken folgt die Kurve dem Knick - genau das soll eine Markierung auch.
return line.offset_curve(distance)
def _solid(line: LineString, distance: float):
return _offset(line, distance).buffer(WIDTH / 2, cap_style="flat", join_style="mitre", mitre_limit=1.5)
def _dashed(line: LineString, distance: float) -> list:
"""Striche im Takt DASH/GAP, beginnend am Anfang der Kante.
Die Phase läuft bewusst nicht über Kreuzungen hinweg weiter: Jede Kante fängt neu an. Alles
andere hieße, eine Ordnung zu behaupten, die die Karte gar nicht hergibt.
"""
versetzt = _offset(line, distance)
out, at = [], 0.0
while at < versetzt.length:
end = min(at + DASH, versetzt.length)
if end - at > 0.5:
out.append(substring(versetzt, at, end).buffer(
WIDTH / 2, cap_style="flat", join_style="mitre", mitre_limit=1.5))
at = end + GAP
return out