Mijn inzending voor de demojs.org Js1k

Ik las op de twitter van Js1k dat DemoJS, een franse demoscene bijeenkomst, een js1k categorie voor inzendingen had. Na mijn eerste simpele poging tot een demo wilde ik wel eens zien hoe hoog ik kon komen in een echte competitie. Het resultaat? 4e plaats achter niemand minder dan p01.
En dit wordt dus vierde
Met zo'n hoge score moet er dus wel iets speciaals aan zijn. Tijd voor een technische ontleding dan maar. Om te beginnen, waaraan heb ik de bytes verspilt? Een snelle telling levert het volgende:
Basis set-up: 86 bytes
Punten genereren: 185 bytes
Rotaties: 181 bytes
Tekenen: 175 bytes
Radial blur-effect: 119 bytes
Andere dingen waar ik niet direct een naam voor heb: 273 bytes

Dit brengt mij tot het prachtige totaal van 1019 bytes. Ik schaam mij uiteraard diep dat ik niet alles heb volgekregen, maar het zij zo. Nu even iets dieper op de materie ingaan. Hoe heb ik die bytes echt besteed?

De ring zelf

De ring bestaat uit 20 "objecten" waarvan ik twee dingen weet: plaats (xyz notatie) en kleur. De plaats is vrij simpel. Ik voegde een kleine ribbel in de ring toe omdat een gewone ring wat saai is. De punten liggen hierdoor 0.05 van de centrale cirkel af. Dit is trouwens geen berekend getal, gewoon proberen. 0.1 was te veel en minder zag je bijna niet. Design by tweaking was hierbij vrij nuttig.
for(i=0;i<20;i++) {
 x = cos(i * 2 * PI / 20);
 y = sin(i * 2 * PI / 20);
 z = (i%2 - 0.5) * 0.1; // Levert 0.05 of -0.05, om en om.
}

Voor de kleur had ik een aantal ideeën, iedere met zijn eigen voordelen.
r = Math.random();
// n>>0 is een verkorte versie van Math.floor(n).

Methode 1: kleur = "rgb("+(r()*256<<0)+","+(r()*256<<0)+","+(r()*256<<0)+")"; // 58 bytes;

Methode 2: kleur = "#"+(0xffffff*r()>>0).toString(16); // 35 bytes

Methode 3: kleur = "#"+(16e6*r()>>0).toString(16); // 31 bytes

Hoewel methoden 2 & 3 korter zijn, had ik genoeg ruimte over voor methode 1, en het voordeel van die is dat hij altijd valide kleuren levert, in plaats van 93% (methode 2) of 89% (methode 3). Daarnaast ga je er dan ook van uit dat browsers je fouten netjes opvangen (wat ze trouwens wel doen, kudos daarvoor).

Het scherm

Ik wilde dat de demo op volledig scherm zou draaien, zonder vervelde scrollbars en dat hij mee zou schalen als je het venster verkleinde. Ik had voor de veelgebruikte methode van width = innerWidth - 20 kunnen gaan, maar besloot dat niet om 2 redenen:

1. Je gebruikt dan stiekem niet het hele scherm
2. Alleen Chrome houdt de scrollbars verborgen bij het verkleinen van het scherm.

Toevallig kwam ik er achter dat als je je Canvas een "style"-attribuut met "position: absolute;top 0;left:0;" je het element wel volledige breedte en hoogte mee kan geven. Dit trucje werkt zelfs volledig cross-browser. Awesome.

Het beeld

Hier gaat het uiteindelijk allemaal om. Ten eerste de zwarte achtergrond. Normaal gesproken zou je een schermvullende zwarte rechthoek tekenen, maar hier is een makkelijker alternatief. Aangezien ik toch al een stijl toewijs aan het canvas, kan daar net zo makkelijk "background:#000;" aan toegevoegd worden. Nu hebben we een zwarte achtergrond voor slechts 16 bytes.

Over de projectie ga ik niet al te veel uitwijden, dit zijn dezelfde rotaties als gebruikt bij de 3d kubus. Wel gebruik ik ook hier weer "bollen", omdat deze makkelijk perspectivisch correct te laten zijn.

Bij het uiteindelijke tekenen speel ik ook een beetje vals. Door gebruik te maken van globalCompositeOperation "lighter" hoef ik namelijk geen extra bytes te gebruiken voor Z-buffering. Weer een klein beetje winst.

Blur

De ring zelf is vrij saai om naar te kijken, dus moest er ook nog een effect overheen. Om de ring "lichtgevend" te laten lijken besloot ik radial blur over het scherm heen te zetten. Dit gaat als volgt. Je maakt een tweede canvas aan, en kopieert daarop de afbeelding, lichtjes vergroot, in dit geval 1.1 keer.
De nieuwe laag kopieer je transparant terug over de oude. Als je dit een paar keer herhaalt met toenemende transparantie, krijg je een vrij overtuigend effect.

Andere dingen

Toen de shim bekend werd, bleek dat ik een aantal dingen al cadeau kreeg, en dus nog zo'n 150 bytes over had. Deze zijn uiteraard nuttig besteed.

Ik besloot om de ring op te delen in twee ringen, die steeds van straal veranderen. Dit is makkelijker dan het lijkt. Door puntnummer modulo twee te doen heb je namelijk heel makkelijk om en om 1 of 0. De straal krijg je dan als volgt:
R = (i%2?2:.5)^sin(e);
waarbij R de uiteindelijke straal is, i het puntnummer, en e het framenummer maal een constante. Hierdoor maakten een punt en zijn volgende altijd een mooie tegengestelde beweging, die nog vloeiend was ook. Ideaal.

Toen ik daarna nog wat overhad besloot ik een oude truc te gebruiken, voeg ruis toe. Door de berekeningen van de tweede rotatie iets te vervuilen met nog een tweede sinus erin danst de ring over het hele scherm heen, in plaats van alleen in het midden.

Tot slot

Ik ben nog steeds ongelofelijk trots dat ik met mijn armzalige poging het toch nog tot de 4e plaats heb weten te schoppen. Daarnaast hoop ik dat deze how-to andere mensen inspiratie geeft om ook een mooie demo in elkaar te zetten.
Bert Peters | 13:52:00 04/07/2011 | Link | 0 reactie(s) | Tags: HTML5 JS1K 
tweet


Reacties


Laat zelf een bericht achter

Naam (verplicht)
E-mail (verplicht, nooit publiek)
Typ deze tekst over: captcha
Link
Opmerking