If you’ve seen some of my work at flashandmath.com, then you know that I enjoy playing with particles, especially the 3D variety (for example, see here and here). Below is one of my first adventures with 3D particles in the HTML5 canvas: particles which begin their life on a sphere and then fly away. The effect looks like some sort of microscopic infectious agent, or perhaps just something from nature that would make you sneeze. Call it whatever you like, but I’ll just call it a dusty sphere.
It is important to not that I am making use of a 2D canvas context in this example, instead of using the exciting but still not widely supported WebGL 3D context.
Click here or on the screenshot below to see the dusty sphere in action, and then read my comments below to learn more about drawing in 3D and also about how to evenly distribute points randomly on the surface of a sphere.
Download
Download the full commented source code here: DustySphere.zip
About the code
The code used here is built off of my earlier examples, and features linked lists and an object pool for efficiency. See my earlier post here for a little more discussion of the particle animation code. Also being reused here is the idea of “envelope” parameters (attack, hold, and decay) which control the evolution of the particles over time. In my earlier examples, the envelope parameters were used to change the size of the particles over time; here they control the alpha value, so each particle can fade in at the beginning of its life, and fade out at the end.
I have also made a few changes to the basic particle animation engine: before, the particles were defined using a constructor function so that they could inherit a prototype method, but I have simplified the code by eliminating this setup. The particles are simply JavaScript Objects, and parameters such as position and velocity components are added dynamically.
Drawing in 3D the simple way
The 3D imaging used here is very simple for a couple of reasons. First of all, the objects to be drawn are simple dots. If they were planar images (like sides of cubes, flipping playing cards, or twirling snowflakes), we would have to worry about skewing the images properly when they are viewed obliquely. But simple dots suggest spherical objects which look the same no matter what angle you view them from. Also, to simplify matters the particles are all given the same color, so we don’t have to worry about depth sorting. If you’re not familiar with depth sorting, the idea is that when we draw objects in 3D, objects which are behind other objects have to be drawn first and then the nearer objects are painted over them. But if they are all the same color then the layering is not detectable, so we can draw the objects in whatever order we like.
So without having to worry about skewing images or depth sorting, drawing in 3D just comes down to proper scaling of coordinates and object sizes. Objects further away should appear smaller, and nearer objects should appear bigger. In the coordinate setup used here, the x and y axes are in their usual position for canvas drawing: x goes from left to right, y goes from top to bottom. The third axis, z, can be thought of as pointing out of the computer screen toward you. (The more mathematicalminded reader will note that this choice of z direction creates a lefthanded coordinate system instead of the usual righthanded version. This was an arbitrary choice.)
The transformation to use is simple. First we must set a few parameters. The first parameter, fLen
, can be thought of as the distance from the viewer’s eye to the origin, where the line of sight is along the zaxis. Second, we define two coordinates projCenterX
and projCenterY
which set the position in the 2D view plane where the 3D origin will be projected. Then anything which is to be drawn at the 3D point (x,y,z) should be drawn in the 2D plane at the projected x and y coordinates
projX = fLen/(fLen  z)*x,
projY = fLen/(fLen  z)*y.
Rotating and projecting
To make things a bit more interesting, the whole space containing the particles rotates sowly about a vertical axis. An equivalent way to think of this is that the viewer’s eye (or camera) is rotating around the display. For an extensive and wellwritten article explaining the mathematics behind 3D projections and rotations of coordinates, see Barbara Kaskoszâ€™s excellent post at flashandmath here. In the example here, however, things are simplified because the rotation is occuring automatically (not from user interaction), and the rotation only alters the x and z coordinates (because the rotation is about a vertical axis).
Here is how this all comes together in the code for the demo above. First, a current rotation angle turnAngle
is set by adding a fixed amount turnSpeed
on every frame:
turnAngle = (turnAngle + turnSpeed) % (2*Math.PI);
We will need the sine and cosine of this angle twice each, so we first calculate
sinAngle = Math.sin(turnAngle);
cosAngle = Math.cos(turnAngle);
We will now determine the rotated 3D coordinates rotX
and rotZ
for a particle p
which is at a point with coordinates p.x
, p.y
, p.z
(note that the y coordinate will remain unchanged). If we were rotating about the yaxis itself, the correct transformation would be
rotX = cosAngle*p.x + sinAngle*p.z;
rotZ = sinAngle*p.x + cosAngle*p.z;
But in fact, we are rotating about a vertical axis at the center of our sphere, which is set to a z‑coordinate sphereCenterZ
. So the rotational transformation has to be adjusted accordingly:
rotX = cosAngle*p.x + sinAngle*(p.z  sphereCenterZ);
rotZ = sinAngle*p.x + cosAngle*(p.z  sphereCenterZ) + sphereCenterZ;
Finally, we project these new 3D coordinates (rotX, y, rotZ) to the 2D viewing plane using the transformation described above:
m = fLen/(fLen  rotZ);
p.projX = rotX*m + projCenterX;
p.projY = p.y*m + projCenterY;
It is at this point in the 2D viewing plane where we will draw our particle, and its size will be scaled by the same factor m
.
Depth based darkening
To add to the 3D effect, particles which are further away from the viewer are colored more darkly than nearer particles. This is achieved by simply lowering the alpha value of particles further back, which has the effect of darkening them as they are drawn on a black background. Again, because the particles all have the same color, the alpha blending makes depthsorting unnecessary.
Randomly distributing points on a sphere
Choosing a random point on a sphere is easy if you understand spherical coordinates (see the Wikipedia entry here). You simply need to randomly choose a random angle theta
; ranging from 0 to 2pi; and another angle phi
; ranging from 0 to pi (the radius coordinate will be the constant sphere radius). But if you set these random angles in the naive way:
//WRONG way
theta = Math.random()*2*Math.PI;
phi = Math.random()*Math.PI;
then points near the poles of the sphere will be more heavily weighted and this will not give you an even distribution of points. Counteracting this bias can be accomplished using the arccosine function:
//RIGHT way
theta = Math.random()*2*Math.PI;
phi = Math.acos(Math.random()*21);
This has the effect of more frequently choosing angles near the equator, which is what you want, because in an even distribution of points there are more points near the equator than there are near the poles.
The particle motion and evolution
Each particle has a lifetime which is kept track of using its age
property, which increments ahead by one on every update of the screen. The particle will have different attributes and behaviors according to its age. If the age is still less than its stuckTime
, then the particle will remain stuck to its initial position. After this period of time, its velocity and position will be updated according to some acceleration factors. The initial velocity parameters will cause the particle to fly outwards away from the sphere center (after it becomes unstuck). Some random acceleration amounts will be added on each frame to create some irregular motion.
The particles also change their alpha value so that they fade in and out over time. These timing parameters are called the attack, hold, and decay times. A particle will go from alpha zero to its maximum alpha in the attack time, hold the maximum alpha for the duration of the hold time, then fade back to zero alpha in the decay time. When a particle reaches the end of this lifecycle, it is removed from the list of active particles and placed into a recycle bin (object pool) to be used again later when another particle needs to be added to the display.
Experiment!
It is easy to modify this example to your liking. You simply need to decide how you want the particles to move through 3D space, and set the x, y, and z coordinates of the particles within the code. You can leave the projection computations in the code untouched, and change the rotational speed or remove the rotation completely. Just be aware that this is not the ultrafast hardware accelerated 3D rendering that you’ll get from WebGL or Flash Stage3D. But for simple 3D drawing, a 2D canvas is sufficient.
Very impressive. Beautiful animation and blurring. Nice post, and site!
April 14, 2012 @ 5:43 pm
Thanks!
April 14, 2012 @ 8:27 pm
Looks great. I was playing around with your code and tried to make the canvas background transparant. Any tips to accomplish that?
February 22, 2013 @ 2:54 pm
To make the background transparent, take out the
fillRect
on line 167 and 168 and replace it with:March 1, 2013 @ 3:12 am
Really nice work.
November 7, 2013 @ 7:22 am
Thank you!
November 26, 2013 @ 3:00 pm
Hi,
I really impressed. It’s very fluid and code is quite generic.
Can I use it on my projects of course referencing here?
January 18, 2014 @ 3:38 pm
Glad you like it! Sure, go ahead and use the code. Acknowledgment appreciated, if possible.
January 18, 2014 @ 6:05 pm
Hilarious! Nice Dan, I did not catch the name the first time. I was looking around for particle trails in js for Canvas… low and behold the trail master! I knew the way that code was written looked familiar. Big fan of flashandmath.
Love the work… love the math!
May 16, 2014 @ 4:57 am
Sorry I missed your comment for so long! Glad you like my work, and thanks for your kind message.
June 25, 2014 @ 1:02 am
This is great work, I really like the effect and the code is easy to follow for a beginner.
I’ve been trying to work out if there’s a way of generating particles with different colours? Or failing that a way of smoothly blending the rgb of all the particle?
Cheers
January 31, 2015 @ 12:48 pm
Hi Tom,
I’m sorry I never saw this comment! You can certainly set individual colors for each particle – in the code, add the color as a variable to each particle object, then use this while drawing each particle. However, this won’t give the desired results because then you will have to code depthsorting so the particles appear in front or behind each other properly. But if you use additive blending, this would be a cheap way to avoid depthsorting. Use the “lighter” globalCompositeOperation for the canvas.
Thanks for stopping by!
March 18, 2015 @ 5:01 am
Hey, I’m trying to change color of the whole globe of particles to something else. Is this possible?
And I would also like to make the whole background transparent, is that also possible?
ive been trying to play around with it but it dosent seems to work. Awesome anyway!
March 18, 2015 @ 3:08 pm
Just found how I changed all that! But I wanna know how you another content over that one? As it seems right now the particles will always be in front of everything else, could I change that?
Still really fun to play with, thanks for sharing! đź™‚
March 18, 2015 @ 4:57 pm
Hi Robin,
Sorry for never responding to your comment until now! Glad you were able to figure out how to change the colors. As for putting more content in front or behind this, the easiest way may be to use a second canvas, placed on top with absolute positioning in your CSS.
May 14, 2015 @ 2:21 am
Hi Dan,
Great work! The effect is really nice and easy to follow.
I was just wondering whether it would be possible to have the particles rotating around in 2D opposed to on the zaxis? I essentially need them to be rotating around a circular shape.
Thank you for sharing!
February 14, 2017 @ 11:47 am
Certainly possible, but I don’t have the time to do it myself! If you want particles to rotate around a central point in 2D, there are various ways to do this, but the easiest is to set coordinates as follows:
x = centerX + rad*Math.cos(angle);
y = centerY + rad*Math.sin(angle);
and you should add some small number to angle on each frame.
February 20, 2017 @ 12:54 am
