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

335 lines
13 KiB
Python

"""
#File Name: sandpypi.py
# Sandpypi by Stanton.
# Project name is a placeholder.
# This has been a multimonth or year project i have time blindness sorta.
# 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.
The main function to run the Sandpypi program.
This function initializes the Pygame environment, creates the simulation and rendering objects, and enters the main event loop.
It handles user input events such as mouse clicks, mouse wheel scrolling, and keyboard presses.
It also updates the simulation, draws the particles, buttons, and other UI elements, and manages the settings menu.
The main loop runs at a target frame rate of 60 FPS, with the actual frame rate displayed in the debug overlay.
"""
import cProfile
import pstats
from settings import pygame, engine_settings
from rendering import Rendering
"""
This is for the future physics engine until i figure out a better method used for testing right now.
#import os
#import sys
#sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
"""
from sim import Simulation
def update_simulation(sim, dt, engine_settings):
"""Update simulation state"""
sim.simulate_step(dt, engine_settings)
def render_frame(rendering, sim, mouse_pos):
"""Render all visual elements"""
# Draw particles
rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors)
# Draw UI elements
rendering.draw_buttons()
rendering.draw_brush_size_slider(sim.brush_size)
# Draw brush cursor
mouse_x, mouse_y = mouse_pos
rendering.render_brush_cursor(mouse_x, mouse_y, sim.brush_size * sim.particle_size)
def handle_input(event, sim, rendering, settings_visible, zoom_active, zoom_locked, zoom_pos):
"""Handle all input events"""
if event.type == pygame.MOUSEBUTTONDOWN:
return handle_mouse_down(event, sim, rendering, settings_visible, zoom_active)
elif event.type == pygame.MOUSEBUTTONUP:
return handle_mouse_up(event)
elif event.type == pygame.KEYDOWN:
return handle_key_press(event, rendering, sim)
return None
def handle_mouse_down(event, sim, rendering, settings_visible, zoom_active):
"""Handle mouse button down events"""
mouse_pos = pygame.mouse.get_pos()
in_settings_area = False
if settings_visible:
settings_rect = pygame.Rect(rendering.width - 320, 100, 300, 400)
in_settings_area = settings_rect.collidepoint(mouse_pos)
if event.button == 4: # Mouse wheel up
sim.brush_size = min(sim.brush_size + 1, sim.max_brush_size)
elif event.button == 5: # Mouse wheel down
sim.brush_size = max(sim.brush_size - 1, 1)
elif event.button == 1: # Left click
return handle_left_click(mouse_pos, sim, rendering, settings_visible, in_settings_area, zoom_active)
elif event.button == 3: # Right click
return {'mouse_down_right': True}
elif event.button == 2: # Middle click
return {'mouse_down_middle': True}
return {}
def handle_left_click(mouse_pos, sim, rendering, settings_visible, in_settings_area, zoom_active):
"""Handle left click interactions"""
result = {'mouse_down_left': False, 'settings_visible': settings_visible, 'over_button': False}
if rendering.settings_button.collidepoint(mouse_pos):
result['settings_visible'] = not settings_visible
result['over_button'] = True
return result
if zoom_active:
result['zoom_locked'] = not result.get('zoom_locked', False)
if result['zoom_locked']:
result['zoom_pos'] = mouse_pos
return result
if settings_visible and in_settings_area:
handle_settings_click(mouse_pos, sim)
result['over_button'] = True
return result
if not in_settings_area:
if handle_ui_click(mouse_pos, sim, rendering):
result['over_button'] = True
else:
result['mouse_down_left'] = True
return result
def handle_settings_click(mouse_pos, sim):
"""Handle clicks in settings menu"""
settings_menu_y = 100
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]
def handle_ui_click(mouse_pos, sim, rendering):
"""Handle clicks on UI elements"""
for category, button in rendering.category_buttons.items():
if button.collidepoint(mouse_pos):
rendering.current_category = category
return True
for particle_type, button in rendering.buttons.items():
if button.collidepoint(mouse_pos):
sim.current_particle_type = particle_type
return True
if rendering.clear_screen_button.collidepoint(mouse_pos):
rendering.clear_screen(sim)
return True
return False
def handle_mouse_up(event):
"""Handle mouse button up events"""
if event.button == 1:
return {'mouse_down_left': False}
elif event.button == 3:
return {'mouse_down_right': False}
elif event.button == 2:
return {'mouse_down_middle': False}
return {}
def handle_key_press(event, rendering, sim):
"""Handle keyboard press events"""
if event.key == pygame.K_ESCAPE:
print("Escape button pressed")
print(f"Exiting Program {__file__}")
return {'running': False}
elif event.key == pygame.K_SPACE:
engine_settings['pause_sim'] = not engine_settings['pause_sim']
elif event.key == pygame.K_c:
rendering.clear_screen(sim)
sim.reset_particle_count()
elif event.key == pygame.K_z:
return {'zoom_active': True, 'zoom_locked': False, 'zoom_pos': pygame.mouse.get_pos()}
return {}
def main():
pygame.init()
clock = pygame.time.Clock()
width = 1024
height = 768
screen = pygame.display.set_mode((width, height), pygame.HWSURFACE | pygame.DOUBLEBUF)
sim = Simulation(width, height)
rendering = Rendering(width, height)
# State variables
mouse_down_left = False
mouse_down_right = False
mouse_down_middle = False
over_button = False
zoom_active = False
zoom_locked = False
zoom_pos = None
settings_visible = False
running = True
while running:
# Clear screen at start of frame
screen.fill((0, 0, 0))
fps = clock.get_fps()
dt = clock.tick(1000) / 1000
keys = pygame.key.get_pressed()
mouse_pos = pygame.mouse.get_pos()
zoom_active = keys[pygame.K_z]
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
continue
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
in_settings_area = False
if settings_visible:
settings_rect = pygame.Rect(rendering.width - 320, 100, 300, 400)
in_settings_area = settings_rect.collidepoint(mouse_pos)
if event.button == 4: # Mouse wheel up
sim.brush_size = min(sim.brush_size + 1, sim.max_brush_size)
elif event.button == 5: # Mouse wheel down
sim.brush_size = max(sim.brush_size - 1, 1)
elif event.button == 1: # Left click
over_button = False
# Check settings button first
if rendering.settings_button.collidepoint(mouse_pos):
settings_visible = not settings_visible
over_button = True
elif zoom_active:
zoom_locked = not zoom_locked
if zoom_locked:
zoom_pos = mouse_pos
# Handle settings menu interactions
elif settings_visible and in_settings_area:
relative_y = mouse_pos[1] - 100 # 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]
over_button = True
elif not in_settings_area:
# Check category buttons
for category, button in rendering.category_buttons.items():
if button.collidepoint(mouse_pos):
rendering.current_category = category
over_button = True
break
# Check particle buttons
if not over_button:
for particle_type, button in rendering.buttons.items():
if button.collidepoint(mouse_pos):
sim.current_particle_type = particle_type
over_button = True
break
# Check clear screen button
if rendering.clear_screen_button.collidepoint(mouse_pos):
rendering.clear_screen(sim)
particle_count = 0
over_button = True
if not over_button and not in_settings_area:
mouse_down_left = True
elif event.button == 3: # Right click
mouse_down_right = True
elif event.button == 2: # Middle click
mouse_down_middle = True
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
mouse_down_left = False
elif event.button == 3:
mouse_down_right = False
elif event.button == 2:
mouse_down_middle = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
engine_settings['pause_sim'] = not engine_settings['pause_sim']
elif event.key == pygame.K_c:
rendering.clear_screen(sim)
elif event.key == pygame.K_z:
zoom_active = True
zoom_locked = False
zoom_pos = pygame.mouse.get_pos()
# Update simulation if not paused
if not engine_settings['pause_sim']:
sim.simulate_step(dt, engine_settings)
# Handle continuous mouse input
if mouse_down_left and not over_button:
x, y = mouse_pos
if sim.current_particle_type not in ['wind', 'air']:
sim.create_particle_circle(x, y)
else:
sim.add_wind_zone(x, y)
if mouse_down_right:
x, y = mouse_pos
sim.clear_particles_circle(x, y)
if mouse_down_middle:
x, y = mouse_pos
sim.create_particle(x, y)
# Draw everything in correct order
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)
rendering.render_brush_cursor(mouse_pos[0], mouse_pos[1], sim.brush_size * sim.particle_size)
# Handle zoom window
if zoom_active or zoom_locked:
current_zoom_pos = zoom_pos if zoom_locked else mouse_pos
zoom_surface = rendering.draw_zoom_window(sim.particles, sim.particle_size,
rendering.particle_colors, current_zoom_pos)
zoom_x = 10 if current_zoom_pos[0] > width/2 else width - 110
zoom_y = 10 if current_zoom_pos[1] > height/2 else height - 110
screen.blit(zoom_surface, (zoom_x, zoom_y))
# Draw settings and debug overlay last
if settings_visible:
settings_menu = rendering.draw_settings_menu()
rendering.screen.blit(settings_menu, (rendering.width - 320, 100))
rendering.draw_debug_overlay(fps, sim)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
# Profile the application
profiler = cProfile.Profile()
profiler.enable()
main()
profiler.disable()
# Write profiling results to file
with open('profile_results.log', 'w') as f:
stats = pstats.Stats(profiler, stream=f)
stats.sort_stats('cumulative')
stats.print_stats()