Particles, Neighborhoods, and Forces

How a fluid can emerge from particles that only know about their neighbors.

Part 1 of Smooth Particle Hydrodynamics

Particles Instead of Grid Cells

One way to simulate a fluid is to divide space into a grid and track how much fluid is in each cell. Click and drag below to inject dye into a grid-based fluid. The simulation advects density and velocity through fixed cells.

Grid-based fluid. Click and drag to inject dye and force. The grid approach works well for enclosed flows but struggles with splashing, free surfaces, and mixing.

Grid simulations are powerful, but every cell exists whether there's fluid in it or not. Another way is to forget the grid and model the fluid as a collection of particles, each carrying mass, position, and velocity. Particles only exist where the fluid is.

If you've played with a flocking simulation before, the particle approach feels similar. In flocking, each bird looks at nearby birds and follows three rules: separate, align, cohere.

Flocking (boids). Click a boid to select it and see its neighbor radius and force breakdown: separation, alignment, cohesion. Each boid only knows about its neighbors, yet flocking emerges.

In SPH, each particle follows the same pattern: look at nearby particles, compute local forces, update. Instead of steering rules, SPH uses forces derived from fluid physics: how crowded is it here? Which way is the pressure pushing me? How fast are my neighbors moving compared to me? Fluid-like behavior emerges from these local interactions.

The Smoothing Kernel

The "smooth" in SPH comes from the kernel function. It answers a simple question: how much should a particle care about a neighbor at a given distance?

A particle at the center of its smoothing radius cares most about particles right next to it, and cares less about particles further away. Beyond the smoothing radius, influence drops to zero.

Kernel function
drag a neighbor particle
r = distance(i, j)     // distance between particles
q = r / h               // normalized distance
W = (q < 1) ? (1 - q)³ : 0  // cubic falloff, zero beyond h
Figure 1. Drag neighbor particles to see how the kernel weight changes. The equation updates with actual values.

The specific shape of the kernel matters less than its properties: it's smooth, it falls off to zero at the smoothing radius, and it's normalized so that the total influence integrates to 1.

Estimating Density

The first thing each particle needs to figure out is: how crowded is it here? This is the density at the particle's location. A particle estimates its density by summing up kernel-weighted contributions from all neighbors within the smoothing radius.

Density estimation
loading...
density[i] = 0
for each neighbor j within h:
  density[i] += mass[j] * W(r, h)
Figure 2. Click to select a particle, drag any particle to rearrange. The selected particle (ringed) shows its density sum in the equation.

Pressure: Pushing Apart

Fluids resist compression. When particles are packed too tightly, they push apart. The pressure force on a particle points away from regions of high density.

Pressure force (probe particle)
loading...
P[i] = k * (ρ[i] - ρ₀)  // equation of state
force[i] -= m[j] * (P[i]+P[j]) / (2*ρ[j]) * ∇W
Figure 3. The probe particle orbits through an uneven ring of neighbors. Red arrows show pressure forces. The dashed circle shows the smoothing radius. Uncheck "Animate" to drag particles.

Viscosity: Smoothing Velocities

Pressure alone gives you something like an ideal gas. Real fluids have viscosity, which makes neighboring particles tend to move at similar speeds. The viscosity force is proportional to the velocity difference between a particle and its neighbors.

Viscosity force
loading...
force[i] += μ * m[j]/ρ[j] * (v[j] - v[i]) * ∇²W(r, h)
Speed:
Figure 4. The pink particle moves through a field of resting particles. Blue arrows show viscosity forces pulling neighbors along. The sparkline tracks how the mover slows down. It loops when it reaches the edge.

Updating Positions

We now have all the forces. At each time step, we update velocity and position. The order of operations and the number of intermediate evaluations determine how accurately the integration tracks the true path. Here are four methods, from simplest to most accurate.

Integration methods
scrub the step slider
// Symplectic Euler: velocity first, then position
vel += F/ρ * dt;  pos += vel * dt
// RK4: four force evaluations per step, much more accurate
Figure 5. Four integration methods on the same orbit. Scrub to walk through steps. At larger dt, cheaper methods spiral outward while RK4 stays on track.

Putting It Together

A spout on the left wall emits particles into a shallow container where they feel pressure, viscosity, and gravity. Watch the fluid fill up and splash.

Figure 6. 2D SPH fluid filling a container from a spout. Particles are colored by speed: dark blue is slow, lighter blue is fast. Click and drag to push particles around.

The simulation loop is straightforward: compute densities, compute forces, update velocities, update positions. The expensive part is finding neighbors, which we'll solve with spatial hashing on the GPU in Part 2.