Specific attraction: springs

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 bolierplate 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 inlcude the pause control.

from math import pi
import random
import pygame
import PyParticles

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

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]

    pygame.display.flip()

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:
    universe.update()
        
screen.fill(universe.colour)
    
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:
    selected_particle.mouseMove(pygame.mouse.get_pos())

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 exterted 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 Enivironment's update function:

for spring in self.springs:
     spring.update()

Day dreaming

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 prehaps 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, which can be contracted and relax, so the creature can move. 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.

AttachmentSize
particle_tutorial_14.txt1.61 KB
PyParticles4.txt7.8 KB