Starbeamrainbowlabs

Stardust
Blog

Behind the Parallax Stars

Unfortunately I have been busy, so I have been unable to write this post up until now. Anyway, welcome to the second technical post on this blog. This time, we will be looking behind the Parallax Stars.

The FPS and rendering time graphs are drawn by a slightly modified version of stats.js, by mrdoob. It's original github repository can be found here, and the slightly modified version can be found here. The modifications I made allow both the FPS graph and the rendering time graphs to be displayed at once.

The interesting code that makes the stars work is found in stars.js. The function is a constructor that returns an containing everything needed to make the stars appear and animate.

this.build = function() {
    this.data = [];

    for (var i = 0; i < this.number; i++)
    {
        this.data[i] = new star();
    }

    if(this.depthsort == true)
    {
        this.data.sort(depthsort);
    }
};
function star()
{
    this.z = Math.random()*(0.7) + 0.3;

    this.x  = rand(canvas.width);
    this.y  = rand(canvas.height);
    this.radius = Math.floor(this.z * 7);

    var r, g;
    r = rand(200, 255);
    g = rand(200, g - 20);

    this.colour = "rgb(" + r + ", " + g + ", " + 0 + ");"

    this.speed = this.z * 3;
}

The build() function contains some setup code that populates an array with stars, which are constructed by a different function, star(). Each star is assigned an x, y, and z co-ordinate. The x and y co-ordinates determine it's position on the screen. and the z co-ordinate determines it's size and speed. The stars are also given a colour too. The function rand() is a small utility function I sometimes use for generating random numbers:

// EXAMPLES:
// rand(256) returns a whole number between or equal to 0 and 255
// rand(100,201) returns a whole number between or equal to 100 and 200
// rand() returns a decimal between or equal to 0 and less than 1
function rand()
{
    // takes 0-2 arguments (numbers)
    var args = arguments, num;
    if (args[0] && !args[1]) { 
        num = Math.floor(Math.random()*(args[0]));
    } else if (args[0] && args[1]) {
        num = Math.floor(Math.random()*(args[1]-args[0])) + args[0];
    } else {
        num = Math.random();
    }
    return num;
}

The array that hold all the stars is also depth sorted before we start and on each frame to keep the stars in order since stars are leaving the screen and new one coming onto the screen on every frame. The standard array .sort() function is used, along with a custom sort function:

function depthsort(a,b) {
    if(a.z < b.z)
        return -1;
    if(a.z > b.z)
        return 1;
    else
        return 0;
}

The updates that happen every frame are split into two different functions. step() deals with incrementing co-ordinates and keeping the depth sorting up to date, and render() takes the current state and renders it on the canvas.

this.step = function() {
    for (var i = 0; i < this.data.length - 1; i ++)
    {
        this.data[i].x += this.data[i].speed;

        if(this.data[i].x > canvas.width + this.data[i].radius + 1)
        {
            this.data[i].x = new star();
            this.data[i].x = -this.data[i].radius - 1;

            if(this.depthsort == true)
            {
                this.data.sort(depthsort);
            }
        }
    }
}

The this.data array holds all of the stars. For each star, it's x value is incremented by it's speed. If this moves the star off the screen, we replace it with a new star that starts off at the left hand side. The stars are then depth sorted again to keep all the further away stars behind the stars that are (supposed to be) closer to the front.

this.render = function() {
    context.clearRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < this.data.length - 1; i ++)
    {
        context.globalAlpha = 1;
        context.globalCompositeOperation = "destination-out";

        context.beginPath();
        context.arc(this.data[i].x, this.data[i].y, this.data[i].radius, 0, Math.PI*2, true);
        context.closePath();
        context.fill();

        context.globalAlpha = this.data[i].z;
        context.globalCompositeOperation = "source-over";

        context.fillStyle = this.data[i].colour;

        context.beginPath();
        context.arc(this.data[i].x, this.data[i].y, this.data[i].radius, 0, Math.PI*2, true);
        context.closePath();
        context.fill();
    }
}

To start off the rendering, the canvas is cleared. After that, For each star, a hole in the canvas is cleared with the destination-out drawing mode. The reason for this is that the z co-ordinate is also used as an alpha value for each star to make it dimmer the further away it is. This gives a slightly more realistic 3d appearance. After clearing a hole, the drawing mode is rest to the default (source-over), and the alpha and fill colours are set up according to the star's current z co-ordinates and colour respectively. Then the star is drawn. Since when this script was written we did not have a canvas drawing function to draw an ellpise, the context.arc(this.data[i].x, this.data[i].y, this.data[i].radius, 0, Math.PI*2, true); code is used to draw circles via context.beginPath() and context.fill().

Finally, One nice neat update() function is provided that calls render() and step() in sequence:

this.update = function() {
    this.render();
    this.step();
}

That concludes this technical post! If you have any questions, please post them below and I will try my best to answer them.

Tag Cloud

3d 3d printing account algorithms android announcement architecture archives arduino artificial intelligence artix assembly async audio automation backups bash batch blender blog bookmarklet booting bug hunting c sharp c++ challenge chrome os cluster code codepen coding conundrums coding conundrums evolved command line compilers compiling compression conference conferences containerisation css dailyprogrammer data analysis debugging defining ai demystification distributed computing dns docker documentation downtime electronics email embedded systems encryption es6 features ethics event experiment external first impressions freeside future game github github gist gitlab graphics guide hardware hardware meetup holiday holidays html html5 html5 canvas infrastructure interfaces internet interoperability io.js jabber jam javascript js bin labs latex learning library linux lora low level lua maintenance manjaro minetest network networking nibriboard node.js open source operating systems optimisation outreach own your code pepperminty wiki performance phd photos php pixelbot portable privacy problem solving programming problems project projects prolog protocol protocols pseudo 3d python reddit redis reference release releases rendering research resource review rust searching secrets security series list server software sorting source code control statistics storage svg systemquery talks technical terminal textures thoughts three thing game three.js tool tutorial twitter ubuntu university update updates upgrade version control virtual reality virtualisation visual web website windows windows 10 worldeditadditions xmpp xslt

Archive

Art by Mythdael