Kreslíme krychli

Jak již bylo zmíněno dříve, funkce init() nastaví příkazem setInterval(animate,100), aby se funkce animate() volala pravidelně každých 100ms. Funkce animate() dělá vlastní animaci. Nejprve získá ukazatel ctx na kreslící prostor realizovaný pomocí tagu <canvas>. Dále vymaže kreslící prostor a nakreslí na něj krychli v její okamžité pozici. Nakonec body krychle pootočí pomocí matice rotace rot, tj. vezme matici points, kde jsou v řádcích uloženy souřadnice vrcholů krychle, a vynásobí ji zprava maticí rot, viz rovnice (2). Konkrétní kód funkce animate() je následující:

    function animate() {
      var canvas = document.getElementById("canvas");  // Ziskej odkaz na nas <canvas> tag, kam budeme kreslit.
      if (canvas.getContext) {                         // Tohle je tady jen proto, aby prohlizec, ktery nepodporuje <canvas> tag, nedelal nic.
        var ctx = canvas.getContext("2d");             // ctx je promenna pomoci, ktere lze k nasemu canvasu pristupovat.
        ctx.lineJoin = "round";                        // Nastav, aby spojeni dvou car bylo zaoblene.
	ctx.clearRect(0,0,150,150);		       // Vymaz canvas.
        draw_cube(ctx);                                // Nakresli krychli.
        points = points.multiply(rot);                 // Otoc body v poli points pomoci matice rotace rot.
      }
    };

Dále si rozebereme, co přesně dělá funkce draw_cube(), která vykreslí krychli podle souřadnic vrcholů v matici points. Funkce projde postupně všechny stěny krychle, rozhodne, které z nich jsou viditelné, a ty potom vykreslí pomocí funkce draw_face(). Kód funkce je následující:

    function draw_cube(ctx) {
        var i,j,d,e,s;                          // pomocne promenne
        var normal = Vector.create([0,0,0]);    // promenna pro normalovy vektor ke stene

        for(i=0;i<6;i++) {                      // pro kazdou stenu udelej:
          normal = points.row(normals[i][0]);
          normal = normal.subtract(points.row(normals[i][1]));  // zjisti normalovy vektor.

          v = points.row(faces[i][0]);          // Vezmi libovolny bod steny
          v = v.add(shift);                     // pricti posun od oka pozorovatele
          v = v.toUnitVector();                 // udelej z vektoru v a normal vektory delky 1
          normal = normal.toUnitVector();
          s = v.dot(normal);                    // spocti skalarni soucin normaloveho vektoru a vektoru od pozorovatele k bodu steny.
          if (s<0) {                            // pokud je zaporny, je stena videt.
            draw_face(ctx,i,s);                 // tak ji nakresli. Parametr s se jeste hodi na stinovani, proto je predan funkci draw_face.
          }
        }
    };
V těle funkce je cyklus probíhající přes všech šest stěn krychle, indexované proměnnou i. Na začátku cyklu je spočítá normálový vektor normal k i-té stěně pomocí pole normals. Dále vezmeme libovolný bod na i-té stěně a najdeme vektor v směřující od oka pozorovatele do tohoto bodu. Nyní pomocí vektorů v a normal zjistíme, jestli je i-tá stěna vidět či nikoli tímto způsobem. Uděláme z obou vektorů vektory délky $1$ a spočítáme skalární součin v$\cdot$normal, který je roven kosinu úhlu mezi těmito vektory. Pokud je onen úhel větší než $\pi/2$, je stěna vidět, a proto ji nakreslíme pomocí funkce draw_face(). V opačném případě neděláme nic a pokračujeme s další stěnou. Tento postup funguje, protože v každém okamžiku je každá stěna buď viditelná celá nebo není vidět vůbec (tj. nedochází k žádným částečným zákrytům).

Nakonec si vysvětlíme, co dělá funkce draw_face(). Tato funkce má tři parametry: ukazatel na kreslící prostor ctx, pořadové číslo stěny f a skalární součin s, který jsme spočítali ve funkci draw_cube(). Pomocí čísla stěny f a pole faces funkce draw_face() zjistí, které vrcholy stěna f obsahuje. Tyto vrcholy promítne do roviny pomocí perspektivní projekce funkcí perspective_projection(). Každá stěna má svojí barvu. My ji trochu upravíme pomocí skalárního součinu s tak, aby byla jasnější, pokud je k nám stěna více nakloněná a tmavší, pokud méně. Nakonec stěnu f vykreslíme, tj. vyplníme barvou a obtáhneme černou čárou. Konkrétní kód funkce draw_face() je následující:

    function draw_face(ctx,f,s) {
        var i;
        for(i=0;i<4;i++) {  // pro kazdy bod steny f spocitej perspektivni projekci
          vert[i] = perspective_projection(points.row(faces[f][i]));
        }

        s = 0.5*(1-s);  // preskalovani s, aby barvy nebyly tak tmave.
        ctx.fillStyle = "rgb(" + Math.floor(s*colors[f][0]) + "," + Math.floor(s*colors[f][1]) + "," + Math.floor(s*colors[f][2]) + ")"; 

        ctx.beginPath();
        ctx.moveTo(vert[0][0],vert[0][1]);
        ctx.lineTo(vert[1][0],vert[1][1]);
        ctx.lineTo(vert[2][0],vert[2][1]);
        ctx.lineTo(vert[3][0],vert[3][1]);
        ctx.closePath();
        ctx.fill();       // Vyplni ji barvou.
        ctx.stroke();     // Obkresli ji cernou carou (nefunguje v IE).
    };

Rostislav Horcik 2009-01-04