[ruikutusmoodi on]
ATTENTION! PLEASE READ THE LICENSE AGREEMENT BEFORE EVEN GLANCING AT
THE ACTUAL TEXT! BY USING THIS TUTORIAL IN ANY WAY (EVEN AS WC PAPER)
YOU AUTOMATICALLY AGREE ON EVERY SINGLE TERM IN THE LICENSE AGREEMENT!
[ruikutusmoodi off]

[selitysmoodi on]
Ja sitten vakavampiin aiheisiin: tss versiossa olen muuttanut tekstin
rakennetta meleekosen rankasti ja jttnyt pois kaikenlaista turhaksi
katsomaani. Nyt on sana vapaa: pleeze kommentoikaa aktiivisesti uutta
kappalejakoa ja sisllyst -- jotain takaisin, jotain selvemmksi?
Samoin olisi mukava kuulla uusia aiheideoita ihan yksinkertaisista
perusasioista tai mist vain 3d:hen liittyen. Kirjoitan mink osaan,
ja vaikka en osaisi, yritn ottaa selv ajan riittess. Viel toi-
von, ett otatte kantaa thn asiaan: olen ajatellut alkaa kirjoittaa
dokumenttia html-pohjaisena. Nin sit olisi mukavampi lukea prin-
tattuna, ja toisaalta linkit helpottaisivat online-lukemista. Teksti
konvertoitaisiin luonnollisesti mys dos-versioksi, tosin tyyli kr-
sisi tllin varmasti.
[selitysmoodi off]



      3DICA.TXT v2.19b (C) Ica /Hubris 1996,1997
      ---------

        - 4172 lines of pure sh...er, 3d coding power!




Sisllys
--------

   * = Aiheesta mukana C-esimerkkiohjelma

   0             Yleist lpin

     0.0           LICENSE AGREEMENT
      0.0.1         DISCLAIMER
      0.0.2         LIMITED WARRANTY
      0.0.3         Seriously
     0.1           Kuka on Ica?
     0.2           Kuka ihmeen erkki se toinen nrtti on?
     0.3           3dica carewarea -- mit se tarkoittaa?
     0.4           Kenelle tm dokumentti on suunnattu?
     0.5           Mist saan dokumentin uusimman version?
     0.6           Kiitokset avusta
     0.7           What's new?
     0.8           Tulossa
     0.9           Gr33tZ
     0.10          3d-termist yms.
     0.11          Frequently Asked Questions
     0.12          Thanks for support
     0.13          Lhdeteoksia & -dokumentteja


   1             Vektori- ja matriisialgebraa

     1.1           Vektorit
      1.1.1         Yleist
      1.1.2         Vektorin yhtl
      1.1.3         Vektorin pituus *
      1.1.4         Vektorien yhteenlasku
      1.1.5         Skalaaritulo *
      1.1.6         Vektoriprojektio
      1.1.7         Vektoritulo *
      1.1.8         Skalaarikolmitulo
     1.2           Matriisit
      1.2.1         Yleist
      1.2.2         Matriisien laskutoimitukset
       1.2.2.1       Yhteenlasku
       1.2.2.2       Kertominen skalaarilla *
       1.2.2.3       Kertolasku *
        1.2.2.3.1     Erikoistapaus: vektorin ja matriisin tulo *
       1.2.2.4       Transponointi


   2             3D-geometriaa

     2.1           2D- ja 3D-maailman yhteys *
     2.2           Matriisitekniikka -- johdanto
     2.3           Objektimatriisin pyrittminen *
     2.4           Kamera *
      2.4.1         Kameramatriisin generointi vektorin avulla
     2.5           Pisteen transformointi objektimatriisilla *
     2.6           Hierarkiset transformaatiot


   3             Erilaisia polygonifillej

     3.1           Flat-kolmio *
      3.1.1         Fixed point *
     3.2           Gouraud-kolmio *
     3.3           Texture-kolmio *
      3.3.1         Perspektiivikorjauksen periaate *
      3.3.2         Texturen fittaus objektiin *
     3.4           Texture + varjostus *
     3.5           Convex-polygonien tekniikka

   4             Sorttaustapoja

     4.1           Z-sorttaus *
     4.2           Z-buffer *
     4.3           BSP-puu
      4.3.1         The main idea
      4.3.2         Tarvittavat kaavat
      4.3.3         Vinkkej
     4.4           S-buffer


   5             Varjostustapoja

     5.1           Flat-sheidaus
      5.1.1         Z-Flat
      5.1.2         Lambert Flat *
     5.2           Gouraud-sheidaus
      5.2.1         Z-Gouraud
      5.2.2         "Oikea" Gouraud *
     5.3           Phong-sheidaus
      5.3.1         Phong Illumination *
      5.3.2         Environment-mappaus *
      5.3.3         Aito Phong *
     5.4           Ican ikioma tekniikka
     5.5           Valonlhteiden ksittelyst
      5.5.1         Vapaasti liikkuvat valonlhteet
      5.5.2         Spotlitet
      5.5.3         Valon himmeneminen


   6             Hidden face removal

      6.1          Backface culling *
      6.2          View cone *
       6.2.1        Polygonien 3d-klippaus
      6.3          Portalit


   7             Muuta mukavaa

      7.1          Frameskip
      7.2          Assemby-optimointia
      7.3          Paletinkvantisointi
       7.3.1        Mit se oikeastaan on?
       7.3.2        Abstrakti lhestymistapa
       7.3.3        Teknisempi lhestymistapa




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


 0.0 LICENSE AGREEMENT

   0.0.1 DISCLAIMER

     3DICA PROGRAMMING TUTORIAL IS SUPPLIED AS-IS. THE AUTHOR
     DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING,
     WITHOUT LIMITATION, THE WARRANTIES OF MERCHANTABILITY AND
     OF FITNESS FOR ANY PURPOSE. THE AUTHOR ASSUMES NO LIABILITY
     FOR DAMAGE TO ANY LIVING / DEAD CREATURE OR EQUIPMENT,
     DIRECT OR CONSEQUENTIAL, WHICH MAY RESULT FROM THE USE OF
     THE 3DICA PROGRAMMING TUTORIAL.

   0.0.2 LIMITED WARRANTY

     SATISFACTION GUARANTEE. If you are dissatisfied with this
     product you downloaded from some F..ine BBS or the Internet,
     you may delete it at any time up to 30 days after download
     and you will get your hard disk space, occupied by the
     product, back. The space will be based on the space that
     was occupied during the installation, with the cost of some
     bad sectors, viruses and neat hidden files excluded. You
     must contact the author before deleting the product; I want
     to have a good laugh (you are having one right now, I hope 8-)

   (Pohjana kytetty Action Supercrossin readme.txt:t)

   0.0.3 Seriously

     3DICA-ohjelmointiopas on FREEWAREA. Tm tarkoittaa, ett
     sit saa vapaasti kytt ja levitt MUUTTAMATTOMANA
     edelleen niin paljon kuin sielu siet (mbnetin sielu on
     kuulemma sietnyt jo vajaan tuhannen kappaleen levitt-
     misen).
     Levittmisest ei saa peri maksua ilman tekijn kirjal-
     lista suostumusta.
     Oppaan saa sisllytt kaupallisille cd-romeille vain
     tekijn suostumuksella ja mahdollisilla erityisehdoilla.
     Tekij ei vastaa esimerkkiohjelmien aiheuttamista ongel-
     mista tai vahingoista, kyttjt toimivat omalla vastuul-
     laan. Tarkoituksella bugeja yms. ei toki ole koodiin
     pstetty, mutta jos kovalevysi kosahtaa, harmi. Shit
     happens.


 0.1 Kuka on Ica?

   Olen oikealta nimeltni Ilkka Pelkonen, 19-vuotias c-,
   (pascal-) ja assembler-ohjelmoija. Asustelen Nurmijrvell
   30 kilometrin pss Helsingist pohjoiseen ("Nurmijrvi,
   kotiseutu, tll eln -- enk tasan tnne mtnemn j!"
   -- Nurmijrvi-laulu ;) Harrastan 3d-ohjelmointia, muutamia
   kivoja pelej (Paybacktime2, Steel Panthers, C&C, Warcraft 2),
   3dican kirjoittamista, hlkkmist ja kaikenlaista muuta
   mukavaa.
     Opiskelen Teknillisess Korkeakoulussa Otaniemess tieto-
   tekniikkaa ensimmist vuotta. Mestan suuntautumisvaihtoehto
   nimelt Tietojenksittelyoppi tuntuu mielenkiintoiselta,
   samoin Digitaalinen media, mutta sas' nyt nhr mit sit
   rupeaa leiptykseen tekemn...
     Armeijan palvelukseen astun muistaakseni 14.7.1998, paikkana
   Hyryl ja Helsingin IT-rykmentti, erikoistehtv matemaatti-
   sesti suuntautuneille miehille 8-)
     Kuulun gruuppiin nimelt Hubris, entinen Dozer. Watch out,
   vuoden lopulla on tulossa jonkinlainen demoviritelm. Nimikin
   moisella jo on: Rain. Niin muuten, jos olet asiasi osaava
   graafikko / muusikko, ilmoittaudu (tapio.vuorinen@mbnet.fi).
     Yhteyden minuun saa parhaiten e-mailaamalla osoitteeseen
   ipelkone@cc.hut.fi, eli kommentit tst tekstist (ja miksei
   jostain muustakin =) sinne.
   Virheet saa, ei vaan PIT, ilmoittaa :)


 0.2 Kuka ihmeen erkki se toinen nrtti on?

   [sori vaan kaj, piti hiukan sensuroida tekstisi :]
   Olen oikealta nimeltni Kaj Mikael Bjrklund, 18-vuotias
   c-, pascal- ja assembler-ohjelmoija. Asustelen Helsingiss,
   Roihuvuoressa tarkemmin. Kiva paikka. Harrastan pasiassa
   tietokoneohjelmointia (mahtoi olla ylltys), mutta olen
   pyrkinyt pitmn itseni fyysisestikin kunnossa puntti-
   salin avustuksella.
     Kyn koulua Vuosaaren lukiossa viimeist vuotta, jonka
   jlkeen tavoitteena olisi suunnistaa Teknilliseen [haahhaha,
   ei sill ole mitn mahiksia 9.7:n matikan ka:lla ;)  -ica].
   Armeijan palvelukseen astun muistaakseni 4.1.99, paikkana
   Isosaari.
     Olen pistnyt levitykseen pari playeria, joita kukaan ei
   (ymmrrettvsti) kyt, ja tehnyt kavereideni kanssa pelin
   "Rocket Chase" (jota jotkut jopa pelaavat). Kuulun gruuppiin
   nimelt Hubris. Watch out, parin kolmen vuosikymmenen pst
   meilt on tulossa jonkinlainen demoviritelm.
     Yhteyden minun saa parhaiten e-mailaamalla osoitteeseen
   kaj.bjorklund@mbnet.fi. Etenkin kauniimman sukupuolen
   edustajia kehotetaan ottamaan vlittmsti yhteytt ;)
   [ymmrrn smileyn. ..no ei, kaj on oikeen komee uros ;D -ica]


 0.3 3dica carewarea -- mit se tarkoittaa?

   Jos olet huomannut, miten paljon tekemist tmn dokumentin
   kirjoittamisessa on ollut ja mietit miten voisit korvata vai-
   vannkni, otan vastaan kannustusmieless vapaavalintaisen
   kokoisia rahalahjoituksia. Vhintn 20FIM lhettneet saavat
   hyvn mielen lisksi greetit ja jonkin oman kommenttinsa jul-
   kaistuksi seuraavissa versioissa. Pidtn itsellni oikeuden
   olla julkaisematta sairaita, (liian) loukkaavia tai muuten
   mauttomia juttuja.
   Mainittakoon viel, ett kaikista lahjoituksista kolmasosa
   ptyy Kaj Bjrklundille, joka on versiosta 2.1 lhtien toi-
   minut kantavana voimana kolmella tavalla: oikolukemalla aina
   uuden version, kirjoittamalla itsekin selostuksia eri aiheis-
   ta, ja esittmll silloin tllin uskomattoman typeri ky-
   symyksi ;D
   Niin, ja verojen maksua epilevt tietkt, ett jos rekis-
   terintej tulee niin paljon, ett niist maksetut verot
   ylittisivt kirjauskustannukset verotoimistossa, ne makse-
   taan (kirjoittamishetkell ilmoittamisessa ei ole mitn
   jrke tulojen ollessa tasan 150FIM).

   Tss osoite, johon voitte lhett lompakkonne / iskn
   lompakon / sen sislln:

     Ilkka Pelkonen
     Kynnysmentie 10
     01860 Perttula.

   Tilillekin voi maksaa PSP 800027-30577532, jolloin viitteeksi
   teksti "3DICA", rekisterijn nimi (jos ei sama kuin maksajan),
   sek mahdollinen 3dican yhteydess julkaistavaksi haluttava
   kommentti.


 0.4 Kenelle tm dokumentti on tarkoitettu?

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


 0.5 Mist saan dokumentin uusimman version?

   3dican dos-version virallinen levityspaikka lytyy www:st
   Hubriksen kotisivuilta, http://www.hut.fi/~ipelkone/hubris/.
   Uusin versio lytyy aina mys Jere Sanisalon kotisivuilta
   osoitteessa http://www.sicom.fi/~cooldude/. MBnetiin uusin
   versio tulee viimeistn kolmen viikon pst julkaisusta,
   useimmiten jo viikon sisll.

   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 ;) En ole kyll varma, mik versio tuolta
   lytyypi. 2.3-versiosta lhtien 3dican painottuu html-ver-
   sioon (eli kirjoitan ensin html:ksi, sitten konvertoija
   knt sen dossille).


 0.6 Kiitokset avusta

   Listaan tss henkilt jotka ovat jotenkin joko auttaneet
   minua oman engineni kehittelyss tai sitten kirjoittaneet
   itse 3dicaan jonkin kappaleen. Nimen alla listaus asioista,
   joissa henkilt ovat auttaneet. *Kiitos* teille.


     Kaj Bjrklund (Chem/Hubris)
       Kaj on 3dican virallinen oikolukija. Lisksi hnen ksi-
       alaansa ovat lineaarinen interpolointi, phong illuminat-
       ing, frameskip, c (ja pascal) -esimerkkisorsat, texture-
       mappauksen optimointisysteemit, asm-kurssin parantelu,
       gouraud, mip-mappaus, convex-polyt sek oikea phong.
       Kaj voidaan katsoa kuuluvaksi 3dican vakituiseen kehitys-
       'tiimiin', ja hn saakin kolmasosan sen 'tuotosta' :)

     Henri Tuhkanen (Rodex/Static)
       Asm-kurssi, texturemappauksen optimointivinkkej.

     Jere Sanisalo
       Portalit. Jeren kotisivuilta lytyy aina 3dican uusin
       versio, jos hut.fi sattuu olemaan down.

     Timo Saarinen
       Perspektiivikorjaus.

     Sampsa Lehtonen
       Paletinkvantisointi.

     Jukka Liimatta (Wog/Orange)
       Bilineaarinen filtterinti.

     Tapio Vuorinen (Bull/Hubris)
       Z-buffer.

     Ilkka Pelkonen (Ica/Hubris)
       Kaikki loput. :)


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

   Uutta versiossa 2.1a (ei pssyt koskaan levitykseen :)
     - Matriisipyritysten esimerkkipseudossa oli moka, korjattu.
       Kiitos informoinnista, sin-joka-MBnetiss-kytt-nime
       Mika&Janne Tolvanen.
     - Pikkupukkeja korjattu.
     - Gouraudiin listty optimointivinkki joka vhent cmp:n ja
       mahdollisia xchg:it hliness.
     - Perspektiivikorjauksen periaate selostettu. Kiitos kun
       selvensit sit minulle, Timo Saarinen.
     - Hidden face removalia tarkennettu.
     - Tulossa-osio listty.
     - FAQ listty.
     - Thanks for support -osio listty.

   Uutta versiossa 2.11:
     - 3dican virallisen levityspaikan osoite listty.
     - Yleist lpin lpisty lis, mm. 3d-enginen mainos ja
       paikat asm97:lla ilmoitettu.
     - Jotkut purkit ottivat  3di_src.zipin dizzin koko 3dican
       dizziksi, joten muutin sen ptteen.
     - Yksi kysymys listty FAQ:iin (vau).

   Uutta versiossa 2.2:
     - Enp taida joka versioon kirjoittaa ett yleist lpin on
       tullut lis. Tss verssussa se loppuu. Viimeinkin.
     - Yleist lpin tullut lis.  ;D
     - Mukana -- yleisn hartaista rukouksista huolimatta ;) --
       c-esimerkkikoodia (djgpp) by Kaj.
     - Kappalejakoa fiksattu (alkeellisemmat 3d-pyritykset pudotettu
       pois).
     - En ole en Ica/2 vaan Ica /Hubris, niin mainostetaan
       samalla ktevsti tuota gruuppiakin 8-)
     - Vaihdoin 3x3-matriiseihin ihan kytnnn syist, ja 3dicakin
       ksittelee sitten tst lhtien niit. Valitan kovasti jos
       nist versionumeroiden muuttuessa tapahtuvista muutoksista
       on haittaa 3d-enginesi kehittelyyn, mutta karu totuus on ett
       opettelen itsekin koko ajan uusia tekniikoita ja paremman
       lytyess vaihdan aina vlittmsti siihen.
     - Portalien idea on selostettu by Jere Sanisalo.
     - Texturen fittaus objektiin listty.
     - Texture + varjostus listty.
     - Jtin pascal-esimerkkiohjelmat ainakin vliaikaisesti pois
       paketista, koska niiden kehittj Kaj on siirtynyt c:hen
       eik en osoita mielenkiintoa niiden paranteluun (sellai-
       senaanhan nuo ovat aikamoista sotkua). Jos joku muu haluaa
       jatkaa pascal-sorsien kehittmist, ottakoon yhteytt.
     - Johdatusta matriiseihin laajennettu.
     - Mukana wanha kunnon licenssi-kriimentti.
     - Rekisterityneit tullut lis, kiitokseni heille. Toivotta-
       vasti tahti pysyy vhintn samana.
     - Henri Tuhkasen kirjoittama assembly-optimointikurssi on
       liitetty mukaan.
     - Oikean view conen periaate mukana lukijoiden painostuksesta.
     - Vektoriprojektio listty.
     - Kameramatriisin muodostaminen suuntavektorista listty.
     - Paletinkvantisointi (Local K) mukana by Sampsa Lehtonen.
     - Verteksinormaalien generointipseudossa on eptarkkuus, jota
       en kyllkn korjannut mutta kerroin sentn ett siell
       on sellainen :)
     - 3d-sanastoon listty mip-mappaus.
     - u- ja v-deltan vakiona silymist texturemappauksessa sel-
       vennetty by Kaj.
     - Z-bufferissa mukana 1/z-optimointivinkki.
     - Verteksinormaalien luonnekuvaus :) mukana by Kaj.
     - Aito phong listty.
     - Vastaavasti Voltaire/OTM:n phong pudotettu pois yleisn
       pyynnst. Ok, Voltairen systeemi kieltmtt oli itsestn
       selv ja nytt ikvsti gouraudilta.
     - 0-osion kappalejakoa muutettu fiksumpaan suuntaan; mukana
       mm. lhdekirjallisuuslistaus (ei kattava, tosin).
     - Mukana itse kyttmni varjostussysteemi.
     - Samoin vapaasti liikkuvien valonlhteiden tekniikka (siis
       systeemi jolla valot voivat kulkea scenen lpi).
     - Kuin mys spotlightien toteutus.
     - Ja viel himmenevien valojen idea.
     - Convex-polyjen tekniikkaa yritetty valaista by Kaj.


 0.8 Tulossa

   Some potential features to be added:

     - Bugikorjausta, jos korjattavaa lytyy
     - Lis c-esimerkkikoodia
     - 3d-sanaston ja FAQ:n laajentamista
     - Kompleksilukujen alkeet (tarvitaan fraktaaleissa)
     - Quaternionit
     - Splinet
     - 3d-varjot
     - Bresenhamin viiva-algoritmi (tmappauksen optaamiseen)
     - Pyritys vapaavalintaisen vektorin ympri
     - Ehdota itse uusia aiheita!


 0.9 Gr33tZ

   iCA /hUBRiS gRe37z th3 f0ll0WiNG d00dz...

   Friction/Morel Arts: Esit taas kanaa=) Ventolin qsee.
   Ducha: (Don't) Keep up the (vhemmn) good work, pal!
          (kuulin yhden biisisi X)
   Ripple/Inside: Ei kukaan VOI olla noin hyv. Paitsi min ;)
   Firestorm/Morel Arts: Kehittisit nyt sit QWKProtakin...
   Frac(tal): Olet perfektionisti. ("miten pin ne vektorit
              oikein muodostetaan?")
   MiGbear: "Nyt se on loppu. Nyt se on LOPPU! AAAAAAAA!!!"
   Oca: Vielk kryttelet prossutuulettimia? :) Kyykkyyn, yls,
        kyykkyyn, yls :P
   Ozmo: Onko se polttamisen lopettaminen tosiaan niin hauskaa,
         ett se pit tehd useampia kertoja kuussa?-)
   Jucka: En vielkn osaa piin sataaviittkymment desimaalia :(
   RoDeX/Static: ijll on aika kovat puheet...
   Praetor: Voisi varmaan lopettaa sen sinun html-versiosi kehittmisen..
   Pizzaman: Vitelln jos eletn. Eik se elm kirjoituksiin
             loppunut :I
   Wrath: Mik projekti nykyn rullaa? ;)
   Jokke: Kai se pit sitten sinuakin greetata ;) Olet muuten
          aika lailla sen nkinen kuin oletinkin.
   tonic/Trauma: tAAt rlz 8-) Vasomotorinen riniitti prjsi
                 yllttvn hyvin assyilla.
   Wog/Orange: Colors oli 'ihan kiva'. Irkataan!
   RRRulettavaKonna: Olet ers oudoimmista tyypeist joiden kanssa
                     olen mlissyt... :I
   Chem/Hubris: Kiitos RC:n ilmaisesta rekkauksesta! :) Olet
                muuten ensimminen ihminen joka hakkasi MINUT
                ko. peliss :..(
   Bull/Hubris: Lhdit assyilta varkain, min en edes huomannut.
                Flaresi on c00l!
   Tweeker/Hubris: Ostitko sen uuden skitan?-)
   Acute/Hubris: Optimoihan poika kvantisointia :P
   Codger/Hubris: Et ole ollenkaan hullumpi rendaaja. Joko olet saanut
                  kasaan niit auto-obuja?-)
   Hyphen/Static: Mieshn OSAA jotain... Minulle oli ylltys ett
                  kuulut Staticiin.
   Beta/5C: ij se vaan tekee rahaa javap**kalla... Snyyh :(
   _ilga_: Nhrnhn koululla! Mikset ky matikanluennoilla?-)
   JayLettu: YK mik alias #XI Imuttakaa kaikki ijn koodaama
             FLAMECD.ZIP mbnetist! Mukana hirvittvt pascal-sorsat :D
   JereSaniSalo: Miten raytrace-enginesi toimii?
   HenriKronLund: Miten menee? Ei ole messuiltu sitten TZ:n
                  kaatumisen! Kai olet sentn pari nuorten EM:
                  jo voittanut kssiss?-)
   JanneGrnThal: SIK rulaa? Voe poeka, etp tied hyvn plle :P
   TeemuKmrinen: Tere tulemast tikille ensi vuonna!


 0.10 3d-termist yms.

   Ensinnkin kyttmni koordinaatisto on seuraavanlainen:
   x-akseli kulkee vasemmalta oikealle, y-akseli ylhlt alas ja
   z-akseli osoittaa suoraan ruudun sisn, eli koordinaatisto on
   oikean kden systeemi. Tm tarkoittaa kahta asiaa:

     1) Z-akselin suunta saadaan oikean kden kolmella ensimmisell
        sormella tyntmll peukalo oikealle ja etusormi alas.
        Keskisormi osoittaa tllin z-akselin suuntaan.

     2) Pyritys akselien ympri mrytyy nin: kun oikean kden
        peukalo osoittaa akselin suuntaan, sormet taipuvat positii-
        viseen pyrityssuuntaan (eli pyrittesssi z-akselin ympri
        positiiviseen suuntaan, pisteet pyrivt ruudulla mytpi-
        vn).

   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.
                 Tm oli muuten Wog/Orangen messusta, kiitos.

     Complex-polygoni
                 Monimutkainen polygoni; polygoni voi leikell
                 itsen (masokismia! X). piirtminen monimutkai-
                 sempaa kuin kuperan polygonin.

     Concave-polygoni
                 Kovera polygoni; polygonissa on kulmia, jotka
                 ovat yli 180 astetta, mutta polygoni ei kuiten-
                 kaan leikkaa itsen. Piirtminen monimutkaisempaa
                 kuin kuperan polygonin.

     Convex-polygoni
                 Kupera polygoni; polygonin kaikkien reunojen
                 vliset kulmat pienemmt kuin 180 astetta. Piir-
                 tminen yht helppoa kuin tavallisen kolmion.

     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.

     Mip-mappaus
                 Texturesta pidetn eri kokoisia, valmiiksi
                 filtterityj versioita muistissa. Piirrett-
                 vlle polygonille valitaan sopivan kokoinen
                 mipmap riippuen polygonin koosta. Nin saadaan
                 maukas filtterintiefekti texturemappaukseen
                 "ilmaiseksi". Kun tm listn bilineaariseen
                 filtterintiin, on tulos todellinen nautinto :O

     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. Kytnnss lhinn vri-
                 mrn pienentmist enemmn tai vhemmn opti-
                 maalisesti.

     Polygoni    Monikulmio, huom. EI vlttmtt kolmio. n-
                 kulmainen polygoni voidaan muodostaa n-2 kolmi-
                 osta.
     
     Quaternion  4d-piste, kytetn haluttaessa helpottaa esimerkiksi
                 demossa liikkumista; ilmoitetaan vain alkuorientaatio
                 ja loppuorientaatio, ja interpoloidaan quaternioneilla
                 vliss.

     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       Texture element eli bittikartan pisteen vri (vrt
                 pixel = picture element).

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


 0.11 Frequently Asked Questions

   Perustavanlaatuinen neuvo kaikkiin kysymyksiin: KYT
   JRKESI! TIETENKN koodisi ei toimi, jos ohjelma ei vastaa
   edes kyttmsi ohjelmointikielen SYNTAKSIA!


   Q: 3d-pyritykseni, jotka on otettu suoraan pseudosta, eivt
      toimi.

   A: Tutki, ovatko kaikki muuttujat oikeaa tyyppi. Oletko kyt-
      tnyt yhdess kohdin muuttujaa jonka nimi on otettu pseu-
      dosta, ja toisessa itse nimetty, muka samana muuttujana?
      Oletko vilkaissut c-sorsia? ;)

   Q: Texturemappausrutiinini sotkee ruudun. En lyd mistn
      mitn vikaa.

   A: Mahdollisesti pyristysongelma.
      Fixed:

        putpixel( x, y, tmap[ u/65536+v/256) ] )

      Olet nerokkaasti ajatellut optimoivasi, eli kun v pitisi
      jakaa 2^16:lla ja kertoa 2^8:lla, oletkin jakanut 2^8:lla.
      Tulos on vr. Miksik? Nyt fixedpointtisi kahdeksaa
      ylemp bitti ei nollata ollenkaan vaan tmap ksitt ne
      u-koordinaateiksi joten tulos on pin ..rsett.
      Oikea tapa on seuraava:

        putpixel( x, y, tmap[ u/65536+(v/65536)*256) ] )

      Float:

        putpixel( x, y, tmap[ word(u+v*256) ] )

      u:n ja v:n desimaaliosat sotkeutuvat toisiinsa vristen
      tuloksen. Oikea tapa:

        putpixel( x, y, tmap[ word(u)+word(v)*256) ] )

      Itsellni oli muuten tasan tm ongelma ja ihmettelin
      *pitkn* kun flotareiden kyttminen sotki rutiinit
      totaalisesti :I

   Q: Flat-polygonirutiinini ei toimi.

   A: Onko muuttujat mritelty oikein? Kyttk pascalin
      kokonaislukujakolaskua floateilla tai pinvastoin? Onko
      fixedpointtisi oikein mritelty? Muistatko jakaa sill
      loppuvaiheessa? Toimiiko verteksinsorttauksesi varmasti?

   Q: Mit ovat 3D-Studion asc-formaatissa face-listauksessa
      esiintyvt termit AB, BC ja CA? (no huhhuh :)

   A: Ne ilmoittavat wireframe-kuviossa, piirretnk viiva vai
      ei (minusta ainakin erittin ktev ominaisuus).

   Q: Milloin kytetn verteksi- milloin facenormaaleja?

   A: Verteksinormaaleja kytetn gouraudissa ja phongissa,
      facenormaaleja lambert flatissa.


 0.12 Thanks for support

   Lmpimt kiitokseni nille 3dican kehitystyt rahallisesti
   tukeneille henkilille (lainausmerkeiss heidn omat moikkauk-
   sensa, niiden perss minun kommenttini).

     Joonas Pihlajamaa:

       "Imuroi lamertutti, imuroi lamertutti! BAD KARMA rulettaa!"

       Ai ai, kyllhn Jokke-pojan pitisi tiet ett Ilkka-set
       on jo liian iso kyttmn minknlaisia tutteja. Tosin
       sen takiahan sit voisi niit vaikka imuroidakin jos ei
       roskiin jaksa heitt ;)
       Ja kaikkihan tietvt ett Bad Karma on lhinn vitsi.
       (vitsi (vitsi (vitsi (vitsi (vitsi))))) (eli loppujen
       lopuksi: kommentti oli vitsi -- siis se edellinen, ei
       jlkimminen) :]

     Tommi Pisto

       Mits hommaat nykyn? Matiskan kanssa at The Opisto?-)
       Mailaa jos haluat kommentin thn!

     JiMM:

       "jeejee psin greetzeihin .."

       Kyllhn kaikki RAHASTA psevt :) Suurempi kunnia taisi
       viel olla kun sait kirjoittaa tuon kommenttisi itse minun
       koneellani assyilla ;)

     Erik Seesjrvi

       Vau, maksumryksen thnastinen enntys: 40FIM. Tosin
       lienet saanut sen edest helppi jo privamailissakin ;)

     Juhana Venlinen:

       "Ei milln pahalla mutta hyv piv."

       Piv, piv. Pllit tuon tageistani! :) Tytyy kyll sanoa
       ett oli se hauska messu lukea 8-)
       Uusi enntys: 50FIM. *Kiitos*.


 0.13 Lhdeteoksia & -dokumentteja

   Suurena apunani ja tietotekniikan Raamattuna kytn loistavaa
   kirjaa Computer Graphics: Principles & Practice, 2nd Ed, teki-
   jin herrat Foley, Van Dam, Feiner ja Hughes ja julkaisijana
   Addison-Wesley. Muita teoksia ja dokumentteja listattu alla.

     Algebra ja Geometria, Kivel, Simo, Otatieto 1997
      - matriisit, 3d-pyritykset, projektiot

     OTMMATX.DOC
      - matriisit, hierarkiset systeemit

     OTMPHONG.DOC
      - phong illuminating

     PC Game Programmer's Encyclopedia 1.0
      - BSP-puu

     PENTOPT.TXT
      - pentiumin optimointikikat

     [muita lukuisia dokumentteja joita en milln saa phni]


-- asiaan... --




1. Vektori- ja matriisialgebraa
-------------------------------


 Jos tm ei tunnu kiinnostavan, siirry vain suoraan efekteihin;
 joudut kuitenkin palaamaan tmn kappaleen pariin jossain
 vaiheessa ;)


 1.1 Vektorit

    1.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.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)
     Toinen mahdollinen esittmistapa on tm:
       __
       AB = ( Bx-Ax , By-Ay , Bz-Az )

     (tt esitystapaa kytetn 'edistyneemmss' kirjallisuudessa).

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

     Vektorin pituuden laskemista tarvitaan mm. phongissa ja ver-
     teksinormaalin laskemisessa.
                    _                      _
    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.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.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!

     Skalaarituloa tarvitaan mm. phong illuminating -efektiss ja
     monissa valo-operaatiossa.
                              _ _      _ _
    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.1.6 Vektoriprojektio

     Tarkoituksena on laskea vektorin projektio toisella vektorilla,
     eli laskea vektorin a vektorin b suuntainen komponentti.

                      b^
                       |
                       |_______
                       ^      /|
                a:n    |    /
             projektio |  /a
               b:ll   |/

     Selventmiseksi pitnee sanoa ett kaikki vektorit alkavat samasta
     pisteest, ja a:n projektio b:ll on lyhyt b:n suuntainen vektori.
     Homma hoituu yksinkertaisesti laskemalla vektorien a ja b skalaa-
     ritulon ja kertomalla b:n sill:

       a:n b:n suuntainen komponentti = (a piste b)*b.

     Skalaaritulon tuloshan on skalaari eli vakio, joten (a piste b)
     vain skaalaa b:t kuten pitkin.

     Vektoriprojektiota tarvitaan ainakin vektorin konvertoimisessa
     matriisiksi.


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

     Ristituloa tarvitaan mm. normaalien ja siten mys tason yhtln
     laskemisessa.

    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.1.8 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.2 Matriisit

   1.2.1         Yleist

     (Algebra ja Geometria, Simo K. Kivel, Otatieto 1989. Pt
     srkee, siksi tll ei ole mitn hauskaa vliss X(
     Matriisiksi kutsutaan reaaliluvuista muodostettua jrjestetty
     joukkoa:
                                     
            a(1,1) a(1,2) ... a(1,n) 
            a(2,1) a(2,2) ... a(2,n) 
       A =    .      .    .     .    
              .      .      .   .    
            a(p,1) a(p,2) ... a(p,n) 
                                     
     Matriisit merkitn siis sikaisoihin hakasulkeisiin (tai taval-
     lisiin kaarisulkeisiin). Determinantti erotetaan matriisista
     jttmll sulkeiden pist haat piirtmtt eli vetmll vain
     pystyviivat molemmille puolille determinanttia (kts. kappale
     1.1.6).
     Luvut a ovat matriisialkioita. Ne muodostavat p vaakarivi ja n
     pystyrivi. Matriisin tyyppi on tllin p x n; tarvittaessa se
     voidaan ilmaista matriisin nimen alapuolelle kirjoitettuna: A.
                                                                pxn
     Matriisialkion a(j,k) edellinen indeksi j osoittaa, mill
     vaakarivill alkio on; jlkimminen indeksi k osoittaa vastaa-
     vasti pystyrivin (indeksit menevt siis samalla tavalla kuin
     c-kielen taulukoissa: verrattaessa pisteen (x,y) esittmiseen,
     matriiseissa ja c-kielen taulukoissa indeksit ovatkin (y,x)).

     Matriisien nimin kytetn yleens isoja latinalaisia kirjaimia.

     Tyyppi p x 1 olevia matriiseja kutsutaan pystyvektoreiksi.
     Poikkeuksellisesti niit merkitn pienill latinalaisilla kir-
     jaimilla:
                   
            x(1,1) 
            x(2,1) 
       x =    .    
              .    
            x(p,1) 
                   
     Tyyppi 1 x n olevat matriisit ovat vastaavasti vaakavektoreita.
     Esimerkiksi vektori i+j+2k on pystyvektorina tmn nkinen:
              
            1 
       x =  1 
            2 
              
     Jos matriisin vaaka- ja pystyrivien mrt ovat yht suuret,
     kyseess on nelimatriisi (vrt. 3d-pyritysten 3x3-matriisit).
     Matriisialkiot, joiden indeksit ovat yht suuret, muodostavat
     nelimatriisin lvistjn; kyseess on siis vasemmasta ylnur-
     kasta oikeaan alanurkkaan suuntautuva viistorivi.
     Jos lvistjn ulkopuoliset matriisialkiot ovat = 0, on kyseess
     lvistj- eli diagonaalimatriisi:
                                     
            a(1,1)   0    ...   0    
              0    a(2,2) ...   0    
              .      .    .     .    
              .      .      .   .    
              0      0    ... a(p,n) 
                                     
     Lvistjmatriisin erikoistapaus on yksikkmatriisi:
                      
            1 0 . . 0 
            0 1 . . 0 
       I =  . . .   . 
            . .   . . 
            0 0 . . 1 
                      
     Yksikkmatriisin symboli on siis I.

     Nollamatriisiksi kutsutaan matriisia, jonka kaikki alkiot ovat = 0.
     Matriisin tyyppi voi olla mik tahansa. Merkint on O tai vektorin
     tapauksessa o.


   1.2.2 Matriisien laskutoimitukset

    1.2.2.1 Yhteenlasku

      Matriisien yhteenlasku mritelln siten, ett jokaiseen ensim-
      misen matriisin alkioon a(j,k) listn jlkimmisen alkio b(j,k)
      eli nin:
                                              
              a(1,1)+b(1,1) ... a(1,n)+b(1,n) 
              a(2,1)+b(2,1) ... a(2,n)+b(2,n) 
       A+B =        .       .         .       
                    .         .       .       
              a(p,1)+b(p,1) ... a(p,n)+b(p,n) 
                                              

    1.2.2.2 Kertominen skalaarilla

      (Skalaari = reaaliluku)
      Tm hoituu samalla systeemill kuin yhteenlasku, mutta nyt lai-
      tetaan +-merkin tilalle * ja b(j,k):n tilalle se skalaari jolla
      halutaan kertoa.
      Skalaarilla kertominen skaalaa matriisia.

    1.2.2.3 Kertolasku

      Kertolasku onkin jo astetta teknisempi laskutoimitus. Tssp
      siis kaava:
                                                        
         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.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, kaavan
      mukaisesti.
      HUOM! Vain yhteensopivat matriisit voidaan kertoa keskenn.
      Tm tarkoittaa, ett jos ensimminen matriisi on kokoa p x q,
      toisen on oltava kokoa q x r, eli sen pystyrivien mrn pit
      olla sama kuin ensimmisen matriisin vaakarivien mr. p:lle
      ja q:lle ei aseteta rajoituksia.
      Kun mainitun kokoiset matriisit kerrotaan keskenn, ulos
      tulee p x r -kokoinen matriisi, kuten kertolaskun mritel-
      mst helposti nhdn.
      Matriisikertolaskua tarvitaan erilaisissa objektien transfor-
      mointioperaatioissa.

     1.2.2.3.1    Erikoistapaus: vektorin ja matriisin tulo

       Asia on helpoin ymmrt tekemll halutusta vektorista
       vaakavektori:

         Xi+Yj+Zk = [ X Y Z ]

       Nyt muodostetaan tmn vektorin ja transformointimatriisin
       kertolasku:
                            
                      a b c 
         [ X Y Z ] *  d e f  = [ aX+dY+hZ bX+eY+iZ cX+fY+jZ ]
                      h i j 
                            
       Tt kaavaa voidaan kytt vektorien lisksi mys pisteen
       transformoimiseen matriisilla, jolloin kirjaimet (X,Y,Z)
       ilmoittavat pisteen alkuperiset koordinaatit, ja kaavan
       tulos vastaavasti tuloskoordinaatit.

    1.2.2.4 Transponointi

      Mritelm: Matriisin A(pxn) transpoosi on

               T
        C  =  A    (ylindeksi T),
       nxp   pxn

      miss C(j,k) = A(k,j). Matriisi siis 'flipataan' ympri.
      Esimerkki: Matriisin
                   
             1 2 3                           
             4 5 6                 1 4 7 0 1 
        A =  7 8 9  transpoosi on  2 5 8 4 5 .
             0 4 2                 3 6 9 2 3 
             1 5 3                           
                   

      Transponointia tarvitaan mielivaltaisen vektorin ympri
      pyrittmisess.




2. 3D-geometriaa
----------------


 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:

      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 * SCALE / Z0
      Y_SCREEN = Y0 * SCALE / Z0

   SCALE 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 Matriisitekniikka -- johdanto

   HUOM! Vaikka et ymmrtisikn matriisien ideaa, tutki esi-
   merkkisorsia ja kyt hiukan cut'n'pastea luodaksesi oman
   3d-enginesi pohjan. Myhemmin voit sitten palata matriisien
   perusteisiin, itse matriisisysteemien kytt ei edellyt
   kovinkaan kummoista tietoutta.

   Matriisitekniikassa objekti esitetn objektimatriisin, joka
   sislt kolme vektoria (suunnat alas, oikealle ja eteen),
   avulla. Kaikki operaatiot suoritetaan sille ja vasta lopuksi
   liitetn verteksit mukaan kertomalla ne objektimatriisilla.
     Tss tavassa on monta hyv puolta: se on nopeampi,
   yksinkertaisempi ymmrt, helpompi toteuttaa ja eksaktimpi,
   kuin 'perinteinen' systeemi jota useimmiten nkee 3d-docuissa.
   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 3x3-matriiseja, joista tiedot
   lytyvt seuraavasti:
                                         
       [X-akselin yksikksuuntavektori]  
       [Y-akselin yksikksuuntavektori]  
       [Z-akselin yksikksuuntavektori]  
                                         
   Eli ensimminen rivi ilmoittaa objektin x-akselin suuntavek-
   torin i:n, j:n ja k:n kertoimien arvot, toinen ja kolmas
   rivin vastaavasti y- ja z-akselin, kuvan mukaisesti.
   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 1 0 
      0 0 1 
            
   Tllainen matriisi on nimeltn yksikkmatriisi.
   Nyt 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.


 2.3 Objektimatriisin pyrittminen

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

   X-akselin ympri:
                  
        1   0   0 
        0  cx  sx 
        0 -sx  cx 
                  
   Y-akselin ympri:
                  
       cy   0 -sy 
        0   1   0 
       sy   0  cy 
                  
   Z-akselin ympri:
                  
       cz  sz   0 
      -sz  cz   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:
                                                           
                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    
                 =  sx*sy*cz-cx*sz sx*sy*sz+cx*cz  sx*cy 
                    cx*sy*cz+sx*sz cx*sy*sz-sx*cz  cx*cy 
                                                         
   Tss optimoidussa muodossa on se hyv puoli, ett pyritykset
   toimivat aina oikein (lentokone-esimerkki).

   Pyritetty objektimatriisi saadaan siis kaavasta

     [O] = [O]*[XYZ].

   HUOM! 3dican versioissa <2.2 vitettiin virheellisesti ett
   trigonometriset funktiot (kytnnss sini ja kosini) kannattaa
   taulukoida nopeuttamiseksi. Pentium kuitenkin kalkuloi mainitut
   funktiot niin vikkeln, ettei ole mitn jrke tuhlata muistia
   riittvn suuren taulukon kasaamiseen. Hierarkisilla objekteilla
   kumulatiivisuus vaatii esimerkiksi aikamoista tarkkuutta. Tllin
   jouduttaisiin varaamaan vhintn megan kokoinen taulukko pelklle
   trigonometrialle, eik se tunnu en kovinkaan mielekklt.
   Kiitokset vain Paul Nettlelle joka minua valaisi tss asiassa
   (tuskin ko. henkil kuitenkaan lukee tt ;I


 2.4 Kamera

   Kameran saa matriisitekniikalla huomattavasti ktevmmin
   mukaan kuin geometrisissa pyrityksiss: ei tarvitse tehd
   muuta kuin kertoa transformoitu world-matriisi kameramat-
   riisilla! Kytnnss tm tapahtuu nin:

     [O] = [O] * [XYZ]
     [W] = [W] * [O]
     [W] = [W] * [C],

   miss W on world-matriisi (ilmoittaa 3d-maailman orientaation)
   ja C kameramatriisi.

   HUOM! Muistathan kytt *verteksien* transformointiin W-mat-
   ruusia ja *normaalien* transformointiin O-matriisia (muuten
   valonlhteet pyrivt mukana kun liikutellaan kameraa).
   Niin, ja jokaiselle objektille tehdn sitten luonnollisesti
   ikioma world-matriisi.

   C-esimerkkikoodista lytyy tydelliset kamerarutiinit, mukaan
   lukien kameran liikuttamiseen tarvittavat rutiinit.

   2.4.1 Kameramatriisin generointi vektorin avulla

     Kaj-poju se kyseli ett mihin ihmeeseen tt tarvitaan. Mai-
     nittakoon kaikille, ett esim. 3D Studion kamerat on ilmoi-
     tettu vain suuntavektorilla, jolloin on hiukan hankala alkaa
     vst niill mitn ilman tiettyj matemaattisia operaatioita
     eli kytnnss tmn kappaleen tietoja :) Ok, asiaan.

     Tm on ollut The Big Question monelle pikku 3d-koodaajalle.
     Itsenikin aihe kiinnosti sen verran ett vnsin matikan
     luennolla mukavan purkkaviritelmn, johon kului aikaa kolme
     tuntia. Tuskin olin pssyt valmiiksi, kun vieruskaveri ni-
     melt Jussi Vainionp kysisi viattomasti: eiks tm olisi
     ktevmpi tapa? Tutkin hnen viidess minuutissa vsmns
     systeemi vhn aikaa ja tulin siihen tulokseen ett paitsi
     ett hnen tapansa oli selkempi, se mys toimi paremmin X)
     Tss siis Vainionpn Jussin kehittm tekniikka. Perustuu
     yksinkertaiseen vektorialgebraan.
     Kysymyksess on siis tapa jolla saadaan generoitua kameralle
     pyritysmatriisi, kun tiedetn vain vektori jonka suuntaan
     se osoittaa.

     Pieni ascii-kuva varmasti sekoittaa pnne niin ettei kysymyk-
     sille j sijaa ;D
     (z = kameran suunta, y = kameran pysty, j = maailman pysty,
     (j piste z)*z = j:n projektio z:lla)

                  ^            _ z
                  | \          /|
                  |   \y     /
                 j|     \_ /
                  |      /|
                  |    /
                  |  / (j piste z)*z
                  |/

     Sivuhuomautus: en viitsinyt kehitell tuosta 3d-kuvaa, jossa
     olisi ollut kameran x-akseli mukana ;I
     Maailman pystyvektori j on tiedossa: (0,1,0). Lasketaan j:n
     z:n suuntainen komponentti (j piste z)*z, ja vhennetn se
     j:st, jolloin tuloksena on y:

       y = j - (j piste z)*z.

     (vektoriprojektio on ksitelty kappaleessa 1.1.6).
     Kaavahan on ilmiselv ja kuvan tilanteesta suoraan johdetta-
     vissa. Se vielp sievenee johtuen j:n erikoislaatuisesta
     luonteesta:

       (yx,yy,yz) = (0,1,0) - (0*zx+1*zy+0*zz)*(zx,zy,zz)
                  = (0,1,0) - (zy*zx,zy*zy,zy*zz)
                  = (-zy*zx,1-zy*zy,-zy*zz)

     Kun tm viel normalisoidaan, saadaan kameramatriisin lopul-
     linen y-vektori (vektorin normalisointi ks. 1.1.3). Z-vektori
     oli tiedossa, mutta sekin pit normalisoida. Lopulta saadaan
     x-vektori y:n ja z:n ristitulona (huom. jrjestys):

       x = y risti z.

     X-vektoria ei tarvitse normalisoida jos y ja z olivat jo yksik-
     kvektoreita.

     Lopuksi sijoitetaan vektorit matriisiin kuten matriisitekniikan
     johdantokappaleessa on nytetty.


 2.5 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 + center_x
     Y = X0*b + Y0*f + Z0*j + center_y
     Z = X0*c + Y0*g + Z0*k + center_z

   Thn tarvitaan siis yhdeksn kertolaskua ja yhdeksn
   yhteenlaskua.

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

     - tm (transformation matrix), om (object matrix),
       wm (world matrix) (3,3)-kokoisia liukulukutaulukoita
     - (xp,yp,zp) alkup. piste, (x,y,z) pyritetty piste
     - ox, oy, oz objektin keskipisteen koordinaatit
     - ORIGO_X, ORIGO_Y ruudun keskipisteen koordinaatit

     reset_matrix(om) ; objektimatriisi nollataan vain alussa

     xa,ya,za = 1*PI/180; radiaaneina 1 aste; pyrityskulmat
     ox = 0 ; objekti SCALE yksikn phn z-akselille
     oy = 0
     oz = SCALE
     xp = 50
     yp = 0
     zp = 30

     looppaa joka framelle:

       reset_matrix(tm)
       reset_matrix(wm)

       tm[0,0] = cos(xa)*cos(za)
       tm[0,1] = cos(ya)*sin(za)
       tm[0,2] = -sin(ya)
       tm[1,0] = sin(xa)*sin(ya)*cos(za)-cos(xa)*sin(za)
       tm[1,1] = sin(xa)*sin(ya)*sin(za)+cos(xa)*cos(za)
       tm[1,2] = sin(xa)*cos(ya)
       tm[2,0] = cos(xa)*sin(ya)*cos(za)+sin(xa)*sin(za)
       tm[2,1] = cos(xa)*sin(ya)*sin(za)-sin(xa)*cos(za)
       tm[2,2] = cos(xa)*sin(ya)

       matrix_mul(om,tm) ; transformoidaan objektimatriisi
       matrix_mul(wm,om) ; samat world-matriisille

       ; pyritetn pistett
       x = xp*wm[0,0] + yp*wm[1,0] + zp*wm[2,0] + ox
       y = xp*wm[0,1] + yp*wm[1,1] + zp*wm[2,1] + oy
       z = xp*wm[0,2] + yp*wm[1,2] + zp*wm[2,2] + oz

       x = x * SCALE / z + ORIGO_X ; 2d-transformointi
       y = y * SCALE / z + ORIGO_Y

       putpixel(x,y)

     l_en_looppaa

   HUOM! Objekti pyrii tasaisesti kun kulmaa ei muuteta, eli
   se lopettaa pyrimisen vasta kun kulmaksi annetaan nolla.
   Miksik? You see, objektimatriisia ei resetoida missn eli
   se pyrii aina yhden asteen / frame, jos kulma pysyy ennallaan.


 2.6 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. 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 enimmkseen
 kolmioita, n-kulmiotekniikasta on mukana vain idea.


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

   Ja viel pikku huomautus: jos kytt 3d-klippauksia, voit
   tietysti unohtaa nm polygonirutiinissa tapahtuvat klip-
   paukset tyystin, ja sst siis kohtalaisen paljon las-
   kettavaa. Suosittelen.

   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.

   3.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 3.2 (eli IHAN alta :)


 3.2 Gouraud-kolmio

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

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

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

   Gouraud-horizline fixedpointilla ja ilman klippauksia:

     - dc on 32-bittist kokonaislukutyyppi

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


 3.3 Texture-kolmio

   Ksittelyss lineaarinen texturemappaus ilman perspektiivi-
   korjausta. Perspektiivikorjaamaton mappaus toimii aika
   mukavasti objekteilla joissa ei ole isoja polygoneja. Tllin
   texturen vristymist ei huomaa, eli klassisen mappauksen
   kytt lienee tss tapauksessa perusteltua. Perspektiivi-
   korjausta tarvitaan kuitenkin useimmissa 3d-systeemeiss.

   Anyway, asiaan:
   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 ;)

   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 ;)
   Otetaan esimerkiksi lineaarisen (=klassisen) texturemappauksen
   u-delta. Kuten tiedmme, hliness u1 pit interpoloida u2:ksi
   (x2 - x1):ss askeleessa. Tarvitsemme u-deltan (ku, u-kulmaker-
   roin), joka on siis koko polygonin ajan vakio.
   Eli sen sijaan ett laskisimme joka hlinelle
   (h_ tarkoittaa ett kyse on hlinen muuttujista)

     h_ku = (h_u2 - h_u1) / (h_x2 - h_x1),

   toimimme polygonin setup-osassa seuraavasti:
   Tiedmme, ett

     h_u2 = u2 = u1 + (y2 - y1) * ku2,
     h_u1 =      u1 + (y2 - y1) * ku1,
     h_x2 = x2 = x1 + (y2 - y1) * kx2,
     h_x1 =      x1 + (y2 - y1) * kx1,

   KUN y=y2 (eli y on kolmion toisen verteksin y). Tm nhdn
   helposti esimerkiksi kolmion toisen osan setupista.
   Kun sijoitamme u-deltan h_ku lauseeseen

     h_ku = (h_u2 - h_u1) / (h_x2 - h_x1)

   muuttujille h_u2, h_u1, h_x2 ja h_x1 lasketut arvot kohdassa
   y=y2, saamme deltalle lausekkeen

             [u1 + (y2 - y1) * ku2] - [u1 + (y2 - y1) * ku1]
     h_ku = -------------------------------------------------
             [x1 + (y2 - y1) * kx2] - [x1 + (y2 - y1) * kx1]

           (y2 - y1) * (u1 - u1 + ku2 - ku1)
        = -----------------------------------
           (y2 - y1) * (x1 - x1 + kx2 - kx1)

           ku2 - ku1
        = -----------,
           kx2 - kx1

   eli siis selkemmin

                        outerloopUdelta2-outerloopUdelta1
     innerloopdeltaU = -----------------------------------.
                        outerloopXdelta2-outerloopXdelta1

   Nice! Mutta ent jos kx2 = kx1? Tmhn tietysti tarkoittaa,
   ett polygoni on kytnnss viiva, joten on aivan yhdentekev
   mit ku:ssa on; nolla ky oikein hyvin. Vastaavasti voidaan
   yo. kaavaa muuten kytt mys v:n tapauksessa.
   HUOM! Kokonaislukujen ollessa kyseess pit luonnollisesti
   kytt fixed pointtia riittvn tarkkuuden takaamiseksi!

   Optimointivinkki #2: Hline-rutiinissa ei tarvitse verrata x:n
   arvoja toisiinsa vaan x1 on aina pienempi kuin x2, jos
   kolmiorutiinissa tutkitaan kulmakertoimia (eli d:n arvoja)
   y1:st y2:een (d[0]), y1:st y3:een (d[2]) ja y2:sta y3:een
   (d[1]) sek annetaan hlinen ensimmiseksi parametriksi
   seuraavat arvot: interpoloitaessa y1:st y2:een suurempikulma-
   kertoiminen, y2:sta y3:een pienempikulmakertoiminen tekij.


   3.3.1 Perspektiivikorjauksen periaate

     Aloitetaan esimerkill: 3d-starfieldin toiminta. Otetaan
     piste (1,1,3000), josta haluamme liikkua tasaisesti
     pisteeseen (1,1,1). Tm onnistuu tietysti interpoloimalla
     z-koordinaattia lineaarisesti: nyt piirtyy 3d-avaruuteen
     kaunis suora viiva, joka 2d-nytlle transformoitaessa
     muuttuukin ylltten kyrksi. Mits jos haluamme liikkua
     tmn vlin siten, ett se nytt *ruudulla* suoralta
     viivalta? Pit tietysti kytt 2d-koordinaattien
     lineaarista interpolointia. 3d->2d -transformointikaavathan
     ovat seuraavat:

       x_2d = x/z,
       y_2d = y/z.

     Esimerkkitapauksessamme x ja y ovat molemmat ykksi, eli
     kaavamme sievenevt muotoon

       x_2d = 1/z,
       y_2d = 1/z.

     Nin olemme saaneet selville, ett jos haluamme 3d-kyrn
     nyttvn ruudulla suoralta, pit interpoloida z:n knteis-
     arvoa eik z:aa itsen. Myskin huomaamme, ett jos z pysyy
     vakiona, 'perspektiivin korjaamista' ei tarvita.

     Mitenk tm texturemappaukseen liittyy -- texture-kolmiohan
     lnttn jo 2d:hen transformoitujen koordinaattien varaan?
     Kyll, *3d-maailman* koordinaatit on transformoitu nytlle,
     mutta miten on *texture-avaruuden* eli kytnnss bittikar-
     tan kanssa? Kyll, se on 2d-taso jota ei tunnu jrkevlt
     en uudelleen vnt (tai siis suoristaa :) 2d:hen, mutta
     kokeile itse klassista texturemappausta ja tule sen jlkeen
     sanomaan ett texture-kolmiosi liittyvt toisiinsa saumatto-
     masti -- ja kerro minulle tekniikka jolla teit sen ;)
     Tarttis tehr jotain jlleen kerran. Mitenks olis jos
     tehtiski se nin:

       u_2d = u/z,
       v_2d = v/z.

     Ei, homma ei todellakaan ole tll selv. Nyt lineaarinen
     interpolointi nytt ruudullakin lineaariselta, mutta
     eivthn nuo ole ne koordinaatit joita kaipasimme esittmn
     bittikartta-avaruuden koordinaatteja; (u,v) olisi sopivampi
     koordinaattipari. Hey! Nyt keksin! Interpoloidaan mys
     1/z:aa (z_2d) lineaarisesti, ja toimitetaan jokaiselle
     pikselille seuraava opereissni (operatsioone jos on
     italialaisia lukijoita):

       u_bitmap = u_2d / z_2d,
       v_bitmap = v_2d / z_2d eli

       u_bitmap = (u/z) / (1/z),
       v_bitmap = (v/z) / (1/z) eli

       u_bitmap = u,
       v_bitmap = v!

     Tuloksena ovat siis juuri haluamamme koordinaatit ja viel
     oikein interpoloituina! Ktev systeemi, etten sanoisi.
     Mutta hidas. Ah niin sikamaisen hidas. Vaatimattomat kaksi
     jakolaskua per PIKSELI X) Ei auta itku markkinoilla jos
     *ht* ylltt, mutta kyll se keinot keksii vaikka ylei-
     nen vessa olisikin varattu.
     Suomeksi, optimointikeinoja lytyy.

      1) Ei toimiteta tt hankalaa operaatiota joka pikselille,
         vaan otetaan mallia Quakesta ja kytetn sit vain joka
         8. tai 16. pikseli sek interpoloidaan lineaarisesti eli
         kytetn klassista, perspektiivikorjaamatonta tekniik-
         kaa niiden vlill. Eroa ei kytnnss voi havaita
         muussa kuin nopeudessa ;)

      2) Toinen jakolasku saadaan pois (tosin tilalle kaksi
         kertolaskua, mutta joka tapauksessa paaljon nopeampaa)
         nin (hlinen loopissa):

           z = 1/z_2d ; z = 1/(1/z) = z
           u_bitmap = u_2d*z
           v_bitmap = v_2d*z.

         Tmhn ei vaikuta perspektiivikorjaukseen hiritsevsti,
         koska z_2d se kuitenkin on jota interpoloidaan.

   3.3.2 Texturen fittaus objektiin

     Voehan pojaat. Vai ett teksturointi ei onnistu kun ei tied
     miten texturen fittaisi siihen obuun? Ei se nyt niiin
     vaikeaa ole... [jurputusta] [marinaa] [uhittelua]
     No jooh, kai se pit kertoa: otat env-mappauksen pyritt-
     mttmist verteksinormaaleista, seivaat nm texturekoor-
     dinaatit ja kytt niit koko ajan vaikka pyrittelisit obua
     sitten miten tahansa. Olikossenyt niin kamalan vaikeaa?
     Kytnnss:

       - au = verteksin a u-koordinaatti
       - av = verteksin a v-koordinaatti
       etc...

       for (a=0 -> face_lkm)
         face[a].au = normal[ face[a].a ].x / 2 + 127
         face[a].av = normal[ face[a].a ].y / 2 + 127
         face[a].bu = normal[ face[a].b ].x / 2 + 127
         face[a].bv = normal[ face[a].b ].y / 2 + 127
         face[a].cu = normal[ face[a].c ].x / 2 + 127
         face[a].cv = normal[ face[a].c ].y / 2 + 127
       endfor

     Useeraus frb sitten thn tapaan:

       super_mega_giga_hieno_texture_rutiini (
                x1,y1,z1,face[].au,face[].av,
                x2,y2,z2,face[].bu,face[].bv,
                x3,y3,z3,face[].cu,face[].cv
                )

     HUOM! Systeemi sukkaa yhdess asiassa: polygonit, joiden
     normaalina toimii lhes x- tai y-akseli, saavat huonot uv-
     koordinaatit tll systeemill. Tarttis tehr jotain,
     mutta mit? Parempia ideoita saa esitt.


 3.4 Texture + varjostus

   Texturen ja varjostuksen kyttminen samalla kertaa on
   kohtalaisen yksinkertaista toteuttaa. Perusideahan on ett
   interpoloidaan sek texturen ett varjostuksen arvoja ja
   blendataan sitten niit sopivassa suhteessa.

   Esimerkki-inneri:
     - 16-bittinen fixed point
     - tab = precalcattu taulukko josta lytyy texturen ja
       kulmainterpolaatiophongin arvot
     - kx, ku, kv, kc kulmakertoimet
     - tpixel = texture pixel
     - putpixelin parametrit: x,y,red,green,blue.

     for (a=y1 -> y2)
       tpixel = tmap[ u/65536 + v/65536*256 ]
       putpixel( x/65536,a,tab[tpixel,c/65536].r,
                           tab[tpixel,c/65536].g,
                           tab[tpixel,c/65536].b )
       x += kx
       u += ku
       v += kv
       c += kc
     endfor

   Tab-taulukon esimerkki-precalccaus (ei hilite):

     for (a=0 -> 255)
       for (b=0 -> 255)
         tab[a,b].r = pal[a].r * phong[b].r / 256
         tab[a,b].g = pal[a].g * phong[b].g / 256
         tab[a,b].b = pal[a].b * phong[b].b / 256
       endfor
     endfor


 3.5 Convex-polygonien tekniikka

   Kyll, saatte taas nauttia ihanasta asciista! (eik yhtn vinoilla,
   se on hiano!-) [gawd ett min odotan sit html:] (joo, se on vaan
   kaj ness. -ica ;)

   Haluamme piirt seuraavan viisikulmion:

                        __--\  <- kulma
                    __--     \
       kulma -> __--          \
               /               \
              /                 \   <- kulma
             /   "viisikulmio"  /
   kulma -> \                  /
              \               /
                \            /
                  \         /
                    \      /
                      \   /
                        \/ <- kulma

   Lhden liikeelle siit oletuksesta, ett olet lukenut ja YMMRTNYT
   tydellisesti miten piirretn kolmio. Samoin kuin siin, ylloleva
   kuva selvsti paperille piirrettyn ei voi johtaa mihinkn muuhun
   kuin hienoon filleriin.

   Pideana on, ett seuraamme ylimmst verteksist alimpaan puolta 1
   ja puolta 2 ja piirtelemme vliin vaakaviivoja.

    1. Etsimme ylimmn verteksin, josta aloitamme piirtmisen.
       Ylin verteksi on sama sek puolelle 1, ett puolelle 2,
       joten ylin verteksi on sek "start1" ett "start2".

    2. Otamme verteksilistasta *edellisen* verteksin ja kutsumme
       sit nimell "stop1". Otamme seuraavan verteksin ja
       kutsumme sit "stop2":ksi.

    3. Tss vaiheessa lienee selv, ett olemme seuraamassa
       janoja start1-stop1 ja start2-stop2. Sitten vaan interpoloimme
       start ykksen ks stop ykksen ksksi, start kakkosen
       ks stop kakkosen ksksi ja piirrmme joka askeleella
       vaakaviivan interpoloitujen x-koordinaattien vliin.

       Interpolointi aloitetaan "startin" y-kohdasta ja
       lopetetaan *ylemmn* "stop"in y-kohtaan. Start on alussa
       start1. Ylempi stop on jatkossa "stop".

    4. Olemme saapuneet kohtaan "stop", eli olemme onnistuneesti
       piirtneet yhden palan polygonia. Nyt riippuen siit, kumpi
       "stop" (stop1 vai stop2) oli ylempi, toimimme seuraavasti:

            stop1 oli ylempi:
                 start1 = stop1
                 stop1 = stop1:st *edeltv* verteksi
                 start = start1 = stop1
            stop2 oli ylempi:
                 start2 = stop2
                 stop2 = stop2:sta seuraava verteksi
                 start = start2

    5. Mene kohtaan 3 kunnes olemme piirtneet kolmion kaikki palat.

    En suinkaan vit, ett tm olisi jotenkin paras tyyli piirt
    nit. Tm oli se tekniikka, joka ensiksi tuli mieleen. Huomaa
    mys, ett tm on nimenomaan convex-polygoneille, concave ja
    complex eivt toimi nin yksinkertaisesti!
    [Min kyll sanoisin ett tm on paras tekniikka. Kommentteja?
    -ica]




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


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


 4.1 Z-sorttaus

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

    funktio quicksort(left, right)
     - q on apumuuttuja

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

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

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

      toista ikuisesti:

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

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

      endtoista

    endf

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


 4.2 Z-buffer

   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

   Optimoinnista tmn verran: z-bufferia ei tarvitse tyhjent
   niin usein (kytnnss ei koskaan), jos kytetn 1/z-systee-
   mi. Tm tarkoittaa ett interpoloidaan 1/z:aa, ja tehdn
   aina muuten vastaavat operaatiot kuin z:n kanssa mutta ver-
   tailussa kntyy vertailumerkki < toisin pin >:ksi :)
   Ai miten tuo sitten toimii kytnnss? Yksinkertaisesti nin:
   1/z on aina vlill 0..1, joten kun vhennetn z-bufferin
   jokaisesta arvosta 1 (tai otetaan vastaluku), z-buffer on
   'tyhjennetty' eli mik tahansa 1/z:n arvo on suurempi kuin
   tm vlill -1..0 oleva arvo. Kun viel hoksaamme, ett tmn
   uuden systeemin voi ktevsti tehd samassa inneriss muun
   z-bufferin operoinnin kanssa, tyhjentmisen viem teho on
   saatu minimoitua. Esim:

   1. frame:
     if (uusi1/z-arvo + 1) > zbuffer[kohta] then pist pikseli

   2. frame:
     if (uusi1/z-arvo + 2) > zbuffer[kohta] then pist pikseli.

   etc.


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


   4.3.1 The main idea

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

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

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

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

     Esimerkkitapauksessamme tehdn seuraavat viivojen
     jako-operaatiot:

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

     Binripuu taas nytt tlt:

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

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

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


   4.3.2 Kaavat

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

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

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

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

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

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

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

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

     Tst ratkaistaan siis t:

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

                       < = >

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

                       < = >

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

     Onneksi tt ei tarvitse kytt kuin inittiosassa ;)


   4.3.3 Vinkkej

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

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

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

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

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


 4.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).
   Hmhm... Paul kertoi #codersilla ett hnen oma s-bufferinsa
   ei tarvitse kuin korkeintaan pari cmp:t hliness! Olisipa
   mielenkiintoista nhd toteutus...

   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




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


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


 5.1 Flat-sheidaus

   5.1.1 Z-flat

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

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

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


   5.1.2 Lambert Flat

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

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

      funktio LambertFlat

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

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

        palauta color
      endf

     Tmhn on aika hidas 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.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 backface cullingia)
          color = 0
        endjos
         palauta color
      endf
 

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

   5.2.1 Z-Gouraud

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

   5.2.2 "Oikea" Gouraud

     Tm taas skulaa samoin kuin Lambert Flat, sill erotuksella
     ett ei oteta polygonin ja valovektorin vlist kulmaa vaan
     jokaiselle verteksille siihen kiinnittyvien polygonien ja
     valovektorin vlisten kulmien keskiarvo; verteksinormaalien
     keskiarvo.
     Verteksinormaalit ovat periaatteessa objektin (joka on itse
     asiassa approksimoitu kytten polygoneja) normaaleja, eli
     ne ovat jokaisessa pisteess kohtisuorassa objektin (*OIKEAN*
     kappaleen, ei polygonikasan) pintaa vastaan. Kytnnsshn
     moista ei helposti pysty laskemaan, joten otamme kivan arvion
     oikeasta objektin normaalista laskemalla verteksi ymprivien
     tasojen normaalien keskiarvon ja kyttmll sit verteksi-
     normaalina.
     Homma toimii siis nin:

       1. Nollaa kaikki verteksinormaalit
       2. Laske jokaiselle facelle normaali ja lis se facen
          jokaiseen verteksinormaaliin
       3. normalisoi kaikki verteksinormaalit

     Ei mikn minuutin nakki, tuon toteuttaminen, joten pseudo
     lienee paikallaan (esimerkkisorsistakin tuo tosin lytyy).
     Pseudo kytt vaihtoehtoista tapaa:

       1. Etsi jokaiseen verteksiin osuvat facet
       2. Lis ko. verteksin normaaliin nm facenormaalit
       3. Jaa verteksinormaalin kertoimet facenormaalien mrll
          (verteksinormaalihan oli facenormaalien keskiarvo)
       4. Normalisoi verteksinormaali

     Tm on luonnollisesti hitaampi tapa, mutta eips ollut taas
     aikaa kirjoittaa pseudoa nopeammasta :I Noh, toimiipahan
     ainakin.

     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.
     Huom! Tm systeemi pissii siin, ett jos jokin monikulmio
     on muodostettu useammasta kolmiosta, se kydn kahteen kertaan
     lpi, jolloin sit painotetaan liikaa. Saattaa nytt aika
     ikvlt. Korjauksena ainoastaan pikku checki ett onko tiet-
     ty normaaliarvoa viel kytetty laskettaessa verteksinormaa-
     lia -- tai n-kulmaiset polygonit :)


 5.3 Phong-sheidaus

   5.3.1 Phong Illumination

     Phong illumination tarkoittaa sit, ett paletin avulla
     kikkailemalla (ns. eplineaarinen paletti) saadaan gouraud
     nyttmn hienommalta phongilta. Miks siin, hienompi
     siit tulee kuin tavallisesta gouraudista.
       Phong illumination 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).


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


   5.3.3 Aito Phong

     Tss selostetaan hiukan optimoitu versio, joka ei ota huo-
     mioon kameran ja pinnan vlisen kulman muuttumista. Silm ei
     eroa juurikaan havaitse, mutta tydellisyyden tavoittelijat
     lukekoot vgophong.doc:n.

     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 kosinin skalaaritulon avulla (ks. 1.5).

     Phongiin tarvitaan siis seuraavat komponentit:

       - valosta pintaan -vektori (xyz) ja deltat (xyz)
       - pinnan normaali

     Gouraudissahan otit valovektorin ja verteksinormaalin vlisen
     kulman kosinin joka *verteksille*, ja kytit sit kirkkausarvona
     pikseleille lineaarisen interpolaation siivittmn.
     Tm ei ole yhtn monimutkaisempaa; vriarvon sijasta inter-
     poloimme verteksin normaalivektoreita (kaikkia komponentteja),
     sek valovektoreita (jos valot eivt ole rettmn kaukana),
     normalisoimme ne, otamme niden vlisen kulman kosinin, ja
     kytmme sit kirkkausarvona. Nin siis *JOKA* *PIKSELILLE*.
     Toisin sanoen teemme gouraudin laskut joka pikselille.

     Tm ainoa oikea phong-sheidaus on upean nkinen, mutta sit
     ei ole jrkev kytt kuin still-systeemeiss eli esimer-
     kiksi raytrace-enginess; lislaskettavaa tulee joka pikse-
     lille pistetulon verran: nelj kertolaskua, kaksi yhteenlas-
     kua, yksi jakolasku ja nelijuuri -- lhemms 300 pentium
     -kelloa! Uh-oh, realtimen ei taida onnistua X)


 5.4 Ican ikioma tekniikka

   Suuri ja Mahtava Ica kytt eplineaarisuuden takaavalla tau-
   lukkocheckill boostattua gouraudia texturemappauksen kanssa.
   Tekniikka on siit kiva, ett se

     a) on nopea kuin gouraud
     b) on hieno kuin kunnon feikkiphongi ainakin
     c) sallii vrilliset valonlhteet
     d) on itse kehitelty :)
     e) on suurien #coders-gurujen Paul Nettle ja Harmless (en
       tied oikeaa nime) suosittelemaa tyyppi.

   Itse pidn painoarvoisimpana argumenttina e-kohtaa :)
   Kytnnss teen nin: ohjelman init-osassa alustan jonkin
   kivan taulukon jokaiselle valonlhteelle phong illuminationin
   avulla. Polyn initiss toimin tsmlleen kuten gouraudissa,
   mutta piirtorutiinissa kytn interpoloitua vriarvoa indeksin
   mainittuun taulukkoon, en piirr sit suoraan. Systeemi toimii
   siis paut nin:

   for a=0 -> lights-1
    for b=0 -> 255 ; max. vrimr
      light[a][b].r = ambient.r + diffuse[a].r*cos(b) +
                      specular[a].r*cos(b)^n[a]
      light[a][b].g = ambient.g + diffuse[a].g*cos(b) +
                      specular[a].g*cos(b)^n[a]
      light[a][b].b = ambient.b + diffuse[a].b*cos(b) +
                      specular[a].b*cos(b)^n[a]

   Haittapuolena on, ett jokaisen valon kirkkausmuuttujaa pit
   interpoloida erikseen, but who cares? Addit ovat halpoja.


 5.5 Valonlhteiden ksittelyst

   Nm tekniikat ptevt kaikkiin sheidausmenetelmiin. Ne ovat
   itse asiassa hyvin yksinkertaisia, jopa niin yksinkertaisia
   ett vnsin ne kasaan alusta lhtien itse :)

   5.5.1 Vapaasti liikkuvat valonlhteet

     Ainut ongelma on valovektorin pivittminen. Miten se on-
     nistuu? Ha, piece of cake! Pidetn valosta ylhll vain
     3d-pistett jossa se kullakin hetkell on, ja muodostetaan
     vektori sen ja jokaisen piirrettvn verteksin (tai mink
     tahansa pisteen josta normaalivektori lhtee) vlille. Tm
     normalisoidaan, ja ta-daa! Uusi valovektori on valmis.

   5.5.2 Spotlitet

     ..kuulen jonkun pikku nen ruikuttavan ettei yo. systeemi
     pde kuin pistemisille valonlhteille; spotlitet saa unoh-
     taa saman tien. Lllstil, eips saa. Ne ovat vallan hy-
     vin toteutettavissa tll seuraavasti: pidetn kirjaa mys
     alkuperisest valovektorista (tai -matriisista niin on
     helpompi pyritell sit valoa; matriisin tapauksessa valo-
     vektori on npprsti z-akselin suuntavektori) ja spotliten
     kulmasta. Kun sitten on muodostettu uusi valovektori, tut-
     kitaan onko sen ja alkuperisen valovektorin vlinen kulma
     suurempi kuin spotliten kulma. Jos on, valoisuus on pyre
     nolla (tai sitten tehdn kiva pikku suhde niille kulmille
     ja saadaan pehmereunainen spotlite!), muuten sen arvo
     saadaan normaalisti uuden valovektorin ja normaalin vli-
     sest kulmasta.
     ls sitten ihmettele jos spotlitesi reunat nyttvt jn-
     nlt tai se bugaa jotenkin muuten, kun kytt gouraudia
     tai flattia. Kyse on yksinkertaisesti siit, ett kun ver-
     teksien vlill interpoloidaan lineaarisesti, erikokoi-
     sille polyille tulee eripitkt fadet, ja tllin spotlite
     saattaa nytt aika slittvlt. Hyvi ratkaisuehdotuksia
     otetaan vastaan ("aito phong" ei kelpaa ;) Kaj ehdotti, ett
     splitattaisiin poly useammaksi aina kun sit mentisiin
     tarpeeksi lhelle. Saattaa toimia, mutta nopeudesta / luotet-
     tavuudesta en sano mitn.


   5.5.3 Valon himmeneminen

     Just some more basic math: otetaan jokaisen verteksin eti-
     syys valonlhteest, ja tehdn valon intensiteetin ja
     etisyyden vlille jonkinlainen riippuvuus. Sitten vain
     lasketaan :)
     Eli:

       ; jokaiselle facen verteksille
       for a=0 -> num_of_vertices-1

         ; lasketaan uusi valovektori
         l_vector.x = vertex[a].x_coord - light.x_coord
         l_vector.y = vertex[a].y_coord - light.y_coord
         l_vector.z = vertex[a].z_coord - light.z_coord

         ; etisyys tulee 'kaupan plle' vektorin pituutena
         distance = sqrt((l_vector.x)^2 + (l_vector.x)^2 + (l_vector.x)^2)

         ; normalisoidaan uusi valovektori
         l_vector.x = l_vector.x / distance
         l_vector.y = l_vector.y / distance
         l_vector.z = l_vector.z / distance

         ; lasketaan himmeys
         brightness = 1 - (distance/light.fadezedo)^fogness

         ; lasketaan valoisuusarvot
         light_at_vertex[a] = gouraud(vertex1.normal,brightness)

       funktio gouraud (param normal, brightness)
         color = ( l_vector.x*normal.x + l_vector.y*normal.y +
                   l_vector.z*normal.z ) * brightness
         if color<0
           color = 0
         else if color>255
           color = 255 ; tai mik maksimivriarvosi onkin..
         return color
       endf

     Jooh. Light.fadezero ilmoittaa, mill etisyydell valo
     ei en ny lainkaan eli on tasan nolla. Fogness on scenen
     vakio (Kajn mielest ei kovin looginen muuttujanimi), joka
     ilmaisee, mink kyrn muotoisesti valo himmenee. Suositan
     arvoa 0.5 useimpiin tarkoituksiin (tllinhn potenssi on
     1/2 eli kyseess on itse asiassa nelijuuri).

     Tm ei sitten ole the one and only tapa, monenlaisia mui-
     takin varmasti lytyy. Kunhan nyt pidn tt tutkimusteni
     perusteella parhaana :)




6. Hidden face removal
----------------------


 6.1 Backface culling

   Backface cullingin idea on yksinkertainen: jos polygoni on
   vrin pin, sit ei tarvitse piirt. Tm ei tietenkn
   toimi, ellei polygoneja ole mritelty oikein, joten kannat-
   taa olla tarkkana (esimerkiksi 3DStudiolla tehdyt objektit
   ovat oikeanlaisia, eli verteksit clockwise-jrjestyksess).
     Onko polygoni vrin pin, on helppo ptt tmn pseudo-
   koodin avulla ((c) Bas van Gaalen / Jeroen Bouwens, don't know
   / remember):

     funktio backface(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.


 6.2 View cone

   View cone tarkoittaa kartion (cone) muotoista nkyviss
   olevaa aluetta katsottaessa johonkin suuntaan. Tekniikka on
   siit ktev, ett jos jokin polygoni tai objekti on
   mritellyn kartion ulkopuolella, sen voi jtt saman tien
   huomiotta. Hienostuneempi (ja nopeampi) view conen muoto
   klippaa kartion reunaan osittain nkyviss olevat polygonit,
   mutta yksinkertaisemmassa 3d-enginess tllaiset tapaukset
   voi hoitaa grafiikkaklippauksilla l. polygonirutiinin sisll.

   Homma hoituu yksinkertaisesti vertaamalla 2dx-, 2dy- ja
   z-koordinaattia ruudun rajoihin ja toimimalla tulosten
   mukaan. Z-koordinaatin tapauksessa polygoni jtetn
   piirtmtt, jos sen yhdenkin pisteen z on pienempi kuin
   nolla, koska muuten kypi piirtorutiineissa hiukan hpsti.
   Jos scenesi alkaa pukittaa Z-comparen kanssa, sinun on
   turvauduttava polygonien 3d-klippaukseen.
   Pseudoa:

     if
       KAIKKIEN verteksien z:t positiiviset
      and
       JONKIN verteksin x vlill 0..MAX_X
      and
       JONKIN verteksin y vlill 0..MAX_Y
      then
       piirr.

   6.2.1 Polygonien 3d-klippaus

     Tm onkin hiukan kinkkisempi homma. Helpointa on, jos
     enginesi sallii n-kulmaiset polygonit, koska klipatusta
     kolmiosta saattaa tulla nelikulmio, jolloin siit pitisi
     kolmiosysteemiss muodostaa kaksi kolmiota. Ei kiva.
     Anyway, pseudoa kehiin:

       ; tutkitaan zetat
       kaikki_zetat_ulkona=true
       kaikki_zetat_ruudulla=true
       for a=0 -> verteksien_mr_polyssa-1
         if z[a]>1 ; varmuus paras...
           kaikki_zetat_ulkona=false
         else
           kaikki_zetat_ruudulla=false

       if (not kaikki_zetat_ulkona) and (not kaikki_zetat_ruudulla)
         klippaa zetat
         kaikki_zetat_ruudulla=true

       konvertoi verteksit 2d:hen

       if kaikki_zetat_ruudulla
         ; tutkitaan kaikkien verteksien 2d-x ja -y
         kaikki_verteksit_ulkona=true
         kaikki_verteksit_ruudulla=true
         for a=0 -> verteksien_mr_polyssa-1
           if (2d_x[a] vlill 1..MAX_X) and (2d_y[a] vlill 1..MAX_Y)
             kaikki_verteksit_ulkona=false
           else
             kaikki_verteksit_ruudulla=false

         if (not kaikki_verteksit_ulkona)
          and (not kaikki_verteksit_ruudulla)
           klippaa
           kaikki_verteksit_ruudulla=true

         if kaikki_verteksit_ruudulla
           piirr

     Siin siis pperiaate. Klippaus hoituu kyttmll suoran
     parametriesityst ((x0,y0,z0) = verteksin 1 koordinaatit
     jne):

       x = x0 + t*(x1-x0)
       y = y0 + t*(y1-y0)
       z = z0 + t*(z1-z0)

     Z-klipin tapauksessa merkitn z=1 ja ratkaistaan t:

       z0 + t*(z1-z0) = 1
       t = (1-z0) / (z1-z0)

     Tm sijoitetaan x:n ja y:hyn:

       x = x0 + (x1-x0)*(1-z0)/(z0-z1)
       y = y0 + (y1-y0)*(1-z0)/(z0-z1)
       z = 1

     Ja siin ovat uuden verteksin koordinaatit!

     Esim.
                     z=1       v1
                          __--
                      __--    \
                   __--         \
               v3--___          \
                      ---___      \
                           ---___ \
                                 ---
             Ruudulla  Ulkona       v2

     Muodostetaan suorat (v3,v1) ja (v3,v2), klipataan ne, ja
     kytetn tulosverteksej uusina v1:n ja v2:na.
     Ongelmaksi saattaa tuntua muodostuvan se, miss jrjes-
     tyksess nm uudet verteksit laitetaan klipattuun poly-
     tauluun; backface culling ei tykk yhtn hyv jos ne
     miskii sinne miten sattuu. Ei paniikkia: tehdnkin
     backface culling jo ennen klippauksia! Klipattuja polyjahan
     ei kytet kuin yhdell framella.
     N-kulmioita kytettess verteksit on silti hyv laittaa
     jrjestyksess faceen, koska rutiinit heittvt ainakin
     vasemman ja oikean reunan tekniikalla muuten melkoista
     sontaa ruudulle. Jrjestys on siis tm: (uusi_v1,uusi_v2,
     ...,uusi_vn). Eli jos ollaan klipattu suora (v1,v2) siten
     ett v1 j ruudulle, laitetaan uudeksi v2:ksi klippaus-
     koordinaatit v1:n silyess ennallaan.


 6.3 Portalit

   Portalit ovat todella yksinkertaisia je helppoja ymmrt
   ja toteuttaa. Niill siis saadaan rajattua piirrettyjen
   polyjen mr, ettei koko scene tarvitse koko ajan kelata.

   Portalit perustuvat siihen, ett koko scene on tehty
   huoneista. Jokainen huone sislt omien polyjensa lisksi
   nkymttmt pinnat niiss kohdissa joista nkyy toisiin
   huoneisiin. Nm nkymttmt pinnat ovat portal-kolmioita,
   ja niit ksitelln kuin mit tahansa polya, eli ne (tai
   siis pteverteksit) pyritelln aivan normaalisti. Ero
   normaaleihin polyihin on siin, ett kun portal-kolmio
   pitisi piirt, siirrytnkin sen osoittamaan huoneeseen
   jos se on nkyviss. Nin jatketaan kunnes olemme tyytyvisi
   piirrettyyn tulokseen.
   Kannattaa huomata, ett niitkn portal-pintoja, jotka ovat
   liian kaukana, ei kannata ksitell. Toinen huomionarvoi-
   nen asia on, ett seuraavien huoneiden pinnat voivat nky
   vain edellisten huoneiden portal-pintojen "lpi".
   Portalien toteuttamistekniikoita on monia. Jotkut niist
   tekevt niin, ett jokainen huone koostuu vain convex
   -polyista, ja kaikki polyt klipataan sitten lennossa portal
   -alueeseen. Muitakin tapoja on, rajana mielikuvitus.
     Ja kaikkihan tietysti hoksasivat heti ettei portaleita
   voida kytt kuin tietyntyyppisiss sceneiss... :/ Tosin
   erilaisia variaatioita lytyy, esimerkiksi sellainen ett
   tehdn tarkistuspiste jonka toisella puolella oltaessa ei
   tiettyj osia scenest piirret (kytnnllinen demoissa!).




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


 7.1 Frameskip

   Tss esitelln vain yksi tekniikka tmn tekemiseksi. Jos se
   ei jostain tysin ksittmttmst syyst kelpaa neidille, hn
   on vain hyv ja lukee Midaksen dokumentit.
     Frameskipin 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.

   Toinen mahdollisuus frameskipin toteuttamiseen on tm: tekee
   interruptin, joka pivitt kaikkia muuttujia 70 kertaa sekassa
   ja sitten rutiinit piirtvt mink kerkevt, kuitenkin niin,
   ett eivt ylit tuota seitsemkymment. Not bad, this one,
   either.


 7.2 Assembly-optimointia

   (
     Kirjailija : Henri 'RoDeX' Tuhkanen
     Email      : henri.tuhkanen@mbnet.fi
     Minut saa mys kiinni MBnetin ohjelmointi/demoscene alueilta.
     Gruupit    : CyberVision, Embrace, the Damned, Hard Spoiled,
                  tAAt, Regeneration, Magic Visions ja pari muuta,
                  joita en muista. ;)
     Saavutukset: 7. sija asm'96 4k-introcompossa.
     Releassit  : Paljon sorsaa, jotka eivt ole viel jakelussa.
     Esittely   : Olen 19v 'sisilmaihminen', joka koodaa about
                  aina, lhinn 3Dt/gfx. Pyrin julkaisemaan kaiken
                  koodin jota en ole tehnyt kaupalliseen tarkoituk-
                  seen. Yritn mys seurailla kaikkea, mit scenell
                  tapahtuu. En pihtaa koodaamiseen liittyv materi-
                  aalia, mutta annan lhinn vain algoritmeja, koska
                  en pid rippaajista. En pelk olla vrss ja
                  haluan oppia kaiken ohjelmoinnista ja tietokoneista.
                  (:
   )

   Assembly-koodin optimointi on kynyt pentiumin tultua melko moni-
   mutkaiseksi. Tmn tekstinptkn on tarkoitus selvitt pentiumin
   salat ja se, miten penalla voi saada aikaa jopa tuplasti nopeampaa
   koodia kuin mik normaalisti luulisi olevan mahdollista.
     Pentiumin mukana tuli pc-maailmassa tutuksi ksite pairaaminen ja
   nopea matikkaprossu. Nyt kerron, miten pairaaminen toimii ja miten
   pystyt hydyntmn sit omissa rutiineissasi.

   Pentium-prosessoreissa on itse asiassa kaksi prosessoria rinnan.
   Nist prosessoreista vain toinen on tydellinen. Tt kutsutaan
   U-, toista V-pipeksi. V-pipe on karsittu niin, ett se pystyy
   suorittamaan vain hypyt ja joitain yleisimpi kskyj, kuten mov,
   add, shl ja lea. Pairaaminen on niden kahden prosessorin toimintaa
   yht aikaa samalla kellotaajuudella, eli U:n suorittaessa vaikkapa
   movia, V suorittaa addia. Pairaaminen ei kuitenkaan ole nin yksin-
   kertaista, vaan toimii vain tietyiss olosuhteissa.

   Esim.

     Oletetaan, ett kaikkien kskysarjojen alussa tilanne on neutraali,
     eli ensimminen ksky suoritetaan U-pipess (tm tilanne saadaan
     toteutettua vaikka laittamalla 'sti' sarjan eteen).

       mov   eax,ebx ; suoritetaan U-pipess.
       add   ecx,edx ; suoritetaan V-pipess, koska mikn ei sit est.
                     ; Tm ei vie kytnnss yhtn aikaa; se tapahtuu
                     ; yht aikaa U-pipess olevan operaation kanssa.

       mov   eax,ebx ; suoritetaan U-pipess.
       add   eax,2   ; ei voida suorittaa yht aikaa edellisen movin
                     ; kanssa koska tarvitsee luonnollisesti ax:n uuden
                     ; tuloksen.

   Eli ensimminen olennainen asia, jonka pit olla kohdallaan, on
   rekisterien kytt. Yleisesti: jos johonkin rekisteriin kirjoitetaan
   ja seuraavassa kskyss samaa rekisteri kytetn, niin kskyt eivt
   pairaa. Jos taas rekisterist luetaan, niin seuraava ksky pairaa
   vaikka siin kytettisiin tt rekisteri (joko lukemiseen tai kir-
   joittamiseen). Oletus tietysti on, ett kskyt itsessn pairaisivat
   keskenn.

   Esim.

     mov   eax,ebx ; 1 kello; suoritetaan U-pipess.
     add   eax,ecx ; 1 kello; suoritetaan U-pipess
                   ; edellisen movin jlkeen.
                   ; yht. 2 kelloa

     add   ebx,eax ; 1 kello; suoritetaan U-pipess.
     mov   edx,eax ; 0 kelloa; suoritetaan V-pipess.
                   ; yht. 1 kello

   Edellisiss kskyiss olemme olettaneet, ett kummatkin kskyt pai-
   raavat kummassakin pipess. Vaikka tm on yleisin ja selkein
   tilanne, ei se yksinn anna sit viimeist sysyst, joka tekee
   koodista todella nopeaa.

   Esim.
                         ; kellot
     mov   ebx,edx       ;   1
     shl   eax,1         ;   1  (ei pairaa V:ss)
                         ;  ---
                         ;   2
   Mutta:

     shl   eax,1         ;   1
     mov   ebx,edx       ;   0
                         ;  ---
                         ;   1

   Nin, koska shl pairaa vain U-pipess.

     cmp   eax,ebx       ; 1 (U-pipess)
     jne   (johonkin)    ; 0 (pairaa vain V-pipess)

   Flagit pivittyvt niin nopeasti ett ne ovat kytettviss jo
   samalla kellolla, jolloin esim. ylloleva tapaus toimii.

   On useita kskyj, jotka pairaavat vain U- tai V-pipess tai
   eivt ollenkaan. Esimerkiksi shl ja rol pairaavat vain U-pipess.
   Hypyt pairaavat vain V-pipess, cli ja mul taas eivt pairaa
   missn tapauksessa (varaavat molemmat pipet). Kun alkaa selvit-
   t mitk kskyt pairaavat minkkin kanssa, huomaa, ett alkaa
   muodostaa pareja kskyist ja/tai siirrell kskyj paikkoihin,
   joissa ne pairaavat. Tosin useat kskyt toimivat hyvinkin tapaus-
   kohtaisesti.
     Kskyjen pairaussnnt lytyvt ainakin HelpPC:n pentium-pi-
   vityksest, joka lytyy esim. MBnetist.

   Nyt alkavat pairauksen snnt olla teoria puolelta selvill,
   joten on aika soveltaa.

   Optimointiesimerkiss teemme hitaasta viivanpiirtorutiinin inner
   loopista nopean.

     UV: pairaa kummassakin pipess
     NU: pairaa vain U-pipess
     NV: pairaa vain V-pipess
     NP: ei pairaa
     ? : Muistin kytn nopeus riippuu monesta asiasta.
         Oletamme, ett tilanne on ihanteellinen.

   Linedraw, innerloop 320*200*256-tilassa. Horizline eli piirt
   vaakaviivan.
   Looppiin tultaessa rekistereiden sisllt ovat seuraavat:

     eax = alku_x*256
     edx = alku_y
     [xp]=x_kerroin*256 (dd eli 32 bit)
     [yp]=y_kerroin     (dd eli 32 bit)
     ebx = 0

   Koodi:
                             ; Kellot Pipe Pairaus Kommentti
   @@inner:                  ; -----------------------------
     lea   edi,[edx+edx*4]   ;   1     U     UV     edi=edx*5
     mov   bl,ah             ;   0     V     UV     ebx=ax/256
     shl   edi,6             ;   1     U     NU     edi*=64
     add   edi,ebx           ;   1     U     UV     edi+=ebx
     add   edi,0a0000h       ;   0     U     UV     edi+=ruudunalku
     mov   [edi],b 10d       ;   1?    U     UV     [edi]=10
     add   edx,[yp]          ;   0?    V     UV     edx+=[yp]
     add   eax,[xp]          ;   1?    U     UV     eax+=[xp]
     dec   ecx               ;   0     U     UV     ecx-=1
     jnz   short @@inner     ;   1     V     NV     jump if not zero
                             ;  ---
                             ;   6?

   Pmodea kytetn yleens flatmodessa, joten nyttmuistin alku-
   osoitte joudutaan lismn ediin. Hyppy kanttaa kytt shorttina,
   koska etisyys @@inneriin on tavuissa alle 128; nin sstyy 2 tavua
   kskyn knnetyst versiosta.

   Nyt saamme suoraan pois 'add edi,ebx':n muuttamalla laskun
   'mov [edi+ebx+0a0000h],b 10d':ksi. Jrjestelemme kskyt samalla niin,
   ett ne pairaavat aina kun on mahdollista.

                                       ; Kellot Pipe Pairaus Kommentti
   @@inner:                            ; -----------------------------
         lea   edi,[edx+edx*4]         ;   1     U     UV     edi=edx*5
         mov   bl,ah                   ;   0     V     UV     ebx=ax/256

         shl   edi,6                   ;   1     U     NU     edi*=64
         add   edx,[yp]                ;   0?    V     UV     edx+=[yp]

         mov   [edi+ebx+0a0000h],b 10d ;   1?    U     UV     []=10
         add   eax,[xp]                ;   0?    V     UV     eax+=[xp]
 
         dec   ecx                     ;   1     U     UV     ecx-=1
         jnz   short @@inner           ;   0     V     NV     jump
                                       ;  ---
                                       ;   4?

   Nopeus lisntyi huomattavasti. Miinuksena koodista tulee seka-
   vampaa, mutta se hinta on halpa nopeudesta. Tosin rivien, joilla
   rekisteriin listn [muuttuja], kellot ovat erittin kyseen-
   alaisia ja luultavasti jotain ihan muuta kuin nollia. Joka ta-
   pauksessa looppi pairaa nyt tehokkaimmillaan. Tuntuu vain turhal-
   ta kertoa aina y 320:ll.
   Tm ongelma ratkeaa helposti kertomalla y:n kerroin headerissa
   320:lla, jolloin loopista tulee seuraavanlainen:

     eax=alku_x*256
     edi=alku_y*320
     [xp]=x_kerroin*256
     [yp]=y_kerroin*320
     edi=0a0000h
     ebx=0
                                   ; Kellot Pipe Pairaus Kommentti
   @@inner:                        ; -----------------------------
     mov   bl,ah                   ;   1     U     UV     ebx=ax/256
     add   eax,[xp]                ;   0?    V     UV     eax+=[xp]

     mov   [edi+ebx],b 10d         ;   1?    U     UV     []=10
     add   edi,[yp]                ;   0?    V     UV     edi+=[yp]

     dec   cx                      ;   1     U     UV     cx-=1
     jnz   @@inner                 ;   0     V     NV     jump if not zero
                                   ;  ---
                                   ;   3?

   Nopeaa? Tosin tm on vain harhaa, sill nopeuteen vaikuttavat
   mys cache missit, joita tapahtuu usein, ja interruptit, jotka
   keskeyttvt pairauksen ja usein tuottavat ikvsti viel cache
   missin kaiken oman suoritusaikansa lisksi.

   Cache miss on tilanne, jossa tarvittavaa dataa ei lydykn proses-
   sorin sisisest muistista (1-level cache), jolloin data joudutaan
   hakemaan ulkoisesta vlimuistista (2-level cache). Tm tiet
   yleens parin kellon lisyst looppiin ja pairauksen keskeytymist.
   Jos dataa ei lydy 2-level cachestakaan, se joudutaan hakemaan
   varsinaisesta muistista, jolloin aikaa kuluu jo yli 10 kellon verran.
   Nm hakuajat riippuvat suoraan siit, mit muistia koneessa on, ja
   erot voivat olla isojakin. Esim. 60ns multiaccess-edolta voi kulua
   5 kelloa ja 70ns tavalliselta muistilta 15 kelloa. 2l-cachessa erot
   pipelineburstin ja jonkun muun vlill voivat vaihdella parikin
   kelloa. Tss vaiheessa puhkeaa siis se kupla, ett kaikki on kiinni
   koodista :(

   Mutta emme ole sentn ihan koneen armoilla. Cacheihin ei voi suo-
   raan vaikuttaa, mutta muistin ksittely voi nopeuttaa jrjestele-
   mll datat niin, ett esim. samassa loopissa kytettvt muuttujat
   ovat muistissa perkkin ja dataa siirretn 32 tavun sarjoissa. 32
   tavua on se maaginen palikka, mink ymprill pentium-optimoinnissa
   pyritn: dataa siirretn cachen ja perusmuistin vlill 32 tavun
   palikoissa, ja tllinen palikka silyy aina kokonaisena. Koodi ja
   data kannattaa siis yritt tehd 32 tavun aligmenttiin. Erityisesti
   looppien ja niiden muuttujien on syyt olla niin vhss mrss
   palikoita kuin mahdollista.

   Aina kannattaa kytt mielikuvitusta kun tekee isompia tai useita
   looppeja, jotta saa kaiken mahdollisen hydyn irti 1l-cachesta.


 7.3 Paletinkvantisointi

   [Tekstin kirjoitti alunperin Sampsa Lehtonen, julkaistaan edi-
   toituna. Kiitos.]

   7.3.1 Mit se oikeastaan on?

     Paletinkvantisointi on tapa, jolla yritetn saada 256 (tai
     vaikka 16 ;) -vrinen tila mahdollisimman tyylikkksi opti-
     moimalla palettia. Kvantisointimenetelmi on useita, tss
     selostetaan Local K Mean -niminen systeemi. Se on todella hidas,
     mutta tulos on vastaavasti erittin nyttv. Kajkin kuulemma
     kyhsi tmn selostuksen pohjalta 'ihan kivan' systeemin :)

   7.3.2 Abstrakti lhestymistapa

     Abstarkisti ajateltuna LK toimii seuraavasti: kvantisoitavan
     kuvan ja/tai vrien arvot kuvitellaan palloina vriavaruudessa
     (XYZ = RGB), joka on kuution muotoinen. Mit enemmn tietty
     vri on, sen "isompi" pallo on. Avaruuteen listn paletti-
     palloja, jotka voivat liikkua vapaasti toisin kuin vripallot,
     jotka ovat aina paikallaan. Palettipalloja on sen verran kuin
     halutulla paletilla on kokoa (256 vri = 256 palloa).
       Suoritetaan seuraava prosessi: jokainen vripallo kydn
     lpi, ja ne kutsuvat kullakin hetkell lhinn olevaa paletti-
     palloa. Mit isompi vripallo on, sit voimakkaammin se kutsuu.
     Nyt suunnilleen kaikkiin palettipalloihin kohdistuu voima, joka
     siirt palloa suuntaan tai toiseen. (Pallon uusi koordinaatti
     on sit kutsuvien vripallojen vriarvojen keskiarvo, siis vrien
     summa jaettuna pallojen mrll.)
       Ne palettipallot, joihin ei kohdistu voimaa, "telewarppaavat"
     jonkun vripallon kohdalle tai viereen. Nin palettipallot liik-
     kuvat avaruudessa, kunnes niiden liike hidastuu tietyn tason
     alle (uskokaa, kyll hidastuu). Sitten uusi paletti voidaan
     lukea palettipallojen koordinaateista.

   7.3.3 Teknisempi lhestymistapa

     Kuvitellaan, ett on tysvrikuva, joka pit muuttaa 256-vri-
     seksi. Aluksi teemme kuvasta histogrammin, joka kytt 15-bit-
     tisi lukuja (tai mit tahansa 3*x-bittisi lukuja; 15-bittinen
     taulukko on 65536 tavun kokoinen jos yhdelle vrille on 2 tavun
     suuruinen laskuri, 18-bittinen taulukko = 524288 tavua jne :)
     HUOM! Aluksi histogrammi pit tietysti nollata.

     Nyt teemme toisen taulukon jossa on lista niist vreist, joita
     kuvassa oikeasti on. Kymme siis histogrammin lpi, ja jokaisessa
     kohdassa, jossa vri on enemmn kuin nolla, pistmme taulukkoon
     vrin arvon ja mrn.
     Taulukko voi olla esim. muotoa

       typedef struct {
         unsigned char R,G,B;         // vriarvot
         unsigned long count;         // vrimr kuvassa
       } colorListStruct;
       colorListStruct colorList[32768];

     Lisksi eri vrien mr talletetaan esim. colorListCountiin.

     Seuraavaksi teemme peruspaletin. Olkoon se muotoa

       unsigned long palette[256][3]; // 3: R,G & B

     Peruspaletin lisksi tarvitsemme kaksi muuta muuttujaa: 256:n
     vrin kokoisen vrilaskuritaulukon muotoa

       unsigned long colorSum[256][3]; // 256 vri, 3 = R,G & B,

     ja laskuritaulukon muotoa

       unsigned long colorCount[256];

     (Voidaan yhdist kyll colorSummiinkin..)
     Sitten viel muuttuja joka pit kirjaa paletin muutoksista:

       unsigned long variance;

     Seuraavaksi kydn lpi seuraavia askelia:

       1) Nollaa colorSum- ja colorCount-taulukot, ja tyt palette
          colorListin alussa olevilla vreill

       2) Ky lpi kaikki vrit colorList:ist (c = 0..colorListCount)
         a) ota colorListist vri c
         b) katso sille lhin vri palette-muuttujasta (saadaan numero
             x=0..256)
         c) lis lhimmn vrin colorSum-taulukkoon colorListist
            saatu vri
            esim. colorSum[x][0] += colorList[c].R;
                  colorSum[x][1] += colorList[c].G;
                  colorSum[x][2] += colorList[c].B;
         d) lis colorCountia kohdassa x (colorCount[x]++;)

       3) Nollaa variance

       4) Ky lpi kaikki vrit peruspaletista (c = 0..255)
         a) jos colorCount > 0
              laske R, G ja B-arvot colorSummin ja colorCountin avulla
              eli keskiarvovri:
              R = colorSum[c][0] / colorCount[c];
              jne...
            muuten
              ota satunnainen luku colorListist
              R,G & B <- colorList[RANDOM]
         b) mittaa paletin vaihtelu:
            temp = abs(R-palette[c][0]); //punaisen vrin vaihdos
            variance+=temp;              //ota yls
            jne...
         c) kirjaa uusi vri: palette[c][0] = R jne..

       5) nollaa colorSum ja colorCount

       6) jos variance > MAX_VARIANCE mene kohtaan 2
          (MAX_VARIANCE on raja, jolloin paletti on "valmis". Mit
          pienempi luku, sen hitaampi prosessi.)





EOF
