mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Reference point rework.
* Introduce a real type. * Rewrite _transform_point to make use of Point. * Add shift modifier for large (10 pixel) adjustements to reference points. Unmodified behavior is now single pixel. * Use WASD for moving the second point (shift modified numpad keys don't seem to work). * Add a debug option to draw transformed reference points to check for errors. If they don't overlap, something is wrong. * Cleaned up all the existing reference points. Caucasus in particular is now *much* better. As an added bonus, the cleanup for carrier movement projection now also shows an invalid destination when the destination is on land.
This commit is contained in:
parent
8137d57cdf
commit
f659dc1f76
@ -375,10 +375,16 @@ class MizCampaignLoader:
|
||||
self.theater.set_frontline_data(self.front_lines)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReferencePoint:
|
||||
world_coordinates: Point
|
||||
image_coordinates: Point
|
||||
|
||||
|
||||
class ConflictTheater:
|
||||
terrain: Terrain
|
||||
|
||||
reference_points: Dict[Tuple[float, float], Tuple[float, float]]
|
||||
reference_points: Tuple[ReferencePoint, ReferencePoint]
|
||||
overview_image: str
|
||||
landmap: Optional[Landmap]
|
||||
"""
|
||||
@ -593,8 +599,10 @@ class ConflictTheater:
|
||||
class CaucasusTheater(ConflictTheater):
|
||||
terrain = caucasus.Caucasus()
|
||||
overview_image = "caumap.gif"
|
||||
reference_points = {(-317948.32727306, 635639.37385346): (278.5 * 4, 319 * 4),
|
||||
(-355692.3067714, 617269.96285781): (263 * 4, 352 * 4), }
|
||||
reference_points = (
|
||||
ReferencePoint(caucasus.Gelendzhik.position, Point(176, 298)),
|
||||
ReferencePoint(caucasus.Batumi.position, Point(1307, 1205)),
|
||||
)
|
||||
|
||||
landmap = load_landmap("resources\\caulandmap.p")
|
||||
daytime_map = {
|
||||
@ -608,10 +616,11 @@ class CaucasusTheater(ConflictTheater):
|
||||
class PersianGulfTheater(ConflictTheater):
|
||||
terrain = persiangulf.PersianGulf()
|
||||
overview_image = "persiangulf.gif"
|
||||
reference_points = {
|
||||
(persiangulf.Shiraz_International_Airport.position.x, persiangulf.Shiraz_International_Airport.position.y): (
|
||||
772, -1970),
|
||||
(persiangulf.Liwa_Airbase.position.x, persiangulf.Liwa_Airbase.position.y): (1188, 78), }
|
||||
reference_points = (
|
||||
ReferencePoint(persiangulf.Jiroft_Airport.position,
|
||||
Point(1692, 1343)),
|
||||
ReferencePoint(persiangulf.Liwa_Airbase.position, Point(358, 3238)),
|
||||
)
|
||||
landmap = load_landmap("resources\\gulflandmap.p")
|
||||
daytime_map = {
|
||||
"dawn": (6, 8),
|
||||
@ -620,11 +629,14 @@ class PersianGulfTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class NevadaTheater(ConflictTheater):
|
||||
terrain = nevada.Nevada()
|
||||
overview_image = "nevada.gif"
|
||||
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45 * 2, -360 * 2),
|
||||
(nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440 * 2, 80 * 2), }
|
||||
reference_points = (
|
||||
ReferencePoint(nevada.Mina_Airport_3Q0.position, Point(252, 295)),
|
||||
ReferencePoint(nevada.Laughlin_Airport.position, Point(844, 909)),
|
||||
)
|
||||
landmap = load_landmap("resources\\nevlandmap.p")
|
||||
daytime_map = {
|
||||
"dawn": (4, 6),
|
||||
@ -633,11 +645,14 @@ class NevadaTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class NormandyTheater(ConflictTheater):
|
||||
terrain = normandy.Normandy()
|
||||
overview_image = "normandy.gif"
|
||||
reference_points = {(normandy.Needs_Oar_Point.position.x, normandy.Needs_Oar_Point.position.y): (330, -970),
|
||||
(normandy.Evreux.position.x, normandy.Evreux.position.y): (1780, 520)}
|
||||
reference_points = (
|
||||
ReferencePoint(normandy.Needs_Oar_Point.position, Point(515, 329)),
|
||||
ReferencePoint(normandy.Evreux.position, Point(2029, 1709)),
|
||||
)
|
||||
landmap = load_landmap("resources\\normandylandmap.p")
|
||||
daytime_map = {
|
||||
"dawn": (6, 8),
|
||||
@ -646,11 +661,14 @@ class NormandyTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class TheChannelTheater(ConflictTheater):
|
||||
terrain = thechannel.TheChannel()
|
||||
overview_image = "thechannel.gif"
|
||||
reference_points = {(thechannel.Abbeville_Drucat.position.x, thechannel.Abbeville_Drucat.position.y): (2400, 4100),
|
||||
(thechannel.Detling.position.x, thechannel.Detling.position.y): (1100, 2000)}
|
||||
reference_points = (
|
||||
ReferencePoint(thechannel.Abbeville_Drucat.position, Point(2005, 2390)),
|
||||
ReferencePoint(thechannel.Detling.position, Point(706, 382))
|
||||
)
|
||||
landmap = load_landmap("resources\\channellandmap.p")
|
||||
daytime_map = {
|
||||
"dawn": (6, 8),
|
||||
@ -659,11 +677,14 @@ class TheChannelTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class SyriaTheater(ConflictTheater):
|
||||
terrain = syria.Syria()
|
||||
overview_image = "syria.gif"
|
||||
reference_points = {(syria.Eyn_Shemer.position.x, syria.Eyn_Shemer.position.y): (1300, 1380),
|
||||
(syria.Tabqa.position.x, syria.Tabqa.position.y): (2060, 570)}
|
||||
reference_points = (
|
||||
ReferencePoint(syria.Eyn_Shemer.position, Point(564, 1289)),
|
||||
ReferencePoint(syria.Tabqa.position, Point(1329, 491)),
|
||||
)
|
||||
landmap = load_landmap("resources\\syrialandmap.p")
|
||||
daytime_map = {
|
||||
"dawn": (6, 8),
|
||||
@ -672,6 +693,7 @@ class SyriaTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComplexFrontLine:
|
||||
"""
|
||||
|
||||
@ -28,7 +28,7 @@ from dcs.mapping import point_from_heading
|
||||
import qt_ui.uiconstants as CONST
|
||||
from game import Game, db
|
||||
from game.theater import ControlPoint, Enum
|
||||
from game.theater.conflicttheater import FrontLine
|
||||
from game.theater.conflicttheater import FrontLine, ReferencePoint
|
||||
from game.theater.theatergroundobject import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
@ -190,37 +190,55 @@ class QLiberationMap(QGraphicsView):
|
||||
self.reference_point_setup_mode = False
|
||||
self.reload_scene()
|
||||
else:
|
||||
numpad_mod = int(event.modifiers()) & QtCore.Qt.KeypadModifier
|
||||
i = 0
|
||||
for k,v in self.game.theater.reference_points.items():
|
||||
if i == 0:
|
||||
point_0 = k
|
||||
else:
|
||||
point_1 = k
|
||||
i = i + 1
|
||||
distance = 1
|
||||
modifiers = int(event.modifiers())
|
||||
if modifiers & QtCore.Qt.ShiftModifier:
|
||||
distance *= 10
|
||||
elif modifiers & QtCore.Qt.ControlModifier:
|
||||
distance *= 100
|
||||
|
||||
if event.key() == QtCore.Qt.Key_Down:
|
||||
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0] + 10, self.game.theater.reference_points[point_0][1]
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[0],
|
||||
Point(0, distance))
|
||||
if event.key() == QtCore.Qt.Key_Up:
|
||||
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0] - 10, self.game.theater.reference_points[point_0][1]
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[0],
|
||||
Point(0, -distance))
|
||||
if event.key() == QtCore.Qt.Key_Left:
|
||||
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0], self.game.theater.reference_points[point_0][1] + 10
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[0],
|
||||
Point(-distance, 0))
|
||||
if event.key() == QtCore.Qt.Key_Right:
|
||||
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0], self.game.theater.reference_points[point_0][1] - 10
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[0],
|
||||
Point(distance, 0))
|
||||
|
||||
if event.key() == QtCore.Qt.Key_S:
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[1],
|
||||
Point(0, distance))
|
||||
if event.key() == QtCore.Qt.Key_W:
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[1],
|
||||
Point(0, -distance))
|
||||
if event.key() == QtCore.Qt.Key_A:
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[1],
|
||||
Point(-distance, 0))
|
||||
if event.key() == QtCore.Qt.Key_D:
|
||||
self.update_reference_point(
|
||||
self.game.theater.reference_points[1],
|
||||
Point(distance, 0))
|
||||
|
||||
if event.key() == QtCore.Qt.Key_2 and numpad_mod:
|
||||
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0] + 10, self.game.theater.reference_points[point_1][1]
|
||||
if event.key() == QtCore.Qt.Key_8 and numpad_mod:
|
||||
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0] - 10, self.game.theater.reference_points[point_1][1]
|
||||
if event.key() == QtCore.Qt.Key_4 and numpad_mod:
|
||||
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0], self.game.theater.reference_points[point_1][1] + 10
|
||||
if event.key() == QtCore.Qt.Key_6 and numpad_mod:
|
||||
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0], self.game.theater.reference_points[point_1][1] - 10
|
||||
|
||||
print(self.game.theater.reference_points)
|
||||
logging.debug(
|
||||
f"Reference points: {self.game.theater.reference_points}")
|
||||
self.reload_scene()
|
||||
|
||||
@staticmethod
|
||||
def update_reference_point(point: ReferencePoint, change: Point) -> None:
|
||||
point.image_coordinates += change
|
||||
|
||||
@staticmethod
|
||||
def aa_ranges(ground_object: TheaterGroundObject) -> Tuple[int, int]:
|
||||
detection_range = 0
|
||||
@ -590,51 +608,60 @@ class QLiberationMap(QGraphicsView):
|
||||
else:
|
||||
self._zoom = -4
|
||||
|
||||
def _transform_point(self, p: Point) -> Tuple[int, int]:
|
||||
point_a = list(self.game.theater.reference_points.keys())[0]
|
||||
point_a_img = self.game.theater.reference_points[point_a]
|
||||
@staticmethod
|
||||
def _transpose_point(p: Point) -> Point:
|
||||
return Point(p.y, p.x)
|
||||
|
||||
point_b = list(self.game.theater.reference_points.keys())[1]
|
||||
point_b_img = self.game.theater.reference_points[point_b]
|
||||
def _scaling_factor(self) -> Point:
|
||||
point_a = self.game.theater.reference_points[0]
|
||||
point_b = self.game.theater.reference_points[1]
|
||||
|
||||
Y_dist = point_a_img[0] - point_b_img[0]
|
||||
lon_dist = point_a[1] - point_b[1]
|
||||
world_distance = self._transpose_point(
|
||||
point_b.world_coordinates - point_a.world_coordinates)
|
||||
image_distance = point_b.image_coordinates - point_a.image_coordinates
|
||||
|
||||
X_dist = point_a_img[1] - point_b_img[1]
|
||||
lat_dist = point_b[0] - point_a[0]
|
||||
x_scale = image_distance.x / world_distance.x
|
||||
y_scale = image_distance.y / world_distance.y
|
||||
return Point(x_scale, y_scale)
|
||||
|
||||
Y_scale = float(Y_dist) / float(lon_dist)
|
||||
X_scale = float(X_dist) / float(lat_dist)
|
||||
# TODO: Move this and its inverse into ConflictTheater.
|
||||
def _transform_point(self, world_point: Point) -> Tuple[float, float]:
|
||||
"""Transforms world coordinates to image coordinates.
|
||||
|
||||
# ---
|
||||
Y_offset = p.x - point_a[0]
|
||||
X_offset = p.y - point_a[1]
|
||||
World coordinates are transposed. X increases toward the North, Y
|
||||
increases toward the East. The origin point depends on the map.
|
||||
|
||||
X = point_b_img[1] + X_offset * X_scale
|
||||
Y = point_a_img[0] - Y_offset * Y_scale
|
||||
Image coordinates originate from the top left. X increases to the right,
|
||||
Y increases toward the bottom.
|
||||
|
||||
return X, Y
|
||||
The two points should be as distant as possible in both latitude and
|
||||
logitude, and tuning the reference points will be simpler if they are in
|
||||
geographically recognizable locations. For example, the Caucasus map is
|
||||
aligned using the first point on Gelendzhik and the second on Batumi.
|
||||
|
||||
def _scene_to_dcs_coords(self, p: Point):
|
||||
pa = list(self.game.theater.reference_points.keys())[0]
|
||||
pa2 = self.game.theater.reference_points[pa]
|
||||
The distances between each point are computed and a scaling factor is
|
||||
determined from that. The given point is then offset from the first
|
||||
point using the scaling factor.
|
||||
|
||||
pb = list(self.game.theater.reference_points.keys())[1]
|
||||
pb2 = self.game.theater.reference_points[pb]
|
||||
X is latitude, increasing northward.
|
||||
Y is longitude, increasing eastward.
|
||||
"""
|
||||
point_a = self.game.theater.reference_points[0]
|
||||
scale = self._scaling_factor()
|
||||
|
||||
dy2 = pa2[0] - pb2[0]
|
||||
dy = pa[1] - pb[1]
|
||||
offset = self._transpose_point(point_a.world_coordinates - world_point)
|
||||
scaled = Point(offset.x * scale.x, offset.y * scale.y)
|
||||
transformed = point_a.image_coordinates - scaled
|
||||
return transformed.x, transformed.y
|
||||
|
||||
dx2 = pa2[1] - pb2[1]
|
||||
dx = pb[0] - pa[0]
|
||||
def _scene_to_dcs_coords(self, scene_point: Point) -> Point:
|
||||
point_a = self.game.theater.reference_points[0]
|
||||
scale = self._scaling_factor()
|
||||
|
||||
ys = float(dy2) / float(dy)
|
||||
xs = float(dx2) / float(dx)
|
||||
|
||||
X = ((float(p.x - pb2[1])) / float(xs)) + pa[1]
|
||||
Y = ((float(pa2[0] - p.y)) / float(ys)) + pa[0]
|
||||
|
||||
return Y, X
|
||||
offset = point_a.image_coordinates - scene_point
|
||||
scaled = self._transpose_point(
|
||||
Point(offset.x / scale.x, offset.y / scale.y))
|
||||
return point_a.world_coordinates - scaled
|
||||
|
||||
def km_to_pixel(self, km):
|
||||
p1 = Point(0, 0)
|
||||
@ -739,38 +766,47 @@ class QLiberationMap(QGraphicsView):
|
||||
scene.addPolygon(poly, CONST.COLORS["grey"], CONST.COLORS["dark_dark_grey"])
|
||||
|
||||
# Uncomment to display plan projection test
|
||||
#self.projection_test(scene)
|
||||
# self.projection_test()
|
||||
self.draw_scale()
|
||||
|
||||
if self.reference_point_setup_mode:
|
||||
for i, r in enumerate(self.game.theater.reference_points.values()):
|
||||
self.scene().addRect(QRectF(r[0], r[1], 25, 25), pen=CONST.COLORS["red"], brush=CONST.COLORS["red"])
|
||||
text = self.scene().addText("P{0} = {1}, {2}".format(i, r[0], r[1]),
|
||||
font=QFont("Trebuchet MS", 14, weight=8, italic=False))
|
||||
for i, point in enumerate(self.game.theater.reference_points):
|
||||
self.scene().addRect(
|
||||
QRectF(point.image_coordinates.x, point.image_coordinates.y,
|
||||
25, 25), pen=CONST.COLORS["red"],
|
||||
brush=CONST.COLORS["red"])
|
||||
text = self.scene().addText(
|
||||
f"P{i} = {point.image_coordinates}",
|
||||
font=QFont("Trebuchet MS", 14, weight=8, italic=False))
|
||||
text.setDefaultTextColor(CONST.COLORS["red"])
|
||||
text.setPos(r[0]+26, r[1])
|
||||
text.setPos(point.image_coordinates.x + 26,
|
||||
point.image_coordinates.y)
|
||||
|
||||
def projection_test(self, scene):
|
||||
# Set to True to visually debug _transform_point.
|
||||
draw_transformed = False
|
||||
if draw_transformed:
|
||||
x, y = self._transform_point(point.world_coordinates)
|
||||
self.scene().addRect(
|
||||
QRectF(x, y, 25, 25),
|
||||
pen=CONST.COLORS["red"],
|
||||
brush=CONST.COLORS["red"])
|
||||
text = self.scene().addText(
|
||||
f"P{i}' = {x}, {y}",
|
||||
font=QFont("Trebuchet MS", 14, weight=8, italic=False))
|
||||
text.setDefaultTextColor(CONST.COLORS["red"])
|
||||
text.setPos(x + 26, y)
|
||||
|
||||
def projection_test(self):
|
||||
for i in range(100):
|
||||
for j in range(100):
|
||||
x = i * 100
|
||||
y = j * 100
|
||||
self.scene().addRect(QRectF(x, y, 4, 4), CONST.COLORS["green"])
|
||||
proj = self._scene_to_dcs_coords(Point(x, y))
|
||||
unproj = self._transform_point(Point(proj[0], proj[1]))
|
||||
text = scene.addText(str(i) + ", " + str(j) + "\n" + str(unproj[0]) + ", " + str(unproj[1]),
|
||||
font=QFont("Trebuchet MS", 6, weight=5, italic=False))
|
||||
text.setPos(unproj[0] + 2, unproj[1] + 2)
|
||||
text.setDefaultTextColor(Qt.red)
|
||||
text2 = scene.addText(str(i) + ", " + str(j) + "\n" + str(x) + ", " + str(y),
|
||||
font=QFont("Trebuchet MS", 6, weight=5, italic=False))
|
||||
text2.setPos(x + 2, y + 10)
|
||||
text2.setDefaultTextColor(Qt.green)
|
||||
self.scene().addRect(QRectF(unproj[0] + 1, unproj[1] + 1, 4, 4), CONST.COLORS["red"])
|
||||
if i % 2 == 0:
|
||||
self.scene().addLine(QLineF(x + 1, y + 1, unproj[0], unproj[1]), CONST.COLORS["yellow"])
|
||||
else:
|
||||
self.scene().addLine(QLineF(x + 1, y + 1, unproj[0], unproj[1]), CONST.COLORS["purple"])
|
||||
x = i * 100.0
|
||||
y = j * 100.0
|
||||
original = Point(x, y)
|
||||
proj = self._scene_to_dcs_coords(original)
|
||||
unproj = self._transform_point(proj)
|
||||
converted = Point(*unproj)
|
||||
assert math.isclose(original.x, converted.x, abs_tol=0.00000001)
|
||||
assert math.isclose(original.y, converted.y, abs_tol=0.00000001)
|
||||
|
||||
def setSelectedUnit(self, selected_cp: QMapControlPoint):
|
||||
self.state = QLiberationMapState.MOVING_UNIT
|
||||
@ -779,23 +815,27 @@ class QLiberationMap(QGraphicsView):
|
||||
self.movement_line = QtWidgets.QGraphicsLineItem(QLineF(QPointF(*position), QPointF(*position)))
|
||||
self.scene().addItem(self.movement_line)
|
||||
|
||||
def is_valid_ship_pos(self, scene_position: Point) -> bool:
|
||||
world_destination = self._scene_to_dcs_coords(scene_position)
|
||||
distance = self.selected_cp.control_point.position.distance_to_point(
|
||||
world_destination
|
||||
)
|
||||
if meter_to_nm(distance) > MAX_SHIP_DISTANCE:
|
||||
return False
|
||||
return self.game.theater.is_in_sea(world_destination)
|
||||
|
||||
def sceneMouseMovedEvent(self, event: QGraphicsSceneMouseEvent):
|
||||
if self.state == QLiberationMapState.MOVING_UNIT:
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
pos = event.scenePos()
|
||||
p1 = self.movement_line.line().p1()
|
||||
self.movement_line.setLine(
|
||||
QLineF(self.movement_line.line().p1(), event.scenePos()))
|
||||
|
||||
distance = Point(p1.x(), p1.y()).distance_to_point(Point(pos.x(), pos.y()))
|
||||
|
||||
self.movement_line.setLine(QLineF(p1, pos))
|
||||
|
||||
if distance / self.nm_to_pixel_ratio < MAX_SHIP_DISTANCE:
|
||||
pos = Point(event.scenePos().x(), event.scenePos().y())
|
||||
if self.is_valid_ship_pos(pos):
|
||||
self.movement_line.setPen(CONST.COLORS["green"])
|
||||
else:
|
||||
self.movement_line.setPen(CONST.COLORS["red"])
|
||||
|
||||
|
||||
|
||||
def sceneMousePressEvent(self, event: QGraphicsSceneMouseEvent):
|
||||
if self.state == QLiberationMapState.MOVING_UNIT:
|
||||
if event.buttons() == Qt.RightButton:
|
||||
@ -805,13 +845,9 @@ class QLiberationMap(QGraphicsView):
|
||||
# Set movement position for the cp
|
||||
pos = event.scenePos()
|
||||
point = Point(int(pos.x()), int(pos.y()))
|
||||
proj = Point(*self._scene_to_dcs_coords(point))
|
||||
proj = self._scene_to_dcs_coords(point)
|
||||
|
||||
# Check distance (max = 80 nm)
|
||||
distance = meter_to_nm(proj.distance_to_point(self.selected_cp.control_point.position))
|
||||
|
||||
# Check if point is in sea
|
||||
if self.game.theater.is_in_sea(proj) and distance < MAX_SHIP_DISTANCE:
|
||||
if self.is_valid_ship_pos(point):
|
||||
self.selected_cp.control_point.target_position = proj
|
||||
else:
|
||||
self.selected_cp.control_point.target_position = None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user