Add navmesh map mode to the new map.

https://github.com/dcs-liberation/dcs_liberation/issues/1097
This commit is contained in:
Dan Albert 2021-05-28 17:00:33 -07:00
parent a2abdcf5d3
commit bc7faee880
3 changed files with 90 additions and 14 deletions

View File

@ -40,7 +40,6 @@ from PySide2.QtWidgets import (
) )
from dcs import Point from dcs import Point
from dcs.mapping import point_from_heading from dcs.mapping import point_from_heading
from dcs.planes import F_16C_50
from dcs.unitgroup import Group from dcs.unitgroup import Group
from shapely.geometry import ( from shapely.geometry import (
LineString, LineString,

View File

@ -12,6 +12,7 @@ from shapely.geometry import LineString, Point as ShapelyPoint, Polygon, MultiPo
from game import Game, db from game import Game, db
from game.factions.faction import Faction from game.factions.faction import Faction
from game.navmesh import NavMesh
from game.profiling import logged_duration from game.profiling import logged_duration
from game.theater import ( from game.theater import (
ConflictTheater, ConflictTheater,
@ -32,7 +33,8 @@ from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
LeafletLatLon = List[float] LeafletLatLon = list[float]
LeafletPoly = list[LeafletLatLon]
# **EVERY PROPERTY NEEDS A NOTIFY SIGNAL** # **EVERY PROPERTY NEEDS A NOTIFY SIGNAL**
# #
@ -52,7 +54,7 @@ LeafletLatLon = List[float]
def shapely_poly_to_leaflet_points( def shapely_poly_to_leaflet_points(
poly: Polygon, theater: ConflictTheater poly: Polygon, theater: ConflictTheater
) -> List[LeafletLatLon]: ) -> LeafletPoly:
if poly.is_empty: if poly.is_empty:
return [] return []
return [theater.point_to_ll(Point(x, y)).as_list() for x, y in poly.exterior.coords] return [theater.point_to_ll(Point(x, y)).as_list() for x, y in poly.exterior.coords]
@ -528,7 +530,7 @@ class FlightJs(QObject):
return self._selected return self._selected
@Property(list, notify=commitBoundaryChanged) @Property(list, notify=commitBoundaryChanged)
def commitBoundary(self) -> List[LeafletLatLon]: def commitBoundary(self) -> LeafletPoly:
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan): if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
return [] return []
start = self.flight.flight_plan.patrol_start start = self.flight.flight_plan.patrol_start
@ -552,10 +554,10 @@ class ThreatZonesJs(QObject):
def __init__( def __init__(
self, self,
full: List[List[LeafletLatLon]], full: list[LeafletPoly],
aircraft: List[List[LeafletLatLon]], aircraft: list[LeafletPoly],
air_defenses: List[List[LeafletLatLon]], air_defenses: list[LeafletPoly],
radar_sams: List[List[LeafletLatLon]], radar_sams: list[LeafletPoly],
) -> None: ) -> None:
super().__init__() super().__init__()
self._full = full self._full = full
@ -564,25 +566,25 @@ class ThreatZonesJs(QObject):
self._radar_sams = radar_sams self._radar_sams = radar_sams
@Property(list, notify=fullChanged) @Property(list, notify=fullChanged)
def full(self) -> List[List[LeafletLatLon]]: def full(self) -> list[LeafletPoly]:
return self._full return self._full
@Property(list, notify=aircraftChanged) @Property(list, notify=aircraftChanged)
def aircraft(self) -> List[List[LeafletLatLon]]: def aircraft(self) -> list[LeafletPoly]:
return self._aircraft return self._aircraft
@Property(list, notify=airDefensesChanged) @Property(list, notify=airDefensesChanged)
def airDefenses(self) -> List[List[LeafletLatLon]]: def airDefenses(self) -> list[LeafletPoly]:
return self._air_defenses return self._air_defenses
@Property(list, notify=radarSamsChanged) @Property(list, notify=radarSamsChanged)
def radarSams(self) -> List[List[LeafletLatLon]]: def radarSams(self) -> list[LeafletPoly]:
return self._radar_sams return self._radar_sams
@staticmethod @staticmethod
def polys_to_leaflet( def polys_to_leaflet(
poly: Union[Polygon, MultiPolygon], theater: ConflictTheater poly: Union[Polygon, MultiPolygon], theater: ConflictTheater
) -> List[List[LeafletLatLon]]: ) -> list[LeafletPoly]:
if isinstance(poly, MultiPolygon): if isinstance(poly, MultiPolygon):
polys = poly.geoms polys = poly.geoms
else: else:
@ -621,6 +623,41 @@ class ThreatZoneContainerJs(QObject):
return self._red return self._red
class NavMeshJs(QObject):
blueChanged = Signal()
redChanged = Signal()
def __init__(self, blue: list[LeafletPoly], red: list[LeafletPoly]) -> None:
super().__init__()
self._blue = blue
self._red = red
# TODO: Boundary markers.
# TODO: Numbering.
# TODO: Localization debugging.
@Property(list, notify=blueChanged)
def blue(self) -> list[LeafletPoly]:
return self._blue
@Property(list, notify=redChanged)
def red(self) -> list[LeafletPoly]:
return self._red
@staticmethod
def to_polys(navmesh: NavMesh, theater: ConflictTheater) -> list[LeafletPoly]:
polys = []
for poly in navmesh.polys:
polys.append(shapely_poly_to_leaflet_points(poly.poly, theater))
return polys
@classmethod
def from_game(cls, game: Game) -> NavMeshJs:
return NavMeshJs(
cls.to_polys(game.blue_navmesh, game.theater),
cls.to_polys(game.red_navmesh, game.theater),
)
class MapModel(QObject): class MapModel(QObject):
cleared = Signal() cleared = Signal()
@ -631,6 +668,7 @@ class MapModel(QObject):
flightsChanged = Signal() flightsChanged = Signal()
frontLinesChanged = Signal() frontLinesChanged = Signal()
threatZonesChanged = Signal() threatZonesChanged = Signal()
navmeshesChanged = Signal()
def __init__(self, game_model: GameModel) -> None: def __init__(self, game_model: GameModel) -> None:
super().__init__() super().__init__()
@ -644,6 +682,7 @@ class MapModel(QObject):
self._threat_zones = ThreatZoneContainerJs( self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty() ThreatZonesJs.empty(), ThreatZonesJs.empty()
) )
self._navmeshes = NavMeshJs([], [])
self._selected_flight_index: Optional[Tuple[int, int]] = None self._selected_flight_index: Optional[Tuple[int, int]] = None
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load) GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos) GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos)
@ -664,6 +703,7 @@ class MapModel(QObject):
self._threat_zones = ThreatZoneContainerJs( self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty() ThreatZonesJs.empty(), ThreatZonesJs.empty()
) )
self._navmeshes = NavMeshJs([], [])
self.cleared.emit() self.cleared.emit()
def set_package_selection(self, index: int) -> None: def set_package_selection(self, index: int) -> None:
@ -708,6 +748,7 @@ class MapModel(QObject):
self.reset_atos() self.reset_atos()
self.reset_front_lines() self.reset_front_lines()
self.reset_threat_zones() self.reset_threat_zones()
self.reset_navmeshes()
def on_game_load(self, game: Optional[Game]) -> None: def on_game_load(self, game: Optional[Game]) -> None:
if game is not None: if game is not None:
@ -846,6 +887,14 @@ class MapModel(QObject):
def threatZones(self) -> ThreatZoneContainerJs: def threatZones(self) -> ThreatZoneContainerJs:
return self._threat_zones return self._threat_zones
def reset_navmeshes(self) -> None:
self._navmeshes = NavMeshJs.from_game(self.game)
self.navmeshesChanged.emit()
@Property(NavMeshJs, notify=navmeshesChanged)
def navmeshes(self) -> NavMeshJs:
return self._navmeshes
@property @property
def game(self) -> Game: def game(self) -> Game:
if self.game_model.game is None: if self.game_model.game is None:

View File

@ -176,6 +176,9 @@ const redAircraftThreatZones = L.layerGroup();
const redAirDefenseThreatZones = L.layerGroup(); const redAirDefenseThreatZones = L.layerGroup();
const redRadarSamThreatZones = L.layerGroup(); const redRadarSamThreatZones = L.layerGroup();
const blueNavmesh = L.layerGroup();
const redNavmesh = L.layerGroup();
// Main map controls. These are the ones that we expect users to interact with. // Main map controls. These are the ones that we expect users to interact with.
// These are always open, which unfortunately means that the scroll bar will not // These are always open, which unfortunately means that the scroll bar will not
// appear if the menu doesn't fit. This fits in the smallest window size we // appear if the menu doesn't fit. This fits in the smallest window size we
@ -238,10 +241,15 @@ L.control
"Air Defenses": redAirDefenseThreatZones, "Air Defenses": redAirDefenseThreatZones,
"Radar SAMs": redRadarSamThreatZones, "Radar SAMs": redRadarSamThreatZones,
}, },
"Navmeshes": {
Hide: L.layerGroup().addTo(map),
Blue: blueNavmesh,
Red: redNavmesh,
},
}, },
{ {
position: "topleft", position: "topleft",
exclusiveGroups: ["Blue Threat Zones", "Red Threat Zones"], exclusiveGroups: ["Blue Threat Zones", "Red Threat Zones", "Navmeshes"],
groupCheckboxes: true, groupCheckboxes: true,
} }
) )
@ -259,6 +267,7 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
game.frontLinesChanged.connect(drawFrontLines); game.frontLinesChanged.connect(drawFrontLines);
game.flightsChanged.connect(drawFlightPlans); game.flightsChanged.connect(drawFlightPlans);
game.threatZonesChanged.connect(drawThreatZones); game.threatZonesChanged.connect(drawThreatZones);
game.navmeshesChanged.connect(drawNavmeshes);
}); });
function recenterMap(center) { function recenterMap(center) {
@ -862,6 +871,24 @@ function drawThreatZones() {
); );
} }
function drawNavmesh(zones, layer) {
for (const zone of zones) {
L.polyline(zone, {
color: "#000000",
weight: 1,
fill: false,
}).addTo(layer);
}
}
function drawNavmeshes() {
blueNavmesh.clearLayers();
redNavmesh.clearLayers();
drawNavmesh(game.navmeshes.blue, blueNavmesh);
drawNavmesh(game.navmeshes.red, redNavmesh);
}
function drawInitialMap() { function drawInitialMap() {
recenterMap(game.mapCenter); recenterMap(game.mapCenter);
drawControlPoints(); drawControlPoints();
@ -870,6 +897,7 @@ function drawInitialMap() {
drawFrontLines(); drawFrontLines();
drawFlightPlans(); drawFlightPlans();
drawThreatZones(); drawThreatZones();
drawNavmeshes();
} }
function clearAllLayers() { function clearAllLayers() {