perjantai 9. maaliskuuta 2018

Making of Cyber Auroras



Update - I'm happy to annouce the demo got 3rd place in the JS1k 2018 competition!

1. Changing the process


For JS1k 2018 I took a new approach to creating a small demo. In the past I had come up with some fancy rendering techniques and built the demos on top of those ideas over several iterations. When looking back at them I always had some regrets and felt that some of the early versions might have been better than the final release. Also I didn't feel like the programming was much fun after the initial PoC versions were done. Since I'm always seeking to improve it was time to change this process. Thanks to my partner's suggestion I started using Pinterest to search and collect inspiration. Through this fun period of just browsing through images and videos I got several ideas and I started to think how they could be implemented with limited bytes. I created a Padlet page to write small notes and store all kinds of related stuff. The demo started to become alive even though not a single line of code existed. I narrowed the ideas down to two which eventually were combined into one.

2. Julia fractal auroras


I wanted to try rendering a Julia fractal since I've never done anything with them before. I quickly programmed a small demo that would floorcast the fractal and then move it around to paint a landscape. Mirroring the shadow of the fractal to the top of the image reminded me a lot of northern lights aka auroras. As a nature photographer this concept really hit me.



3. 80s wireframes


I always get inspired by retro style art with simple wireframes and went through a ton of them on Pinterest. I then came up with a scanline rendering technique which could do those landscapes in small size. I wrote down the logic and then did a test implementation of a Taiga forest. Again working from sources of inspiration towards my personal interests.




4. Implementation


Since I had done a lot of background work I was really anxious and motivated to start programming the demo. I had a clear vision of the end product and had achieved a personal connection to it on many levels. I was so excited I basically did the whole thing over one weekend. I code-golfed the PoC implementations as small as I could so I would have room to add a storyline and add some bonus effects. As usual the biggest improvements came from simplifying the visuals rather than working on the code itself:

  • I dropped the vertical lines from the wireframe engine and made the height function really simple
  • I limited the number of total colors
  • I rendered the fractal with really large pixels and only two colors. This not only saved space but made the demo run nicely on mobile phones
  • I reused my old implementation of a starfield for a cheap night sky

Now I had a solid demo and a lot of bytes still left. It was time to go back to Pinterest to look for those little things that make a huge difference...


5. Adding the storyline


I think the reason that my Highway at Night demo became somewhat popular was because it not only created a small world but also had a story. Events followed each other and got crazier towards the end just when you thought you'd seen it all. I wanted to do the same thing this time and because of the auroras theme it was obvious how it all should go...
  • Start with a nice landscape view with the camera following the path of a river
  • Sun sets changing the water color and revealing the stars in the sky
  • Scene opens up for the upcoming light show when we arrive at a lake
  • Auroras fade in and approach the viewer
  • Water starts reflecting the sky adding more interest to the scene
At this point I had pretty much ran out of space but I needed a grand finale. I got the idea for a meteor shower from a GIF on the internet showing objects hitting water. It was time to do some more code-golfing because it was clear I wouldn't be happy without getting it all in. Luckily my scanline rendering engine allowed to do the splashes with small cost and the meteors could be drawn as a simple chain of growing rectangles.


After some more heavy size optimizations I could actually get lazy and just do the background mountains with regular canvas lineTo-function and add shadowBlur to them. I know I could have done it differently to save space for even more things but I felt the demo was ready. I released it two weeks before the deadline. I was and still am very happy with it. I was left with many more ideas in my head but luckily found my way to Dwitter. I spent the next two weeks releasing several 140 bytes demos there and you can check them all out HERE. I'll be writing about them later.

6. Conclusions and source code


Inspiration and personal connection to the subjects will drive you to better performance and leave you a much happier programmer! Thanks for reading and see you on Twitter & Dwitter. :)




X=0;
S=50;
w=S*6;
setInterval(()=>{
X||(Z=T=0); // intialize counters
X=X>999?Z=0:X+.2; // loop back to start
a.height=a.width=W=w*2;
c.fillRect(0,0,W,W);
c.fillStyle="#A4A";
c.globalAlpha=(Z++-W)/W;
for(x=W;x&&Z>W;x-=6)// Julia
for(y=W;y>w;y-=6){
i=W*S/(y-w);
k=(X+i*Math.cos((x-w)/W)-S)/W;
l=i*Math.sin((x-w)/W)/W;
for(i=0;k*k+l*l<4&&i<40;i++)z=k*k-l*l+Math.cos(Z/w)/20-.8,l=2*k*l+.2,k=z;
if(i>25){
c.fillStyle=i>38?"#A4A":"#0C4";
c.fillRect(x,W-y,3,5);
X>420&&c.fillRect(x,y,3,1)// reflection
}
}
c.globalAlpha=1;

s=Z<w?0:s+.3; // sunset
for(x=7;x--&&s<S;)c.fillRect(250+x*x,337+s-x*9,99-x*x*2,6);
c.fillStyle="#0AC"; // stars
for(x=w;x&&s>S;x-=6)c.fillRect(x*x%W,x,1,1);
c.fillStyle="#112"; // mountains
c.strokeStyle=c.shadowColor="#0FF";c.shadowBlur=S;
c.lineTo(-99,350);c.lineTo(90,220);c.lineTo(140,270);c.lineTo(170,250);c.lineTo(w,360);c.lineTo(500,220);c.lineTo(700,350);c.fill();
c.fillStyle="#000"; // mask
c.fillRect(c.shadowBlur=0,340,W,S);

p=Z/S|0;
for(y=p*S;y<W*4+p*S;y+=S){ // scanlines
c.beginPath();
for(x=W*4;x-=S;){
z=w/(y-Z+w); // scaling
k=Math.abs(W*2-x)/(W+y)*9-1|0; // terrain height
i=w+(x-W*2)*z;j=w-(k*90-w)*z; // project to screen coordinates
y<w*7&&y%80<S&&x!=W*2&&c.lineTo(i,j);
k=y>1900?y-1900:0; // river flows into a lake
if(x==W*2){
c.fillStyle="#000";
y<W*4&&c.fillRect(i+Math.cos(y)*S*z-(S+k)*z,w+w*z,(150+k*2)*z,S*z);
c.fillStyle=s>S?"#0AC":"#A4A";
c.fillRect(i+Math.cos(y)*S*z-(S+k)*z,w+w*z+70*z,(150+k*2)*z,.5)
}
T>S&&( // meteor hit
k=(Q-120)*-4+x,
l=y-p*S-W,
j=Math.sqrt(k*k+l*l),
T>S&&j<T*4&&j>T*4-70&&c.fillRect(i,w+w*z,4,2) 
)
}
c.stroke()
}
// meteor shower
if(T){
for(x=20;x--&&T<S;)c.fillRect(Q-T*2-x,T*7+x*3,x/4,x/2);
T=T>120?0:T+.9
}
!T&&Z>W*6&&(Q=w+Z%w,T=1)
},9)

Ei kommentteja:

Lähetä kommentti