From 1b96e87cf2c737c796e5140c052a9e1f8ce50e4e Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 2 Jul 2024 13:20:43 +0200 Subject: [PATCH 1/8] feat: add ctrl_key for faster sim_view + change inverted sim_view controls --- robot_sf/render/sim_view.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 5ea957e..4846f58 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -164,15 +164,19 @@ def _handle_video_resize(self, e): def _handle_keydown(self, e): """Handle key presses for the simulation view.""" + new_offset = 10 + if pygame.key.get_mods() & pygame.KMOD_CTRL: + new_offset = 100 + key_action_map = { # scale the view pygame.K_PLUS: lambda: setattr(self, 'scaling', self.scaling + 1), pygame.K_MINUS: lambda: setattr(self, 'scaling', max(self.scaling - 1, 1)), # move the view - pygame.K_LEFT: lambda: self.offset.__setitem__(0, self.offset[0] - 10), - pygame.K_RIGHT: lambda: self.offset.__setitem__(0, self.offset[0] + 10), - pygame.K_UP: lambda: self.offset.__setitem__(1, self.offset[1] - 10), - pygame.K_DOWN: lambda: self.offset.__setitem__(1, self.offset[1] + 10), + pygame.K_LEFT: lambda: self.offset.__setitem__(0, self.offset[0] + new_offset), + pygame.K_RIGHT: lambda: self.offset.__setitem__(0, self.offset[0] - new_offset), + pygame.K_UP: lambda: self.offset.__setitem__(1, self.offset[1] + new_offset), + pygame.K_DOWN: lambda: self.offset.__setitem__(1, self.offset[1] - new_offset), # reset the view pygame.K_r: lambda: self.offset.__setitem__(slice(None), (0, 0)), } From 29d59bb9cf1ded43baa148389e5eed78a9251c94 Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 9 Jul 2024 16:07:31 +0200 Subject: [PATCH 2/8] feat: draw spawn/goal zones + update controls for viewing --- robot_sf/render/sim_view.py | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 4846f58..2f9356c 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -27,6 +27,8 @@ BACKGROUND_COLOR = (255, 255, 255) BACKGROUND_COLOR_TRANSP = (255, 255, 255, 128) OBSTACLE_COLOR = (20, 30, 20, 128) +PED_SPAWN_COLOR = (255, 204, 203) +PED_GOAL_COLOR = (144, 238, 144) PED_COLOR = (255, 50, 50) PED_ROUTE_COLOR = (0, 0, 255) ROBOT_ROUTE_COLOR = (30, 30, 255) @@ -164,14 +166,19 @@ def _handle_video_resize(self, e): def _handle_keydown(self, e): """Handle key presses for the simulation view.""" - new_offset = 10 + new_offset = 100 + new_scaling = 1 if pygame.key.get_mods() & pygame.KMOD_CTRL: - new_offset = 100 + new_offset = 250 + new_scaling = 10 + + if pygame.key.get_mods() & pygame.KMOD_ALT: + new_offset = 10 key_action_map = { # scale the view - pygame.K_PLUS: lambda: setattr(self, 'scaling', self.scaling + 1), - pygame.K_MINUS: lambda: setattr(self, 'scaling', max(self.scaling - 1, 1)), + pygame.K_PLUS: lambda: setattr(self, 'scaling', self.scaling + new_scaling), + pygame.K_MINUS: lambda: setattr(self, 'scaling', max(self.scaling - new_scaling, 1)), # move the view pygame.K_LEFT: lambda: self.offset.__setitem__(0, self.offset[0] + new_offset), pygame.K_RIGHT: lambda: self.offset.__setitem__(0, self.offset[0] - new_offset), @@ -243,6 +250,10 @@ def render(self, state: VisualizableSimState): self._draw_robot_routes() if self.map_def.ped_routes: self._draw_pedestrian_routes() + if self.map_def.ped_spawn_zones: + self._draw_spawn_zones() + if self.map_def.ped_goal_zones: + self._draw_goal_zones() self._draw_grid() @@ -301,6 +312,28 @@ def _draw_obstacles(self): # Draw the obstacle as a polygon on the screen pygame.draw.polygon(self.screen, OBSTACLE_COLOR, scaled_vertices) + def _draw_spawn_zones(self): + # Iterate over each spawn_zone in the list of spawn_zones + for spawn_zone in self.map_def.ped_spawn_zones: + # Scale and offset the vertices of the zones + vertices_np = np.array(spawn_zone) + scaled_vertices = [( + self._scale_tuple((x, y)) + ) for x, y in vertices_np] + # Draw the spawn zone as a polygon on the screen + pygame.draw.polygon(self.screen, PED_SPAWN_COLOR, scaled_vertices) + + def _draw_goal_zones(self): + # Iterate over each goal_zone in the list of goal_zones + for goal_zone in self.map_def.ped_goal_zones: + # Scale and offset the vertices of the goal zones + vertices_np = np.array(goal_zone) + scaled_vertices = [( + self._scale_tuple((x, y)) + ) for x, y in vertices_np] + # Draw the goal_zone as a polygon on the screen + pygame.draw.polygon(self.screen, PED_GOAL_COLOR, scaled_vertices) + def _augment_goal_position(self, robot_goal: Vec2D): # TODO: display pedestrians with an image instead of a circle pygame.draw.circle( @@ -378,6 +411,13 @@ def _draw_robot_routes(self): width = 1 ) + def _draw_coordinates(self, x, y): + """ + Draws the coordinates (x, y) on the screen. + """ + text = self.font.render(f'({x}, {y})', False, TEXT_COLOR) + self.screen.blit(text, (x, y)) + def _augment_timestep(self, timestep: int): # TODO: show map name as well text = f'step: {timestep}' From e74576be7aa4e1d48277f7292064ebf7340d4fb8 Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 9 Jul 2024 16:08:45 +0200 Subject: [PATCH 3/8] feat: Add new maps --- examples/simulate_with_svg_map.py | 4 +- maps/svg_maps/03_mid_object.svg | 129 ++++++++++++++++++++++++++ maps/svg_maps/04_small_mid_object.svg | 129 ++++++++++++++++++++++++++ 3 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 maps/svg_maps/03_mid_object.svg create mode 100644 maps/svg_maps/04_small_mid_object.svg diff --git a/examples/simulate_with_svg_map.py b/examples/simulate_with_svg_map.py index 956344e..947d645 100644 --- a/examples/simulate_with_svg_map.py +++ b/examples/simulate_with_svg_map.py @@ -58,7 +58,9 @@ def main(): """Simulate a random policy with a map defined in SVG format.""" logger.info("Simulating a random policy with the map.") - svg_file = "maps/svg_maps/02_simple_maps.svg" + #svg_file = "maps/svg_maps/02_simple_maps.svg" + #svg_file = "maps/svg_maps/03_mid_object.svg" + svg_file = "maps/svg_maps/04_small_mid_object.svg" logger.info("Converting SVG map to MapDefinition object.") logger.info(f"SVG file: {svg_file}") diff --git a/maps/svg_maps/03_mid_object.svg b/maps/svg_maps/03_mid_object.svg new file mode 100644 index 0000000..7d4ee70 --- /dev/null +++ b/maps/svg_maps/03_mid_object.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/maps/svg_maps/04_small_mid_object.svg b/maps/svg_maps/04_small_mid_object.svg new file mode 100644 index 0000000..ee04e78 --- /dev/null +++ b/maps/svg_maps/04_small_mid_object.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + From ea64bd25fab0ff5fa2de2abb2c478f12c6c1be9c Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 9 Jul 2024 16:14:08 +0200 Subject: [PATCH 4/8] feat: Option to use dedicated spawn/goal zones --- robot_sf/nav/svg_map_parser.py | 63 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/robot_sf/nav/svg_map_parser.py b/robot_sf/nav/svg_map_parser.py index c390aac..2103a80 100644 --- a/robot_sf/nav/svg_map_parser.py +++ b/robot_sf/nav/svg_map_parser.py @@ -47,6 +47,8 @@ def _get_svg_info(self): """ Extracts path and rectangle information from an SVG file. + It is important that the SVG file uses absolute coordinates for the paths. + This method finds all 'path' and 'rect' elements in the SVG file and extracts their coordinates, labels, and ids. The information is stored in the 'path_info' and 'rect_info' attributes of the SvgMapConverter instance. @@ -162,13 +164,33 @@ def _info_to_mapdefintion(self) -> MapDefinition: ped_crowded_zones: List[Rect] = [] ped_routes: List[GlobalRoute] = [] + for rect in self.rect_info: + if rect.label == 'robot_spawn_zone': + robot_spawn_zones.append(rect.get_zone()) + elif rect.label == 'ped_spawn_zone': + ped_spawn_zones.append(rect.get_zone()) + elif rect.label == 'robot_goal_zone': + robot_goal_zones.append(rect.get_zone()) + elif rect.label == 'bound': + bounds.append(rect.get_zone()) + elif rect.label == 'ped_goal_zone': + ped_goal_zones.append(rect.get_zone()) + elif rect.label == 'obstacle': + obstacles.append(obstacle_from_svgrectangle(rect)) + elif rect.label == 'ped_crowded_zone': + ped_crowded_zones.append(rect.get_zone()) + else: + logger.error( + f"Unknown label <{rect.label}> in id <{rect.id_}>" + ) + for path in self.path_info: # check the label of the path if path.label == 'obstacle': # Convert the coordinates to a list of vertices vertices = path.coordinates.tolist() - + # Check if the first and last vertices are the same if not np.array_equal(vertices[0], vertices[-1]): logger.warning( @@ -183,18 +205,27 @@ def _info_to_mapdefintion(self) -> MapDefinition: # Append the obstacle to the list obstacles.append(Obstacle(vertices)) - elif path.label == 'ped_route': + elif 'ped_route' in path.label: # Convert the coordinates to a list of vertices vertices = path.coordinates.tolist() + # ped_routes have a label of the form 'ped_route__' + numbers = re.findall(r'\d+', path.label) + if numbers: + spawn = int(numbers[0]) + goal = int(numbers[1]) + else: + spawn = 0 + goal = 0 + # Append the obstacle to the list ped_routes.append( GlobalRoute( - spawn_id=0, # TODO: What is this? value is arbitrary - goal_id=0, # TODO: What is this? value is arbitrary + spawn_id=spawn, + goal_id=goal, waypoints=vertices, - spawn_zone=(vertices[0], 0, 0), # TODO - goal_zone=(vertices[-1], 0, 0) # TODO + spawn_zone=ped_spawn_zones[spawn] if ped_spawn_zones else (vertices[0],0,0), + goal_zone=ped_goal_zones[goal] if ped_goal_zones else (vertices[-1],0,0) )) elif path.label == 'robot_route': @@ -225,26 +256,6 @@ def _info_to_mapdefintion(self) -> MapDefinition: f"Unknown label <{path.label}> in id <{path.id}>" ) - for rect in self.rect_info: - if rect.label == 'robot_spawn_zone': - robot_spawn_zones.append(rect.get_zone()) - elif rect.label == 'ped_spawn_zone': - ped_spawn_zones.append(rect.get_zone()) - elif rect.label == 'robot_goal_zone': - robot_goal_zones.append(rect.get_zone()) - elif rect.label == 'bound': - bounds.append(rect.get_zone()) - elif rect.label == 'ped_goal_zone': - ped_goal_zones.append(rect.get_zone()) - elif rect.label == 'obstacle': - obstacles.append(obstacle_from_svgrectangle(rect)) - elif rect.label == 'ped_crowded_zone': - ped_crowded_zones.append(rect.get_zone()) - else: - logger.error( - f"Unknown label <{rect.label}> in id <{rect.id_}>" - ) - if not obstacles: From 8d55ed56e38ee95223ebb12f5c0d52cecb940c7b Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Thu, 11 Jul 2024 15:07:04 +0200 Subject: [PATCH 5/8] feat: example file to showcase recording --- examples/view_recording.py | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 examples/view_recording.py diff --git a/examples/view_recording.py b/examples/view_recording.py new file mode 100644 index 0000000..4b9774a --- /dev/null +++ b/examples/view_recording.py @@ -0,0 +1,71 @@ +"""Simulate a random policy with a map defined in SVG format and view the recording.""" +import os +from pathlib import Path + +import numpy as np +from loguru import logger + +from robot_sf.nav.svg_map_parser import SvgMapConverter +from robot_sf.nav.map_config import MapDefinition, MapDefinitionPool +from robot_sf.gym_env.env_config import SimulationSettings, EnvSettings +from robot_sf.gym_env.robot_env import RobotEnv +from robot_sf.sensor.sensor_fusion import OBS_RAYS, OBS_DRIVE_STATE +from robot_sf.robot.differential_drive import DifferentialDriveSettings +from robot_sf.render.playback_recording import load_states_and_visualize + + + + +logger.info("Simulate a random policy with a map defined in SVG format.") + +def test_simulation(map_definition: MapDefinition): + """Test the simulation with a random policy.""" + + logger.info("Creating the environment.") + env_config = EnvSettings( + map_pool=MapDefinitionPool(map_defs={"my_map": map_definition}) + ) + env = RobotEnv(env_config, debug=True, recording_enabled=True) # Activate recording + + env.reset() + + logger.info("Simulating the random policy.") + for _ in range(1000): + action = env.action_space.sample() + env.step(action) + env.render() + + env.reset() # Save the recording + env.exit() + +def convert_map(svg_file: str): + """Create MapDefinition from svg file.""" + + logger.info("Converting SVG map to MapDefinition object.") + logger.info(f"SVG file: {svg_file}") + + converter = SvgMapConverter(svg_file) + return converter.map_definition + +def get_file(): + """Get the latest recorded file.""" + + filename = max( + os.listdir('recordings'), key=lambda x: os.path.getctime(os.path.join('recordings', x))) + return Path('recordings', filename) + + + +def main(): + """Simulate a random policy with a map defined in SVG format and view the recording.""" + + # Create example recording + map_def = convert_map("maps/svg_maps/02_simple_maps.svg") + test_simulation(map_def) + + # Load the states from the file and view the recording + load_states_and_visualize(get_file()) + + +if __name__ == "__main__": + main() From 59d9296223f50e4f023d7ee1063b826c890ce331 Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Thu, 11 Jul 2024 15:08:36 +0200 Subject: [PATCH 6/8] fix: enabled keyevents for recording --- robot_sf/gym_env/robot_env.py | 1 + robot_sf/render/playback_recording.py | 5 ++++- robot_sf/render/sim_view.py | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/robot_sf/gym_env/robot_env.py b/robot_sf/gym_env/robot_env.py index 7bb481e..1e6057e 100644 --- a/robot_sf/gym_env/robot_env.py +++ b/robot_sf/gym_env/robot_env.py @@ -63,6 +63,7 @@ def __init__( a dictionary as input and returns a float as reward. - debug (bool): If True, enables debugging information such as visualizations. + - recording_enabled (bool): If True, enables recording of the simulation """ # Environment configuration details diff --git a/robot_sf/render/playback_recording.py b/robot_sf/render/playback_recording.py index a3d945b..57af8ef 100644 --- a/robot_sf/render/playback_recording.py +++ b/robot_sf/render/playback_recording.py @@ -37,10 +37,13 @@ def visualize_states( use the SimulationView to render a list of states on the recorded map defintion """ - sim_view = SimulationView(map_def=map_def) + sim_view = SimulationView(map_def=map_def, caption='RobotSF Recording') + sim_view.show() # to activate key_events for state in states: sim_view.render(state) + sim_view.exit() # to automatically close the window + def load_states_and_visualize(filename: str): """ load a list of states from a file and visualize them diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 5ea957e..e09178f 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -76,6 +76,7 @@ class SimulationView: font: pygame.font.Font = field(init=False) redraw_needed: bool = field(init=False, default=False) offset: np.array = field(init=False, default=np.array([0, 0])) + caption: str='RobotSF Simulation' """The offset is already uses `scaling` as a factor.""" @property @@ -88,7 +89,7 @@ def __post_init__(self): pygame.font.init() self.screen = pygame.display.set_mode( (self.width, self.height), pygame.RESIZABLE) - pygame.display.set_caption('RobotSF Simulation') + pygame.display.set_caption(self.caption) self.font = pygame.font.SysFont('Consolas', 14) self.surface_obstacles = self.preprocess_obstacles() self.clear() From 9188e28175a9a7160e8f3460adb4ef9591f5c2a6 Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 16 Jul 2024 17:33:06 +0200 Subject: [PATCH 7/8] feat: Focus on robot + help key --- robot_sf/render/sim_view.py | 59 ++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index e09178f..0f3088e 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -77,6 +77,8 @@ class SimulationView: redraw_needed: bool = field(init=False, default=False) offset: np.array = field(init=False, default=np.array([0, 0])) caption: str='RobotSF Simulation' + focus_on_robot: bool = False + display_help: bool = False """The offset is already uses `scaling` as a factor.""" @property @@ -176,6 +178,10 @@ def _handle_keydown(self, e): pygame.K_DOWN: lambda: self.offset.__setitem__(1, self.offset[1] + 10), # reset the view pygame.K_r: lambda: self.offset.__setitem__(slice(None), (0, 0)), + # focus on the robot + pygame.K_f: lambda: setattr(self, 'focus_on_robot', not self.focus_on_robot), + # display help + pygame.K_h: lambda: setattr(self, 'display_help', not self.display_help), } if e.key in key_action_map: @@ -228,8 +234,8 @@ def render(self, state: VisualizableSimState): self.surface_obstacles = self.preprocess_obstacles() self.redraw_needed = False - # TODO: is it correct to scale the ped state here? - state = self._scale_pedestrian_state(state) + # Adjust the view based on the focus + self._move_camera(state) self.screen.fill(BACKGROUND_COLOR) @@ -251,8 +257,12 @@ def render(self, state: VisualizableSimState): self._augment_goal_position(state.action.robot_goal) self._draw_pedestrians(state.pedestrian_positions) self._draw_robot(state.robot_pose) + + # information self._augment_timestep(state.timestep) self._add_text(state.timestep, state) + if self.display_help: + self._add_help_text() # update the display pygame.display.update() @@ -264,11 +274,13 @@ def _resize_window(self): (self.width, self.height), pygame.RESIZABLE) self.screen.blit(old_surface, (0, 0)) - def _scale_pedestrian_state(self, state: VisualizableSimState) \ - -> Tuple[VisualizableSimState, Tuple[float, float]]: - state.pedestrian_positions *= self.scaling - state.ped_actions *= self.scaling - return state + def _move_camera(self, state: VisualizableSimState): + """ Moves the camera based on the focused object.""" + if self.focus_on_robot: + r_x, r_y = state.robot_pose[0] + self.offset[0] = int(r_x * self.scaling - self.width / 2) * -1 + self.offset[1] = int(r_y * self.scaling - self.height / 2) * -1 + # TODO: implement moving for trained pedestrian def _draw_robot(self, pose: RobotPose): # TODO: display robot with an image instead of a circle @@ -284,7 +296,7 @@ def _draw_pedestrians(self, ped_pos: np.ndarray): pygame.draw.circle( self.screen, PED_COLOR, - (ped_x+self.offset[0], ped_y+self.offset[1]), + self._scale_tuple((ped_x, ped_y)), self.ped_radius * self.scaling ) @@ -342,8 +354,8 @@ def _augment_ped_actions(self, ped_actions: np.ndarray): pygame.draw.line( self.screen, PED_ACTION_COLOR, - p1+self.offset, - p2+self.offset, + self._scale_tuple(p1), + self._scale_tuple(p2), width=3 ) @@ -389,7 +401,8 @@ def _add_text(self, timestep: int, state: VisualizableSimState): f'y-offset: {self.offset[1]/self.scaling:.2f}', f'RobotPose: {state.robot_pose}', f'RobotAction: {state.action.robot_action}', - f'RobotGoal: {state.action.robot_goal}' + f'RobotGoal: {state.action.robot_goal}', + '(Press h for help)', ] for i, text in enumerate(text_lines): text_surface = self.font.render(text, False, TEXT_COLOR) @@ -399,6 +412,30 @@ def _add_text(self, timestep: int, state: VisualizableSimState): ) self.screen.blit(text_surface, pos) + def _add_help_text(self): + text_lines = [ + 'Move camera: arrow keys', + 'Move fast: CTRL + arrow keys', + 'Move slow: ALT + arrow keys', + 'Reset view: r', + 'Focus robot: f', + 'Scale up: +', + 'Scale down: -' , + 'Help: h', + ] + + # Determine max width of the text + text_surface = self.font.render(text_lines[1], False, TEXT_COLOR) + width = text_surface.get_width() + 10 + + for i, text in enumerate(text_lines): + text_surface = self.font.render(text, False, TEXT_COLOR) + pos = ( + self.width - width, + self._timestep_text_pos[1] + i * self.font.get_linesize() + ) + self.screen.blit(text_surface, pos) + def _draw_grid( self, grid_increment: int=50, From f51f8b7bd57902c9dd81e6d1f2472c6333f5a24e Mon Sep 17 00:00:00 2001 From: Julius Miller Date: Tue, 16 Jul 2024 17:36:26 +0200 Subject: [PATCH 8/8] fix: Delete obsolete preprocess_obstacles --- robot_sf/render/sim_view.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 0f3088e..0a50477 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -93,7 +93,6 @@ def __post_init__(self): (self.width, self.height), pygame.RESIZABLE) pygame.display.set_caption(self.caption) self.font = pygame.font.SysFont('Consolas', 14) - self.surface_obstacles = self.preprocess_obstacles() self.clear() def _scale_tuple(self, tup: Tuple[float, float]) -> Tuple[float, float]: @@ -102,39 +101,6 @@ def _scale_tuple(self, tup: Tuple[float, float]) -> Tuple[float, float]: y = tup[1] * self.scaling + self.offset[1] return (x, y) - def preprocess_obstacles(self) -> pygame.Surface: - # Scale the vertices of the obstacles - obst_vertices = [o.vertices_np * self.scaling for o in self.map_def.obstacles] - - # Initialize the minimum and maximum x and y coordinates - min_x, max_x, min_y, max_y = np.inf, -np.inf, np.inf, -np.inf - - # Find the minimum and maximum x and y coordinates among all the obstacles - for vertices in obst_vertices: - min_x = min(np.min(vertices[:, 0]), min_x) - max_x = max(np.max(vertices[:, 0]), max_x) - min_y = min(np.min(vertices[:, 1]), min_y) - max_y = max(np.max(vertices[:, 1]), max_y) - - # Calculate the width and height of the surface needed to draw the obstacles - width, height = max_x - min_x, max_y - min_y - - # Create a new surface with the calculated width and height - surface = pygame.Surface((width, height), pygame.SRCALPHA) - - # Fill the surface with a transparent background color - surface.fill(BACKGROUND_COLOR_TRANSP) - - # Draw each obstacle on the surface - for vertices in obst_vertices: - # Shift the vertices so that the minimum x and y coordinates are 0 - shifted_vertices = vertices - [min_x, min_y] - # Draw the obstacle as a polygon with the shifted vertices - pygame.draw.polygon(surface, OBSTACLE_COLOR, [(x, y) for x, y in shifted_vertices]) - - # Return the surface with the drawn obstacles - return surface - def show(self): """ Starts a separate thread to process the event queue and handles SIGINT signal. @@ -231,7 +197,6 @@ def render(self, state: VisualizableSimState): self._resize_window() self.size_changed = False if self.redraw_needed: - self.surface_obstacles = self.preprocess_obstacles() self.redraw_needed = False # Adjust the view based on the focus