885 words ~ 4-8 minsMathieu 'p01' Henri on August 3rd, 2013


The Andes are the world longest mountain range: 7,000 km and 1,022 bytes of JavaScript, making for a nice entry for Assembly 2013

There is also a video capture and unpacked version in case your browser or graphic card does not support webGL or present some compatibility issues with the bootstrapped versions.

No rest for the wicked

It's only been one month since MINAMI DISTRICT. I was really happy with the design and result but somehow I missed the mark.

Assembly 2013 was around the corner. It was the perfect opportunity to test my growing interogation of where do JS1k prods stand against the big guys. To play anywhere near the same league, the choice of technology was obvious: webGL + audio + PNG bootstrapping

While looking in my inspiration folder, I kept coming back to this picture by Geoffroy THOORENS, concept artist and matte painter:

An epic scene, perfect to render in a relatively small raymarcher.

Two weeks to production

Time was running quick, I had some reading and testing to do to render a nice landscape in webGL, let alone in 1k. After one week of tinkering and failing, I finally got a decent landscape. Unfortunately it was clear the falling cubes would not fit.

Pluging the shader into the setup of HYPERSONIC MANDELBULB, I could start optimizing and working on the sound. The optimization was straight forward: work on the shader, lower the entropy of the code, etc... The sound part was challenging. I was hoping to get some wind and a melody or at least a sweep sound. All attempts to make a melody sounded too simple and repetitive.

Quite a mix

The PNG bootstrapping technique relies on a Canvas element and its 2D context. Unfortunately it's not possible to get another context from the same Canvas element, so another Canvas element needs to be created, and styled to take the whole screen, and get a 3D context. The most compact way to do this was to set the outerHTML of the existing Canvas to replace it completely. Yuck!

c.outerHTML='<canvas width=768 height=432 id=d style=position:fixed;left:0;top:0;width:100%;height:100%;background:#210>';

For the animation loop I usually go with a simple setInterval(foo,9) but when you deal with webGL, this approach makes it too easy to flood the GPU and crash the graphic driver. This time I had no choice but to do the right way and use requestAnimationFrame(foo). Also, using Date objects in order to synchronize the intro and outro fades was too verbose. The shortest and most elegant way was to use the currentTime property of the Audio element. Nice!


A familiar face ?

If you follow the demoscene, you may recall a brillant Windows 1k intro written in assembly and HLSL featuring a mountain landscape: HIMALAYA by TBC, released at Icons 2008:

Well, to be honest I recalled this one a little late.

I was worried about the similitudes. But upon watching both intros I fell ANDES had enough merits on its own to not scrap the project altogether. Sure it has less features. A lot less actually. However I believe the camera, design and color treatment bring something to the table.

Beside it's been 5 years since HIMALAYA, and both use a very different set of technologies. If TBC, or anybody find an offense there. I'm sorry, it was not my intention to rip off HIMALAYA. If anything the name, ANDES, is an homage to HIMALAYA.

Something missing

There are many things I wished I did better or managed to fit in 1024 bytes:

All in all, I am quite happy with the result.

Source code

Here is the PNG bootstrap.

<canvas id=c><img src=# onload=for(a=c.getContext('2d'),i=e='',S=String.fromCharCode;a.drawImage(this,i--,0),t=a.getImageData(0,0,1,1).data[0];)e+=S(t);(1,eval)(e)>

And here is the actual code of ANDES. Note that the PNG bootstrap exposes the following variables: c, t and S

c.outerHTML='<canvas width=768 height=432 id=d style=position:fixed;left:0;top:0;width:100%;height:100%;background:#210>';for(x in g=d.getContext('experimental-webgl'))g[x[0]+x[6]]=g[x];with(g){for(p=cP();s=cS(t+35632);ce(s),aS(p,s))sS(s,t++?'attribute vec4 t;void main(){gl_Position=t;}':'precision lowp float;uniform float t;float a(vec4 r,int d){float f=0.,s=32.;for(int i=0;i<48;i++)if(i<d){vec4 n=fract(r/32.)-.5;f+=(n.x*n.x+n.z*n.z)*s;s*=.5;r.xz*=mat2(1.2,1.4,-1.4,1.2);}return f;}float f(inout vec4 r,vec4 y,int d){float f=0.,s=32.;for(int i=0;i<48;i++)if(.1<s&&f<32.){f+=s=(r.y-a(r,d))*.3;r+=s*y;}return f;}void main(){vec4 r=vec4(1.2+cos(t/8.)*8.,24.+cos(t*.3),t,0.),y=normalize(vec4(gl_FragCoord.xy/vec2(24.,13.5)-16.+cos(t),8.,0.)),m=vec4(.1,0.,min(1.,f(r,y,8)/32.),0.);gl_FragColor=mix(vec4(f(r,normalize(vec4(a(r+m.xyyy,8),a(r+m.yxyy,8),a(r+m.yyxy,8),a(r,8))-a(r,8)),2)/32.),vec4(.8,1.2,1.4,1.)*y.z,m.z)-pow(t/32.-1.,32.);}');vA(eV(bf(34962,cB())),2,5126,lo(p),ug(p),bD(34962,new Float32Array([1,1,1,-3,-3,1]),34962+82));for(r=q='data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEA5h0AAOYdAAABAAgA';t<5e5;t++)q+=S(Math.random()+t/(32.+Math.cos(t/8.))%5);o=new Audio(r+btoa(q));m=function(){dr(4,uniform1f(gf(p,'t'),o.currentTime),3);requestAnimationFrame(m)};m(}

You can clearly see the different parts:

Different aspect ratios

Considering the size limit, I had to make one version of ANDES for each of the common aspect ratios:

Hope you like ANDES. Make sure to leave comments, piggies and thumbs up or down for ANDES on