Skip to content

Instantly share code, notes, and snippets.

@karminski
Created August 8, 2025 01:49
Show Gist options
  • Select an option

  • Save karminski/63023169e570efb41036591b20a2c833 to your computer and use it in GitHub Desktop.

Select an option

Save karminski/63023169e570efb41036591b20a2c833 to your computer and use it in GitHub Desktop.

Create a 2D fluid simulation program using Python and pygame library that demonstrates liquid particles pouring out from a tilting cup under gravity.

Core Requirements:

Pygame Environment Setup:

Initialize a pygame window with recommended size 1920x1080 pixels. Set up a main loop to handle events, update physics states, and render graphics. Set background color to white.

Cup Container Definition:

Create a 2D cup shape using line segments. The opening should face upward, defined by a list of vertices like [(x1, y1), (x2, y2), ...]. Initially position the cup vertically at screen center. Render cup lines in black color.

Particle System:

Represent liquid using numerous particles (e.g. 400). Each particle should be a small circle with attributes:

  • Position (x, y)
  • Velocity (vx, vy)
  • Diameter (e.g. 4 pixels)
  • Color (e.g. blue)

Recommend creating a Particle class to manage these properties. Initially generate particles above the cup and let them flow into it.

Physics Simulation:

Gravity:

Apply constant downward acceleration (e.g. g = 0.1) to all particles each frame.

Container Collision:

Implement collision detection between particles and cup walls. Prevent particle leakage through wall joints. Apply velocity reversal with restitution coefficient (e.g. 0.7) upon collision.

Particle Interaction (Simplified Fluid Behavior):

Key simulation element. Implement repulsion force to prevent unnatural clustering. When particle distance falls below radius sum threshold, apply outward force along their center line. Achieve by direct position/velocity adjustment.

Motion Update:

Use Euler integration: velocity += acceleration * dt position += velocity * dt (Assume dt=1 for simplification, add acceleration directly to velocity)

Screen Bottom:

Particles should settle at screen bottom instead of falling through.

Animation Effects:

Particle generation: First create particles above cup and let them fill it. Cup tilting: Gradually rotate cup over time. Rotate cup around its base point. Increase rotation angle from 0° to ~135° to simulate pouring.

Code Structure:

  • Use OOP approach (Particle class, Flask class)
  • Add comments for key physics calculations (gravity, collisions, interactions) and coordinate transformations (cup rotation)
  • Draw cup first then particles to prevent visual glitches
  • All code should be written in English
  • Keep entire code in single .py file
@FalcionX
Copy link

FalcionX commented Aug 8, 2025

Try this:

Create a single-file Python program (one .py) using pygame that simulates a 2D fluid made of particles pouring from a tilting cup. Code and comments must be in English, PEP8-compliant, with type hints and concise docstrings.

Window & Timing

  • Create a 1920x1080 window, white background. Add a resizable mode (maintain a logical world size and scale rendering).
  • Target 60 FPS. Use a fixed physics timestep of dt = 1/120 s and perform substeps each frame (e.g., 2 substeps per frame at 60 Hz, configurable).
  • Display an FPS counter in the top-left.

Architecture

  • Use OOP with these classes:
    • Particle: position (float x,y), velocity (float vx,vy), radius (default 2 px, diameter 4), color.
    • Cup: defined by line segments (list of vertices). Provide methods to rotate around a pivot at its base, and to compute collision normals for each segment and rounded end-caps to avoid leaks at joints.
    • SpatialHashGrid: uniform grid for neighbor queries (cell size ≈ 2× particle diameter). Methods: insert, clear, query_neighbors(particle).
    • Simulator: owns particles, cup, grid; steps physics; handles input; draws; holds parameters.
  • Put all tunable constants at the top (gravity, restitution, friction, viscosity, cohesion, particle_count, substeps, grid_cell_size, tilt_speed, etc.).
  • Use a fixed RNG seed for reproducible runs and allow resetting it.

Cup Geometry & Rotation

  • Define an upright cup with a slightly thicker bottom and two side walls; opening facing up. Use 2D line segments with a small “thickness” in collision logic.
  • Place the cup center-screen initially; pivot at the bottom mid-point.
  • Implement rotation via a 2D transform: local->world using rotation matrix around the pivot. Keep original (unrotated) vertices; compute rotated vertices each frame.
  • Animate tilt from 0° to about 135° over time. Provide keyboard control: LEFT/RIGHT to adjust tilt manually, T to toggle auto-tilt, R to reset pose.

Particles & Emission

  • Create ~800–1500 particles (configurable). Initially spawn them from an emitter above the cup (randomized within a small horizontal span) so they fall into it. Optionally allow continuous spawning until a max count.
  • Particle attributes: pos, vel, radius, color. Color can be a blue gradient randomized slightly for visual richness.

Physics

  • Use semi-implicit (symplectic) Euler integration:
    velocity += (gravity + external_forces) * dt
    position += velocity * dt
  • Gravity: constant downward acceleration (e.g., g = 900 px/s^2 in world units; choose values that look good at 120 Hz).
  • Clamp extreme velocities to avoid tunneling. Optionally perform CCD-lite: if speed*dt > radius, split the move into smaller steps inside a substep.

Interactions (Simplified Fluid)

  • Use SpatialHashGrid to find neighbors within an interaction radius = 2.2× particle radius.
  • Repulsion: if two particles overlap, compute penetration depth and push them apart along their center line by half the overlap each; also correct velocities (projected along the normal) using a small restitution to prevent jitter.
  • Viscosity: apply velocity damping proportional to the relative velocity along the line of centers (e.g., v_rel_n = (1 - viscosity_coeffdt)).
  • Cohesion (light surface tension): a small spring-like attraction when distance is slightly larger than the sum of radii, capped to avoid clumping. Make these terms configurable and easy to disable.

Collisions

  • World bounds: particles settle on the screen bottom; handle left/right walls to keep fluid on-screen.
  • Cup collision: for each line segment, compute the closest point to the particle center. If distance < (radius + wall_thickness), resolve:
    • Positional correction along the outward normal to remove penetration.
    • Velocity reflection: v' = v - (1 + restitution) * (dot(v, n)) * n
    • Tangential friction: reduce tangential component by a friction coefficient.
  • Prevent leaks at corners: treat each vertex as a small circular end-cap during collision checks.

Pouring Behavior

  • As the cup tilts past ~90°, particles near the rim should escape. Ensure the collision normals always point from wall interior to particle to avoid sticking on outside faces.
  • Add a small linear drag to help settling and reduce ringing.

Rendering

  • Draw order: cup first (black lines), then particles. Optionally render particles to an offscreen Surface with per-pixel alpha and blit it for a soft look. Add an optional simple trail (low alpha decay) toggle with key J.
  • Add a minimal debug overlay (toggle with D): shows the spatial grid cells, number of neighbor checks per frame, cup normals, and current tilt angle.

Controls

  • SPACE: pause/resume physics.
  • T: toggle auto-tilt on/off. LEFT/RIGHT: manual tilt.
  • D: debug overlay. J: enable/disable trail post-processing.
  • G: toggle gravity. R: reset (particles, RNG, cup tilt).
  • ESC or window close: clean quit.

Performance Expectations

  • With ~1000 particles on a typical laptop, maintain ~60 FPS. Use the spatial hash to keep the neighbor checks near-linear. Avoid Python lists-of-lists churn each frame; reuse arrays where possible.

Comments & Clarity

  • Add comments explaining: gravity integration, neighbor search, collision response (including math for reflection), positional correction, and coordinate transforms for cup rotation.
  • Keep everything in one .py file, no external deps beyond Python and pygame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment