


      3DICA.TXT v2.0 (C) Ica/2 1996,1997
      ---------




Sisllys
--------


   0         Yleist lpin
     0.1     Kuka on Ica/2?
     0.2     Miksi ihmeess kirjoitin tmn dokumentin?
     0.3     Kenelle tm dokumentti on suunnattu?
     0.4     What's new?
     0.5     Gr33tZ

   1         Vektorit
     1.1     Yleist
     1.2     Vektorin yhtl
     1.3     Vektorin pituus
     1.4     Vektorien yhteenlasku
     1.5     Skalaaritulo
     1.6     Vektoritulo
     1.7     Skalaarikolmitulo
     1.8     Matriisitulo
     1.9     Vektorin ja matriisin tulo

   2         3D:n alkeet
     2.1     2D- ja 3D-maailman yhteys
     2.2     3D-pyritysten perusyhtlt
     2.3     Sinin ja kosinin taulukoiminen
     2.4     Pisteen pyrittminen kaikkien akseleiden ympri
             samanaikaisesti
     2.5     Idea enginen toteuttamiseen

   3         Erilaisia polygonifillej
     3.1     Flat-kolmio
     3.2     Gouraud-kolmio
     3.3     Texture-kolmio

   4         Sorttaustapoja
     4.1     Z-sorttaus
     4.2     Z-buffer
     4.3     BSP-puu
      4.3.1  The main idea
      4.3.2  Tarvittavat kaavat
      4.3.3  Vinkkej

   5         Varjostustapoja
     5.1     Flat-sheidaus
      5.1.1  Z-Flat
      5.1.2  Lambert Flat
     5.2     Gouraud-sheidaus
      5.2.1  Z-Gouraud
      5.2.2  "Oikea" Gouraud
      5.2.3  Phong Illuminating
     5.3     Env-mappaus

   6         Muuta mukavaa
     6.1     Hidden face removal
     6.2     Kamera and how did I (at first) do it




0 Yleist lpin
-----------------


 0.1 Kuka on Ica/2?

   Olen oikealta nimeltni Ilkka Pelkonen, 18-vuotias c-,
   pascal- ja assembler-ohjelmoija. Koodaamiseni on ollut lhinn
   hupihommaa (en ole julkaissut mitn), joten harva lukija
   varmaan Arkadian yhteislyseolaisia lukuunottamatta on minusta
   aiemmin kuullut.
     Opiskelen siis mainitussa koulussa ABi-asteella, ja syksyll
   olisi tarkoitus onnistuneiden yo-kirjoitusten jlkeen aloittaa
   uusi elm Otaniemen tietotekniikkaosastolla. Ett pitks
   sitten peukkuja :)
     Yhteyden minuun saa parhaiten e-mailaamalla osoitteeseen
   ilkka.pelkonen@mbnet.fi, eli kommentit tst tekstist (ja
   miksei jostain muustakin =) sinne. Etenkin muita samaan
   kouluun ja samalle osastolle ensi vuonna yrittji - tai
   vaikkapa jo siell olijoita - kehotetaan ottamaan yhteytt.

   Ai niin, kyll. Kytn OS/2 Warpia ja olen siihen erittin
   tyytyvinen :)


 0.2 Miksi ihmeess kirjoitin tmn dokumentin?

   Minua ovat jo jonkin aikaa hirinneet eptoivoisten
   ylastetta pttmss olevien tai lukion vasta
   aloittaneiden nuorten koodaajanalkujen ruinaukset kunnon
   opastuksesta 3d-koodauksen maailmaan. Puoli vuotta sitten
   kirjoitin mielestni sangen tyhjentvn selostuksen MBnetin
   PC-ohjelmointi-alueelle, mutta kvi ilmi, etteivt monet
   olleet lainkaan ymmrtneet mist tekstiss puhuttiin.
   Niinp ptin alkaa kaiken aivan alusta - selostaa perti
   vektoreista lhtien, miten kolmiulotteisen maailman koodaus
   PC:n (tai miksei jonkin muunkin koneen) ruudulle oikein
   tapahtuu.
     Tm taitaa olla oikea paikka morjenstaa Kaj Bjrklundia,
   joka on auttanut (ja itse asiassa vielkin auttaa :)
   3d-koodauksessani. Olen saanut hnelt apua mm. valonlhteen
   ksittelyyn, hidden face removaliin, gouraudiin ja phong
   illuminating -shadeen. Kiitn lmpimsti.


 0.3 Kenelle tm dokumentti on tarkoitettu?

   Tmn saa lukea kuka vain joka tuntee olevansa sen tarpeessa :)
   Kohderyhmksi suunnittelin alunperin itse n. 14-17-vuotiaat,
   joille on jo opetettu trigonometrian ja geometrian perusk-
   sitteet, mutta vektorit tai matriisit ovat tytt hepreaa.
   Ohjelmointikokemusta oletan olevan ainakin sen verran, ett
   lukija osaa kytt taulukoita.
   Nyt kun on psty jo versioon 2.0, kohderyhmkin on laajempi,
   mukana kun ovat sentn BSP-puu ja jremp interpolointia
   vaativat gouraud ja teksturointi.
     Lukemallahan se selvi ett onko tst tekstist jotain
   hyty :) Ja jos on niin paljon ett mietit miten voisit
   korvata vaivannkni, kyh opiskelija ei panisi ollenkaan
   pahakseen pient avustusta esim. kaksikymppisen muodossa (toki
   enemmnkin saa lhett, mutta vhintn tuon lhettneet
   saavat hyvn mielen ja greetit ensi versioon). HUOM! l pid
   tt ahneena rahanlypsmisen, vaan asetu minun asemaani: jos
   olisit itse viettnyt viisikymment tuntia kirjoittaen teksti
   josta ei itsellesi ole mitn hyty, etk ottaisi mielellsi
   vastaan pikku stipendi lukijoiden taholta?-)
   Ja tm on tosiaan siis vain ehdotus, ei missn nimess
   vaatimus. Jos raha tuskin riitt ruokaan, ymmrrt varmaan
   sst sen kaksikymppisen parempaan tarkoitukseen ;)

   Mutta osoite on joka tapauksessa

   Ilkka Pelkonen
   Kynnysmentie 10
   01860 Perttula,

   tai tilillekin voi maksaa PSP 800027-30577532, jolloin
   viitteeksi teksti "3DICA".

   Kiitn etukteen, jos joku katsoo asiakseen kannustaa minua
   jatkamaan tmn dokumentin kehittmist :)


 0.4 What's new?

   Uutta versiossa 1.1:
     Olen johtanut 3d:n konvertoimisen 2d:hen ja 3d-pyritykset
     alusta lhtien. Mukaan on tullut mys muutama uusi osio,
     jotka lytyvt sisllysluettelosta (ja luonnollisesti teks-
     tistkin :D
       Lisksi huomasin muutamia puutteita ja epselvyyksi, jotka
     olivat psseet livahtamaan mukaan. Nit ei en pitisi
     esiinty.
       Viel valitettiin kuvien epselvyydest ja siit, ett pseudo-
     3d-engine oli "optimoitu" eik helposti tajuttava "tydellinen"
     engine. Nitkin haittoja olen yrittnyt korjata.
     Ai niin, muutin mys nimen vhn persoonallisempaan suuntaan :)

   Uutta versiossa 2.0:
     - Polygoninpiirron selityst on hiukan parannettu, ja
       klippausrutiineista lytynyt bugi korjattu.
     - Kappalejakoa on muutettu jrkevmmksi dokumentin
       kasvamisen myt.
     - Vektorit on selostettu *vielkin* paremmin :)
     - Mukana on nyt mys gouraud, texturemappaus, pari fake-
       phongia, Z-buffer, BSP-puu...
     - Matriisilaskennassa tarvittavia laskutoimituksia on listty
       matriisitulolla ja vektorin ja matriisin tulolla.
     - Liitin thn versioon env-mappaukseen sopivan bittikartan
       mukaan pcx-muodossa.


 0.5 Gr33tZ

   iCA/2 gRe37z th3 f0ll0WiNG d00dz...

   Friction: Esit taas kanaa=) Ventolin qsee.
   Ducha: Keep up the good work, pal!
   Ripple: Ei kukaan VOI olla noin hyv :)
   Firestorm: Warp! Warp! Warp!
   Frac: *Joko* se toimii se 3d-starfield?-)
   Crane: Kiitn syksyisest texturemappaushelpist.
   MiG: "Nyt on Hiiri. Nyt on HIIRI! AAAAAAAA!!!"
   Oca: Sulkis rulaa, varsinkin kun min voitan :)
   RRRulettavaKonna: Olet ers oudoimmista tyypeist joiden kanssa
                     olen mlissyt... :I
   KajBjrklund: Kiitos avusta interpoloinnissa jne. Ja RC:n
                 ilmaisesta rekkauksesta! :)
   HezeRaunio: Prt.
   JukkaPManninen: Ei thn keksi mitn :)
   WesaWepslinen: Win95! Win95! Win95! ..on *sielt* :)
   JereSanisalo: Mua harmittaa *vielkin* se datathtijuttu :/
   PanuArtimo: Sun Warp-desktoppi on RUMA! :) (MBnet-versio)
   HenriKronlund: Miten menee? Ei ole messuiltu sitten TZ:n
                  kaatumisen!
   IlkkaWuorenMaa: Mits me gurut:)
   KimmoVihola: Eivt ne vektorit nyt *niin* vaikeita ole ;)
   TapioWuorinen: "Morjesta vaan miten morjestat..." :D
   JukkaLiimatta: Siinp guru... Messuillaan!
   HenriTuhkanen: ..ja toista peliin... Kerilisit postimerkkej,
                  polygonirutiinien kerily on jotenkin lamea ;)
   ZachMortensen: For his great OTMMATX.DOC.


-- asiaan... --




1. Vektorit
-----------


 1.1 Yleist
                                                   y ^      _ B
   Vektorit ovat janoja joilla on suunta,            |      /|
   ja joita voidaan siirrell koordinaa-             |    /
   tistossa haluttuihin paikkoihin kunhan            |  A\
   niit ei knnet.                                |
   Vektori piirretn merkitsemll kuvan     -------|-------->
   mukaisesti poikkiviiva sen alkuphn ja          |        x
   nuolen krki loppuphn.
     Vektorit on tapana ilmoittaa yksikkvektoreiden i, j ja k
   avulla. i on x-, j y- ja k z-akselin suuntainen vektori. Niden
   kaikkien pituus on yksi pituusyksikk, mist nimityskin.
     Mainittakoon tss vaiheessa, ett kaikki vektoriyhtlt ovat
   sovellettavissa sek 2d- ett 3d-koordinaatistoihin, ainoa ero on
   3d-koordinaatistossa ylimrinen termi zk.
     Vektoreiden kirjaintunnusten plle piirretn yleens viiva
   osoittamaan kyseess olevien vektorien, mutta DOS-tekstitilan
   ollessa kyseess katson oikeudekseni olla nin tekemtt :)
   (Mys i:n, j:n ja k:n pll on siis normaalisti viiva. i:n ja
   j:n pll olevia pisteit ei tllin tarvitse merkit.)


 1.2 Vektorin yhtl

   Nyt laskemme vektorin AB yhtln. Jos A-pisteen koordinaatit ovat
   A(Ax,Ay,Az) ja B-pisteen vastaavasti B(Bx,By,Bz), miss x,y ja z
   ovat alaindeksej, vektorin AB yhtl on
     __          _          _          _
     AB = (Bx-Ax)i + (By-Ay)j + (Bz-Az)k
           _    _    _
        = Xi + Yj + Zk.

   (yhtl esitetn siis normaalisti alemmassa, sievennetyss
   muodossa)
   Tm tarkoittaa siis, ett vektorin suunta saadaan kun mennn
   ensin jostain mielivaltaisesta pisteest X yksikk x-akselin
   suuntaan, sitten Y yksikk y-akselin suuntaan ja lopuksi Z
   yksikk z-akselin suuntaan. Kun thn saatuun pisteeseen
   piirretn viiva pisteest josta aloitettiin (ja viivan loppu-
   phn se nuoli osoittamaan vektorin suuntaa), saadaan vektori
   jonka yhtl on Xi+Yj+Zk.
   HUOM! LOPPUPISTEEN koordinaatista VHENNETN aina ALKUPISTEEN
   koordinaatti!


 1.3 Vektorin pituus

   Vektorin AB pituus merkitn sen itseisarvona ja lasketaan
   ynnmll sen termien nelit yhteen ja ottamalla summasta
   nelijuuri (sqrt, SQuare RooT; ruotsia. ;)
      __
     |AB| = sqrt( X^2 + Y^2 + Z^2 ).


 1.4 Vektorien yhteenlasku

   Vektorien yhteenlasku tapahtuu laskemalla termit yhteen.
   Vhennyslasku vastaavasti, mutta miinusmerkkinen vektori
   osoittaa vastakkaiseen suuntaan kuin plusmerkkinen.
                 _     _     _     _            _     _     _
   Esim. Olkoon  a = Axi + Ayj + Azk  ja  b = Bxi + Byj + Bzk.
                  _   _          _          _          _
         Tllin  a + b = (Ax+Bx)i + (Ay+By)j + (Az+Bz)k
                  _   _          _          _          _
              ja  a - b = (Ax-Bx)i + (Ay-By)j + (Az-Bz)k.
                               _
                          __-->/|
                   a+b__--   /
                  __--     / b
                |----a---->
                 \      /
               a-b\   / -b
                   >/_

                  _    _    _      _   _    _           _   _
  TEHTV: Olkoon a = 2i - 6j  ja  b = i + 3j. Laske | 2a - b |.
  (ratk. sqrt(234))


 1.5 Skalaaritulo

   Vektorien tulosta kytetn nimityst skalaari- eli pistetulo.
   Pistetulo luetaan "a piste b" ja se lasketaan kertomalla a:n
   pituus b:n pituudella ja viel vektorien vlisen kulman
   kosinilla. Sen voi mys laskea kertomalla molempien vektorien
   i:n, j:n ja k:n kertoimet yhteen ja lismll ne toisiinsa.
   HUOM! Vektorien vlinen kulma tarkoittaa aina pienemp kulmaa
   joka j vektorien vliin niiden alkaessa samasta pisteest.
     _ _    _  _     _ _                 _
     ab = |a||b|cos(a,b)                /|
         = Ax*Bx + Ay*By + Az*Bz.    b /\
                                     /   |
                                    ---------->
                                         a

   HUOM! Vektorit ovat toisiaan vastaan kohtisuorassa, kun niiden
   vlinen kulma on 90 astetta eli cos(a,b)=0 eli ab=0!
                              _ _      _ _
  TEHTV: Mrit vektorien  a+b  ja  a-b  vlinen kulma, kun
  a = 3i + j  ja  b = i - 7j.
  (ratk. 132.3 astetta)


 1.6 Vektoritulo

   Kolmiulotteisessa avaruudessa mritelln kahden vektorin a ja
   b vektori- eli ristitulo  a x b  (luetaan "a risti b") vektorina,
   jonka                                   _
    - pituus |a x b| ilmoittaa vektorien  |\ axb      _
      a ja b mrmn suunnikkaan alan:    \         /|
      |a x b| = |a||b|sin(a,b) (suunnikkaan  \    a /   \ b
      alan kaava, kytetn mys geomet-      \/\ /      >
      riassa)                                  \/ |axb|  /|
    - suunta mrytyy siten, ett  a x b       \      /
      on kohtisuorassa vektoreita a ja b        b\   / a
      vastaan.                                    >/

   HUOM! Kuviossa siis mys b ja axb ovat suorassa kulmassa
   toisiaan vastaan, vaikkei silt nyt :)

   Muuta muistettavaa ristitulosta:
    - a x a = 0,
    - a x b = -(b x a) eli vaihdantalaki ei ole voimassa suoraan,
    - kun r on vakio, (ra) x b = r(a x b) eli vakion siirrntlaki
      on voimassa,
    - a x (b + c) = a x b + a x c  eli osittelulaki on voimassa,
    - i x j = k (ja vastaavasti k x i = j  ja  j x k = i).

   Ristitulon lauseke voidaan kirjoittaa helposti muistettavaan
   muotoon ottamalla avuksi ksite determinantti (matriiseja, jee!-)
   Kaksirivinen determinantti mritelln neljst luvusta
   muodostettuna kaaviona, jonka arvo lasketaan seuraavasti:

     | a b |
     | c d | = ad - cb (kyseesshn on RISTItulo :)

   Kolmirivinen determinantti taas, jota useimmiten kytetn,
   tarkoittaa vektorien a=Axi+Ayj+Azk ja b=Bxi+Byj+Bzk ristituloa:
                _  _  _
     _   _    | i  j  k  |
     a x b =  | Ax Ay Az |
              | Bx By Bz |

             | Ay Az | _   | Az Ax | _   | Ax Ay | _
           = | By Bz | i + | Bz Bx | j + | Bx By | k
                          _                _                _
           = (Ay*Bz-By*Az)i + (Az*Bx-Bz*Ax)j + (Ax*By-Bx*Ay)k

   Eli siirrytn yksi kerrallaan ylimmll vaakarivill i:n, j:n
   ja k:n kohdalle, ja peitetn jokaisen kohdalla vuorollaan
   kohdalla oleva pystyrivi (siis niin, ett kyseess oleva
   kirjain j peittoon). Lopuista alemmista riveist muodostetaan
   kaksirivinen determinantti, lasketaan sen normaalisti ja
   lopuksi kerrotaan ylimmn rivin kirjaimella. Piis of keik,
   isnt it, mn?-)

   Useampirivisikin determinantteja on nkynyt, ja ne ratkaistaan
   vastaavasti. Siis esimerkiksi nelirivisest determinantista
   muodostetaan kolmirivisi jne.

  TEHTV: Mrit vektorien  i - j + 2k  ja  2i + 3j - k
  mrmn suunnikkaan ala.
  (ratk. sqrt(75))


 1.7 Skalaarikolmitulo

   Skalaarikolmitulo merkitn  (a x b)c  ja sen itseisarvo
   ilmoittaa vektorien a, b ja c mrmn suuntaissrmin
   tilavuuden (ks. ristitulossa suunnikas). HUOM! Jos tm
   tilavuus on nolla, vektorit a, b ja c ovat tietysti samassa
   tasossa!
     Skalaarikolmitulo on helppo laskea kolmirivisell
   determinantilla, jolloin ristitulon tapauksessa kytetty
   determinanttia muutetaan vain ylimmn rivin osalta,

      _   _  _    | Cx Cy Cz |
     (a x b)c =  | Ax Ay Az |
                  | Bx By Bz |

   ja lasketaan muuten tsmlleen samalla tavalla.

   Esim. Vektorien  i + j - k,  2i - 4j + k  ja  -4i + 3k
   skalaarikolmitulo:

     |  1  1 -1 |     |-4 1|     | 1  2|      | 2 -4|
     |  2 -4  1 | = 1*| 0 3| + 1*| 3 -4| + -1*|-4  0|
     | -4  0  3 |
                  = (-4*3 - 0*1) + (1*(-4) - 3*2) - (2*0 - (-4)*(-4))

                  = -12 + (-10) + 16 = -6.

   TEHTV: Mrit vakio a siten, ett vektorit  i - j + 2k,
   2i + 3j - k  ja  ai - 4j + k ovat saman tason suuntaisia.
   (ratk. a=-3)


 1.8 Matriisitulo

   Ovat ne sitten hianoja, nuo determinantit eli matriisit.
   Pelkll ristitulolla vain ei paljon tee, esimerkiksi
   matriisien avulla tapahtuvassa 3d-pyrittelyss tarvitaan
   matriisien kertolaskua, josta tss selostus:

   | a b c |   | k l m |   | ak+bn+cq al+bo+cr am+bp+cs |
   | d e f | * | n o p | = | dk+en+fq dl+eo+fr dm+ep+fs |
   | h i j |   | q r s |   | hk+in+jq hl+io+jr hm+ip+js |

   Matriisituloa voidaan selvitt tulkitsemalla se ensimmisen
   matriisin vaaka- ja toisen matriisin pystyrivien muodostamien
   "vektorien" pistetuloiksi (ks. 1.5): ensin ensimmisen
   matriisin ylin rivi ja toisen matriisin ensimminen pystyrivi,
   sitten ensimmisen matriisin ylin rivi ja toisen keskimminen
   pystyrivi, sitten 1. matriisin ylin rivi ja toisen viimeinen
   pystyrivi, sitten 1. matriisin keskimminen rivi ja toisen
   ensimminen, ... Ja tulokset uuteen matriisiin 1. matriisin
   termien paikkojen mukaisesti, esimerkin mukaisesti.


 1.9 Vektorin ja matriisin tulo

   Eip tss muuta kuin esimerkki kehiin (4x4-matriisi koska
   niit kytetn 3d-laskutoimituksissa):

                | a b c 0 |
   (Xi+Yj+Zk) * | e f g 0 | = (aX+bY+cZ+m)i + (eX+fY+gZ+n)j +
                | i j k 0 |   (iX+jY+kZ+o)k
                | m n o 1 |

   Ensi versioon tulevat sitten mys matriisipyritykset ja
   tmnkin tarkempi selostus, tmnkertainen on vain vhn
   esimakua ;)




2. 3D:n alkeet
--------------


 2.1 2D- ja 3D-maailman yhteys

   Avaruudessa sijaitsevan pisteen koordinaatit ilmoitetaan kolmen
   koordinaatin - x:n, y:n ja z:n - avulla. Kaksiulotteiselle
   monitorin pinnalle tllaista pistett ei kuitenkaan voida
   suoraan piirt, joten tarvitaan muunnoskaava, jolla
   z-koordinaatti voidaan sisllytt x:n ja y:hyn.
     Mietitnp asiaa. Pisteen voi kertoa vakiolla, jolloin se
   pysyy origon kautta kulkevalla suoralla:

      (X, Y, Z) => k(X, Y, Z) => (kX, kY, kZ)

   Jos mritmme kamerapisteeksi origon, voimme kytt tt
   kaavaa ja ottaa k:ksi 1/z:n jolloin z-koordinaatista tulee
   vakio:

     1/Z * (X, Y, Z) = (X/Z, Y/Z, 1).

   Tllaiset pisteet ovat tasolla Z=1. Jos haluamme avaruutemme
   lhemms / kauemmas oletusarvoisesti, voimme muuttaa kaavaa
   hiukan:

     a/Z * (X, Y, Z) = (X*a/Z, Y*a/Z, a)

   Nyt tason, jolla kaikki pisteet ovat, yhtl on Z=a.

   Kaava on siis seuraavanlainen:

      X = X * PERSPECTIVE / Z
      Y = Y * PERSPECTIVE / Z

   PERSPECTIVE kertoo, kuinka lhelt 3d-maailmaa tarkastellaan
   (mit pienempi arvo, sit lhempn ollaan). Kokeilemalla
   selvi kuhunkin tilanteeseen sopiva arvo, usein kytetn
   2:n potensseja 8 ja 9; niill on nopea laskea, ja ne ovat
   sopivalla etisyydell. Tllin z-akselin 0-kohta eli kohta,
   jossa z-koordinaatin arvo ei vaikuta x:n ja y:hyn, on
   tietysti sama eli 256 tai 512, ja muuteltaessa z:n arvoa
   reaaliaikaisesti huomataan 3d-efekti.
     HUOM! Jos z:n arvoksi tulee kesken ohjelman ajon nolla, ei
   tapahdu mitn kivaa. Siisp vlttkmme tllaista sattu-
   essaan niin kovin valitettavaa tapahtumaa mahdollisuuksiemme
   mukaan!

   Kolmiulotteisuuden havainnollistamisen esimerkki pseudokoodina
   (piirt pisteen ruudun keskiosaan. Z:n arvon muuttuessa se
   hvi ruudusta):

     x = 1
     y = 1
     z = 256
     while (gx<320) and (gx>-1) and (gy<200) and (gy>-1)
       gx = x * 256 / z + 160
       gy = y * 256 / z + 100
       putpixel(gx,gy,15)
       z = z - 1
       if (z = 0)  ; varmistetaan ettei VAIN... :)
         z = -1
       endif
     endwhile

   "Viritetty" versio onkin sitten jo 3d-starfield, demoscenen
   perusefekti puolenkymment vuotta sitten. Suositeltava
   harjoitustehtv muuten!

 
 2.2 3D-pyritysten perusyhtlt

   Huom. en itse kyt nit pyrityksi en, kun sain ksiini
   matriisipyritysten selostuksen. Vakavampaan 3d-koodaamiseen
   matriisit ovat ainoa oikea tapa, mutta aluksi nm
   peruspyritykset kelpaavat ihan hyvin.

   Ensin pitnee selvitt trigonometristen funktioiden periaate.
   Jos piirrmme ympyrn, jonka keskipiste on xy-tason origossa
   ja jonka sde on 1, ympyrn jokaisen pisteen koordinaatit
   saadaan seuraavasti t:n saadessa kaikki reaaliarvot (lukion
   matikkaa ihan ensimmisilt kursseilta):

                                             ^
     x = cos(t)                          ____|____
     y = sin(t)                        /     |     \A(x,y)
                                     /       |    /  \
   Janan OA pituus on siis 1 ja     /        |  /     \
   t on kulma jonka jana OA         |        |/  \t   |
   muodostaa x-akselin kanssa.    --|--------O---|----|---->
                                    |        |        |
   Kun aloitamme uuden kierroksen,  \        |        /
   sini ja kosini pyrhtvt mys    \      |       /
   ympri ja rumba jatkuu.              \____|____ /
                                             |
   Jos kerromme x:n ja y:n jollain
   reaaliluvulla, voimme pienent tai suurentaa ympyrn kokoa
   tarpeen mukaan.

   -- ok --

   Haluamme pyritt pistett (X1,Y1)            ^ y
   kulman k verran vastapivn eli               |
   positiiviseen kiertosuuntaan. Tss       x(X2,Y2)      _x(X1,Y1)
   tapauksessahan pyrittminen tapahtuu       \  |    __--
   z-akselin ympri, koska z-koordinaatti        \|__--\ a
   pysyy koko ajan samana eli sit ei         ----------|----> x
   huomioida.                                     |
     HUOM! Kytmme vasemman kden pyrityssuuntaa, eli kun
   vasemman kden peukalo osoittaa akselin, jonka ympri py-
   ritetn, suuntaan, sormet taipuvat positiiviseen kierto-
   suuntaan (z-akseli tyntyy siis tss koordinaatistossa kohti-
   suoraan ruudun sisn, y-akseli alas ja x-akseli oikealle).

   Kulma, jonka jana origosta pisteeseen (X1,Y1) muodostaa
   x-akselin kanssa, olkoon a. Tllin kulma, jonka jana ori-
   gosta pisteeseen (X2,Y2) muodostaa saman akselin kanssa on
   k+a.
   Uudet koordinaatit ovat siis

     X2 = r * cos(k+a)
     Y2 = r * sin(k+a),

   miss r on pyrityssde. Kaavojen (lukion taulukoista)

     cos(a+b) = cos(a) * cos(b) - sin(a) * sin(b)
     sin(a+b) = sin(a) * cos(b) + cos(a) * sin(b)

   avulla muutamme uusien koordinaattien yhtln muotoon

     X2 = r * cos(k) * cos(a) - r * sin(k) * sin(a)
     Y2 = r * sin(k) * cos(a) + r * cos(k) * sin(a)

   Toisaalta seuraavan kuvan perusteella voimme ptell lis:

            ^
            |      _(X1,Y1)
            |      /|
            |  r /  |
            |  /    |Y1
            |/ a    |
   ---------|--------------->
            |   X1

   Kyseesshn on suorakulmainen kolmio, jonka kateetit
   muodostuvat pisteen x- ja y-koordinaateista ja hypotenuusa
   pyrityssteest. Koska (tmn verran varmaan kaikki osaa-
   vat?-)

     cos(a) = X1 / r
     sin(a) = Y1 / r,

   voimme ilmoittaa r*cos(a):n ja r*sin(a):n x:n ja y:n avulla:

     r * cos(a) = X1
     r * sin(a) = Y1.

   Niinp kaavoistamme supistuvat hankalat pyrityssde ja
   alkupisteen ja origon muodostaman janan sek x-akselin vli-
   sen kulman lausekkeet pois:

     X2 = X1 * cos(k) - Y1 * sin(k)
     Y2 = X1 * sin(k) + Y1 * cos(k).

   Omiin silmiinshn sit ihminen parhaiten uskoo, joten kokeilu
   laskimella ja ruutupaperilla voi olla paikallaan. Piirr siis
   koordinaatisto, ja siihen piste (5,5). Pyryt tt pistett
   45 astetta vastapivn (piirr geokolmion avulla uusi piste,
   etisyys origoon silyy tietysti samana). Laske sitten
   yllolevien kaavojen avulla sama, ja huomaat, ett mittaus-
   tarkkuuden rajoissa tulokset ovat yhtpitvt (0,7).

   Nyt lismme mukaan z-koordinaatin, ja voimme esitell kaikki
   kaavat siistiss paketissa.

     Z-akselin ympri:
       X2 = X1 * cos(k) - Y1 * sin(k)
       Y2 = X1 * sin(k) + Y1 * cos(k)
       Z2 = Z1,

     Y-akselin ympri:
       X2 = X1 * cos(j) - Z1 * sin(j)
       Y2 = Y1
       Z2 = X1 * sin(j) + Z1 * cos(j),

     X-akselin ympri:
       X2 = X1
       Y2 = Y1 * cos(i) - Z1 * sin(i)
       Z2 = Y1 * sin(i) + Z1 * cos(i).

   Kokeillaan jlleen (ympyr ruudulle):

     X1 = 30
     Y1 = 0
     Z1 = 256
     for (i=0 --> 359)
       X2 = X1 * cos(i) - Y1 * sin(i)
       Y2 = X1 * sin(i) + Y1 * cos(i)
       Z2 = Z1
       X2 = X2 * 256 / Z + 160
       Y2 = Y1 * 256 / Z + 100
       putpixel(X2,Y2,15)
     endfor

    Ympyrn saa toki ruudulle paljon helpomminkin, mutta
    niuhottajille tiedoksi, ett nyt oli tarkoitus havainnollistaa
    nit kaavoja :)


 2.3 Sinien ja kosinien taulukoiminen

   Trigonometristen funktioiden kytt reaaliajassa on niin
   hidasta, ett kun ne taulukoidaan, koodin nopeus monin-
   kertaistuu.
     Valaisen taulukoimisen ideaa esimerkin avulla:

     (sinus ja cosinus ovat 256-alkioisia signed 16bit-taulukoita)
     for a=0 to 255
       sinus[a]   = SCALE * sin( a * pi / 128 )
       cosinus[a] = SCALE * cos( a * pi / 128 )
     endfor

   Sinin ja kosinin arvothan liikkuvat vlill -1..1. Koska
   kokonaislukumuuttujia on nopeampi kytt, joudumme turvautumaan
   fixedpointiin eli kertomaan desimaaliluvun tietyll kertoimella,
   jolla pit sitten kalkulointien jlkeen mys jakaa. Tss
   tapauksessa kerroin on vakio SCALE, joka siis vaikuttaa vain
   mittaustarkkuuteen. Muista jakaa sill sitten jokaisessa
   pyrityslausekkeessa tai tulee *huikeita* lukuja!
   Sopiva arvo SCALElle on esim. 256 (optimoidessa on
   helppo ja nopeaa jakaa 2^8:lla eli shiftata luvun bittej
   oikealle 8:lla (Shift Arithmetic Right)).
                         ^^^^^^^^^^
                 EI shr vaan sar, koska merkki-
                 bitti pit ottaa huomioon!

   Ohjelmointikielet vaativat trigonometrisille funktioille
   parametreina radiaaniarvoja, joten yllolevassa esimerkiss on
   konvertoitu asteet radiaaneiksi.
     Asteiden ja radiaanien yhteyshn on se, ett pii on puoli-
   ympyrn asteet eli 180 astetta. Niinp asteiden konvertoiminen
   radiaaneksi tapahtuu normaalisti nin:

     kulma_rad = kulma_asteina * pi / 180.

   Yllolevassa esimerkiss on kuitenkin jaettu 128:lla. Miksik?
   No siksi, ett nin saadaan npprsti mritelty, ett 128*2
   astetta on tysympyr. Sehn on 256 eli byte/unsigned char/jne!
   Nyt kun siis byte-tyyppist muuttujaa kasvatetaan astemuut-
   tujana, pyrhtvt trigonometriataulukot ympri juuri oikeas-
   sa kohdassa eli tysiympyrn kohdalla -> ei tule hyppy, vaan
   pyritys jatkuu tasaisena aloittaen uuden kierroksen!

   Ylloleva pit sitten luultavasti lukea moneen kertaan :)


 2.4 Pisteen pyrittminen kaikkien akseleiden ympri
     samanaikaisesti

   Kaikkien akseleiden ympri pyrittminen ei tapahdukaan yht
   helposti kuin yhden akselin ympri. Itse kytin ennen
   matriiseihin siirtymist seuraavaa tekniikkaa (omaksuttu Bas
   van Gaalenilta, tnx vaan sinnekin pin :)

     1) x-koordinaatti y-akselin suhteen
     2) y-     "       x-   "       "
     3) z-     "       y-   "       "
     4) z-     "       x-   "       "
     5) y-     "       z-   "       "
     6) x-     "       z-   "       "

   Nyt sitten mritelln kolme byte-tyyppist muuttujaa jotka
   kasvavat tai vhenevt tasaisesti, ja kytetn niiden arvoja
   kulmina. Esimerkiksi i,j ja k ovat kivoja, kun niit kytetn
   vektoreissakin.
     Sitten pyritykset, konvertoinnit ja pikseli ruudulle! Fiilis
   on hauska, kun ensimmisen kerran liikkuu pikseli 3d-avaruudessa
   jonkinlaista ympyrrataa ;)

   Tss on pseudokoodinptk, jolla voidaan pyritt pistett
   3d-avaruudessa yhden, kahden tai kolmen akselin ympri
   samanaikaisesti. Kun konvertoit tmn kyttmllesi ohjelmointi-
   kielelle, on sinulla kytsssi 3d-rutiini, jolla saat aikaan
   *melkein* mit vain. Kytt tapahtuu muuttamalla haluttujen
   kulmien arvoja (i=x-akselin ympri, j=y, k=z), ajamalla tm
   rutiini ja piirtmll piste ruutuun. 10 minuutin sessio
   ruudulla liikkuvan pikselin ihailua on muuten ihan normaalia :D

     - a, b, c ovat apumuuttujia,
     - xp, yp, zp ovat pisteen alkukoordinaatit,
     - orig_x, orig_y ovat ruudun keskipisteen koordinaatit.

      a = (  cosinus[j] * xp - sinus[j] * zp ) / 256
      b = ( -cosinus[k] * yp - sinus[k] * a  ) / 256
      c = (  cosinus[j] * zp + sinus[j] * xp ) / 256
      x = (  cosinus[k] * a  - sinus[k] * yp ) / 256
      y = ( -cosinus[i] * b  + sinus[i] * c  ) / 256
      z = (  cosinus[i] * c  + sinus[i] * b  ) / 256 + PERSPECTIVE
      if (z=0)
        z=1
      endif
      x = x * PERSPECTIVE / z + orig_x
      y = y * PERSPECTIVE / z + orig_y

   Jos muuten ihmetytt, ett mill perusteella a, b ja c
   esiintyvt juuri noissa kohdissa kaavoja, voit kokeilla: pyrit
   vain yhden akselin ympri kerrallaan muiden kulmien ollessa
   nollia. Tllin a:n, b:n ja c:n lausekkeesta supistuvat aina
   sinikertoimiset koordinaatit pois (sin(0) = 0), ja jljelle
   j vain haluttu koordinaatti.
     Plus- ja miinusmerkkej on mys eri kohdissa kuin
   yksittisiss kaavoissa. Tm johtuu siit, ett kun lasketaan
   jokaisen akselin ympri pyritykset joka kerta vaikkei aina
   vlttmtt pyritettisikn kuin vaikkapa yhden akselin
   ympri, merkkeihin tulee muutoksia laskutoimitusten
   tiimellyksess.

   Esim. Pyritetn pistett 90 astetta (eli 256-asteisessa
   ympyrss 128/2 = 64 astetta) z-akselin ympri. Mit koodi
   tllin tekee? Rivi kerrallaan:
     a = (  256 *   xp  - 0   *   zp  ) / 256 = xp
     b = ( -0   *   yp  - 256 *   xp  ) / 256 = -xp
     c = (  256 *   zp  + 0   *   xp  ) / 256 = zp
     x = (  0   *   xp  - 256 *   yp  ) / 256 = -yp
     y = ( -256 * (-xp) + 0   *   zp  ) / 256 = xp
     z = (  256 *   zp  + 0   * (-xp) ) / 256 = zp.
   Nin ollaan siis saatu aivan oikea tulos, ett x-koordinaatista
   tulee y-koordinaatin vastaluku ja y-koordinaatista
   x-koordinaatti z-koordinaatin silyess ennallaan.


 2.5 Idea enginen toteuttamiseen

   Polygonit ja pisteet olisi tietysti mukava saada jonkinlaiseen
   taulukkoon, josta ne olisi helppo poimia. Aika fiksu tapa on
   tehd pisteille taulukko, ja asettaa polygonitaulukon arvot
   osoittamaan tmn pistetaulukon indeksej:

     signed integer vertex[3,3] = ( (0,0,0), (10,0,0), (0,10,0) )
     unsigned integer face[1,3] = ( (0,1,2) )

   Nyt voidaan osoittaa vertex- eli pistetaulukkoon npsksti

     vertex[face[0,0],0] etc.

   Polygoneille voi samaan taulukkoon mritt mys vaikkapa
   vriarvon.

   HUOM! Ei kannata kytt pyritetyille ja alkuperisille
   pisteille samaa taulukkoa, koska tllin tulee miltei vlitt-
   msti sekaannuksia. Siisp alkuperiset ja pyritetyt pisteet
   omiin taulukoihinsa!




3. Erilaisia polygonifillej
----------------------------


 Piste pyrii nyt siis ruudulla, mutta alkaa pidemmn plle
 nytt aika tylslt, joten jotain jremp olisi kiva saada
 aikaan. Tss astuvat kuvaan polygonit.
   Ksittelen vain kolmioita, koska niiden piirtminen on
 nopeinta ja vaivattominta, ja niist voi koostaa vaikka kuinka-
 monikulmaisen polygonin jos tarvetta esiintyy.


 3.1 Flat-kolmio

   Mielestni flat-kolmion piirtminen on niin yksinkertainen
   operaatio, ett idean ksitt lukemalla vain pseudokoodin ja
   ymmrtmll interpolaation idean, joten selitykseni ksitt
   tsmlleen nm kaksi: pseudokoodin ja interpolaation selityk-
   sen. Jos et kuitenkaan saa idean pst kiinni, kysy e-mailissa
   lis, niin seuraavassa versiossa on klikkaavatkin kohdat
   selvitetty.

   Pseudona:

   ** begin triangle **

     - koordinaatit ovat (x1,y1), (x2,y2), (x3,y3),
     - x, y ovat kolmen alkion taulukoita samaa tyyppi kuin
       koordinaatit,
     - a on apumuuttuja,
     - delta_x, delta_y ovat kolmen alkion taulukoita samaa tyyppi
       kuin koordinaatit,
     - d on kolmen alkion taulukko reaalilukutyyppi.

     < jrjestetn x- ja y-taulukkoon pisteet siten, ett
       (x[0],y[0]):ssa on piste, jonka y-koordinaatti on pienin,
       (x[1],y[1]):ssa toiseksi pienin ja viimeisess suurin >

     delta_x[0] = x[1]-x[0]
     delta_y[0] = y[1]-y[0]
     delta_x[1] = x[2]-x[1]
     delta_y[1] = y[2]-y[1]
     delta_x[2] = x[0]-x[2]
     delta_y[2] = y[0]-y[2]

     for (a=0 -> 2)
       jos (delta_y[a] ei ole nolla)
         d[a] = delta_x[a] / delta_y[a]
       muussa tapauksessa
         d[a] = 0
       endjos
     endfor

     for (a=y[0] -> y[1])
       horizline( x[0] + (a-y[0]) * d[0], x[0] + (a-y[0]) * d[2], a, color )
     endfor

     for (a=y[1] -> y[2])
       horizline( x[1] + (a-y[1]) * d[1], x[0] + (a-y[0]) * d[2], a, color )
     endfor

   ** end triangle **

   ** begin horizline **

     - a on apumuuttuja

     for (a=x1 -> x2)
       putpixel(a, y, color)
     endfor

   ** end horizline **
 

   ..n ti iksplneissn:

   Kohta joka luultavasti ihmetytt on koodin ensimminen looppi,
   jossa mritelln d:n arvot. Mik ihmeen dee? Katsotaanpas...
     Horizline-loopeissa d:t nytn kyttvn x-koordinaattien
   mrittmiseen. Hmm... Kun ollaan viimeist kertaa ensimmi-
   sess silmukassa, eli a = y[1], kutsuttaessa horizline ensim-
   minen x-koordinaatti nkyy saavan arvon

       x[0] + (y[1]-y[0]) * d[0])
     = x[0] + (y[1]-y[0]) * (x[1]-x[0]) / (y[1]-y[0]).

   Lausekkeesta supistuvat (y[1]-y[0]:t pois, ja jljelle j

     x[0] + x[1] - x[0] = x[1],

   eli kun y-koordinaatti on y[1], x-koordinaatti on vastaavasti
   x[1], mik lienee melko jrkev. Ahaa! d on siis jonkinlainen
   kerroin, jonka avulla x- ja y-koordinaatti suhteutetaan
   toisiinsa! Very, VERY smart, I'd say!
     Tm on nyt sitten sit interpolointia: x- ja y-koordinaatit
   suhteutetaan toisiinsa siten, ett kun y:t listn yhdell,
   x: listn sellaisella luvulla, ett y:n saavuttaessa loppu-
   arvonsa mys x saavuttaa omansa. Interpolointia tarvitaan
   monissa efekteiss, kuten tulemme huomaamaan.

   Ylloleva pseudokoodi ei ota huomioon sit, ett polygoni
   saattaa olla osittain tai kokonaan reunojen ulkopuolella.
   Horizline ei myskn osaa aavistaakaan, ett sille saattaa
   tulla ensimmisen x-arvona suurempi kuin jlkimminen, jolloin
   loopista tulee *hyvin* pitk.
     Tarttis varmaan tehr jotain, ja se jotain olisi sitten niin
   kuin klippaukset ja parit xchg:t. Klippaukset on helppo tyrkt
   horizline-rutiiniin:

   ** begin horizline **

     - a on apumuuttuja
     - max_x on ruudun leveys

     jos y>max_y tai y<0
       ei_sitten_piirretkn
     endjos
     jos x1 suurempi kuin x2
       eXCHanGe(x1,x2)
     endjos
     jos x1 pienempi kuin nolla
       x1 = 0
     jos taas x1 suurempi kuin max_x
       ulostaudu_rutiinista
     endjos
     jos x2 pienempi kuin nolla
       ulostaudu_rutiinista
     muussa tapauksessa jos x2 suurempi kuin max_x
       x2 = max_x
     endjos
     for (a=x1 -> x2)
       putpixel(a, y, color)
     endfor

   ** end horizline **

   Wauziwau. Siin siis kaikessa yksinkertaisuudessaan
   kolmionpiirtmisen idea, mutta optimoitavaa tuostakin toki
   lytyy. Hauskoja hetki vain optimisaation parissa, sill
   tmhn on se kaikkein eniten aikaa viev osa koko
   3d-enginess.
     Yhden optimointijekun voisin oikeastaan kertoa heti:
   reaaliluvut eivt vlttmtt ole kaikkein nopein vaihtoehto,
   kaikkihan eivt varmaan koodaa penalla?-)
   Niinp joskus kannattaa kytt fixedpointtia:
     d-taulukon alkiot ovat 32-bittist signed-tyyppi. Tllin
   jakolasku d=dx/dy muutetaan muotoon d=(dx*65536)/dy, eli d:n
   alimmat 16 bitti sisltvt desimaalit ja ylimmt 16 bitti
   kokonaiset. Lopuksi pit tietysti mys jakaa tuolla 2^16:lla,
   se tapahtuu outer- eli y-loopissa ennen piirtmist ruudulle.


 3.2 Gouraud-kolmio

   Gouraud-kolmion ja flat-kolmion piirtmisen idea on pitklti
   sama. Gouraud-rutiinille ilmoitetaan vain kolme arvoa enemmn
   (jokaisen kulman vriarvo), ja rutiini interpoloi sitten niiden
   vlill piirten kauniin varjostetun kolmion. Flat-kolmio
   kytti yksinkertaista interpolaatiota, gouraudiin tarvitaan
   kolminkertainen (interpoloidaan x:n ja y:n, vriarvon ja y:n
   sek vriarvon ja x:n vlill).
     Piirrettess gouraud-kolmio flat-kolmioon listn vain
   kaksi uutta osiota. Horizline-rutiini muuttuu monimutkaisem-
   maksi vrin ja x:n vlisen interpolaation myt, mutta itse
   prutiini silyy suurin piirtein samanlaisena.
     Gouraud-kolmiosta en esit tsmllist pseudoversiota, vaan
   jokainen saa itse kehitell sen vinkkien pohjalta. Nytn
   kuitenkin trkeimmt kohdat.
     Ensimminen outer loop uusitusta prutiinista:

     - c[0] on (x[0],y[0]):n vriarvo
     - dc:t lasketaan d:n tapaan mutta interpoloimalla c:n ja
       y:n eik x:n ja y:n vlill

     for (a=y[0] -> y[1])
       gouraud_horizline( x[0] + (a-y[0]) * d[0],  x[0] + (a-y[0]) * d[2],
                          c[0] + (a-y[0]) * dc[0], c[0] + (a-y[0]) * dc[2],
                          a )
     endfor

   Gouraud-horizline fixedpointilla ja ilman klippauksia:

     - dc on 32-bittist kokonaislukutyyppi

     < vertailu: onko x1 suurempi kuin x2? Jos on, vaihda sek x1
       ja x2 ett c1 ja c2 >
     dc = ((c2-c1)*65536)/(x2-x1)
     for (a=x1 -> x2)
       putpixel(a,y,c1+((a-x1)*dc)/65536)

   Sulkuja tarvitaan noin paljon jotta laskutoimitukset tulisivat
   varmasti oikeassa jrjestyksess (jotkut kntjt aloittavat
   lausekkeen purkamisen oikealta).


 3.3 Texture-kolmio

   Ja jlleen interpolointi-ideaa kehitelln: nyt on vuorossa
   texturemappaus. Ja jlleen kerran idea on sama, vain kaksi
   interpoloitavaa lis eli yhteens viisinkertainen inter-
   polaatio. Texturemappauksessa interpoloidaan x:n ja y:n,
   u:n ja y:n, v:n ja y:n, u:n ja x:n sek v:n ja x:n vlill
   (u ja v ovat pisteit bittikartta-avaruudessa).
     Tm ei tietenkn ole ainoa tapa tehd texturemappausta
   -- eik itse asiassa edes ole oikea tapa, koska 3d-avaruudessa
   bittikartat vntyvt (z-koordinaattiahan ei oteta huomioon)
   -- mutta koska se toimii ja siit saa ihan kohtuullisen no-
   peankin, kytt lienee perusteltua. "Oikea" texturemappaus
   tarkoittaa, ett z-koordinaatti otetaan huomioon aivan samoin
   kuin muutettaessa objektin kulmapisteit 3d:st 2d:hen.
     Tilanne on "helpoin" (lainausmerkit varmaan ymmrrtte :I
   hahmottaa seuraavanlaisesta kuviosta:

          (x1,y1)           (u1,v1)_________________(u2,v2)
          /\                      |    /(au2,av2) /
         /   \                    |  /          /
 (ax1,y)/------\(ax2,y)           |/          /
       /         \                |(au1,av1)/
      /____        \              |       /
  (x3,y3)  -----_____\            |     /
                   (x2,y2)        |   /
                                  | /
                                 (u3,v3)

   Vasemmanpuoleinen kuvio on siis ruudulle piirtyv kolmio,
   johon on piirretty yksittinen "scanline" eli yhden outer
   loopin tulos, yksi horizlinen kutsu. Oikeanpuoleinen kuvio
   vastaa kolmiota bittikartta-avaruudessa, ja sama scanline
   on merkitty siihenkin toiselta kantilta katsottuna.
     Texturemappausrutiinissa ei siis tarvitse kuin inter-
   poloida, interpoloida ja viel kerran interpoloida; helppo
   homma gouraud-rutiinin ymmrtneelle (itse vnsin
   gouraudin jlkeen texturemappauksen yhdess pivss)!
     Ai ett koodia kaipaisitte? No way:
   a) koodi on niin samanlaista kuin gouraudissa ja flat
      -kolmiossa, ett sit olisi tyls kirjoittaa thn
      uudelleen ja
   b) pithn sit jotain itsekin tehd ;)

   Mutta kuten sanottu, e-mail toimii jos olet keskimrist
   typermpi yksil ;)




4. Sorttaustapoja
-----------------


 Ilman minknlaista sorttausta ruudulle tulee aikamoista sotkua
 piirrettess isompia mri polygoneja; polygonit piirretn
 aina samassa jrjestyksess, joten engine piirt joissain
 kulmissa pllimmiset polygonit ensin ja kauimmaiset vasta
 viimeiseksi. Tsshn tulee ikvi "lpinkyvyys"-efektej,
 eli jonkinlaista polygonien lajittelua kaivattaisiin.


 4.1 Z-sorttaus

   Z-sorttauksen idea on, ett lajitellaan polygonit niiden
   z-koordinaattien keskiarvon mukaan. Thn on helppo kytt
   quicksorttia, kuten seuraavassa pseudokoodissa:

    funktio quicksort(left, right)
     - q on apumuuttuja

       jos left pienempi kuin right
         q = partition(left,right)
         quicksort(left,q)
         quicksort(q+1,right)
       endjos
     endf

    funktio partition(left, right)
     - x, a, b apumuuttujia
     - crd on pyritettyjen pisteiden taulukko
     - face on polygonitaulukko

      x = crd[face[left,0],2] + crd[face[left,1],2] + crd[face[left,2],2]
      a = left-1
      b = right+1

      toista ikuisesti:

        vhenn b:st yksi niin kauan kuin
          (crd[face[b,0],2] + crd[face[b,1],2] + crd[face[b,2],2])
          on pienempi kuin x
        lis a:ta yhdell niin kauan kuin
          (crd[face[a,0],2] + crd[face[a,1],2] + crd[face[a,2],2])
          on suurempi kuin x

        jos a pienempi kuin b
          vaihda(face[a],face[b])
        muussa tapauksessa
          lopeta funktio ja palauta arvona b
        endjos

      endtoista

    endf

   Sitten vain piirretn polygonit jrjestyksess.
     Z-sortti ei ole todellakaan tydellinen sorttaustapa, mutta
   kelpaa perussortiksi ihan hyvin. Kun pidemmlle pstn,
   kannattaa opetella vaikka tsskin selitetty BSP-puu, joka on
   jo huomattavasti kehittyneempi algoritmi -- ja toisaalta mys
   huomattavan paljon vaikeatajuisempi ja vaikeampi toteuttaa.


 4.2 Z-buffer

   En ole itse nhnyt tarvetta koodata z-buffer-"sorttia", joten
   ksittelen siit vain idean. Z-buffer ei oikeastaan edes ole
   sortti, siin kun ei jrjestell mitn mihinkn.
   Z-buffer on sen verran hidas ettei sit kannata kytt kuin
   jos tarvitsee "tydellist" sorttia, jos siis esimerkiksi
   objektit voivat liikkua toistensa lpi.
     Z-buffer tarkoittaa jokaisen ruudun pisteen z-koordinaat-
   tien taulukkoa. Ennen ruudulle piirtmist taulukon koor-
   dinaattien z-arvoja verrataan jokaisen polygonin jokaisen
   pisteen z-arvoon. Jos polygonin pisteen z-arvo on *pienempi*
   kuin taulukossa oleva arvo, sijoitetaan polygonin z-koor-
   dinaatin arvo taulukkoon edellisen paikalle. Lopuksi piir-
   retn ruudulle taulukossa olevat pisteet eli pisteet,
   joiden z-koordinaatit ovat pienimmt (niinp taulukkoon
   kannattaa z-arvojen lisksi laittaa pisteiden vriarvot).
     Z-bufferissa tarvitaan ilmeisesti 3d-interpolaatiota, mutta
   sehn ei tuota pnvaivaa 3d-ohjelmointiguruille ;)


 4.3 BSP-puu

   BSP-puu on saanut mainetta erittin vaikeana mutta
   erinomaisena sorttaustapana. Molemmat mritelmt ovat
   oikeita: omien BSP-puuta kyttvien rutiinien koodaus on
   todella tyls operaatio. Varsinkin jos kaikki dokumentaatio
   on englanniksi eik tarvittava matikka ole hallussa ;)
   EN selosta BSP-puuhun tarvittavan linkitetyn listan kytt,
   sen voi jokainen osaamaton lukea esim. Mikrobitin numerosta
   11/95.
   Linkitetyn listan erikoistapaus, binripuu, tarkoittaa, ett
   jokaisesta puun haarakohdasta lhtee kaksi uutta haaraa:

               1
             /   \
            2     3
           / \   / \
          4   5 6   7

   Etcetc. Tmn toteuttaminen on yksinkertaista, enk usko
   lukijallekaan jvn vaikeuksia.


   4.3.1 The main idea

     BSP-puuta on yht helppo soveltaa 2d-maailmaan kuin
     3d-avaruuteenkin (tosin en ne mitn jrke 2d-maailman
     sorttauksessa). 2d-maailma on helpompi piirt ;) joten
     selostan pperiaatteen sen avulla.
       Tss on kuusi viivaa:

          1                   |
     -------------            | 2
                              |
                ---___  3     |           /
                      ---___            /
                \           ---___    /
                 \ 4                / 6
                  \               /
                   \            /

                  ________---------
          --------     5
                                      X (kamerapiste)

     Nm pitisi piirt oikeassa jrjestyksess kytten
     BSP-puuta.
       Ensin tehdn puun alustus:
     Aloitamme viivasta 1. Tutkimme, mill puolella sit ovat
     loput viivat, ja teemme binripuun jonka toiselle haaralle
     menevt viivan 1 "oikealla" puolella olevat viivat ja toiselle
     "vasemmalla" olevat viivat. Jos jokin viiva on osittain
     oikealla, osittain vasemmalla, se katkaistaan leikkauskohdasta
     ja siit muodostetaan kaksi viivaa joista molemmat ovat
     luonnollisesti jommalla kummalla puolella viivaa 1. Sitten
     otetaan viiva 2 ja tutkitaan viivojen 3, 4, 5 ja 6 osalta
     sama, ja jatketaan tt kunnes kaikki viivat on jrjestetty
     somaan pikku puuhun.
       Kun sitten pitisi piirt nm viivat, tehdn seuraavasti:
     Tutkitaan, mill puolella ollaan viivaa 1. Jos ollaan
     vasemmalla, tutkitaan onko oikealla puolella viivoja, ja jos
     on, mennn alas oikeaa haaraa. Kun oikea haara on kyty lpi,
     piirretn viiva 1 ja siirrytn vasempaan haaraan. Jos
     alunperin oltiin oikealla, tehdn pinvastoin eli ensin
     vasenta, sitten oikeaa haaraa. MUISTA piirt viiva 1
     mainitussa kohdassa!

     Esimerkkitapauksessamme tehdn seuraavat viivojen
     jako-operaatiot:

          1                   | 2a
     -------------
                              | 2b
                ---___  3a    |           /
                      ---___            / 6a
                \           -- ___
                 \ 4           3b   /
                  \               / 6b
                   \            /
                         5b
             5c   ___ ____--- ----
          --------             5a
                                      X (kamerapiste)

     Binripuu taas nytt tlt:

                     _____1______
                   2a            2b
                               /    \
                             3a      3b
                            /       /  \
                           4      5a    6a
                          / \       \
                        5c   5b      6b

     Kuvassa nkyvn kamerapisteen suhteen piirrettyn
     viivojenpiirtojrjestys lasketaan siis nin:
      1) Olemme ykksen oikealla puolella. 2a piirretn siis ensin,
         sitten 1, sitten mennn alas oikeaa haaraa.
      2) Olemme mys 2b:n oikealla puolella, eli ensin mennn alas
         vasenta haaraa.
      3) 3a:n suhteen olemme vasemmalla. Siisp 3a piirretn ensin
         ja sitten mennn alas vasenta haaraa (oikealla puolella ei
         ole viivoja).
      4) Viivan 4 suhteen olemme 5b:n puolella eli oikealla.
         Piirtojrjestys on siis 5c, 4, 5b.
      5) Palaamme takaisin 2b:hen ja piirrmme sen, mink jlkeen
         marssimme kohti sen oikeaa puolta ja 3b:t.
      6) Olemme 3b:n vasemmalla puolella eli piirrmme 6a:n ja 3b:n
         ja menemme 5a:han.
      7) 5a:n suhteen olemme vasemmalla, siisp piirrmme ensin
         6b:n ja vihon viimeisen 5a:n.
     Eli jrjestys on 2a,1,3a,5c,4,5b,2b,6a,3b,6b,5a. Nyttpi
     kohtuullisen jrkevlt.

     Kannattaa kokeilla itse paperilla, ei tuosta vlttmtt
     muuten tolkkua saa. Itse piirsin 18:n viivan "maailman" vain
     hahmottaakseni BSP-puun paremmin. Puusta tulikin sitten A4:n
     kokoinen ;)


   4.3.2 Kaavat

     Hieno homma tuo BSP-puu, mutta miten se toteutetaan?
     Itsekin kysyin asiaa ennen kuin itse asiassa edes mietin
     toteutustapoja, mutta keksin itsekin ihan hyvn konstin.

     Lukion analyyttisen geometrian kurssilla opetetaan mm. miten
     muodostetaan tason ja avaruussuoran yhtl pelkist
     avaruuspisteist. Koska en voi olettaa lukijalla olevan tt
     tietomr, selostan asian itse.
       Tason yhtl on seuraava:

       Nx * (X-ax) + Ny * (Y-ay) + Nz * (Z-az) = 0,

     miss N on tason normaalivektori (Nx etc ovat siis sen i:n,
     j:n ja k:n kerroin), X, Y ja Z samat kuin esim. 2d-suoran
     yhtlss eli niiden arvot vaihtelevat ihan sikana, ja
     (ax,ay,az) on tason yksi piste.
       Tll perusteella pitisi siis muodostaa kolmion krkien
     kautta kulkeva taso. Thn taas tarvitaan normaalivektoria,
     joka lasketaan tekemll ensiksi kolmion pisteist kaksi
     vektoria ja ottamalla sitten niiden ristitulon.
       Nyt sijoitetaan X:n, Y:n ja Z:n paikalle tutkittavan
     pisteen koordinaatit. Jos tulokseksi saadaan nolla, piste
     on tasolla. Jos taas tulos on negatiivinen, se on tason
     toisella puolella, jos se on positiivinen, se on toisella.
     Tt sovelletaan kohdassa 4.3.1 selostettuun systeemiin, ja
     toimitaan tulosten mukaisesti.

     Jos kaikki tietyn kolmion kulmat eivt ole samalla puolella
     tutkittavaa tasoa, ne leikkaavat toisensa. Tllin pit
     laskea leikkauspisteet ja muodostaa niiden avulla kolme
     uutta kolmiota, joista jokainen on jommalla kummalla
     puolella tasoa.
       Thn tarvitaan avaruussuoran yhtl, joka on tllainen:

       X = X1 + (X2-X1)t
       Y = Y1 + (Y2-Y1)t
       Z = Z1 + (Z2-Z1)t

     Tm suora kulkee siis pisteiden (X1,Y1,Z1) ja (X2,Y2,Z2)
     kautta, t-kertoimen saadessa kaikki reaalilukuarvot.
     Kun suoran X, Y ja Z sijoitetaan tason X:n, Y:n ja Z:n
     paikalle, ratkaistaan t ja sijoitetaan se taas suoran X:n,
     Y:hyn ja Z:aan, olemme saaneet suoran ja tason
     leikkauspisteen koordinaatit:

       Nx*(X1+(X2-X1)t-ax) + Ny*(Y1+(Y2-Y1)t-ay) +
       Nz*(Z1+(Z2-Z1)t-az) = 0

     Tst ratkaistaan siis t:

       Nx*(X2-X1)t + Ny*(Y2-Y1)t + Nz*(Z2-Z1)t =
       Nx*(X1-ax) + Ny*(Y1-ay) + Nz*(Z1-az)

                       < = >

       t * ( Nx*(X2-X1) + Ny*(Y2-Y1) + Nz*(Z2-Z1) ) =
       Nx*(X1-ax) + Ny*(Y1-ay) + Nz*(Z1-az)

                       < = >

           Nx*(X1-ax) + Ny*(Y1-ay) + Nz*(Z1-az)
       t = ------------------------------------
           Nx*(X2-X1) + Ny*(Y2-Y1) + Nz*(Z2-Z1)

     Onneksi tt ei tarvitse kytt kuin inittiosassa ;)


   4.3.3 Vinkkej

     1) Tason yhtln laskeminen on sen verran hidasta, ett
        kannattaa laskea normaalit vain ihan ohjelman alussa
        ja pyritell sitten niit pisteiden mukana.

     2) Helpoin tapa ei tosiaankaan ole nopein. Kannattaa ehk
        inittiosassa tutkia, montako polygonisplitti eri
        vaihtoehdot eli eri polygonien valinta ykkspolygoniksi
        aiheuttavat, ja ottaa sitten kyttn se jossa niit
        tulee vhiten -- nopeutta tulee roimasti lis kun
        polygonien mr pienenee puoleen, mik tapahtuu varsin
        helposti.

     3) Krsivllisyytt. BSP-puurutiinien koodaus on todella
        hidasta hommaa, ne kun vaativat jo sen verran
        monimutkaisempaa matikkaa kuin joku rupunen z-sort :)

     4) Jos BSP-rutiinisi ovat hitaammat kuin Z-buffer, on
        jossain pahasti vikaa ;)

     5) BSP-puu ei ole itsetarkoitus eik se sovellu kaikkeen
        3d:hen. Esimerkiksi raytrace-enginess on pakko kytt
        Z- tai S-bufferia koska objektit voivat leikata toisensa,
        mit BSP-puu ei salli.




5. Varjostustapoja
------------------


 Nyt on sitten se polygonikin ruudulla, mutta nytt sekin aika
 tylslt kun vrit pysyvt koko ajan samoina; realismi olisi
 kiva juttu.


 5.1 Flat-sheidaus

   5.1.1 Z-flat

     Z-flat on melko slittvn nkinen sheidaus, joka saadaan
     aikaan nimens mukaisesti antamalla polygonille vriarvo sen
     kulmien z-koordinaattien keskiarvon perusteella:

       vri = max_col - (kulma1.z + kulma2.z + kulma3.z) / a,

     miss a on jokin sopiva luku jolla jakamalla keskiarvo saadaan
     suurimman mahdollisen vrinumeron (max_col) ja nollan
     vlimaastoon. Koska kyseess on koordinaatisto jossa z-akseli
     sojottaa siihen suuntaan johon luultavasti tllkin hetkell
     katsot ;) niin z-arvo on kauempana suurempi eli saatu arvo
     pit vhent suurimmasta mahdollisesta vrinumerosta.


   5.1.2 Lambert Flat

     Lambert Flat onkin jo huomattavasti paremman nkinen, siin
     kun on ihka oikea valonlhdekin. Samalla saadaan viimeinkin
     jotain kytt niille vektoreille, joita alussa yritettiin niin
     kovasti opiskella ;) Lambert flatkin tosin vlkkyy ikvsti.
       Idea on seuraavanlainen: annetaan valonlhteelle i-, j- ja
     k-arvot, eli vektorin kertoimet, jotka osoittavat valonlhteen
     suunnan (itse valonlhde on rettmn kaukana). Jokaiselle
     framelle lasketaan sitten jokaista polygonia vastaavan tason
     normaalivektori (ristituloa ja vektorin yhtln laskemista),
     ja selvitetn pistetulon avulla tmn vektorin ja valovektorin
     vlisen kulman kosini (mit pienempi kulma, sit kirkkaampi
     vriarvo). Sopivalla kertoimella saadaan tm vriarvo haluttujen
     vrirajojen sisn, esimerkiksi itsellni RGB-tilassa vlille
     0..63 kertoimella 63. Lopuksi tarkistetaan onko vriarvo nega-
     tiivinen. Jos on, muutetaan se nollaksi, eik polygonia ny.
       Pseudoa ken:

      - LSi, LSj, LSk valonlhdevektorin kertoimet (Light Source)
      - Ni, Nj, Nk tason normaalivektorin kertoimet
      - a apumuuttuja

      funktio LambertFlat

        < lasketaan tason normaalivektorin kertoimet (ensin muodos-
          tetaan kaksi vektoria kolmesta pisteest ja sitten niiden
          ristitulo) >

        a = sqrt(Ni*Ni + Nj*Nj + Nz*Nz) *
            sqrt(LSi*LSi + LSj*LSj + LSk*LSk)     // a = |N| * |LS|
        jos a ei ole nolla (nollalla ei viitsi jakaa)
          color = max_col * (LSi*Ni + LSj*Nj + LSk*Nk) / a
          jos color pienempi kuin nolla
            color = 0
          endjos
        muussa tapauksessa
          color = 0
        endjos

        palauta color
      endf

     Tmhn on aika hidas konsti (tulee kaksi nelijuurta,
     lukuisia mulleja ja div jokaiselle polygonille), joten
     nopeutus olisi tarpeellinen.
       Jos molempien vektorien pituus on yksi, lhtee suurin
     osa mulleista, div ja molemmat sqrt:t litomaan. Pulmana
     on vain ett miten varmistetaan se, ett molempien pituus on
     yksi. Thn on olemassa konsti: matemaattinen totuus on,
     ett vektorin, jonka kaikki komponentit jaetaan sen
     pituudella, pituus on yksi:

       vektori ai + bj + ck,
       pituus l = sqrt(a^2+b^2+c^2).

      Samansuuntainen yksikkvektori:

         (a/l)i + (b/l)j + (c/l)k.

     Jos valonlhdevektoria liikutellaan, pit sen pituudeksi
     varmistaa tm yksi joka framella, tosin jos liev eptark-
     kuus ei haittaa, voidaan laskea se vain n. joka neljnnell
     framella tai tarpeen mukaan. Jos taas se pysyy paikallaan,
     voidaan sen pituus laskea ennen pyrittely ja se pysyy
     koko ajan samana.
       Normaalivektorien tilanne on vhn kinkkisempi; tuo
     pituus on aika hidasta laskea, ja divvejkin tulisi enemmn
     kuin Suomen perustuslaki sallii. Ei, kyll on oltava
     nopeampi konsti. Ja onkin!
       Mits sanot tst: lasketaan jokainen normaalivektori
     taulukkoon init-vaiheessa, varmistetaan sen pituudeksi yksi
     (tai oikeastaan mieluummin max_col niin ei tarvita sit
     yht mullia joka polylle ja fixedpointkin tulee samalla
     suoraan) ja pyritelln sitten niitkin ihan kuin ne
     olisivat koordinaatteja! Nopeutus on huomattava.
     Siis:

       - laske normaalivektorin i:n, j:n ja k:n kertoimet,
       - laske vektorin pituus,
       - kerro i:n, j:n ja k:n kertoimet max_col:lla,
       - jaa ne vektorin pituudella.

     Nyt funktio sieventyy muotoon

      funktio LambertFlat
        color = LSi*Ni + LSj*Nj + LSk*Nk
        jos color pienempi kuin nolla
          color = 0
        endjos
         palauta color
      endf
 

 5.2 Gouraud-sheidaus

   "Mits me jollain Flatilla, Gouraudia kehiin!" huutaa kansa.
   "Gou-raud, Gou-raud!!" kiljuivat kohta jo pikkulapsetkin.
   "Miks siin", tuumi Caesar, "Kansa on huvinsa ansainnut."
   "Avatkaa portit!", hn komensi. "Gouraud esiin!" Ja rauta-
   porttien viel naristessa ryntsi areenalle Gouraud, petojen
   sukua.
   (anteeksi, kello on paljon :D

   Lukija pohtii varmaan mit selittmist gouraudissa viel on,
   gouraud-kolmio kun on jo ksitelty. Homma ei vain toimi niin
   kauan kuin ei tiedet mit vriarvoja millekin objektin
   pisteelle annetaan.

   5.2.1 Z-Gouraud

     Z-gouraud toimii samalla periaatteella kuin Z-flat, eli
     vriarvo otetaan pisteen z-koordinaatista. Se on aika tylsn
     nkinen, mutta hienompi joka tapauksessa kuin Z-flat, jonka
     pesee mik shade tahansa ;)
     Eli otetaan pisteen Z-koordinaatti, jaetaan se sopivalla
     kertoimella (sopiva riippuu objektista) ja vhennetn
     maksimivriarvosta; no problem.

   5.2.2 "Oikea" Gouraud

     Tm taas skulaa samoin kuin Lambert Flat, sill erotuksella
     ett ei oteta polygonin ja valovektorin vlist kulmaa vaan
     jokaiselle pisteelle siihen kiinnittyvien polygonien ja
     valovektorin vlisten kulmien keskiarvo. Ei mikn minuutin
     nakki, joten pseudo lienee paikallaan (*tmkin* (c) Kaj
     Bjrklund):

     funktio CalcNormals

       < lasketaan yhden tason normaalivektori ristitulolla >
       funktio calcnor(X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3,NX,NY,NZ)
         int RelX1,RelY1,RelZ1,RelX2,RelY2,RelZ2
         RelX1=X2-X1
         RelY1=Y2-Y1
         RelZ1=Z2-Z1
         RelX2=X3-X1
         RelY2=Y3-Y1
         RelZ2=Z3-Z1
         NX=RelY1*RelZ2-RelZ1*RelY2
         NY=RelZ1*RelX2-RelX1*RelZ2
         NZ=RelX1*RelY2-RelY1*RelX2
       endf

       < face = polygonitaulukko, vertex = pistetaulukko >
       int i,a,ox,oy,oz
       float cx,cy,cz,len,cn

       for i=0 -> pisteiden lukumr-1

         cx=0
         cy=0
         cz=0
         cn=0
         for a=0 -> polygonien lukumr-1

           < jos polygoni koskettaa pistett i >
           if ((face[a][0]=i) tai (face[a][1]=i) tai (face[a][2]=i))

             < funktio palauttaa (ox,oy,oz):aan normaalin i:n,
               j:n ja k:n kertoimet >
             calcnor(vertex[face[a][0]].x,vertex[face[a][0]].y,
                     vertex[face[a][0]].z,vertex[face[a][1]].x,
                     vertex[face[a][1]].y,vertex[face[a][1]].z,
                     vertex[face[a][2]].x,vertex[face[a][2]].y,
                     vertex[face[a][2]].z,ox,oy,oz)
             < (cx,cy,cz) tulevat sisltmn normaalien
               keskiarvon, cn: listn koska se ilmoittaa
               montako normaalia on laskettu cx:n jne yhteen >
             cx=cx+ox
             cy=cy+oy
             cz=cz+oz
             cn+=1
           endif

         endfor

         < jos joku polygoni koskettaa pistett >
         if cn > 0
           < lasketaan keskiarvot >
           cx=cx/cn
           cy=cy/cn
           cz=cz/cn
           < lasketaan normaalivektorin pituus >
           len=sqrt(cx*cx+cy*cy+cz*cz)
           if len = 0
             len=1
           endif
           < varmistetaan ett kaikki normaalivektorit ovat
           pituudeltaan 64 >
           normal[i].x=(cx/len)*64
           normal[i].y=(cy/len)*64
           normal[i].z=(cz/len)*64
         endif

       endfor

     endf

     Ja kytt siis tapahtuu samaan tapaan kuin Lambert flatissa.


   5.2.3 Phong Illuminating

     Phong illuminating tarkoittaa sit, ett paletin avulla
     kikkailemalla (ns. eplineaarinen paletti) saadaan gouraud
     nyttmn hienommalta phongilta. Miks siin, hienompi
     siit tulee kuin tavallisesta gouraudista.
     Pseudoa jlleen, arvatkaa-kenen lahjoittamaa:

     funktio SetPalette

       < katkaistaan vriarvo oikealle vlille 0..63 >
       funktio snap(arvo)
         if arvo > 63
           arvo = 63
         else if arvo < 0
           arvo = 0
         endif
         palauta arvo
       endf

       int i
       float cosi,r,g,b
       for i=0 -> 255
         cosi=i*PI/1024
         r=snap(5+cosi*60+cosi*cosi*cosi*cosi*150)
         g=snap(0+cosi*30+cosi*cosi*cosi*cosi*150)
         b=snap(-5+cosi*15+cosi*cosi*cosi*cosi*150)
         muuta_vari(i,r,g,b)
       endfor
     endf

     
 5.3 Env-mappaus

   Monet sekoittavat oikean phongin env-mappaukseen, mutta ne
   ovat kaksi eri asiaa. Env-mappaus + motion blur on tehokas
   konsti peitt enginen heikkoudet, env-mappaus saa esimerkiksi
   riittvnmonisrmisen palloa matkivan objektin todella
   nyttmn pallolta (tosin gouraudkin onnistuu tss aika
   hyvin), ja sen avulla voi tehd mys erilaisia kuvioita
   varjostukseen, jolloin pinnat alkavat nytt vaikkapa
   metallisilta. Tmn dokumentinkin mukana tulee yksi
   bittikartta, joka kelpaa melko hyvin env-mappaukseen. Ai
   mistk niit saa? Kaverinkaverin kuvanksittelyohjelmilla
   tuollaisten tekeminen on helppoa ;)
     Env-mappaus toimii muuten samoin kuin gouraud, mutta
   gouraud-kolmion sijasta kutsutaan texture-kolmiota, ja
   normaalien ja valovektorin vlisten kulmien sijasta kytetn
   normaalien i:n ja j:n kertoimia indeksein esim. 256x256
   -kokoiseen bittikarttaan. Tllin tosin ei saada aikaan
   aitoa liikuteltavaa valonlhdett, mutta sekin onnistuu pienen
   kikkailun avulla.

   Ohjelman init-osa:
   - lataa taulukkoon bittikartta (kuva tai itse generoitu,
     reunoilta tummaa, keskelt kirkasta)
   Jokaiselle framelle, jokaiselle polygonille:
   - ota jokaisen pisteen normaalin x- ja y-arvo, kerro ne
     kahdella ja lis 127 (koska ne ovat vlill 0..64 ja ne
     pit saada vlille 0..255 niin ett keskipiste on kohdassa
     (128,128)): col_x1 = normal_x * 2 + 127
   - kutsu texture-rutiinia antaen koordinaattien arvoiksi sken
     lasketut.




6. Muuta mukavaa
----------------


 6.1 Hidden face removal

   Hidden face removalin eli nkymttmn polygonin poiston
   idea on yksinkertainen: jos polygoni on vrin pin, sit
   ei tarvitse piirt. Tm ei tietenkn toimi, ellei
   polygoneja ole mritelty oikein, joten kannattaa olla
   tarkkana.
     Onko polygoni vrin pin, on helppo ptt tmn pseudo-
   koodin avulla:

     funktio visible(x1, y1, x2, y2, x3, y3)
      - dx1, dy1, dx2, dy2 ovat koordinaattien erotuksia

       dx1 = x3 - x1
       dy1 = y3 - y1
       dx2 = x3 - x2
       dy2 = y3 - y2
       jos ( ( dx1*(dy2-dy1)-(dx2-dx1)*dy1 ) > 0 )
         palauta_arvo TRUE
       muuten
         palauta_arvo FALSE
     endf

   Jos funktio palauttaa arvonaan TRUE, polygoni piirretn, muuten
   ei.


 6.2 Kamera and how did I (at first) do it

   Tm ei todellakaan ole mikn yleisptev konsti, mutta
   joillekuille sekin riitt: miksi vaivautua suurellisiin
   koordinaatistojen muuttamisiin kameran liikkuessa, kun saman
   saa aikaan pyrittmll koko 3d-avaruutta kamerakulmien
   verran vastakkaisiin suuntiin? Toki valonlhteet etc pit
   muistaa pyritt samalla, muuten tulee helposti "pyrivn
   planeetan syndrooma", jossa planeetta pyrii, ja lheisen
   thden aiheuttama varjo pyrii samanaikaisesti. Sellainen
   nytt *melko* naurettavalta ;)



EOF
