


      3DICA.TXT v1.1 (C) Ica/2 1996,1997
      ---------




Sisllys
--------


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

   1       Vektorit
     1.1   Yleist
     1.2   Vektorin yhtl
     1.3   Vektorin pituus
     1.4   Vektorien yhteenlasku
     1.5   Skalaaritulo
     1.6   Vektoritulo
     1.7   Skalaarikolmitulo

   2       2D- ja 3D-maailman yhteys

   3       3D-pyritysten perusyhtlt

   4       Jrkev optimointikohde: sinien ja kosinien
           taulukoiminen

   5       Pisteen pyrittminen kaikkien akseleiden ympri
           samanaikaisesti

   6       Polygonit

   7       Idea enginen toteuttamiseen

   8       Z-sorttaus

   9       Hidden face removal

   10      Flat-sheidaus
      10.1 Z-Flat
      10.2 Lambert Flat

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


 0.1 Kuka on Ica/2?

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

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


 0.2 Miksi ihmeess kirjoitin tmn dokumentin?
 
  Minua ovat jo jonkin aikaa hirinneet eptoivoisten
  ylastetta pttmss olevien tai lukion vasta
  aloittaneiden nuorten koodaajanalkujen ruinaukset kunnon
  opastuksesta 3d-koodauksen maailmaan. Muutama kuukausi
  sitten kirjoitin mielestni sangen tyhjentvn selostuksen
  MBnetin PC-ohjelmointi-alueelle, mutta kvi ilmi, etteivt
  monet olleet lainkaan ymmrtneet mist tekstiss puhuttiin.
  Niinp ptin alkaa kaiken aivan alusta - selostaa perti
  vektoreista lhtien, miten kolmiulotteisen maailman koodaus
  PC:n (tai miksei jonkin muunkin koneen) ruudulle oikein
  tapahtuu.
    Jos ihmetytt ett mihin niit vektoreita sitten kytetn,
  lue vaikkapa kappale 10.2 jossa ksittelen Lambert Flat
  -sheidauksen.


 0.3 Kenelle tm dokumentti on tarkoitettu?

  Tmn saa lukea kuka vain joka tuntee olevansa sen tarpeessa :)
  Kohderyhmksi suunnittelin kuitenkin 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.
    Lukemallahan se selvi ett onko tst tekstist jotain
  hyty :)


 0.4 What's new in this *hot* version 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 :)


 0.5 Gr33tZ

  iCA/2 gRe37z th3 f0ll0WiNG d00dz...

  Friction: Esit taas kanaa=)
  Ducha: Keep up the good work, pal!
  Ripple: Hikari. Ei kukaan VOI olla noin hyv :)
  Firestorm: Warp! Warp! Warp!
  Frac: Joko 3d-starfield toimii?-)
  Crane: Kiitn texturemappaushelpist. Harmi vain se tuhoutui :/
  MiG: "Nyt on Kone. Nyt on KONE! AAAAAAAA!!!"
  Oca: Pelataanks Quakee taas joskus? :)
  RRRulettavaKonna: Millos kilpailutetaan viivarutiineita? :D
  KajBjrklund: S teet sitten seuraavan osan thn sarjaan ;)
  HezeRaunio: Prt.
  JukkaPManninen: Joko toteutit sen kuutio-jonka-jne-efektin?-)
  WesaWepslinen: Win95! Win95! Win95! ..on *sielt* :)
  JereSanisalo: Mua harmittaa vielkin se datathtijuttu :/
  PanuArtimo: Sun Warp-desktoppi on RUMA! :) (MBnet-versio)
  HenriKronlund: Miten menee? Ei ole messuiltu sitten TZ:n kaatumisen!
  IlkkaWuorenMaa: Sait greetit jo thn versioon :D
  KimmoVihola: Eivt ne vektorit nyt *niin* vaikeita ole ;)
  KaikkiPiraatit: Haistakaa <erittinrumasanajotaeiviitsithnlaittaa>.


-- asiaan... --




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


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


 1.2 Vektorin yhtl

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

  (yhtl esitetn siis normaalisti alemmassa, sievennetyss
  muodossa)
  HUOM! LOPPUPISTEEN koordinaatista VHENNETN aina ALKUPISTEEN
  koordinaatti!


 1.3 Vektorin pituus

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


 1.4 Vektorien yhteenlasku

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

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


 1.5 Skalaaritulo

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

  HUOM! Vektorit ovat toisiaan vastaan kohtisuorassa, kun niiden
  vlinen kulma on 90 astetta eli cos(a,b)=0 eli ab=0!

 TEHTV: Mrit vektorien  a+b  ja  a-b  vlinen kulma, kun
 a = 3i + j  ja  b = i - 7j.
 (ratk. 132.3 astetta)


 1.6 Vektoritulo

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

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

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

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

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

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

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

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

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


 1.7 Skalaarikolmitulo

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

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

  ja lasketaan muuten tsmlleen samalla tavalla.

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

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

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

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




2. 2D- ja 3D-maailman yhteys
----------------------------


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

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

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

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

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

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

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

 Kaava on siis seuraavanlainen:

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

 PERSPECTIVE kertoo, kuinka lhelt 3d-maailmaa tarkastellaan
 (mit pienempi arvo, sit lhempn ollaan). Kokeilemalla
 selvi kuhunkin tilanteeseen sopiva arvo, usein kytetn
 arvoa 256. Tllin z-akselin 0-kohta eli kohta, jossa
 z-koordinaatin arvo ei vaikuta x:n ja y:hyn, on tietysti sama
 eli 256, ja muuteltaessa z:n arvoa reaaliaikaisesti huomataan
 3d-efekti.
   HUOM! Jos z:n arvoksi tulee kesken ohjelman ajon nolla, ei
 tapahdu mitn kovinkaan hienoa. Siisp vlttkmme tllaista
 sattuessaan niin kovin valitettavaa tapahtumaa mahdollisuuksien
 mukaan!

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

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

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




3. 3D-pyritysten perusyhtlt
------------------------------


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

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

 -- ok --

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

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

 Uudet koordinaatit ovat siis

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

 miss r on pyrityssde. Kaavojen (lukion taulukoista)

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

 avulla muutamme uusien koordinaattien yhtln muotoon

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

 Toisaalta seuraavan kuvan perusteella voimme ptell lis:

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

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

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

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

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

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

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

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

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

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

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

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

 Kokeillaan jlleen (ympyr ruudulle):

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

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




4. Jrkev optimointikohde: sinien ja kosinien taulukointi
----------------------------------------------------------


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

   (sinus ja cosinus ovat 256-alkioisia signed 16bit-taulukoita)

   for (a=0 --> 255)
     sinus[a]   = SCALE * sin( a * pi / 128 )
     cosinus[a] = SCALE * cos( a * pi / 128 )
   endfor

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

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

   kulma_rad = kulma_asteina * pi / 180.

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

 Ylloleva pit sitten luultavasti lukea moneen kertaan :)




5. Pisteen pyrittminen kaikkien akseleiden ympri samanaikaisesti
-------------------------------------------------------------------


 Kaikkien akseleiden ympri pyrittminen ei tapahdukaan yht
 helposti kuin yhden akselin ympri. Itse kytn seuraavaa
 tekniikkaa (omaksuttu Bas van Gaalenilta, tnx vaan sinnekin
 pin :)
  1) x-koordinaatti y-akselin suhteen
  2) y-     "       x-   "       "
  3) z-     "       y-   "       "
  4) z-     "       x-   "       "
  5) y-     "       z-   "       "
  6) x-     "       z-   "       "

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

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

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

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

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

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




6. Polygonit
------------


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

 Pseudona:

** begin triangle **

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

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

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

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

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

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

** end triangle **

** begin horizline **

  - a on apumuuttuja

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

** end horizline **


 ..n ti iksplneissn:

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

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

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

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

 eli kun y-koordinaatti on y[1], x-koordinaatti on vastaavasti
 x[1], mik lienee melko jrkev. Ahaa! d on siis jonkinlainen
 kerroin, jonka avulla x- ja y-koordinaatti suhteutetaan
 toisiinsa! Very, VERY smart, I'd say!

 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. X-klippaus on helppo tyrkt
 horizline-rutiiniin:

** begin horizline **

  - a on apumuuttuja
  - max_x on ruudun leveys

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

 Y-klippaukset on suunnilleen yht nppr tehd looppien
 pituutta stelemll tyyliin

  - max_y on ruudun korkeus

   jos (y[2]<0)
     heissan, lopetetaan piirtminen thn koska silloin
     kaikki muutkin y-arvot ovat pienempi kuin nolla (nehn
     on jrjestetty rutiinin alussa)
   muussa tapauksessa jos (y[0]>max_y)
     heissan, sama syy mutta kaikki arvot ovat suurempia
     kuin max_y
   jos taas (y[1]<0)
     ys[0] = 0
     ys[1] = 0
   mutta jos vain (y[0]<0)
     ys[0] = 0
   jospa sitten (y[1]>max_y)
     ys[1] = max_y
     ys[2] = max_y
   jos sittenkin vain (y[2]>max_y)
     ys[2] = max_y
   nytloppujossittelu

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

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

 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.




7. Idea enginen toteuttamiseen
------------------------------


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

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

 Nyt voidaan osoittaa vertex- eli pistetaulukkoon npsksti

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

 Polygoneille voi samaan taulukkoon mritt mys vaikkapa
 vriarvon.

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




8. Z-sorttaus
-------------


 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.

 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 BSP-puu, joka on jo huomattavasti
 kehittyneempi algoritmi -- ja toisaalta mys huomattavan
 paljon vaikeatajuisempi ja vaikeampi toteuttaa.




9. Hidden face removal
----------------------


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

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

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

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




10. Flat-sheidaus
-----------------


 Nyt on sitten se polygonikin ruudulla, mutta nytt sekin aika
 tylslt kun vrit pysyvt koko ajan samoina; realismi olisi
 kiva juttu.
   Ksittelen tss kappaleessa kaksi helpointa sheidausta, jotka
 tuntevat nimet Z-flat ja Lambert Flat.


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


 10.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 ;)
    Idea on seuraavanlainen: annetaan valonlhteelle i-, i- 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




That's it, mutta viel tahtoisin sanoa yhʇNO CARRIER
