diff --git a/.gitignore b/.gitignore index 4effda0..d6bab80 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ cython_debug/ current_stations.html forecast_data.json openapi.json +__pycache__/sim.cpython-312.pyc +__pycache__/sim.cpython-312.pyc +__pycache__/rendering.cpython-312.pyc diff --git a/__pycache__/rendering.cpython-312.pyc b/__pycache__/rendering.cpython-312.pyc index fc4b5ac..e2d25c4 100644 Binary files a/__pycache__/rendering.cpython-312.pyc and b/__pycache__/rendering.cpython-312.pyc differ diff --git a/__pycache__/sim.cpython-312.pyc b/__pycache__/sim.cpython-312.pyc index d93ef72..f1907d4 100644 Binary files a/__pycache__/sim.cpython-312.pyc and b/__pycache__/sim.cpython-312.pyc differ diff --git a/rendering.py b/rendering.py index 1b89da8..1d9c9aa 100644 --- a/rendering.py +++ b/rendering.py @@ -1,6 +1,7 @@ #File Name: rendering.py -from settings import pygame, random, particle_properties +from settings import pygame, random, particle_properties, engine_settings + class Rendering: @@ -69,31 +70,68 @@ class Rendering: base_color = particle_colors.get(particle.particle_type, (255, 255, 255)) color = list(base_color) - - 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) + 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(100) + 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) + + 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)) + def draw_zoom_window(self, particles, particle_size, particle_colors, mouse_pos, zoom_factor=4): + 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 draw_debug_overlay(self, fps, particles): # this is the function that draws the debug overlay # Get mouse position and convert to grid coordinates mouse_x, mouse_y = pygame.mouse.get_pos() @@ -124,13 +162,16 @@ class Rendering: # Draw debug information font = pygame.font.SysFont(None, 24) + particle_count = sum(1 for row in particles for cell in row if cell is not None) + debug_info = [ f"FPS: {int(fps)}", f"Mouse: ({mouse_x}, {mouse_y})", f"Grid: ({grid_x}, {grid_y})", - f"Particle: {particle_info}" + f"Particle: {particle_info}", + #f"Active Particle Count: {sim.Simulation.get_active_particle_count(sim.Simulation)}", + f"Particle Count: {particle_count}" ] - y_offset = 10 for info in debug_info: debug_text = font.render(info, True, (255, 255, 255)) @@ -175,6 +216,13 @@ class Rendering: label = font.render("Clear", True, (255, 255, 255)) self.screen.blit(label, (self.clear_screen_button.x + 5, self.clear_screen_button.y + 5)) + # Draw Settings menu directly below the buttons + self.settings_button = pygame.Rect(x_offset, y_offset + 50, 80, 25) + pygame.draw.rect(self.screen, (255, 255, 255), self.settings_button) + font = pygame.font.SysFont(None, 24) + label = font.render("Settings", True, (0, 0, 0)) + self.screen.blit(label, (self.settings_button.x + 5, self.settings_button.y + 5)) + def render_brush_curser(self, x, y, radius): # this is the function that draws the brush curser but isn't used yet so unkown if works # Draw a circle cursor for brushsize @@ -189,8 +237,27 @@ class Rendering: label = pygame.font.SysFont(None, 24).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 = pygame.font.SysFont(None, 24) + + 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 + def clear_screen(self, sim): # this is the function that clears the screen # Store current particle type current_type = sim.current_particle_type diff --git a/sandpypi.py b/sandpypi.py index ae689bc..435e07d 100644 --- a/sandpypi.py +++ b/sandpypi.py @@ -5,27 +5,36 @@ # This is my most functional system for falling sand in python yet i took some things i learned in JS. # This needs further optimizations to core performance sections. -from settings import pygame +from settings import pygame, engine_settings from rendering import Rendering from sim import Simulation def main(): # Main function to run the program pygame.init() clock = pygame.time.Clock() - pygame.display.set_mode((1024, 768), pygame.HWSURFACE | pygame.DOUBLEBUF) - sim = Simulation(1024, 768) - rendering = Rendering(1024, 768) + width = 1024 + height = 768 + screen = pygame.display.set_mode((width, height), pygame.HWSURFACE | pygame.DOUBLEBUF) + sim = Simulation(width, height) + rendering = Rendering(width, height) mouse_down_left = False mouse_down_right = False mouse_down_middle = False mouse_down_wheel_up = False mouse_down_wheel_down = False over_button = False + zoom_active = False + zoom_locked = False + zoom_pos = None + settings_visible = False + settings_menu_y = 100 running = True while running: fps = clock.get_fps() dt = clock.tick(60) / 1000 + keys = pygame.key.get_pressed() + zoom_active = keys[pygame.K_z] # Handle events @@ -38,6 +47,11 @@ def main(): # Main function to run the program sim.brush_size = max(sim.brush_size - 1, 1) elif event.button == 1: # Left click over_button = False + + if zoom_active: + zoom_locked = not zoom_locked + if zoom_locked: + zoom_pos = mouse_pos # Check category buttons for category, button in rendering.category_buttons.items(): @@ -59,6 +73,18 @@ def main(): # Main function to run the program rendering.clear_screen(sim) over_button = True + if rendering.settings_button.collidepoint(event.pos): + settings_visible = not settings_visible + elif settings_visible: + # Handle settings toggles + mouse_pos = pygame.mouse.get_pos() + relative_y = mouse_pos[1] - settings_menu_y + setting_index = relative_y // 30 + if 0 <= setting_index < len(engine_settings): + setting_name = list(engine_settings.keys())[setting_index] + engine_settings[setting_name] = not engine_settings[setting_name] + + if not over_button: mouse_down_left = True @@ -74,6 +100,23 @@ def main(): # Main function to run the program mouse_down_right = False elif event.type == pygame.MOUSEBUTTONUP and event.button == 2: mouse_down_middle = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + print("Escape button pressed") + print(f"Exiting Program {__file__}") + running = False + elif event.key == pygame.K_SPACE: + print(f"Pause button pressed but not functional 'Space'") + pass + elif event.key == pygame.K_c: + rendering.clear_screen(sim) + elif event.key == pygame.K_z: + zoom_active = not zoom_active + if zoom_active: + zoom_locked = False + zoom_pos = pygame.mouse.get_pos() + + elif event.type == pygame.QUIT: running = False @@ -94,6 +137,17 @@ def main(): # Main function to run the program rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) rendering.draw_buttons() rendering.draw_brush_size_slider(sim.brush_size) + if zoom_active or zoom_locked: + mouse_pos = zoom_pos if zoom_locked else pygame.mouse.get_pos() + zoom_surface = rendering.draw_zoom_window(sim.particles, sim.particle_size, rendering.particle_colors, mouse_pos) + # Position zoom window + zoom_x = 10 if mouse_pos[0] > width/2 else width - 110 + zoom_y = 10 if mouse_pos[1] > height/2 else height - 110 + screen.blit(zoom_surface, (zoom_x, zoom_y)) + + if settings_visible: + settings_menu = rendering.draw_settings_menu() + rendering.screen.blit(settings_menu, (rendering.width - 320, 100)) rendering.draw_debug_overlay(fps, sim.particles) pygame.display.flip() diff --git a/settings.py b/settings.py index e3bdf50..d2d62ef 100644 --- a/settings.py +++ b/settings.py @@ -6,6 +6,13 @@ import json import random import time +engine_settings = { + 'enable_glow': True, + 'enable_gas_effect': True + # 'settings': True/False +} + +# Load particle properties from JSON file def load_particle_properties(): try: with open('particles.json') as f: @@ -16,3 +23,4 @@ def load_particle_properties(): # Load particle properties once when module is imported particle_properties = load_particle_properties() + diff --git a/sim.py b/sim.py index e9e529c..9a393d8 100644 --- a/sim.py +++ b/sim.py @@ -100,10 +100,14 @@ class Simulation: def update_spatial_grid(self): # this is where we update the spatial grid. """Update spatial grid for optimized collision detection""" - self.spatial_grid.clear() - for x, y in self.active_particles: - self.add_to_spatial_grid(self.particles[x][y], x, y) - + if len(self.active_particles) > 100: # Threshold for rebuild + self.spatial_grid.clear() + for x, y in self.active_particles: + cell_key = self.get_cell_key(x, y) + if cell_key not in self.spatial_grid: + self.spatial_grid[cell_key] = set() + self.spatial_grid[cell_key].add((x, y)) + def handle_phase_transitions(self, particle, x, y): # this is where we handle all the phase transitions. @@ -593,6 +597,21 @@ class Simulation: liquid2.color = self.calculate_color(liquid2.temperature) + def get_particle_count(self): + """Returns the number of particles in the simulation for the Debug display and for performance analysis.""" + count = 0 + for x in range(self.width): + for y in range(self.height): + if self.particles[x][y] is not None: + count += 1 + return count + + def get_active_particle_count(self): + """Returns the number of active particles in the simulation for the Debug display and for performance analysis.""" + pass + + + def simulate_step(self, dt): """Run a single step of the simulation""" # Update particle positions and physics diff --git a/template_particles.json b/template_particles.json new file mode 100644 index 0000000..1e48019 --- /dev/null +++ b/template_particles.json @@ -0,0 +1,80 @@ +{ + "Template": { + "description": "This is a template for particles.", + "description2": "Remove this for your own particle mods to work" + }, + "sand": { + "name": "Sand", + "size": 1, + "hardness": 0.5, + "color": [255, 255, 0, 255], + "velocity": 0.5, + "mass": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "flamability": 0.8, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0, + "pressure": 0, + "melt": "molten-Glass", + "melt_temperature": 1000, + "conductive": false, + "liquid": false, + "solid": true, + "is_gas": false + }, + "water": { + "name": "Water", + "size": 1, + "hardness": 0.2, + "velocity": 0.3, + "conductivity": 1, + "heat_capacity": 1, + "color": [0, 0, 255, 255], + "mass": 1, + "flamability": 0, + "temperature": 22, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 1, + "viscosity": 1, + "pressure": 0.5, + "evaporate": "steam", + "evaporate_temperature": 145, + "freeze": "ice", + "freeze_temperature": 0, + "melt": "water", + "melt_temperature": 20, + "liquid": true, + "solid": false, + "is_gas": false + }, + "steam": { + "name": "Steam", + "size": 1, + "hardness": 0.0, + "velocity": 0.2, + "conductivity": 1, + "heat_capacity": 1, + "color": [255, 255, 255, 255], + "mass": 0.01, + "flamability": 0, + "temperature": 100, + "solidify_temperature": 98, + "solidify": "water", + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": false, + "is_gas": true, + "conductive": false + } +} \ No newline at end of file