This year is my third year participating in JS1k, a contest about making something cool using Javascript in 1 kilobyte or less. After submitting two text based demos (those are two links, btw), this year I submitted a more pleasing to the eye demo and I called it “Elemental Forest”.

This demo was inspired by Grove, an Android Experiment by Simon Geilfus. Also, I would like to thank Coding Math YouTube channel for the awesome tutorials. Without it my submission this year probably would just be another text based one.


In this and the next post I will explain the code that works behind the demo, but before that let’s take a look at the ‘original’ source of the demo (you will know why the word ‘original’ is quoted). You can also view the source code on GitHub Gist.

/*

  Story time, after submitting an update to the demo I planned to make another
  update for the demo (which I didn't submit in the end) so I edited the code
  to do some edits etc etc. And maybe you've guessed it, stupid me didn't make
  a copy of the original source of the demo and just modified the code straight
  away. Therefore, I have to reverse the changes in the code but I can't seem to
  restore the changes completely. So, here's the almost original source of the
  demo. It's not exactly the same as the one submitted, but it's almost the same
  and the differences between this and the original code should be just very
  minor things.

  TL;DR this is not exactly the original source of the demo I submitted.

*/

/*

  a, b, c = shim
  e = theme color h
  f = frame number
  g = theme color l
  h = canvas height
  i, k, K = looping variable
  r() = generate seeded random number
  R() = render
  s = random number seed
  S = random number
  t[] = road data
  T() = draw tree
  w = canvas width
  x, y, z = function variable
  X, Y, Z = placeholder variable

*/

S = Math.random(),
s = f = 0,
t = [],
e = S * 360;

for(k=200;k--;)t.push([Math.sin(k*(Math.PI/180))*500-250,150+Math.cos(k/30)*50,180-k,Math.random(),k]); //[x,y,z,seed,tree type]
R()

function r(x,y,z) {
  s = (s * 9301 + 49297) % 233280;
  return s / 233280
}

function R(x,y,z) {
  s = S;
  c.fillStyle="hsl("+[e+=.1,"90%",g=s*20+50]+"%)";
  c.fillRect(0,0,w = a.width, h = a.height);

  for(k=2;k--;) {
    c.save();
    c.fillStyle = "rgba(0,0,0,.1)";
    c.translate(0,k*h/5+h/10-Math.cos(f/30)*20);
    c.beginPath();
    c.moveTo(w,h);
    c.lineTo(w,0);
    for(i=15;i--;)c.lineTo(w/15*i+r()*20, 30*r());
    c.lineTo(-w,h);
    c.fill();
    c.restore();
  }

  for(K=199;K--;) {
    k=(K+(f%200))%200;
    c.save();
    p = 30 / (30 + t[k][2]--), s = t[k][3];
    Z=(h/2>230)?230:h/2;
    c.translate(w/2+500*Math.sin(Math.PI/180*f)*-p,h-Z-20*Math.cos(f/30));
    c.scale(p,p);
    r();
    c.fillStyle = "hsl("+[e,"90%",g-30+t[k][2]/13]+"%)";
    c.fillRect(-h*15,t[k][1],h*30,10);
    c.fillStyle = "hsl("+[e,"90%",g-25+t[k][2]/13]+"%)";
    c.fillRect(t[k][0]-50*r(),t[k][1],500,20);
    c.fillStyle = c.strokeStyle = "hsla("+[e,"90%",t[k][2]/8+15+r()*3+"%",1-(t[k][2]-150)/30]+")";
    if(r()<.5)T(t[k][0]-150-2500*r(),t[k][1],t[k][4]);
    if(r()<.5)T(t[k][0]+2500*r()+650,t[k][1],t[k][4]);
    c.restore();
    if(f%200==k)t[k]=[Math.sin((f+181)*(Math.PI/180))*500-250,150+Math.cos(k/30)*50,180,Math.random(),200+f];
  }
  f++;

  requestAnimationFrame(R)
}

function T(x,y,z) {

  if(z%1e3<500) {
    c.lineWidth = r()*12+28;

    c.beginPath();
    c.moveTo(x,y);
    X = x-20 + r()*40;
    Y = y - r()*40 - 280;
    c.lineTo(X,Y);
    X = x-20 + r()*40;
    Y = Y - r()*40 - 280;
    c.lineTo(X,Y);
    c.stroke();

    c.beginPath();
    for(i=0;i<360;i+=r()*15+50) {
      if(i<1)c.moveTo(X + Math.cos(i*(Math.PI/180)) * (180 + r()*70), Y + Math.sin(i*(Math.PI/180)) * (180 + r()*40));
      else c.lineTo(X + Math.cos(i*(Math.PI/180)) * (180 + r()*70), Y + Math.sin(i*(Math.PI/180)) * (180 + r()*40));
    }
    c.fill()
  }
  else {
    c.beginPath();
    c.moveTo(x,y - r()*80 - 600);
    X = 99 + r()*50;
    c.lineTo(-X+x,y);
    c.lineTo(X+x,y);
    c.fill()
  }

}

The code was then passed through Closure Compiler and RegPack, which is a little improvement from the past years because I haven’t discovered RegPack yet and used JS Crush instead which is pretty good already. Also, if you’re wondering, the original original source was 1688 bytes long and then crushed into 1004 bytes (-684B, -40.52%). Yes, I copied that data and that’s how I knew that I didn’t recover the whole original source successfully.

The Trees

I decided to talk about these first and in a separate section because let’s face it, most of the things you see in the demo are just trees. If you watched the demo long enough, you will discover that there are two types of trees, the ‘normal trees’, and the ‘triangle trees’. I will only cover the normal ones because the triangle trees literally are just triangles.

The function that draws trees is named T() and could be found on lines 91-122 of the gist. The parameters x and y contain the x and y positions of the tree, and the parameter z is used only to determine whether to show the normal or triangle trees.

The trunk of the normal trees are made of two lines with the line width of r()*12+28 (r() is a seeded random number generator function). The leaves are a little bit more complex, and I recommend you to watch the 4th episode of Coding Math first. In the end of the video you could see circles that are drawn in a circle shape using simple trigonometry, and that’s almost exactly how the leaves are drawn. But instead of drawing circles, the position is used for the lineTo (or moveTo) function and then fill will be called. To make the trees look a little bit different for each of them, the radius of the ‘big circle’ and the angle between each points are a little bit randomized.

In the next part I will cover about the rest of the stuff in the code, thank you for reading!