Suppose you wish to make a particle effect where the particles are constrained to stay in a large circle, and when they hit the boundary of the circle they should bounce off. Bouncing off a circular boundary is rather straightforward if you understand some basic vector algebra. Click here or on the screenshot below to see a simple particle example, then read below to learn more about the mathematics behind it.
Download
the complete code can be downloaded here: CircleBoundary.zip
About the code
The code is wellcommented, and you can download it and check it out for yourself. But the calculations for the boundary bouncing may be a little mysterious, so I explain it below along with some of the mathematics behind it.
The basic particle code in the example above is similar to my earlier experiments: particles are defined by abstract JavaScript objects, retaining parameters for their position and velocity. On every refresh of the screen, the particle velocities are updated; here I am adding a small randomized acceleration amounts to change the particle trajectories as they fly around. After updating positions, testing for collisions with the boundary and recalculating as necessary, we draw the particles to the canvas using a sprite sheet. This sprite sheet, which is created at runtime, is similar to those used in my earlier experiments (for example, 2D here and 3D here) and allows for faster drawing of the particles compared to repeated graphics commands. See my post HTML5 Canvas – A Simple Sprite Sheet Example for more details about creating and copying from such a sprite sheet.
Bouncing off the (circular) walls
A classic particle effect constrains particles to stay inside a rectangle. When a particle collides with the border of a rectangle (that is, its updated position is out of the rectangle bounds), creating the bouncing effect is simple: you simply need to place the particle back inside the rectangle and negate either the x or y component of the velocity. Bouncing off a circle is similar, except finding the part of the velocity that needs to be negated is a little more involved.
A 2D vector such as velocity can always be thought of as formed out of two separate components. A typical decomposition is into x and y components, but that is not the only way to break down vectors. When bouncing off a wall we need to consider what part of the velocity is perpendicular (normal) to the wall, and what part is parallel (tangent) to the wall. The normal part of the velocity should be negated, but the tangential part should be left alone.
Finding these normal and tangential componenents requires a little bit of vector algebra. What we are interested in finding is called the projection of one vector onto another. In the diagram below, we show two vectors \(\mathbf{a}\) and \(\mathbf{b}\), and the projection of \(\mathbf{a}\) onto \(\mathbf{b}\):
This projection vector can be computed in terms of something called a dot product: the dot product of two 2D vectors \(\mathbf{a} = \langle x_1,y_1 \rangle \) and \(\mathbf{b} = \langle x_2,y_2 \rangle\) is the quantity
\[
\mathbf{a} \cdot \mathbf{b} = x_1x_2 + y_1y_2.
\]
Because of a nice geometric property of the dot product, it can be used to find projection vectors:
\[
\mathrm{\textbf{proj}}_\mathbf{b}(\mathbf{a}) = \left(\frac{\mathbf{a}\cdot \mathbf{b}}{\mathbf{b}\cdot \mathbf{b}}\right)\mathbf{b}
\]
In the case of our particle colliding with a circular boundary, we are interested in finding the normal component of the velocity. As shown below, suppose the particle is at position \(P\), right on the circle, and it has velocity \(\mathbf{v}\).
The normal component of the velocity is \(\mathrm{\textbf{proj}}_\mathbf{N}(\mathbf{v})\), and the tangential part is
\(
\mathbf{v} – \mathrm{\textbf{proj}}_\mathbf{N}(\mathbf{v}).
\) We negate the normal part but leave the tangential component unchanged, obtaining a new velocity vector
\[
\mathbf{v}^{\prime} = \mathbf{v} – 2\;\mathrm{\textbf{proj}}_\mathbf{N}(\mathbf{v}).
\]
Finding the normal vector on a circle is easy. If the circle is centered at the origin, then a normal vector at any point \((x,y)\) is given by the “position vector” \(\langle x, y \rangle\).
One more important bit of math we will use below is that any vector dotted with itself produces the square of the length of the vector:
\[
\mathbf{a} \cdot \mathbf{a} = \mathbf{a}^2,
\]
where \(\mathbf{a}\) represents the length (magnitude) of a vector \(\mathbf{a}\). Note that the dot product of a vector with itself appears in the projection formula above.
Writing the code
When we update the x and y coordinates of a particle p
, we must first determine if a collision with the walls has occurred. In the case of a boundary circle centered at the origin, this is easy to test: we simply see if the distance from the particle to the center is more than the radius of the circle (actually, to avoid a square root in the distance formula, we compare squares). If the particle is out of bounds, we must place it back inside the circle and adjust its velocity. A simple way to place the particle back inside is to find the midpoint between the last (interior) position and the new (out of bounds) position, and place the particle there. But to ensure that the particle is placed inside the circle, we scale this position to have the correct length. This positioning is only approximate, but it is fine for our purposes. So our boundary collision code begins as follows:
radSquare= p.x*p.x + p.y*p.y;
if (radSquare > boundaryRadSquare) {
//find intersection point with circle. simple method: midpoint
exitX = (lastX + p.x)/2;
exitY = (lastY + p.y)/2;
//scale to proper radius
exitRad = Math.sqrt(exitX*exitX + exitY*exitY);
exitX *= boundaryRad/exitRad;
exitY *= boundaryRad/exitRad;
//place particle there
p.x = exitX;
p.y = exitY;
Now we must set the velocity using the mathematics we outlined above. First note that for our normal vector we will simply be using our position vector, which is now \(\langle \mathrm{exitX}, \mathrm{exitY} \rangle\). Also, because this point is on the circle, when we dot it with itself we obtain the square of the radius of the circle (see the note above concerning the dot product of a vector with itself).
We now complete the boundary collision code by computing the new velocity \(\mathbf{v} – 2\mathrm{\textbf{proj}}_\mathbf{N}(\mathbf{v})\):
twiceProjFactor = 2*(exitX*p.velX
+ exitY*p.velY)/boundaryRadSquare;
vx = p.velX  twiceProjFactor*exitX;
vy = p.velY  twiceProjFactor*exitY;
p.velX = vx;
p.velY = vy;
}
That’s all there is to it. The actual calculations are short, but the ideas behind them take a little while to explain!
After the particle positions are updated, they are drawn to the screen using an offscreen sprite sheet which contains an image of one typical particle. To add to the aesthetics of the effect, I have set the globalCompositeOperation
to “lighter,” so the particles will additively blend where they overlap.
Acknowledgment
The mathematics was typset in this blog post using the open source JavaScript display engine MathJax.
Because I’m sensing a theme here (particles… then particles + boundary collisions… next could be particle vs particle collisions!?), this might help: https://gist.github.com/1760774.
It’s an implementation of a hierarchical spatial hash grid. It allows for massive numbers of objects to be eligible for collision detection, without having to do a bruteforce comparison of all objects. Unlike something like a quadtree, differences in size do not affect detection time, and there are no issues with an object trying to find the root leaf when directly on an edge.
Basically, the only thing that affects how long your collision detection will take is dependent on how many objects, in linear time.
If it helps, cool! I’m working on a 3D version next ðŸ™‚ When it’s done, I’ll be releasing both with proper tests and READMEs.
February 7, 2012 @ 5:18 pm
Hi Andrew,
Thanks for the note! That algorithm sounds pretty neat…I’ll have to look at it carefully sometime soon. I had never heard of it before. You may be sensing a theme to my posts…but the theme for now is “whatever is in my head at the moment.” But particle vs. particle collisions were sure to pop up in my head sooner or later.
Best Regards!
February 11, 2012 @ 7:33 pm
These demos are excellent, they’ve really given a lot of inspiration for what can be done with Canvas. Good work!
December 7, 2014 @ 11:41 am
Thank you, James!
December 7, 2014 @ 12:23 pm
Hello,
I hope you don’t mind, but I’ve taken used some of your code as a guideline for me to build a “circular” pong game. This was the only place I could find a tutorial on collisions off of round surfaces, but I’ve run into a problem. Sometimes, the “particle” reaches the circle, and starts bouncing rapidly and eventually does not come back into the circle. It seems this happens when either the velocities get a bit higher (even with the limits), or if the particle travels in more/less a vertical or horizontal line. Any ideas on why this might be happening? Thank you!
November 15, 2015 @ 1:18 am
Hi! Sorry for the very late response…I missed this comment!
Not sure what the problem may be, but it might be because you don’t put the particle back inside the circle. When you do this kind of boundary bouncing, you have to (1) put any particle which has gone out of bounds back inside the circle (or whatever shape you’re in), and (2) negate the appropriate component of velocity. If you don’t do step (1) it can behave strangely.
January 10, 2016 @ 3:22 pm
