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

Comments

Peter,

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

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.

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.

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

 

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

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

 

 

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.

 

 

 

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

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!

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

Post new comment

The content of this field is kept private and will not be shown publicly.