250 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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, render_area):
 | |
|         self.render_area = render_area
 | |
|         self.render_height = render_area.get_height()
 | |
|         self.render_width = render_area.get_width()
 | |
| 
 | |
|     def clear_screen(self):
 | |
|         """Clear the screen with a black background."""
 | |
|         self.render_area.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 = self.render_width // 2 - camera.x * camera.zoom
 | |
|         grid_center_y = self.render_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 > self.render_width or
 | |
|                 grid_bottom < 0 or grid_top > self.render_height):
 | |
|             return
 | |
| 
 | |
|         # Fill the grid area with dark gray background
 | |
|         grid_rect = pygame.Rect(
 | |
|             max(0, grid_left),
 | |
|             max(0, grid_top),
 | |
|             min(self.render_width, grid_right) - max(0, grid_left),
 | |
|             min(self.render_height, grid_bottom) - max(0, grid_top),
 | |
|         )
 | |
| 
 | |
|         if grid_rect.width > 0 and grid_rect.height > 0:
 | |
|             pygame.draw.rect(self.render_area, 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 <= self.render_width:
 | |
|                     start_y = max(0, grid_top)
 | |
|                     end_y = min(self.render_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 <= self.render_height:
 | |
|                     start_x = max(0, grid_left)
 | |
|                     end_x = min(self.render_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.render_area, GRAY, start, end)
 | |
|         for start, end in horizontal_lines:
 | |
|             pygame.draw.line(self.render_area, GRAY, start, end)
 | |
| 
 | |
|     def render_world(self, world, camera):
 | |
|         """Render all world objects."""
 | |
|         world.render_all(camera, self.render_area)
 | |
| 
 | |
|     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.render_area, 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.render_area, 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.render_area, 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.render_area, 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.render_area, 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.render_area, 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.render_area, color,
 | |
|             [(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)]
 | |
|         )
 | |
| 
 | |
|     def render_selection_rectangle(self, selection_rect, sim_view_rect=None):
 | |
|         """Render the selection rectangle, offset for sim_view if sim_view_rect is provided."""
 | |
|         if not selection_rect:
 | |
|             return
 | |
| 
 | |
|         left, top, width, height = selection_rect
 | |
| 
 | |
|         # Offset for sim_view if sim_view_rect is given
 | |
|         if sim_view_rect is not None:
 | |
|             left -= sim_view_rect.left
 | |
|             top -= sim_view_rect.top
 | |
| 
 | |
|         # Draw semi-transparent fill
 | |
|         s = pygame.Surface((width, height), pygame.SRCALPHA)
 | |
|         s.fill(SELECTION_GRAY)
 | |
|         self.render_area.blit(s, (left, top))
 | |
| 
 | |
|         # Draw border
 | |
|         pygame.draw.rect(self.render_area, 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.render_area, SELECTION_BLUE, rect, 1)
 |