Soft-Body Jelly, Explained

After reading this you will understand how a wobbly jelly blob is built from point masses, springs, damping and internal pressure, and how Verlet integration keeps it stable while you drag and squash it.

What a soft body actually is

A soft body is not a solid shape. It is a small crowd of point masses joined by springs. Picture 16 dots arranged around a circle, each dot connected to its neighbors and to a common center. Give every connection a rest length and a stiffness, then let gravity pull the whole thing down. The springs fight to keep their rest lengths, the floor pushes back when a dot sinks below it, and the result jiggles like jelly.

Here is the hook. Drop a blob from a height of 200 pixels onto the floor. On impact the bottom springs compress by roughly 30 percent, storing energy. They release it over the next few frames, flinging the top of the blob upward. With low damping the blob bounces four or five times before settling. With high damping it lands once and spreads into a puddle. Nothing in the code says "bounce" or "jelly". The behavior falls out of Hooke's law applied to a few dozen springs, updated 60 times a second.

When to reach for this model, and when not to

Use a spring-mass soft body when you want plausible, springy motion cheaply: game characters that squash, a UI element that wobbles, a teaching demo for Hooke's law and numerical integration. The math is simple and it runs in a browser at 60 frames per second with a few hundred springs.

Do not use it when you need physical accuracy. This model does not conserve volume exactly, does not model real material stress tensors, and its "pressure" is a crude approximation. If you need engineering answers about how a rubber gasket deforms under load, you want the finite element method, not this. The blob here is a toy that looks right, not a solver that is right.

The related Cloth Simulator uses the same Verlet spring network but leaves out the pressure term, because cloth is a sheet that should fold flat. A jelly blob adds pressure precisely so it refuses to fold flat.

The spring force and how it behaves

Every edge between two masses is a damped spring. The core is Hooke's law: the force is proportional to how far the spring is stretched or compressed away from its rest length, and it points back toward that rest length.

F = -k \, (L - L_0)

Here L is the current distance between the two masses, L_0 is the rest length, and k is the stiffness. The sign is negative because the force opposes the displacement. If a spring with L_0 = 40 is stretched to L = 50 and k = 0.2, the restoring force has magnitude 0.2 \times 10 = 2, pulling the two masses together.

Springs alone would oscillate forever. Damping bleeds off energy by opposing the relative velocity along the spring:

F_d = -c \, (v_1 - v_2) \cdot \hat{n}

where c is the damping coefficient, v_1 - v_2 is the relative velocity of the two endpoints, and \hat{n} is the unit vector along the spring. Large c makes the wobble die quickly. Small c lets it ring.

The pressure term acts on the whole ring of outer edges. It computes the current enclosed area A and pushes each edge outward along its normal with a force scaled by 1/A, so a shrinking blob pushes back harder. That is what keeps the blob from collapsing into a line even when every spring is slack.

Why Verlet integration instead of storing velocity

Most physics code stores a position and a velocity, then steps both forward. Verlet integration stores the current position and the previous position instead, and infers velocity from the difference. The update for one mass under acceleration a and timestep \Delta t is:

x_{n+1} = 2 x_n - x_{n-1} + a \, \Delta t^2

The velocity never appears explicitly. It lives inside the term x_n - x_{n-1}, which is the distance moved last frame. This matters for a spring network because many springs tug on the same mass at once. Verlet handles that gracefully: you can satisfy each spring constraint by simply nudging the two positions, and the implied velocity updates automatically. Position-based constraints stay stable even when they conflict, which is why the blob does not explode when you squash it hard against a wall.

Reproducing the demo blob's first bounce

Load the default blob and let it fall. Take one mass on the underside, timestep \Delta t = 1 in frame units, gravity a = 0.5 pixels per frame squared, and follow it through the moment it hits the floor.

  1. Frame 0: position x0 = 300, previous x_prev = 299.5. The implied velocity is 0.5 downward.
  2. Frame 1: x_1 = 2(300) - 299.5 + 0.5 = 301.0. Velocity is now 1.0.
  3. Frame 2: x_2 = 2(301) - 300 + 0.5 = 302.5. Velocity 1.5. Each frame the fall speed grows by 0.5, exactly as free fall should.
  4. Floor at y = 320. When a step would push the mass past 320, clamp it to 320. The previous position stays where it was, so the implied velocity flips sign the next frame: the mass rebounds.
  5. The rebound speed is reduced by the floor's restitution factor. With restitution 0.6, an impact speed of 3.0 returns as 1.8 upward, so the blob loses about 64 percent of its kinetic energy on each bounce.

Track the blob's center height across bounces and you get a decaying staircase. Each peak is about 0.6^2 = 0.36 of the previous peak height, because peak height scales with the square of speed.

Peak height drops by a factor of about 0.36 each bounce, so the blob settles within roughly 100 frames.

Tune stiffness and damping with the widget

The single most instructive knob is the ratio of stiffness to damping. Too much stiffness with too little damping and the blob buzzes like a jittery balloon. Too much damping and it moves like wet clay. The middle is where it reads as jelly.

A single spring released from a stretched length oscillates back to rest. High stiffness makes it snap back fast and overshoot many times; high damping makes each overshoot smaller so it settles sooner. A stiffness of 0.2 with damping 0.05 gives about four visible wobbles before rest; damping 0.3 removes the overshoot entirely.

Reading what you see on screen

Three visible behaviors map directly to three parameters.

Wobble frequency
Set by stiffness. For a mass m on a spring, the natural frequency is f = \frac{1}{2\pi}\sqrt{k/m}. Quadruple k and the wobble roughly doubles in frequency.
Settling time
Set by damping. With light damping the blob rings for a second or two. With heavy damping it stops within a few frames.
Sag and squash depth
Set by the balance of gravity against stiffness and pressure. Weak springs plus strong gravity give a puddle; strong springs plus internal pressure give a firm dome.

If you drag a blob and let go, the throw speed equals the drag speed of your last frame, because Verlet reads velocity from the last position change. Flick it fast and it launches fast.

Common mistakes

Cranking stiffness to the maximum while leaving damping near zero is the classic way to make the blob explode. High k means large forces, and Verlet with a fixed timestep cannot follow a spring that wants to move more than its own length in one step. The fix is either lower stiffness or run more constraint iterations per frame.

A second mistake is expecting conserved volume. This model conserves nothing exactly. Watch the blob's area drift by a few percent as it bounces. That drift is a feature of the cheap pressure approximation, not a bug in your reading.

A third is confusing damping with restitution. Spring damping bleeds energy inside the blob and controls wobble. Floor restitution controls how much of the impact speed survives a bounce. Both remove energy, but at different places. A blob can wobble very little (high internal damping) yet still bounce high (high restitution), and vice versa.

Related simulations

If the Verlet network here interests you, the Cloth Simulator is the same engine without pressure, so you can see how removing that one term changes a balloon into a sheet. For chaos from simple rules, the Double Pendulum shows how tiny differences in start explode apart. For gravity-driven motion in a very different setting, try the Orbit Sandbox. And for a many-particle view of jitter and diffusion, see Brownian Motion.

Frequently asked questions

Why does my jelly blob explode when I raise stiffness?

High stiffness produces large forces, and a fixed timestep of one frame cannot track a spring that wants to snap faster than one step. The integrator overshoots, the next force is even larger, and the blob flies apart. Lower the stiffness or add damping.

Does the simulation conserve the blob's volume?

No. The pressure term only pushes back harder as area shrinks, which resists collapse without fixing area. Expect the enclosed area to drift by a few percent during hard squashes.

What is the difference between stiffness and damping?

Stiffness sets how fast the shape snaps back and therefore its wobble frequency. Damping sets how quickly that wobble dies out. Stiffness alone gives endless jiggle; damping is what makes it settle.

Why use Verlet instead of tracking velocity?

Verlet stores the previous position and infers velocity from it, which makes overlapping spring constraints stable. You can nudge positions to satisfy each spring and the velocities update for free, so the network does not blow up when many springs conflict.

Can I model real rubber accurately with this?

Not accurately. This is a visual toy that captures the feel of springy material. Real material behavior needs finite element methods with proper stress and strain, not a handful of point masses.