Monday, 25th July 2011
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
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()
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.