diff --git a/Controller/DrawingObject/FieldGroundDrawing.py b/Controller/DrawingObject/FieldGroundDrawing.py deleted file mode 100644 index 59d5d20..0000000 --- a/Controller/DrawingObject/FieldGroundDrawing.py +++ /dev/null @@ -1,28 +0,0 @@ -# Under MIT License, see LICENSE.txt -from Controller.DrawingObject.color import Color -from Controller.DrawingObject.BaseDrawingObject import BaseDrawingObject -from Controller.QtToolBox import QtToolBox - -__author__ = 'RoboCupULaval' - - -class FieldGroundDrawing(BaseDrawingObject): - def __init__(self): - BaseDrawingObject.__init__(self) - - def draw(self, painter): - if self.isVisible(): - # Dessine la pelouze - painter.setPen(QtToolBox.create_pen(is_hide=True)) - painter.setBrush(QtToolBox.create_brush(color=Color.DARK_GREEN_FIELD)) - painter.drawRect(0, 0, 9500, 6500) - x, y = QtToolBox.field_ctrl.get_top_left_to_screen() - width, height = QtToolBox.field_ctrl.get_size_to_screen() - width /= 10 - painter.setBrush(QtToolBox.create_brush(color=Color.GREEN_FIELD)) - for i in range(0, 10, 2): - painter.drawRect(x + width * i, y, width, height) - - @staticmethod - def get_datain_associated(): - return 'field-ground' diff --git a/Controller/DrawingObject/FieldLineDrawing.py b/Controller/DrawingObject/FieldLineDrawing.py index c18010e..5b1ed7a 100644 --- a/Controller/DrawingObject/FieldLineDrawing.py +++ b/Controller/DrawingObject/FieldLineDrawing.py @@ -1,6 +1,8 @@ # Under MIT License, see LICENSE.txt +import math from PyQt5 import QtCore +from PyQt5.QtGui import QPixmap, QPainter from Controller.DrawingObject.color import Color from Controller.DrawingObject.BaseDrawingObject import BaseDrawingObject @@ -11,43 +13,76 @@ class FieldLineDrawing(BaseDrawingObject): def __init__(self): BaseDrawingObject.__init__(self) + self.field_cache = QPixmap(1000, 1000) + self.field_painter = QPainter(self.field_cache) + def draw(self, painter): - if self.isVisible(): - painter.setBrush(QtToolBox.create_brush(is_visible=False)) + if self.isVisible() and QtToolBox.field_ctrl.need_redraw: + QtToolBox.field_ctrl.need_redraw = False + + # Grass drawing + self.drawGrass() - painter.setPen(QtToolBox.create_pen(color=Color.WHITE, + self.field_painter.setBrush(QtToolBox.create_brush(is_visible=False)) + + self.field_painter.setPen(QtToolBox.create_pen(color=Color.WHITE, style='SolidLine', width=QtToolBox.field_ctrl.line_width * QtToolBox.field_ctrl.ratio_screen)) for name, arc in QtToolBox.field_ctrl.field_arcs.items(): - self.drawArc(painter, arc) + self.drawArc(self.field_painter, arc) for name, line in QtToolBox.field_ctrl.field_lines.items(): - self.drawLine(painter, line) + self.drawLine(self.field_painter, line) # Dessine left goal - painter.setPen(QtToolBox.create_pen(color=Color.BLUE, + self.field_painter.setPen(QtToolBox.create_pen(color=Color.BLUE, style='SolidLine', width=50 * QtToolBox.field_ctrl.ratio_screen)) for name, line in QtToolBox.field_ctrl.field_goal_left.items(): - self.drawLine(painter, line) + self.drawLine(self.field_painter, line) # Dessine right goal - painter.setPen(QtToolBox.create_pen(color=Color.YELLOW, + self.field_painter.setPen(QtToolBox.create_pen(color=Color.YELLOW, style='SolidLine', width=50 * QtToolBox.field_ctrl.ratio_screen)) for name, line in QtToolBox.field_ctrl.field_goal_right.items(): - self.drawLine(painter, line) + self.drawLine(self.field_painter, line) + + painter.drawPixmap(0, 0, self.field_cache) + + def drawGrass(self, nb_lines = 10): + self.field_painter.setPen(QtToolBox.create_pen(is_hide=True)) + self.field_painter.setBrush(QtToolBox.create_brush(color=Color.DARK_GREEN_FIELD)) + self.field_painter.drawRect(0, 0, 9500, 6500) + self.field_painter.setBrush(QtToolBox.create_brush(color=Color.GREEN_FIELD)) + + width, height = QtToolBox.field_ctrl.field_length, QtToolBox.field_ctrl.field_width + xo, yo = width / 2, height / 2 + width_line = width / nb_lines + for i in range(0, nb_lines, 2): + # Top left + ax, ay, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(i * width_line - xo, -yo) + # Bot right + bx, by, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst((i + 1) * width_line -xo, height -yo) + self.field_painter.drawRect(ax, ay, bx-ax, by-ay) + def drawArc(self, painter, arc): - x, y, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(arc.center[0] - arc.radius, - arc.center[1] + arc.radius) - width = arc.radius * 2 * QtToolBox.field_ctrl.ratio_screen + # Top left + ax, ay, theta_off = QtToolBox.field_ctrl.convert_real_to_scene_pst(arc.center[0] - arc.radius, + arc.center[1] + arc.radius) + # Lower right + bx, by, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(arc.center[0] + arc.radius, + arc.center[1] - arc.radius) # Qt is weird and it require a rectangle to be defined to draw an arc - rect = QtCore.QRect(x, y, width, width) + rect = QtCore.QRect(ax, ay, bx-ax, by-ay) + + # If the screen is rotate, the angle is also rotated + theta_off_deg = theta_off * 180 / math.pi # Angle are calculate in 1/16th of a degree painter.drawArc(rect, - int(arc.start_angle) * 16, - int(arc.end_angle - arc.start_angle) * 16) + int(arc.start_angle - theta_off_deg) * 16, + int(arc.end_angle - arc.start_angle - theta_off_deg) * 16) def drawLine(self, painter, line): x1, y1, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(line.p1[0], line.p1[1]) diff --git a/Controller/DrawingObject/color.py b/Controller/DrawingObject/color.py index 60f0f36..db49a75 100644 --- a/Controller/DrawingObject/color.py +++ b/Controller/DrawingObject/color.py @@ -12,4 +12,6 @@ class Color: WHITE = ColorTuple(red=255, green=255, blue=255) DARK_GREEN_FIELD = ColorTuple(red=0, green=150, blue=0) GREEN_FIELD = ColorTuple(red=0, green=125, blue=0) - ORANGE = ColorTuple(red=255, green=100, blue=0) \ No newline at end of file + CLEAR_ORANGE = ColorTuple(red=238, green=239, blue=168) + ORANGE = ColorTuple(red=255, green=100, blue=0) + DARK_ORANGE = ColorTuple(red=125, green=69, blue=25) \ No newline at end of file diff --git a/Controller/FieldController.py b/Controller/FieldController.py index 1be5347..f9c80a0 100644 --- a/Controller/FieldController.py +++ b/Controller/FieldController.py @@ -1,5 +1,5 @@ # Under MIT License, see LICENSE.txt - +import math from math import cos, sin, atan2, sqrt, pi from Communication.messages_robocup_ssl_geometry_pb2 import SSL_GeometryFieldSize @@ -51,6 +51,7 @@ def __init__(self): self.ratio_screen = 1 / 10 self.is_x_axe_flipped = False self.is_y_axe_flipped = True + self.is_horizontal = True # Dimension du terrain self.margin = 250 # Marge au tour du terrain pour l'écran @@ -74,6 +75,10 @@ def __init__(self): self.field_goal_left = {} self.field_goal_right = {} + self.prev_field = None + + self.need_redraw = True + @property def line_width(self): return self._line_width @@ -124,27 +129,43 @@ def ratio_field_mobs(self, new_ratio): def convert_real_to_scene_pst(self, x, y, theta=0.0): """ Convertit les coordonnées réelles en coordonnées du terrain """ + + if not self.is_horizontal: + x, y = -y, x + theta += math.pi / 2 rot_x = cos(theta) rot_y = sin(theta) + if self.is_x_axe_flipped: x *= -1 rot_x *= -1 if self.is_y_axe_flipped: y *= -1 rot_y *= -1 - x = (x + self.field_length / 2 + self.margin) * self.ratio_screen + self._camera_position[0] - y = (y + self.field_width / 2 + self.margin) * self.ratio_screen + self._camera_position[1] - return x, y, atan2(rot_y, rot_x) + + x_screen = (x + self.field_length / 2 + self.margin) * self.ratio_screen + self._camera_position[0] + y_screen = (y + self.field_width / 2 + self.margin) * self.ratio_screen + self._camera_position[1] + return x_screen, y_screen, atan2(rot_y, rot_x) def convert_screen_to_real_pst(self, x, y): """ Convertir les coordonnées du terrain en coordonnées réelles """ - x_2 = (x - self._camera_position[0]) / self.ratio_screen - self.field_length / 2 - self.margin - y_2 = (y - self._camera_position[1]) / self.ratio_screen - self.field_width / 2 - self.margin + x_real = (x - self._camera_position[0]) / self.ratio_screen - self.field_length / 2 - self.margin + y_real = (y - self._camera_position[1]) / self.ratio_screen - self.field_width / 2 - self.margin + if self.is_x_axe_flipped: - x_2 *= -1 + x_real *= -1 if self.is_y_axe_flipped: - y_2 *= -1 - return x_2, y_2 + y_real *= -1 + + if self.is_horizontal: + return x_real, y_real + else: + return y_real, -x_real + + def set_horizontal(self, val: bool): + if self.is_horizontal != val: + self.need_redraw = True + self.is_horizontal = val def flip_x_axe(self): """ Retourne l'axe des X du terrain """ @@ -166,6 +187,7 @@ def get_size_to_screen(self): def drag_camera(self, x, y): """ Déplacement de la caméra """ + self.need_redraw = True if not self._lock_camera: if self._cursor_last_pst is None: self._cursor_last_pst = x, y @@ -196,7 +218,9 @@ def _limit_camera(self): def zoom(self, x, y, scroll_delta_y): """ Zoom la caméra de +10% (+/- un facteur de ralentissement)""" - SCALE_CHANGE = 0.1 + SCALE_CHANGE = 0.05 + + self.need_redraw = True if not self._lock_camera and self.ratio_screen < 0.6: rx, ry = self.convert_screen_to_real_pst(x, y) self.ratio_screen *= 1 + SCALE_CHANGE * scroll_delta_y / self.scroll_slowing_factor @@ -206,7 +230,9 @@ def zoom(self, x, y, scroll_delta_y): def dezoom(self, x, y, scroll_delta_y): """ Dézoom la caméra de -10% (+/- un facteur de ralentissement)""" - SCALE_CHANGE = 0.1 + SCALE_CHANGE = 0.05 + + self.need_redraw = True if not self._lock_camera and self.ratio_screen > 0.03: rx, ry = self.convert_screen_to_real_pst(x, y) self.ratio_screen /= 1 + SCALE_CHANGE * -scroll_delta_y / self.scroll_slowing_factor @@ -237,6 +263,9 @@ def set_field_size(self, field: SSL_GeometryFieldSize): def _set_field_size_new(self, field: SSL_GeometryFieldSize): self.field_lines = self._convert_field_line_segments(field.field_lines) self.field_arcs = self._convert_field_circular_arc(field.field_arcs) + if self.prev_field == field: + return + self.need_redraw = True # Only redraw when receiving new geometry message self.field_goal_left = \ {name: line for name, line in self.field_lines.items() if name.startswith("LeftGoal") and name != "LeftGoalLine"} @@ -259,6 +288,8 @@ def _set_field_size_new(self, field: SSL_GeometryFieldSize): self._center_circle_radius = self.field_arcs['CenterCircle'].radius self._defense_stretch = self.field_lines['LeftPenaltyStretch'].length / 2 + self.prev_field = field + def _convert_field_circular_arc(self, field_arcs): return {arc.name: FieldCircularArc(arc) for arc in field_arcs} diff --git a/Controller/MainController.py b/Controller/MainController.py index 92c078f..63b03a2 100644 --- a/Controller/MainController.py +++ b/Controller/MainController.py @@ -186,6 +186,14 @@ def init_menubar(self): fieldMenu.addSeparator() + horiAction = QAction("Changer à l'horizontal", self) + horiAction.triggered.connect(self.set_screen_to_horizontal) + fieldMenu.addAction(horiAction) + + vertAction = QAction("Changer à la vertical", self) + vertAction.triggered.connect(self.set_screen_to_vertical) + fieldMenu.addAction(vertAction) + flipXAction = QAction("Changer l'axe des X", self, checkable=True) flipXAction.triggered.connect(self.flip_screen_x_axe) fieldMenu.addAction(flipXAction) @@ -360,6 +368,14 @@ def toggle_full_screen(self): else: self.setWindowState(Qt.WindowActive) + def set_screen_to_horizontal(self): + """ Met l'écran à l'horizontal """ + QtToolBox.field_ctrl.set_horizontal(True) + + def set_screen_to_vertical(self): + """ Met l'écran à la vertical """ + QtToolBox.field_ctrl.set_horizontal(False) + def flip_screen_x_axe(self): """ Bascule l'axe des X de l'écran """ QtToolBox.field_ctrl.flip_x_axe() diff --git a/Controller/MobileObject/BallMob.py b/Controller/MobileObject/BallMob.py index a147571..43e05d3 100644 --- a/Controller/MobileObject/BallMob.py +++ b/Controller/MobileObject/BallMob.py @@ -1,8 +1,11 @@ # Under MIT License, see LICENSE.txt +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPainterPath from Controller.DrawingObject.color import Color from Controller.MobileObject.BaseMobileObject import BaseMobileObject from Controller.QtToolBox import QtToolBox +from collections import deque __author__ = 'RoboCupULaval' @@ -12,23 +15,44 @@ def __init__(self, x=0, y=0): BaseMobileObject.__init__(self, x, y) self._radius = 43 / 2 + NB_BALL_POSE_TO_KEEP = 50 + self.queue = deque(maxlen=NB_BALL_POSE_TO_KEEP) + @property def radius(self): return self._radius * QtToolBox.field_ctrl.ratio_field_mobs def draw(self, painter): if self.isVisible(): + bx, by, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(self._x, self._y) + + self.queue.appendleft((self._x, self._y)) + + # Draw ball's trail + path_painter = QPainterPath() + path_painter.moveTo(bx, by) + for rx, ry in self.queue: + x, y, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(rx, ry) + path_painter.lineTo(x, y) + + painter.setBrush(Qt.NoBrush) + painter.setPen(QtToolBox.create_pen(color=Color.CLEAR_ORANGE, + style='SolidLine', + width=2)) + painter.drawPath(path_painter) + + # Draw ball painter.setBrush(QtToolBox.create_brush(color=Color.ORANGE)) painter.setPen(QtToolBox.create_pen(color=Color.BLACK, style='SolidLine', width=1)) - x, y, _ = QtToolBox.field_ctrl.convert_real_to_scene_pst(self._x, self._y) radius = self.radius * QtToolBox.field_ctrl.ratio_screen - painter.drawEllipse(x - radius, - y - radius, + painter.drawEllipse(bx - radius, + by - radius, radius * 2, radius * 2) + @staticmethod def get_datain_associated(): return 'ball' diff --git a/View/FieldView.py b/View/FieldView.py index 4255bdf..1388c03 100644 --- a/View/FieldView.py +++ b/View/FieldView.py @@ -165,10 +165,6 @@ def draw_effects(self, painter): for effect in list_effect: effect.draw(painter) - def draw_field_ground(self, painter): - """ Dessine le sol du terrain """ - self.graph_draw['field-ground'].draw(painter) - def draw_mobs(self, painter): """ Dessine les objets mobiles """ self.graph_mobs['ball'].draw(painter) @@ -204,8 +200,6 @@ def init_graph_mobs(self): max_robots_in_team = 16 # TODO : Variable globale? # Élément graphique pour les dessins - self.graph_draw['field-ground'] = self.controller.get_drawing_object('field-ground')() - self.graph_draw['field-ground'].show() self.graph_draw['field-lines'] = self.controller.get_drawing_object('field-lines')() self.graph_draw['field-lines'].show() self.graph_draw['frame-rate'] = self.controller.get_drawing_object('frame-rate')() @@ -450,11 +444,11 @@ def paintEvent(self, e): painter = QPainter() painter.begin(self) painter.setBackground(QtToolBox.create_brush()) - self.draw_field_ground(painter) + self.draw_field_lines(painter) self.draw_map(painter) self.draw_multiple_points(painter) self.draw_effects(painter) - self.draw_field_lines(painter) + # self.draw_field_lines(painter) if self.slingshot_mode: self.draw_slingshot(painter) self.draw_mobs(painter) diff --git a/View/ParamView.py b/View/ParamView.py index a792ed8..9731a2a 100644 --- a/View/ParamView.py +++ b/View/ParamView.py @@ -286,7 +286,6 @@ def _apply_param(self): self.form_ratio_mobs.setStyleSheet(style_good) QtToolBox.field_ctrl.ratio_field_mobs = float(self.form_ratio_mobs.text()) except Exception as e: - print(e) self.form_ratio_mobs.setStyleSheet(style_bad) is_wrong = True