sandpypi/rendering.py
2024-12-28 20:15:57 -06:00

451 lines
20 KiB
Python

"""
#File Name: rendering.py
Rendering class for the particle simulation.
This class is responsible for rendering the particles, UI elements, and debug information on the screen. It handles the setup of the display, pre-rendering of static UI elements, and the drawing of particles, buttons, and debug overlays.
The `draw_particles` function is the main method for rendering the particles on the screen. It takes the particle data, active particles, particle size, and particle colors as input, and renders the particles on the `particle_surface`. The `particle_surface` is then blitted onto the main screen.
The `draw_zoom_window` function is used to render a zoomed-in view of the particles around the mouse cursor. It creates a separate surface for the zoomed-in view and returns it.
The `draw_debug_overlay` function is responsible for rendering the debug information, such as FPS, mouse position, and particle information, on the screen.
The `draw_buttons` function handles the rendering of the category buttons, particle buttons, and other UI elements like the clear screen and settings buttons.
The `render_brush_cursor` function is used to draw a visual indicator for the current brush size.
The `draw_brush_size_slider` function renders a slider for adjusting the brush size.
The `draw_settings_menu` function creates a settings menu surface that can be displayed on the screen.
The `clear_screen` function is used to reset the simulation grid and clear the display surfaces.
"""
from settings import pygame, random, particle_properties, engine_settings
class Rendering:
def __init__(self, width, height):
#self.setup_gpu_rendering()
self.screen = pygame.display.set_mode((width, height))
self.background = pygame.Surface((width, height))
self.background.fill((0, 0, 0))
self.width = width
self.height = height
self.particle_colors = {}
self.particle_properties = particle_properties
self.particle_surface = pygame.Surface((width, height), pygame.SRCALPHA)
self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA)
self.cached_fonts = {
'debug': pygame.font.SysFont(None, 24),
'button': pygame.font.SysFont(None, 20),
'slider': pygame.font.SysFont(None, 20),
'settings': pygame.font.SysFont(None, 20),
'zoom': pygame.font.SysFont(None, 20),
'brush_size': pygame.font.SysFont(None, 20),
'zoom_window': pygame.font.SysFont(None, 20),
'zoom_window_text': pygame.font.SysFont(None, 20)
}
# Pre-render static UI elements
self.button_surfaces = {}
# Initialize categories
for name, properties in particle_properties.items():
if 'color' in properties:
self.particle_colors[name.lower()] = properties['color']
self.categories = {'Solids': [], 'Liquids': [], 'Gases': [], 'Special': []}
for particle_name, properties in self.particle_properties.items():
if properties.get('is_gas'):
self.categories['Gases'].append(particle_name)
elif properties.get('liquid'):
self.categories['Liquids'].append(particle_name)
elif properties.get('solid'):
self.categories['Solids'].append(particle_name)
else:
self.categories['Special'].append(particle_name)
self.current_category = 'Solids'
self.category_buttons = {}
self.setup_category_menu()
self.setup_static_ui()
def setup_gpu_rendering(self):
# Initialize OpenGL context
pygame.display.gl_set_attribute(pygame.GL_ACCELERATED_VISUAL, 1)
self.screen = pygame.display.set_mode((self.width, self.height),
pygame.OPENGL | pygame.DOUBLEBUF)
def setup_static_ui(self):
for category in self.categories:
surf = pygame.Surface((80, 25))
surf.fill((150, 150, 150))
text = self.cached_fonts['button'].render(category, True, (0, 0, 0))
surf.blit(text, (5, 5))
self.button_surfaces[category] = surf
def setup_category_menu(self):
# Category buttons at the top
x_offset = self.width - 350
y_offset = 10
for category in self.categories:
button_rect = pygame.Rect(x_offset, y_offset, 80, 25)
self.category_buttons[category] = button_rect
x_offset += 90
def load_buttons(self):
x_offset = 10
y_offset = 10
for particle_type, properties in self.particle_properties.items():
if 'color' in properties:
button_rect = pygame.Rect(x_offset, y_offset, self.button_width, self.button_height)
self.buttons[particle_type.lower()] = button_rect
x_offset += self.button_width + 10 # Add spacing between buttons
def draw_particles(self, particles, active_particles, particle_size, particle_colors): # this is the function that draws the particles
#self.particle_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
self.particle_surface.fill((0, 0, 0, 0))
particle_batches = {}
for x, y in active_particles:
particle = particles[x][y]
if not particle:
continue
p_type = particle.particle_type
if p_type not in particle_batches:
particle_batches[p_type] = []
rect = pygame.Rect(x * particle_size, y * particle_size,
particle_size, particle_size)
particle_batches[p_type].append(rect)
if particle.particle_type in particle_colors:
color = particle_colors[particle.particle_type]
else:
color = (255, 255, 255)
if engine_settings['enable_glow']:
glow_color = (255, 255, 255)
glow_radius = 0.5 * particle_size
glow_surface = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA)
pygame.draw.circle(glow_surface, glow_color, (glow_radius, glow_radius), glow_radius)
glow_surface.set_alpha(85)
self.particle_surface.blit(glow_surface, (x * particle_size - glow_radius, y * particle_size - glow_radius))
if engine_settings['enable_gas_effect']:
if particle.is_gas:
# Enhanced gas visibility
alpha = random.randint(128, 200)
color = list(color)
if len(color) < 4:
color.append(alpha)
else:
color[3] = alpha
# Add subtle movement effect
offset_x = random.randint(-1, 1)
offset_y = random.randint(-1, 1)
rect = (x * particle_size + offset_x, y * particle_size + offset_y,
particle_size, particle_size)
else:
rect = (x * particle_size, y * particle_size,
particle_size, particle_size)
pygame.draw.rect(self.particle_surface, color, rect)
self.screen.blit(self.background, (0, 0))
self.screen.blit(self.particle_surface, (0, 0))
"""#Potentially for future
for p_type, rects in particle_batches.items():
color = particle_colors.get(p_type, (255, 255, 255))
rect = (x * particle_size, y * particle_size,
particle_size, particle_size)
if len(rects) > 1:
pygame.draw.rect(self.particle_surface, color, rect)
else:
pygame.draw.rect(self.particle_surface, color, rect)
self.screen.blit(self.background, (0, 0))
self.screen.blit(self.particle_surface, (0, 0))"""
def draw_zoom_window(self, particles, particle_size, particle_colors, mouse_pos, zoom_factor=4): # this is the function that draws the zoom window
print(f"Drawing zoom window.")
zoom_size = 100 # Size of zoom window
zoom_surface = pygame.Surface((zoom_size, zoom_size))
zoom_surface.fill((0, 0, 0))
# Get area around mouse to zoom
mouse_x, mouse_y = mouse_pos
view_x = mouse_x - zoom_size/(2*zoom_factor)
view_y = mouse_y - zoom_size/(2*zoom_factor)
print(f"Viewing area: {view_x}, {view_y}")
# Draw zoomed particles
for x in range(int(view_x), int(view_x + zoom_size/zoom_factor)):
for y in range(int(view_y), int(view_y + zoom_size/zoom_factor)):
if 0 <= x < len(particles) and 0 <= y < len(particles[0]):
particle = particles[x][y]
if particle:
color = particle_colors.get(particle.particle_type, (255, 255, 255))
rect = ((x - view_x) * zoom_factor,
(y - view_y) * zoom_factor,
particle_size * zoom_factor,
particle_size * zoom_factor)
pygame.draw.rect(zoom_surface, color, rect)
print(f"Drawing zoom window at {mouse_pos}{zoom_surface}")
return zoom_surface
def _get_particle_info(self, particles, x, y):
if 0 <= x < len(particles) and 0 <= y < len(particles[0]):
particle = particles[x][y]
if particle:
attrs = ['temperature', 'liquid', 'is_gas', 'solid', 'mass', 'velocity', 'friction']
info = [f"Type: {particle.particle_type}"]
info.extend(f"{attr}: {getattr(particle, attr)}" for attr in attrs
if hasattr(particle, attr))
return " | ".join(info)
return "None"
def draw_wind_overlay(self, wind_zones):
wind_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
for zone in wind_zones:
# Draw wind direction arrows
x, y = zone['x'], zone['y']
radius = zone['radius']
strength = zone['strength']
direction = zone['direction']
# Draw wind field visualization
arrow_color = (0, 150, 255, 100) # Light blue, semi-transparent
arrow_length = strength * 20
# Draw main direction arrow
end_x = x + direction[0] * arrow_length
end_y = y + direction[1] * arrow_length
pygame.draw.line(wind_surface, arrow_color, (x, y), (end_x, end_y), 2)
# Draw arrow head
pygame.draw.circle(wind_surface, arrow_color, (int(x), int(y)), 5)
self.screen.blit(wind_surface, (0, 0))
def draw_pressure_overlay(self, particles, active_particles):
pressure_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
# Create pressure map
for x, y in active_particles:
particle = particles[x][y]
if particle and hasattr(particle, 'pressure'):
# Color gradient based on pressure
pressure = particle.pressure
if pressure > 0:
color = (255, 0, 0, int(min(pressure * 50, 255))) # Red for high pressure
else:
color = (0, 0, 255, int(min(-pressure * 50, 255))) # Blue for low pressure
pygame.draw.rect(pressure_surface, color,
(x * self.particle_size, y * self.particle_size,
self.particle_size, self.particle_size))
self.screen.blit(pressure_surface, (0, 0))
def draw_temperature_overlay(self, particles, active_particles):
temperature_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
# Create temperature map
for x, y in active_particles:
particle = particles[x][y]
if particle and hasattr(particle, 'temperature'):
# Color gradient based on temperature
temperature = particle.temperature
if temperature > 0:
color = (255, 0, 0, int(min(temperature * 50, 255))) # Red for high temperature
else:
color = (0, 0, 255, int(min(-temperature * 50, 255))) # Blue for low temperature
pygame.draw.rect(temperature_surface, color,
(x * self.particle_size, y * self.particle_size,
self.particle_size, self.particle_size))
self.screen.blit(temperature_surface, (0, 0))
def draw_debug_overlay(self, fps, sim):
if not engine_settings['enable_fps'] and not engine_settings['enable_debug']:
return
# Create static debug surface if not exists
if not hasattr(self, 'debug_surface'):
self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA)
# Only update when values change significantly
mouse_x, mouse_y = pygame.mouse.get_pos()
grid_x, grid_y = mouse_x // 3, mouse_y // 3
current_info = (
int(fps),
int(sim.track_tps()),
mouse_x // 10, # Reduced update frequency
mouse_y // 10,
sim.particle_count
)
if not hasattr(self, '_last_debug_info') or current_info != self._last_debug_info:
self._last_debug_info = current_info
self.debug_surface.fill((0, 0, 0, 0))
font = self.cached_fonts['debug']
y_offset = 10
if engine_settings['enable_fps']:
fps_surf = font.render(f"FPS: {fps:.1f} | TPS: {sim.track_tps():.1f}", True, (255, 255, 255))
self.debug_surface.blit(fps_surf, (10, y_offset))
y_offset += 25
if engine_settings['enable_debug']:
debug_lines = [
f"Mouse: ({mouse_x}, {mouse_y})",
f"Grid: ({grid_x}, {grid_y})",
f"Particles: {sim.particle_count}"
]
for line in debug_lines:
text_surf = font.render(line, True, (255, 255, 255))
self.debug_surface.blit(text_surf, (10, y_offset))
y_offset += 25
# Single blit of cached surface
self.screen.blit(self.debug_surface, (0, 0))
def draw_buttons(self): # this is the function that draws the buttons
self.buttons = {}
# Draw category buttons on right
x_offset = self.width - 100
y_offset = 10
# Draw cached category buttons
for category, button in self.category_buttons.items():
surf = self.button_surfaces[category]
if category == self.current_category:
surf.set_alpha(255)
else:
surf.set_alpha(200)
self.screen.blit(surf, button)
# Draw particle buttons for current category
y_offset = 150 # Start particle buttons below categories
for particle_type in self.categories[self.current_category]:
if particle_type in self.particle_properties:
button_rect = pygame.Rect(x_offset, y_offset, 80, 25)
self.buttons[particle_type] = button_rect
# Use cached button surface
if particle_type in self.button_surfaces:
self.screen.blit(self.button_surfaces[particle_type], button_rect)
else:
# Create and cache button surface if not exists
button_surface = pygame.Surface((80, 25))
color = self.particle_properties[particle_type].get('color', (255, 255, 255))
button_surface.fill(color)
font = self.cached_fonts.get('button')
label = font.render(particle_type, True, (0, 0, 0))
button_surface.blit(label, (5, 5))
self.button_surfaces[particle_type] = button_surface
self.screen.blit(button_surface, button_rect)
y_offset += 30 # Stack buttons vertically
final_y_offset = y_offset + 10
# Draw clear screen button
if 'clear' not in self.button_surfaces:
clear_surface = pygame.Surface((80, 25))
clear_surface.fill((255, 0, 0))
font = self.cached_fonts.get('button')
label = font.render("Clear", True, (255, 255, 255))
clear_surface.blit(label, (5, 5))
self.button_surfaces['clear'] = clear_surface
self.clear_screen_button = pygame.Rect(x_offset, y_offset + 10, 80, 25)
self.screen.blit(self.button_surfaces['clear'], self.clear_screen_button)
# Draw Settings menu button
if 'settings' not in self.button_surfaces:
settings_surface = pygame.Surface((80, 25))
settings_surface.fill((255, 255, 255))
font = self.cached_fonts.get('button')
label = font.render("Settings", True, (0, 0, 0))
settings_surface.blit(label, (5, 5))
self.button_surfaces['settings'] = settings_surface
self.settings_button = pygame.Rect(x_offset, y_offset + 50, 80, 25)
self.screen.blit(self.button_surfaces['settings'], self.settings_button)
def render_brush_cursor(self, x, y, radius):
if engine_settings ['enable_cursor']:
# Draw outline circle
pygame.draw.circle(self.screen, (255, 255, 255), (x, y), radius, 1)
# Draw slightly transparent fill
cursor_surface = pygame.Surface((radius*2, radius*2), pygame.SRCALPHA)
pygame.draw.circle(cursor_surface, (255, 255, 255, 55), (radius, radius), radius)
self.screen.blit(cursor_surface, (x-radius, y-radius))
def draw_brush_size_slider(self, brush_size): # this is the function that draws the brush size slider
# Draw the slider for brush size
pygame.draw.rect(self.screen, (255, 255, 255), (500, 10, 100, 20))
pygame.draw.rect(self.screen, (0, 0, 0), (500, 10, 100, 20), 2)
pygame.draw.rect(self.screen, (255, 0, 0), (500 + brush_size, 10, 100 - brush_size * 2, 20))
label = self.cached_fonts.get('brush_size').render(f"Brush Size: {brush_size}", True, (255, 255, 255))
self.screen.blit(label, (500, 10))
def draw_settings_menu(self):
settings_surface = pygame.Surface((300, 400))
settings_surface.fill((50, 50, 50))
y_offset = 10
font = self.cached_fonts.get('settings')
for setting, value in engine_settings.items():
# Create toggle button
button_rect = pygame.Rect(10, y_offset, 20, 20)
pygame.draw.rect(settings_surface, (0, 255, 0) if value else (255, 0, 0), button_rect)
# Draw setting name
label = font.render(setting.replace('_', ' ').title(), True, (255, 255, 255))
settings_surface.blit(label, (40, y_offset))
y_offset += 30
return settings_surface
def clear_screen(self, sim): # this is the function that clears the screen
# Store current particle type
current_type = sim.current_particle_type
self.particle_count = 0
# Reset simulation grid while preserving particle type
sim.particles = [[None for _ in range(sim.height)] for _ in range(sim.width)]
sim.active_particles.clear()
sim.current_particle_type = current_type
sim.reset_particle_count()
# Clear display surfaces
self.background.fill((0, 0, 0))
self.particle_surface.fill((0, 0, 0, 0))