mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
bezier frontline display
Start on refactoring and cleanup of QLiberationMap.py
This commit is contained in:
parent
bd1457c435
commit
87afc2fcef
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from PySide2.QtCore import QPointF, Qt
|
from PySide2.QtCore import QPointF, Qt
|
||||||
@ -39,13 +40,40 @@ from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
|
|||||||
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
|
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from theater import ControlPoint
|
from theater import ControlPoint
|
||||||
from theater.frontline import FrontLine
|
from theater.conflicttheater import FrontLine
|
||||||
from theater.theatergroundobject import (
|
from theater.theatergroundobject import (
|
||||||
EwrGroundObject,
|
EwrGroundObject,
|
||||||
MissileSiteGroundObject,
|
MissileSiteGroundObject,
|
||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def binomial(i, n):
|
||||||
|
"""Binomial coefficient"""
|
||||||
|
return math.factorial(n) / float(
|
||||||
|
math.factorial(i) * math.factorial(n - i))
|
||||||
|
|
||||||
|
|
||||||
|
def bernstein(t, i, n):
|
||||||
|
"""Bernstein polynom"""
|
||||||
|
return binomial(i, n) * (t ** i) * ((1 - t) ** (n - i))
|
||||||
|
|
||||||
|
|
||||||
|
def bezier(t, points):
|
||||||
|
"""Calculate coordinate of a point in the bezier curve"""
|
||||||
|
n = len(points) - 1
|
||||||
|
x = y = 0
|
||||||
|
for i, pos in enumerate(points):
|
||||||
|
bern = bernstein(t, i, n)
|
||||||
|
x += pos[0] * bern
|
||||||
|
y += pos[1] * bern
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
|
||||||
|
def bezier_curve_range(n, points):
|
||||||
|
"""Range of points in a curve bezier"""
|
||||||
|
for i in range(n):
|
||||||
|
t = i / float(n - 1)
|
||||||
|
yield bezier(t, points)
|
||||||
|
|
||||||
class QLiberationMap(QGraphicsView):
|
class QLiberationMap(QGraphicsView):
|
||||||
WAYPOINT_SIZE = 4
|
WAYPOINT_SIZE = 4
|
||||||
@ -190,6 +218,67 @@ class QLiberationMap(QGraphicsView):
|
|||||||
|
|
||||||
return detection_range, threat_range
|
return detection_range, threat_range
|
||||||
|
|
||||||
|
def display_culling(self, scene: QGraphicsScene) -> None:
|
||||||
|
"""Draws the culling distance rings on the map"""
|
||||||
|
culling_points = self.game_model.game.get_culling_points()
|
||||||
|
culling_distance = self.game_model.game.settings.perf_culling_distance
|
||||||
|
for point in culling_points:
|
||||||
|
culling_distance_point = Point(point.x + culling_distance*1000, point.y + culling_distance*1000)
|
||||||
|
distance_point = self._transform_point(culling_distance_point)
|
||||||
|
transformed = self._transform_point(point)
|
||||||
|
diameter = distance_point[0] - transformed[0]
|
||||||
|
scene.addEllipse(transformed[0]-diameter/2, transformed[1]-diameter/2, diameter, diameter, CONST.COLORS["transparent"], CONST.COLORS["light_green_transparent"])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ground_object_display_options(ground_object: TheaterGroundObject, cp: ControlPoint) -> Tuple[bool, bool]:
|
||||||
|
is_missile = isinstance(ground_object, MissileSiteGroundObject)
|
||||||
|
is_aa = ground_object.category == "aa" and not is_missile
|
||||||
|
is_ewr = isinstance(ground_object, EwrGroundObject)
|
||||||
|
is_display_type = is_aa or is_ewr
|
||||||
|
should_display = ((DisplayOptions.sam_ranges and cp.captured)
|
||||||
|
or
|
||||||
|
(DisplayOptions.enemy_sam_ranges and not cp.captured))
|
||||||
|
return is_display_type, should_display
|
||||||
|
|
||||||
|
def draw_threat_range(self, scene: QGraphicsScene, ground_object: TheaterGroundObject, cp: ControlPoint) -> None:
|
||||||
|
go_pos = self._transform_point(ground_object.position)
|
||||||
|
detection_range, threat_range = self.aa_ranges(
|
||||||
|
ground_object
|
||||||
|
)
|
||||||
|
if threat_range:
|
||||||
|
threat_pos = self._transform_point(Point(ground_object.position.x+threat_range,
|
||||||
|
ground_object.position.y+threat_range))
|
||||||
|
threat_radius = Point(*go_pos).distance_to_point(Point(*threat_pos))
|
||||||
|
|
||||||
|
# Add threat range circle
|
||||||
|
scene.addEllipse(go_pos[0] - threat_radius / 2 + 7, go_pos[1] - threat_radius / 2 + 6,
|
||||||
|
threat_radius, threat_radius, self.threat_pen(cp.captured))
|
||||||
|
|
||||||
|
if detection_range and DisplayOptions.detection_range:
|
||||||
|
# Add detection range circle
|
||||||
|
detection_pos = self._transform_point(Point(ground_object.position.x+detection_range,
|
||||||
|
ground_object.position.y+detection_range))
|
||||||
|
detection_radius = Point(*go_pos).distance_to_point(Point(*detection_pos))
|
||||||
|
scene.addEllipse(go_pos[0] - detection_radius/2 + 7, go_pos[1] - detection_radius/2 + 6,
|
||||||
|
detection_radius, detection_radius, self.detection_pen(cp.captured))
|
||||||
|
|
||||||
|
def draw_ground_objects(self, scene: QGraphicsScene, cp: ControlPoint) -> None:
|
||||||
|
added_objects = []
|
||||||
|
for ground_object in cp.ground_objects:
|
||||||
|
if ground_object.obj_name in added_objects:
|
||||||
|
continue
|
||||||
|
|
||||||
|
go_pos = self._transform_point(ground_object.position)
|
||||||
|
if not ground_object.airbase_group:
|
||||||
|
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
|
||||||
|
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 14, 12, cp, ground_object, self.game, buildings))
|
||||||
|
|
||||||
|
is_display_type, should_display = self.ground_object_display_options(ground_object, cp)
|
||||||
|
|
||||||
|
if is_display_type and should_display:
|
||||||
|
self.draw_threat_range(scene, ground_object, cp)
|
||||||
|
added_objects.append(ground_object.obj_name)
|
||||||
|
|
||||||
def reload_scene(self):
|
def reload_scene(self):
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
scene.clear()
|
scene.clear()
|
||||||
@ -200,20 +289,13 @@ class QLiberationMap(QGraphicsView):
|
|||||||
self.addBackground()
|
self.addBackground()
|
||||||
|
|
||||||
# Uncomment below to help set up theater reference points
|
# Uncomment below to help set up theater reference points
|
||||||
#for i, r in enumerate(self.game.theater.reference_points.items()):
|
# for i, r in enumerate(self.game.theater.reference_points.items()):
|
||||||
# text = scene.addText(str(r), font=QFont("Trebuchet MS", 10, weight=5, italic=False))
|
# text = scene.addText(str(r), font=QFont("Trebuchet MS", 10, weight=5, italic=False))
|
||||||
# text.setPos(0, i * 24)
|
# text.setPos(0, i * 24)
|
||||||
|
|
||||||
# Display Culling
|
# Display Culling
|
||||||
if DisplayOptions.culling and self.game.settings.perf_culling:
|
if DisplayOptions.culling and self.game.settings.perf_culling:
|
||||||
culling_points = self.game_model.game.get_culling_points()
|
self.display_culling()
|
||||||
culling_distance = self.game_model.game.settings.perf_culling_distance
|
|
||||||
for point in culling_points:
|
|
||||||
culling_distance_point = Point(point.x + culling_distance*1000, point.y + culling_distance*1000)
|
|
||||||
distance_point = self._transform_point(culling_distance_point)
|
|
||||||
transformed = self._transform_point(point)
|
|
||||||
diameter = distance_point[0] - transformed[0]
|
|
||||||
scene.addEllipse(transformed[0]-diameter/2, transformed[1]-diameter/2, diameter, diameter, CONST.COLORS["transparent"], CONST.COLORS["light_green_transparent"])
|
|
||||||
|
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
|
|
||||||
@ -231,45 +313,7 @@ class QLiberationMap(QGraphicsView):
|
|||||||
pen = QPen(brush=CONST.COLORS[enemyColor])
|
pen = QPen(brush=CONST.COLORS[enemyColor])
|
||||||
brush = CONST.COLORS[enemyColor+"_transparent"]
|
brush = CONST.COLORS[enemyColor+"_transparent"]
|
||||||
|
|
||||||
added_objects = []
|
self.draw_ground_objects(scene, cp)
|
||||||
for ground_object in cp.ground_objects:
|
|
||||||
if ground_object.obj_name in added_objects:
|
|
||||||
continue
|
|
||||||
|
|
||||||
go_pos = self._transform_point(ground_object.position)
|
|
||||||
if not ground_object.airbase_group:
|
|
||||||
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
|
|
||||||
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 14, 12, cp, ground_object, self.game, buildings))
|
|
||||||
|
|
||||||
is_missile = isinstance(ground_object, MissileSiteGroundObject)
|
|
||||||
is_aa = ground_object.category == "aa" and not is_missile
|
|
||||||
is_ewr = isinstance(ground_object, EwrGroundObject)
|
|
||||||
is_display_type = is_aa or is_ewr
|
|
||||||
should_display = ((DisplayOptions.sam_ranges and cp.captured)
|
|
||||||
or
|
|
||||||
(DisplayOptions.enemy_sam_ranges and not cp.captured))
|
|
||||||
|
|
||||||
if is_display_type and should_display:
|
|
||||||
detection_range, threat_range = self.aa_ranges(
|
|
||||||
ground_object
|
|
||||||
)
|
|
||||||
if threat_range:
|
|
||||||
threat_pos = self._transform_point(Point(ground_object.position.x+threat_range,
|
|
||||||
ground_object.position.y+threat_range))
|
|
||||||
threat_radius = Point(*go_pos).distance_to_point(Point(*threat_pos))
|
|
||||||
|
|
||||||
# Add threat range circle
|
|
||||||
scene.addEllipse(go_pos[0] - threat_radius / 2 + 7, go_pos[1] - threat_radius / 2 + 6,
|
|
||||||
threat_radius, threat_radius, self.threat_pen(cp.captured))
|
|
||||||
if detection_range:
|
|
||||||
# Add detection range circle
|
|
||||||
detection_pos = self._transform_point(Point(ground_object.position.x+detection_range,
|
|
||||||
ground_object.position.y+detection_range))
|
|
||||||
detection_radius = Point(*go_pos).distance_to_point(Point(*detection_pos))
|
|
||||||
if DisplayOptions.detection_range:
|
|
||||||
scene.addEllipse(go_pos[0] - detection_radius/2 + 7, go_pos[1] - detection_radius/2 + 6,
|
|
||||||
detection_radius, detection_radius, self.detection_pen(cp.captured))
|
|
||||||
added_objects.append(ground_object.obj_name)
|
|
||||||
|
|
||||||
for cp in self.game.theater.enemy_points():
|
for cp in self.game.theater.enemy_points():
|
||||||
if DisplayOptions.lines:
|
if DisplayOptions.lines:
|
||||||
@ -400,44 +444,45 @@ class QLiberationMap(QGraphicsView):
|
|||||||
flight_path_pen
|
flight_path_pen
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def draw_bezier_frontline(self, scene: QGraphicsScene, pen:QPen, frontline: FrontLine) -> None:
|
||||||
|
"""
|
||||||
|
Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from.
|
||||||
|
https://gist.github.com/Alquimista/1274149#file-bezdraw-py
|
||||||
|
"""
|
||||||
|
bezier_fixed_points = []
|
||||||
|
for segment in frontline.segments:
|
||||||
|
bezier_fixed_points.append(self._transform_point(segment.point_a))
|
||||||
|
bezier_fixed_points.append(self._transform_point(segment.point_b))
|
||||||
|
|
||||||
|
old_point = bezier_fixed_points[0]
|
||||||
|
for point in bezier_curve_range(int(len(bezier_fixed_points) * 2), bezier_fixed_points):
|
||||||
|
scene.addLine(old_point[0], old_point[1], point[0], point[1], pen=pen)
|
||||||
|
old_point = point
|
||||||
|
|
||||||
def scene_create_lines_for_cp(self, cp: ControlPoint, playerColor, enemyColor):
|
def scene_create_lines_for_cp(self, cp: ControlPoint, playerColor, enemyColor):
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
pos = self._transform_point(cp.position)
|
|
||||||
for connected_cp in cp.connected_points:
|
for connected_cp in cp.connected_points:
|
||||||
pos2 = self._transform_point(connected_cp.position)
|
pos2 = self._transform_point(connected_cp.position)
|
||||||
if not cp.captured:
|
if not cp.captured:
|
||||||
color = CONST.COLORS["dark_"+enemyColor]
|
color = CONST.COLORS["dark_"+enemyColor]
|
||||||
elif cp.captured:
|
|
||||||
color = CONST.COLORS["dark_"+playerColor]
|
|
||||||
else:
|
else:
|
||||||
color = CONST.COLORS["dark_"+enemyColor]
|
color = CONST.COLORS["dark_"+playerColor]
|
||||||
|
|
||||||
pen = QPen(brush=color)
|
pen = QPen(brush=color)
|
||||||
pen.setColor(color)
|
pen.setColor(color)
|
||||||
pen.setWidth(6)
|
pen.setWidth(6)
|
||||||
frontline = FrontLine(cp, connected_cp)
|
frontline = FrontLine(cp, connected_cp, self.game.theater)
|
||||||
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
|
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
|
||||||
if not cp.captured:
|
posx = frontline.position
|
||||||
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
h = frontline.attack_heading
|
||||||
else:
|
pos2 = self._transform_point(posx)
|
||||||
posx = frontline.position
|
self.draw_bezier_frontline(scene, pen, frontline)
|
||||||
h = frontline.attack_heading
|
p1 = point_from_heading(pos2[0], pos2[1], h+180, 25)
|
||||||
pos2 = self._transform_point(posx)
|
p2 = point_from_heading(pos2[0], pos2[1], h, 25)
|
||||||
for segment in frontline.segments:
|
scene.addItem(QFrontLine(p1[0], p1[1], p2[0], p2[1],
|
||||||
seg_a = self._transform_point(segment.point_a)
|
frontline, self.game_model))
|
||||||
seg_b = self._transform_point(segment.point_b)
|
|
||||||
scene.addLine(seg_a[0], seg_a[1], seg_b[0], seg_b[1], pen=pen)
|
|
||||||
|
|
||||||
p1 = point_from_heading(pos2[0], pos2[1], h+180, 25)
|
|
||||||
p2 = point_from_heading(pos2[0], pos2[1], h, 25)
|
|
||||||
scene.addItem(QFrontLine(p1[0], p1[1], p2[0], p2[1],
|
|
||||||
FrontLine(cp, connected_cp)))
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for segment in frontline.segments:
|
self.draw_bezier_frontline(scene, pen, frontline)
|
||||||
seg_a = self._transform_point(segment.point_a)
|
|
||||||
seg_b = self._transform_point(segment.point_b)
|
|
||||||
scene.addLine(seg_a[0], seg_a[1], seg_b[0], seg_b[1], pen=pen)
|
|
||||||
|
|
||||||
def wheelEvent(self, event: QWheelEvent):
|
def wheelEvent(self, event: QWheelEvent):
|
||||||
|
|
||||||
@ -460,7 +505,7 @@ class QLiberationMap(QGraphicsView):
|
|||||||
|
|
||||||
#print(self.factorized, factor, self._zoom)
|
#print(self.factorized, factor, self._zoom)
|
||||||
|
|
||||||
def _transform_point(self, p: Point, treshold=30) -> (int, int):
|
def _transform_point(self, p: Point, treshold=30) -> Tuple[int, int]:
|
||||||
point_a = list(self.game.theater.reference_points.keys())[0]
|
point_a = list(self.game.theater.reference_points.keys())[0]
|
||||||
point_a_img = self.game.theater.reference_points[point_a]
|
point_a_img = self.game.theater.reference_points[point_a]
|
||||||
|
|
||||||
@ -510,18 +555,11 @@ class QLiberationMap(QGraphicsView):
|
|||||||
return CONST.COLORS[f"{name}_transparent"]
|
return CONST.COLORS[f"{name}_transparent"]
|
||||||
|
|
||||||
def threat_pen(self, player: bool) -> QPen:
|
def threat_pen(self, player: bool) -> QPen:
|
||||||
if player:
|
color = "blue" if player else "red"
|
||||||
color = "blue"
|
return QPen(CONST.COLORS[color])
|
||||||
else:
|
|
||||||
color = "red"
|
|
||||||
qpen = QPen(CONST.COLORS[color])
|
|
||||||
return qpen
|
|
||||||
|
|
||||||
def detection_pen(self, player: bool) -> QPen:
|
def detection_pen(self, player: bool) -> QPen:
|
||||||
if player:
|
color = "purple" if player else "yellow"
|
||||||
color = "purple"
|
|
||||||
else:
|
|
||||||
color = "yellow"
|
|
||||||
qpen = QPen(CONST.COLORS[color])
|
qpen = QPen(CONST.COLORS[color])
|
||||||
qpen.setStyle(Qt.DotLine)
|
qpen.setStyle(Qt.DotLine)
|
||||||
return qpen
|
return qpen
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user