Drawing a Galaxy with Mathematics and Javascript

On the surface HTML Canvas does not offer many interesting looking tools to work with; boxes, circles, lines, sprites and gradients don’t promise very much. They may even seem painfully basic and even experts on the subject are not greatly enthusiastic:

“The basic setup is a little complicated, the involved objects themselves are poorly designed, which is also a result of a real decrease in the evolution of the standards.” – Bucephalus.org

However, the power comes from using these things programmatically within a scripting environment, and this is what I did using Javascript. You don’t have to exploit the awesome power of WebGL to produce something interesting, indeed a project such as this could serve as a springboard into the world of shader programming, which exists at the intersection of mathematics, art and computing.

At this point I’m going to assume you already know the basics of html and javascript and just concentrate on the mathematical functions used to plot the stars to the canvas. If you know enough about javascript and the HTML Canvas to paint a circle on a screen then you have enough prerequisite knowledge.

Let’s begin with the following. Visit my example of this project and set Arm Radius and Theta Value to minimum and increase the values of Star Spread and Star Density to see something like this:

looks like a globular cluster

This strongly resembles a globular cluster, not a galaxy as such, but it serves as a kind of background to the finished galaxy as we will see later. Indeed, a few galaxies do not have arms at all and resemble a blob.

ESO 486-21
ESO 486-21, there is the faint sensation of a spiral if you look carefully.

For each (x, y) coordinate point on the canvas we can define a probability field that extends outwards from the very centre of the canvas to a given radius. In polar coordinates we may express this as follows:

f(r) = P_0 \,e^{-(r- d)^2/s}

You may notice this strongly resembles the Gaussian distribution function. The variables are as follows:

  • r is the radial distance from the centre, and obeys the condition r2 = x2 + y2
  • d is the displacement of the centre from the origin of the coordinate system
  • s is the “spread” term that controls how much the probability field spreads out from it’s centre
  • P0 is the maximum probability at the centre of the field

Try inputing the search term “graph f(x) = 1*e^-(((x – 5)^2)/50)” to see this demonstrated on a regular graph in one dimension.

the probability field in one dimension

Notice how the displacement term, which above is 5, locates the maximum of the function at x = 5, this can be used to locate the probability field at the centre of the canvas instead of the origin of the coordinate system which is the top-left corner.

You can use this equation to define a radially symmetrical probability field for every point (x, y) on the canvas. The probability_array stores the pre-calculated value. Combined with the random number generator that comes with javascript we can use the following condition to determine if we draw a point to represent a star:

if(Math.random() <= probability_array[c])
{
    drawStar(i,j, STAR_SIZE, "white");
}

The argument c corresponds to a unique index for every point (xi, yj) on the canvas. Lines 108-164 of my example demonstrate the entire generation and drawing process.

That demonstrates the basic idea behind the background haze of the galaxy, now onto the arms.

Like so many things in nature the arms of a spiral galaxy roughly conform to the proportions of a logarithmic spiral.

Galaxy - M51

The galaxy arms are modelled as logarithmic spirals using this equation:

r(θ) = ae^{kθ}

This can be quickly parameterised as:

x = ae^{kθ}*cos\,θ 
\newline
y = ae^{kθ}*sin\,θ

Entire books can be written about the logarithmic spiral in nature, and still only scratch the surface.

In a function simply called “spirals”, which is defined on lines 169-222 of my example, I use the parameterised logarithmic spiral equation to draw the spiral arms to the canvas with a slight amount of random “fuzz” in the x and y directions to create the sensation of a natural distribution.

// calculating star positions and adding some random noise via fuzz_fac
for(let i = 0; i < theta_arg; i++)
{
    theta = degrees_to_radians(i);

    let x = r*Math.exp(b*theta) * Math.cos(theta + Math.PI * rot_fac)
    + ((Math.random() - Math.random()) *fuzz) * fuz_fac*(1 - 0.6*(i/theta_arg));

    let y = r*Math.exp(b*theta) * Math.sin(theta + Math.PI * rot_fac)
    + ((Math.random() - Math.random()) *fuzz) * fuz_fac*(1 - 0.6*(i/theta_arg));

    spiral_stars.push([x+offset, y+offset]); 
}

By repeating this argument multiple times and with certain arguments one can draw spiral arms at regular intervals around the central axis.

function drawArms(b, rad, rot_adjust, fuzz, radians)
  {
    spirals(-b, rad, 1.94 + rot_adjust, fuzz, 1, radians); // yellow stars of 1 pixel
    spirals(-b, rad, 2 + rot_adjust, fuzz, 0, radians);

    spirals(-b, -rad, 1.94 + rot_adjust, fuzz, 1, radians); // yellow stars of 1 pixel
    spirals(-b, -rad, 2 + rot_adjust, fuzz, 0, radians);

    spirals(-b, rad, 0.44 + rot_adjust, fuzz, 1, radians); // yellow stars of 1 pixel
    spirals(-b, rad, 0.5 + rot_adjust, fuzz, 0, radians);

    spirals(-b, -rad, 0.44 + rot_adjust, fuzz, 1, radians); // yellow stars of 1 pixel
    spirals(-b, -rad, 0.5 + rot_adjust, fuzz, 0, radians);

  }

Thus summarises the basic mathematical ideas behind my galaxy image generator. Try experimenting here and review the source code here.