451 lines
20 KiB
Python
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))
|
|
|