Specific attraction: springs

25 Jul 2011


A couple of tutorial, we created a simulation in which every particle attracted every other particle. Another classic system that physicists have long considered is one in which bodies are are moved towards or away from one another by a spring that connects them. In this tutorial, we will:

  • Create a Spring object
  • Link particles with springs

Once we have created springs, we can use them to connect particles and simulate complex soft bodies. A simple example is shown in the video below. The springs can also represent edges in a graph, with the particles representing vertices.

Setting up the simulation

We start in almost exactly the same way we started the gas cloud simulation, with some boilerplate code to create our screen. The only differences are that this time we will need to import pi from the math module and we can include the pause control.

from math import pi
import random
import pygame
import PyParticles

(width, height) = (400, 400)
screen = pygame.display.set_mode((width, height))

paused = False
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                paused = (True, False)[paused]


As before, we then create an Environment, only this time, the background colour is white and we want our particles, to move, bounce off the wall, collide with one another, experience drag and experience downwards acceleration (gravity). I've also changed the mass_of_air to 0.02, as that seems to give nicer results.

universe = PyParticles.Environment((width, height))
universe.colour = (255,255,255)
universe.addFunctions(['move', 'bounce', 'collide', 'drag', 'accelerate'])
universe.acceleration = (pi, 0.01)
universe.mass_of_air = 0.02

Adding particles

Next we add some particles; the exact parameters aren't too important.

for p in range(4):
    universe.addParticles(mass=100, size=16, speed=2, elasticity=1, colour=(20,40,200))

Then, in the main loop, we update and display the particles.

if not paused:
for p in universe.particles:
    pygame.draw.circle(screen, p.colour, (int(p.x), int(p.y)), p.size, 0)

And we can add some code to allow us to select and move particles. Outside the main loop we add:

selected_particle = None

And inside the main loop, where we deal with user interactions, we add:

elif event.type == pygame.MOUSEBUTTONDOWN:
        selected_particle = universe.findParticle(pygame.mouse.get_pos())
    elif event.type == pygame.MOUSEBUTTONUP:
        selected_particle = None

if selected_particle:

If you run the code now, you should see some particles bouncing around and coming to halt. You can pick them and a throw them about. I hope you agree that with our module it's relatively simple to set up such a simulation. Now to add something new.

The Spring object

The force exerted by a spring is given by Hooke's law, which tells us that F = -kx, or that the force is equal to a constant (sometimes called the spring constant), k, multiplied by its displacement from its equilibrium position. Our spring object will therefore have a length attribute, representing the length it 'tries' to obtain, and a strength attribute, representing how quickly it tries to reach that length (i.e. the spring constant). It will also record which particles are at either end.

To the PyParticles file add a new class somewhere:

class Spring:
    def __init__(self, p1, p2, length=50, strength=0.5):
        self.p1 = p1
        self.p2 = p2
        self.length = length
        self.strength = strength

Then to the Environment class we add a list to keep track of spring objects and a method to add spring object to it.

self.springs = []
def addSpring(self, p1, p2, length=50, strength=0.5):
    """ Add a spring between particles p1 and p2 """
    self.springs.append(Spring(self.particles[p1], self.particles[p2], length, strength))

Now we can add springs in our simulation code like so:

universe.addSpring(0,1, length=100, strength=0.5)
universe.addSpring(1,2, length=100, strength=0.1)
universe.addSpring(2,0, length=80, strength=0.05)

These lines of code add a spring between particles 0 and 1, particles 1 and 2, and particles 2 and 0, thus making a triangle. We can represent the springs as lines between the particles by adding the following after the code for drawing particles.

for s in universe.springs:
    pygame.draw.aaline(screen, (0,0,0), (int(s.p1.x), int(s.p1.y)), (int(s.p2.x), int(s.p2.y)))

If you run the simulation now, you should see the particles bouncing around as usual, but with three of them joined up in a triangle.

To make our springs actually exert and effect, we need to add a method that accelerates particles at either end in line with the spring:

def update(self):
    dx = self.p1.x - self.p2.x
    dy = self.p1.y - self.p2.y
    dist = math.hypot(dx, dy)
    theta = math.atan2(dy, dx)
    force = (self.length - dist) * self.strength
   self.p1.accelerate((theta + 0.5 * math.pi, force/self.p1.mass))
   self.p2.accelerate((theta - 0.5 * math.pi, force/self.p2.mass))

This function is almost identical to the attract() function that we wrote for gravitational attraction, which is unsurprising. Both measure the distance and angle between two particles and calculate the force acting on the particles using this information. The only difference is the equation for force.

Finally we need to call the springs' update function within the Environment's update function:

for spring in self.springs:


We now have the building blocks for all kinds of simulations and I recommend playing about with different parameters and building structure. By linking particles we can make complex structures. We could perhaps make an Angry Birds style game if we allow springs to break under a certain force (though Angry Birds actually uses rigid body physics). We could allow colliding particles to make and break connections, thus simulating a virtual chemistry. We could also create some sort of creature in which the springs act as muscles, contracting and relaxing to move the creature. At some point I hope to explore some of these ideas.

As an quick example, below shows the approximation to two solid object: a sphere (actually a dodecagon) and a square, defined by their edges and some diagonals. You can see there are a few problems, such as the circle can slip into the square, and the circle can collapse into a tightly-bound mess.

Comments (15)

Joe on 6 Jul 2012, 2:34 p.m.


Just worked through your tutorials. They are excellent. Many thanks.

derek on 1 Aug 2012, 5:57 p.m.

Thank you Peter for the tutorials and the pygame physics simulation. I've been looking around for a simple python simulation environment with some physics like properties, hoping to use for a cognitive modeling simulation. This will probably save me a lot of work and time. As you mentioned in another tutorial, I'm also interested in environments with complex enough features to provide possibilities for rich interactions and distinct niches for agents. I will remember to acknowledge your work in any pubs that might come from it (still beginning stages), but wanted to thank you now.

Peter on 2 Aug 2012, 2:06 p.m.

Hi Derek,

Sounds like an interesting project. If you do publish any of it, I'd be interested to read it. I had planned to make a 3D version to create a world in which simulated creatures could live and learn, but I doubt I'll get around to it. I suspect I would need something more powerful than Python for it to work well.

Glad the tutorials were of some use and thanks for pointing out the mistakes and inconsistencies. I'll go through and sort them out when I get a chance.

Anonymous on 8 Jan 2013, 12:23 a.m.

Hello, the tutorials are very good, but the is one bug, could you please help?

I am trying to run the script about the spring s an getting this error:

selected_particle = universe.findParticle(pygame.mouse.get_pos())
TypeError: findParticle() takes exactly 3 arguments (2 given)
I really want to try other things, with that example, please let me know what is wrong or how to fix it.

many thanks

Peter on 23 Jan 2013, 4:08 p.m.

Sorry about that. You need to change to the code to:

(mouseX, mouseY) = pygame.mouse.get_pos()
selected_particle.mouseMove(mouseX, mouseY)

Ignacio Aular on 24 Jun 2013, 3:08 p.m.

Hello Mr. Peter.

Thank you very much for this amazing tutorial, I'm learning very much of it.
I'm translating it into spanish to teaching about animations to my students.
Best regards from Venezuela, Mr. Peter.

PD: please excuse me for my English. It's not very well still.

Peter on 27 Jun 2013, 4:07 p.m.

Hi Ignacio, that's great! Thanks for letting me know.

icedtrees on 8 Jul 2013, 4:51 p.m.

Sup Peter!

Your tutorials are exactly what I needed, I'm going to steal your source code to use to teach people with, hopefully you're alright with some high school students messing around with your code.

Many thanks!

Peter on 9 Jul 2013, 1:11 a.m.

No problem. I'm flattered and a little embarrassed by the poor quality of the code.

Getting Annoyed on 21 Oct 2015, 4:12 p.m.

Even after adding code in previous comments, I still get the same error. Would it not be easier to update the attached txt file or the tutorial itself?

Azli on 14 Jul 2017, 11:27 a.m.

Great material - really easy to follow. Many thanks.

Rutvi Padhy on 9 Jan 2019, 7:13 a.m.

Just done with this tutorial! Thank you so much for explaining everything with so much clarity. Learn't a great deal, many thanks.

Pratyush on 27 Mar 2019, 10:12 a.m.

These tutorials are so intuitive. The MVC structure even though being so tiring to build is a beauty. Thanks man.

Good! on 28 Mar 2020, 9:19 a.m.

Very good!, can you please adapt this into the latest python version, I can do it myself but I don't want to miss anything...

Khant Nyar Paing on 30 May 2020, 5:04 a.m.

I finished your tutorial. The quality of tutorial is amazing. As a beginner, I learnt a lot. Thank you.