diff --git a/game/theater/landmap.py b/game/theater/landmap.py index 8192b057..6e59237b 100644 --- a/game/theater/landmap.py +++ b/game/theater/landmap.py @@ -4,10 +4,17 @@ from functools import cached_property from typing import Optional, Tuple, Union import logging from pathlib import Path +from typing import List from shapely import geometry from shapely.geometry import MultiPolygon, Polygon +from dcs.drawing.drawing import LineStyle, Rgba +from dcs.drawing.polygon import FreeFormPolygon +from dcs.mapping import Point +from dcs.mission import Mission +from dcs.terrain.terrain import Terrain + @dataclass(frozen=True) class Landmap: @@ -39,3 +46,94 @@ def load_landmap(filename: Path) -> Optional[Landmap]: def poly_contains(x: float, y: float, poly: Union[MultiPolygon, Polygon]) -> bool: return poly.contains(geometry.Point(x, y)) + + +def to_miz(landmap: Landmap, terrain: Terrain, mission_filename: str) -> None: + """ + Writes landmap to .miz file so that zones can be visualized and edited in the + mission editor. + """ + + def multi_polygon_to_miz( + mission: Mission, + terrain: Terrain, + multi_polygon: MultiPolygon, + color: Rgba, + prefix: str, + layer_index: int = 4, + layer_name: str = "Author", + ) -> None: + reference_position = Point(0, 0, terrain) + for i in range(len(multi_polygon.geoms)): + polygon = multi_polygon.geoms[i] + if len(polygon.interiors) > 0: + raise ValueError( + f"Polygon hole found when trying to export {prefix} {i}. to_miz() does not support landmap zones with holes." + ) + coordinates = polygon.exterior.xy + points = [] + for j in range(len(coordinates[0])): + points.append(Point(coordinates[0][j], coordinates[1][j], terrain)) + polygon_drawing = FreeFormPolygon( + visible=True, + position=reference_position, + name=f"{prefix}-{i}", + color=color, + layer_name=layer_name, + fill=color, + line_thickness=10, + line_style=LineStyle.Solid, + points=points, + ) + mission.drawings.layers[layer_index].objects.append(polygon_drawing) + + mission = Mission(terrain=terrain) + multi_polygon_to_miz( + mission, terrain, landmap.exclusion_zones, Rgba(255, 0, 0, 128), "Exclusion" + ) + multi_polygon_to_miz( + mission, terrain, landmap.sea_zones, Rgba(0, 0, 255, 128), "Sea" + ) + multi_polygon_to_miz( + mission, terrain, landmap.inclusion_zones, Rgba(0, 255, 0, 128), "Inclusion" + ) + mission.save(mission_filename) + + +def from_miz(mission_filename: str, layer_index: int = 4) -> Landmap: + """ + Generate Landmap object from Free Form Polygons drawn in a .miz file. + Landmap.inclusion_zones are expected to be named Inclusion- + Landmap.exclusion_zones are expected to be named Exclusion- + Landmap.sea_zones are expected to be named Sea- + """ + mission = Mission() + mission.load_file(mission_filename) + polygons: dict[str, List[Polygon]] = {"Inclusion": [], "Exclusion": [], "Sea": []} + for draw_object in mission.drawings.layers[layer_index].objects: + if type(draw_object) != FreeFormPolygon: + logging.debug( + f"Object {draw_object.name} is not a FreeFormPolygon, ignoring" + ) + continue + name_split = draw_object.name.split( + "-" + ) # names are in the format - + zone_type = name_split[0] + if len(name_split) != 2 or zone_type not in ("Exclusion", "Sea", "Inclusion"): + logging.debug( + f"Object name {draw_object.name} does not conform to expected format -, ignoring" + ) + continue + polygon_points = [] + for point in draw_object.points: + polygon_points.append( + (point.x + draw_object.position.x, point.y + draw_object.position.y) + ) + polygons[zone_type].append(Polygon(polygon_points)) + landmap = Landmap( + inclusion_zones=MultiPolygon(polygons["Inclusion"]), + exclusion_zones=MultiPolygon(polygons["Exclusion"]), + sea_zones=MultiPolygon(polygons["Sea"]), + ) + return landmap diff --git a/tests/theater/test_landmap.py b/tests/theater/test_landmap.py new file mode 100644 index 00000000..61a113ab --- /dev/null +++ b/tests/theater/test_landmap.py @@ -0,0 +1,32 @@ +import os +import pytest + +from shapely.geometry import MultiPolygon, Polygon + +from dcs.terrain.caucasus.caucasus import Caucasus +from game.theater import landmap + + +def test_miz() -> None: + """ + Test miz generation and loading + """ + test_map = landmap.Landmap( + inclusion_zones=MultiPolygon([Polygon([(0, 0), (0, 1), (1, 0)])]), + exclusion_zones=MultiPolygon([Polygon([(1, 1), (0, 1), (1, 0)])]), + sea_zones=MultiPolygon([Polygon([(0, 0), (0, 2), (1, 0)])]), + ) + test_filename = "test.miz" + landmap.to_miz(test_map, Caucasus(), test_filename) + assert os.path.isfile("test.miz") + loaded_map = landmap.from_miz("test.miz") + assert test_map.inclusion_zones.equals_exact( + loaded_map.inclusion_zones, tolerance=1e-6 + ) + assert test_map.sea_zones.equals_exact(loaded_map.sea_zones, tolerance=1e-6) + assert test_map.exclusion_zones.equals_exact( + loaded_map.exclusion_zones, tolerance=1e-6 + ) + + if os.path.isfile(test_filename): + os.remove(test_filename)