How to resolve the algorithm Particle fountain step by step in the Python programming language
How to resolve the algorithm Particle fountain step by step in the Python programming language
Table of Contents
Problem Statement
Implement a particle fountain. Emulate a fountain of water droplets in a gravitational field being sprayed up and then falling back down. The particle fountain should be generally ordered but individually chaotic; the particles should be going mostly in the same direction, but should have slightly different vectors. Your fountain should have at least several hundred particles in motion at any one time, and ideally several thousand. It is optional to have the individual particle interact with each other. If at all possible, link to a short video clip of your fountain in action. Off-site link to a demo video
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Particle fountain step by step in the Python programming language
This Python code simulates a particle fountain using the PySDL2 library. It creates particles that fall under the influence of gravity, generating a fountain effect.
Key Concepts:
- PySDL2: A Python library that provides an interface to the SDL2 library, used for creating 2D graphics and handling events.
- Particle: Represents a particle with position (x, y) and velocity (v_x, v_y).
- Fountain: Manages a collection of particles, adding and updating them as they fall.
Implementation:
-
Particle Class:
- Defines methods to initialize, update (move and adjust speed), and set particle values.
-
Fountain Class:
- Manages the particle system.
- Initializes particles with random positions and velocities.
- Updates particle positions and velocities based on gravity.
- Renders the particles on the screen.
- Allows for parameter adjustments (particle spread, range, color, and reciprocation).
-
Main Loop:
- Infinite loop that handles events (key presses), updates the fountain, and renders the particles.
- Limits the frame rate to maintain a consistent animation speed.
-
Event Handling:
- Handles key presses for manipulating fountain parameters (spread, range, color, reciprocation).
- Exits the program when the 'Q' key is pressed.
-
Renderer:
- Uses PySDL2 to create a window and a renderer for drawing the particles.
- Clears the screen and draws the particles as points.
- Presents the rendered frame on the screen.
-
Initialization:
- Initializes the SDL2 library and creates a window and renderer.
- Initializes a fountain object with a specified number of particles and particles per frame.
-
Main Function:
- Runs the main loop, handles events, and updates the fountain.
- Exits the program when the main loop ends.
How it Works:
- The program initializes a fountain and a renderer.
- In the main loop, it handles events and updates the fountain.
- The fountain updates the positions and velocities of its particles based on gravity.
- The fountain renders the particles as points on the screen.
- The program limits the frame rate to ensure a smooth animation.
- When the user presses certain keys, the fountain adjusts its parameters (spread, range, color, reciprocation).
- The program continues running until the user exits by pressing 'Q'.
Additional Notes:
- The fountain's initial parameters can be adjusted by passing different values to the
Fountain
constructor. - The fountain uses reciprocation to create a bouncing effect for the particles.
- The
clamp
function ensures that parameter values stay within specified bounds.
Source code in the python programming language
# Using SDL2 library: # pip install PySDL2
import sys
import random
import time
import math
import sdl2
import sdl2.ext
FPS = 60
NEW_PARTICLES_PER_FRAME = 10
MAX_PARTICLES = 5_000
GRAVITY = 100
WIDTH = 640
HEIGHT = 480
def clamp(value, min_, max_):
"""Return value clamped between min and max"""
return max(min_, min(value, max_))
class Particle:
"""Particle obeying gravity law."""
def __init__(self):
self.x = 0
self.y = 0
self.v_x = 0
self.v_y = 0
def update(self, dtime: float) -> None:
"""Move particle and update speed with gravity"""
self.x = self.x + self.v_x * dtime
self.y = self.y + self.v_y * dtime
self.v_y = self.v_y + GRAVITY * dtime
def set(self, x, y, v_x, v_y):
"""Set particle values"""
self.x = x
self.y = y
self.v_x = v_x
self.v_y = v_y
class Fountain:
"""The fountain"""
def __init__(self, max_particles: int, particles_per_frame: int):
self.particles_per_frame = particles_per_frame
self.max_particles = max_particles
self.spread = 10.0
self.range = math.sqrt(2 * GRAVITY * (HEIGHT - 20 - self.spread))
self.saturation = 155
self.reciprocate = False
self.reciprocating_time = 0.0
self.particles = [
self.init_particle(Particle()) for _ in range(self.particles_per_frame)
]
def update(self, dtime) -> None:
"""Update particles"""
if self.reciprocate:
self.reciprocating_time += dtime
for particle in self.particles:
particle.update(dtime)
if particle.y > HEIGHT - 10:
self.init_particle(particle)
if len(self.particles) < self.max_particles:
for _ in range(self.particles_per_frame):
self.particles.append(self.init_particle(Particle()))
# print(len(particles))
def render(self, renderer: sdl2.ext.renderer.Renderer) -> None:
"""Render particles"""
points = [(particle.x, particle.y) for particle in self.particles]
renderer.clear()
renderer.draw_point(
points, sdl2.ext.Color(self.saturation, self.saturation, 255)
)
renderer.present()
def step_parameter(self, param, step):
"""Change parameters"""
if param == "spread":
self.spread = clamp(self.spread + step, 0, 50)
elif param == "range":
self.range = clamp(self.range + step, 0, 300)
elif param == "color":
self.saturation = clamp(self.saturation + step, 0, 255)
elif param == "reciprocate":
self.reciprocate = not self.reciprocate
self.reciprocating_time = 0.0
def init_particle(self, particle: Particle) -> Particle:
"""Move particle at initial position with a random-y speed"""
radius = random.random() * self.spread
direction = random.random() * math.pi * 2
v_x = radius * math.cos(direction) + math.sin(self.reciprocating_time) * 20.0
v_y = -self.range + radius * math.sin(direction)
particle.set(WIDTH // 2, HEIGHT - 10, v_x, v_y)
return particle
def make_renderer() -> sdl2.ext.renderer.Renderer:
"""Initialise SDL and make renderer"""
sdl2.ext.init()
window = sdl2.ext.Window("Particle Fountain", size=(WIDTH, HEIGHT))
window.show()
renderer = sdl2.ext.renderer.Renderer(window)
return renderer
def limit_frame_rate(fps: float, cur_time: int) -> bool:
"""Limit frame rate"""
dtime = time.monotonic_ns() - cur_time
frame_duration = 1e9 / fps
if dtime < frame_duration:
time.sleep((frame_duration - dtime) / 1e9)
return True
return False
def handle_events(fountain: Fountain):
"""Act on events"""
key_actions = {
sdl2.SDL_SCANCODE_PAGEUP: lambda: fountain.step_parameter("color", 5),
sdl2.SDL_SCANCODE_PAGEDOWN: lambda: fountain.step_parameter("color", -5),
sdl2.SDL_SCANCODE_UP: lambda: fountain.step_parameter("range", 1),
sdl2.SDL_SCANCODE_DOWN: lambda: fountain.step_parameter("range", -1),
sdl2.SDL_SCANCODE_LEFT: lambda: fountain.step_parameter("spread", -1),
sdl2.SDL_SCANCODE_RIGHT: lambda: fountain.step_parameter("spread", 1),
sdl2.SDL_SCANCODE_SPACE: lambda: fountain.step_parameter("reciprocate", 1),
}
events = sdl2.ext.get_events()
for event in events:
if event.type == sdl2.SDL_QUIT:
return False
if event.type == sdl2.SDL_KEYDOWN:
if event.key.keysym.scancode in key_actions:
key_actions[event.key.keysym.scancode]()
elif event.key.keysym.scancode == sdl2.SDL_SCANCODE_Q:
return False
return True
def main_loop(renderer: sdl2.ext.renderer.Renderer, fountain: Fountain) -> None:
"""Main animation loop"""
running = True
cur_time = time.monotonic_ns()
while running:
running = handle_events(fountain)
fountain.render(renderer)
if not limit_frame_rate(FPS, cur_time):
print(f"Didn't make it in time with {len(fountain.particles)} particles.")
dtime = (time.monotonic_ns() - cur_time) / 1e9 # in seconds
fountain.update(dtime)
cur_time = time.monotonic_ns()
sdl2.ext.quit()
def run():
"""Start!"""
renderer = make_renderer()
fountain = Fountain(MAX_PARTICLES, NEW_PARTICLES_PER_FRAME)
main_loop(renderer, fountain)
return 0
if __name__ == "__main__":
sys.exit(run())
You may also check:How to resolve the algorithm Rosetta Code/Count examples step by step in the Tcl programming language
You may also check:How to resolve the algorithm Named parameters step by step in the Applesoft BASIC programming language
You may also check:How to resolve the algorithm Scope modifiers step by step in the TI-89 BASIC programming language
You may also check:How to resolve the algorithm Exceptions step by step in the Haskell programming language
You may also check:How to resolve the algorithm Happy numbers step by step in the AutoIt programming language