# core/renderer.py """Handles all rendering operations.""" import pygame import math from config.constants import * from world.base.brain import CellBrain class Renderer: def __init__(self, screen): self.screen = screen def clear_screen(self): """Clear the screen with a black background.""" self.screen.fill(BLACK) def draw_grid(self, camera, showing_grid=True): """Draw the reference grid.""" if not showing_grid: return # Calculate effective cell size with zoom effective_cell_size = CELL_SIZE * camera.zoom # Calculate grid boundaries in world coordinates (centered at 0,0) grid_world_width = GRID_WIDTH * effective_cell_size grid_world_height = GRID_HEIGHT * effective_cell_size # Calculate grid position relative to camera (with grid centered at 0,0) grid_center_x = SCREEN_WIDTH // 2 - camera.x * camera.zoom grid_center_y = SCREEN_HEIGHT // 2 - camera.y * camera.zoom grid_left = grid_center_x - grid_world_width // 2 grid_top = grid_center_y - grid_world_height // 2 grid_right = grid_left + grid_world_width grid_bottom = grid_top + grid_world_height # Check if grid is visible on screen if (grid_right < 0 or grid_left > SCREEN_WIDTH or grid_bottom < 0 or grid_top > SCREEN_HEIGHT): return # Fill the grid area with dark gray background grid_rect = pygame.Rect( max(0, grid_left), max(0, grid_top), min(SCREEN_WIDTH, grid_right) - max(0, grid_left), min(SCREEN_HEIGHT, grid_bottom) - max(0, grid_top), ) if grid_rect.width > 0 and grid_rect.height > 0: pygame.draw.rect(self.screen, DARK_GRAY, grid_rect) # Draw grid lines only if zoom is high enough if effective_cell_size > 4: self._draw_grid_lines(grid_left, grid_top, grid_right, grid_bottom, effective_cell_size) def _draw_grid_lines(self, grid_left, grid_top, grid_right, grid_bottom, effective_cell_size): """Draw the grid lines.""" vertical_lines = [] horizontal_lines = [] for i in range(max(GRID_WIDTH, GRID_HEIGHT) + 1): # Vertical lines if i <= GRID_WIDTH: line_x = grid_left + i * effective_cell_size if 0 <= line_x <= SCREEN_WIDTH: start_y = max(0, grid_top) end_y = min(SCREEN_HEIGHT, grid_bottom) if start_y < end_y: vertical_lines.append(((line_x, start_y), (line_x, end_y))) # Horizontal lines if i <= GRID_HEIGHT: line_y = grid_top + i * effective_cell_size if 0 <= line_y <= SCREEN_HEIGHT: start_x = max(0, grid_left) end_x = min(SCREEN_WIDTH, grid_right) if start_x < end_x: horizontal_lines.append(((start_x, line_y), (end_x, line_y))) # Draw all lines for start, end in vertical_lines: pygame.draw.line(self.screen, GRAY, start, end) for start, end in horizontal_lines: pygame.draw.line(self.screen, GRAY, start, end) def render_world(self, world, camera): """Render all world objects.""" world.render_all(camera, self.screen) def render_interaction_radius(self, world, camera, selected_objects, show_radius=False): """Render interaction radius and debug vectors for objects.""" if not show_radius: return for obj in world.get_objects(): obj_x, obj_y = obj.position.get_position() radius = obj.interaction_radius if radius > 0 and camera.is_in_view(obj_x, obj_y, margin=radius): if selected_objects and obj not in selected_objects: continue screen_x, screen_y = camera.world_to_screen(obj_x, obj_y) screen_radius = int(radius * camera.zoom) if screen_radius > 0: # Draw interaction radius circle pygame.draw.circle(self.screen, RED, (screen_x, screen_y), screen_radius, 1) # Draw direction arrow self._draw_direction_arrow(obj, screen_x, screen_y, camera) # Draw debug vectors self._draw_debug_vectors(obj, screen_x, screen_y, camera) def _draw_direction_arrow(self, obj, screen_x, screen_y, camera): """Draw direction arrow for an object.""" rotation_angle = obj.rotation.get_rotation() arrow_length = obj.max_visual_width / 2 * camera.zoom end_x = screen_x + arrow_length * math.cos(math.radians(rotation_angle)) end_y = screen_y + arrow_length * math.sin(math.radians(rotation_angle)) # Draw arrow line pygame.draw.line(self.screen, WHITE, (screen_x, screen_y), (end_x, end_y), 2) # Draw arrowhead tip_size = DIRECTION_TIP_SIZE * camera.zoom left_tip_x = end_x - tip_size * math.cos(math.radians(rotation_angle + 150 + 180)) left_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle + 150 + 180)) right_tip_x = end_x - tip_size * math.cos(math.radians(rotation_angle - 150 + 180)) right_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle - 150 + 180)) pygame.draw.polygon( self.screen, WHITE, [(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)] ) def _draw_debug_vectors(self, obj, screen_x, screen_y, camera): """Draw debug vectors (acceleration, velocity, angular acceleration).""" # Draw angular acceleration if hasattr(obj, 'angular_acceleration'): self._draw_angular_acceleration(obj, screen_x, screen_y, camera) # Draw acceleration vector if hasattr(obj, 'acceleration') and isinstance(obj.acceleration, tuple) and len(obj.acceleration) == 2: self._draw_acceleration_vector(obj, screen_x, screen_y, camera) # Draw velocity vector if hasattr(obj, 'velocity') and isinstance(obj.velocity, tuple) and len(obj.velocity) == 2: self._draw_velocity_vector(obj, screen_x, screen_y, camera) def _draw_angular_acceleration(self, obj, screen_x, screen_y, camera): """Draw angular acceleration vector.""" rotation_angle = obj.rotation.get_rotation() arrow_length = obj.max_visual_width / 2 * camera.zoom end_x = screen_x + arrow_length * math.cos(math.radians(rotation_angle)) end_y = screen_y + arrow_length * math.sin(math.radians(rotation_angle)) angular_acceleration = obj.angular_acceleration angular_accel_magnitude = abs(angular_acceleration) * ANGULAR_ACCELERATION_SCALE * camera.zoom angular_direction = rotation_angle + 90 if angular_acceleration >= 0 else rotation_angle - 90 angular_acc_end_x = end_x + angular_accel_magnitude * math.cos(math.radians(angular_direction)) angular_acc_end_y = end_y + angular_accel_magnitude * math.sin(math.radians(angular_direction)) pygame.draw.line(self.screen, LIGHT_BLUE, (end_x, end_y), (angular_acc_end_x, angular_acc_end_y), 2) # Draw arrowhead self._draw_arrowhead(angular_acc_end_x, angular_acc_end_y, angular_direction, ANGULAR_TIP_SIZE * camera.zoom, LIGHT_BLUE) def _draw_acceleration_vector(self, obj, screen_x, screen_y, camera): """Draw acceleration vector.""" acc_x, acc_y = obj.acceleration acc_magnitude = math.sqrt(acc_x ** 2 + acc_y ** 2) if acc_magnitude > 0: acc_direction = math.degrees(math.atan2(acc_y, acc_x)) acc_vector_length = acc_magnitude * ACCELERATION_SCALE * camera.zoom acc_end_x = screen_x + acc_vector_length * math.cos(math.radians(acc_direction)) acc_end_y = screen_y + acc_vector_length * math.sin(math.radians(acc_direction)) pygame.draw.line(self.screen, RED, (screen_x, screen_y), (acc_end_x, acc_end_y), 2) self._draw_arrowhead(acc_end_x, acc_end_y, acc_direction, ARROW_TIP_SIZE * camera.zoom, RED) def _draw_velocity_vector(self, obj, screen_x, screen_y, camera): """Draw velocity vector.""" vel_x, vel_y = obj.velocity vel_magnitude = math.sqrt(vel_x ** 2 + vel_y ** 2) if vel_magnitude > 0: vel_direction = math.degrees(math.atan2(vel_y, vel_x)) vel_vector_length = vel_magnitude * VELOCITY_SCALE * camera.zoom vel_end_x = screen_x + vel_vector_length * math.cos(math.radians(vel_direction)) vel_end_y = screen_y + vel_vector_length * math.sin(math.radians(vel_direction)) pygame.draw.line(self.screen, BLUE, (screen_x, screen_y), (vel_end_x, vel_end_y), 2) self._draw_arrowhead(vel_end_x, vel_end_y, vel_direction, ARROW_TIP_SIZE * camera.zoom, BLUE) def _draw_arrowhead(self, end_x, end_y, direction, tip_size, color): """Draw an arrowhead at the specified position.""" left_tip_x = end_x - tip_size * math.cos(math.radians(direction + 150 + 180)) left_tip_y = end_y - tip_size * math.sin(math.radians(direction + 150 + 180)) right_tip_x = end_x - tip_size * math.cos(math.radians(direction - 150 + 180)) right_tip_y = end_y - tip_size * math.sin(math.radians(direction - 150 + 180)) pygame.draw.polygon( self.screen, color, [(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)] ) def render_selection_rectangle(self, selection_rect): """Render the selection rectangle.""" if not selection_rect: return left, top, width, height = selection_rect # Draw semi-transparent fill s = pygame.Surface((width, height), pygame.SRCALPHA) s.fill(SELECTION_GRAY) self.screen.blit(s, (left, top)) # Draw border pygame.draw.rect(self.screen, SELECTION_BORDER, pygame.Rect(left, top, width, height), 1) def render_selected_objects_outline(self, selected_objects, camera): """Render blue outline for selected objects.""" for obj in selected_objects: obj_x, obj_y = obj.position.get_position() width = obj.max_visual_width if hasattr(obj, "max_visual_width") else 10 screen_x, screen_y = camera.world_to_screen(obj_x, obj_y) size = camera.get_relative_size(width) rect = pygame.Rect(screen_x - size // 2, screen_y - size // 2, size, size) pygame.draw.rect(self.screen, SELECTION_BLUE, rect, 1)