When creating a simulation you will often want to make it bounded, to avoid simulating an infinite region. In this simulation, a realistic, physical solution is to add virtual walls that particles bounce off. In this tutorial, we will:
- Test whether particles have moved out of the window
- Move such particles to where they would have bounced
- Change their direction appropriately
Another solution would be to bend a 2D simulation into virtual torus (ring doughnut shape). We do this my making particles that leave one side of the simulation appear on the opposite side.
The first thing our bounce() function needs to do is test whether a particle has gone past a boundary. The four boundaries are at:
- x = 0 (the left wall)
- x = width (the right wall)
- y = 0 (the ceiling)
- y = height (the floor)
Since this simulation has discrete steps of time, we unlikely to catch a particle at the exactly point that it 'hits' a boundary, but rather at a point when it has travelled a bit beyond the boundary. If the speed of the particle is low, then the particle is unlikely to have gone much beyond the boundary (the maximum distance it will have exceeded the boundaries is, in fact, equal to its speed).
We could chose to ignore the discrepancy and simply (especially if our simulation is becoming too computationally intense), and simply reflect the particle angle, but we might as well be accurate for now. Therefore, if a particle exceeds a boundary, we first calculate by how much it has exceeded it (which I'll call d).
For example, if the particle's x value is greater than the window's width minus the particle's size (it has gone beyond the right wall), then we calculate d.
d = self.x - (width - self.size)
We then reflect the position in the boundary (i.e. bounce it) by setting the x coordinate as below:
self.x = (width - self.size) - d
This simplifies to:
self.x = 2 * (width - self.size) - self.x
So we don't actually need to calculate variable d. Replacing width with 0 calculates the x position when the particle crosses the left wall. The y coordinates can be calculated in a similar way.
The most important feature of bouncing is reflecting its angle in the boundary, which is where our use of vectors starts to become useful (though its more useful when the boundaries aren't straight). A vertical boundary is has an angle of 0, while a horizontal boundary has an angle of pi. We therefore reflect vertically bouncing particles by subtracting their current angle from 0 and from pi in the case of horizontal bouncing.
The final bounce function should look like this:
def bounce(self): if self.x > width - self.size: self.x = 2 * (width - self.size) - self.x self.angle = - self.angle elif self.x < self.size: self.x = 2 * self.size - self.x self.angle = - self.angle if self.y > height - self.size: self.y = 2 * (height - self.size) - self.y self.angle = math.pi - self.angle elif self.y < self.size: self.y = 2 * self.size - self.y self.angle = math.pi - self.angle
Finally, don't forget to call the bounce() function after calling the move() function and before calling the display() function.
for particle in my_particles: particle.move() particle.bounce() particle.display()
Before running the simulation, I would also increase the width and height to 400 to make things easier to see. When you run this simulation, you should now see ten particles bouncing around at different speeds, but completely ignoring one another. In a later tutorial, we’ll allow the particles to interact with one another.