

      3DICA.TXT v2.1 (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
     0.6     3d-termist yms.

   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         3D ja matriisit
     3.1     Aivan uusi ajattelutapa
     3.2     Objektimatriisin liikuttaminen eli transformointi
      3.2.1  Siirtminen eli translaatio
      3.2.2  Pyrittminen eli rotaatio
      3.2.3  Kamera
     3.3     Pisteen transformointi objektimatriisilla
     3.4     Hierarkiset transformaatiot
     3.5     Sekalaista matriiseista

   4         Erilaisia polygonifillej
     4.1     Flat-kolmio
      4.1.1  Fixed point
     4.2     Gouraud-kolmio
     4.3     Texture-kolmio

   5         Sorttaustapoja
     5.1     Z-sorttaus
     4.2     Z-buffer
     5.3     BSP-puu
      5.3.1  The main idea
      5.3.2  Tarvittavat kaavat
      5.3.3  Vinkkej
     5.4     S-buffer

   6         Varjostustapoja
     6.1     Flat-sheidaus
      6.1.1  Z-Flat
      6.1.2  Lambert Flat
     6.2     Gouraud-sheidaus
      6.2.1  Z-Gouraud
      6.2.2  "Oikea" Gouraud
     6.3     Phong-sheidaus
      6.3.1  Phong Illuminating
      6.3.2  Env-mappaus
      6.3.3  Voltaire/OTM:n Phong

   7         Muuta mukavaa
     7.1     Hidden face removal
     7.2     Kamera and how did I do it
     7.3     Objektin pyritysnopeuden suhteuttaminen koneen
             nopeuteen




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 (edellisten versioiden lukijat sitkin
   enemmn ;)
     Opiskelin siis mainitussa koulussa ABi-asteella (kirjoitukset
   ovat jo takana), ja syksyll olisi tarkoitus aloittaa uusi
   elm Otaniemen tietotekniikkaosastolla. Ett pitks sitten
   peukkuja toukokuun loppupuolella :)
     Yhteyden minuun saa parhaiten e-mailaamalla osoitteeseen
   ilkka.pelkonen@mbnet.fi (mbnettiliset voivat toki messuta
   privamailiin ;), 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, mutta kuulen
   siis erittin mielellni palautetta tst dokumentistakin.
   Virheet saa, ei vaan PIT, ilmoittaa :)

   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. Syksyll '96
   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.
     Nyttemmin olen jatkanut kirjoittamista lhinn dokumentin
   saaneen suosion positiivisesti yllttmn, ja liskin lienee
   luvassa riippuen jaksamisestani ja ajan riittmisest.

   Tm taitaa olla oikea paikka morjenstaa Kaj Bjrklundia,
   jonka kanssa olemme 3d-koodauksen ideanvaihdossa (tosin se on
   ollut turhan   yksipuolista ;I. Olen saanut hnelt apua mm.
   valonlhteen ksittelyyn, hidden face removaliin, gouraudiin
   ja phong illuminating -shadeen. Herra Bjrklund on mys
   vastuussa osasta tmn dokumentin tekstist versiosta 2.1
   lhtien, tosin allekirjoittaneen editoimana (voi kaauhea
   minkklaassja krijootus- ja kieli oppivihreit se heppu tekee ;)

   Dokumentista on olemassa mys helppolukuisempi html-versio
   mist kunnia lankeaa Jonni Lehtirannalle. Ko. versio lytyy
   osoitteesta http://www.nimad.fi/sudet/members/valokas/, joten
   kaikki heti sinne ;)


 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 -- *ja**omaa**jrken*! Ei
   ohjelmointia -- edes 3d-ohjelmointia ;) -- opita pivss tai
   viikossa, eik htilyst ole kuin haittaa. Paras tapa on edet
   jrjestelmllisesti efekti kerrallaan.
     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 lisksi greetit ensi versioon). HUOM! l
   pid tt ahneena rahanlypsmisen, vaan asetu minun asemaani:
   jos olisit itse viettnyt yli puolisataa 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
   (tai siis oikeastaan meit :) 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 ovat 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.


   Uutta versiossa 2.1:
     - Kaj Bjrklund on tullut mukaan kehitystyhn. Flat-poly-
       gonien, interpolaation ja phong illuminating -efektin
       selventminen sek frameskip ovat hnen ksialaansa.
     - Vektorin ja matriisin tulossa oli ajatusvirhe, joka on
       korjattu.
     - Matriisipyritykset ovat nyt mukana kokonaisuudessaan.
     - Innostuin S-Bufferista, opettelin sen ja lissin saman
       tien osion mys thn tutoriaaliin :)
     - Lukijoiden pyynnist useita vanhan tekstin kohtia on
       selvennetty.
     - Vektoritehtvien ratkaisut ovat mukana useiden lukijoiden
       pyynnst. Nyt ei niiss pitisi olla epselvyyksi :I
     - 3d-sanasto, joka tosin on lapsenkengissn.
     - Kaj meni ja vnsi TP7:lla dokumenttiin nipun esimerkki-
       ohjelmia, jotka lytyvt paketista 3DI_SRC.ZIP. Mukana
       3D Studion .ASC-loaderi (ja esimerkki-ASC), .PCX-loaderi
       texturemappausta varten (ja esimerkki-PCX), sek Lambert
       Flatin, Z- ja oikean gouraudin, env-mappauksen ja texture-
       mappauksen esimerkkiohjelmat. Tss vaiheessa viel melko
       dokumentoimatonta mutta silti kohtalaisen selv pascal
       -koodia.
     - Voltaire /OTM:n kehittmn Phong-toteutusmuodon idea on
       selostettu. Oikeaa phongia ei ole vielkn.


 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(tal): No nyt poistin muistutuksen siit 3d-starfieldist :D
   MiG: "Nyt on Duuni. Nyt on DUUNI! AAAAAAAA!!!"
   Oca: Sulkis rulaa, varsinkin kun min voitan :)
   RoDeX: Kerilisit postimerkkej, polygonirutiinien kerily on
          jotenkin ..outoa ;)
   Praetor: Mies, joka knsi tmn docun html-kielelle, mist
            hnelle kiitokseni.
   Pizzaman: Vitelln jos eletn. Eik se elm kirjoituksiin
             loppunut :I
   Wrath: Mik projekti nykyn rullaa? ;)
   Jokke: Kai se pit sitten sinuakin greetata ;)
   tonic: tAAt rlz 8-)
   rAATO/tAAt: Kyhtnk jotain siisti kunhan irvimiseltsi
               ehdit? ;)
   RRRulettavaKonna: Olet ers oudoimmista tyypeist joiden kanssa
                     olen mlissyt... :I
   KajBjrkLund: Kiitos RC:n ilmaisesta rekkauksesta! :)
   HezeRaunio: Sait kai yo-aineista enemmn kuin preleiss? :I
   JukkaPMannInen: Ei thn keksi mitn :)
   WesaWepslInen: Win95! Win95! Win95! ..on *sielt* :)
   JereSaniSalo: Miten raytrace-enginesi toimii?
   PanuArtimo: Sun Warp-desktoppi on RUMA! :) (MBnet-versio)
   HenriKronLund: Miten menee? Ei ole messuiltu sitten TZ:n
                  kaatumisen!
   IlkkaWuorenMaa: Juoksu? Hauskaa? BUHAHA!! Mutta HLKK... ;) ;)
   TapioWuorInen: Hubris on hyv. ;)
   JanneGrnThal: Kommenttisi ovat joskus *uskomattoman*
                  hauskoja :) Pid vain tagilistaani silmll ;)
   KasperRnning: Miss se lupaamasi paletinkvantisointijuttu
                  viipyy?-) Ei ole miehest kuulunut pitkn aikaan
                  mitn.


 0.6 3d-termist yms.

   Ensinnkin kyttmni koordinaatisto on seuraavanlainen:
   x-akseli kulkee vasemmalta oikealle, y-akseli ylhlt alas ja
   z-akseli osoittaa suoraan ruudun sisn. Pyritysrutiinit
   toimivat oikean kden systeemin mukaisesti, eli kun peukalo
   osoittaa akselin suuntaan, sormet taipuvat positiiviseen
   pyrityssuuntaan (eli pyrittesssi z-akselin ympri
   positiiviseen suuntaan, pisteet pyrivt ruudulla mytpivn).

   Ja sitten 3d:hen liittyv termist. Nm ovat vain ensimmi-
   sin phn juolahtaneita termej, seuraaviin versioihin saata-
   neen huomattavasti kattavampi "sanasto". Mailia vain, niin
   selvitetn muutkin epselvt termit ja saadaan listaankin
   lisyst!

     Bilineaarinen filtterinti (suodatus)
                 Tekniikka, jolla lhelle zoomattujen bittikart-
                 tojen pikselitymist voidaan vhent. Esim:
                 Ota pala ruutupaperia, ja tkk siihen piste.
                 Se ei luultavasti osu ruudun keskelle. Piirr
                 pisteen ymprille ruutu. Osa ruudusta on nyt
                 toisten paperin ruutujen alueella, eli tietty
                 pinta-alaprosentti osuu yhteens neljn ruudun
                 alueelle. Niden prosenttimrien mukaan
                 sekoitetaan texeleist oikeanvrinen pikseli,
                 joka laitetaan ruudulle.

     Face        Objektin pinnan polygoni, jonka kulmat ovat
                 verteksej.

     Klippaus    Nkymttmiss olevien polygonien tai niiden
                 osien piirtmtt jttmist.

     Matriisi    Ernlainen lukutaulukko, jonka avulla ert
                 monimutkaiset laskutoimitukset ovat helpompia.
                 Matriisimatematiikkaa kytetn mm. 3d-pyri-
                 tysten helpottamiseen.

     Objekti     Esine 3d-avaruudessa.

     Origo       Avaruuden keskipiste, koordinaatit (0,0,0).

     Paletin kvantisointi
                 Tapa, jolla voidaan kytt 256-vrisess tilas-
                 sa samanaikaisesti sek 256-vrist bittikarttaa
                 ett varjostusta, montaa eri bittikarttaa joissa
                 on eri paletit, tai vaikkapa 24-bittisi kuvia
                 256-vrisess tilassa.

     Polygoni    Monikulmio, huom. EI vlttmtt kolmio. n-
                 kulmainen polygoni voidaan muodostaa n-2 kolmi-
                 osta.
     
     Quaternion  4d-avaruus, kytnnllinen kuulemma pyrityksiss.

     S-buffer    Segmented buffer, Z-bufferin paranneltu versio
                 jossa pisteiden sijaan tarkastellaan vaakaviivoja
                 (kytetn mys nimistyst scanline Z-buffer,
                 mikli olen ymmrtnyt oikein).

     Sorttaus    Polygonien piirtmisjrjestyksen mrittminen
                 siten, ett takimmaiset piirretn ensin ja etum-
                 maiset vasta lopuksi.

     Subpixel    Sijainti pikselin sisll (ks. bilineaarinen
                 filtterinti).

     Subtexel    Sijainti texelin sisll (ks. bilineaarinen
                 filtterinti).

     Texel       Texturemap pixel eli bittikartan pisteen vri.

     Vektori     Jana jolla on vakituinen pituus ja suunta, mutta
                 ei paikkaa (voi lhte origosta, mutta mys
                 huomattavan monesta muusta pisteest :)

     Verteksi    Objektin kulmapiste, johon kiinnittyy yksi tai
                 useampia faceja.

     Verteksinormaali
                 Kulmapisteen normaali, joka saadaan laskemalla
                 ristitulolla jokaisen verteksiin kiinnittyvn
                 polygonin normaali ja ottamalla niiden keskiarvo
                 (tai laittamalla vektori sojottamaan objektin
                 origosta verteksiin, jos halutaan pst helpolla
                 gouraudissa -- EI toimi muissa kuin gouraudissa!).
                 Yleens verteksinormaalit ovat yksikkvektoreita,
                 koska siten pstn eroon jakolaskuista loopeissa
                 (niiss tarvitaan usein skalaarituloa joka vaatii
                 parikin vektorin pituudella jakamista. Jos pituus
                 on yksi, ei jakolaskua luonnollisesti tarvita :)

     Z-buffer    Sorttaustapa, jossa pisteet lajitellaan ruudun
                 kokoiseen taulukkoon niiden z-koordinaattien
                 mukaiseen jrjestykseen. Vain etummaisin pikseli
                 piirretn.


-- 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             |        x
   (ei pakollinen) ja 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. Vektori voidaan siis ksitt mys
   janana pisteest (0,0,0) pisteeseen (X,Y,Z).
   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 ).

   Matikkaa koulussa opiskelleet muistavat, ett tm on sama
   kuin pisteen (X,Y,Z) etisyys origosta.
     Yksikkvektori on vektori, jonka pituus on yksi. Saat
   vektorista yksikkvektorin jakamalla i:n, j:n ja k:n kertoimen
   vektorin pituudella. Jos et usko, voit kokeilla itse ;)
                  _                      _
  TEHTV: Olkoon a = i + j + k. Mrit a:n suuntainen yksikk-
  vektori.

    Ratkaisu:
       _
      |a| = sqrt ( 1^2 + 1^2 + 1^2 ) = sqrt(3),
      _
      a:n suuntainen yksikkvektori:

        (1/sqrt(3))i + (1/sqrt(3))j + (1/sqrt(3))k).


 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 |.

    Ratkaisu:

      2a = 2*2i - 2*6j = 4i - 12j,

      2a-b = (4-1)i + (-12-3)j = 3i - 15j,

      | 2a-b | = sqrt( 3^2 + (-15)^2 ) = sqrt(9+225) = 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.

    Ratkaisu:

      a+b = (3+1)i + (1-7)j = 4i - 6j
      a-b = (3-1)i + (1+7)j = 2i + 8j

      (a+b)(a-b) = |a+b| * |a-b| * cos(a+b,a-b),
      toisaalta (a+b)(a-b) = 4*2 + (-6)*8 = 8-48 = -40.

      |a+b| = sqrt( 4^2 + (-6)^2 ) = sqrt(16+36) = sqrt(52),
      |a-b| = sqrt( 2^2 + 8^2 ) = sqrt(4+64) = sqrt(68).

      Yhtlst

      sqrt(52) * sqrt(68) * cos(a+b,a-b) = -40 saadaan

      cos(a+b,a-b) = -40 / sqrt(52*68).

      Tst kulma on 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.

    Ratkaisu:

       i  j  k    -1  2     2 1      1 -1
       1 -1  2  =  3 -1i + -1 2 j +  2  3k
       2  3 -1 
                  = (-1*(-1)-3*2)i + (2*2-(-1)*1)j + (1*3-2*(-1))k

                  = (1-6)i + (4+1)j + (3+2)k = -5i + 5j + 5k.

      Ala = vektorin pituus = sqrt( (-5)^2 + 5^2 + 5^2 ) = 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.

     Ratkaisu:

        1 -1  2       3 -1        -1 2       2  3
        2  3 -1  = 1*-4  1 + (-1)* 1 a  + 2* a -4
        a -4  1 
                   = (3*1-(-4)*(-1)) - (-1*a-1*2) + 2*(2*(-4)-a*3)

                   = (3-4) - (-a-2) + 2*(-8-3a)

                   = -1 + a + 2 - 16 - 6a = -5a - 15

       Samassa tasossa -> skalaarikolmitulo = 0,

       -5a - 15 = 0  <=>  -5a = 15  <=>  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:

    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 vaakarivi ja toisen matriisin ensimminen
   pystyrivi, sitten ensimmisen matriisin ylin vaakarivi ja
   toisen keskimminen pystyrivi, sitten 1. matriisin ylin
   vaakarivi ja toisen viimeinen pystyrivi. Edelleen 1. matriisin
   keskimminen vaakarivi ja toisen ensimminen, ... Ja tulokset
   uuteen matriisiin 1. matriisin termien paikoille, 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+eY+iZ+m)i + (bX+fY+jZ+n)j +
                 i j k 0    (cX+gY+kZ+o)k
                 m n o 1 

   Eli i:n uusi kerroin saadaan kertomalla 1. pystyrivin eli
   i-rivin arvot vektorin kertoimilla sek alin yhdell, j:n ja
   k:n kertoimet vastaavasti.

   Tt kaavaa voidaan kytt vektorien lisksi mys pisteen
   transformoimiseen matriisilla, jolloin kirjaimet (X,Y,Z)
   ilmoittavat pisteen alkuperiset koordinaatit, ja kaavan
   tuloksen i:n, j:n ja k:n kertoimet vastaavasti tuloskoordi-
   naatit.




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,

                 ____________________________________
                |  ^y            _                   |
                |               /|z           x(10,0,30)
                |             /                     |
                |           /                       |
              ------------/---------x(10*a/30,0,a)-----
                |       / ^                         |
                |_____/____________________________|
                    /   (0,0,a)
       >
                                             x
                   

   eli kaikki avaruuden pisteet siirretn nelikulmion
   esittmlle tasolle siten, ett ne pysyvt origon kautta
   kulkevalla suoralla.

   Kaava on siis seuraavanlainen:

      X_SCREEN = X0 * PERSPECTIVE / Z0
      Y_SCREEN = Y0 * PERSPECTIVE / Z0

   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 ; keskipiste = ruudun keskipiste
       gy = y * 256 / z + 100 ; (320x200-tila)
       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

   HUOM!!! Monet lukijat ovat lhettneet toimimatonta koodiaan,
   jossa ers useimmiten lukuisista virheist on se, ettei
   alkoita varata kuin 255. C-kieless taulukko pit siis varata
   nin: short sinus[256], EI sinus[255]! Ksittmtnt miten
   ihmiset voivat olla tyhmi ;)

   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 :)

   Ei tietenkn ole vlttmtnt kytt juuri 256-alkioista
   taulukkoa, vaikka sill saavutetaankin tuo rollaustemppu. On
   tysin perusteltua kytt joskus suurempialkioisiakin taulu-
   koita, sill siten kappaleita voidaan pyritt pehmemmin.
   Kokonaisuudessaan rollaustemppu ei ole mitenkn erityisesti
   koodia nopeuttava, ktevmpi vain.


 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 (arvoina ei mitn "mrtty"),
     - xp, yp, zp ovat pisteen alkukoordinaatit joita *EI*
       muutella,
     - 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.

   HUOM!! Rutiini pyritt pisteen (0,0,0) ympri, EI pisteen
   (x_ruudun_kp,y_ruudun_kp,PERSPECTIVE) tms ympri kuten jotkut
   ovat luulleet, eli pisteeseen listn ruudun keskipisteen
   koordinaatit vasta pyrittmisen jlkeen!


 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 (ja vertekseille verteksinormaalit env-mappausta,
   phongia tai gouraudia varten).

   HUOM! l kyt pyritetyille ja alkuperisille pisteille
   samaa taulukkoa -- tuloksena on muuten pelkk sotkua ruudun
   tydelt. Siisp alkuperiset ja pyritetyt pisteet omiin
   taulukoihinsa!




3. 3D ja matriisit
------------------


 Tt kappaletta ei ole vlttmtnt lukea, ellet sitten satu
 haluamaan enginestsi nopeampaa ja ktevmp ;)


 3.1 Aivan uusi ajattelutapa

   Thn asti olemme ksitelleet objekteja pisteryppin, eli
   olemme aina suorittaneet operaatiot jokaiselle pisteelle
   erikseen. Milt tuntuisi esitt objektia yhtkki neljn
   vektorin (suunnat yls, sivulle ja eteen sek paikkavektori)
   avulla, suorittaa kaikki operaatiot niille ja vasta lopuksi
   liitt pisteet mukaan kertomalla se objektimatriisilla?
     Tss tavassa on monta hyv puolta: se on nopeampi,
   yksinkertaisempi ymmrt, helpompi toteuttaa ja eksaktimpi.
   Ai missk mieless eksaktimpi? Otetaan esimerkiksi (juujuu,
   on se suoraan otmmatx.doc:sta :) lentokone, jonka nokka
   osoittaa z-akselin, oikea siipi x-akselin ja vasen siipi
   y-akselin suuntaan. Pyrit lentokonetta y-akselin ympri
   niin, ett sen nokka osoittaa negatiivisen x-akselin suuntaan.
   Nyt kun pyritt sit z-akselin ympri, pyriik se *oman*
   z-akselinsa vai *avaruuden* z-akselin ympri? Tarkoitus on
   tietysti, ett se pyrii oman z-akselinsa ympri (miten muuten
   olisivat toteutettavissa esim. lentosimulaattorit?), mutta
   "normaaleilla" pyrityksill nin ei ky. Siisp matriiseja.

   3D-ohjelmoinnissa kytetn 4x4-matriiseja, joista tiedot
   lytyvt seuraavasti:

       [X-akselin yksikksuuntavektori]  0  
       [Y-akselin yksikksuuntavektori]  0  
       [Z-akselin yksikksuuntavektori]  0  
       [Keskipisteen koordinaatit]       0  

   Eli ensimmisen rivin kolme enimmist saraketta ilmoittavat
   objektin x-akselin suuntavektorin i:n, j:n ja k:n kertoimien
   arvot, toisen ja kolmannen rivin vastaavasti y- ja z-akselin,
   kuvan mukaisesti. Viimeisen pystyrivin arvot ovat aina nuo
   (0,0,0,1), joten ne voi jtt poiskin.
   Alimman rivin kolme ensimmist saraketta ilmoittavat siis
   objektin keskipisteen koordinaatit ((0,0,0) jos halutaan sen
   pyrivn oman keskipisteens ympri -- pyritysrutiinit
   pyrittvt *aina* origon ympri -- tai muuten tarpeen
   mukaan). Viimeisen sarakkeen arvot ovat aina kuviossa
   esitetyt, ne eivt vaihtele.
   HUOM! Kuten kuvasta nkyy, vektorit ovat YKSIKKvektoreita,
   eli niiden pituus on yksi. Kannattaa ehk alustaa objekti-
   matriisi aina siten, ett objekti lep esimerkkimme
   lentokoneen mukaisesti xz-tasolla, jolloin se on seuraava:

      1 0 0 0 
      0 1 0 0 
      0 0 1 0 
      0 0 0 1 

   Tllin siis objektin yksikkvektorit ovat i, j ja k eli samat
   kuin avaruuden yksikkvektorit.

   Kaikki 3d-operaatiot ovat matriisitekniikalla matriisien
   kertolaskua tai matriisin ja vektorin kertolaskua.


 3.2 Objektimatriisin liikuttaminen eli transformointi

   3.2.1 Siirtminen eli translaatio

     Objekti siirtminen tapahtuu yksinkertaisesti siirtmll
     objektimatriisin keskipistett, eli kertomalla tllaisella
     matriisilla:

        1 0 0 0 
        0 1 0 0 
        0 0 1 0 
        X Y Z 1 

     miss X, Y ja Z ovat kunkin akselin suuntaan liikuttava
     matka (eri akselien suuntaiset yksikkvektorit jtetn
     tietysti ennalleen).
     HUOM! Siirtminen kannattaa tehd vasta pyrittmisen
     jlkeen, ellet halua objektin pyrivn *uuden* origon, ei
     oman keskipisteens ympri.

   3.2.2 Pyrittminen eli rotaatio

     Objektia pyritettess kerrotaan objektimatriisi nill
     matriiseilla jrjestyksess X, Y, Z tai tarpeen mukaan.

     X-akselin ympri:

          1   0   0  0 
          0  cx  sx  0 
          0 -sx  cx  0 
          0   0   0  1 

     Y-akselin ympri:

         cy   0 -sy  0 
          0   1   0  0 
         sy   0  cy  0 
          0   0   0  1 

     Z-akselin ympri:

         cz  sz   0  0 
        -sz  cz   0  0 
          0   0   1  0 
          0   0   0  1 

     cx, sx, cy, sy, cz ja sz tarkoittavat tietysti ao. akse-
     lien ympri pyritettvien kulmien kosineja ja sinej (KUVIA
     en sentn ripannut otmmatx:sta ;)

     Nit ei tietenkn tarvitse kertoa joka framelle, vaan
     voimme hiukan prekalkuloida (alin vaakarivi ja oikeanpuo-
     leisin pystyrivi pysyvt samoina, joten kytn aluksi
     3x3-matriiseja):

                  1  0  0      cy 0 -sy     cy     0  -sy    
       [X]*[Y] =  0  cx sx  *  0  1  0   =  sx*sy  cx  sx*cy 
                  0 -sx cx     sy 0  cy     cx*sy -sx  cx*cy 

                      cy     0  -sy         cz sz 0 
       [X]*[Y]*[Z] =  sx*sy  cx  sx*cy  *  -sz cz 0 
                      cx*sy -sx  cx*cy      0  0  1 

                      cy*cz          cy*sz          -sy    0 
                      sx*sy*cz-cx*sz sx*sy*sz+cx*cz  sx*cy 0 
                   =  cx*sy*cz+sx*sz cx*sy*sz-sx*cz  cx*cy 0 
                      0              0               0     1 

     Pyritetty ja siirretty objektimatriisi saadaan siis kaavasta

       [O'] = [O]*[X]*[Y]*[Z]*[T].


     Puhuin kappaleen alussa lentokone-esimerkist ja mainitsin,
     ett matriisit ovat ainoa tapa toteuttaa se. Mitenk homma
     toimii? Pyritetn vain pinvastaisessa jrjestyksess:

       [O'] = [O]*[Z]*[Y].

     Nyt lentokone-esimerkki toimii oikein. Toisaalta ohjelmoija
     ei voi tiet, mink akselien suhteen milloinkin
     pyritetn, joten voisi olla viisasta pyritt maailmaa
     Y-akselin ja konetta Z-akselin ympri, eli yleisemmin
     *ensin* jlkimmisten, sitten edellisten kulmien ympri.

   3.2.3 Kamera

     Kameran saa matriisitekniikalla huomattavasti ktevmmin
     mukaan kuin geometrisissa pyrityksiss: ei tarvitse tehd
     muuta kuin kertoa pyritetty ja siirretty objektimatriisi
     kameramatriisilla!

       [O'] = [O']*[C].


 3.3 Pisteen transformointi objektimatriisilla

   Nyt tarvitsee en transformoida kaikki pisteet tll
   saadulla matriisilla, ja objektin liikuttaminen on selv!
   Transformointi voidaan tehd helposti luvun 1.9 esittmll
   tavalla, tosin pisteen koordinaatteja pidetn tllin
   vektorin i:n, j:n ja k:n kertoimina:

     X = X0*a + Y0*e + Z0*i + m
     Y = X0*b + Y0*f + Z0*j + n
     Z = X0*c + Y0*g + Z0*k + o

   (m, n, o ovat objektin keskipisteen koordinaatit)
   Thn tarvitaan siis yhdeksn kertolaskua ja yhdeksn
   yhteenlaskua -- ei todellakaan paha, kun verrataan
   edellisiin 12 kerto- ja 6 yhteenlaskuun, ja tm tapa on
   mys huomattavasti selkempi.

   Pseudoa lapsukaisille (pyritetn pistett matriisitek-
   niikalla x-akselin ympri):

     - xm, om, temp_m, old_om (4,4)-kokoisia liukulukutaulukoita
     - (xp,yp,zp) alkup. piste, (x,y,z) pyritetty piste

     kulma = 0
     old_om = om

     looppaa joka framelle:

       xm[1,1] = cos(kulma)
       xm[1,2] = sin(kulma)
       xm[2,1] = -sin(kulma)
       xm[2,2] = cos(kulma)

       om = old_om

       for a=0 -> 3
         for b=0 -> 3
           temp_m[a,b] = om[a,0]*xm[0,b] + om[a,1]*xm[1,b]
                       + om[a,2]*xm[2,b] + om[3,1]*xm[3,b]

       om = temp_m

       x = xp*om[0,0] + yp*om[1,0] + zp*om[2,0] + om[3,0]
       y = xp*om[0,1] + yp*om[1,1] + zp*om[2,1] + om[3,1]
       z = xp*om[0,2] + yp*om[1,2] + zp*om[2,2] + om[3,2]

       putpixel(x,y,z)

       < kasvata kulmaa >

     l_en_looppaa

   HUOM! Kulmaa ei siis muuteta, koska objektimatriisi om muuttuu
   jatkuvasti, eik alkuperist silytet. Objekti pyrii siis
   tasaisesti, jos kulmaa ei muuteta.

   HUOM2! Kaikille pisteille ei tarvitse toistaa matriisien
   kertolaskua, vaan ne kerrotaan samalla objektimatriisilla eli
   rivit x=xp*.. etc ovat ainoat jotka toistetaan. Loput
   laskutoimitukset tehdn vain kerran / frame.


 3.4 Hierarkiset transformaatiot

   Juu, *tmkin* on otmmatx:sta, enk edes keksinyt parempaa
   suomenkielist nime tuolle otsikolle (englanninkielinen
   vastinehan kuuluu "hierarchical transformations" :I

   Oletko koskaan miettinyt, miten on toteutettu pelit, joissa
   on (vektoripohjaisia) tyylikksti liikkuvia ihmismisi
   objekteja (Tomb Raider, Quake, mechailut, ...)? Homma hoituu
   hierarkisilla transformaatioilla.
     Otetaan esimerkiksi ihmisen ksi. Kun pyritt ktt
   olkapst asti ympri, mys ranne, kmmen ja sormet pyrivt.
   Jos taas pyritt kmment, ksi ei pyri ranteesta ylspin
   mutta sormet pyrivt. Kden osat ovat siis hierarkisesti
   riippuvaisia: kmmen voi antaa komentoja sormille, muttei
   ranteelle, eli se on hierarkisessa jrjestyksess niden
   vliss. Samaan tapaan voidaan jakaa linkitettyyn listaan
   kaikki ruumiinosat.
     Hierarkisessa systeemiss objekti on siis jaettuna osiin,
   ja eri osat ovat riippuvaisia toisista osista. Mutta miten
   tm sitten toteutetaan?
     Jatketaan ksiesimerkill. Ranteen matriisi on [R], kmmenen
   [K] ja sormien [S1], [S2], [S3], [S4] sek [S5], ja ne
   sijaitsevat linkitetyss listassa seuraavasti:

                       [R]
                        
                [K]Ŀ
                             
             [S1] [S2] [S3] [S4] [S5]

   (hieno kuva, vaikka itse sanonkin :)
   Ranteen transformointi, kuten muistamme, tapahtuu nin:

     [R] = [R]*[Xr]*[Yr]*[Zr]*[Tr].

   Kmmen pit siis oman liikkeens lisksi transformoida
   samalla kaavalla,

     [K] = [K]*[Xk]*[Yk]*[Zk]*[Tr]*[R],

   ja sormet kaavalla

     [S?] = [S?]*[Xs?]*[Ys?]*[Zs?]*[Tr]*[K].

   HUOM! Jotta nit kaavoja voi kytt, pit jonossa edellinen
   matriisi olla valmiiksi transformoitu, eli laskemisjrjestys on
   "nokkimisjrjestys": linkitetyss listassa ylhlt alas.


 3.5 Sekalaista matriiseista

   1) Jos kytetn luvun 3.4 kaavaa objektimatriisin
      transformointiin, pyristysvirhe alkaa vhitellen vaikuttaa
      tulokseen vristen objektia. Objektimatriisi kannattaa
      siis tallentaa ja tarkistaa sen arvo tasaisin vliajoin,
      vaikka joka sadannella framella. Liukulukumuuttujilla
      asialla ei ole merkityst.

   2) On kuulemma olemassa mys toisenlainen (nopeampi) tapa
      kertoa kaksi matriisia, mutta en ole saanut siit tarkempia
      tietoja, eli jos joku tiet jotain, saa kertoa :)

   3) Minulle on mainostettu mys hiukan erilaista matriisitek-
      niikkaa. Vittvt sen olevan nopeampi ja ktevmpikin,
      mutta minun selittmni tapa on yksinkertaisempi ymmrt.
      Eli: kytetn 3x3-matriiseja, ja objektien keskipisteiden
      koordinaatit silytetn erillisess taulukossa jolle
      tehdn ikiomat transformointirutiinit. Nyt kameran
      liikkuminen on vaikeampi saada mukaan, mutta objekti pyrii
      aina oman keskipisteens ympri eik tarvitse tallettaa
      vanhaa objektimatriisia joka framelle erikseen.
      4x4-matriiseillakin objekti saadaan pyrimn keskipisteen-
      s ympri, jos kerrottaessa objektimatriisi pyritysmatrii-
      seilla leikitn, ett kyseess ovatkin 3x3-matriisit. Nyt
      koko keskipistett ei edes oteta huomioon, ja sstytn
      monelta vaivalta.
      Mit tekniikkaa kytt, se on oma asiasi ;)




4. 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. Jremmss
 3d-grafiikassa kolmioiden hyty hvi init-osan viedess tehoja
 jopa enemmn kuin loopit vievt, joten nelikulmioiden kytt
 myhemmin on jrkev.


 4.1 Flat-kolmio

   Haluamme piirt seuraavan kolmion:

                   a
                  /|
                /  |   <-ylosa
              /     |
          b /_______|     <- huom
            \        |
              \      |
                \     |  <-alaosa
                  \   |
                    \  |
                      \|
                       c

   Pelkstn tt kuviota katselemalla pitisi saada aikaan
   tydellinen kolmiofilleri. Jos ongelmia (tai laiskuutta :)
   kuitenkin ilmenee, idea on seuraava:
     Interpoloi a.x: niin ett se liukuu a.x:st b.x:n
   (b.y-a.y) askeleessa. Interpoloi a.x: mys niin, ett se
   liukuu a.x:st c.x:n (c.y-a.y) askeleessa. Sitten liiku
   a.y:st b.y:hyn listen joka askelella yhden y-koordinaattiin
   ja interpoloiden skeisi arvoja sek vetelemll vaakaviivoja
   liu'utettavien x-arvojen vliin. Nin olet piirtnyt kolmion
   ylosan. Sitten vain sama homma b.y:st c.y:hyn, niin
   alaosakin piirtyy.

   Voisi olla fiksua mietti ja koodata tm kohta itse, ilman
   seuraavan pseudoptkn apua, jotta idea menisi kunnolla
   kaaliin ja myhemmt interpoloinnit etc selkiytyisivt parem-
   min, joten suosittelen sen yli hyppmist kunnes olet ymmr-
   tnyt interpoloinnin idean (selitetty alla).

   ** 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, eli suomeksi: lineaarinen
   interpolaatio on yhden arvon liu'uttamista tasaisesti toiseen.

   Esim. Interpoloikaamme arvo 0 arvoon 4 seitsemss askeleessa.

     Askel   Arvo

     0       0.00
     1       0.57
     2       1.14
     3       1.71
     4       2.29
     5       2.86
     6       3.43
     7       4.00

   Kuten varmasti huomasitkin, arvo kasvaa 4/7:lla joka askeleel-
   la, eli arvo on funktio

     f(X) = X0 + X*(4/7).

   Yleinen funktio lineaariseen interpolointiin on siis

     f(X) = A + X*((B-A)/steps),

   miss liu'utaan A:sta B:hen steps mrss askeleita, ja f'(X)
   (f(X):n derivaatta) eli f(X):n kasvunopeus on (B-A)/steps.

   Eli jos meill on looppi

     for (y=10 -> 20)
       x=f(y)
       piste(x,y)
     endfor

   eli

     for (y=10 -> 20)
       x=a+x((b-a)/steps)
       piste(x,y)
     endfor,

   se voidaan esitt mys muodossa

     x=a
     for (y=10 -> 20)
       piste(x,y)
       x=x+f'
     endfor

   eli

     x=a
     for (y=10 -> 20)
       piste(x,y)
       x=x+(b-a)/steps
     endfor.

   Nin olemme npprsti optimoineet yhden addin, yhden
   kertolaskun, yhden jakolaskun ja yhden miinuslaskun yhteen
   ainoaan pluslaskuun (koska (b-a)/steps on vakio, sen voi
   laskea etukteen l. prekalkuloida).

   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 (huomattavaa on mys, ett mikli klippauk-
   set ovat gouraud- tai texture-filleriss, on muistettava pi-
   vitt u:ta ja v:t tai gouraudin vriarvoja oikeissa paikoissa):

   ** 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.

   4.1.1 Fixed point

     Tm oli jotenkin luonnollisinta laittaa interpoloinnin
     jlkeen, vai mit mielt olette?-)
       Aina ei ole jrkev kytt liukulukuja. Tllin,
     esimerkiksi tarvittaessa jakolaskuja, kannattaa joskus ottaa
     fixed point, jossa esim. 32-bittist lukua ksitelln niin,
     ett 16 ylint bitti ovat luvun kokonaislukuosa ja alimmat
     16 bitti desimaaliosa. Mitenk tm onnistuu? Helppoa:
     ensin siirretn 32-bittiseen muuttujaan jaettava, sitten
     kerrotaan se 2^16:lla eli siirretn jaettava luvun 16
     ylimmksi bitiksi, ja lopuksi jaetaan jakajalla.
     Tuloksesta saadaan 2^16 kertaa liian suuri, mutta sikapaljon
     tarkempi kuin normaalilla kokonaislukujen jakolaskulla,
     juuri niiden 16 desimaalibitin ansiosta.
       Nyt sitten operoidaan tll luvulla, ja muistetaan kertoa
     kaikki muutkin samassa yhteydess tarvittavat luvut
     2^16:lla, ja kun ollaan valmiita ja tulos pitisi esitt
     (esim. polygonirutiinissamme pikseli ruutuun), muistetaan
     viel jakaakin, ylltys ylltys, 2^16:lla (tuosta 2^16:sta
     pit kyll tehd makro -- shift-f1 ;)
       Fixed pointtia kyttv pseudokoodiesimerkki lytyy alta,
    kappaleesta 4.2 (eli IHAN alta :)


 4.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)
     endfor

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

   Thnkin voisi ottaa derivaatan ksitteen, eli c1+((a-x1)*dc):n
   derivaatta eli kasvunopeus on dc. Nin psemme eroon kaikista
   kertolaskuista, ja saamme gouraudin interpolaatiosan hilpesti
   yhteen addiin:

     c1=c1*65536 ; HUOM!
     for (a=x1 -> x2)
       putpixel(a,y,c1/65536) ; HUOM!
       c1=c1+dc
     endfor

   Itse asiassa onkin muuten epselvemp miettikn koko
   c1+((a-x1)*dc)-ksitett, sill tuo on varsin selv, tuttu
   jo edellisest interpolointi-esimerkist, jossa interpoloitiin
   arvo 0 arvoksi 4 seitsemss askeleessa. Tuo looppi siis
   liu'uttaa arvon c1 arvoksi c2 (x2-x1) askeleessa. Voiko tt
   en helpommaksi tehd? ;)

   Nyt lienee pikainen sana optimoinnista paikallaan, jotta kaikki
   psevt hieman sen makuun :) Assya osaat tietysti, mill sin
   muuten luulet nopeaa vektorigrafiikkaa koodaavasi?-)
     Kuten huomaatte, tuossa yllolevassa esimerkiss joudutaan
   jakamaan 65536:lla, ja jakolasku on tunnetusti varsin hidas
   operaatio jos sit tehdn paljon (tosin esim. djgpp optaa 2:n
   potensseilla jakamiset sareiksi). Nopeinta jlke saa aikaan
   kyttmll hyvksi iki-ihanaa carry-lippua:

   Meill on kaksi 16-pittist muuttujaa, rekisteri if you wish.
   (Vaihtoehtoisesti voi olla yksi 8-bittinen ja toinen 16-bittinen,
   jos haluamme sst rekistereit ja tyyty 8 bitin desimaali-
   osaan.)

     < dx = c1:n desimaaliosa, eli 32-bittisen fixedpoint-luvun 16
     alinta bitti, bx = c1:n kokonaislukuosa, yli ylimmt piltit. >

   loopissa:
     add dx,[adderin_desimaali_osuus]
     adc bx,[adderin_kokonaisluku_osuus]

   Ai miksik noin? Kun ylempi ksky pyrytt dx:n ympri, carry
   -lippu menee plle, ja dx:n arvoksi j yhteenlaskun alimmat
   16 bitti. adc-kskyss listn bx:n kokonaislukuosa ja yksi
   ylimrist, jos desimaaliosuus ylitti luvun 2^16-1 eli 0.999...
   (eli jos siis carry-lippu on pll).
   Nin bx:ss on sitten pelkk kokonaislukuosuus, joten emme joudu
   edes shiftaamaan.
     Toinen vaihtoehto olisi .8-fixedpointilla toteutus:

     < ax=16-bittinen fixedpoint-luku  (=alkuperinen c1*256) >

   loopissa:
     mov [ruutu+ruutupos],ah ;ah = kokonaislukuosa.
     add ax,[fixed_inkkaaaja]

   Nin 256:lla jakaminen ky ihan ilmaiseksi (ah:ssa on ax:n 8
   ylint bitti).

   Tt ideaa voi laajennella hyvin, hyvin paljon, esimerkiksi niin,
   ett yhdess rekisteriss interpoloidaan useampaa arvoa etc.

  Optimointivinkki: gouraudissa vrideltat ja texturessa (u,v)
  -koordinaattien deltat silyvt vakioina, joten niit ei tarvitse
  laskea kuin kerran / poly. Toki kannattaa mietti, mist sen
  deltan ottaa ;)


 4.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).
     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 -- tosin ilman pers-
   pektiivikorjausta -- 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 ;)




5. 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.


 5.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.


 5.2 Z-buffer

   (Kappaleen kirjoitti uusiksi Tapio Vuorinen, ja hnen
   tekstins muokkasi luettavaksi Ica/2.)

   Z-bufferointi on helpoin (mutta ei todellakaan nopein!) tapa
   tehd pllekkin menevi objekteja. Toisin kuin esim. BSP,
   Z-buffer ei vaadi polygonien pilkkomista osiin niiden leika-
   tessa. BSP on kyllkin aivan omaa luokkaansa muuttumattomien
   objektien tms. tekoon.

   Kuten nimikin kertoo, posassa on puskuri (taulukko), johon
   talletetaan objektin pisteiden Z- ja vriarvoja. Taulukon
   koon tytyy olla sama kuin ruudun pikselimrn, esim. MCGA
   -tilassa 64000 vhintn 16-bittist alkiota (hirve sana)
   sek jokaiselle alkiolle listavu vriarvoa varten.
   Muistia kuluu siis rmodessa 192000 tavua ja pmodessa 256000
   tavua.

   Z-buffer on siis tmn nkinen C:ll:

     typedef struct
     {
       short z;
       unsigned char color;
     } zbuftype;
     zbuftype zbuf[64000];

   Ennen kuin piirrmme ruudulle mitn, tytyy meidn asettaa
   Z-buffer-taulukkomme tyteen sen suurinta mahdollista arvoa
   (16-bittisill muuttujilla 32767). Sitten polygoneja piirt-
   essmme interpoloimme X:n (ja esim. gouraudissa vrin)
   lisksi mys Z:aa. Vaakaviivanpiirtorutiinissa tarkistamme
   jokaisella pikselill onko interpoloimamme Z *pienempi* kuin
   Z-bufferissa tll kohdalla oleva Z-arvo. Jos on, asetamme
   interpoloimamme Z-arvon bufferiin. Jos taas ei, siirrymme
   suoraan seuraavaan pikseliin.
   Tm tarkistus hidastaa rutiinia jrjettmsti, mutta onneksi
   Z:aa voi interpoloida aivan samoin kuten X:n tai vrin: vain
   yksi ADD per pikseli.
   Lopuksi, kun kaikkien nkyvien polygonien kaikki pisteet on
   kytetty Z-bufferin kautta, piirretn Z-bufferissa olevat
   pisteet ruudulle.

   Jos kytt fixed-point -tekniikkaa, muista, ett Z-bufferiin
   kannattaa tallettaa vain kokonaislukuosa; muuten muistintarve
   kasvaa nopeasti kaksinkertaiseksi (esim. C: short -> long),
   koska tarvitaan arvoalueeltaan suurempia muuttujia.

   *Lyhyt* pseudo-ptk h-linest:

     z = z1
     for x=x1 -> x2
       if (z < zbuffer[y*320+x])
         zbuffer[y*320+x] = z
       endif
       < interpoloidaan z:aa >
       z = z + kz
     endfor

   Optimoinnit voi sitten jokainen tehd itse (eli lhinn siir-
   tymisen S-bufferiin ;).


 5.3 BSP-puu

   BSP-puu on saanut mainetta erittin vaikeana mutta
   erinomaisena sorttaustapana. Molemmat mritelmt ovat
   oikeita: vaikka valmiilla rutiineilla voi saada erittin
   nopeaa jlke, 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.


   5.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 ;)


   5.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 ;)


   5.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. Henkilkohtaisesti suosittelenkin
        S-bufferia.


 5.4 S-buffer

   S-buffer eli segmented buffer on Hot Wax Softwaren Paul
   Nettlen kehittm Z-bufferin paranneltu -- ja *huomattavasti*
   nopeampi (mies itse kehuu, ett S-buffer pihitt nopeudessa
   jopa hardware-Z-bufferin :O -- versio, jossa ei pidet kirjaa
   pisteist vaan vaakaviivoista. Tll tavalla sstetn
   huomattavasti laskutoimituksia; suurimmassa osassa horizline-
   kutsuista ei tarvitse vertailla kuin ptykoordinaatteja.
     S-bufferissa on siis 3d-taulukko (tai linkitetty lista, jota
   Nettle itse suosittelee jostain ksittmttmst syyst),
   johon sijoitetaan jokaisen hline-kutsun tiedot, vertaillaan
   niit toisiinsa, toimitaan asianmukaisesti ja lopulta piirre-
   tn nkyviss olevat viivat (tai osat).
   Itse kytn seuraavanlaista taulukkoa (texturemappaukseen
   tarkoitettu, pmode):

     typedef struct
     {
       short xb,xe,zb,ze;         // x_begin, x_end, ...
       byte ub,ue,vb,ve;
       long kz,ku,kv;             // kz = (ze-zb)*65536/(xe-xb) jne
       unsigned char used;        // jos 0 -> ei kytss
     } sbuf_t;
     sbuftype sbuf[200][100];     // 200 rivi 320x200-tilassa,
                                  //  max. 100 segmentti / rivi

   HUOM! Segmentit ovat taulukossa x-koordinaattien suuruus-
   jrjestyksess vasemmalta oikealle.

   Tllainen taulukko vie siis muistia rmodessa 200*100*25=500000,
   pmodessa alignoinnista johtuen 200*100*28=560000 tavua, ja mit
   monimutkaisempi maailma on kyseess, sit suuremmaksi pit
   maksimisegmenttimr kasvattaa.

   S-buffer-polygonirutiinille pit "normaalista" rutiinista
   poiketen (samoin kuin Z-bufferissa) ilmoittaa mys pisteiden
   z-koordinaatit (x:ien ja y:iden pit silti olla jo 2d-nytlle
   transformoituja). Outer loopissa interpoloidaan sitten muiden
   lisksi mys z-koordinaatteja, ja ilmoitetaan ne S-buffer-
   hlinellekin. S-buffer-hline ei varsinaisesti piirr mitn,
   vaan lis vain uuden rivin S-bufferiin, ja vasta kun S-buffer
   on valmis, piirretn koko roska kerralla nytlle.

   Ptk polyrutiinista:

     for y=y1 -> y2
       sbuf_hline(xb,xe,y,zb,ze,col)
       xb=xb+dx1
       yb=yb+dx2
       zb=zb+dz1
       ze=ze+dz2
     endif

   HUOM! Jos polyrutiinille ilmoitettavat koordinaatit ovat
   pisteen 3d-koordinaatit, pit muistaa transformoida x ja y
   2d-koordinaateiksi ennen looppia!

   Sbuf_hline:

     Klippaa ja tee muut normaalit initialisoinnit (klippaus
     vasta dz:n, du:n ja dv:n laskemisen jlkeen, of coz)
     for i=0 -> 99
       if not sbuf[y][i].used
         lis hline suoraan bufferiin ja merkitse se varatuksi
         poistu
       Tee muut vertailut
     endfor

   Ai mik "tee muut vertailut"? Nooh: samalla rivill olevat
   horizlinet voivat sijaita monin eri tavoin toistensa suhteen,
   joten niit pit vertailla toisiinsa ja toimia tulosten
   mukaisesti.
   Lydn itse Nettlest poiketen vain kuusi tapaa, miten viivat
   voivat sijaita toistensa suhteen (vanha viiva = v, uusi = u):

   (1)           vvvvvvvv
        uuuuuuuu

   (2)  vvvvvvvv
                 uuuuuuuu

   (3)  vvvvvvvv
           uuuuuuuuuuu

   (4)        vvvvvvvv
        uuuuuuuuuuu

   (5)  vvvvvvvv
       uuuuuuuuuuu

   (6) vvvvvvvvvvv
        uuuuuuuu

   Nill tavoilla voidaan esitt kaikki viivojen erilaiset
   sijainnit, mukaanlukien mys erikoistapaukset (kuten yhden
   pikselin mittaiset viivat ja tsmlleen samoissa (x,y)
   -koordinaateissa olevat viivat).

   Kohdan 1 viiva listn vain suoraan puskuriin *ennen* oikealla
   puolella olevaa viivaa ja keskeytetn hlinen ajo (kaikki
   viivat ovat sen jommalla kummalla puolella, koska vasemman-
   puoleiset viivat on jo tarkistettu -- nehn ovat S-bufferissa
   jrjestyksess vasen -> oikea).
   Kohdan 2 tapauksessa hyptn tutkimaan seuraavaa vanhaa
   viivaa, jos sellainen lytyy. Jos ei, listn viiva puskurin
   viimeiseksi.
   Kohdassa 3 viiva katkaistaan vanhan viivan xe:n kohdalta
   (muista interpoloida z:aa, u:ta ja v:t oikein), vertaillaan
   vasemmanpuoleisen ptkn ja vanhan viivan z-koordinaatteja,
   listn vasemmanpuoleinen ptk tai sen osa tarvittaessa
   puskuriin, ja jatketaan oikeanpuoleisen ptkn ja seuraavan
   vanhan viivan kanssa, jos sellainen lytyy. Jos ei, ptk
   listn puskurin viimeiseksi.
   4. kohdan viiva katkaistaan vanhan viivan xb:n kohdalta,
   listn vasemmanpuoleinen ptk puskuriin ennen vanhaa
   viivaa, ja toimitaan oikeanpuoleisen ptkn kanssa samoin kuin
   yll vasemmanpuoleisen kanssa.
   5. kohdassa katkaistaan viiva sek oikealta ett vasemmalta
   puolelta ja toimitaan muuten kuin 3. ja 4. kohdassa.
   6. kohdassa menetelln suoraan kuten kohdan 3 vasemmanpuo-
   leisen ptkn kanssa.

   Kun kaikki polygonit on piirretty, kydn S-buffer lpi
   ylhlt alas, vasemmalta oikealle, ja piirretn viivojen-
   ptkt normaalin hlinen tapaan. Piece of cake ;)

   ... [epmrist ruikutusta ja vikin] ...

   No hyv on, kun pyysitte niin kauniisti: tss S-bufferin
   hallintaan tyhjennys- ja piirtmisrutiinit. Kyttvt muuten
   ylempn esitelty sbuf-taulukkoa.

     - tmap on 256x256-kokoinen bittikartta

     funktio flip_sbuf

       integer i,j,k  ; apumuuttujia
       integer du,dv  ; hline varten

       for i=0 -> 199

         j = 0
         while sbuf[i][j].used<>0 sek j<100
           du = 0
           dv = 0
           for k=sbuf[i][j].xb -> sbuf[i][j].xe
             putpixel(k,i,tmap[sbuf[i][j].ub+du/65536+
                               (sbuf[i][j].vb+dv/65536)*256])
             du = du + sbuf[i][j].ku
             dv = dv + sbuf[i][j].kv
           endfor
           j = j + 1
         endwhile
       endfor
     endf


     funktio clear_sbuf()

       integer i,j

        for i=0 -> 199
          j = 0
          while sbuf[i][j].used<>0 sek j<200
            sbuf[i][j].used=0
            j = j + 1
          endwhile
        endfor
      endf




6. Varjostustapoja
------------------


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


 6.1 Flat-sheidaus

   6.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.


   6.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 tapa (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. (miten
     vektorin pituudeksi varmistetaan yksi, ks. luku 1.3)
       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 init-osassa:

       - 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 (ei muuten ole jos
        kytetn hidden face removalia)
          color = 0
        endjos
         palauta color
      endf
 

 6.2 Gouraud-sheidaus

   "Mits me jollain Flatilla, Gouraudia kehiin!" huutaa kansa.
   "Gou-raud, Gou-raud!!" kiljuvat 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.

   6.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.

   6.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. Luulin, ett tm on
     (c) Kaj Bjrklund, mist miehen itsens kommentti:
     "hehee, tuo calcnor on suoraan ripattu jostain sorsasta :)"
     No juuh, crediitit calcnor-funktiosta kuuluvat siis Jeroen
     Bouwensille. Eip siin sinns ole mitn erikoista, olin
     vntnyt tsmlleen samanlaisen funktion kuin tuo calcnor
     on ennen kuin Kaj heitti minulle koko roskan, siis CalcNormals
     -funktion, jossa oli tuo calcnorkin.

     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 1 eli tehdn niist yksikkvektoreita >
           normal[i].x=cx/len
           normal[i].y=cy/len
           normal[i].z=cz/len
         endif

       endfor

     endf

     Ja kytt siis tapahtuu samaan tapaan kuin Lambert flatissa.


 6.3 Phong-sheidaus

   6.3.1 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.
       Phong illuminating saadaan aikaan kyttmll seuraavaa
     kaavaa:

     color = ambient + (cos x) * diffuse + (cos x)^n * specular,

     miss ambient on polyn vri kun siihen ei osu ollenkaan
     valoa eli minimivri, diffuse polyn alkuperinen vri, ja
     specular polyn vri kun siihen tuleva valo on kohtisuorassa
     pintaa vastaan eli vrin maksimiarvo.
     x on valovektorin ja verteksin normaalivektorin vrinen
     kulma, ja se saa vaihdella vain vlill -90..90 astetta eli
     -pi/2..pi/2 radiaania. Miksei vlill 0..360 astetta? Siksi,
     ett mentess yli 90 asteen valoa ei osu polyyn ollenkaan,
     jolloin pit kytt minimiarvoa ambient. Siisp tarkistuk-
     set, ja jos kulma ei ole vaaditulla vlill, annetaan sille
     arvo -90 tai 90 astetta, eli kosinille arvo nolla, eli
     vrille arvo ambient.
     n on objektin hohtavuus eli highlightin ominaisuus, joille-
     kuille varmaan tuttu rendausohjelmista. Kokeilemalla selvi
     kuhunkin tilanteeseen sopiva arvo.

     Pseudoa jlleen:

     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

     Tss esimerkiss punaisen vrin (r) minimiarvo on siis
     viisi, arvo normaalivalaistuksella 60, maksimiarvo 150,
     hohtavuus nelj ja kulma vaihtelee vlill 0..90 astetta
     (vrill 255 kulma on nolla, vrill nolla se on 90
     astetta, eli kirkkain arvo tulee paletin loppuun).

   6.3.2 Env-mappaus

     Monet sekoittavat oikean phongin env-mappaukseen, mutta ne
     ovat kaksi eri asiaa. Env-mappauksessa kytetn bittikarttaa
     (environment map) josta saadaan pikseleille vriarvot. Sen
     avulla voi tehd erilaisia kuvioita varjostukseen, jolloin
     pinnat alkavat nytt vaikkapa metallisilta. Tmn dokumen-
     tinkin mukana tulee yksi bittikartta, joka kelpaa melko hyvin
     env-mappaukseen. Ai mistk niit saa? Kaverinkaverin kuvan-
     ksittelyohjelmilla 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 -- pseudoesimerkki alla.

       - LS on valonlhdevektori
       - N[0..2] ovat kolmion verteksien normaalivektorit
       - cx1, cy1 etc ovat koordinaatit env-mappiin
       - a on apumuuttuja
       - env-map on 256x256-kokoinen bittikartta (metal.pcx)

       if ( LS.k <= 0 ) ; kytetn systeemi suoraan

         cx1 = env_crd( N[0].i - LS.i )
         cy1 = env_crd( N[0].j - LS.j )
         cx2 = env_crd( N[1].i - LS.i )
         cy2 = env_crd( N[1].j - LS.j )
         cx3 = env_crd( N[2].i - LS.i )
         cy3 = env_crd( N[2].j - LS.j )

       else

         a = N[0].i + LS.i ; yhteenlasku - LS.i vastakkainen kuin yll
         if (a<0) a = a + 1 ; vastakkaiselle puolelle
         else a = a - 1
         cx1 = env_crd( a ) ; konvertointi
         a = N[0].j + LS.j
         if (a<0) a = a + 1
         else a = a - 1
         cy1 = env_crd ( a )

         a = N[1].i + LS.i
         if (a<0) a = a + 1
         else a = a - 1
         cx2 = env_crd( a )
         a = N[1].j + LS.j
         if (a<0) a = a + 1
         else a = a - 1
         cy2 = env_crd ( a )

         a = N[2].i + LS.i
         if (a<0) a = a + 1
         else a = a - 1
         cx3 = env_crd( a )
         a = N[2].j + LS.j
         if (a<0) a = a + 1
         else a = a - 1
         cy3 = env_crd ( a )

       endif

       texture( x1, y1, x2, y2, x3, y3, cx1, cy1, cx2, cy2, cx3, cy3 )


     funktio env_crd ( float arvo )

       - a apumuuttuja

       a = arvo * 127 + 128

       palauta a

     Funktio env_crd konvertoi siis normaalin kertoimen (vlill
     -1..1) bittikartan koordinaateiksi (0..256, keskell kirkkain).

     Pseudon alussa tutkittiin, onko valonlhteen k:n kerroin
     positiivinen vai negatiivinen. Tm siksi, koska menetelm ei
     pde molemmille yht hyvin, vaan positiiviset arvot vaativat
     hiukan stmist. Negatiivisilla k:n arvoilla saadaan siis
     pseudon osoittamalla tavalla laskettua env-mapin koordinaatit:
       Vhennetn normaalien i:n ja j:n kertoimista valovektorin i:n
     ja j:n kertoimet ennen kuin transformoidaan niit bittikartan
     keskikoordinaatteihin. Ai mihink k:n kerroin? Ei mihinkn,
     mutta koska niden vektorien pituuden pit olla yksi, voidaan
     vhentmll i:n ja j:n arvoja saada k:n kertoimelle painoarvoa:
     esimerkiksi jos i:n ja j:n kerroin on 0.5, k:n kertoimelle j
     arvoa 0.7 (vektorin pituus 0.5^2 + 0.5^2 + 0.7^2 = 1).
     *Tm*systeemi*ei*tepsi*valonlhteisiin*joiden*k:n*kerroin*on*
     *positiivinen*, vaan ne vaativat seuraavan:
     k:n kerroin on positiivinen ja vektori (-LS.i,-LS.j,-LS.k) on
     valonlhdevektoria vastakkainen. Jos huijataan rutiini luule-
     maan valonlhdevektorin olevankin toisella puolella, saadaan
     tarkalleen pinvastainen tulos kuin olisi tarkoitus.
     Miksi nin? Siksi, ett tmn vastakkaissuuntaisen vektorin
     k:n kerroin on luonnollisesti negatiivinen, ja voimme kytt
     normaalia systeemi. Pinvastaisuus taas hvi, kun siirretn
     keskell bittikarttaa olevat arvot laidoille ja laidoilla
     olevat keskelle ->  vot: tuloksena on haluttu, alkuperinen
     valonlhdevektori!

     Tytyy kyll rintaa kumistellen kertoa, ett tm valonlhde-
     systeemi on kokonaan itse keksimni :O

   6.3.3 Voltaire/OTM:n Phong

     Phong-sheidauksen idea on lyt tarkka vriarvo jokaiselle
     pikselille. Tm onnistuu interpoloimalla polygonirutiinissa
     verteksinormaaleja, eli etsimll jokaiselle pikselille oman
     normaalivektorinsa, ja mrittmll tmn sek valovektorin
     vlisen kulman kosini skalaaritulon avulla (ks. 1.5). Koska
     interpoloidut normaalit eivt ole vlttmtt yksikkvekto-
     reita, tarvitaan jokaiselle pikselille nelijuuri -- uh-oh,
     realtimen ei taida onnistua ;)
     Tm ainoa oikea phong-sheidaus on upean nkinen, mutta
     sit ei ole jrkev kytt kuin still-systeemeiss eli
     esimerkiksi raytrace-enginess; lislaskettavaa tulee joka
     pikselille pistetulon verran: nelj kertolaskua, kaksi
     yhteenlaskua, yksi jakolasku ja nelijuuri -- lhemms 300
     pentium-kelloa! Toki tt voi optimoida, mutta kolme
     kertolaskua ja yksi jakolasku j joka tapauksessa, mik on
     meidn tarkoituksiimme aivan liikaa.

     Onneksemme Zach Mortensen on keksinyt realtimeen paremmin
     sopivan tekniikan: eips interpoloidakaan normaaleja, vaan
     otetaan ennen polygonirutiinin kutsumista jokaisen verteksi-
     normaalin ja valovektorin vlinen kulma, ja interpoloidaan-
     kin niit (kulmathan muuttuvat lineaarisesti)! Heihei, pis-
     tetulo, nyt ei tarvita jokaiselle pikselille kuin kosini-
     taulukkoon vilkaisu, mik on miltei yht nopeata kuin
     gouraud-shadettaminen! Kun kosinitaulukon suurimmaksi
     arvoksi mritelln viel 255 ja pienimmksi nolla, ei
     saatuja arvoja tarvitse edes fiksailla vaan ne ilmoittavat
     suoraan pisteen vriarvon. Yksinkertaista, mutta helppoa :)
       Tsakhin tekniikassa on vain yksi ongelma: kun kulmat
     kahdella tai useammalla verteksill ovat samat, koko niden
     verteksien vlinen matka on samaa vri -- lineaarisen
     interpoloinnin haitta. Ttkn ei juuri huomaa objektien
     liikkuessa, ja onhan tm joka tapauksessa gouraudia paljon
     hienompi sheidausmenetelm, kunhan viel kytetn phong
     illuminating -menetelm palettia generoidessa ;)




7. Muuta mukavaa
----------------


 7.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 (esimerkiksi 3DStudiolla tehdyt objektit ovat
   oikeanlaisia).
     Onko polygoni vrin pin, on helppo ptt tmn pseudo-
   koodin avulla ((c) Bas van Gaalen / Jeroen Bouwens, don't know
   / remember):

     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, eli kameravektorin ja
   tason normaalivektorin vlisen kulman kosini on positiivinen
   eli -90 < kulma < 90 astetta, polygoni piirretn, muuten
   luonnollisesti ei.


 7.2 Kamera and how did I do it

   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 saman-
   aikaisesti. Sellainen nytt *melko* naurettavalta ;)
   Matriisitekniikassahan tt tapaa kytetnkin, mutta paljon
   helpommin: kertomalla objektimatriisi kameramatriisilla on
   kameran paikkaa muutettu.


 7.3 Objektin pyritysnopeuden suhteuttaminen koneen
     nopeuteen

   Tss esitelln vain yksi tekniikka tmn tekemiseksi. Jos
   se ei jostain tysin ksittmttmst syyst kelpaa neidille,
   hn on vain hyv ja lukee Midaksen dokumentit.
   Otsikon ilmoittamaa tekniikkaa kutsutaan hienosti frameskipiksi,
   skriinsynkkaukseksi etc, mutta tss siit kytetn vhemmn
   tyylikst nime "harppominen".
     Harppomisen idea on siis pyritt opjekteja kaikilla
   koneilla samaa vauhtia. Perusidea on hyvin yksinkertainen:
   kun objektit pivitetn yhden kerran, on piirretty yksi frame.
   386:lla framen piirtmiseen menee huomattavasti enemmn aikaa
   kuin penttijumilla, mist seuraa, ett 386:lla objektin tulee
   harppoa enemmn kuin pentiumilla eli piirt vhemmn frameja
   aikayksikss. Jos siis oletamme, ett objektin pyrimiskulman
   pitisi kasvaa 9 astetta per sekunti, toimimme seuraavasti:

     Frame   Kulma 386:lla    Kulma penttijumilla
     --------------------------------------------
       1       0                0
       2       3                1
       3       6                2
       4       9                3
       5       -                4
       6       -                5
       7       -                6
       8       -                7
       9       -                8
      10       -                9

   Miksik nin? Siksi, ett pentium joutaa piirtmn 10 freimi
   per sekunti, kun kolkasikuutonen pystyy vain neljn. Nin
   opjektisi pyrii kaikilla koneilla samaa vauhtia, tosin penalla
   huomattavasti sulavammin kuin 386:lla.
   Pseudon koodaaminen on sitten hauskaa. Kntj ei ainakaan
   valita ;)

     falku=lueaika;
     toista
       <laske>
       <piirr>
       <flippaa>
       <do vatever jyy vant>
       floppu=lueaika
       kulma=kulma+nopeus*(floppu-falku)
       falku=floppu;
     kunnes 1=2

   Kaikki muuttujat ovat tietysti reaali(tai fixed)-kamaa. Niin, ja
   lueaika on sitten *tarkka*, eli mikn avuton 18.2Hz:n resoluu-
   tiolla varustettu perustaimeri ei kelpaa.

   ---
   KB> (niin ilkka, selvenn (lue kirjoita uudestaan) tuo koko ptk :)

   Muutin vain kieliasua hiukan, korjasin kielioppivirheet ja
   lissin muutaman capitaaliletterin :)
   ---

   Didn't get the point? Tmn kappaleen kirjoitti alunperin Kaj
   Bjrklund :)




EOF
