Advertisement
Guest User

Untitled

a guest
May 26th, 2016
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 327.45 KB | None | 0 0
  1. ▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄
  2. █▓▒░ ░█ █▓▒░ ░▒▓▒█ █▒░ ░▒▓▒░ █ █░ ░▒▓▒░█ █▓▒░ ░▒▓█ █ ░▒▓▒░ ░▒█
  3. █▒░ ░▒█ █▒░ ░▒▓▒░█ █░ ░▒▓▒░ ░█ █ ░▒▓▒░ █ █▒░ ░▒▓▒█ █░▒▓▒░ ░▒▓█
  4. █░ ░▒▓█ █▒░ ░▒▓▒░ ░█ █░ ░▒▓▒░ ░▒▓█ █░▒▓▒░ ░▒█▒░ ░▒▓▒░█ █▒▓▒░ ░▒▓▒█
  5. █ ░▒▓▒█ █░ ░▒▓▒░ ░▒█ █ ░▒▓▒░ ░▒▓▒█ █▒▓▒░ ░▒▓▒░ ░▒▓▒░ █ █▒▓▒░ ░▒▓▒░ █
  6. █░▒▓▒░█ █ ░▒▓██ ░▒▓█ █░▒▓▒░██▒▓▒░█ █▓▒░ ░▒▓▒░ ░▒▓▒░ ░█ █▓▒░ ░██▒░ ░█
  7. █▒▓▒░ █ █░▒▓▒██░▒▓▒█ █▒▓▒░ ██▓▒░ ░██▒░ ░▒█▒░ ░▒█▒░ ░▒█ █▒░ ░▒██░ ░▒█
  8. █▓▒░ ░█ █░▒▓▒░██▒▓▒░ █▒▓▒░ ░██▒░ ░▒██░ ░▒▓█░ ░▒▓█░ ░▒▓█ █░ ░▒▓██ ░▒▓█
  9. █▒░ ░▒█▄▄▄█▒▓▒░ ░▒▓▒░ ░█▓▒░ ░▒▓▒░ ░▒▓██ ░▒▓▒█ ░▒▓▒█ ░▒▓▒██░ ░▒▓▒░ ░▒▓▒░█
  10. █░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░██▒▓▒██░▒▓▒░██ ░▒▓▒░ ░▒▓▒░ █
  11. █ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ░▒▓▒░ ██▓▒░██▒▓▒░ ██░▒▓▒░ ░▒▓▒░ ░█
  12. █░▒▓▒░ ░▒▓▒░ ░▒▓██ ░▒▓▒░ ░▒▓▒░██▒▓▒░ ░▒▓▒░ ░██▒░ ██▓▒░ ░██▒▓▒░ ░██▒░ ░▒█
  13. ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀
  14. Laaman tie DJGPP-peliohjelmointiin versio 2.10. By Jokke / BAD KARMA
  15. Copyright (C) Joonas Pihlajamaa 1997. All rights reserved.
  16.  
  17.  
  18. Sisällysluettelo:
  19.  
  20. 1. Esittely
  21. 1.1 Disclaimer
  22. 1.2 Mistä uusin versio?
  23. 1.3 Huomattavaa lukijalle
  24. 1.4 Kenelle tämä on tarkoitettu?
  25. 1.5 Kreditsit
  26. 1.6 Versiohistoria
  27. 1.7 Yhteystiedot
  28. 1.8 Esimerkkien kääntäminen
  29. 2. Alkeet
  30. 2.1 DJGPP - vaikea, suuri, monimutkainen, omituinen, hidas?
  31. 2.2 Grafiikkaa - mitä se on?
  32. 2.3 Paletti - hörhelöhameita ja tanssia?
  33. 3. Peruskikkoja
  34. 3.1 Kaksoispuskuri - luonnonoikku, horoskooppi?
  35. 3.2 PCX-kuvien lataus - vain vähän oikaisemalla
  36. 4. Bittikartat ja animaatiot
  37. 4.1 Bitmapit - eikai vain suunnistusta?
  38. 4.2 Animaatiot
  39. 4.3 Pitääkö spriten törmätä? Entä coca-colan?
  40. 4.4 Maskatut spritet
  41. 5. Hieman kehittyneempää yleistavaraa
  42. 5.1 Näppäimistön käsittely - ja nyt meillä on hauskaa
  43. 5.2 Fixed point matematiikka
  44. 5.3 Lookup-tablet ja muita optimointivinkkejä
  45. 5.4 Väliaikatulokset ja fontteja
  46. 5.5 Hiirulainen, jokanörtin oma lemmikki
  47. 5.6 Tekstitilan käsittely suoraan
  48. 6. Projektinhallinta
  49. 6.1 Projektien hallinta - useat tiedostot
  50. 6.2 Useiden tiedostojen projektit - kääntäminen ja hallinta
  51. 6.3 Hieman automaatiota - tapaus Rhide
  52. 6.4 Todellista guruutta - salaperäinen make
  53. 6.5 Ammattimaista meininkiä - enginen teko
  54. 7. Kehittyneemmät yksityiskohdat
  55. 7.1 Vauhtia peliin - ulkoisen assyn käyttö
  56. 7.2 PIT - aikaa ja purkkaa
  57. 7.3 Miten peli toimii yhtä nopeasti kaikilla koneilla
  58. 7.4 Yleistä asiaa pelin levityksestä
  59. 7.5 Interpolointi ja viivoja
  60. 7.6 Vapaa skrollaus
  61. 7.7 Sinit ja kosinit sekä plasmaa
  62. 7.8 Paletin kvantisointi ja rekursio - Median cut
  63. 7.9 Lisää paletin kvantisointia - Local K Mean
  64. 7.10 VESA 2.0, rakenteet
  65. 7.11 VESA 2.0, ensimmäiset keskeytykset
  66. 7.12 Miten se todella pitäisi tehdä
  67. 8. Asioiden taustaa
  68. 8.1 Datatiedostot - miten?
  69. 8.2 Läpinäkyvyys ja sen vaihtoehto - shadebobit
  70. 8.3 Motion blur - sumeeta menoa
  71. 8.4 Vektorit pelimaailmassa
  72. 8.5 Musiikkijärjestelmistä
  73. 8.6 Plasma tekee comebackin - wobblerit
  74. 8.7 Prekalkattuja pintoja - ja tunneli
  75. 8.8 Lisää kivaa - zoomaus
  76. 8.9 Polygoneista ja niiden fillauksesta
  77. 8.10 Pari kivaa feikkimoodia
  78. 9. Liitteet, jatkeet ja muu roina
  79. 9.1 Saatteeksi
  80. 9.2 Hieman koodereiden jargonia
  81. 9.3 Lähteet
  82.  
  83.  
  84. 1.1 Disclaimer
  85. --------------
  86.  
  87. Tämän dokumentin ja kaikkien muiden paketin tiedostojen tekijänoikeudet
  88. kuuluvat Joonas Pihlajamaalle, ellei tiedostossa ole toisin ilmoitettu
  89. ja nämä ehdot pätevät kaikkiin paketin tiedostoihin jotka eivät
  90. sisällä erillisiä ehtoja tai joista ei ole näissä ehdoissa erikseen
  91. mainittu. Paketin sisältämän materiaalin käyttö on sallittu vain
  92. allaolevien ehtojen rajoissa. Jos käyttäjä ei hyväksy ehtoja tulee
  93. hänen poistaa tämä paketti ja sen tiedostot. Paketin sisältämän
  94. materiaalin käyttö tarkoittaa käyttäjän hyväksyneen levitysehdot.
  95.  
  96. Dokumentin levitys, monistus ja muu jakelu on sallittu vain
  97. alkuperäisessä, muuttamattomassa muodossa, lukuunottamatta file_id.diz
  98. -tiedostoa, joka voidaan halutessa uudelleennimetä .old- tai
  99. .org-päätteiseksi ja lisätä uusi .diz-tiedosto, jotta kuvaus sopisi
  100. levitettävän BBS-järjestelmän käyttämään formaattiin.
  101.  
  102. Minkäänlaista maksua ei saa periä lukuunottamatta kopiointi- ja
  103. levityskustannuksia, niin kauan kuin niiden yhteenlaskettu summa ei
  104. ylitä 20 suomen markkaa. Lähdekoodin käyttö on sallittu omissa
  105. ohjelmissa, mutta ohjelman dokumentaatiossa täytyy mainita lähdekoodin
  106. lähde. Tutoriaalin kautta opittu tieto on täysin vapaasti
  107. sovellettavissa.
  108.  
  109. Tekijä ei ota minkäänlaista vastuuta paketin tiedostojen toiminnasta tai
  110. tietojen oikeellisuudesta. Minkäänlaista takuuta tutoriaalin sisältämän
  111. informaation käytännöllisyydestä ja virheettömyydestä ei anneta.
  112.  
  113. Jos paketti aiotaan sisällyttää jonkin suuren tiedostopalvelimen,
  114. CD-ROM levyn tai muun vastaavan massalevitykseen tarkoitetun median
  115. jonka oletetaan leviävän suuria määriä olisi tekijälle hyvä ilmoittaa
  116. sähköpostitse tapahtumasta.
  117.  
  118. Tutoriaaliin liittyy myös rajoitettu tyytyväisyystakuu. Jos et jostain
  119. syystä pidä tuotteesta voit poistaa sen määräämättömän ajan jälkeen
  120. ohjelman asennuksesta. Vapautat kiintolevytilaasi ja saat ilman
  121. erillistä maksua kokea tutoriaalin poistamisesta aiheutuvan henkisen
  122. tyydytyksen.
  123.  
  124. Epäselvyydet, puutteet ja huomautukset disclaimerista pyydetään
  125. lähettämään tekijälle.
  126.  
  127.  
  128. 1.2 Mistä uusin versio?
  129. -----------------------
  130.  
  131. Tutoriaalin teko alkoi alunperin MBnetin FAQ-jahkailusta, kun veikkailtiin
  132. tehtäisiinkö PC-Ohjelmointi -alueen kysymyksistä FAQ vai eikö. Minä päätin
  133. sitten tehdä ainakin jotain ja niinpä uusin versio pitäisi olla aina
  134. saatavilla MBnetistä PC-Ohjelmointi -alueelta. Alue tullaan jakamaan
  135. jossain vaiheessa, mutta Apajalta se löytyy ainakin.
  136.  
  137. Lisäksi Laamatutin virallinen kotisivu löytyy osoitteen www.mbnet.fi/~jokke
  138. alta. Tästä osoitteesta pitäisi myöskin löytyä Laamatutin uusin versio
  139. nopeasti ja helposti (jopa nopeammin kuin mitä se tulee MBnettiin).
  140.  
  141. Tiedostonimi on aina LAAMAxyy.ZIP, jossa x on suurempi (major) versionumero
  142. ja yy pienempi (minor). Pitäkäähän silmä tarkkana!
  143.  
  144.  
  145. 1.3 Huomattavaa lukijalle
  146. -------------------------
  147.  
  148. Dokumentin koko on alkuperäisestä jo viisinkertaistunut ja public
  149. betasta ei voine enää puhua. Silti kommentteja täydellisyyksistä,
  150. virheistä ja puutteista tarvitaan ehkä jopa enemmän kuin beta-aikoina,
  151. kun alue on liian laaja yksin tarkistettavaksi. Olen myös kiinnostunut
  152. mahdollisista lisäjuttujen tekijöistä, jolloin luonnollisesti minun ei
  153. tarvitse kirjoittaa kaikkea. Korvauksena pääset sitten kreditseihin ja
  154. dokumenttisi julkaistaan tämän mukana.
  155.  
  156. Teemu Keinonen on jo osallistunut Laamatutin tekoon ja on näinollen
  157. ansainnut erityiskiitokseni samat kiitokset kuuluvat myös 3D-starfield
  158. -selostuksen tehneelle Erik Seesjärvelle. Heidän teoksensa löytyvät
  159. myöskin tästä päähakemistosta nimillä LUVUT.TXT ja STARFLD.TXT. Herra
  160. Seesjärvi koodaa nykyään kunniallisena ihmisenä 3D-engineä ja
  161. pyynnöistä huolimatta starfield säilyy kunniakkaana osana
  162. tutoriaalia. Lisäksi kiitoksen jo tässä ansaitsee Pekka Nurminen
  163. lukuista tarkennuksista ja lisäehdotuksista joidenkin asioiden
  164. suhteen, sekä Tero Kontkanen maanmainion "Laama"-logon teosta.
  165.  
  166. Eli kun törmäät johonkin epäselvyyteen, päällekkäisyyteen,
  167. epäloogisuuteen, toistoon, virheeseen tai puutteeseen niin
  168. ilmoittelehan heti minulle. Osoite tuolta tiedoston lopusta. Vastaan
  169. postiin mahdollisuuksieni mukaan (vastaan siis jokaiseen ellen sitten
  170. huku postiin). Jokainen kommentti tekee minut iloiseksi, sillä on aina
  171. mukavaa nähdä jos joku on tutoriaalista hyötynyt.
  172.  
  173. Minulle saa lähettää viestimuotoisen kannustuksen lisäksi myös rahaa
  174. ja 20 markkaa olisi oikein hauska yllätys joskus löytää postiluukusta,
  175. tosin vähemmän ja enemmänkin voi halutessaan lähettää. =) Myös pelkkä
  176. postikortti tai e-maili on mukavaa. Rahan takia en tätä tee, saldo
  177. taitaa tähän mennessä olla yksi lahjoitus Erikiltä. :)
  178.  
  179. Jatkossa tulen julkaisemaan uusia versioita sitä mukaa kun asiaa tulee
  180. lisää. Eli pidä silmä tarkkana ja mieli valppaana tutkiessasi
  181. käyttämiesi purkkien tiedostoalueita. Uusimman version löytäminen on
  182. selostettu tarkemmin luvussa 1.2. Muista, että Laamatutin levittäminen
  183. on suorastaan toivottua muuttamattomassa muodossa, joten älä epäröi
  184. lähettää sitä suosikkipurkkeihisi!
  185.  
  186.  
  187. 1.4 Kenelle tämä on tarkoitettu?
  188. --------------------------------
  189.  
  190. Aloitin dokumentin kirjoittamisen Ilkka Pelkosen mainion suomenkielisen
  191. 3d-tutoriaalin innoittamana ja toivon, että tästä on hyötyä monille
  192. aloitteleville peli/demokoodereille DJGPP:llä. Tutoriaali kattaa
  193. DJGPP:n asennuksen ja monia grafiikkaohjelmoinnin perusniksejä, joskus
  194. lähdekoodinkin kanssa. Myöhempi osa alkaa menemään pikkuhiljaa yhä
  195. teoreettisemmalle tasolle eikä tahattomasti, sillä kunnon
  196. ohjelmointiin kuuluu paljon muutakin kuin hardware-tuntemus. Pyrin
  197. myös valaisemaan asioita jotka kuuluvat vähemmänkin peliohjelmointiin,
  198. mutta joista ei kunnollista juttua mistään muualta ole saatavilla.
  199. Ehdotuksia saa aina lähettää.
  200.  
  201. Lähtövaatimuksena tämän lukijalle on siedettävä matematiikan taito
  202. (kertolaskut pitää olla hallussa, kuten myös jotkin muut
  203. peruskäsitteet, kuten kokonaisluvut, desimaaliluvut jne.) Sekä
  204. C-kielen taitaminen. Assemblerikin voi olla hyödyllinen. Tätä
  205. kirjoittaessani en vielä tiedä millainen tutoriaalista tulee, joten
  206. katsotaan nyt... Teemu Keinonen on kirjoittanut tähän tutoriaaliin
  207. mainion pikku dokumentin lukujärjestelmistä ja bittioperaatioista, joten
  208. jos et niitä vielä hallitse niin lue ensin tiedosto LUVUT.TXT!
  209.  
  210. Tutoriaalin esimerkit EIVÄT MISSÄÄN NIMESSÄ ole tarkoitettu
  211. käytettäviksi peleissä suoraan. Niitä kyllä saa käyttää, mutta ne ovat
  212. hitaita ja ne ovat esimerkkiohjelmia, eivät juuri yhdenlaiseen
  213. pelityyppiin sopivia räätälöityjä rutiineja. Sitäpaitsi mikään ei
  214. voita kokemusta ja kirjoittaessasi omat rutiinisi opit asian paremmin
  215. kuin mitenkään muuten. Jos minä olisin käyttänyt muiden rutiineja niin
  216. en olisi nyt tässä selittämässä ideaa niiden takana, vaan tekisin
  217. alkeellisia pelejä, koska en osaisi muunnella muiden koodeja peleihini
  218. sopiviksi. Eli tämä dokumentti ei kirjoita sinulle valmiiksi parhaita
  219. ja sopivimpia rutiineja, vaan ainoastaan demonstroi mahdollisia
  220. toteutustapoja, joka tulisi pitää mielessä dokumenttia lukiessa.
  221.  
  222. Huomaa myös, että tämän on kirjoittanut OikeaIhminen(tm), jolla on
  223. myös Sähköpostiosoite, jolla voit ottaa häneen yhteyttä. Mikään ei ole
  224. minulle mieluisampaa kuin nähdä, että edes joku on tyytyväinen tai
  225. tyytymätön tähän tutoriaaliin.
  226.  
  227. Ja tietenkin koska olen oikea ihminen voit kysyä minulta epäselväksi
  228. jääneitä kohtia ja katson voinko selventää tätä ja kenties lisään
  229. vastauksen myös seuraavaan versioon tutoriaalista ja autat siten muita
  230. aloittelijoita. Voit jopa saada nimesi jonnekin, ken tietää? Eli kun
  231. tulee jotain mieleen niin mene dokumentin loppuun ja lue
  232. yhteystietoni. Myös kirjoitusvirheistä, huonosta / hyvästä tekstistä
  233. tai selvästä tekstistä kannattaa ilmoittaa, en nimittäin ole ainakaan
  234. vielä lukenut tätä kokonaan lävitse (lukuunottamatta kun kirjoitin
  235. tämän). Ja kaikki enemmän osaavat voivat ilmoittaa tarkennuksia ja
  236. oikaisuja tutoriaalin tekstiin. =)
  237.  
  238. Koulutus Kokkolan Yhteislyseon lukiossa (eli lyhyemmin Länsipuistossa)
  239. on nyt sitten viimein alkanut, jonka jälkeen edessä on jokin
  240. teknillinen korkeakoulu ja DI:n arvo, jos luoja suo. :)
  241.  
  242.  
  243. 1.5 Kreditsit
  244. -------------
  245.  
  246. Ennenkuin aloitamme, haluaisin tervehtiä joukkoa tuntemiani
  247. henkilöitä. Tiedoksi kaikki MBnetin ohjelmointi-alueen lukijoille,
  248. että ainakin yritin muistaa niin monta kuin vain mahdollista, jos
  249. siis nimeäsi ei ole listassa ja tunnet sinne kuuluvasi niin ilmoittele!
  250.  
  251. Teemu Keinonen: Erityiskiitokset lukujärjestelmät -jutustasi!
  252. Erik Seesjärvi: Kiitoksia starfieldistä ja onnea 3D-enginelle. =)
  253. Pekka Nurminen: Kiitos mainiosta palautteesta ja avusta monessa asiassa.
  254. Tero Kontkanen: Mahtava logo! Muistinpas vihdoin lisätä senkin.
  255. Sami Kuronen: Alias pysyy, I hope. Jatka vain lukemista! ;)
  256. Jyri Pieniniemi: Tällä dokumentilla voi olla laksatiivisia vaikutuksia!
  257. Ilkka Pelkonen: Sinun takiasi jouduin tällaista kirjoittamaan... Tsemppiä!
  258. Tommi Kemppainen: Koodaus, skene ja elämä. Pitääkö muuta sanoa?-)
  259. Johan Brandt: Täytyyhän meidän nörttien pitää yhtä!
  260. Asko Soukka: Onnea sen C++:ssan opettelun kanssa, toivottavasti onnistut!
  261. Jari Karppanen: Filekamu, vain 2 vuotta myöhässä?-) Muistin nyt sinutkin!
  262. Tero Karras: Jos joinaat Doomsdayhin niin katso, että Bad Karmaa greetataan!
  263. Jere Sanisalo: Terveisiä vain sinnekin, toivottavasti Kaboomia on rekattu! ;)
  264. Kaj Björklund: Toivon RC:n imevän monta sielua ja seuraavan version! :)
  265. Aleksi Kallio: Näpit irti siitä Watcomista! DJGPP ja herneet 4ever!
  266. Juhana Venäläinen: Hmm, kai tagisaarto ES:ää vastaan on vielä voimassa?-)
  267. Marko Åkerberg: Menikö nimi oikein?-) BLAST 'EM RULEZ, JEE!!!
  268. Jarmo Muukka: Miten ikinä JAKSAT kirjoittaa yli sadan rivinohjelmaesimerkkejä?
  269. Jukka Vuokko: Huomentapäivää. Aiotko tehdä Emacsiin sprite-enginen?-)
  270. Petteri Järvinen: Tsemiä autopeliin! Toivottavasti kirje saapui perille. :)
  271. Ilja Bräysy: No toivottavasti sait jotain tolkkua jostakin =)
  272. Henri Pyyny: Toivottavasti ette huku lumeen siellä Lapissa!
  273. Lasse Laurila: Kyllä minä vielä saan sinut kirjoitetuissa messuissa kiinni!
  274. Santeri Saarimaa: Yhä NNY?
  275. Äiti&Isi: Mitä te tätä luette?!?
  276. Tomi Jutila: Olet sinäkin siis päättänyt alkaa kooderiksi?-)
  277. Timo Jutila: Quakee?!?!
  278. Teemu Kellosalo: Älä vain väitä että aiot lukea tämän?
  279. Kalle Liukkonen: Muistin sitten sinutkin. =) Shefun oikat hanskassa?-)
  280. Juho Östman: No laitoinpas sinutkin tänne. Yllätyitkö?-)
  281. The Pihlajamaa: Hemmetti, etunimi pääsi unohtumaan, tsemiä!
  282. Viznut / PwP: Onko sinulla jokin oikea nimikin?-) No mitä tuosta...
  283.  
  284. Erityiskiitoksen ansaitsevat vielä koko MBnetin ylläpito, sillä ilman ko.
  285. purkkia ei minulle olisi koskaan ollut mahdollista oppia niin paljon
  286. ohjelmoinnista, että voisin kirjoittaa tämän. Näistäkin ylläpitäjistä
  287. mainitsen vielä erikseen Jere Käpyahon, Tarmo Toikkasen ja Rasmus Wickholmin,
  288. jotka ovat ahkerasti olleet mukana PC-Ohjelmointialueella. Kiitos!
  289.  
  290.  
  291. 1.6 Versiohistoria
  292. ------------------
  293.  
  294. Kehitystä on jälleen tapahtunut ja mikäs sen mukavampi paikka
  295. nauttia niistä etukäteen kuin tämä luku. Uusi termikin on ilmaantunut,
  296. "uusi tausta" tarkoittaa selostusta toiminnasta Asioiden taustaa
  297. -osaan.
  298.  
  299. Versio 2.1:
  300. + Jälleen korjauksia, pitäisi alkaa olla jo aika virheetöntä
  301. tavaraa, poistin //-kommentit ja kaikki mainit nyt tyyppiä int
  302. + Uusi luku VESA 2.0-rakenteista
  303. + Uusi luku VESA 2.0-keskeytyksistä
  304. + Uusi luku grafiikkaenginen teosta
  305. + Asioiden taustaa -osa, jossa kerron vain mikä on homman nimi,
  306. koodia ei enää tipu
  307. + Uusi tausta datatiedostoista
  308. + Uusi tausta läpinäkyvyydestä ja shadebobeista
  309. + Uusi tausta motion blurrista
  310. + Uusi tausta vektoreista pelimaailmassa
  311. + Uusi tausta musiikkijärjestelmistä
  312. + Uusi tausta wobblereista
  313. + Uusi tausta tunneli-efektistä
  314. + Uusi tausta zoomauksesta
  315. + Uusi tausta polygoneista ja niiden fillauksest
  316. + Uusi tausta feikkimoodeista
  317.  
  318. Versio 2.01:
  319. + Joukko korjauksia enemmän tai vähemmän kriittisiin asioihin
  320. + Ei julkisessa levityksessä
  321.  
  322. Versio 2.0: The Joulu Edition Enhanced
  323. + Ei enää READJUST.NOW -tiedostoa
  324. + Vaikeaselkoisempi disclaimer-teksti
  325. + Pikku korjauksia materiaaliin ja joitakin tarkennuksia
  326. + Mahtava, tuore versionumero
  327. + Uusi, hieno ja selkeä lukujako ja joitain järjestelyjä
  328. + Uusi, laaja (?) slangisanasto
  329. + Lisää kiinnostavia ja selkeitä ohjelmaesimerkkejä
  330. + Uusi luku interpoloinnista ja viivanpiirrosta
  331. + Uusi luku skrollauksesta
  332. + Uusi luku sineistä, kosineista ja plasmasta
  333. + Uusi luku kvantisoinnista median cut -algoritmilla
  334. + Uusi luku kvantisoinnista local K mean -algoritmilla
  335.  
  336. Versio 1.3: Assembly-mix, jotain purtavaa myös demokoodereille
  337. + Tarkennuksia ja parannuksia VGA:n muistista kertovaan osaan
  338. + Lisää koodia pseudona bitmap-osuuteen ja muutenkin enemmän
  339. selvennystä ko. kohtaan. Kiitoksia selvennyspyynnöistä.
  340. + Uusi luku useiden C-tiedostojen käytöstä
  341. + Uusi luku objekti- ja archive-tiedostojen teosta
  342. + Uusi luku Rhiden konffauksesta ja projektinhallinnasta
  343. + Uusi luku makefileiden käytöstä
  344. + Uusi luku enginen teosta
  345. + Uusi luku ulkoisen assyn käytöstä
  346. + Uusi luku timerin koukutuksesta C:llä
  347. + Uusi luku frameskipistä
  348. + EJS:n starfield-esimerkki ja -selostus.
  349.  
  350. Versio 1.2: Kesä-release, toinen julkisesti levitetty versio
  351. + Hiiren käsittely
  352. + Tekstitilan käsittely
  353. + Lisää korjauksia, kiitos ahkeran palautteen
  354.  
  355. Versio 1.1: Bugikorjaus-release, ei yleisesti levityksessä
  356. + Lukuisia korjauksia enemmän tai vähemmän vialliseen
  357. tietoon siellä sun täällä tutoriaalissa
  358.  
  359. Versio 1.0: Ensimmäinen julkaistu versio
  360. + DJGPP:n asenuns
  361. + Grafiikka
  362. + Paletti
  363. + Kaksoispuskuri
  364. + PCX-kuvat
  365. + Bittikartat
  366. + Animaatiot
  367. + Spritet
  368. + Näppäimistö
  369. + Fixed point
  370. + Lookup-tablet
  371. + Fontit
  372. + Maskatut spritet
  373.  
  374.  
  375. 1.7 Yhteystiedot
  376. ----------------
  377.  
  378. Hyvä, olet siis päättänyt ottaa yhteyttä minuun. Yhteyden minuun saat
  379. useallakin tavalla, mutta tässä ovat ne joita luultavimmin tarvitset:
  380.  
  381. www.mbnet.fi/~jokke/ sisältää minun, Bad Karman ja sen tuotosten, sekä
  382. Laamatutin viralliset kotisivut sekä joukon linkkejä maailmalle (ainakin
  383. jossain vaiheessa ;).
  384.  
  385. joonas.pihlajamaa@mbnet.fi on sähköpostiosoite, josta minut pitäisi saada
  386. kiinni.
  387.  
  388. Joonas Pihlajamaa on käyttäjätunnukseni MBnetissä, jolle voit kirjoittaa
  389. yksityispostiin. Ainakin tällä hetkellä luen viestini keskimäärin 3 kertaa
  390. viikossa, joten vastaus pitäisi tulla viikon sisällä (ellen ole
  391. lomailemassa tai paastolla koneestani ;).
  392.  
  393. Joonas Pihlajamaa
  394. Säveltäjäntie 40
  395. 67300 Kokkola
  396.  
  397. Tämä on se osoite, jossa asun. Jos et aivan käymään viitsi tulla niin mikset
  398. lähettäisi postikortilla terveisiä? Vastauksista kirjeisiin en tiedä, mutta
  399. katsotaan nyt, ei ole ainakaan vielä tullut ainoatakaan kirjettä...
  400.  
  401. Kuulun gruuppiin BAD KARMA, joka tekee tällä hetkellä peliä nimeltään
  402. SLiDER: Roadkill, joka on autopeli ja sen on tarkoitus hakata Slicks 'n'
  403. Slide sekä muut vastaavat pelit mennen tullen. Kannattaa tutkia tarkasti
  404. purkkien tiedostoalueita, jos vaikka ilmestyisi. Ilmestymisajankohta
  405. on luultavasti (ensi?-) vuosituhannen loppupuolella.
  406.  
  407.  
  408. 1.8 Esimerkkien kääntäminen
  409. ---------------------------
  410.  
  411. Tutoriaalin mukana seuraa sankka joukko esimerkkiohjelmia ja ne
  412. löytyvät hakemistosta EXAMPLE. Jos sinulla on 'make', niin kääntö
  413. sujuu yksinkertaisesti menemällä esimerkkikoodit sisältävään
  414. hakemistoon ja ajamalla komennon 'make' ja sen jälkeen 'make test.exe'
  415. jos sinulla on NASM. 'make clean' / 'make realclean' vastaavasti
  416. tyhjentävät objektitiedostot / objekti- ja exetiedostot.
  417.  
  418. Kiitoksia Tero Kontkaselle makefile-esimerkistä. Tein sen pohjalta nyt
  419. uuden, koska esimerkkiohjelmia oli tullut jonkin verran lisää. Jos
  420. sinulla ei ole 'make'-ohjelmaa onnistuu kääntäminen käsinkin. Lähes
  421. kaikki tiedostot ovat itsenäisiä eivätkä tarvitse muita
  422. objektitiedostoja tai kirjastoja toimiakseen. Poikkeuksina
  423. timertst.exe joka tarvitsee sekä timer.c:n ja timertst.c:n käännettynä
  424. ja test.exe, joka tarvitsee test.asm:n ja test.c:n käännettynä.
  425.  
  426. Hauskaa kokeilua, minä menen nukkumaan!
  427.  
  428.  
  429. 2.1 DJGPP - vaikea, suuri, monimutkainen, omituinen, hidas?
  430. -----------------------------------------------------------
  431.  
  432. Tutoriaali sivuaa koko ajan DJ Delorien ilmaista Gnu-kääntäjää
  433. DOS:ille, eli DJGPP:tä, erityisesti sen kakkosversiota. Itse siirryin
  434. puolessa välissä tätä tutoriaalia 2.0 -versiosta versioon 2.01 ja
  435. luulisin, että esimerkit toimivat molemmilla näistä versioista ja
  436. luultavasti uudemmillakin. Vanhemmat versiot eivät luultavastikaan
  437. toimi näiden lähdekoodien kanssa.
  438.  
  439. Tämän mahtavan ilmaiskääntäjän löydät esimerkiksi internetistä
  440. osoitteesta ftp://x2ftp.oulu.fi jostain
  441. pub/msdos/programming-hakemiston alihakemistosta. Sen saa myös
  442. MBnetistä, tarvittavat tiedostot ovat alueella PC-Ohjelmointi (area
  443. 8), tiedostoja on useita, ja ne löytyvät ko. alueelta löytyvästä
  444. MBNETDJ2.TXT:stä. Myös kaikille Mikrobitin tilaajille tullut Huvi &
  445. Hyötyromppu sisältää tämän kääntäjän hakemistossa MIKROBIT\DJGPP201\,
  446. tosin sieltä puuttuu LGP2721B.ZIP (tarvitaan C++ koodin kääntämisessä),
  447. jonka Käpyaho unohti laittaa mukaan. Halutessasi voit hakea puuttuvan
  448. tiedoston MBnetistä.
  449.  
  450. DJGPP:n asennukseen purat vain kaikki tarvitsemasi paketit haluamaasi
  451. hakemistoon (esim. D:\OHJELMAT\DJGPP) PKUNZIP:in -d parametrillä. Sen
  452. jälkeen lisäät polkuun tuon hakemiston alihakemiston BIN (esim.
  453. D:\OHJELMAT\DJGPP\BIN), ja vielä lopuksi teet uuden environment-muuttujan
  454. DJGPP, joka osoittaa DJGPP:n juurihakemistossa olevaan DJGPP.ENV
  455. -tiedostoon. Eli esim.:
  456.  
  457. SET DJGPP=D:\OHJELMAT\DJGPP\DJGPP.ENV
  458.  
  459. Nyt voit kokeilla toimivuutta tekemällä pienen C-ohjelman (vaikka
  460. koe.c) ja kirjoittamalla:
  461.  
  462. GCC koe.c -o koe.exe
  463.  
  464. Lisää infoa GCC:n käännösoptioista ja kääntäjästä saat kirjoittamalla:
  465.  
  466. INFO GCC
  467.  
  468. Suosittelisin että lueskelet DJGPP:n dokumentaatiota ja teet tässä
  469. vaiheessa paljon testiohjelmia ja opettelet käyttämään
  470. info-lukijaa. Hyödyllinen hankinta on myös Rhide, joka on IDE
  471. DJGPP:lle. Ohjelma löytyy MBnetistä alueelta 8 (ETSI RHIDE) sekä
  472. H&H-Rompulta. Kun tunnet osaavasi käyttää vaivattomasti kääntäjää
  473. palaa takaisin dokumentin pariin.
  474.  
  475. Jos et vielä C:tä osaa, niin hanki jostain, esimerkiksi kirjastosta hyvä
  476. kirja ja opettele sen avulla C-ohjelmointi. En aio alkaa
  477. selittämään kaikkein yksinkertaisimpia asioita esimerkkikoodeissa
  478. taikka kommentoimaan liiemmälti koodia.
  479.  
  480.  
  481. 2.2 Grafiikkaa - mitä se on?
  482. ----------------------------
  483.  
  484. No olet siis päättänyt edetä seuraavaan aiheeseen, joka näyttäisi
  485. olevan grafiikan ohjelmointi DJGPP:llä. Aloittakaamme siis! Tiedoksi
  486. nyt etukäteen, että muistiosoitteet ovat heksoina, vaikkei sitä
  487. ilmoitetakaan.
  488.  
  489. Esimerkkinä käytän VGA:n perusmoodia, 13h (heksaluku, desimaalina
  490. 19), joka on erittäin helppokäyttöinen. Kun tarvitset muita moodeja
  491. sinulla on varmasti jo tarpeeksi taitoa hankkia itse informaatiota,
  492. mutta tämän neuvon ihan alusta alkaen.
  493.  
  494. Eli olipa kerran PC, jossa oli 16-bittinen muistiväylä, joka salli
  495. vain 64 kilon osoittamisen kerralla, sillä 16-bittisellä osoitteella
  496. voidaan maksimissaan osoittaa 2^16=65536 tavua muistia. PC:n oli
  497. suunnitellut Intel, mutta PC:hen oli luvattu yli 64 kilotavua muistia
  498. ja 32-bittinen muistiväylä oli niihin aikoihin kovin kallis. Joten
  499. joku sai suorastaan neronleimauksen: Jaetaan koko muisti 64 kilon
  500. palasiin!
  501.  
  502. En syvenny tekniikkaan sen kummemmin, vaan totean vain, että 8088
  503. prosessoriin perustuvassa PC:ssä muodostettiin muisti SEGMENTISTÄ ja
  504. OFFSETISTA (SEG:OFF, esim B800:0000). Todellinen osoite muistissa
  505. saatiin kertomalla SEGMENTTI kuudellatoista ja lisäämällä siihen
  506. OFFSET. (B800:0000 = B800*16+0000 = B8000) Ja kun kummatkin olivat
  507. 16-bittisiä lukuja saatiin näin 20-bittinen siirrososoite. Ja koska 20
  508. bitillä voi ilmoittaa täsmäälleen kaksi potensiin 20 eri arvoa oli
  509. maksimimäärä mitä voidaan osoittaa 1 megatavu. Kymmenen ensimmäistä
  510. segmenttiä (eli 0000 1000 2000 3000 4000 5000 6000 7000 8000 ja 9000)
  511. omistettiin ohjelmille ja nimettiin perusmuistiksi, jota oli siis
  512. 10*64=640 kilotavua. Sitten segmentistä A000 alkoi grafiikkamuisti.
  513.  
  514. No tietokoneet kehittyivät ja esiteltiin suojattu tila, eli PROTECTED
  515. MODE (PM), joka käsitteli koko muistia selektoreilla ja offseteilla,
  516. jotka olivat entisen 16 bitin sijasta 32-bittisiä (selektorit ovat
  517. kuitenkin yhä 16-bittisiä). Vanhat segmenttien varastoimiseen tarkoitetut
  518. SEGMENTTIREKISTERIT varattiin nyt selektoreille, jotka kertoivat
  519. prosessorille, mitä LOOGISTA muistialuetta käsiteltiin. DJGPP, joka on
  520. suojatun tilan kääntäjä esim. antaa ohjelmalle alussa 2 selektoria, toinen
  521. osoittaa dataan ja toinen koodiin. Tästä pidemmälle en tiedä tarkasti,
  522. mutta riittää tietää, että selektorin osoittaessa dataan ei offset 1234
  523. todellakaan ole muistissa kohdassa 1234, vaan se on ohjelman oman
  524. data-alueen 1234. tavu.
  525.  
  526. Ja mikä meitä kiinnostaa, on perusmuistin 11. segmentti, jonka osoite
  527. siis oli A000:0000. Siirrososoite on siis A000*16+0000 = A0000. Mutta,
  528. kuten muistamme, ei onnistu, että vain tekisimme pointterin, joka osoittaa
  529. tuonne osoitteeseen, sillä ohjelman datahan on aivan toisessa
  530. selektorissa kuin perusmuisti. Meidän täytyy ensin löytää oikea
  531. selektori, jonka osoittama looginen muistialue vastaisi PC:n
  532. perusmuistia. Ja tällainen löytyykin nimellä _dos_ds. Tämän selektorin
  533. osoittaman muistialueen 0. tavu on perusmuistin 0. tavu, 1. tavu on
  534. perusmuistin 1. tavu ja niin jatkuu edelleen, kunnes tavu numero A0000
  535. on ensimmäinen VGA:n grafiikkamuistin tavu.
  536.  
  537. Nyt meillä on siis tiedossa segmentin A000, eli VGA-kortin
  538. muistialueen siirrososoite, A0000 ja oikea selektori, _dos_ds. Mutta
  539. miten laitamme tavun tuonne? Hyvä kysymys. Se onnistuu vähintään 5:llä
  540. eri tavalla, mutta perehdymme helpompaan. Kirjaston sys/farptr.h
  541. funktioon _farpokeb(selektori, siirrososoite, tavu), jolla pääsemme
  542. käsiksi tuonne. Normaalin pointterin tekohan ei onnistu, vaan meillä
  543. pitää olla funktio, joka kykenee osoittamaan toisen selektorin
  544. alueelle.
  545.  
  546. Näinollen esimerkkiohjelma, joka asettaa VGA-muistin 235. tavun arvoon
  547. 100 on tämän näköinen (PIXEL1.C):
  548.  
  549. #include <go32.h> /* muistathan, _dos_ds on määritelty täällä! */
  550. #include <sys/farptr.h> /* täältä löytyy _farpokeb */
  551.  
  552. int main() {
  553. int selektori=_dos_ds,
  554. siirros=0xA0000 + 235,
  555. arvo=100;
  556.  
  557. _farpokeb( selektori, siirros, arvo );
  558.  
  559. return 0;
  560. }
  561.  
  562. Arvaan, että ehkä menit ja kokeilit tuota ja petyit, kun mitään ei
  563. tapahtunutkaan. Ei se mitään, niin pitääkin tapahtua, sillä olimme
  564. tekstitilassa. Jotta jotain tapahtuisi meidän pitää olla oikeassa
  565. tilassa, joka oli siis 0x13 (heksanumero 13 C:ssä, desimaalimuodossa
  566. 19). Tämän tilan rakenne onkin seuraava mihin perehdymme. Ole huoleti,
  567. valitsin tämän tilan, sillä se on KAIKKEIN yksinkertaisin tila
  568. PC-yhteensopivalla tietokoneella. Resoluutio on 320 riviä vaakatasossa
  569. ja 200 pystytasossa. Jokaista pikseliä merkitään yhdellä tavulla, eli
  570. sillä voi olla 256 erilaista arvoa. Näyttö alkaa aivan ruudun
  571. vasemmasta yläkulmasta (miksi? sitä ei kukaan oikein tiedä, menee
  572. filosofiaksi) ja jatkuu tavu tavulta (pikseli pikseliltä) päättyen
  573. lopulta oikeaan alakulmaan. Eli ensimmäiset 320 tavua ovat ensimmäisen
  574. rivin kaikki vaakatasossa olevat pikselit, sitten seuraavat 320 ovat
  575. toisen rivin pikselit, kunnes lopulta ollaan ruudun alakulmassa.
  576.  
  577. Ja kun muistamme, että ensimmäinen tavu on kohdassa A0000 (heksa siis
  578. tämäkin), eli 0 tavua alusta eteenpäin, niin me voimmekin tehdä hienon
  579. kaavion:
  580.  
  581. Pikselit: Sijainti:
  582. ..........................
  583. 0...319 1. rivi
  584. 320...639 2. rivi
  585. ...
  586. 63680...63999 200. rivi
  587.  
  588. Näin meillä onkin hieno kaava, jolla saamme selville pikselin
  589. sijainnin:
  590.  
  591. alkio = rivi * 320 + sarake eli:
  592. offset = y*320+x
  593.  
  594. Muista, että C:ssä 1. rivi olisi tietenkin rivi numero 0!
  595.  
  596. Nyt yhdistämme tietomme: VGA:n muisti sijaitsee selektorissa _dos_ds,
  597. alkaen osoitteesta A0000 (heksa, C:ssä 0xA0000) ja siitä lähtee 64000
  598. tavua, joka on näyttömuisti. Pikselin osoite tässä muistissa voidaan
  599. laskea kaavalla y*320+x. Selektorin kanssa voidaan muistia asettaa
  600. komennolla _farpokeb(selektori, siirros, arvo). Tarvittava moodi on
  601. 0x13 ja siinä on 256 väriä ja resoluutio 320 x 200.
  602.  
  603. Mutta miten pääsemme sinne? Vastaus on helppo: conio.h:n funktiolla
  604. textmode(moodi)! Ja kun vielä yhdistämme tähän funktion getch(), joka
  605. odottaa napinpainallusta (löytyy myöskin kirjastosta conio.h), sekä
  606. palaamme lopuksi tekstitilaan (0x3, eli heksa 3, eli desimaali 3) on
  607. meillä jo aika kiva ohjelma kasassa (PIXEL2.C):
  608.  
  609. #include <go32.h> /* _dos_ds ! */
  610. #include <sys/farptr.h> /* _farpokeb(selektori, siirros, arvo) */
  611. #include <conio.h> /* textmode(moodi), getch() */
  612.  
  613. int main() {
  614. int selektori=_dos_ds, siirros=0xA0000, y=100, x=160,
  615. graffa=0x13, texti=0x3, color=100;
  616.  
  617. textmode(graffa);
  618. _farpokeb(selektori, siirros+y*320+x, color);
  619. getch();
  620. textmode(texti);
  621.  
  622. return 0;
  623. }
  624.  
  625. Tietenkin olisi ollut helpompaa sijoittaa arvo suoraan parametrin
  626. kohdalle:
  627.  
  628. textmode(0x13);
  629. _farpokeb(_dos_ds, 0xA0000+100*320+160, 100);
  630. getch();
  631. textmode(0x3);
  632.  
  633. Mutta katsoin aiemman tavan havainnollisemmaksi. Kaiken tekemiseksi
  634. oikein helpoksi teemme tästä pikselinsytytyksestä makron
  635. #define-komennolla. Tämä ei hidasta ohjelmaa yhtään, mutta varmasti
  636. selventää koodia. Se määrittelee makron putpixel(x, y, c), jonka
  637. kääntäjä muuttaa käännösvaiheeksa _farpokeb-funktioksi. x tarkoittaa
  638. saraketta väliltä 0-319 ja y riviä väliltä 0-199, sekä c väriä väliltä
  639. 0-255. Muista, että vaikka teetkin makron sinun pitää silti
  640. sisällyttää mukaan kirjastot sys/farptr.h ja go32.h! Sulut makron
  641. farpokeb-funktion muuttujien x ja y ympärillä selittyvät sillä, että
  642. koska makro puretaan suoraan kutsukohtaan niin esim. komento:
  643. putpixel(50, 40+a, 100) purkautuisi muotoon: _farpokeb( _dos_ds,
  644. 0xA0000+40+a*320+50, 100), joka ei tietenkään ole haluttu tulos, sillä
  645. 40+a pitää käsitellä ennen sijoitusta, eli sulut vain ympärille! Tässä
  646. se siis on:
  647.  
  648. #define putpixel(x, y, c) _farpokeb( _dos_ds, 0xA0000+(y)*320+(x), c)
  649.  
  650. Kun haluat käyttää sitä, niin teet vaikka seuraavanlaisen
  651. koodinpätkän (PIXEL3.C):
  652.  
  653. #include <sys/farptr.h>
  654. #include <go32.h>
  655. #include <conio.h> /* textmode(moodi) ja getch() löytyvät täältä! */
  656.  
  657. #define putpixel(x, y, c) _farpokeb( _dos_ds, 0xA0000+y*320+x, c)
  658.  
  659. int main() {
  660. textmode(0x13);
  661. putpixel(319, 199, 150);
  662. getch();
  663. textmode(0x3);
  664. return 0;
  665. }
  666.  
  667. Ohjelma sytyttää pikselin aivan ruudun alareunaan. Jos et enää muista,
  668. miten ohjelma käännettiin DJGPP:llä, on tämän kokeilemiseksi
  669. tarvittava komento: "GCC PIXEL3.C -o PIXEL3.EXE" ja sitten kokeilu
  670. komennolla "PIXEL3".
  671.  
  672. Painu nyt kokeilemaan ohjelmaa ja muuntelemaan sitä! Laita se tekemään
  673. ruksi, pystyviiva, vaakaviiva, tai vaikka ympyrä jos osaat, tai
  674. yhdistä se randomin kanssa ja tee näytönsäästäjä! Kokeilemalla tulet
  675. parhaiten sinuiksi uuden asian kanssa. Ja kun olet valmis, siirrymme
  676. seuraavaan aiheeseen, palettiin.
  677.  
  678.  
  679. 2.3 Paletti - hörhelöhameita ja tanssia?
  680. ----------------------------------------
  681.  
  682. Kuten edellisessä luvussa opimme, voi tilassa 13h olla 256 erilaista
  683. väriä. Teit ehkä jo ohjelman, joka piirtää pikselin jokaisella värillä
  684. viivaa ja huomasit, että käytössä olevat värit ovat huonoja,
  685. puuttelisia, kirkkaita, tummia tai muuten vain inhottavia. Mutta ei
  686. hätää - niitä voi muuttaa! Ja vaikka paletissa ei mielestäsi olisikaan
  687. mitään vikaa haluat ehkä tehdä sellaisia efektejä kuten häivytys,
  688. plasma, "crossfade" (toinen kuva ilmestyy toisen alta pikkuhiljaa)...
  689. Näissä kaikissa tarvitaan enemmän tai vähemmän itse tehtyä palettia ja
  690. siksi meidän pitääkin opetella nämä asiat ennenkuin menemme pidemmälle.
  691. Kaiken ytimenä on VGA ja sen paletti, etenkin sen asettaminen, mutta
  692. ehkä myös sen lukeminen. Tässä luvussa teemme funktiot, yhden tai
  693. useamman värin, asettamiseen ja lukemiseen, sekä tutustumme
  694. paletinpyöritykseen (palette rotation).
  695.  
  696. Ensin taas vähän teoriaa efektien ja paletin takana. Kuten ehkä
  697. tiedätkin, valo voidaan koostaa komponenteista. Tietokoneella
  698. jokaisella värillä on yleensä kolme komponenttia: punainen, vihreä ja
  699. sininen (red, green, blue). Tätä kutsutaan nimellä RGB. Itseasiassa
  700. jokainen moodin 13h väri on vain osoite taulukkoon, jonka jokainen
  701. alkio sisältää värin punaisen, virheän ja sinisen komponentin määrän,
  702. eli vahvuuden.
  703.  
  704. Jos meillä olisi puhtaan punainen väri, sen arvot olisivat seuraavat:
  705. r=63, g=0 ja b=0. Sininen taas olisi 0,0 ja 63. Violetti, joka on
  706. sinisen ja punaisen yhdistelmä, voisi olla vaikkapa 63,0 ja 63 (eli
  707. täysi määrä punaista ja sinistä). Jos taas haluaisimme tumman punaisen
  708. värin, olisivat sen väriarvot vaikka 30, 0, 0. Koska 30 on vähemmän
  709. kuin puolet kirkkaan punaisen puna-arvosta, on tämä väri siis yli
  710. puolet tummempi! Helppoa! Ja miksi maksimimäärä on vain 63? Siksi,
  711. koska VGA:n rekistereissä värille on varattuna vain 6 bittiä, jolla
  712. voidaan esittää numerot välillä 0...63. Tämä joudutaan huomioimaan
  713. esim. PCX:n paletin latauksessa, sillä siinä värit ovat välillä
  714. 0...255. Tässä joudutaan jakamaan väriarvot neljällä, jotta saadaan
  715. toimiva luku.
  716.  
  717. Eli ymmärrämme nyt, että jokaisella värillä on itseasiassa punainen,
  718. vihreä ja sininen komponentti, mutta mitä siitä? Vastaus on helppo,
  719. jos haluamme, voimme muuttaa mitä tahansa tilan 0x13 (tai miksei
  720. muunkin tilan) väriä helpolla joukolla komentoja. Meidän tarvitsee
  721. vain kirjoittaa asetettavan värin numero porttiin 3C8h (h lopussa
  722. siis tarkoittaa heksalukua, C:ssä 0x3C8) ja sitten porttiin 3C9 ensin
  723. punainen komponentti, sitten vihreä komponentti ja lopuksi sininen
  724. komponentti. Tämän jälkeen VGA korottaa väri-indeksiä automaattisesti
  725. yhdellä, eli jos ensin syötämme porttiin 3C8h värinumeron 5 ja sitten
  726. punaisen, virheän ja sinisen porttiin 3C9h korottuu VGA:n sisäinen
  727. laskuri yhdellä, ja voimme halutessamme tunkea heti seuraavan värin
  728. RGB arvot porttiin 3C9.
  729.  
  730. Nyt olemme jauhaneet teoriaa tarpeeksi. Menkäämme pikkuiseen
  731. esimerkkiin. Esittelemme tietorakenteen RGB, joka sisältää värin
  732. RGB-arvot ja sitten funktion, jolle annetaan parametrinä osoitin
  733. tällaiseen rakenteeseen ja värin numero jolle nämä väriarvot
  734. asetetaan. Myöhemmin yhdistämme tämän pieneen esimerkkiohjelmaamme,
  735. mutta (PALETTE.H):
  736.  
  737. typedef struct {
  738. char red;
  739. char green;
  740. char blue;
  741. } RGB;
  742.  
  743. void setcolor(int index, RGB *newdata) {
  744. outportb(0x3C8, index);
  745. outportb(0x3C9, newdata->red);
  746. outportb(0x3C9, newdata->green);
  747. outportb(0x3C9, newdata->blue);
  748. }
  749.  
  750. Huomiosi ehkä kiinnittyy vielä outoon funktioon outportb, jolle
  751. annetaan ensimmäisenä portin numero ja sitten sinne syötettävä
  752. tavu. Funktion käyttämiseksi sisällytät mukaan kirjaston dos.h.
  753. Ehkä sinua kiinnostaisi myös tämän käyttö? No olkoon, tehkäämme
  754. esimerkkiohjelma kokonaisuudessaan. Kun edellinen pikku koodinpätkä on
  755. nimellä PALETTE.H, voimme helposti sisällyttää sen seuraavaan
  756. esimerkkiohjelmaamme kuten ihan tavallisen kirjaston. Muista vain,
  757. että kirjaston täytyy olla samassa hakemistossa ohjelman kanssa,
  758. muuten ei esimerkki käänny. Eli tässä sitten itse koodiosa, joka
  759. tuikkaa keskelle ruutua värin 50. Sitten se odottaa napinpainallusta
  760. ja muuttaa funktiollamme värin punaiseksi. Huomaa, että vain alussa
  761. kajotaan näyttömuistiin. Toinen kohta hoidetaan värinvaihdolla!
  762. Eli (PAL1.C):
  763.  
  764. #include <conio.h>
  765. #include <sys/farptr.h>
  766. #include <go32.h>
  767. #include <dos.h>
  768.  
  769. #include "palette.h"
  770.  
  771. #define putpixel(x, y, c) _farpokeb(_dos_ds, 0xA0000+y*320+x, c)
  772.  
  773. int main() {
  774. RGB newcolor;
  775.  
  776. textmode(0x13);
  777. putpixel(160, 100, 50);
  778. getch();
  779. newcolor.red=63;
  780. newcolor.green=0;
  781. newcolor.blue=0;
  782. setcolor(50, &newcolor);
  783. getch();
  784. textmode(0x3);
  785. return 0;
  786. }
  787.  
  788. Seuraavana huomionkohteenamme onkin sitten väriarvojen luku, joka on
  789. yhtä suoraviivaista kuin edellinenkin (tosin tarpeellisuus on
  790. kyseenalaista, tätä ei tarvitse jos on itse asettanut paletin).
  791. Erotuksena on, että väriarvo kirjoitetaankin porttiin 3C7h ja portista
  792. 3C9h _luetaan_ värin arvo. Jälleen tripletin (kolme alkiota, RGB) luvun
  793. jälkeen indeksi kohoaa, joten voisimme lukea seuraavat värit. Luku
  794. portista tapahtuu funktiolla inportb(portti). Muuta tietoa emme
  795. tarvitsekaan.
  796.  
  797. Lisätkäämme nyt kirjastoomme (PALETTE.H) kolme uutta funktiota.
  798. getcolor(int index, RGB *color) lukee värin <index> väriarvot ja
  799. asettaa ne RGB-rakenteeseen <color>. setpal(char *palette) asettaa
  800. koko paletin kerralla hyväksikäyttäen automaattista indeksin korotusta
  801. (indeksi nollataan aluksi ja syötetään koko data perään, indeksi
  802. korottuu jokaisen rgb-arvon jälkeen). getpal(char *palette) taas lukee
  803. vastaavasti koko paletin. Niiden käytöstä sitten
  804. esimerkkiohjelmassamme, joka seuraa ajallaan. Eli uutuudet kirjastoon
  805. PALETTE.H:
  806.  
  807. void getcolor(int index, RGB *color) {
  808. outportb(0x3C7, index);
  809. color->red=inportb(0x3C9);
  810. color->green=inportb(0x3C9);
  811. color->blue=inportb(0x3C9);
  812. }
  813.  
  814. void setpal(char *palette) {
  815. int c;
  816.  
  817. outportb(0x3C8, 0);
  818. for(c=0; c<256*3; c++)
  819. outportb(0x3C9, palette[c]);
  820. }
  821.  
  822. void getpal(char *palette) {
  823. int c;
  824.  
  825. outportb(0x3C7, 0);
  826. for(c=0; c<256*3; c++)
  827. palette[c]=inportb(0x3C9);
  828. }
  829.  
  830. Kuten huomasit, ei viimeisissä funktiossa ole lainkaan enää
  831. RGB-rakennetta. Tämä siksi, että koko paletti on huomattavasti
  832. helpompi käsitellä näin. Jos olet sitä mieltä, että RGB oli parempi
  833. tai haluat muuttaa loputkin pointtereiksi, en sitä
  834. estä. Char-pointteriversiossa on aina kolme tavua peräkkäin
  835. ilmoittamassa RGB-triplettiä. Toisen värin r alkaa siis 4. tavusta,
  836. eli indeksistä 3. Jos haluat jonkin värin r-arvon, niin lasket:
  837. "palette[number*3+0]". Vihreällä korotat tuota yhdellä (number*3+1) ja
  838. sinisen kanssa kahdella. Helppoa tämäkin.
  839.  
  840. Nyt on kaikki tärkein katettu VGA:n paletista, joten kysytkin ehkä
  841. (aina sinä sitten olet kysymässä ;) mihin näitä nyt sitten voi
  842. käyttää. Itseasiassa paletilla on loputtomasti
  843. käyttömahdollisuuksia. Ensimmäinen on 256-väristen kuvien paletin
  844. asettaminen, sillä väärällä paletilla kuvat yleensä näyttävät enemmän
  845. tai vähemmän sotkulta. Toisena on häivytysefekti, sekä feidaus
  846. valkoiseen. Palettiliutuksesta käytetään usein termiä feidaus, joka
  847. tarkoittaa, että palettia liutetaan sävy sävyltä toiseen väriin,
  848. jolloin saadaan vaikka hieno ruudun tummeneminen. Kokeilemmekin sitä
  849. ihan kohta, kunhan selitän vielä yhden efektin, palettirotaation.
  850.  
  851. Palettirotaatiossa on paletti, jonka väriarvoja pyöritetään
  852. ympäri. Eli käytännössä väri, joka ennen oli numerolla 5 onkin
  853. rotaation jälkeen värinumerossa 6. Tätä jatketaan koko ajan, ja väri
  854. matkaa koko paletin lävitse, ja kun se on lopussa niin se siirretään
  855. paletin alkuun. Yleensä väriä 0 ei kuitenkaan siirretä, sillä se on
  856. taustaväri ja yleensä musta. Usein käytetään myös palettia, jossa on
  857. useampia värejä kuin 256, jolloin erona on vain se, että ainoastaan
  858. osa väreistä näkyy ruudulla.
  859.  
  860. "JA MIHIN TÄTÄ", kuulen sinun kysyvän. Olet kenties nähnyt plasman,
  861. jonka värit vaihtuvat koko ajan (kunnon plasmassa on kyllä lisäksi
  862. mukana muutakin kuin pyörivä paletti, mutta pyörityksellä saadaan
  863. kummasti lisäeloa muuten liikkuvaan plasmaan). Tai tunnelin, jossa
  864. värit siirtyvät kauemmaksi tai lähemmäksi. Tällaisia efektejä voidaan
  865. helposti toteuttaa palettirotaatiolla. Ennenkuin ymmärrät voit ehkä
  866. tarvita pienen demonstraation. Kohta teemmekin esimerkin, joka piirtää
  867. vaakatasossa viivoja, jokainen eri värillä alkaen yhdestä päättyen
  868. 255:teen. Sitten teemme hienon liukupaletin ja alamme pyörittämään
  869. sitä. Eli tehkäämme vielä funktio (lisätään kirjastoon PALETTE.H):
  870.  
  871. void rotatepal(int startcolor, int endcolor, char *pal) {
  872. char r, g, b;
  873. int c;
  874.  
  875. r=pal[startcolor*3+0]; /* tallennamme ensimmäiset värit ja siirrämme */
  876. g=pal[startcolor*3+1]; /* ne lopuksi loppuun. Tämä paletti pyörii siten, */
  877. b=pal[startcolor*3+2]; /* että viimeinen väri kulkeutuu kohti alkua */
  878.  
  879. for(c=startcolor*3; c<endcolor*3; c++)
  880. pal[c]=pal[c+3]; /* muista, että uusi väri on kolmen välein,
  881. sillä välissähän on aina kolme tavua, r,
  882. g ja b, joita ei saa sekoittaa, muuten
  883. saisimme aikaan vaikkapa sinisen paloauton!
  884. (kiinnostava tavoite sinänsä) */
  885.  
  886. pal[endcolor*3+0]=r;
  887. pal[endcolor*3+1]=g;
  888. pal[endcolor*3+2]=b;
  889. }
  890.  
  891. Vielä ennen esimerkkiä tarvitsemme yhden rutiinin, joka tekee
  892. efektistämme edes jotenkin siedettävän. Palettia pitää nimittäin
  893. vaihtaa ennen kuin ruudulle aletaan piirtää, tai muuten voi edessä
  894. olla aika huonolaatuinen efekti (normaalipaletissa ei ole mitään
  895. väriliukuja). Varsinkin näin yksinkertaisessa ohjelmassa voi nopealla
  896. näytönohjaimella/koneella nopeus olla liiankin suuri, joten hidastamme
  897. vähän rutiinia odottamalla signaalia, jonka VGA antaa päästessään
  898. ruudun loppuun ja lähtiessään palaamaan yläkulmaan aloittaakseen taas
  899. piirron. Tähän teemme funktion, joka odottaa kunnes piirto on valmis
  900. ja kuvaruudulle voi kopioida pelkäämättä kesken piirron muutoksia
  901. tehdessä aiheutuvia ongelmia. Lisätkäämme seuraava funktio kirjastoon
  902. PALETTE.H:
  903.  
  904. void waitsync() {
  905. while( (inportb(0x3DA)&8) != 0);
  906. while( (inportb(0x3DA)&8) == 0);
  907. }
  908.  
  909. Nyt sitten hienoon esimerkkiohjelmaamme, joka piirsi niitä viivoja ja
  910. pyöritti palettia. Huomaa funktio genpal(char *palette), joka asettaa
  911. paletin liukuväreillä tehdyksi, sekä waitsync()-funktion käyttö
  912. (kokeile vaikka ilman waitsync():iä, niin näet eron)! Eli tässä se
  913. olisi (PAL2.C):
  914.  
  915. #include <conio.h>
  916. #include <sys/farptr.h>
  917. #include <go32.h>
  918. #include <dos.h>
  919.  
  920. #include "palette.h"
  921.  
  922. #define putpixel(x, y, c) _farpokeb(_dos_ds, 0xA0000+y*320+x, c)
  923.  
  924. void genpal(char *palette) {
  925. char r=0, g=0, b=0;
  926. int c, color=0;
  927.  
  928. for(c=0; c<64; c++) { /* MUSTA (0,0,0) - PUNAINEN (63,0,0) */
  929. palette[color++]=r;
  930. palette[color++]=g;
  931. palette[color++]=b;
  932. if(r<63) r++;
  933. }
  934. for(c=0; c<64; c++) { /* PUNAINEN (63,0,0) - VIOLETTI (63,0,63) */
  935. palette[color++]=r;
  936. palette[color++]=g;
  937. palette[color++]=b;
  938. if(b<63) b++;
  939. }
  940. for(c=0; c<64; c++) { /* LILA (63,0,63) - VALKOINEN (63,63,63) */
  941. palette[color++]=r;
  942. palette[color++]=g;
  943. palette[color++]=b;
  944. if(g<63) g++;
  945. }
  946. for(c=0; c<64; c++) { /* VALKOINEN (63, 63, 63) - MUSTA (0,0,0) */
  947. palette[color++]=r;
  948. palette[color++]=g;
  949. palette[color++]=b;
  950. if(r) r--;
  951. if(g) g--;
  952. if(b) b--;
  953. }
  954. }
  955.  
  956. int main() {
  957. int x, y;
  958. char palette[256*3];
  959.  
  960. textmode(0x13);
  961. genpal(palette);
  962. setpal(palette);
  963. for(y=0; y<200; y++) for(x=0; x<320; x++)
  964. putpixel(x, y, y);
  965. while(!kbhit()) {
  966. rotatepal(1, 255, palette);
  967. waitsync(); /* odotetaan että piirto on valmis ennen uuden
  968. paletin asettamista! */
  969. setpal(palette);
  970. }
  971. getch();
  972. textmode(0x3);
  973.  
  974. return 0;
  975. }
  976.  
  977. Huomasit varmaan, että ruudun onnettoman geometrian takia kaikki värit
  978. EIVÄT mahtuneet ruudulle. No niin. Ja mitäs kivaa seuraavaksi?
  979. Seuraavaksi tutustumme viimeiseen palettikikkaan, jonka periaatteen
  980. olet jo voinut keksiäkin, eli feidauksen.
  981.  
  982. Genpal-funktio olisi voinut käyttää myös erillistä rutiinia jolle
  983. annetaan parametreina monenko värin matkalla liu'utaan väristä toiseen.
  984. Kuitenkin koska tuo oli yksinkertaisemman näköinen tein sen tuolla
  985. tapaa.
  986.  
  987. Teemme minimaalisia lisäyksiä PALETTE.H:hon, sekä pikkuisen
  988. esimerkkiohjelman, joka demonstroi efektiä käytännössä. Ideahan on
  989. erittäin yksinkertainen. Meillä on paletti, jossa on sekailaisia
  990. värejä ja haluamme häivyttää sen. Miten? No tietenkin muuttamalla
  991. ruudun mustaksi. Miten se tapahtuu? Nollaamme jokaisen värin, mutta
  992. emme kerralla, vaan vähennämme joka kierroksella ja asetamme uuden
  993. paletin. Tästä funktiosta voit tehdä helposti muitakin efektejä,
  994. kuten feidauksen valkoiseen (korotetaan jokaista väriä joka
  995. kierroksella kunnes ollaan värissä 63) tai vaikka paletista toiseen
  996. (jos kohdepaletin vastaava komponentti on suurempi niin korotetaan
  997. arvoa, jos pienempi niin vähennetään). Esittelen tässä vain
  998. häivytyksen, mutta löydät kirjastosta PALETTE.H toteutettuna myös
  999. valkoiseen ja toiseen palettiin feidauksen. Voit myös itse tehdä
  1000. hauskoja efektejä, kuten feidata valkoiseen, tehdä valkoisen paletin
  1001. ja feidata sen mustaan. Kokeile! Mutta, tässä rutiinimme:
  1002.  
  1003. void fadetoblack(char *palette) {
  1004. char temppal[256*3];
  1005. int c, color;
  1006.  
  1007. memcpy(temppal, palette, 256*3);
  1008. for(c=0; c<63; c++) { /* tarvitsemme maksimissaan 63 muutosta */
  1009. for(color=0; color<256*3; color++)
  1010. if(temppal[color]) temppal[color]--;
  1011. waitsync();
  1012. setpal(temppal);
  1013. }
  1014. }
  1015.  
  1016. Sitten yhdistämme efektin lopuksi edelliseen esimerkkiohjelmaamme
  1017. lisäämällä sen juuri ennen tekstitilaan vaihtoa:
  1018.  
  1019. fadetoblack(palette);
  1020.  
  1021. Kokonaisuudessaan ja toimivana, vanhat osat mukana on esimerkkimme
  1022. tiedostossa PAL3.C. Siihen on tehty myös pari muuta muutosta, kuten
  1023. se, että aluksi paletti feidataan valkoiseen, asetetaan oikeasti val-
  1024. koiseksi (muuten feidatessa mustaan paletti välähtää hetken normaaliväri-
  1025. senä, tätäkin SAA kokeilla).
  1026.  
  1027. No niin. Pahin tiedonnälkäsi lienee tältä erältä tyydytetty! Viihdy
  1028. esimerkkien parissa ja tee mitä vain mieleen juolahtaa niillä. Muista,
  1029. että palettifunktiot toimivat myös tekstitilassa. Tämän voit kokeilla
  1030. vaikka käyttämällä fadetoblack-funktiota. Muista kuitenkin laittaa
  1031. loppuun textmode(0x3), vaikket moodia olisi vaihtanutkaan, sillä et
  1032. välttämättä pidä DOS-kehotteestasi jokainen väri mustana...
  1033.  
  1034.  
  1035. 3.1 Kaksoispuskuri - luonnonoikku, horoskooppi?
  1036. -----------------------------------------------
  1037.  
  1038. No niin, olet näemmä sulattanut jo kaiken edellisen tiedon. Mainiota!
  1039. Tänään pääsemme (tai miten nyt haluamme asian ilmaista) yhteen
  1040. peliohjelmoinnin perustempuista, kaksoispuskuriin. Periaate tämän
  1041. takana on aivan naurettavan yksinkertainen, ja itseasiassa minä opin
  1042. tämän erään lehden lähdekoodia vilkaisemalla (Mikrobitin
  1043. grafiikkaohjelmointikurssi, numero 11/95). Eli tähän asti olemme
  1044. tunkeneet grafiikkaamme suoraan näyttöpuskuriin tavu
  1045. kerrallaan. Valitettavasti tässä on haittoja. Ensimmäisenä on se, että
  1046. meillä on kiire. Nimittäin käytössä on vain lyhyt aika kun näyttöä ei
  1047. piirretä monitorille ja jos siinä ajassa ei ehdä piirtää näyttöä niin
  1048. näyttö alkaa välkkymään, ilmestyy lumisadetta (varsinkin paletinvaihdon
  1049. kanssa!) ja muitakin ei-toivottavia ilmiöitä esiintyy.
  1050.  
  1051. Lisäksi on todettava valitettava tosiasia: Näyttömuisti on
  1052. HIDASTA. Jos haluamme tehdä sen kaikkein tehokkaimmin niin kopioimme
  1053. kaiken tavaran kerralla näytölle. Eli sen sijaan, että läiskisimme
  1054. pikseleitä sinne, toisia tänne kopioimme tavaran näytölle näytön
  1055. alusta loppuun neljän tavun (kaksoissana) kokoisina palasina. Mutta
  1056. miten saamme ruudulle pikseleitä sinne tänne, kun kaikki pitäisi
  1057. kopioida kerralla? Vastaus on, että käytämme kaksoispuskuria!
  1058. Kaksoispuskuri, englanniksi doublebuffer on saman kokoinen kuin
  1059. näyttömuisti, mutta sille on varattu tilaa keskusmuistista, joten se
  1060. on nopeampaa kuin hidas, kortilla sijaitseva näyttömuisti (näin vain
  1061. on, uskokaa pois). Sinne pikselinpiirto tapahtuu huomattavasti
  1062. sutjakammin, ja kaiken lisäksi meillä ei ole mitään kiirettä. Vaikka
  1063. piirrämme uuden pikselin, ei se näy näytöllä ennenkuin kaksoispuskuri
  1064. on kopioitu, eli flipattu näyttömuistiin.
  1065.  
  1066. DJGPP:llä näyttömuisti varataan vaikka malloc-käskyllä ja vapautetaan
  1067. suorituksen loppuessa free-käskyllä. Kokoa pitää puskurilla olla
  1068. tilassa 13h 64000 tavua. Eroja oikeaan näyttömuistiin
  1069. kaksoispuskurissa on DJGPP:llä:
  1070.  
  1071. - Se on nopeampaa.
  1072. - Se sijaitsee omassa muistissa, joten se voidaan taulukoida. Ei
  1073. enää putpixel-makroja, vaan dblbuf[y*320+x]=color.
  1074. - Se voidaan kopioida nopealla _dosmemputl-rutiinilla, joka on
  1075. viimeiseen saakka optimoitu (hidas se on siltikin, mutta se on
  1076. näyttökortin ja VGA:n rakenteen vika.)
  1077. - Se ei näy ruudulla ennenkuin käsketään.
  1078. - Se ei vilku.
  1079. - Se säilyy muistissa vaikka käytäisiin tekstitilassa.
  1080. - Paljon muuta kivaa.
  1081.  
  1082. Voit käyttää myös dynaamisen muistinvarauksen (malloc tai C++:ssalla
  1083. new-operaattori) tilalla taulukkoa, kuten joissakin esimerkeissä on
  1084. tehty, tällöin käytät muotoa char dblbuf[64000] (tai unsigned
  1085. char...). Mallocin käyttö on kuitenkin suositeltavampaa kuin tällainen
  1086. valtavien taulukoiden ottaminen pinosta.
  1087.  
  1088. Muttamutta, tarvitsisimme esimerkin. Mistä saamme sellaisen? No tässä
  1089. pieni esimerkki. Mukana on makro flip(char *buffer), joka kopioi 64000
  1090. tavua puskuria näyttömuistiin DJGPP:n _dosmemputl-komennolla, joka
  1091. löytyy kirjastosta sys/movedata.h ja tarvitsee myös _dos_ds:ää ja
  1092. siten kirjastoa go32.h. Eli tässä tällaista (DOUBLE1.C):
  1093.  
  1094. #include <go32.h>
  1095. #include <sys/movedata.h>
  1096. #include <time.h>
  1097. #include <stdlib.h>
  1098. #include <conio.h>
  1099. #include <dos.h>
  1100. #include <stdio.h>
  1101.  
  1102. #define flip(c) _dosmemputl(c, 64000/4, 0xA0000)
  1103.  
  1104. char *dblbuf;
  1105.  
  1106. void varaamuisti() {
  1107. dblbuf=(char *)malloc(64000);
  1108. if(dblbuf==NULL) {
  1109. printf("Ei tarpeeksi muistia kaksoispuskurille!\n");
  1110. exit(1);
  1111. }
  1112. }
  1113.  
  1114. int main() {
  1115. int x, y;
  1116.  
  1117. varaamuisti();
  1118. srand(time(NULL)); /* alustetaan satunnaislukugeneraattori */
  1119. textmode(0x13);
  1120. while(!kbhit()) {
  1121. for(y=0; y<200; y++)
  1122. for(x=0; x<320; x++)
  1123. dblbuf[y*320+x]=rand()%256;
  1124. flip(dblbuf);
  1125. }
  1126. getch();
  1127. textmode(0x3);
  1128. return 0;
  1129. }
  1130.  
  1131. Kokeile myös ohjelmaa DOUBLE2.C, joka on toteutettu ilman
  1132. kaksoispuskurointia, jos eroa ei vielä huomaa, tulee se
  1133. joka tapauksessa vielä esiin, ja on muitakin hyödyllisiä asioita missä
  1134. kaksoispuskuri, tai kolmoispuskurikin on tarpeen. Mutta, kokeile tämän
  1135. käyttöä ja palaa tämän dokumentin pariin VASTA kun osaat täydellisesti
  1136. kaksoispuskurin käytön (oikeammin ymmärrät miten se toimii, miten sitä
  1137. käytetään, mihin se perustuu ja miten siihen piirretään
  1138. pisteitä). Sitten syöksymmekin uuteen tuntemattomaan. Katsotaan nyt
  1139. mihin...
  1140.  
  1141.  
  1142. 3.2 PCX-kuvien lataus - vain vähän oikaisemalla
  1143. -----------------------------------------------
  1144.  
  1145. Noniin, kaikki wannabe gamekooderit. Nyt on aika mennä vaikeimpaan
  1146. aiheeseemme, johon monen kooderin taidot ovat viimein tyssänneet ja
  1147. jota minäkään en vielä täysin ymmärrä, enkä tiedä osaanko sitä
  1148. selittää.
  1149.  
  1150. Se on hyväuskoisuus, sillä PCX:n sisältä löytyy looginen ja helposti
  1151. ymmärrettävä rakenne. Ja vaikkei sitäkään täysin ymmärrä, voi
  1152. aina vain käyttää samaa rutiinia (kuten minä) PCX:n lataamiseen.
  1153. Esittelenkin tässä kappaleessa lyhyesti tämän yhden yleisimmistä
  1154. kuvaformaateista olevan tiedostotyypin saloja. 256-värisen tyypillisen
  1155. PCX:n rakenne voidaan jakaa karkeasti neljään (4) osaan:
  1156.  
  1157. - 128 ensimmäistä tavua headeria sisältäen info kuvasta
  1158. - kuvadata RLE-pakattuna
  1159. - salaperäinen tavu 12
  1160. - palettidata, viimeiset 768 tavua
  1161.  
  1162. Ensimmäisenä ja kaikkein vaikeimpana on headeri, jonka loikimme lähes
  1163. kokonaan yli, sillä tosipelikooderi tietää lataavansa oikeaa
  1164. PCX-kuvaa, joka on oikeaa formaattia oikeankokoiseen puskuriin ja
  1165. jättää selittämättömät kaatumiset muiden harteille! Tai itseasiassa en
  1166. sitä selitä kun en siihen ole perehtynyt syvemmin. Kiinnostuneille
  1167. PCGPE:ssä on tämäkin formaatti selitettynä lahjakkaan kryptisesti
  1168. englannin kamalalla mongerruksella. Kaikki sitä haluavat hankkivat sitten
  1169. tiedoston PCGPE10.ZIP, joka sisältää kaikkea hyödyllistä
  1170. peliohjelmointiasiaa, englanniksi siis.
  1171.  
  1172. Headerista tahdomme tietää vain sen, että PCX-kuvan koko lasketaan
  1173. seuraavasti:
  1174.  
  1175. - Mennään offsettiin 8 (fseek(handle, 8, SEEK_SET)).
  1176. - Luetaan kaksi tavua ja tehdään niistä sana (unsigned short int,
  1177. katsomme latauskoodia kohta) ja meillä on koko vaakatasossa.
  1178. - Luetaan toiset kaksi tavua ja tehdään niille samoin kuin
  1179. edellisille, nyt meillä on y-koko.
  1180.  
  1181. Sitten onkin vaikein pala PCX:n rakenteessa. Sitä kutsutaan nimellä
  1182. RLE-koodaus (run length encoding) ja se tarkoittaa sitä, että jos
  1183. meillä on peräkkäin 10 pikseliä väriä 15 emme kirjoitakaan PCX:ään
  1184. kymmentä kertaa numeroa 15, vaan kirjoitamme sinne tavun 192+10=202 ja
  1185. sen perään tavun 15. Nyt kun PCX-lukija lukee ensimmäisen tavun se
  1186. katsoo, että ahaa, nyt tulee toistoa ja toistaa seuraavaa tavua
  1187. puskuriin tavu-192 kertaa (202-192=10). Näin me teemmekin
  1188. yksinkertaisen pseudorungon:
  1189.  
  1190. - Lue tavu1
  1191. - Jos tavu1 on suurempi kuin 192 niin lue tavu2 ja toista tavua 2
  1192. tavu1-192 kertaa.
  1193. - Jos tavu1 ei ollut suurempi laita puskuriin tavu1.
  1194.  
  1195. Näin helppoa, nyt vielä paletti. Sekin on helppoa, kunhan muistamme
  1196. kaksi seikkaa:
  1197.  
  1198. 1) Etsimme paletin tiedoston LOPUSTA päin (fseek(handle, -768, SEEK_END))
  1199. 2) Jaamme värikomponentit neljällä, sillä PCX:ssä väriarvot ovat
  1200. väliltä 0-255, VGA:ssa 0-63 (255/4=63).
  1201.  
  1202. Nyt yhdistämme taas kaiken tietomme, ja teemme funktion, joka ottaa
  1203. argumenttinaan PCX:n nimen ja puskurin jonne se ladataan. Ohjelma EI
  1204. VARAA MUISTIA puskurille, vaan se pitää varata etukäteen. Voit itse
  1205. tehdä muutokset ohjelmaan jos haluat. Yleensä kuitenkin etukäteen on
  1206. tiedossa kuvan koko, kun PCX:iä käytetään
  1207. peleissä. Kuvankatseluohjelmaa tehdessä pitää kuitenkin koko ottaa
  1208. selville jo viimeistään sen vuoksi, että kuva näytetään oikein, vaikka
  1209. puskurissa olisikin tilaa.
  1210.  
  1211. Eli tässä meillä on valmiiksi pureskeltu PCX-lataajan runko, teemme
  1212. sille oikein oman kirjaston PCX.H. Kirjasto tarvitsee stdio.h:n
  1213. tiedostonkäsittelyfunktioita ja niiden tietorakenteita:
  1214.  
  1215. void loadpcx(char *filename, char *buffer) {
  1216. int xsize, ysize, tavu1, tavu2, position=0;
  1217. FILE *handle=fopen(filename, "rb");
  1218.  
  1219. if(handle==NULL) {
  1220. printf("Virhe PCX-tiedoston avauksessa: Tiedostoa ei löydy!\n");
  1221. exit(1);
  1222. }
  1223. fseek(handle, 8, SEEK_SET);
  1224. xsize=fgetc(handle)+(fgetc(handle)<<8)+1;
  1225. ysize=fgetc(handle)+(fgetc(handle)<<8)+1;
  1226. fseek(handle, 128, SEEK_SET);
  1227. while(position<xsize*ysize) {
  1228. tavu1=fgetc(handle);
  1229. if(tavu1>192) {
  1230. tavu2=fgetc(handle);
  1231. for(; tavu1>192; tavu1--)
  1232. buffer[position++]=tavu2;
  1233. } else buffer[position++]=tavu1;
  1234. }
  1235. fclose(handle);
  1236. }
  1237.  
  1238. void loadpal(char *filename, char *palette) {
  1239. FILE *handle=fopen(filename, "rb");
  1240. int c;
  1241.  
  1242. if(handle==NULL) {
  1243. printf("Virhe PCX-tiedoston palettia luettaessa:"
  1244. " Tiedostoa ei löydy!\n");
  1245. exit(1);
  1246. }
  1247.  
  1248. fseek(handle,-768,SEEK_END);
  1249. for(c=0; c<256*3; c++)
  1250. paletti[c] =fgetc(handle)/4;
  1251. fclose(handle);
  1252. }
  1253.  
  1254. Kuten jo varmasti huomasit ovat paletin ja PCX:n latausrutiinit
  1255. erillisinä. Tämä siksi, että joskus on huomattavasti kätevämpää ladata
  1256. vain kuva, jos palettia ei mihinkään tarvita. Seuraavaksi seuraa
  1257. kappaleen esimerkkiohjelma, joka käyttää hyväkseen tutoriaalin
  1258. varrella esiteltyjä rutiineja ja muodostaa pienen esityksen. Ohjelma
  1259. lataa PCX-kuvan PICTURE.PCX ja paletin siitä. Sitten se läiskäisee sen
  1260. ruudulle. Lopuksi kuva himmenee tyhjyyteen ja palataan
  1261. tekstitilaan. Esimerkki olettaa kuvan olevan kokoa 320x200,
  1262. 256-värinen ja paletin sisältävä PCX-kuva RLE-pakattuna. Voit korvata
  1263. kuvan millä haluat joko muuttamalla lähdekoodia tai kopioimalla oman
  1264. kuvasi PICTURE.PCX:n päälle.
  1265.  
  1266. Huomaa, että ohjelmassa luodaan kaksoispuskuri, johon kuva
  1267. ladataan. Näyttömuistin vänkääminen parametriksi aiheuttaa 100%
  1268. varmasti kaatumisen, tai jos jotenkin säästyt siltä niin ainakaan
  1269. mitään ei ilmesty näytölle. Mutta asiaan (PCX1.C):
  1270.  
  1271. #include <go32.h>
  1272. #include <conio.h>
  1273. #include <stdio.h>
  1274. #include <sys/movedata.h>
  1275. #include <dos.h>
  1276.  
  1277. #include "palette.h"
  1278. #include "pcx.h"
  1279.  
  1280. #define flip(c) _dosmemputl(c, 64000/4, 0xA0000)
  1281.  
  1282. int main() {
  1283. char palette[256*3];
  1284. char dblbuf[64000];
  1285.  
  1286. textmode(0x13);
  1287. loadpcx("PICTURE.PCX", dblbuf);
  1288. loadpal("PICTURE.PCX", palette);
  1289. setpal(palette);
  1290. flip(dblbuf);
  1291. getch();
  1292. fadetoblack(palette);
  1293. textmode(0x3);
  1294.  
  1295. return 0;
  1296. }
  1297.  
  1298. Toivottavasti ymmärsit tästä luvusta ainakin käyttöperiaatteen. Eli
  1299. loadpcx(nimi, puskuri) lataa kuvan puskuriin ja flip(puskuri) laittaa
  1300. sen näytölle (jos kuva on kokoa 320x200). Paletti ladataan tarvittaessa
  1301. funktiolla loadpal(nimi, palettipuskuri) ja asetetaan aktiiviseksi
  1302. komennolla setpal(palettipuskuri). Huomaa, että esimerkissä asetetaan oikea
  1303. paletti ENNEN kuvan laittamista ruudulle. Huomataksesi miksi vaihda
  1304. setpal- ja flip-funktioiden paikkaa ja lisää väliin getch(), jotta ehdit kat-
  1305. sella rauhassa muutosta. Tällaista tässä kappaleessa. Mene nyt kokeilemaan
  1306. PCX-kuvien latausta. Seuraavassa kappaleessa tutustummekin sitten johonkin
  1307. peliohjelmoijaa lähellä olevaan asiaan...
  1308.  
  1309.  
  1310. 4.1 Bitmapit - eikai vain suunnistusta?
  1311. ---------------------------------------
  1312.  
  1313. Tänään siis teemme pienen bitmap-enginen C:llä. Itse olen aiemmin tehnyt
  1314. kaikki sprite- ja bitmap -rutiinini C++:ssalla, mutta tällä kertaa
  1315. käytämme C:tä, sillä haluan näiden esimerkkien toimivan ilman plussiakin.
  1316. Eli mitä on bitmap?
  1317.  
  1318. Bitmap, eli bittikartta on määrätyn kokoinen suorakulmion muotoinen esine,
  1319. jolla on puskuri muistissa sisältäen sen värit, kuten näyttöpuskurinkin
  1320. kanssa on. Hyödylliseksi bitmapin tekee se, että laitamme siihen pyyhkimis-
  1321. ja piirtotoiminnot, sekä liikutustoiminnot, joilla voimme siirrellä bitmap-
  1322. piamme ympäri ruutua. Lisäksi teemme siihen värin, joka tarkoittaa ettei
  1323. sitä kohtaa bitmapista tarvitse kopioida ruudulle. Näin saamme tehtyä bit-
  1324. mappiimme reikiä, eli teemme sen osittain läpinäkyväksi. Mutta miten tämä
  1325. kaikki sitten tehdään? Koko asia on, kuten kaikki asiat ohjelmoinnissa lo-
  1326. pulta ovat - naurettavan helppo.
  1327.  
  1328. Eli, menkäämme takaisin kaksoispuskurin aikoihin. Siinä meillä on
  1329. puskuri, jonka koko on 320x200 pikseliä ja se kopioidaan kokonaan näytön
  1330. päälle. Bittikartassa on muutama selkeä ero:
  1331.  
  1332. - Se voi alkaa mistä tahansa kohdasta ruutua, vaikka koordinaateista
  1333. 15, 123.
  1334. - Se voi olla minkä kokoinen tahansa (yleensä kuitenkin ruutua pienempi).
  1335. - Sen peittämä tausta tallennetaan ja palautetaan kun bittikartta
  1336. pyyhitään pois, mikä mahdollistaa liikuttelemisen.
  1337. - Siinä on läpinäkyvä väri, meillä 0, jota ei piirretä ruudulle. Jos siis
  1338. koko bittikartta olisi väriä 0, emme näkisi ruudulla mitään!
  1339.  
  1340. Eli itseasiassa bittikartta on pari puskuria, joille on varattu tilaa
  1341. siten, että jokainen bittikartan väri voidaan säilöä
  1342. puskuriin. Puskureita on perusbittikartassa kaksi, eli itse kuvan
  1343. sisältävä kartta, joka on järjestelty aivan samoin kuin
  1344. esim. kaksoispuskuri, mutta koko on bittikartan mukainen. Toinen on
  1345. taustapuskuri, joka on muuten sama, mutta sinne vain säilötään
  1346. piirrettäessä alle jääneet pikselit, jotta ne voidaan bittikarttaa
  1347. ruudulta pyyhkiessä palauttaa sieltä.
  1348.  
  1349. Eli tällainen voisi olla 3x3 kokoinen bittikartta:
  1350.  
  1351. Bittikartta: Taustapuskuri (mitä bittikartan alle on
  1352. piirrettäessä jäänyt):
  1353. 30 20 19 0 0 0
  1354. 19 23 42 0 0 0
  1355. 12 32 43 0 0 0
  1356.  
  1357. Kuten huomaatte bittikartta on piirretty mustalle pohjalle, sillä
  1358. taustapuskuri eli se mitä bittikartan alle jäi on täynnä mustaa, eli
  1359. väriä 0. Bittikartta on kaikkein helpointa määritellä omaan
  1360. datarakenteeseensa, joka sisältää tarvittavat tiedot kartan piirtelyyn
  1361. ja pyyhkimiseeen, nimetään se vaikka structiksi BITMAP.
  1362.  
  1363. Koordinaattien määrittely saavutetaan siten, että meillä on rakenteessamme
  1364. X-ja Y-koordinaatit, joista piirto kaksoispuskuriin aloitetaan. Koko
  1365. taas on helpompi. Jos kaksoispuskurin koko oli 320x200, niin kaava
  1366. oikean pikselin hakemiseksi oli y*320+x. Jos meillä on bitmap kokoa
  1367. ysize * xsize, niin oikea koordinaatti on y*xsize+x. Piirrettäessä
  1368. loopataan X:ää ja Y:tä siten, että luemme yksi kerrallaan pikselin
  1369. bittikartasta, ja jos se on jokin muu kuin väri 0 (yleensä musta, tämä
  1370. oli siis läpinäkyväksi sovittu väri), otamme ensin sen alle jäävän
  1371. pikselin talteen taustapuskuriin ja laitamme sitten vasta bittikartan
  1372. värin ruudulle oikeaan kohtaan (bittikartan värit sisältävästä
  1373. puskurista).
  1374.  
  1375. Eli tarvittavat tiedot bittikarttarakenteeseen ovat:
  1376.  
  1377. - bittikartan värit (char * -pointteri)
  1378. - taustan värit (char * -pointteri)
  1379. - x-sijainti ruudulla (int)
  1380. - y-sijainti ruudulla (int)
  1381. - koko x-suunnassa (int)
  1382. - koko y-suunnassa (int)
  1383.  
  1384. Lisäksi meillä on xspeed ja yspeed, joita käytetään esimerkeissä
  1385. säilömään bittikartan liikenopeutta x- ja y-suunnassa. Näillä
  1386. tempuilla meillä on nyt teoria liikuteltavan bitmapin tekemiseksi.
  1387. Ensin määrittelemme rakenteen, joka sisältää kaiken tarvittavan tiedon
  1388. bittikartastamme (BITMAP.H):
  1389.  
  1390. typedef struct {
  1391. char *bitmap;
  1392. char *background;
  1393. int x;
  1394. int y;
  1395. int xsize;
  1396. int ysize;
  1397. int xspeed;
  1398. int yspeed;
  1399. } BITMAP;
  1400.  
  1401. Sitten tehtävänämme on tehdä "interface", eli käyttöliittymä
  1402. bitmap-engineemme. Siihen sisällytämme seuraavat funktiot:
  1403.  
  1404. - bdraw(BITMAP *b) piirtää bittikartan kohtaan BITMAP.x, BITMAP.y
  1405.  
  1406. - bhide(BITMAP *b) tyhjentää edellisellä piirtokerralla piirretyn bitti-
  1407. kartan. Huomaa, että JOKAISEN PIIRRON JÄLKEEN ON TULTAVA TYHJENNYS
  1408. ja että BITTIKARTTAA EI LIIKUTETA SEN OLLESSA RUUDULLA (todellisuudessa
  1409. tietenkin kaksoispuskurissa, joka kopioidaan ruudulle kun kaikki bitti-
  1410. kartat ovat näkyvissä, sanoinhan, että hyödymme vielä siitä!)
  1411.  
  1412. - bmove(BITMAP *b) lisää X-koordinaattiin muuttujan BITMAP.xspeed ja
  1413. Y-koordinaattiin vastaavasti muuttujan BITMAP.yspeed.
  1414.  
  1415. - bsetlocation(BITMAP *b, int x, int y) asettaa uudet X- ja
  1416. Y-koordinaatit.
  1417.  
  1418. - bsetspeed(BITMAP *b, int xspeed, int yspeed) asettaa uudet X- ja
  1419. Y-nopeudet. Huomaa, että liike ylös saavutetaan negatiivisella
  1420. Y-nopeudella ja vastaavasti liike vasemmalle negatiivisellä
  1421. X-nopeudella.
  1422.  
  1423. - bload(BITMAP *b, int x, int y, int xspeed, int yspeed, int xsize,
  1424. int ysize, char *bitmapbuffer, int bufferx, int buffery,
  1425. int bufferxs), jossa 8. parametristä lähtien kertoo
  1426. latauspuskurista, jona tulemme käyttämään 320x200 kokoista PCX, kuvaa,
  1427. sisältäen kaikki bitmapit mitä pitää ladata. Jos kuvan x-koko ja y-koko,
  1428. sekä aloituskoordinaatit kuvassa on ilmoitettu oikein, onnistuu lataus
  1429. suorakulmion muotoiselta alueelta täysin onnistuneesti, eikä lataus-
  1430. rutiinin käyttö vaadi kovin paljoa miettimistä. Lisää käytöstä ajal-
  1431. laan tulevassa esimerkissä.
  1432.  
  1433. No niin. Lähtekäämme tekemään kirjastoamme BITMAP.H yksi funktio kerrallaan.
  1434. Rakenne BITMAP on jo esitelty, joten alkakaamme keräämään sen perään
  1435. käsittelyfunktioita. Ensimmäisenähän oli vuorossa bdraw(), joka onkin
  1436. helpoimpia ja tärkeimpiä funktioita. Katsellaanpas esimerkkikoodia:
  1437.  
  1438. void bdraw(BITMAP *b) {
  1439. int y=b->y,
  1440. x=b->x,
  1441. yy, xx;
  1442.  
  1443. /* Eli loopataan koko suorakulman kokoinen alue. bitmap- ja
  1444. ja background -puskureissahan lasketaan sijainti seuraavasti:
  1445. y * b->xsize + x. */
  1446.  
  1447. for(yy=0; yy<b->ysize; yy++) {
  1448. for(xx=0; xx<b->xsize; xx++) {
  1449.  
  1450. /* eli värillä 0 tämä vertailu alla ei ole tosi, joten värillä
  1451. 0 merkittyjä kohtia EI piirretä! */
  1452.  
  1453. if(b->bitmap[yy*b->xsize+xx]) {
  1454.  
  1455. /* doublebuffer muuttuja osoittaa kaksoispuskuriin. Huomaa, että
  1456. yläkulma on y*320+x, mutta koska haluamme vielä piirtää useita
  1457. rivejä, lisäämme yy-looppimme y-arvoon, kutenn myös xx-looppi
  1458. x-arvoon. Jos et ymmärtänyt niin poista väliaikaisesti kohdat
  1459. ja näet mitä tapahtuu */
  1460.  
  1461. b->background[yy*b->xsize+xx]=
  1462. doublebuffer[ (y+yy) * 320 + (x+xx) ];
  1463.  
  1464.  
  1465. /* sitten vain asetetaan bittikartasta oikea kohta ruudulle,
  1466. alle peittyvä osa on jo tallessa puskurin background vastaa-
  1467. valla kohdalla. */
  1468.  
  1469. doublebuffer[ (y+yy) * 320 + (x+xx) ]=
  1470. b->bitmap[yy*b->xsize+xx];
  1471. }
  1472. }
  1473. }
  1474. }
  1475.  
  1476. Koska joiltakin on esiintynyt valituksia siitä, että koodi jää hämärän
  1477. peittoon, niin esittelen tässä saman pseudona, jos se olisi hieman
  1478. selvempää:
  1479.  
  1480. funktio bdraw
  1481. kokonaisluvun kokoiset kierroslaskurit a ja b
  1482.  
  1483. looppaa a välillä 0 - <y-koko>
  1484. looppaa b välillä 0 - <x-koko>
  1485.  
  1486. bittikarttasijainti = a * <x-koko> + b
  1487. ruutusijainti = ( <y-sijainti> + a ) * 320 + b + <x-sijainti>
  1488.  
  1489. jos bittikartta(bittikarttasijainti) ei ole 0 niin
  1490.  
  1491. tausta(bittikarttasijainti) = kaksois(ruutusijainti)
  1492. kaksois(ruutusijainti) = bittikartta(bittikarttasijainti)
  1493.  
  1494. end jos
  1495.  
  1496. end looppi b
  1497. end looppi a
  1498.  
  1499. end funktio
  1500.  
  1501. Kun lähdet korvaamaan a:n muuttujalla yy ja b:n muuttujalla xx ja
  1502. korvaat bittikartan sisäiset muuttujat <y-koko>, <x-koko>,
  1503. <y-sijainti> ja <x-sijainti> BITMAP-rakenteen muuttujilla b->ysize,
  1504. b->xsize, b->y ja b->x sekä tausta:n ja bittikartan:n
  1505. b->background:illa ja b->bitmap:illa, kaksois-muuttujan
  1506. kaksoispuskurisi nimellä niin olet aikalailla ensimmäisessä,
  1507. alkuperäisessä sorsassa. Jos yhtään selventää niin voit poistaa
  1508. kommentit alkuperäisestä sorsasta kokonaan ja siirtää sijainnin laskut
  1509. sieltä []-sulkeiden sisästä juuri tuollaisiin
  1510. bittikarttasijainti-tyylisiin apumuuttujiin, jolloin koodi selvenee
  1511. hieman. Olkoot, tässä se on:
  1512.  
  1513. void bdraw(BITMAP *b) {
  1514. int a, b, bitmapsijainti, ruutusijainti;
  1515.  
  1516. for(a=0; a < b->ysize; a++) {
  1517. for(b=0; b < b->xsize; b++) {
  1518. bitmapsijainti=a * b->xsize + b;
  1519. ruutusijainti = ( b->y + a ) * 320 + b + b->x;
  1520.  
  1521. if(b->bitmap[bitmapsijainti] != 0) {
  1522.  
  1523. b->background[bitmapsijainti] = doublebuffer[ruutusijainti];
  1524. doublebuffer[ruutusijainti] = b->bitmap[bitmapsijainti];
  1525.  
  1526. }
  1527. }
  1528. }
  1529. }
  1530.  
  1531. Varaa aikaa edellisten tutkimiseen, sillä on tärkeää, että ymmärrät periaat-
  1532. teen. Tietenkin saat lisäselvyyttä kokeilemalla muuttaa noita kohtia, jol-
  1533. loin näet muutoksen kääntämällä uudelleen esimerkkiohjelman, jonka
  1534. myöhemmin esittelemme ja ajamalla muunnellun version. Seuraavana onkin
  1535. huomattavasti nopeammin tehty pyyhintäfunktio, joka eroaa vain siten, että
  1536. sen sijaan, että säilöisimme taustan ja korvaisimme ruudun pikselin
  1537. bitmap-puskurin arvolla laitammekin background-puskuriin tallennetun pikse-
  1538. lin takaisin kaksoispuskuriin, joka on piilotusfunktion jälkeen samassa
  1539. kunnossa kuin ennen piirtoakin!
  1540.  
  1541. void bhide(BITMAP *b) {
  1542. int y=b->y,
  1543. x=b->x,
  1544. yy, xx;
  1545.  
  1546. /* Eli loopataan koko suorakulman kokoinen alue. bitmap- ja
  1547. ja background -puskureissahan lasketaan sijainti seuraavasti:
  1548. y * b->xsize + x. */
  1549.  
  1550. for(yy=0; yy<b->ysize; yy++) {
  1551. for(xx=0; xx<b->xsize; xx++) {
  1552.  
  1553. /* eli värillä 0 tämä vertailu alla ei ole tosi, joten värillä
  1554. 0 merkittyjä kohtia EI piirretä! */
  1555.  
  1556. if(b->bitmap[yy*b->xsize+xx]) {
  1557. doublebuffer[ (y+yy) * 320 + (x+xx) ]=
  1558. b->background[yy*b->xsize+xx];
  1559. }
  1560. }
  1561. }
  1562. }
  1563.  
  1564. Tuohon ette varmaan enää pseudoja tarvitse, koska sehän eroaa
  1565. edellisestä vain tuon sijoituksen osalta, eli ensimmäinen sijoitus
  1566. draw-funktiosta käännetään vain toisinpäin, niin alkup. tausta
  1567. palautuu.
  1568.  
  1569. Seuraavaksi kolme helponta funktiota heti rivissä, sillä niiden toteuttami-
  1570. nen on helppoa ja ymmärtäminen vielä helpompaa, muista, että X-ja Y-koor-
  1571. dinaatteja vähennetään negatiivisill nopeuksilla, sillä X+(-1)=X-1:
  1572.  
  1573. void bmove(BITMAP *b) {
  1574. b->x+=b->xspeed;
  1575. b->y+=b->yspeed;
  1576. }
  1577.  
  1578. void bsetlocation(BITMAP *b, int x, int y) {
  1579. b->x=x;
  1580. b->y=y;
  1581. }
  1582.  
  1583. void bsetspeed(BITMAP *b, int xspeed, int yspeed) {
  1584. b->xspeed=xspeed;
  1585. b->yspeed=yspeed;
  1586. }
  1587.  
  1588. Seuraava onkin vaikea pala, joten lisään koodia saadakseni siitä vähän
  1589. selvemmäksi. Idea siis on, että otamme pikselin tuplapuskuriin ladatus-
  1590. ta ja laitamme sen bitmap-puskuriin. Eli oikeastaan käänteisesti näyt-
  1591. töfunktioon nähden. Eli katsotaanpas:
  1592.  
  1593. void bload(BITMAP *b, int x, int y, int xspeed, int yspeed, int xsize,
  1594. int ysize, char *bitmapbuffer, int bufferx, int buffery,
  1595. int bufferxs) {
  1596. int yy, xx;
  1597.  
  1598. bsetlocation(b, x, y);
  1599. bsetspeed(b, xspeed, yspeed);
  1600. b->xsize=xsize;
  1601. b->ysize=ysize;
  1602.  
  1603. b->bitmap=(char *)malloc(xsize*ysize);
  1604. b->background=(char *)malloc(xsize*ysize);
  1605. if(b->background==NULL || b->background==NULL) {
  1606. printf("Ei tarpeeksi muistia bitmap-puskureille!\n");
  1607. exit(1);
  1608. }
  1609.  
  1610. /* Eli loopataan koko suorakulman kokoinen alue. bitmap-
  1611. puskurissahan lasketaan sijainti seuraavasti:
  1612. y * b->xsize + x. */
  1613.  
  1614. for(yy=0; yy<ysize; yy++) {
  1615. for(xx=0; xx<xsize; xx++) {
  1616.  
  1617. /* doublebuffer muuttuja osoittaa kaksoispuskuriin. Huomaa, että
  1618. yläkulma on y*320+x, mutta koska haluamme vielä piirtää useita
  1619. rivejä, lisäämme yy-looppimme y-arvoon, kutenn myös xx-looppi
  1620. x-arvoon. Jos et ymmärtänyt niin poista väliaikaisesti kohdat
  1621. ja näet mitä tapahtuu */
  1622.  
  1623. b->bitmap[yy*xsize+xx]=
  1624. bitmapbuffer[ (buffery+yy) * bufferxs + (bufferx+xx) ];
  1625. }
  1626. }
  1627. }
  1628.  
  1629. bload on itseasassa täysin sama kuin ensimmäinenkin funktio, mutta
  1630. alussa meillä on pari alustusta jotta BITMAP-rakenne saadaan halutuksi
  1631. (muistinvarausta, sijainnin nollausta, koon alustus...). Vain
  1632. piirtofunktio on korvattu versiolla, joka ei piirrä ruudulle, vaan
  1633. lataa ruudulta (bitmapbuffer tässä tapauksessa, jottei tarvi oikeaa
  1634. kaksoispuskuria välttämättä käyttää) pikselit. Ei se loppujenlopuksi
  1635. ole sen vaikeampi.
  1636.  
  1637. Nyt kun lisäämme kaikki yhteen kirjastoomme BITMAP.H ja teemme lopuksi
  1638. vielä pienen esimerkkiohjelman, joka liikuttelee palloa
  1639. ruudulla. Koska kirjastomme ei kykene estämään ruudun yli menemisiä,
  1640. niin meidän pitää kääntää liikkuvan pallon suuntaa ennenkuin alareuna
  1641. osuu ruudun alareunaan ja menee sitten siitä yli (eli jos bittikartan
  1642. koko, sijainti ja nopeus yhteenlaskettuna on yli ruudun koon, tai
  1643. bittikartan sijainti ja nopeus yhteenlaskettuna on pienempi kuin
  1644. 0). Eli kun jompikumpi edellisistä ehdoista täyttyy niin käännetään
  1645. pallon suuntaa ja saadaan pallo "pomppimaan" reunoista.
  1646.  
  1647. Mutta, olemme taas puhuneet ihan tarpeeksi. Menkäämme nyt esimerkkiohjel-
  1648. mamme pariin (BITMAP1.C). Siinä lataamme bittikartan tiedostosta BITMAP.PCX
  1649. ja tausta tiedostosta BITBACK.PCX. Näin näemme läpinäkyvyyden toiminnassa
  1650. (muutenhan pallo olisi neliönmuotoinen). Lisäksi tietenkin käytämme jo va-
  1651. kioiksi muuttuneita palettifunktiota ohjelmamme koristukseksi:
  1652.  
  1653. #include <go32.h>
  1654. #include <sys/movedata.h>
  1655. #include <conio.h>
  1656. #include <stdio.h>
  1657. #include <dos.h>
  1658. #include <stdlib.h>
  1659.  
  1660. char *doublebuffer;
  1661.  
  1662. #include "palette.h"
  1663. #include "pcx.h"
  1664. #include "bitmap.h"
  1665.  
  1666. #define flip(c) _dosmemputl(c, 64000/4, 0xA0000)
  1667.  
  1668. int main() {
  1669. char palette[768];
  1670. BITMAP bitmap;
  1671.  
  1672. doublebuffer=(char *)malloc(64000);
  1673. if(doublebuffer==NULL) {
  1674. printf("Ei tarpeeksi muistia kaksoipuskurin varaukseen!\n");
  1675. return 1;
  1676. }
  1677. textmode(0x13);
  1678. loadpcx("BITMAP.PCX", doublebuffer);
  1679. loadpal("BITMAP.PCX", palette);
  1680. setpal(palette);
  1681. bload(&bitmap, 160, 100, 1, 1, 16, 16, doublebuffer, 1, 1, 320);
  1682. loadpcx("BITBACK.PCX", doublebuffer);
  1683. /* Lataus vasta kun bittikartta on otettu edellisestä tiedostosta.
  1684. Ei ladata palettia koska se on sama kuin edellisessä PCX:ssä. */
  1685.  
  1686. while(!kbhit()) {
  1687. bdraw(&bitmap);
  1688. waitsync();
  1689. flip(doublebuffer);
  1690. bhide(&bitmap);
  1691. bmove(&bitmap);
  1692. if((bitmap.x+bitmap.xsize+bitmap.xspeed)>320 ||
  1693. bitmap.x+bitmap.xspeed<0)
  1694. bitmap.xspeed= -bitmap.xspeed;
  1695. if((bitmap.y+bitmap.ysize+bitmap.yspeed)>200 ||
  1696. bitmap.y+bitmap.yspeed<0)
  1697. bitmap.yspeed= -bitmap.yspeed;
  1698. }
  1699. getch();
  1700. fadetoblack(palette);
  1701. textmode(0x3);
  1702.  
  1703. return 0;
  1704. }
  1705.  
  1706. Varaa kunnolla aikaa ja tutki lähdekoodeja, mieti teoriaa ja kokeile kaikkea
  1707. käytännössä mitä mieleen tulee. Kun luulet keksineesi idean niin palaa
  1708. takaisin dokumentin ääreen, ja siirrymme seuraavaan aiheesemme. Menehän
  1709. siitä! Jos vieläkin tuntui siltä ettet tajunnut niin ota yhteyttä ja
  1710. kysy mikä jäi mietityttämään, niin tarkennan sitten vielä tätä.
  1711.  
  1712.  
  1713. 4.2 Animaatiot
  1714. --------------
  1715.  
  1716. Tämänkertainen aiheemme on pieni parannus koodiin, joka on paljon näy-
  1717. töllä ja jonka jälkeen on tämän tutoriaalin bittikarttarutiinit lähes kä-
  1718. sitelty. Tulemme kyllä hyväksikäyttämään edellisen kappaleen koodia
  1719. tehdessämme fonttiengineä, sekä parantelemme koodia tehdessämme törmäys-
  1720. tarkistuksen, mutta itse animointi- ja bittikarttateoria käsitellään
  1721. kokonaan tässä ja edellisessä kappaleessa.
  1722.  
  1723. Eli tänään tutustumme ensimmäisenä animaatiohin. Mitä animaatiot sitten
  1724. ovat? No itseasiasas animaatio on vain sarja kuvia, joita vaihdellaan
  1725. ja saadaan kuva liikkeestä. Animaatiota voidaan käyttä lähes kaikkeen
  1726. pelissä. Sillä voidaan tehdä pyörivä alusanimaatio, jonka jokainen
  1727. kuva on yksi aluksen suunta. Jokaisella suunnalla voisi olla vielä oma
  1728. animaationsa, joka saa vaikka rakettimoottorit hehkumaan ja laserit
  1729. aiheuttamaan välähdyksiä aluksen pinnassa. Pienellä mielikuvituksella
  1730. ja taitavalla graafikolla päästään ihmeisiin. Tässä kappaleessa esi-
  1731. telty kirjasto ei varmaankaan käy suoraan moneen tarkoitukseen tai ole
  1732. tarpeeksi nopea peliin, mutta enginen onkin vain tarkoitus näyttää
  1733. pääperiaatteita animoinnin ja muiden olennaisien asioiden takana.
  1734.  
  1735. Eli animaatio on kuvasarja, jotka näytetään tietyssä järjestyksessä. Miten
  1736. sitten toteutamme tämän. Tässä on tapa jolla minä olen sen tehnyt. Meillähän
  1737. on täysin toimivat rutiinit yhden kuvan näyttämiseen. Tehkäämme vain
  1738. animointikoodi, joka vaihtaa pointterin bitmap osoittamaan seuraavaan
  1739. kuvaa, eli frameen. Tätä täytyy kutsua silloin kun spriteä, joksi kutsumme
  1740. animoivaa bittikarttaamme tästälähin ei ole piirretty puskuriin. Jälleen
  1741. voit kokeilla siirtää animointikoodin kutsun kohtaan jossa esine on piir-
  1742. rettynä, mutta se ei tule näyttämään hyvältä (jos objektin peittämän alueen
  1743. muoto muuttuu). Eli siis tarvitsemme uuden rakenteen, joka voi säilöä
  1744. useita kuvia, koodin joka vaihtaa bitmap-pointterin osoittamaan seuraavaan
  1745. kuvaan, laskurin joka kertoo monennessako kuvassa mennään ja toisen muuttu-
  1746. jan joka kertoo montako kuvaa meillä on animaatiossa, sekä lopulta uuden
  1747. latausfunktion, joka osaa ladata useita kuvia käsittävän animaation.
  1748.  
  1749. Tähän kaikkeen voimme kopioida vanhaa koodiamme ja lisäillä sinne tar-
  1750. peellisia osia. Eli teemme nyt uuden rakenteen, jossa voi olla maksimis-
  1751. saan MAXFRAME määrä frameja, eli kuvia (tämä toteutuksen helpottamiseksi):
  1752.  
  1753. #define MAXFRAME 64
  1754.  
  1755. typedef struct {
  1756. char *frame[MAXFRAME];
  1757. int curfrm;
  1758. int frames;
  1759.  
  1760. char *bitmap;
  1761. char *background;
  1762. int x;
  1763. int y;
  1764. int xsize;
  1765. int ysize;
  1766. int xspeed;
  1767. int yspeed;
  1768. } SPRITE;
  1769.  
  1770. Se olikin helppoa. Nämä rutiinit tulevat kirjastoon SPRITE.H, josta löydät
  1771. myös joukon vanhoja tuttujamme uudelleennimettynä ja vähän
  1772. muunneltuina (sdraw, shide...). Seuraavaksi sitten animointirutiini:
  1773.  
  1774. void sanimate(SPRITE *s) {
  1775. s->curfrm++;
  1776. if(s->curfrm >= s->frames)
  1777. s->curfrm=0;
  1778. s->bitmap=s->frame[s->curfrm];
  1779. }
  1780.  
  1781. Radikaaleja muutoksia tarvinnee myös latausrutiinimme. Tärkeimmät muutok-
  1782. set siinä on, että se lukee framet rivistä. Katso SPRITE.PCX esimerkkinä
  1783. tällaisesta animaatiosta. Jos ihmettelet outoja kertolaskuja joissain
  1784. kohdin se johtuu siitä, että jokaisen framen jälkeen hypätään 1 pikseli
  1785. yli, sillä teemme rajat animaatioiden väliin selvennykseksi. Eli tässä
  1786. olisi latauskoodimme, uusi parametri on animaatioiden määrä:
  1787.  
  1788. void sload(SPRITE *s, int x, int y, int xspeed, int yspeed, int xsize,
  1789. int ysize, char *bitmapbuffer, int bufferx, int buffery,
  1790. int bufferxs, int frames) {
  1791. int yy, xx, current;
  1792.  
  1793. ssetlocation(s, x, y);
  1794. ssetspeed(s, xspeed, yspeed);
  1795. s->xsize=xsize;
  1796. s->ysize=ysize;
  1797. s->curfrm=0;
  1798. s->frames=frames;
  1799.  
  1800. for(current=0; current<frames; current++) {
  1801. s->frame[current]=(char *)malloc(xsize*ysize);
  1802. if(s->frame[current]==NULL) {
  1803. printf("Ei tarpeeksi muistia sprite-puskureille!\n");
  1804. exit(1);
  1805. }
  1806. }
  1807. s->background=(char *)malloc(xsize*ysize);
  1808. s->bitmap=s->frame[s->curfrm];
  1809.  
  1810. if(s->background==NULL) {
  1811. printf("Ei tarpeeksi muistia sprite-puskureille!\n");
  1812. exit(1);
  1813. }
  1814.  
  1815. /* Eli loopataan koko suorakulman kokoinen alue. bitmap-
  1816. puskurissahan lasketaan sijainti seuraavasti:
  1817. y * s->xsize + x. Uloimpana looppina on uutena framelooppi,
  1818. joka on lisätty koska meidän pitää ladata usea kuva. */
  1819. for(current=0; current<frames; current++)
  1820. for(yy=0; yy<ysize; yy++) {
  1821. for(xx=0; xx<xsize; xx++) {
  1822. /* doublebuffer muuttuja osoittaa kaksoispuskuriin. Huomaa, että
  1823. yläkulma on y*320+x, mutta koska haluamme vielä piirtää useita
  1824. rivejä, lisäämme yy-looppimme y-arvoon, kutenn myös xx-looppi
  1825. x-arvoon. Jos et ymmärtänyt niin poista väliaikaisesti kohdat
  1826. ja näet mitä tapahtuu */
  1827. s->frame[current][yy*xsize+xx]=
  1828. bitmapbuffer[ (buffery+yy) * bufferxs + (bufferx+xx) +
  1829. (xsize+1)*current ];
  1830. }
  1831. }
  1832. }
  1833.  
  1834. Kirjastoon SPRITE.H lisätään vielä bdraw, bhide, bmove, bsetlocation ja
  1835. bsetspeed nimettynä nimillä sdraw, shide, smove, ssetlocation ja ssetspeed
  1836. funktioiden erottamiseksi bitmap-rutiineista (jos vaikka halutaan käyttää
  1837. molempia). Muitakin pikkumuutoksia on tehty. Huomaat ne helposti
  1838. kurkkaamalla kirjaston sisään. Nyt meillä onkin animaatiot taitava engine,
  1839. jota meidän täytyy tietenkin heti kokeilla. Tässä on esimerkkiohjelmamme
  1840. SPRITE1.C, joka havainnoi funktioiden käyttöä:
  1841.  
  1842. #include <go32.h>
  1843. #include <sys/movedata.h>
  1844. #include <conio.h>
  1845. #include <stdio.h>
  1846. #include <stdlib.h>
  1847. #include <dos.h>
  1848.  
  1849. char *doublebuffer;
  1850.  
  1851. #include "palette.h"
  1852. #include "pcx.h"
  1853. #include "sprite.h"
  1854.  
  1855. #define flip(c) _dosmemputl(c, 64000/4, 0xA0000)
  1856.  
  1857. int main() {
  1858. char palette[768];
  1859. SPRITE sprite;
  1860.  
  1861. doublebuffer=(char *)malloc(64000);
  1862. if(doublebuffer==NULL) {
  1863. printf("Ei tarpeeksi muistia kaksoipuskurin varaukseen!\n");
  1864. return 1;
  1865. }
  1866. textmode(0x13);
  1867. loadpcx("SPRITE.PCX", doublebuffer);
  1868. loadpal("SPRITE.PCX", palette);
  1869. setpal(palette);
  1870. sload(&sprite, 160, 100, 1, 1, 16, 16, doublebuffer, 1, 1, 320, 8);
  1871. loadpcx("BITBACK.PCX", doublebuffer);
  1872. /* Lataus vasta kun bittikartta on otettu edellisestä tiedostosta.
  1873. Ei ladata palettia koska se on sama kuin edellisessä PCX:ssä. */
  1874.  
  1875. while(!kbhit()) {
  1876. sdraw(&sprite);
  1877. waitsync();
  1878. waitsync();
  1879. flip(doublebuffer);
  1880. shide(&sprite);
  1881. smove(&sprite);
  1882. sanimate(&sprite);
  1883. if((sprite.x+sprite.xsize+sprite.xspeed)>320 ||
  1884. sprite.x+sprite.xspeed<0)
  1885. sprite.xspeed= -sprite.xspeed;
  1886. if((sprite.y+sprite.ysize+sprite.yspeed)>200 ||
  1887. sprite.y+sprite.yspeed<0)
  1888. sprite.yspeed= -sprite.yspeed;
  1889. }
  1890. getch();
  1891. fadetoblack(palette);
  1892. textmode(0x3);
  1893.  
  1894. return 0;
  1895. }
  1896.  
  1897. Luultavasti huomaat nykimistä, sillä täysin optimoimaton sprite-enginemme
  1898. ei aivan pysty 70 frameen sekunnissa. Siksi laitoin ohjelmamme odottamaan
  1899. kahta vertical retracea, jotta nykiminen ei olisi niin häiritsevää
  1900. (P75:lläni kahdella waitilla meno näyttää paljon tasaisemmalta, eikä yhden
  1901. framen hyppy näy läheskään niin selvästi). Jos kuitenkin sinulla on hidas
  1902. kone niin poista toinen tai kummatkin odotuksista, se nopeuttaa koodia
  1903. paljon, mutta voit joutua laittamaan delay-komennolla viivettä säätääksesi
  1904. pyörimistä tasaisemmaksi. Pienellä optimoinnilla olisimme toki saaneet
  1905. moninkertaisesti lisää nopeutta, mutta koodi olisi menettänyt luettavuut-
  1906. taan, joka esimerkkiohjelmien tarkoitus on. Tietenkin kun alat tekemään
  1907. omaa peliäsi teet uudet ja paremmin tarkoitukseesi sopivat rutiinit ke-
  1908. räämiesi tietojen pohjalta.
  1909.  
  1910. Nyt onkin tämän kappaleen aika loppua ja sinun on aika paneutua uuden
  1911. asian pariin. Seuraavassa luvussamme käsitelläänkin sitten viimeistä
  1912. kysymystä spritejen parissa, monen spriten käyttöä, niiden törmäyksiä
  1913. ja ylitseliukumisia. Mutta nyt jätän sinut rauhaan. Näemme seuraavassa
  1914. luvussa!
  1915.  
  1916.  
  1917. 4.3 Pitääkö spriten törmätä? Entä coca-colan?
  1918. ---------------------------------------------
  1919.  
  1920. Nyt pääsemmekin vihoviimeiseen vaiheeseen teoriassamme ja ryyditämme sitä
  1921. pienin, tai ehkä niinkään pienin muutoksin SPRITE.H-kirjastoomme. Nimit-
  1922. täin jokainen vähänkään vakavasti pelintekoa harkinnut tarvitsee useampia
  1923. kuin yhden spriten. Mutta mitä tapahtuu kun ne ovat menossa päällekäin?
  1924. Jos teet vain loopin, joka piirtää spriten ja toisen, joka pyyhkii ne
  1925. samassa järjestyksessä olet varmaan huomannut, että se ei aiheuta toivot-
  1926. tuja tuloksia. Muutos mitä tarvitaan on pieni ja yksinkertainen, mutta
  1927. ajatellaanpas esimerkkiämme.
  1928.  
  1929. Ajatellaan, että sinulla on kolme pikseliä. Punainen, sininen ja keltainen.
  1930. Haluat laittaa ne samaan kohtaan ruudulle. Laitat ne edellä olevassa
  1931. järjestyksessä mustalle ruudulle ja laitat lapulle muistiin punaisen koh-
  1932. dalle, että sen alla oli musta, sinisen kohdalle, että sen alla oli
  1933. punainen ja keltaisen kohdalle, että sen alla oli sininen.
  1934.  
  1935. Nyt haluat poistaa ne. Ottaisitko ne nyt samassa järjestyksessä, eli ensin
  1936. punainen, sitten sininen ja lopuksi keltainen? Et, sillä jos ottaisit lopuksi
  1937. keltaisen, katsoisit lapustasi sen alla olleen sinisen värin ja ruutu
  1938. muuttuisikin siniseksi. Tässä meidän täytyykin mennä käänteisesti, eli
  1939. keltainen, sininen ja sitten vasta punainen, jonka tilalle laitat lopulta
  1940. mustan ja kaikki on hyvin.
  1941.  
  1942. Eli jos sinulla olisi 10 bittikarttaa taulukossa SPRITE s[10], niin niiden
  1943. piirto ja pyyhkiminen tapahtuisi seuraavasti:
  1944.  
  1945. for(c=0; c<10; c++) sdraw(s[c]);
  1946. flip(doublebuffer);
  1947. for(c=10; c>=0; c--) shide(s[c]);
  1948.  
  1949. Ja ei enää toimimattomia koodinpätkiä, vaan hienosti toistensa ylitse
  1950. liukuvat spritet.
  1951.  
  1952. Mutta aina ei haluta kaikkien vain liukuvan toistensa ylitse. Miltä
  1953. näyttäisi matopeli, jossa madot kiltisti liukuvat toistensa ylitse?
  1954. Ei kovin oikealta, sanoisin. Meidän täytyy siis tehdä rutiini, joka
  1955. tarkistaa törmäyksen kahden spriten välillä. Olkoon sen kutsutapa
  1956. seuraava: scollision(SPRITE *a, SPRITE *b) ja se palauttaa arvon
  1957. 1 jos törmäys on tapahtunut, muuten se palauttaa nollan. Jos siis
  1958. haluat tehdä törmäyksen tultua jotakin, niin koodi menisi suurinpiirtein
  1959. näin:
  1960.  
  1961. if(scollision(sprite[0], sprite[1]))
  1962. tee_jotain_kun_tulee_pamahdus();
  1963.  
  1964. Mutta, miten toimii tämä salaperäinen funktiomme? Itseasiassa minä en
  1965. saanut siitä mitään selvää luettuani sen aikoinani Mikrobitin grafiikka-
  1966. ohjelmointikurssin toisesta osasta, mutta luulisin nyt pystyväni teke-
  1967. mään samanlaisen, ja jos onnistumme pystynen selittämäänkin toimintaperi-
  1968. aatteen.
  1969.  
  1970. int scollision(SPRITE *a, SPRITE *b) {
  1971. /* Lasketaan spritejen yläkulmien väliset etäisyydet. Huomaa, että tässä
  1972. lasketaan mukaan nopeudet, eli palautusarvo 1 kertoo spritejen
  1973. törmäävän ENSI vuorolla. Näin ehditään päällekkäin meneminen estää
  1974. ajoissa. */
  1975. int xdistance= (a->x+a->xspeed) - (b->x+b->xspeed);
  1976. int ydistance= (a->y+a->yspeed) - (b->y+b->yspeed);
  1977. int xx, yy;
  1978. /* Jos x- tai y-etäisyys on suurempi kuin suuremman leveys eivät
  1979. spritet voi mitenkään olla toistensa päällä. */
  1980. if(xdistance>a->xsize && xdistance>b->xsize) return 0;
  1981. if(ydistance>a->ysize && ydistance>b->ysize) return 0;
  1982.  
  1983. for(xx=0; xx< a->xsize; xx++)
  1984. for(yy=0; yy< a->ysize; yy++)
  1985. if(xx+xdistance < b->xsize && xx+xdistance>=0 &&
  1986. yy+ydistance < b->ysize && yy+ydistance>=0)
  1987. if(a->bitmap[ yy * a->xsize + xx ] &&
  1988. b->bitmap[ (yy+ydistance) * b->xsize + (xx+xdistance) ])
  1989. return 1;
  1990. return 0;
  1991. }
  1992.  
  1993. Loopissa ideana on se, että laskuilla saadaan b-spriten vastaava koordinaatti
  1994. selville ja jos se on siis positiivinen ja spriten b rajoissa (pienempi
  1995. kuin leveys tai y-koordinaatin ollessa kyseessä korkeus). Tarkemmin en
  1996. ala selittämään. Jos välttämättä haluat saada selville miten pätkä toimii
  1997. niin piirrä pari tilannetta paperilla ja katso miten niiden kanssa tapah-
  1998. tuu. Nyt meillä onkin käsiteltynä kaikki tärkein spriteistä ja voimme
  1999. mennä viimeiseen pelkästään spritejä käyttävään ohjelmaamme. Tämä ohjelma
  2000. on pienimuotoinen peli, jossa liikutaan edellisen esimerkin palikoilla. Pe-
  2001. laajia on 2 ja tarkoitus on leikkiä hippaa. Eli toinen yrittää pakoon ja
  2002. toinen yrittää ottaa kiinni. Peli loppuu kun pelaajat törmäävät. Kontrol-
  2003. lit ovat pelaajalla 1 wsad ja pelaajalla 2 ujhk. Tämä on vain pieni esi-
  2004. merkki siitä mitä näillä taidoilla voisi tehdä. Lisäksi nappeina on
  2005. + ja - nopeuden säätöön (nyt ei odoteta waitsyncillä) sekä ESC lopetuk-
  2006. seen kesken. Eli SPRITE2.C:
  2007.  
  2008. #include <go32.h>
  2009. #include <sys/movedata.h>
  2010. #include <conio.h>
  2011. #include <stdio.h>
  2012. #include <stdlib.h>
  2013. #include <dos.h>
  2014.  
  2015. char *doublebuffer;
  2016.  
  2017. #include "palette.h"
  2018. #include "pcx.h"
  2019. #include "sprite.h"
  2020.  
  2021. #define flip(c) _dosmemputl(c, 64000/4, 0xA0000)
  2022.  
  2023. int main() {
  2024. char palette[768];
  2025. SPRITE pl1, pl2;
  2026. int quit=0, waittime=0;
  2027.  
  2028. doublebuffer=(char *)malloc(64000);
  2029. if(doublebuffer==NULL) {
  2030. printf("Ei tarpeeksi muistia kaksoipuskurin varaukseen!\n");
  2031. return 1;
  2032. }
  2033. textmode(0x13);
  2034. loadpcx("SPRITE.PCX", doublebuffer);
  2035. loadpal("SPRITE.PCX", palette);
  2036. setpal(palette);
  2037. sload(&pl1, 100, 100, 0, 0, 16, 16, doublebuffer, 1, 1, 320, 8);
  2038. sload(&pl2, 220, 100, 0, 0, 16, 16, doublebuffer, 1, 1, 320, 8);
  2039. loadpcx("BITBACK.PCX", doublebuffer);
  2040.  
  2041. while(!quit) {
  2042. sdraw(&pl1);
  2043. sdraw(&pl2);
  2044. flip(doublebuffer);
  2045. shide(&pl1);
  2046. shide(&pl2);
  2047. smove(&pl2);
  2048. smove(&pl1);
  2049. sanimate(&pl1);
  2050. sanimate(&pl2);
  2051. if((pl1.x+pl1.xsize+pl1.xspeed)>320 ||
  2052. pl1.x+pl1.xspeed<0)
  2053. pl1.xspeed= -pl1.xspeed;
  2054. if((pl1.y+pl1.ysize+pl1.yspeed)>200 ||
  2055. pl1.y+pl1.yspeed<0)
  2056. pl1.yspeed= -pl1.yspeed;
  2057.  
  2058. if((pl2.x+pl2.xsize+pl2.xspeed)>320 ||
  2059. pl2.x+pl2.xspeed<0)
  2060. pl2.xspeed= -pl2.xspeed;
  2061. if((pl2.y+pl2.ysize+pl2.yspeed)>200 ||
  2062. pl2.y+pl2.yspeed<0)
  2063. pl2.yspeed= -pl2.yspeed;
  2064.  
  2065. if(scollision(&pl1, &pl2))
  2066. quit=2; /* 2 tarkoittaa, että toinen saatiin kiinni */
  2067.  
  2068. while(kbhit()) { /* tyhjennetään näppispuskuri */
  2069. switch(getch()) {
  2070. case 'w': pl1.yspeed=-1; pl1.xspeed=0; break;
  2071. case 's': pl1.yspeed=1; pl1.xspeed=0; break;
  2072. case 'a': pl1.xspeed=-1; pl1.yspeed=0; break;
  2073. case 'd': pl1.xspeed=1; pl1.yspeed=0; break;
  2074.  
  2075. case 'u': pl2.yspeed=-1; pl2.xspeed=0; break;
  2076. case 'j': pl2.yspeed=1; pl2.xspeed=0; break;
  2077. case 'h': pl2.xspeed=-1; pl2.yspeed=0; break;
  2078. case 'k': pl2.xspeed=1; pl2.yspeed=0; break;
  2079.  
  2080. case '+': if(waittime) waittime--; break;
  2081. case '-': waittime++; break;
  2082.  
  2083. case 27: quit=1; break;
  2084. }
  2085. }
  2086. delay(waittime);
  2087. }
  2088. if(quit==2) { /* jos kiinni, niin feidataan ensin valkoiseen (räjähdys) */
  2089. fadetowhite(palette);
  2090. for(waittime=0; waittime<256*3; waittime++)
  2091. palette[waittime]=63;
  2092. }
  2093. fadetoblack(palette);
  2094. textmode(0x3);
  2095.  
  2096. return 0;
  2097. }
  2098.  
  2099. Tässä oli sitten sellainen lähdekoodi, jota kukaan vähänkään omanarvontuntoa
  2100. omaava peliohjelmoija, taikka muukaan ohjelmoija EI TEE. Jos pelistä to-
  2101. della halutaan selvä ja helposti laajennettava ei tehdä jokaiselle pelaa-
  2102. jalle eri spriteä eri nimellä, vaan kaikki pelaajaspritet ovat
  2103. taulukossa. Ja muutenkin esimerkkikoodi ainoastaan demonstroi mahdolli-
  2104. suuksia oppimiemme asioiden käyttämiseen, ei suinkaan minkälainen pelin
  2105. runko pitäisi olla. Siihen me palaamme myöhemmin. Mutta meneppäs pelaamaan
  2106. ja näytä kavereillesi minkälaisia pelejä osaisit jo tehdä. =) Äläkä
  2107. palaa takaisin ennenkuin tämän kappaleen asiat ovat hallussa. Sillä niiden
  2108. osaamista luultavasti tullaan vaatimaan seuraavissakin luvuissa. Mutta jos
  2109. olet malttamaton, niin on tietenkin mahdollista palata takaisin opettelemaan,
  2110. mutta turhauttavaa se on.
  2111.  
  2112. Jälkikäteen kaiken sprite, animaatio ja bittikarttanäpräilyn jälkeen totean,
  2113. että kaikissa kohdissahan ei käytetty täsmälleen oikeita termejä. Bittikart-
  2114. tahan on käytännössä vain kuvadata ja mahdollisesti hieman lisätietoa, ani-
  2115. maatio on yleensä peräkkäisiä bittikarttoja osaksi yhteisellä datalla,
  2116. olio on yleensä sitten se mikä osaa pyyhkiä itsensä ja joka tietää mitkä
  2117. bittikartat ja muut vastaavat sille kuuluvat, joka voi pyyhkiä itsensä ja
  2118. tehdä monia muitakin kivoja asioita. Sprite on sitten jotain siellä jossain
  2119. välillä tai päässä, en tiedä kovin tarkasti mutta käytin nyt tätä nimitystä
  2120. täysin toimivasta oliosta joka kykenee itsensä käsittelyyn.
  2121.  
  2122. 4.4 Maskatut spritet
  2123. --------------------
  2124.  
  2125. Vähän aikaa sitten kerroin PC-Ohjelmointi -alueella tämän kurssin sisällöstä
  2126. ja eikös vain joku mennyt kysymään minulta selittikö tutoriaali maskatut
  2127. vai maskaamattomat spritet. Minähän en ollut edes kuullut moisesta asiasta
  2128. ja utelin ideaa sen takana. Sainkin kuulla se ja tein sen pohjalta assemb-
  2129. lerilla nopean rutiinin. Pienellä nopeuskokeella se osoittautui 11 kertaa
  2130. nopeammaksi kuin muutama luku sitten tekemämme rutiini. Aion nyt selittää
  2131. idean tämän tekniikan takana, joten kiinnittäkää turvavyönne ja valmistau-
  2132. tukaa!
  2133.  
  2134. Maskatuiden spritejen ideana on se, että niiden piirrossa ei tarvita pikse-
  2135. likohtaisia vertailulauseita lainkaan, jolloin voidaan käyttää assembleril-
  2136. la neljän tavun kanssa operoivia funktioita. Mutta miten sitten kierrämme
  2137. vertailulausekkeet säilyttäen silti läpinäkyvyyden nollavärin kanssa?
  2138. Idea perustuu bittioperaattoreihin.
  2139.  
  2140. Jokaiselle spriten framelle tehdään etukäteen maski, joka on nolla kohdissa
  2141. joissa on pikseli ja 255 läpinäkyvissä kohdissa. Nyt sitten vain suoritamme
  2142. kaksoispuskurin pikselille loogisen AND-operaation:
  2143.  
  2144. Maski spritelle FF 00 FF FF
  2145. Näyttö 4F 3C 93 5A
  2146. ----------------------------
  2147. Tulos 4F 00 93 5A
  2148.  
  2149. Kuten huomaatte, jäävät läpinäkyvät kohdat (FF) jäljelle. Sitten vain
  2150. käytämme OR-operaattoria sytyttämään spriten pikselit, sillä ne kohdat
  2151. ovat juuri äsken nollautuneet, joten looginen OR asettaa juuri oikeat
  2152. bitit:
  2153.  
  2154. Sprite 00 46 00 00
  2155. Maskattu näyttö 4F 00 93 5A
  2156. ----------------------------
  2157. Tulos 4F 46 93 5A
  2158.  
  2159. Lopun saat toteuttaa aivan itse. Huomattavaa tässä on se, että jos haluat
  2160. käyttää tehokkaita 4 tavun (dword) operaatioita on bittikartan leveyden
  2161. oltava jaollinen neljällä. Huipputehoon tarvitset assembleria, sillä C:llä
  2162. on vaikea kontrolloida edellä mainittuja asioita. Jos et vielä osaa assemb-
  2163. leria, varsinkaan DJGPP:n AT&T syntaksia, suosittelen seuraavia tiedostoja:
  2164.  
  2165. ASSYT.ZIP Assemblerin alkeet suomeksi.
  2166. PCGPE10.ZIP PCGPE sisältää kaiken muun lisäksi assemblytutoriaalin.
  2167. DJTUT2_4.ZIP Jos osaat Intel-syntaksin, muttet AT&T-syntaksia
  2168. (movd %eax, %ebx). Sisältää myös muuta kiinnostavaa
  2169. materiaalia, jota tässäkin tutoriaalissa on sivuttu.
  2170. NASM095B.ZIP Tällä voit tehdä Intel-syntaksin assemblerilla DJGPP:n
  2171. COFF-muotoisia objektitiedostoja. Tiivistettynä TASM joka
  2172. osaa myöskin DJGPP:n objektiformaatin. Huomaa, että uusin
  2173. versio voi olla muutakin kuin 0.95 (NASM095B.ZIP).
  2174.  
  2175. Lisäksi voisi olla hyvä idea lainata kirjastosta kirja 486-ohjelmointi,
  2176. joka on suomenkielinen assembler-ohjelmointia käsittelevä kirja ja kaiken
  2177. lisäksi hyvä sellainen!
  2178.  
  2179. Loppulisäyksenä jälleen kiva vinkki Pekka Nurmiselta. Kaksoispuskuri
  2180. kannattaa tarvittaessa tehdä sen verran leveämmäksi, että jos spitea
  2181. ei saada katki juuri neljän tavun kohdalta ei tuo tule toisesta reunasta
  2182. vastaan. Eli jättää sinne neljä tavua ruudun reunoihin, jota ei vain
  2183. sitten kopioida näytälle. Näin kaksoispuskurin kooksi tulisi 328x200.
  2184.  
  2185.  
  2186. 5.1 Näppäimistön käsittely - ja nyt meillä on hauskaa
  2187. -----------------------------------------------------
  2188.  
  2189. Jos pelasit ahkerasti esimerkkipeliämme, niin ehkä huomasit, että painaessasi
  2190. useita nappia ilmenee myös useita ongelmia. Näihin voivat kuulua näppäimis-
  2191. tön jumiutuminen, nappien huomiotta jättäminen jne. Tarvitsemme siis ru-
  2192. tiinin joka päästäisi meidät pälkähästä. Tarvitsemme näppishandlerin!
  2193. Tämä perustuu siihen, että joka kerta kun nappia painetaan kutsutaan
  2194. keskeytystä 9, joka lukee merkin näppäimistöltä portista 60h (0x60) ja
  2195. muuntaa sen ASCII:ksi ja laittaa näppäimistöpuskuriin. Mutta mepäs ohi-
  2196. tammekin tämän ja teemme oman handlerin, joka ei muutakaan mitään miksi-
  2197. kään ASCII:ksi, vaan laittaa näppäimistötaulukon vastaavan kohdan arvoon
  2198. 1, josta peli voi sitten sen tarkistaa. Ja kun nappi päästetään tulee
  2199. myös keskeytys, tällä kertaa tulee napin arvo + 128, joten vähennämme
  2200. luetusta arvosta 128 ja nollaamme vastaavan kohdan taulukosta. Ja millainen
  2201. on tämä taulukko?
  2202.  
  2203. Taulukossa on 128 alkiota, yksi jokaiselle SCAN KOODILLE, jollaisia näppäi-
  2204. mistö syytää. Olen tehnyt näistä numeroista kirjaston, jossa esimerkiksi
  2205. ESC-näppäimen scan koodi on nimellä SxESC ja sen arvo on 1. Jos siis haluat
  2206. pelissäsi tietää onko ESC painettuna, osoitat näppäimistöpuskuriin:
  2207.  
  2208. if(keybuffer[SxESC]==1) printf("ESC painettu!\n");
  2209.  
  2210. Kirjasto on nimellä D_SCAN.H. Ja sitten tarvitsemme siis koodia, joka lukee
  2211. tavun portista 60h ja jos se on alle 128 se laittaa vastaavan kohdan
  2212. taulukosta ykköseksi ja jos se on yli tai yhtäsuuri kuin 128, niin laitamme
  2213. alkion tavu-128 nollaksi. Lopuksi lähetämme signaalin PIC:ille, että kes-
  2214. keytyksemme on valmis, eli outtaamme tavun 20h porttiin 20h. Tällainen on
  2215. siis handlerimme (KEYBOARD.H):
  2216.  
  2217. void keyhandler() {
  2218. register unsigned char tavu=inportb(0x60);
  2219.  
  2220. if(tavu<128) keybuffer[tavu]=1;
  2221. else keybuffer[tavu-128]=0;
  2222.  
  2223. outportb(0x20, 0x20);
  2224. }
  2225.  
  2226. Tämä onkin oikeastaan helpoin osa tehtäväämme. Vaikeampi (joskin esimerkki-
  2227. koodin takia helppo) on koukuttaa tarvitsemamme näppäimistökeskeytys ja
  2228. palauttaa se kun tarvitaan näppäimistörutiineja (gets, getch...) tai pois-
  2229. tutaan ohjelmasta. Lisäksi tarvitsemme joukon apumuuttujia, jotka ovat
  2230. tässä:
  2231.  
  2232. volatile unsigned char keybuffer[128], installed;
  2233. _go32_dpmi_seginfo info, original;
  2234.  
  2235. Keybuffer säilöö näppäinten tilat, installed kertoo onko tämä handleri a-
  2236. sennettuna ja estää samalla uudelleenasentamisen. Kaksi viimeistä muuttujaa
  2237. info ja original ovat koukuttamiseen ja koukutuksen (hooking) poistamiseen
  2238. tarvittavia rakenteita, joista infoa käytetään oman asentamiseen ja origi-
  2239. naliin säilötään alkup. handlerin osoite ja muut tarpeelliset tiedot.
  2240.  
  2241. Tässä on koukutukseen ja palautukseen tarvittava koodi, johon emme perehdy
  2242. kovinkaan tarkasti, lisäinfoa asiasta saat vaikka DJGPP:n FAQ:sta hakusanalla
  2243. handler:
  2244.  
  2245. int setkeyhandler() {
  2246. int c;
  2247.  
  2248. for(c=0; c<0x80; c++)
  2249. keybuffer[c]=0; /* nollataan napit */
  2250. if(!installed) {
  2251. _go32_dpmi_get_protected_mode_interrupt_vector(0x0009, &original);
  2252. info.pm_offset=(unsigned long int)keyhandler;
  2253. info.pm_selector=_my_cs();
  2254. _go32_dpmi_allocate_iret_wrapper(&info);
  2255. _go32_dpmi_set_protected_mode_interrupt_vector(0x0009, &info);
  2256. installed=1;
  2257. return 1;
  2258. } else return 0;
  2259. }
  2260.  
  2261. int resetkeyhandler() {
  2262. if(installed) {
  2263. _go32_dpmi_set_protected_mode_interrupt_vector(0x0009, &original);
  2264. installed=0;
  2265. return 1;
  2266. } else return 0;
  2267. }
  2268.  
  2269. Lisäämme kaikki kolme funktiota ja globaalit muuttujamme tiedostoon
  2270. KEYBOARD.H. Nyt meillä on tarpeen vaatiessa täydellisen toimiva näppäimis-
  2271. töhandleri (jota ehkä myöhemmin tulemme käyttämään).
  2272.  
  2273.  
  2274. 5.2 Fixed point matematiikka
  2275. ----------------------------
  2276.  
  2277. Alamme pikkuhiljaa lähestyä kurssimme loppua (tai ken tietää, todellista
  2278. alkua?), joten käsittelen tässä hieman pelin optimointiin vaikuttavia
  2279. tekijöitä ja parannuksia aiemmin esittelemiimme kirjastoihin (omaan peliin
  2280. kun kannattaa kuitenkin tehdä osa kirjastoista uusiksi). Selitän fixed-
  2281. pointin, lookupin idean ja pari muuta nopeuttavaa temppua sekä mainitsen
  2282. pullonkauloja joita nopeuttamalla saadaan aikaan dramaattisia muutoksia.
  2283.  
  2284. Siis fixed point, mitä se on? Kuten tiedät, C:n int-tyyppi on kokonaisluku,
  2285. eli sillä ei voi ilmoittaa desimaalilukuja. Monesti desimaaliluvu olisivat
  2286. tarpeellisia, esimerkiksi sprite-enginessä, jos halutaan että eri spritet
  2287. liikkuvat eri nopeuksilla. Näyttää nimittäin todella typerältä jos ohjus
  2288. pomppii kymmenen pikseliä eteenpäin, koska se on 10 kertaa nopeampi kuin
  2289. pelin hitain sprite. Tarvitsemme siis nopeudeksi desimaaliluvun, jolloin
  2290. ohjuksen nopeus voisi olla 1 ja kilpikonnan 0.1 (jolloin se liikkuisi yhden
  2291. pikselin joka 10. frame). Valitettavasti float-tyyppisten muuttujien kä-
  2292. sittely on moninkertaisesti hitaampaa (tosin pentium-optimoitu peli voi
  2293. niitä käyttää, ainakin assemblerilla voidaan pentiumin matematiikkapro-
  2294. sessoria käyttää täysipainoisesti ja peliä nopeuttaa). Niinpä meidän täy-
  2295. tyisi pystyä esittämään kokonaisluvuilla desimaalilukuja. Onko tämä mahdol-
  2296. listakaan?
  2297.  
  2298. Kyllä se on, katsokaamme hieman toisella tavalla normaaleja lukujamme.
  2299.  
  2300. Meidän luvuissamme on kokonaislukuosa ja desimaaliosa sekä välissä piste.
  2301. Kokonaislukuosalla voidaan ilmaista 10^<numeroja> lukua, eli jos
  2302. kokonaislukuosassa on 3 numeroa niin voimme ilmaista sillä 10^3=1000
  2303. erilaista lukua, välillä 0-999. Pisteen toisella puolella on kaikki muuten
  2304. samalla tavalla, mutta meidän täytyy ajatella käänteisesti. Voimme ilmaista
  2305. desimaaliosalla desimaalin, joka on yksi 10^<numeroja>:sosa. Tämä näyttää
  2306. sekavalta, mutta oletetaan että meillä on 2-numeroinen desimaaliosa, niin
  2307. pienin desimaali on 1/10^2, eli yksi SADASOSA. Seuraava kaavio varmaan sel-
  2308. ventää asiaa:
  2309.  
  2310. 1234.123 = 1234 + 123/10^3 = 1234 + 123/1000 = 1234.123
  2311.  
  2312. Nyt menemme vähän pidemmälle. Oletetaan, että meillä olisi luvussa pilkku
  2313. AINA samalla kohdalla ja desimaalia esittäviä lukuja 3. Takaisin voisimme
  2314. sen palauttaa vain jakamalla kokonaisluku tuhannella (kolme desimaalinumeroa,
  2315. eli siis 10^3=1000):
  2316.  
  2317. 1234123 = 1234123/1000 = 1234.123
  2318.  
  2319. Kuten huomaat pilkku voidaan ajatella sinne nelosen ja ykkösen väliin.
  2320. Nyt kysyt ehkä että mitä hyötyä tästä on. Siitä on seuraava hyöty: Meillä
  2321. on kaksi lukua, 0.1 ja 5.4, jotka haluamme laskea yhteen. Muunnetaanpa ne
  2322. oikeaan muotoon: 0.1*1000=100 ja 5.4*1000=5400. Haluamme laskea ne yhteen:
  2323. 100+5400 = 5500. Nyt muuntakaamme takaisin:
  2324.  
  2325. 5500/1000 = 5.5 = 5.5 (5.4 + 0.1 = 5.5).
  2326.  
  2327. Eli meillä on sama tulos! Vähennyslasku toimii ihan yhtä hyvin. Voimme las-
  2328. kea desimaalilukuja kokonaisluvuilla. Mutta tarvitsemme vielä kaksi laskua,
  2329. kerto- ja jakolaskun. Koska lukumme ovat kummatkin 1000-kertaisia todelli-
  2330. suuteen nähden niin ne kertomalla saamme 1000000-kertaisen tuloksen, joten
  2331. lopuksi meidän täytyy jakaa tulos tuhannella. Eli:
  2332.  
  2333. 5400*100 = 540000 => 540000/1000 = 540 => 540/1000 = 0.54
  2334. (5.4 * 0.1 = 0.54)
  2335.  
  2336. Ja tadaa! Meillä onkin oikea tulos. Vielä jakolasku, siinähän jaamme vain
  2337. numerot toisillamme, mutta tässä häviää meiltä desimaaliosa, eli meidän pi-
  2338. täisi kertoa tulos lopuksi tuhannella. Tarkemman tuloksen saamme kun
  2339. kerromme ensin jaettavan tuhannella ja sitten vasta jaamme:
  2340.  
  2341. (5400*1000) / 100 = 54000 => 54000/1000 = 54 (5.4 / 0.1 = 54).
  2342.  
  2343. Nyt meidän täytyy sitten syventyä siihen miten toteutamme nopeasti edelliset
  2344. asiat tietokoneen binäärijärjestelmällä. Se on erittäin helppoa. Teemme
  2345. vaikka 32-bittisen luonnollisen (unsigned int), josta 16 alinta bittiä on
  2346. varattu desimaaliosalle. Koska binäärijärjestelmä on 2-kantainen, niin
  2347. meidän täytyy vain muuttaa pikku laskumme kahden potensseilla leikkimisiksi.
  2348. Tällaisella luvulla voimme siis esittää 16-bittisen kokonaislukuosan,
  2349. maksimissaan 2^16=65536 ja 16-bittisen desimaaliosan, joten pienin desimaali
  2350. n 1/2^16 = 1/65536 = n. 0.000015228.
  2351.  
  2352. Entiset laskumme toimivat ihan hyvin, muunnamme vain luvut kertomalla ne
  2353. 65536:llä ja palautamme jakamalla 65536:llä. Nopeuttamisessa apuna ovat
  2354. vielä bittisiirrot, joiden avulla voimme kertoa nopeasti 65536:lla
  2355. siirtämällä bittejä 16 vasemmalle ja jakaa siirtämällä niitä oikealle.
  2356. Tässä on pieni esimerkkiohjelma, joka demonstroi fixedin käyttöä:
  2357.  
  2358. #include <stdio.h>
  2359.  
  2360. int main() {
  2361. unsigned int a, b, tulos;
  2362.  
  2363. a=(unsigned int)(5.4 * 65536.0);
  2364. b=(unsigned int)(0.1 * 65536.0);
  2365.  
  2366. tulos=a+b;
  2367. printf("A+B=%f\n", tulos/65536.0);
  2368.  
  2369. tulos=a-b;
  2370. printf("A-B=%f\n", tulos/65536.0);
  2371.  
  2372. tulos=(a*b)/65536;
  2373. printf("A*B=%f\n", tulos/65536.0);
  2374.  
  2375. tulos=(a/b)*65536;
  2376. printf("A/B=%f\n", tulos/65536.0);
  2377.  
  2378. return 0;
  2379. }
  2380.  
  2381. Mieti nyt kaikkea ihan rauhassa. Jos luulet ymmärtäneesi edes jotain niin
  2382. hyvä, jos et ymmärtänyt mitään niin lue uudelleen ja uudelleen ja kokeile
  2383. paperilla. Jos et siltikään ymmärtänyt niin lue jostain toisesta dokumentis-
  2384. ta! Fixed-pointissa on huomattava pari asiaa:
  2385.  
  2386. 1) Luvut voivat mennä yli ja tulee ihmeellisiä tuloksia. Jakolaskuesimerkis-
  2387. säni en voinut kertoa a:ta ensin 65536:lla, sillä muuten olisi luku men-
  2388. nyt ympäri. Kannattaa aina varmistaa ettei luku voi mennä ympäri.
  2389.  
  2390. 2) Käytä bittioperaatioita aina kuin mahdollista. 32-bittisestä
  2391. 16.16-fixedistä (tarkoittaa, 16 bittiä kokonais- ja 16 bittiä desimaali-
  2392. osalle) saat desimaaliosan halutessasi AND-funktiolla maskin 0xFFFF
  2393. kanssa. Voit käyttää kaikkia nerokkaita optimointikikkoja jos vain kek-
  2394. sit niitä. Myös pyörähdystä voi käyttää hyväksi (jotenkin).
  2395.  
  2396. 3) Signed luvut toimivat samoin, mutta ylin bitti merkkaakin etumerkkiä,
  2397. eli 16.16-luku int-tyyppinä onkin oikeasti 15.16.
  2398.  
  2399. 4) Valitse itse pilkun paikka. Mitä enemmän bittejä desimaaleille sitä tar-
  2400. kempia lukuja. Mitä enemmän bittejä kokonaisluvuille sitä suurempia ja
  2401. epätarkempia lukuja.
  2402.  
  2403.  
  2404. 5.3 Lookup-tablet ja muita optimointivinkkejä
  2405. ---------------------------------------------
  2406.  
  2407. Lookup-tableissa, eli lookupeissa ei ole oikeastaan muuta selittämistä, kuin
  2408. että niissä toistuvia, vain yhtä (tai joskus kahtakin) muuttujaa käyttävis-
  2409. sä monimutkaisissa laskutoimituksissa (tai muuten vain hidastavissa)
  2410. lasketaan tulokset etukäteen taulukkoon käyttäen indeksinä sitä lukua joka
  2411. oli muuttuvana laskutoimituksessa. Tähän käy esimerkkinä sinin laskeminen
  2412. taulukkoon. Sin-funktio on hidas laskea ja siinä pitää aina suorittaa pitkä
  2413. konversio asteista radiaaneiksi (3.14*2*aste/256, 256:n ollessa suurin
  2414. kulma + 1, 360-asteisella ympyrällä luku olisi 360 ja suurin kulma 359) ja
  2415. lopuksi vielä ottaa siitä sini. Nyt laskemmekin kaikki 256 arvoa taulukkoon
  2416. (fixed-point-sellaiseen, muoto 1.14, 16-bittinen signed, muuntoluku 16384):
  2417.  
  2418. for(c=0; c<256; c++)
  2419. sin_table[c] = (short)(sin(3.141592654*2*c/256.0)*16384);
  2420.  
  2421. Nyt jos haluamme kulman 15 sinin, niin osoitamme vain sin_table[15], emmekä
  2422. (short)(sin(3.141592654*2* 15 /256.0)*16384).
  2423.  
  2424. Sitten sekalaisia optimointivinkkejä:
  2425.  
  2426. 1) Suuria määriä dataa käsittelevät loopit assemblerilla. Lisää tietoa
  2427. inline-assemblerin käytöstä DJGPP:llä tiedostosta DJTUT*.ZIP,
  2428. vaikka MBnetistä, tai tämän tutoriaalin Nasmia käsittelevästä
  2429. luvusta.
  2430.  
  2431. 2) Kaikki muuttumattomat vertailulausekkeet loopin ulkopuolelle:
  2432. for(c=0; c<1000000; c++) if(a==b) puskuri[c]=0; onkin:
  2433. if(a==b) for(c=0; c<1000000; c++) puskuri[c]=0;
  2434. Vähennämme näin 1000000 vertailua.
  2435.  
  2436. 3) Älä tuhlaa aikaasi optimoimalla suuria määriä logiikkaa, ellei siitä
  2437. todella ole hyötyä. Esimerkkinä vaikka kaksoispuskurin tyhjennyksen
  2438. tekeminen inlinenä memsetin sijaan säästää kyllä aikaa, mutta kun
  2439. ajansäästö funktiokutsun jäämisessä pois on jotain 1/10000 siitä
  2440. mitä aikaa memsetissä menee joka tapauksessa, on hyödyttömyys
  2441. varsin ilmeistä.
  2442.  
  2443. 4) Käytä fixediä floatin tilalla aina kuin mahdollista.
  2444.  
  2445. 5) Laske kaikki toistuva konemainen laskenta taulukkoihin.
  2446.  
  2447. 6) Käytä DJGPP:n käännösvalitsinta -O2, tai jopa -O3 (joka kyllä suurentaa
  2448. ohjelmaasi reilusti).
  2449.  
  2450. Yleensäkin kannattaa uhrata paljon aikaa grafiikkakirjastojen ja äänikirjas-
  2451. tojen optimointiin ja pitää itse runko selkeänä C-kielisenä kutsujen joukko-
  2452. na. Tämä ei paljoa hidasta ja selventää uskomattomasti koodia ja nopeuttaa
  2453. kehitystä.
  2454.  
  2455.  
  2456. 5.4 Väliaikatulokset ja fontteja
  2457. --------------------------------
  2458.  
  2459. Tässä vaiheessa osaat nyt kaikki tärkeimmät niksit mitä peliohjelmointiin
  2460. tarvitaan. Tästä luvusta lähtien alan tietoisesti vähentämään, ellen
  2461. jopa joissain kohdissa poistamaan esimerkkiohjelmia. Mitä tästä lähtien
  2462. tarvitset on maalaisjärkeä ja kykyä osata soveltaa oppimiasi asioita.
  2463.  
  2464. Eli tänään meillä on siis jotain, mitä kutsutaan nimellä fontit? Idea fon-
  2465. tienginen teossa on tehdä tavallaan karsittu bittikarttaengine. Fontti-
  2466. enginen voit tehdä esimerkiksi poistamalla sprite-koodistamme pyyhkimisen
  2467. (halutessasi voit myös poistaa läpinäkyvyyden tai jättää pyyhkimisen jos
  2468. tarvitset sitä, sinun pitää siinä tapauksessa vain tehdä erikoisjärjeste-
  2469. lyjä) ja käyttää animaationa kuvasarjaa jossa on piirrettynä merkit a-z,
  2470. A-Z, 0-9 ja sitten joitakin mahdollisesti tarvittavia välimerkkejä, kuten
  2471. .!?,;:'" ja muut vastaavat. Sitten vain teet funktion, joka vaihtaa framek-
  2472. si oikean kuvan ja piirtää sen, jonka jälkeen se korottaa x-arvoa merkin
  2473. leveydellä (plus jonkin verran väliä seuraavan merkin ja viimeisen välille)
  2474. ja ottaa käsittelyyn seuraavan merkkijonon merkin.
  2475.  
  2476. Koodi voisi näyttää vaikka tältä:
  2477.  
  2478. void printString(char *string, int x, int y) {
  2479. int c;
  2480.  
  2481. for(c=0; c<strlen(string); c++ {
  2482. if(string[c]>'a' && string[c]<'z') {
  2483. setframe(string[c]-'a'); /* a olisi frame 0 */
  2484. drawchar(x+c*9, y); /* merkin leveys 8 + 1 pikseli erottamaan */
  2485. } else if(string[c]>'A' && string[c]<'Z') {
  2486. setframe(string[c]-'A' + 'z'-'a' + 1);
  2487. /* eli suomeksi A-kirjaimella olisi paikka heti viimeisen pienen
  2488. kirjaimen jälkeen, joka on 'z'-'a' */
  2489. drawchar(x+c*9, y);
  2490. } else if(string[c]>'0' && string[c]<'9') {
  2491. setframe(string[c]-'0' + 'z'-'a' + 1 + 'Z'-'A' + 1);
  2492. /* tämä taas tulee pienien JA isojen kirjaimien jälkeen */
  2493. drawchar(x+c*9, y);
  2494. } else if(c == '.') { /* jos c on erikoismerkki */
  2495. setframe('9'-'0' + 1 + 'z'-'a' + 1 + 'Z'-'A' + 1);
  2496. drawchar(x+c*9, y);
  2497. /* ideana siis, että piste tulee kaikkien kirjainten ja
  2498. numeroiden jälkeen */
  2499. }
  2500. ...
  2501.  
  2502. }
  2503. }
  2504.  
  2505. Kuten ehkä huomasit tuli koodista aivan kammottavaa sekasotkua ja on ihme
  2506. jos sait siitä jotain selvää. Lisäksi koodi ei ole erityisen nopeaakaan,
  2507. saati sitten että se edes välttämättä toimii. Mutta miten voisimme nopeuttaa
  2508. tätä? Vastaus on lookup-tablet. Sillä mehän tiedämme, että C:llä kirjain on
  2509. vain numero välillä 0-255. Niinpä teemme taulukon jonka jokainen alkio
  2510. osoittaa indeksin mukaisen ASCII-kirjaimen framenumeroon. Jos et ymmärtänyt
  2511. niin tässä on esimerkki taulukon käytöstä:
  2512.  
  2513. frame = asciitaulukko['a'];
  2514.  
  2515. Asciitaulukon alkio 'a' (numerona 97) olisi 0, joten framenumeroksi tulisi
  2516. näinollen tämä luku. Sitten vain framenvaihto: "setframe(frame)".
  2517. Tietenkin tuo kannattaisi käyttää näin: "setframe(asciitaulukko['a'])"...
  2518.  
  2519. Mutta miten sitten taulukko alustetaan? Tapoja on monia, jotkin ovat seka-
  2520. vampia ja jotkin vähän selvempiä, mutta annan sinun itsesi päättää mikä on
  2521. paras. Mahdollisuutena olisi ensin täyttää taulukko nollalla (joka olisi
  2522. tyhjä frame) ja sitten loopata aakkoset a-z täyttäen taulukon kohdat 'a'-'z'
  2523. oikeilla framearvoilla (1...26), sitten loopataan 'A'-'Z' täyttäen ne alkiol-
  2524. la 27...52 jne. Myös lataaminen kannattaa automatisoida.
  2525.  
  2526. Muista lisäksi huomioonottaa erikoismerkit enginessäsi. Tarpeellisia voivat
  2527. olla välilyönti (32), rivinvaihto (\n), tabulaattori (\t) jne. Ja lisäksi
  2528. saat aivan vapaasti päättää onko fontin väri mahdollista vaihtaa vai käytät-
  2529. kö aina samanlaisia fontteja, joka mahdollistaa vähän hienommat, vaikka moni-
  2530. väriset fontit.
  2531.  
  2532.  
  2533. 5.5 Hiirulainen, jokanörtin oma lemmikki
  2534. ----------------------------------------
  2535.  
  2536. Tänään, tytöt ja pojat, setä puhuu hieman kotieläimistä. Ne ovat sellaisia
  2537. pieniä valkoisia ötököitä, joilla on häntä ja jotka viipottavat matolla.
  2538. Sen lisäksi niitä voi myös painella. Ei, nyt ei ole kyse mistään karvaisesta,
  2539. vaan ihan aidosta tietokoneen lisälaitteesta, jota hiireksikin kutsutaan.
  2540.  
  2541. Tällä karvattomalla ystävällämme on säädyttömän monia haaroja sukupuussaan.
  2542. Löytyy Logitechia, Microsoftia, Targaa ja ties mitä vimputinta ja kaiken
  2543. kukkuraksi rautatasolla käskyttäminenkin on suorastaan säädyttömän
  2544. epästandardia. Onneksi hätiin rientää kymmenisen vuotta vanha apu nimel-
  2545. tään _hiirikeskeytys_, kiinnostavemmin ilmaistuna keskeytys 33h. Tätä
  2546. keskeytystä käyttäen saadaan kaikkien hiireen tungettujen vimpainten, kuten
  2547. nappien ja pohjassa (yleensä) pyörivän pallukan tila. Nämä tiedot ovat helpon
  2548. saatavuuden lisäksi myös naurettavan helppokäyttöisiä, kunhan vain tietää
  2549. miten niitä käyttää.
  2550.  
  2551. Jos et vielä tiedä miten keskeytyksiä käytetään tulee tässä tiivistettynä
  2552. niiden käyttö DJGPP:llä. Keskeytykselle annetaan parametrit rekistereissä
  2553. ja ne saadaan rekistereissä. Jos DJGPP oli yhtä huoleton kuin Borland
  2554. Turbo-kääntäjineen olisi meilläkin rekisteri ax nimellä _AX jne. Mutta koska
  2555. kaikki on tehty rakkaalla kääntäjällämme hipun vaikeammaksi teemme sen
  2556. standardilla tavalla. Alhaalla näet tarvittavat askeleen keskeytyksen kut-
  2557. sumiseksi ja rekisterien näpläykseksi. Esimerkki käyttää yhtä kymmenistä kes-
  2558. keytyksen aiheuttavista funktiosta int86(...) kirjastosta dos.h:
  2559.  
  2560. 1) Tarvitset rekisterit muuttujinaan sisältävän unionin, int86:n tapauksessa
  2561. unioni on nimeltään REGS ja sen sisällä on pari structia joihin
  2562. tutustut vaikka selaamalla ko. kirjastoa. En ala perehtymään syvemmin
  2563. näihin x, d ja w-rakenteisiin. Tässä kuitenkin käytämme viimeistä, joka
  2564. on 16-bittiset rekisterit.
  2565.  
  2566. union REGS rekisterit;
  2567.  
  2568. 2) Tunge kaikki parametrit uuteen muuttujaasi.
  2569.  
  2570. rekisterit.w.ax=jotain;
  2571. rekisterit.w.di=muuta;
  2572. rekisterit.w.cs=kivaa;
  2573.  
  2574. 3) Kutsu funktiota int86(vektori, inputti rekisterit, outputti rekisterit)
  2575.  
  2576. int86( keskeytys, &rekisterit, &rekisterit );
  2577.  
  2578. 4) Kaivele esiin muuttuneet rekisterisi ja tallenna ne muuttujiin.
  2579.  
  2580. ihan=rekisterit.w.bx;
  2581. helppo=rekisterit.w.ds;
  2582. homma=rekisterit.w.cx;
  2583.  
  2584. Tehdessäsi hiiriohjattua ohjelmaa sinun pitää tietysti hiiren koordinaattien
  2585. ja nappien käsittelyn lisäksi piirtää kursori ruudulle, ellet sitten halua
  2586. käyttää (amatöörimäisen näköistä) kursoria, jonka ajuri piirtelee ruudullesi.
  2587. Grafiikkatilassa tämä onnistuu vaikka tekemällä hiirestä yksi spriteistä ja
  2588. liikuttelemalla sitä. Antaa paljon paremman kuvan ohjelman tekijästäkin!
  2589. Tekstitilassa vaihdat vaikka ko. kohdan väriä. Tähän ihmeelliseen tilaan
  2590. tutustumme kohtapuolin, eli jatka lukemistasi jos haluat tehdä tekstitila-
  2591. ohjelman, joka käyttää kursoria...
  2592.  
  2593. Tässä nyt olisivat nämä kaikkein käytännöllisimmät ja alkuun auttavat funk-
  2594. tiot. Lisää löydät vaikkapas Ralph Brownin interruptilistasta tai kenties
  2595. jopa HelpPC:stä. RB:n lista on MBnetissä nimellä INTERxxy.ZIP, jossa xx on
  2596. versionumero (kai 48 tarkoittaen 4.8:aa) ja y paketin numero, itse listassa
  2597. A-E tjsp. ja muitakin kirjaimia on sisältäen muunmuassa selailuohjelman,
  2598. konvertoinnin Windowsin help-muotoon jne.. Mutta, kuten lupasin:
  2599.  
  2600. Funktio 0 - Hiiren alustus
  2601. Parametrit: AX=0
  2602. Palauttaa: AX=0 jos ajuria ei ole installoitu, FFFFh jos on installoitu.
  2603.  
  2604. Funktio 1 - Näytä kursori (se kauhea siis)
  2605. Parametrit: AX=1
  2606. Palauttaa: -
  2607.  
  2608. Funktio 2 - Piilota kursori (se kauhea siis)
  2609. Parametrit: AX=2
  2610. Palauttaa: -
  2611.  
  2612. Funktio 3 - Anna koordinaatit ja nappien tila
  2613. Parametrit: AX=3
  2614. Palauttaa: CX=x-koordinaatti (0...639)
  2615. DX=y-koordinaatti (0...199)
  2616. BX=nappien tila (bitti 0 vasen nappi, bitti 1 oikea ja
  2617. bitti 2 keskimmäinen nappi)
  2618.  
  2619. Funktio 4 - Aseta kursorin koordinaatit
  2620. Parametrit: AX=4, CX=x-koordinaatti, DX=y-koordinaatti
  2621. Palauttaa: -
  2622.  
  2623. Funktio 5 - Nappien painallukset
  2624. Parametrit: AX=5,
  2625. BX=mikä nappi (0 vasen, 1 oikea ja 2 keskimmäinen)
  2626. Palauttaa: Muuten kuten funktio 3, mutta koordinaatit kertovat kursorin
  2627. sijainnin viime painalluksella ja BX kertoo ko. napin painal-
  2628. luksien määrän sitten viime kutsun.
  2629.  
  2630. Funktio 6 - Nappien vapautukset
  2631. Parametrit: AX=6,
  2632. BX=mikä nappi (0 vasen, 1 oikea ja 2 keskimmäinen)
  2633. Palauttaa: Muuten kuten funktio 5, mutta vapautuksen tiedot.
  2634.  
  2635. Funktio 7 - Vaakarajoitukset
  2636. Parametrit: AX=7,
  2637. CX=pienin sallittu X-sijainti,
  2638. DX=suurin sallittu X-sijainti
  2639. Palauttaa: -
  2640.  
  2641. Funktio 8 - Pystyrajoitukset
  2642. Parametrit: AX=8,
  2643. CX=pienin sallittu Y-sijainti,
  2644. DX=suurin sallittu Y-sijainti
  2645. Palauttaa: -
  2646.  
  2647. Funktio B - Liikemäärä
  2648. Parametrit: AX=B
  2649. Palauttaa: CX=vaakamikkien määrä
  2650. DX=pystymikkien määrä
  2651.  
  2652. Funktio F - Mikkejä pikseliä kohden
  2653. Parametrit: AX=F
  2654. CX=vaakamikkien määrä
  2655. DX=pystymikkien määrä
  2656. Palauttaa: -
  2657.  
  2658. Lisäksi on vielä ainakin funktio C, joka asettaa oman käsittelijän, mutta
  2659. koska se ei luultavasti kiinnosta kovin monta (rm-osoitetta odottava käsit-
  2660. telijä ei ehkä oikein toimi PM:ssä kunnolla jne...) jätän sen tässä väliin.
  2661. Sitten vain tekemään kaiken maailman testiohjelmia. Esimerkkejä ei tule
  2662. tässä lainkaan, sillä oletan jokaisen pystyvän edellisten ohjeiden perusteel-
  2663. la kyhäämään itseään tyydyttävän ohjelman.
  2664.  
  2665. Jos homma ei kuitenkaan ota luonnistuakseen tai tässä kappaleessa oli muita
  2666. epäselvyyksiä niin otahan yhteyttä niin kaivelen lisää tietoa aiheesta.
  2667. Erityiskiitos tämän kappaleen teon auttamisesta kuuluu nyt kyllä MB:n numerol-
  2668. le 4/96 josta katsoin nopeasti tiivistelmän hiirifunktioista.
  2669.  
  2670. Ja ensi kappaleessa onkin uudet kujeet, näyttäisi olevan tekstitilan hallinta
  2671. seuraavana edessä...
  2672.  
  2673.  
  2674. 5.6 Tekstitilan käsittely suoraan
  2675. ---------------------------------
  2676.  
  2677. Tästä kappaleesta tulee tulemaan äärimmäisen lyhyt. Ainoa meitä kiinnostava
  2678. seikkahan on tekstimuistin osoite (tila 3, 80x25, myös muut voivat toimia)
  2679. ja rakenne. Osoite on perusmuistin segmentti B800h, eli lineearinen osoite
  2680. selektorin _dos_ds osoittamassa muistissa olisi C:llä 0xB8000. Rakenne
  2681. on myös naurettavan yksinkertainen. Erona VGA:han (ks. kappale
  2682. "Grafiikkaa - mitä se on?" jos et muista) on vain se, että yksi alkio
  2683. koostuu kahdesta tavusta (joista ensimmäinen on merkin ASCII ja toinen
  2684. merkin väri) ja ruudun leveys on 80 merkkiä. Jos ei mennyt päähän niin
  2685. tutustu vielä kerran VGA:ta käsittelevään kappaleeseen ja tutkaile seuraavia
  2686. makroja:
  2687.  
  2688. #define putchar(x, y, c) _farpokeb(_dos_ds, 0xB8000+(y*80+x)*2, c);
  2689. #define putcolor(x, y, c) _farpokeb(_dos_ds, 0xB8000+(y*80+x)*2+1, c);
  2690.  
  2691. Vielä jos olit kiinnostunut hiiren kursorin tekemisestä tekstitilaan voisi
  2692. seuraava funktio olla sinulle omiaan:
  2693.  
  2694. void inline addcolor(int x, int y, char c) {
  2695. int originalc=_farpeekb(_dos_ds, 0xB8000+(y*80+x)*2+1);
  2696. putcolor(x, y, originalc+c);
  2697. }
  2698.  
  2699. Sitten vain "piirrät" kursorin lisäämällä väriarvoon - sanotaan vaikka 17
  2700. ja pyyhit kursorin lisäämällä siihen saman arvon vastaluvun (-17), eli
  2701. toisinsanoen vähennät siitä 17:
  2702.  
  2703. #define CShow(x, y, c) addcolor(x, y, c)
  2704. #define CHide(x, y, c) addcolor(x, y, -c)
  2705.  
  2706. Makrojen käyttö sitten komennoilla "CShow(17)" ja "CHide(17)"...
  2707.  
  2708. Lopuksi vielä sananen merkin värin muodosta. Se on XYYYZZZZ, jossa jokainen
  2709. kirjain edustaa yhtä bittiä väritavussa. X ilmaisee vilkkuuko merkki (1).
  2710. YYY ilmaisee taustan värin (0-7) ja ZZZZ ilmaisee tekstin värin (0-15).
  2711. Tässä vielä pikkuruinen makro, joka voi osoittautua hyödylliseksi:
  2712.  
  2713. #define BuildC(blink, fore, back) ( (blink<<7) + (back<<4) + (fore) )
  2714.  
  2715. Sitten vain vaikka komento "putcolor(x, y, BuildC(0,15,1))", joka aiheuttaisi
  2716. välkkymättömän valkoisen tekstin sinisellä pohjalla (31).
  2717.  
  2718. Sellaista tällä kertaa. Nyt painun suihkuun ja katsomaan X-Filesia. Jatketaan
  2719. taas vaikka huomenna!
  2720.  
  2721.  
  2722. 6.1 Projektien hallinta - useat tiedostot
  2723. -----------------------------------------
  2724.  
  2725. Nyt seuraakin sitten jakso lukuja (tai yksi luku, katsotaan nyt),
  2726. joissa käsitellään kaikkea tärkeää mitä pelejä ohjelmoidessa pitää
  2727. osata sen hardwaren tuntemuksen lisäksi. Tarkoituksena on käydä läpi
  2728. useiden c-tiedostojen käyttö, headerien teko, Rhiden projektit,
  2729. makefileet, ulkoisen assyn ja assyn yleensäkin käyttö, engineiden
  2730. teko, kirjastojen luonti. Kaikki suhteellisen kevyttä kamaa kun ne
  2731. vain kerran opettelee, joten aloitamme.
  2732.  
  2733. Tähän asti olen opettanut teille huonoja tapoja joita itselläni oli
  2734. tapana käyttää vielä puolitoista vuotta sitten (ja vasta viime aikoina
  2735. olen päässyt lopullesesti niistä eroon). Olen nimittäin laittanut
  2736. koodia noihin .h-tiedoistoihin ja tehnyt niistä kirjastoja, joiden
  2737. rutiineja on sitten helppo käyttää. Laajempien projektien ja miksei
  2738. hieman suppeampienkin kanssa alkaa kuitenkin ennenpitkää esiintyä
  2739. suorastaan ärsyttävän hidasta kääntämistä. Ajattele seuraavaa
  2740. tapausta:
  2741.  
  2742. Peliprojektissa on ääniengine sound.h (yksinkertainen, vain vähän alle
  2743. 3000 riviä), sprite-engine sprite.hh (minimaalinen toiminta, hieman
  2744. inline-assyä, 800 riviä), sekalaisia hardware-rutiineja
  2745. (kellokeskeytys, näppishandleri jne. 1000 riviä) sekä itse pelin
  2746. koodia 2000 riviä. Näin joka kerta käännämme vähän alle 7000 riviä
  2747. C-koodia. Mutta miksi kääntää kaikki joka kerta kun vain yksi muuttuu
  2748. yleensä kerrallaan? Muuttakaamme hieman lähestymistapaa löytääksemme
  2749. parempi keino.
  2750.  
  2751. Keinoa kutsutaan projekteiksi, usean C-tiedoston käytöksi ja ties
  2752. miksi. Ideana on, että jokainen looginen kokonaisuus on jaettu omaan
  2753. .c-tiedostoonsa ja .h-tiedostoonsa. Tällaisia voisivat olla
  2754. näppishandleri, timerhandleri, sprite-rutiinit, modien lataus,
  2755. äänienginen ohjelmointirajapinta, sb-osa koodista, gus-osa koodista
  2756. jne.. Jokaiselle tiedostolle olisi sitten oma .h-tiedostonsa, jossa
  2757. määritellään kaikki c-tiedoston funktiot ja globaalit muuttujat (jos
  2758. niitä tarvitaan). Sitten toiset c-tiedostot jotka tarvitsevat tuon
  2759. tiedoston funktiota tai muuttujia ottaisivat vain includella
  2760. h-tiedoston mukaan ja kääntäjän linkkeri huolehtisi siitä, että
  2761. ohjelmakutsut menevät oikeisiin osoitteisiinsa.
  2762.  
  2763. Katsotaanpas pientä esimerkki h-tiedostoa ja c-tiedostoa. En väitä
  2764. tämän olevan ainoa oikea tapa, tämä on vain yksi tapa hoitaa homma:
  2765.  
  2766. ESIM.H:
  2767.  
  2768. #ifndef __ESIM_H
  2769. #define __ESIM_H
  2770.  
  2771. #include <stdio.h>
  2772.  
  2773. #define ESIMTEKSTI "Moikka, olen esimerkki!"
  2774.  
  2775. void Esimteksti();
  2776.  
  2777. extern int Kutsukertoja;
  2778.  
  2779. #endif
  2780.  
  2781.  
  2782. ESIM.C:
  2783.  
  2784. #include "esim.h"
  2785.  
  2786. int Kutsukertoja=0;
  2787.  
  2788. int Oma=666;
  2789.  
  2790. void Esimteksti() {
  2791. puts(ESIMTEKSTI);
  2792. Kutsukertoja++;
  2793. }
  2794.  
  2795.  
  2796. Lähdetäänpäs askeltamaan ESIM.H-tiedostoamme lävitse. Ensimmäisenä
  2797. rivi #ifndef __ESIM_H, joka ilmoittaa C-koodin esikäsittelijälle, että
  2798. jos __ESIM_H ei ole määritelty (IF Not DEFined, IFNDEF) niin osio
  2799. #ifndef:in ja #endif:in välissä tulee ottaa mukaan. Sen jälkeen
  2800. määritellään tuo kyseinen muuttuja, jotta H-tiedostoa ei pureta
  2801. kahteen kertaan (voi sattua kaikkea hassua jos vaikka h-tiedostot
  2802. kutsuvat toisiaan). Sitten tulee tämän C-tiedoston tarvitsemien
  2803. funktioiden kirjastot ja #definet (kirjastot voitaisiin sijoittaa myös
  2804. C-tiedostoon, mutta joskus tästä tulee ongelmia, jos käytetään makroja
  2805. tai muuta vastaavaa).
  2806.  
  2807. Sitten tulevat muuttujat ja funktiot. Muuttujien eteen TULEE laittaa
  2808. extern-määre, joka kertoo että ne on oikeasti määritelty jossain
  2809. muualla, jottei kääntäjä varaa muistia näille joka H-tiedoston
  2810. includettamisen kohdalla, jolloin linkatessa useissa C-tiedostoissa on
  2811. varattu muistia samannimiselle globaalille muuttujalle -> ongelmia.
  2812. Funktioiden edessä extern ei ole pakollinen ja sen voikin jättää pois
  2813. ja lisätä extern-määreen jos ko. funktio on ulkoisessa
  2814. assembler-tiedostossa.
  2815.  
  2816. Funktion parametrien nimet voi halutessa jättää määrittelyistä pois,
  2817. mutta se ei ole suositeltavaa. Muista myös, että globaalit muuttujat
  2818. esitellään ja alustetaan VAIN ja AINOASTAAN C-tiedostossa, ei
  2819. H-tiedossa!
  2820.  
  2821. C-tiedosto sisältää vastaavat H-tiedostossa "luvatut" funktiot ja
  2822. muuttujat. Jos haluat tehdä globaaleja muuttujia jotka eivät näy
  2823. muihin C-tiedostoihin, niin jätät sen esittelyn H-tiedostosta pois,
  2824. jolloin headerin sisällyttävät muut C-tiedostot eivät tiedä mitään
  2825. ko. muuttujan olemassaolosta eikä vahingossa tule virheitä. Tällainen
  2826. on esimerkki C-tiedoston muuttuja Oma.
  2827.  
  2828. Useita C-tiedostoja käyttäessäsi teet siis jokaisesta loogisesta
  2829. kokonaisuudesta oman "paketin", joka sisältää C-tiedoston, joka on
  2830. toimiva kokonaisuutensa ja H-tiedoston, joka tarjoaa muille
  2831. C-tiedostoille mahdollisuuden käyttää tämän paketin rutiineja.
  2832. Muista, että käyttäessäsi includea tuollaisen tiedoston kohdalla
  2833. käytetään heittomerkkejä normaalin <>-parin sijasta, jottei kääntäjä
  2834. lähde hakemaan ESIM.H:ta omasta include-hakemistostaan, vaan jotta se
  2835. hakisi tiedoston senhetkisestä työskentelyhakemistosta.
  2836.  
  2837. Mieti nyt nämä asiat selviksi, jotta ymmärrät miten tehdään useita
  2838. tiedostoja ja käytetään ilman ongelmia, niin voit sen jälkeen jatkaa
  2839. seuraavaan lukuun, jossa kerrotaan miten niistä muodostetaan ajettavia
  2840. ohjelmia, kirjastoja ja objektitiedostoja.
  2841.  
  2842.  
  2843. 6.2 Useiden tiedostojen projektit - kääntäminen ja hallinta
  2844. -----------------------------------------------------------
  2845.  
  2846. No niin, osaat nyt tehdä C-tiedostoja ja H-tiedostoja, mutta sillä ei
  2847. varmaankaan pitkälle pötkitä. Lähdemme nyt tutkimaan hieman
  2848. kääntäjämme, GCC:n sielunelämää ja tutustumme muutamaan elintärkeään
  2849. tietoon joita ilman ei voi edes elää. Nimittäin janoamme tietoa
  2850. formaateista.
  2851.  
  2852. Tiedostot joiden kanssa pyörimme DJGPP:n kanssa voidaan jakaa helposti
  2853. pelkistäen neljään (4) kategoriaan. Tässä ne ovat:
  2854.  
  2855. 1. Lähdekooditiedostot (c, cc, s, asm). Kääntäjä muuttaa koodin
  2856. konekieleksi ja tekee muut tarvittavat tehtävät tuottaen
  2857. objektitiedoston.
  2858.  
  2859. 2. Objektitiedosto (O). Sisältää koodin ja symboleja (eli funktioiden
  2860. ja muuttujien nimiä) ja kaikkea muuta kivaa infoa jotka liittyvät
  2861. olennaisesti rutiinien käskyihin ja dataan. Linkkeri linkkaa kaikki
  2862. objektitiedostot yhteen ja lisää tarvittavaa käynnistyskoodia sun
  2863. muuta luodakseen ajettavan tiedoston. Nämä ovat eräänlaisia
  2864. rakennuspalikoita, joissa kaikki on jo binäärimuodossa.
  2865.  
  2866. 3. Archive (A). Tätä voidaan halutessa käyttää useiden objektien
  2867. säilömiseen, eli paketoidaan monta objektitiedostoa yhteen kasaan
  2868. jotka voidaan liittää sitten yhtenä pakettina
  2869. kääntäjälle. Objekteista siis kootaan nippu jota voidaan käsitellä
  2870. yhtenä kokonaisuutena.
  2871.  
  2872. 4. Ajettava tiedosto. Sisältää objektitiedostoista tehdyn EXE:n, jossa
  2873. on lisäksi tarvittava koodi ohjelman käynnistämiseen.
  2874.  
  2875. GCC:n toimintaperiaate EXE:n käännössä on seuraava: Lähdetään
  2876. kääntämällä lähdekooditiedostot objektitiedostoiksi. Tässä vaiheessa
  2877. siis laajennetaan makrot, includet ja esikäsittelijän komennot (kaikki
  2878. #ifndef-rakenteet sun muut). Sitten käännetään koodi konekielelle ja
  2879. tehdään objektitiedostot.
  2880.  
  2881. Seuraavaksi kutsutaan linkkeri joka liittää objektitiedostot yhteen ja
  2882. lisää tarvittavat kirjastot (LIBC.A tulee EXE:en aina mukaan ja
  2883. lisäksi muut -l<nimi> parametreillä annetut kirjastot) sekä
  2884. aloituskoodin, joka kutsuu main-funktiota, jonka oletetaan löytyvän
  2885. jostain O-tiedostosta.
  2886.  
  2887. Itseasiassa tuo ei mene aivan noin yksinkertaisesti, mutta tärkeintä
  2888. on ymmärtää, että lähdekoodista tehdään rakennuspalikoita,
  2889. objektitiedostoja joista voidaan myöhemmin koota ajettavia tiedostoja.
  2890.  
  2891. Jos meillä siis olisi C-tiedostot main.c ja apu.c (mahdollisesti
  2892. vastaavine H-tiedostoineen), joista main.c sisältäisi main-funktion ja
  2893. pääkoodin ja apu.c kaikkia tarpeellisia rutiineja, niin voisimme
  2894. kääntää ne objektitiedostoiksi ja aina kun jompaakumpaa muunnetaan,
  2895. niin kääntäisimme tämän lähdekooditiedoston uudelleen. EXE
  2896. muodostettaisiin erikseen toisella komennolla jolloin muutos toisessa
  2897. tiedostossa vähentäisi käännettävän koodin määrää (tosin linkkaustyö
  2898. pysyisi ennallaan).
  2899.  
  2900. Miten sitten näitä erilaisia tiedostoja tehdään? Hyvä kysymys. Alla
  2901. näette kaikkein komentoja objektitiedostojen, EXE:jen ja archivejen
  2902. luontiin, lähdekoodit osaatte varmaan jo. =)
  2903.  
  2904. Objektitiedosto GCC:llä:
  2905. gcc -c koodi.c -o objekti.o (halutessa lähdetiedostoja voi olla useampia)
  2906.  
  2907. Archive-tiedosto objektitiedostoista:
  2908. ar rs archive.a objekti1.o ... (kaikki halutut objektit vain perään)
  2909.  
  2910. Ajettava tiedosto archive-, objekti- ja lähdekooditiedostoista (GCC
  2911. osaa käsitellä ne päätteiden mukaan):
  2912. gcc <tiedostot> -o tulos.exe <parametrit>
  2913.  
  2914. Lisää infoa sitä haluaville löytyy englanninkielisenä komennolla
  2915. INFO. Sitä löytyy aika paljon enkä todellakaan halua tästä
  2916. tutoriaalista mitään DJGPP:n komentoriviparametrien selitystä. =)
  2917.  
  2918. Eli kerrataan vielä vaiheet joita käytätte "oikeaoppisen" projektin
  2919. tekoon:
  2920.  
  2921. 1. Luo C- ja H-tiedostot ja muu tarvittava lähdekoodi
  2922. 2. Käännä ne O-tiedostoiksi (tyyliin gcc -c koodi.c -o objekti.o)
  2923. 3. Jos haluat tehdä kirjastoja, niin tee objektitiedostoista ar:llä
  2924. niitä. Esimerkiksi grafiikkaenginen objektitiedostot voisi liittää
  2925. yhteen ja nimetä libgraf.a:ksi ja siirtää DJGPP:n LIB-hakemistoon.
  2926. Myöhemmin nuo enginen objektit olisi helppo lisätä EXE:een pelkällä
  2927. -lgraf -parametrilla.
  2928. 4. Käännä ajettava ohjelma objektitiedostoista ja archive-tiedostoista
  2929. (gcc <tiedostot> -o tulos.exe <parametrit>). Archive-tiedoston
  2930. nimen voi antaa joko tiedostojen mukana tai parametrinä -l<nimi>
  2931. JOS archive on DJGPP:n LIB-hakemmistossa nimellä lib<nimi>.a.
  2932.  
  2933. Grafiikkaenginekin voi olla projekti, jolloin jätätte EXE:ksi
  2934. kääntämisen kokonaan pois, ja teette vain archive-tiedoston. Tai jos
  2935. tarvit vain yhden .o -tiedoston, niin mikäs siinä, valinta on vapaa.
  2936.  
  2937. Nyt sinun pitäisi osata tehdä objektitiedostoja lähdekoodista,
  2938. kirjastotiedostoja objekteista ja ajettava ohjelma objekteista (ja
  2939. mahdollisesti myös kirjastoista). Kun hallitset nämä asiat jatkamme
  2940. jälleen taivaltamme.
  2941.  
  2942.  
  2943. 6.3 Hieman automaatiota - tapaus Rhide
  2944. --------------------------------------
  2945.  
  2946. No tällä hetkellä me osaamme kaikki tarvittavat taidot komentoriviltä,
  2947. mutta uusien tiedostojen nimien muistaminen ei aina ole kivaa ja
  2948. komentorivillä vääntäminen sopii vain perusteiden harjoitteluun. Rhide
  2949. on tapa päästä koko roskasta helpolla ilman perusteita edes
  2950. objektitiedostoista, mutta koska teillä tulee olemaan niin paljon
  2951. helpompaa kun ne osaatte niin olen katsonut tarpeelliseksi ne myös
  2952. neuvoa. (sillä Rhidenkin kanssa kunnon projekteilla tarvitaan tuota
  2953. osaamista).
  2954.  
  2955. Ainahan pääsee helpolla, mutta valitettava tosiasia on, että se joka
  2956. hyppäsi edelliset kappaleet ylitse onkin sormi suussa kun tulee
  2957. ongelma eteen. Mikään ei korvaa tietoa ja kokemusta, ei edes hyvä
  2958. ohjelmointiväline.
  2959.  
  2960. Eli tämän kappaleen tarjoama informaatio käsittelee Rhideä ja sen
  2961. projekteja projektien hallinnassa. Jos teitä ei Rhide kiinnosta niin
  2962. voitte hypätä yli, lupaan että seuraava kappale kiinnostaa teitä,
  2963. sillä makefilejen käyttö on vaihtoehtoinen (ja gurumpi, elegantimpi ja
  2964. yleisempikin) tapa automatisoida projektien kääntäminen. Mutta te
  2965. joita kiinnostaa yksi tämän hetken parhaimmista DOS-ympäristön
  2966. IDE-ohjelmista pysykää kappaleessa, tosin asia voi olla joillekin jo
  2967. vanhaa leipää.
  2968.  
  2969. Eli Rhiden sisältää makefileiden kaltaisen järjestelmän projektien
  2970. hallintaan, mutta toisin kuin make se sisältää tekoälyä, joka osaa
  2971. projektille valitusta kohteesta päätellä millainen tulos halutaan ja
  2972. projektin tiedostojen päätteistä minkätyyppinen tiedosto on kyseessä
  2973. ja miten se pitää kääntää. Koska Rhide on aika yksinkertainen
  2974. järjestelmä käsittelen vain lyhyesti sen perusasiat, eli projektien
  2975. teon, availun, käsittelyn, Rhiden kustomoinnin ja kohteiden
  2976. määräämisen.
  2977.  
  2978. Eli aloittakaamme tekemällä oletusprojekti Rhidelle. Ensimmäinen
  2979. tehtäväsi lienee installoida Rhide, joka yleensä koostuu purkamisesta
  2980. DJGPP-hakemistoon ja ohjelman käynnistämisestä kokeeksi. Dokumenttien
  2981. lukeminenkaan ei ole pahasta, mutta kyllä ilmankin voi pärjätä, tosin
  2982. vaikeuksien sattuessa ne ovat usein korvaamattomia. Rhiden jotkin
  2983. versiot ovat olleet enemmän tai vähemmän bugisia, mutta ainakin
  2984. versiot 1.1 (bugikorjattuna!), 1.2 ja 1.3 ovat toimineet minulla hyvin,
  2985. joten joko Altavistaan hakusanalla Rhide, MBnettiin tai MB:n
  2986. H&H-rompulle.
  2987.  
  2988. Sitten kun Rhide toimii niin menette DJGPP:n BIN-hakemistoon ja
  2989. kirjoitatte "rhide rhide". Tämä tarkoitus on luoda/muuttaa
  2990. BIN-hakemistossa olevaa rhide-nimistä projektia, jonka asetukset
  2991. ladataan AINA kun rhide käynnistetään ilman projektia ja jotka
  2992. toimivat uusien projektien oletusasetuksina. Muuttele rhide-projektia
  2993. niin paljon kuin haluat/uskallat/viitsit ja lopeta sen jälkeen
  2994. rhide. Voit kokeilla vielä asetusten toimivuutta menemällä jonnekin
  2995. hakemistoon missä on jokin muu määrä kuin yksi projekteja (jos niitä
  2996. on vain yksi niin se ladataan automaattisesti) ja käynnistämällä
  2997. Rhiden.
  2998.  
  2999. Nyt pitäisi kaiken olla valmista uuden projektin teolle. Ota
  3000. Project-valikosta Open project ja kirjoita avautuvan ikkunan
  3001. Name-sarakkeeseen haluamasi projektin nimi. Ruudun alalaitaan avautuu
  3002. ikkuna joka kertoo projektin tiedostot. Aktivoimalla tämän ikkunan ja
  3003. painamalla insert-nappia (tai Project-valikosta Add item) saat
  3004. lisättyä uusia tiedostoja. Kun olet valmis paina Cancel-nappia.
  3005. Tällä tavalla lisäät haluamasi tiedostot (lähdekooditiedostot, tosin
  3006. jos ehdottomasti haluat voit laittaa jonkin valmiiksi käännetynkin O-
  3007. tai A-tiedoston mukaan) projektiin.
  3008.  
  3009. Mukaan lisättäviä kirjastoja voit määrittää Options-valikon
  3010. Libraries-kohdasta. Muista, että tämä hakee kirjastoja VAIN DJGPP:n
  3011. LIB-hakemistosta, ja että kirjaston nimeen lisätään aina kääntäjän
  3012. toimesta eteen LIB ja loppuun .A, eli älä kirjoita koko kirjaston
  3013. nimeä tyyliin LIBJOKIN.A, vaan JOKIN. Sellainen erikoisuus kyllä
  3014. kääntäjästä löytyy, että ylipitkät (yli 5 merkkiä) kirjaston nimet
  3015. katkaistaan, joten IOSTREAM antaa tiedoston LIBIOSTR.A, eikä
  3016. virheellistä LIBIOSTREAM.A:ta (joka olisi siis liian pitkä).
  3017.  
  3018. Kun olet tyytyväinen kaikkeen muuhun niin ota vielä Project-valikosta
  3019. main targetname ja määritä kohteen nimi. Jos olet tekemässä
  3020. ääniengineä, niin sinulla on äänienginen C-tiedostot projektissasi ja
  3021. kohteena (esim.) LIBSND.A. Jos taas teet C++ EXE:ä, niin sinulla on
  3022. C-tiedostot joita käytetään, kohteena (esim.) PLUSPLUS.EXE ja
  3023. mahdollisesti kirjastossa IOSTR ja jotain muuta. .A-päätteestä Rhide
  3024. osaa automaattisesti kääntää archive-muotoisen tiedoston ja
  3025. .EXE-päätteestä ajettavan. Muutkin voivat toimia (O ainakin), mutten
  3026. ole kokeillut koskaan, sillä siihen ei yleensä ole tarvetta.
  3027.  
  3028. Projektin kääntäminen onnistuu napilla F9, jolloin Rhide osaa
  3029. automaattisesti katsoa tiedoston päiväyksistä mitkä tiedostot ovat
  3030. muuttuneita (lähteen päivämäärä uudempi kuin kohteen) ja kääntää näin
  3031. vain tarpeellisen. Aikaa säästyy ja hermoja samoin. Kääntämisen
  3032. jälkeen hakemistostasi löytyy luultavasti kasa objektitiedostoja,
  3033. joita voidaan käyttää myöhemmin linkkauksessa (jos vastaava
  3034. lähdekooditiedosto ei ole muuttunut).
  3035.  
  3036. Sellaista tällä kertaa. Aika perusasiaa ja itsekin pääteltävissä,
  3037. mutta joskus vain käy siten ettei jotain perusasiaa itse hoksaa, tai
  3038. ainakin säästää aikaa kun ei tarvitse kaikkea kokeilla. Nyt hallussa
  3039. pitäisi olla projektien teko Rhidellä ja niiden toimimaan saaminen, ei
  3040. sen kummempaa tällä kertaa. Voit jatkaa halutessasi seuraavaan jos
  3041. tuntuu että osaat tämänkin kappaleen materiaalin.
  3042.  
  3043.  
  3044. 6.4 Todellista guruutta - salaperäinen make
  3045. -------------------------------------------
  3046.  
  3047. Make on kuin suoraan Unix-maailmasta tullut. Jos pelkkä vilkaisu sen
  3048. info-sivuille (INFO MAKE) saa aloittelijan vapisemaan horkassa. Mutta
  3049. ei hätää, minä kävin siellä ja selvisin elossa - tosin en ole enää
  3050. ollut sama itseni sen jälkeen. Olen nimittäin huomattavasti gurumpi
  3051. jälleen sillä voin käännellä projektini halutessani hienosti
  3052. komentoriviltä automatisoituna. Ja se onnistuu maken
  3053. makefileillä. Tässä luvussa kerron miten niitä tehdään, tosin en
  3054. mitään monimutkaisempaa valota kun mitään ihmekonsteja harvemmin
  3055. normaalissa perustyöskentelyssä tarvitsee.
  3056.  
  3057. Eli ensimmäisenä tehtävänä on jälleen kaivaa make jostain, paikat ja
  3058. keinot ovat samat kuin Rhiden kohdalla, mutta toisin kuin Rhide maken
  3059. pitäisi toimia ilman manuaaliin vilkaisua (koska se on huomattavasti
  3060. yksinkertaisempi systeemi). Ideana on tehdä projektille ns. makefile,
  3061. jonka make osaa tulkita ja tehdä sen mukaan tiedostossa käsketyt
  3062. asiat.
  3063.  
  3064. Mutta tehdäksemme oikeanlaisia makefilejä meidän täytyy ensin hieman
  3065. ymmärtää filosofiaa maken takana.
  3066.  
  3067. Normaali makefile koostuu yleensä alussa olevasta kasasta
  3068. muuttujamäärittelyjä, joita myöhemmin käytetään kääntämisessä. Sen
  3069. jälkeen on kasa ohjeita, jotka koostuvat muutamasta
  3070. komponentista. Tässä on ohjeen muoto ja esimerkki yhdestä:
  3071.  
  3072. kohde: riippuvuudet
  3073. komento kohteen tekoon
  3074.  
  3075. esim.
  3076.  
  3077. ohjelma.exe: ohjelma.o
  3078. gcc ohjelma.o -o ohjelma.exe -s -Wall -v -O2
  3079.  
  3080. Eli ensimmäisenä on kohde joka kertoo makelle, että tässä on ohje
  3081. miten teet tämän. Sitten on riippuvuudet, joka kertoo, että näiden
  3082. pitää olla kunnossa ennenkuin tätä ohjetta aletaan
  3083. toteuttamaan. Seuraavalla rivillä on yksi TAB:in painallus ja komento
  3084. jolla kohde tehdään (komentoja voi olla useampiakin, jokainen omalla
  3085. rivillään alkaen TAB:illa). Huomaa, että tarvitsemme EHDOTTOMASTI
  3086. oikean TAB:in, emme mitääs MSDOS EDIT:in lelutabbeja, jotka eivät
  3087. itseasiassa ole kuin määrätty määrä välilyöntejä. Eli pitää olla
  3088. jonkinlainen editori, joka osaa käyttää aitoja TAB-merkkejä.
  3089.  
  3090. En taida alkaa miettimään syvällisemmin maken toimintaa, mutta ideana
  3091. on, että esittelet ensin pääkohteen ja sen riippuvuudet ja sen jälkeen
  3092. esittelet nämä uudet riippuvuudet ja niiden riippuvuudet jatkaen
  3093. pohjalle asti kunnes lopulta sinulla on kohteena objektitiedosto ja
  3094. lähteenä lähdekooditiedosto ja alla komento tämän kääntämiseksi,
  3095. jolloin make katsoo päivämäärän mukaan tarvitseeko tämä kohde
  3096. päivittämistä. Jos lähde on uudempi kuin kohde niin käsky suoritetaan
  3097. mutta jos kohde on uudempi niin se on täydytty kääntää lähteen
  3098. muuttamisen jälkeen eikä kääntöä tarvita. Tällä tavalla vain
  3099. muuttuneiden tiedostojen aiheuttamat käännöstarpeet hoidetaan eikä
  3100. ylimääräistä työtä tehdä.
  3101.  
  3102. Yleensä makefilessä on ensin kohde all, jossa riippuvuuksina on kaikki
  3103. mitä makefilen tulee saada tuloksena valmiiksi (EXE:t, kirjastot),
  3104. sitten on näiden tuloksien ohjeet riippuvuuksina objekti- ja
  3105. archive-tiedostot, sitten archive-tiedostot riippuvuuksina
  3106. objektitiedostot ja lopuksi objektitiedostot riippuvuuksina
  3107. lähdekooditiedostot. Tässä on esimerkki joka varmaan valaisee aika
  3108. sekavaa selitystäni. =) Huomaa myös makrot, jotka määritellään alussa
  3109. ja joita muuttamalla on helppo vaihtaa käännöksessä tarvittavia
  3110. parametrejä ja kääntäjien nimiä:
  3111.  
  3112. CC=gcc
  3113. CFLAGS=-s -Wall
  3114.  
  3115. AR=ar
  3116. ARFLAGS=rs
  3117.  
  3118. all: esim.exe libx.a
  3119.  
  3120. esim.exe: esim.o libx.a
  3121. $(CC) $(CFLAGS) esim.o libx.a -o esim.exe
  3122.  
  3123. libx.a: x1.o x2.o
  3124. $(AR) $(ARFLAGS) libx.a x1.o x2.o
  3125.  
  3126. esim.o: esim.c
  3127. $(CC) $(CFLAGS) -c esim.c -o esim.o
  3128.  
  3129. x1.o: x1.c
  3130. $(CC) $(CFLAGS) -c x1.c -o x1.o
  3131.  
  3132. x2.o: x2.c
  3133. $(CC) $(CFLAGS) -c x2.c -o x2.o
  3134.  
  3135. Kun tämän tiedoston tallentaa nimelle makefile tarvitsee sinun vain
  3136. antaa komento make niin ohjelma osaa automaattisesti kääntää kaikki
  3137. makefilessä määritellyt tiedostot. Käyttääksesi muita makefilen nimiä
  3138. pitää maken komentoriville antaa parametri -f<makefile>.
  3139.  
  3140. Esimerkki oli hyvin yksinkertaistettu ja vältin käyttämästä paria
  3141. hauskaa kikkaa jotka tekevät makefilestä paljon lyhyemmän (ja
  3142. sotkuisemman näköisen). Jos kuitenkin toiminta on epävarmaa, niin
  3143. selostetaan se tässä vielä kertaalleen:
  3144.  
  3145. 1. Make aloittaa lausekkeesta all (komentorivillä voit halutessasi
  3146. määrätä mikä ohje tulee tehdä, esim make libx.a ei koskisi esim.*
  3147. -tiedostoihin) ja etenee tekemään esim.exe:ä.
  3148.  
  3149. 2. Esim.exe:n teko tarvitsee ensin esim.o:n, siirrytään siihen.
  3150.  
  3151. 3. Esim.o tarvitsee esim.c:n, mutta sille ei löydy ohjetta, joten
  3152. suoritetaan ensimmäinen käännös. Makrot CC ja CFLAGS puretaan
  3153. komentoriville ja se suoritetaan ja kaiutetaan näytölle. Jatketaan
  3154. esim.exe:n riippuvuuksien tutkimista.
  3155.  
  3156. 4. Esim.exe:n teko tyssää kun siihenkin pitää tehdä libx.a, joten
  3157. siirrytään tekemään sitä.
  3158.  
  3159. 3. Libx.a:han pitää olla x1.o ja x2.o, joten siirrytään niihin.
  3160.  
  3161. 4. Riippuvuudelle x1.c ei ole ohjetta, joten suoritetaan x1.o:n
  3162. komento (näissä kohtaa olisi päivämäärätarkistus, mutta koska
  3163. noita objektitiedostoja ei vielä ole olemassa niin...) ja palataan
  3164. takaisin.
  3165.  
  3166. 5. x2.o tehdään samaan tapaan kuin edellinen ja palataan libx.a:n
  3167. pariin
  3168.  
  3169. 6. Riippuvuudet kunnossa, tehdään kirjasto libx.a, palataan esim.exe:n
  3170. kimppuun.
  3171.  
  3172. 7. Esim.exe:n riippuvuudetkin ovat hanskassa, joten tehdään se ja
  3173. palataan kohtaan all.
  3174.  
  3175. 8. Libx:kin on tehty juuri, joten kaikki on valmista, poistutaan.
  3176.  
  3177. No niin, kyllä toiminta varmaankin selvisi, ja jos ei niin paljon
  3178. pidemmät ja selvemmät tekstit löytää englanniksi komennolla info make
  3179. (no selvemmistä en itseasiassa tiedä :).
  3180.  
  3181. Mutta make ei vielä ole ohitse, en uskalla päästää teitä kappaleesta
  3182. ennenkuin osaatte tehdä ohjeita jotka tekevät vaikka 30
  3183. objektitiedostoa kerralla, ne kun ovat kovin mukavia systeemejä
  3184. verrattuna siihen että joutuisit kirjoittamaan jokaista varten oman
  3185. ohjeen.
  3186.  
  3187. Ideana tässä on eräänlainen nimentäydennys. Make osaa poistaa päätteen
  3188. nimestä ja korvata sen toisella, jota ominaisuutta käytetään juuri
  3189. tähän useiden samankaltaisten tiedostojen tekoon kerralla. Jos siis
  3190. sinulla on 10 objektitiedostoa ja jokainen käännetään
  3191. vastaavannimisestä lähdekooditiedostosta (o1.o ja o1.c, o2.o ja o2.c
  3192. jne.), niin niiden kääntö onnistuu seuraavalla tyylillä (aika maken
  3193. infoista pöllittyä ja suoraan käännettyä tavaraa mutta who cares?-):
  3194.  
  3195. KOHTEET: KOHDE-PATTERN: RIIPPUVUUS-PATTERN ...
  3196.  
  3197. OBJECTS=object0.o object1.o object2.o object3.o object4.o object5.o
  3198. object6.o object7.o object8.o object9.o
  3199.  
  3200. $(OBJECTS): %.o: %.c
  3201. $(CC) $(CFLAGS) -c $< -o $@
  3202.  
  3203. Eli ensimmäisenä tulee lista (OBJECTS) tehtävistä kohteista, sitten
  3204. tulee %-merkki, joka esiintyy kohde-patternissa vain kerran, ja
  3205. maken infosivut käyttävät siitä nimeä "stem". Tämä vastaa mitä tahansa
  3206. kohtaa yhden kohteen nimestä, kaikki muut kohteen nimessä (.o tässä
  3207. tapauksessa) täytyy vastata täysin.
  3208.  
  3209. Jos siis kohteena olisi foo.o ja kohde-pattern olisi %.o, niin "stem"
  3210. (anteeksi minulla ei ole sanakirjaa käsillä ;) saisi arvon foo. Jos
  3211. riippuvuus-pattern olisi %.c niin riippuvuus tälle tiedostolle olisi
  3212. foo.c. Ei mitään sen vaikeampaa, % on kuin DOS-maailman * ja
  3213. ensimmäisenä tulee lista tiedostoista (kuten hakemistolistaus), sitten
  3214. stemillä varustettu patterni ja lopuksi riippuvuudet jotka
  3215. täydennetään sillä mitä stem vastaa.
  3216.  
  3217. Lisäksi täytyy kiinnittää huomio merkkisarjoihin $< ja $@, joista
  3218. ensimmäinen korvataan riippuvuudella (tai riippuviiksilla jos niitä on
  3219. useampia) ja toinen kohteen nimellä. Myös muita vastaanvankaltaisia
  3220. löytyy, mutta ne eivät ole läheskään niin hyödyllisiä kuin nämä kaksi.
  3221.  
  3222. Näillä eväillä ainakin pitäisi onnistua makefileiden teko aika
  3223. pitkälle. Hyviä esimerkkejä löytyy lukemattomista DJGPP-paketeista,
  3224. joissa kääntäminen hoidetaan makefileillä. Makefilet ovat muutenkin
  3225. yleisin tapa levittää lähdekoodin kanssa softaa, harvemmin olen nähnyt
  3226. kirjaston käännöstä automatisoitavan Rhiden projekteilla. :)
  3227.  
  3228.  
  3229. 6.5 Ammattimaista meininkiä - enginen teko
  3230. ------------------------------------------
  3231.  
  3232. Tämä luku kertoo hieman niistä vähäisistä kokemuksista mitä minulla on
  3233. ollut projektien kanssa, tai oikeammin kertoo mitä kannattaisi ottaa
  3234. huomioon enginen teossa, jotta se toimisi myös huomenna ja jotta siitä
  3235. jälkeenpäin saisi jotain selvääkin.
  3236.  
  3237. Näppärä tapa pääohjelman yksinkertaistamiseksi on tehdä tietyn
  3238. tehtävän suorittavista tiedostoista yksi paketti, kirjasto jonka
  3239. headerin koodiin sisällyttämällä voi kyseisen tehtävän hoitaa
  3240. kirjaston tarjoamilla rutiineilla.
  3241.  
  3242. Sen lisäksi että tapa yksinkertaistaa koodia se myös parantaa sen
  3243. ylläpidettävyyttä huomattavasti ja myöskin muunneltavuus on aivan eri
  3244. luokkaa kuin "kaikki-yhdessä-kasassa" -ohjelmilla. Lisäksi kun engine
  3245. on kerran valmis voi sitä käyttää uudelleen ja uudelleen - yleensä
  3246. pienillä muutoksilla tai parhaimmillaan muuttamattomanakin.
  3247.  
  3248. Mutta tällaisenkin teossa kannattaa huomioida joitakin asioita, jottei
  3249. jälkeenpäin paljastuisi että olet tehnyt turhaa työtä koko
  3250. ajan. Nimittäin ensin on tarkoin otettava selvää mitä engineltä
  3251. vaaditaan ennenkuin sellaista alkaa tekemään. Hyvä tapa on miettiä
  3252. millaista peliä on tekemässä ja millaisia ominaisuuksia engineltä
  3253. vaaditaan. Matopelin teossa ei välttämättä tarvita kovin kummoisia
  3254. järjestelmiä, sillä ne eivät useastikaan vaadi kovinkaan monimutkaista
  3255. toimintaa hyvän jäljen aikaansaamiseksi. Toisin on vaikka sivultapäin
  3256. kuvatussa ammuskelupelissä, jossa spritejen piirron pitää olla
  3257. äärimmäisen nopeaa ja turhaa piirtelyä tulee välttää. Skrollaus vaatii
  3258. myös tällaisissa peleissä tehoja ja muuttujia spriteihin tulee
  3259. huomattavasti enemmän kuin matopelissä.
  3260.  
  3261. Mikään ei voita kunnon suunnittelua kun koodausta sitten aletaan
  3262. tekemään. Hyvällä onnella koko enginen teko on suoraviivaista koodin
  3263. kirjoittamista jos tärkeimpiä algoritmejä on jo hahmoteltu paperilla
  3264. ja mielessä on kunkin funktion toiminta ja tarvittavat muuttujat
  3265. kuhunkin tehtävään.
  3266.  
  3267. Kun tarpeet ovat vihdoin paperilla ja koodin kirjoitus edessä voi olla
  3268. hyvä vielä etukäteen nimetä enginen lohkot ja nimetä ne. Näppärä tapa
  3269. jolla pääsee suoraan toimeen on käynnistää vaikka Rhide ja lähteä
  3270. lisäilemään uuteen projektiin tiedostojen nimiä. Tiedostoja ei
  3271. tarvitse edes olla olemassa vaan riittää että hahmotat mitä
  3272. järjestelmän pitää tehdä ja minkälaisiin osiin se pitäisi
  3273. jakaa. Kaikkein kevyimmät enginet eivät edes paljoa tiedostoja tarvi,
  3274. näppishandleri ja timerhandleri, hiirirutiinit ja yksinkertaisemmat
  3275. grafiikkaenginet menevät ainakin tähän kastiin. Äänienginet,
  3276. playerit ja 3D-enginet sekä raskaammat grafiikkaenginet taas voivat
  3277. hyvinkin viedä toistakymmentäkin tiedostoa.
  3278.  
  3279. Hyviä jakotapoja on monia ja järki varmaan sanoo, että hyvä jakotapa
  3280. ei ole aakkosjärjestys taikka pituusjärjestys. Hyvä jakotapa voi olla
  3281. vaikka äänienginen teossa päätiedosto sisältäen käynnistys- ja
  3282. lopetusfunktiot ja jonka .h-tiedostosta löytyvät keskeiset
  3283. datarakenteet, latausrutiinit sisältävä tiedosto, universaali
  3284. efektinsoittorajapinta ja eri tiedostot jokaiselle äänikortille,
  3285. modien lataus, modien soittorutiinit sisältävä tiedosto jne.. Aivoja
  3286. saa, pitää ja kannattaa käyttää.
  3287.  
  3288. Tärkeitä suunnittelun kohteita on myös se miten ohjelma säilöö datansa
  3289. sekä muistissa että kovalevyllä. Jo alussa fiksusti ja
  3290. laajennettavasti tehty rakenne on monta kertaa käyttökelpoisempi kuin
  3291. senhetkiseen tarpeeseen väsätty kyhäelmä. Myös tallennus- ja
  3292. latausrutiinit kannattaa tehdä erikseen eikä pyrkiä tekemään mitään
  3293. purkkaviritelmiä jotka kaatuvat vähintäänkin kun haluat lisätä uuden
  3294. ominaisuuden.
  3295.  
  3296. Hyvä idea on myös tehdä universaalit rutiinit virheistä
  3297. ilmoittamiseen, muistin varaukseen ja vaikka tiedostojenkin
  3298. lukuun. Yleensäkin enginen suurin osa tulisi sijoittaa keskivälille
  3299. muutaman kriittisten low-level -rutiinien jäädessä alapuolelle ja
  3300. yläpuolelle tuleva rajapinta ohjelmalle mahdollistaa enginen
  3301. muuttumisen radikaalistikin ilman muutoksia pääohjelmaan. Low-level
  3302. -rutiinien siirto toisille nimille jo pelkillä #define-lausekkeilla
  3303. (tyyliin "#define OmaFopen(a,b) fopen(a,b)") auttaa sen verran, että
  3304. kun haluatkin muuttaa kaikki tiedostorutiinit pakattuja datatiedostoja
  3305. käyttäviksi ei tarvitse muuttaa kuin pari kohtaa kaiken muun jäädessä
  3306. samanlaiseksi.
  3307.  
  3308. Kommentointi on elintärkeää engineä tehdessä, sillä hyvä engine voi
  3309. olla käytössä pitkänkin aikaa ja sitten kun se lopulta jää ahtaaksi
  3310. voi huonosti kommentoineen kooderin periä hukka muuntelun
  3311. osoittautuessa mahdottomaksi yksinkertaisesti siitä syystä ettei edes
  3312. tekijällä ole enää mitään aavistusta mitä hänen koodinsa tekee. Hyvä
  3313. ohjelmoija tekee sen verran lyhyitä funktioita, että niistä saa selvää
  3314. vähän tutkailemalla ja nimeää muuttujat ja funktiot kuvainnollisesti
  3315. säästelemättä turhaan nimen pituudessa (järkevällä tasolla kuitenkin,
  3316. mutta saa se nyt enemmän olla kuin Jdrwsprt()). Kun epäselvemmät
  3317. kohdat vielä kommentoi koodista pitäisikin saada huomattavasti
  3318. paremmin selvää.
  3319.  
  3320. Yksi hyödyllinen asia voisi olla tiedostoja editoidessa kirjoittaa
  3321. tietty headeri jokaisen tiedoston alkuun. Hyviä voisi olla
  3322. copyright-ilmoitukset (joilla ei kyllä omassa käytössä tee mitään),
  3323. luontipäivämäärä, viimeisen muutoksen päivämäärä ja muutoshistoria,
  3324. jonne kirjataan muutokset koodiin. Jälkeenpäin ja bugeja etsiessä
  3325. tuollaisesta on kummasti hyötyä, kun miettii mitä onkaan tullut
  3326. lähiaikoina muunneltua.
  3327.  
  3328. Viimeinen asia mikä koodissa pitää vielä huomioida on ne funktiot,
  3329. jotka tarjoavat rajapinnan, "käyttöliittymän" engineen. Nämä funktiot
  3330. ovat siis ne jotka tarjotaan engineä käyttävälle ohjelmalle enginen
  3331. käyttöön. Näiden tulee olla tarpeelliksi kattavat jotta kaikkia
  3332. enginen ominaisuuksia voidaan halutessa käyttää hyväksi. Hyödyllistä
  3333. on tehdä Init- ja Deinit-funktiot, joita kutsutaan pääohjelmasta
  3334. ohjelman käynnistyessä ja siitä poistuttaessa.
  3335.  
  3336. Myös funktioiden nimeäminen erottamiseksi muista mahdollisista
  3337. samankaltaisista funktioista voi olla hyödyllistä. Kirjaston
  3338. funktioille ja globaaleille muuttujille voisi antaa jonkin etuliitteen
  3339. erottamaan ne muista ja huolehtimaan siitä ettei kahdella funktiolla
  3340. ole samaa nimeä. Omassa grafiikkakirjastossani käytän JG-etuliitettä,
  3341. jolloin funktioiden nimet ovat tyyliin JG_Draw, JG_Hide jne.. Myös
  3342. mahdollinen versionumero kirjastolle on kätevä jos sitä aikoo todella
  3343. kehittää kunnolla.
  3344.  
  3345. Sitten vain huolehtimaan siitä että enginestä ei löydy
  3346. pullonkauloja. Helpointa lienee tehdä enginen eniten tehoa vaativat
  3347. osat mahdollisimman nopeiksi, jolloin pääohjelma on helppo tehdä
  3348. korkean tason koodilla. Assembler-optimointikin voisi olla ihan kiva,
  3349. joten seuraavassa luvussa luulen että selitän hieman sen lisäilystä
  3350. DJGPP:n koodiin.
  3351.  
  3352. Tämä luku ei nyt varsinaisesti opettanut mitään, mutta ainakin jotain
  3353. evästä pitäisi nyt löytyä ensimmäisen enginen tekoon. Katsotaan mitäs
  3354. tähän nyt keksisikään seuraavaksi. =)
  3355.  
  3356.  
  3357. 7.1 Vauhtia peliin - ulkoisen assyn käyttö
  3358. ------------------------------------------
  3359.  
  3360. No niin, assembler, tuo kielistä jaloin näyttää olevan tämänkertaisen
  3361. kiinnostukseemme kohteena. Vaan mikä on tuo salaperäinen kieli ja
  3362. miten sitä käytetään. Se jää ihan sinun itsesi selvitettäväksi, mutta
  3363. voin kuitenkin antaa jonkinlaisia ohjeita jotta löytäisit tiedon
  3364. lähteille. Ensihätään kannattaa hakea koneelleen ainakin seuraavat
  3365. opukset vaikkapa MBnetin ohjelmointialueen kautta:
  3366.  
  3367. ASSYT.ZIP:
  3368. Cyberdune (tjsp.) magazinen assykurssit kaikki samassa kasassa,
  3369. suomeksi opettaa assemblerin perusasiat.
  3370.  
  3371. HELPPC21.ZIP + HPC21_P5.ZIP:
  3372. HelpPC referenssiteos ja Pentium-update sisältäen mm. kaikki
  3373. x86-prosessorikäskyt, matikkaprossukäskyt ja Pentiumin omat käskyt
  3374. (kuten CMPXCHG8B tai jotain).
  3375.  
  3376. PCGPE10.ZIP:
  3377. Assytutoriaali löytyy täältäkin, tosin englanniksi.
  3378.  
  3379. 3DICA*.ZIP:
  3380. Sisältää Henri Tuhkasen mainion assembler-optimointitutoriaalin.
  3381. Ehdoton ensihankinta optimoinnista kiinnostuneelle.
  3382.  
  3383. Lisäksi todella hyvä kirja assyn opetteluun (ja ainoita suomeksi) on
  3384. kirja nimeltään 486-ohjelmointi. Tuota kaikki aina suosittelevat enkä
  3385. itsekään voi kirjaa haukkua. Kirjastosta tuon saa vielä kaiken lisäksi
  3386. ilmaiseksi, vähintään kaukolainauksella.
  3387.  
  3388. Jos sinua ei assembler kiinnosta yhtään niin voit tietenkin hypätä
  3389. tämän kappaleen yli, mutta varoituksen sana sitä ennen: Jos aiot tehdä
  3390. joskus nopean toimintapelin (lähiaikoina ainakin), niin tulet hyvin
  3391. luultavasti kaipaamaan assembler-osaamista. No tietenkin jos odottaa
  3392. tarpeeksi niin voi tehdä kaiken vaikka Visual Basicin kasiversiolla,
  3393. mutta en minäkään takaa että pysyn myöhemmin tutoriaalissa pelkässä
  3394. C:ssä. <grin>
  3395.  
  3396. Mutta sen jälkeen kun osaat assyn, niin alahan lukemaan pidemmälle,
  3397. sillä käsittelen hieman C-kielisestä ohjelmasta kutsuttavien
  3398. funktioiden tekoa assyllä. En aio selittää sinulle mikä on pino, sillä
  3399. assyoppaista löytyy tuokin tieto. Muistiasi virkistääkseni mainitsen
  3400. kuitenkin, että tulee muistaa pinon kasvavan alaspäin, eli jos haluat
  3401. varata pinosta 16 tavua niin sinun tulee vähentää esp:stä (extended
  3402. stack pointer) 16 tavua, ei lisätä! Palautus taas hoituu lisäämällä.
  3403.  
  3404. Eli hieman tietoa siitä miten C-kielinen ohjelma kutsuu funktiota ja
  3405. mitä se tekee sinun palattuasi. Eli kutsuessaan funktiota C-kielinen
  3406. ohjelma ensin pushaa parametrit pinoon lähtien parametrilistan
  3407. oikeasta laidasta päätyen lopulta ensimmäiseen parametriin ja sitten
  3408. se heittää ebp:nsä pinoon, kopioi ebp:n esp:hen ja lisää siihen itse
  3409. käyttämänsä muistin määrän (eli itseasiassa vain varmistaa että esp
  3410. osoittaa pinon päälle) ja kutsuu funktiota käyttäen call-komentoa,
  3411. joka vielä kaiken huippuna heittää senhetkisen eip:n (extended
  3412. instruction pointer) pinoon.
  3413.  
  3414. Huomaamme, että kun suoritus alkaa omasta funktiostamme on asioiden
  3415. laita seuraava:
  3416.  
  3417. Pino sisältää indeksissä 0 pinon huipun, eli tällä hetkellä kutsuneen
  3418. ohjelman eip:n. Sen jälkeen on ensimmäinen parametri, sitten toinen
  3419. parametri jne.. Mutta koska meidän täytyy aluksi tallentaa ebp pinon
  3420. päälle pushaamalla se huipulle, jolloin tiedämme, että parametrit ovat
  3421. kahden kaksoissanan (ebp ja eip), eli 8 tavun päässä. Tässä funktion
  3422. tarvitsema alustuskoodi:
  3423.  
  3424. push ebp
  3425. mov ebp, esp
  3426.  
  3427. Lisäksi on mahdollista varata pinosta muistia haluttu määrä
  3428. vähentämällä esp:tä,jolloin siihen jää aukko jonka alussa ebp
  3429. on. Muista kuitenkin vapauttaa muisti korottamalla esp:tä. Muista
  3430. lisäksi, että koska pino menee alaspäin, niin varattu muisti sijaitsee
  3431. myös esp:stä alaspäin, eli negatiivisissä offseteissa.
  3432.  
  3433. Sen jälkeen vain osoitellaan parametrejä. Ensimmäinen parametri on
  3434. siis nyt kohdassa ebp+8 (koska kopioimme ebp:hen esp:n, jossa pino
  3435. oli), ja parametrit seuraavat järjestyksessä 4 tavun välein
  3436. riippumatta parametrin koosta, DJGPP näet sijoittelee myös nuo
  3437. mahdollisimman hyvin, toisin kuin aiemmin luulin.
  3438.  
  3439. Koko roska on itseasiassa hemmetin vaikea ymmärtää ja olen tunnin ajan
  3440. loikkinut ympäri kovalevyäni etsimässä tarkennuksia pinon toimintaan
  3441. ja miten C-funktiota itse asiassa kutsutaan, sillä en ole koskaan
  3442. ottanut viimeisen päälle selvää kääntäjän sielunelämästä.
  3443.  
  3444. Piirrän nyt pikkaisen kaavion siitä mitä tietääkseni muistista löytyy
  3445. sen jälkeen, kun funktiota void func(short,long) on kutsuttu, ebp on
  3446. pushattu ja esp siirretty siihen ja pinosta varattu muistia 2 tavua:
  3447.  
  3448. C B A
  3449. ----------------------------------------------------------------
  3450. RR RR RR MM MM BP BP BP BP IP IP IP IP 11 11 -- -- 22 22 22 22
  3451. ----------------------------------------------------------------
  3452.  
  3453. A) Ohjelmaan tullessa ESP osoittaa tähän
  3454. B) Kun EBP on pushattu niin ESP osoittaa tähän, samoin EBP kun ESP on
  3455. ensin siirretty myös EBP:hen. Huomaa EBP:n ja EIP:n sijainti
  3456. kohdasta B nähden ja parametrin 1 sijainti offsetissa 8 (viivat
  3457. ovat käyttämättömiä palasia), sekä
  3458. parametrin 2 sijainti offsetissa 10 (parametrin 1 koko on short,
  3459. eli 2 tavua!)
  3460. C) Kun ESP:tä vähennetään kahdella jotta pinosta saadaan ohjelmalle 2
  3461. tavua muistia on meillä nyt kaksi tavua muistia käytössä alkaen
  3462. offsetista EBP-2. ESP osoittaa tämän muistin alkuun, mutta
  3463. pushailun sattuessa se lähtee vaeltelemaan yhä kauemmas vasemmalle.
  3464.  
  3465. Palautuksessa poppaillaan kaikki, jolloin ESP on taas kohdassa C. Sen
  3466. jälkeen vapautetaan pino vähentämällä ESP:tä kahdella, jolloin ESP ja
  3467. EBP ovat jälleen samoja, eli kohdassa B molemmat. Nyt vielä popataan
  3468. EBP, jolloin EBP on alkuperäisessä tilassaan, samoin kuin ESP, joka
  3469. osoittaa EIP:n kohdalle. Nyt vain ret, joka ottaa EIP:n pinosta ja
  3470. palaa tähän osoitteeseen.
  3471.  
  3472. JES! TEIN SEN! (anteeksi tunteenpurkaus mutten uskonut saavani tätä
  3473. itsekään selville ilman kenenkään apua ;)
  3474.  
  3475. Huomaa, että on aina kutsuvan ohjelman vastuulla pitää rekistereistään
  3476. huolta ja puhdistaa parametrit pinosta, jotka sinne on pitänyt
  3477. pushailla ennen ohjelman kutsua (niitä ohjelma ei palauta).
  3478.  
  3479. Tässä nyt tämä lopullinen assyosuus, joka pitää olla alussa ja
  3480. lopussa:
  3481.  
  3482. push ebp
  3483. mov ebp, esp
  3484. sub esp, <pinon koko>
  3485.  
  3486. <koodia>
  3487.  
  3488. add esp, <pinon koko>
  3489. pop ebp
  3490. ret
  3491.  
  3492. Toisen C-funktion kutsu taas onnistuu seuraavasti, otetaan esimerkkinä
  3493. vaikka foo(int,short,char,int):
  3494.  
  3495. push <int>
  3496. push <char>
  3497. push <short>
  3498. push <int>
  3499. call _foo
  3500. add esp, 11
  3501.  
  3502. Nuo <int>-hommat siis tarkoittavat oikeankokoisia rekisterejä tai
  3503. muistialueita. Huomaa myös lopussa esp:n palautus korottamalla sitä
  3504. parametrien yhteenlasketun koon verran. Huomaa myös, että C lisää
  3505. assykoodiin aina yhden alaviivan lisää, eli omien rutiiniesi
  3506. funktionimien edessä pitää ASM-tiedostossa olla aina yksi alaviiva
  3507. enemmän kuin mitä C-kielisessä. Myös C-kirjaston funktioita kutsuessa
  3508. pitää muistaa, eli _printf, _puts jne.. Funktioille joiden nimissä on
  3509. C:lläkin yksi tai useampia alaviivoja suoritetaan vain yhden alaviivan
  3510. eteenlisäys.
  3511.  
  3512. No niin, nyt menee kaikki muu funktioissa, mutta vielä palautus ja
  3513. structit sekä reaaliluvut. No tässä kaikki vähä mitä minä siitä
  3514. tiedän:
  3515.  
  3516. Pointtereiden ja dword (4 tavua siis) kokoisten kokonaislukujen
  3517. palautus EAX:ssä. Sanojen (2 tavua, word) palautus AX:ssä ja tavujen
  3518. palautus AL:ssä. Reaaliluvut matikkarekisterissä ST[0]. Structeista
  3519. minulla ei ole aavistusta, sillä olen käyttänyt helpompaa ja yleensä
  3520. hyödyllisempää tapaa välittää ne vain structin osoitteina.
  3521.  
  3522. Reaaliluvut annetaan parametreinä tietääkseni ihan samoin kuin muutkin
  3523. parametrit.
  3524.  
  3525. No mutta. Kaikki tietävät nyt miten varata muistia, kutsua funktioita,
  3526. palauttaa tietoja, käyttää parametreja. Mutta tärkein puuttuu, sillä
  3527. kukaan ei osaa tehdä tiedostoja jotka voisi linkata DJGPP-ohjelman
  3528. mukaan. Siispä töihin!
  3529.  
  3530. Jotta objektitiedoston voisi linkata mukaan DJGPP-ohjelmaan täyty sen
  3531. olla oikeaa formaattia. DJGPP:n hyväksymä formaatti tunnetaan nimellä
  3532. COFF (ei kaljaa!), eli common object file format. Ainoat
  3533. käyttämistäni assembler-kääntäjostä jotka tuota tukevat ovat as ja
  3534. NASM. As on GNU assembler ja sisältää TODELLA kryptisen näköistä AT&T
  3535. assembleria kääntävän yksikön. Mutta kerron jo etukäteen, että
  3536. AT&T-formaatti, jota DJGPP käyttää itse sen Unix-taustan takia on
  3537. aivan toisen näköistä kuin Intel-syntaksin assy, joten suosittelen,
  3538. että ette käytä sitä (halukkaat imuroivat tiedoston DJTUT255.ZIP)!
  3539.  
  3540. Paljon parempi kääntäjä on nimeltään Netwide Assembler, lyhyesti NASM,
  3541. jonka löytää ainakin MBnetistä ja tietenkin Internetistä. Nimi on
  3542. NASM094B.ZIP, mutta voi kyllä olla että uudempiakin on
  3543. ilmestnyt. Jokatapauksessa kääntäjä on aivan loistava ja sen käyttökin
  3544. on suhteellisen yksinkertaista. Kaikkein parhaiten sen käytön oppii
  3545. lukemalla NASM.DOC läpi ja tutkailemalla esimerkkikoodeja (etenkin
  3546. AOUTTEST.ASM!) hakemistosta TEST. Mutta niille jotka eivät mielellään
  3547. lue englantia on ihan pikkuinen esimerkkisorsa, jolla pääsee nyt
  3548. ainakin alkuun siihen asti, että kunnon sanakirja tai tulkkaava kaveri
  3549. löytyy:
  3550.  
  3551. TEST.ASM:
  3552.  
  3553. BITS 32
  3554.  
  3555. EXTERN _cfunktio
  3556. EXTERN _cmuuttuja
  3557. GLOBAL _asmmuuttuja
  3558. GLOBAL _asmfunktio
  3559.  
  3560. SECTION .text
  3561.  
  3562. ; int asmfunktio(int)
  3563.  
  3564. _asmfunktio:
  3565. push ebp
  3566. mov ebp, esp
  3567.  
  3568. mov eax, [ebp+8]
  3569. add [_asmmuuttuja], eax
  3570.  
  3571. push eax
  3572. call _cfunktio
  3573. add esp, 4
  3574.  
  3575. mov eax, [_asmmuuttuja]
  3576. pop ebp
  3577. ret
  3578.  
  3579. SECTION .data
  3580.  
  3581. _asmmuuttuja DD 0
  3582.  
  3583.  
  3584. TEST.H
  3585.  
  3586. extern int asmfunktio(int);
  3587.  
  3588. void cfunktio(int);
  3589.  
  3590. int cmuuttuja;
  3591.  
  3592.  
  3593. TEST.C
  3594.  
  3595. #include <stdio.h>
  3596.  
  3597. void cfunktio(int luku) {
  3598. puts("kutsuttiin C-funktiota parametrilla %d\n", luku);
  3599. }
  3600.  
  3601. int main() {
  3602. printf("asmfunktio(10) palautti arvon %d\n", asmfunktio(10));
  3603. printf("asmfunktio(20) palautti arvon %d\n", asmfunktio(20));
  3604. printf("asmfunktio(5) palautti arvon %d\n", asmfunktio(5));
  3605. printf("asmfunktio(2) palautti arvon %d\n", asmfunktio(2));
  3606.  
  3607. return 0;
  3608. }
  3609.  
  3610. H-tiedoston ja C-tiedoston varmaan ymmärrätte, mutta selvennyksenä
  3611. vielä assysuudesta, että ensin asetetaan NASM 32-bittiseen
  3612. koodinkääntötilaan, sitten määritellään ulkoiset muuttujat _cmuuttuja
  3613. (kaksoisasna) ja _cfunktio (kaksoissana sisältäen rutiinin
  3614. osoitteen). Sitten koodisegmentissä (.text) on _asmfunktio, joka tekee
  3615. kuten aiemmin neuvottiin, eli tallettaa ebp:n ja kopioi esp:n
  3616. ebp:hen. Sen jälkeen se korottaa _asmmuuttuja -muuttujaa parametrillä
  3617. ja kutsuu vielä _cfunktio -funktiota parametrillä palauttaen lopuksi
  3618. _asmmuttuja:n arvon. Datasegmentissä on varattu _asmmuuttuja
  3619. -muuttujalle tilaa kaksoissanan verran ja alustettu se nollaksi.
  3620.  
  3621. Sitten vain tutkimaan antaako ohjelma oikean tulosteen. En minäkään
  3622. tiedä mutta menen katsomaan. =) Toimi ainakin minulla. Jaa että se
  3623. kääntäminen NASM:illa?-) No se on tietenkin komennolla:
  3624.  
  3625. nasm -o jokin.o -f coff jokin.asm
  3626.  
  3627. No niin, nyt sinun pitäisi hallita assemblerin käyttö C:n kanssa
  3628. jotakuinkin välttäen ja nasmilla kääntelykin pitäisi onnistua, sekä
  3629. nasm-tiedostojen tekokin ainakin rajoitetusti. Pahoittelen että
  3630. tarkempia ohjeita ei annettu, sillä ne olisivat olleet niin pitkät,
  3631. että katsoin oppimisen onnistuvan ilman tarkempia ohjeita. Mutta jos
  3632. kuitenkin tuntuu, että tämän kappaleen taso leijui kilometritolkulla
  3633. tajuntasi yläpuolella niin pyydän ottamaan yhteyttä, sillä en
  3634. ihmettele vaikka tämä olisikin vaikein osa tähän asti ja kaikki apu
  3635. sen suhteen miten tätä pitäisi parantaa on tarpeen.
  3636.  
  3637. Mutta toisaalta jos et assyä muuten osaa etkä ole kaikkea
  3638. dokumentaatiota kaivanut esiin mitä löydät voi olla että asia on
  3639. paljon selkeämpi jo muutaman päivän päästä. Jos ei kuitenkaan helpota
  3640. niin heitä viestiä tännekin päin. Mutta nyt jatkan taas kohti uutta
  3641. tuntematonta.
  3642.  
  3643. Phew, tämähän käy työstä kun koko päivän kirjoittaa!
  3644.  
  3645.  
  3646. 7.2 PIT - aikaa ja purkkaa
  3647. --------------------------
  3648.  
  3649. Hiphei taipaleemme jatkuu edelleen, vaikka kello osoitteleekin
  3650. kirjoitushetkellä melkein kahtatoista. Myös ihmeellisestä tekstistä
  3651. voinee sen päätellä etten ole välttämättä aivan parhaimmillani ja
  3652. terävillimmilläni (villimmilläni?) tähän aikaan päivästä. No, tehän
  3653. siitä vain kärsitte, en minä, joten jatkakaamme! ;)
  3654.  
  3655. Eli ihmeellinen lyhenne PIT? Mistä se tulee? No tietenkin sanoista
  3656. Programmable Interval Timer, eli ohjelmoitava keskeytysajastin. Tämä
  3657. on tällainen hauska piiri PC:llä, joka kykenee generoimaan ties millä
  3658. tavalla keskeytyksiä. Kiinnostavaa ja tarkkaa tietoa löytyy PCGPE:stä
  3659. (PCGPE10.ZIP) tiedostosta PIT.TXT, mutta me keskitymme vain
  3660. olennaiseen, nimittäin systeemin omaan kelloon, keskeytykseen
  3661. 8. Kerron kuitenkin hieman millä tavalla piiri laskee milloin pitää
  3662. generoida keskeytys 8, ennenkuin pääsemme hauskaan tavaraan (eli
  3663. esimerkkikoodiin ;).
  3664.  
  3665. Eli PIT tikittää 1193181Hz:n taajuudella, eli suomeksi 1193181 kertaa
  3666. sekunnissa. Joka kerta se esim. vähentää kanavan 0 laskuria yhdellä ja
  3667. jos se on 0 niin se generoi keskeytyksen ja asettaa uudelleen laskurin
  3668. haluttuun arvoon ja lähtee laskemaan alaspäin. Laskuri on kahden
  3669. tavun, eli yhden sanan mittainen ja kykenee näinollen vastaanottamaan
  3670. luvun väliltä 0-65335. Mutta erikoisuutena on se, että jos laskurin
  3671. alustusarvo 0 ei tarkoitakaan että keskeytystä kutsutaan jatkuvalla
  3672. syötöllä, vaan että sitä kutsutaan 65536:n "tikahduksen" (ei näin
  3673. myöhään oikein sanat muistu mieleen) jälkeen. Normaali systeemikello
  3674. on asetettu tähän kutsuntatiheyteen, eli sitä kutsutaan
  3675. 1193181/65536=n. 18.2 kertaa sekunnissa.
  3676.  
  3677. Jos siis koukutamme tämän keskeytyksen kuten olemme aiemmin tehneet
  3678. näppiskeskeytyksellekin tulee alkuperäistä kutsua tähän tahtiin, sillä
  3679. toisin kuin näppiskeskeytys, kellokeskeytys on huomattavasti
  3680. tärkeämmässä asemassa eikä sitä voi hypätä noin vain yli (ainakin
  3681. DOS:in kello pysähtyy koko ajaksi =). Jos me siis koukutamme
  3682. keskeytyksen tulee sen olla tämäntyylinen:
  3683.  
  3684. funktio kellokeskeytys
  3685.  
  3686. <tee jotain>
  3687.  
  3688. laskuri = laskuri + tikkejä_per_kutsu;
  3689.  
  3690. jos (laskuri on suurempi tai yhtäsuuri kuin 65536)
  3691.  
  3692. laskuri = laskuri - 65536
  3693.  
  3694. kutsu_vanhaa();
  3695.  
  3696. muuten
  3697.  
  3698. kuittaa_keskeytys();
  3699.  
  3700. end jos
  3701.  
  3702. end funktio
  3703.  
  3704. Tikkejä_per_kutsu on siis uusi määrä tarvittavia tikkejä jokaisen
  3705. keskeytyksen välissä. Jos vaikka haluaisimme että omaa kelloamme
  3706. kutsutaan 100 kertaa sekunnissa, niin meidän pitäisi asettaa PIT:ille
  3707. laskurin alustusluvuksi 1193181 / 100 = n. 11931. Sitten vain joka
  3708. kutsulla lisätään laskuria sen mukaan montako tikkiä on kulunut
  3709. edellisestä vanhan kellon kutsusta ja jos se on alkuperäinen 65536 tai
  3710. suurempi, niin vähennetään siitä tämä luku ja kutsutaan vanhaa
  3711. keskeytystä. Jos se on vielä alle 65536, niin lähetetään tuttuun
  3712. tapaan tavu 0x20 porttiin 0x20.
  3713.  
  3714. Kellokeskeytyksen <tee jotain> -kohdan voi ja kannattaakin yleensä
  3715. korvata laskurilla, jota korotetaan jatkuvasti. Tätä voi käyttää
  3716. vaikka ajanottoon tai muuhun hyödylliseen, kuten näemme myöhemmin.
  3717. Kaikki tuntuisi olevan toteutusta vailla - MUTTA.
  3718.  
  3719. Ongelmaksi muodostuu vanhan kutsuminen. Kun keskeytys generoidaan niin
  3720. senhetkinen koodisegmentti ja -osoitin (eli CS+EIP) kipataan pinoon,
  3721. samoin kuin liput ja kutsutaan käsittelijää. Vastaavasti iret
  3722. keskeytyskäsittelijän lopussa ne otetaan sieltä pois ja niiden avulla
  3723. palataan jatkamaan keskeytynyttä ohjelman suoritusta samasta tilasta.
  3724.  
  3725. Mutta kun kutsumme vanhaakin käsittelijää välissä, niin pinosta pois
  3726. otto tapahtuu kahdesti, mikä eteen? Selvää on, että ohjelma kaatuu jos
  3727. ei tätä ongelmaa korjata. Mutta hätiin saapuu Kaj Björklund uljaalla
  3728. inline assembler-ratsullaan pelastaen meidät pulasta! Meidän tarvitsee
  3729. vain kellokeskeytystä asetettaessa ottaa talteen alkup. handlerin
  3730. koodiselektori ja offsetti sekä tallentaa ne 64-bittiseen muuttujaan
  3731. (long long). Sitten vain käytetään seuraavanlaista inline-pätkää:
  3732.  
  3733. __asm__ __volatile(
  3734. "pushfl
  3735. lcall %0
  3736. "
  3737. :
  3738. : "g" (oldhandler));
  3739.  
  3740. Edellinen koodinpätkä tekee samat temput ennen funktion kutsumista
  3741. kuin mitä sanoin normaalisti tehtävän, eli heittää liput pinoon ja
  3742. lcall pistää sinne CS:n ja EIP:nkin, joten iret vanhassa
  3743. timer-rutiinissa palaakin omaan koodiimme ja kaikki toimii hienosti,
  3744. kun if...else huolehtii siitä ettei outata kahdesti porttiin 0x20!
  3745. Hienoa! Nyt meillä onkin oikeastaan kaikki tarvittava tieto handlerin
  3746. tekoon:
  3747.  
  3748. #include <dos.h>
  3749. #include <dpmi.h>
  3750. #include <go32.h>
  3751. #include <stdlib.h>
  3752. #include <stdio.h>
  3753. #include <sys/nearptr.h>
  3754.  
  3755. _go32_dpmi_seginfo info;
  3756. _go32_dpmi_seginfo original;
  3757.  
  3758. volatile long long OldTimerHandler;
  3759. volatile int TicksPerCall, OriginalTicks, Counter;
  3760.  
  3761. static volatile void TimerStart() {}
  3762.  
  3763. void TimerHandler() {
  3764. Counter++;
  3765. OriginalTicks+=TicksPerCall;
  3766. if(OriginalTicks>=65536) {
  3767. OriginalTicks-=65536;
  3768. __asm__ __volatile__ ("
  3769. pushfl
  3770. lcall %0
  3771. "
  3772. :
  3773. : "g" (OldTimerHandler));
  3774. } else {
  3775. outportb(0x20, 0x20);
  3776. }
  3777. }
  3778.  
  3779. static volatile void TimerEnd() {}
  3780.  
  3781. void SetTimerRate(unsigned short ticks) {
  3782. outportb(0x43, 0x34);
  3783. outportb(0x40, ( ticks & 0x00FF ) );
  3784. outportb(0x40, ( ( ticks >> 8 ) & 0x00FF ) );
  3785. }
  3786.  
  3787. void InitTimer(int tickspersecond) {
  3788. __dpmi_meminfo lock;
  3789.  
  3790. lock.address = __djgpp_base_address + (unsigned) &TimerStart;
  3791. lock.size = ((unsigned)&TimerEnd - (unsigned)&TimerStart);
  3792. __dpmi_lock_linear_region(&lock);
  3793.  
  3794. Counter=0;
  3795. OriginalTicks=0;
  3796. TicksPerCall=1193181/((unsigned short)tickspersecond);
  3797.  
  3798. disable();
  3799.  
  3800. _go32_dpmi_get_protected_mode_interrupt_vector(0x0008, &original);
  3801.  
  3802. OldTimerHandler=((unsigned long long)original.pm_offset) +
  3803. (((unsigned long long)original.pm_selector)<<32);
  3804.  
  3805. info.pm_offset=(unsigned long int)TimerHandler;
  3806. info.pm_selector=_my_cs();
  3807. _go32_dpmi_allocate_iret_wrapper(&info);
  3808.  
  3809. SetTimerRate(TicksPerCall);
  3810. _go32_dpmi_set_protected_mode_interrupt_vector(0x0008, &info);
  3811. enable();
  3812. }
  3813.  
  3814. void DeinitTimer() {
  3815. disable();
  3816. SetTimerRate(0);
  3817. _go32_dpmi_set_protected_mode_interrupt_vector(0x0008, &original);
  3818. enable();
  3819. }
  3820.  
  3821. Muu mennee ihan hyvin tajunnan perälle asti, mutta InitTimer-rutiinin
  3822. alku voi hyvinkin tuottaa ihmettelyä, samoin kuin kaksi tyhjää
  3823. funktiota kummallakin puolella TimerHandler-rutiinia. No minäpäs
  3824. kerron mistä on kyse. Kyse on muistin lukitsemisesta, kuten ehkä
  3825. komentojen nimistä voi päätellä. Normaalisti DPMI-palvelin (jos se
  3826. siihen kykenee) voi swapata levylle koodia ja dataa jos siltä tuntuu,
  3827. mutta kun muistialue lukitaan niin sitä ei swappaillakaan
  3828. minnekään. Älä huoli jos epäilet ettet olisi osannut noita tehdä itse,
  3829. sillä minäkin varas- käytin apunani libc:n lähdekoodeista löytyvää
  3830. koodinpätkää ja Kaj Björklundin esimerkkikoodia.
  3831.  
  3832. No nyt vain sitten esimerkkiohjelma, joka näyttää hieman mihin
  3833. timer-rutiini pystyy:
  3834.  
  3835. #include <stdio.h>
  3836. #include <conio.h>
  3837.  
  3838. extern void InitTimer(int);
  3839. extern void DeinitTimer();
  3840. extern volatile int Counter;
  3841.  
  3842. int main() {
  3843. InitTimer(100);
  3844. while(!kbhit()) {
  3845. printf("Counter=%d\r", Counter);
  3846. fflush(stdout);
  3847. }
  3848. getch();
  3849. DeinitTimer();
  3850.  
  3851. return 0;
  3852. }
  3853.  
  3854. Näin. Seuraavassa luvussa esittelen ennen nukkumaanmenoani (ellei joku
  3855. tule ajamaan minua unten maille ennen kuin ehdin kirjoittaa seuraavan
  3856. luvun =) kiinnostavaa käyttöäkin tälle, joten pysykää kanavalla!
  3857.  
  3858.  
  3859. 7.3 Miten peli toimii yhtä nopeasti kaikilla koneilla
  3860. -----------------------------------------------------
  3861.  
  3862. No tähän on useita tapoja, mutta lähes kaikissa tarvitaan ajanottoa ja
  3863. näinollen edellisen luvun ajastinrutiini pohjusti varsin mukavasti
  3864. tämän luvun aihetta (josta tulee luultavasti todella lyhyt). Idea on
  3865. siis se, että jokaisella koneella peli pyörisi yhtä nopeasti. No
  3866. helpommin sanottu kuin tehty.
  3867.  
  3868. Varmastikin käytetyin ja toimivin on menetelmä, jota kutsutaan
  3869. hienosti termillä "frameskip", eli kuvien yli hyppiminen. Ilkka
  3870. Pelkonen käytti siitä brutaalia termiä harppominen, mutta koska
  3871. minulle tulee siitä mieleen vain pitkäjalkaiset laihat
  3872. kumisaapasjalkaiset miehet niin käytän englanninkielistä termiä
  3873. (Ilkka, kyllä minä käyttäisin edes "loikkimista", siitä tulee edes
  3874. kengurut mieleen ;).
  3875.  
  3876. Eli idea on, että kaikki muu tehdään joka framelle, mutta piirtäminen
  3877. jätetään väliin jos ollaan "aikataulusta jäljessä". Niinpä kun meillä
  3878. on nyt ajastinrutiini voimme käyttää tällaista systeemiä:
  3879.  
  3880. päälooppi
  3881.  
  3882. käsitteleframe
  3883.  
  3884. vähennä timerlaskuria
  3885.  
  3886. jos timerlaskuri = 0
  3887.  
  3888. piirrä
  3889.  
  3890. tai jos timerlaskuri < 0
  3891.  
  3892. odota kunnes timerlaskuri >= 0
  3893.  
  3894. end päälooppi
  3895.  
  3896.  
  3897. Eli itseasiassa edellisen luvun laskuria vähennetään itse pelissä koko
  3898. ajan pyrkien pitämään se nollassa, mutta jos piirron aikana on ehtinyt
  3899. mennä useampi frame sivu suun niin käsitellään framea ja vähennetään
  3900. timerlaskuria niin kauan että ollaan taas saatu "kiinni" oikea tahti
  3901. ja voidaan päivittää seuraava ruutu. Myös toinen mahdollisuus, eli
  3902. "ylinopea" kone täytyy huomioida odottelemalla jos pyyhkäistään jo
  3903. aikataulusta ohitse.
  3904.  
  3905. Valitettavasti tällä alle 18.2:n framen nopeudet eivät toimi, joten
  3906. sellaisiin tapauksiin pitää kehitellä erikoisratkaisuja (fixed-point
  3907. -laskuri esimerkiksi, joka korottuu vain puolella joka vuoro tms.).
  3908.  
  3909. On myös muita mahdollisuuksia toteuttaa frameskip, kuten siirtämällä
  3910. käsitteleframe -funktio suoraan timeriin, joka ei tosin mielestäni ole
  3911. hyvä ratkaisu, mutta joka toisaalta on tietyllä tavalla selvä
  3912. laskurien jäädessä pois. Mutta mitä teetkään kun kesken ruudulle
  3913. piirron päivitetään aluksien paikkaa? Ei ole enää kenestäkään,
  3914. (varsinkaan pelaajasta) kivaa siinä vaiheessa.
  3915.  
  3916. Toinen paljon toimivampi vaihtoehto on käyttää kulunutta aikaa
  3917. ikäänkuin kertoimena tehtävissä. Eli jos vaikka joka vuorolla pitää
  3918. siirtää spriteä 1 eteenpäin, niin siirretään joka framella spriteä
  3919. 1*kulunut aika verran eteenpäin. Tämä valitettavasti vaatii paljon
  3920. tiheämmän ajastimen kutsun kuin sellaiset 70 kertaa sekunnissa
  3921. toimiakseen hyvin ja lisäksi fixed-point -matikka on yleensä aika
  3922. välttämätön tämänkaltaisessa toteutuksessa.
  3923.  
  3924. Mutta, aihe ei ole vaikea ja varmasti osaat päättää minkälaisen
  3925. toteutuksen teet itse peliisi. Minä häivyn nukkumaan ja jätän sinut
  3926. oman onnesi nojaan. Öitä!
  3927.  
  3928.  
  3929. 7.4 Yleistä asiaa pelin levityksestä
  3930. ------------------------------------
  3931.  
  3932. Tässä luvussa olisi tarkoitus hieman valaista pelinteon toista puolta,
  3933. eli sen joka ei sisällä ohjelmointia, vaan dokumentaation kirjoitusta,
  3934. grafiikan piirtoa, musiikkia, pelin levitystä ja ties mitä. Taidan
  3935. kyllä olla aika turha tästä kauheasti puhumaan, sillä valmiiksi emme
  3936. ole saaneet kuin vasta yhden pelin ja toinen on kovasti tekeillä, kunhan
  3937. laiska kooderimme (minä) löytäisi jostain aikaa kirjoittaa koodia.
  3938.  
  3939. Ensimmäinen homma pelin teossa olisi varmaan päättää minkä tyyppisen
  3940. pelin tekee. Parasta on valita sellainen pelityyppi, jonka uskoo
  3941. pystyvänsä toteuttamaan. Ensimmäisenä projektina kannattaa varmaan
  3942. tehdä jokin yksinkertainen kaksiulotteisen toimintapelin, vaikkapa
  3943. sitten sen iänikuisen matopelin. Sitten vasta pikkuhiljaa kun kokemusta
  3944. kertyy niin kannattaa jatkaa vaikeammilla projekteilla.
  3945.  
  3946. Nimi lienee toinen huolenaihe kun pelityyppi ja sen pääpiirteet ovat
  3947. tiedossa. Älä mielellään nimeä ohjelmaa samannimiseksi kuin jokin
  3948. olemassaoleva tuote. Esimerkiksi matopeli, jonka nimi on Windows voi
  3949. aiheuttaa lievää närää jos se leviää laajemmalle (tosin yleensä
  3950. ensimmäinen peliprojekti ei leviä kauhean laajalle, mutta mistä sitä
  3951. koskaan kuitenkaan tietää).
  3952.  
  3953. Sen jälkeen olisi varmaan parasta alkaa pelin teko. Sen lisäksi, että
  3954. pelin engine täytyy saada kuntoon olisi myös hyvä tehdä siihen
  3955. grafiikkaa. Musiikki ja ääniefektitkin olisivat varsin mukava idea,
  3956. jos kunnianhimoa löytyy tarpeeksi. Levityksessä on useita
  3957. äänikirjastoja, jotka tarjoavat enemmän tai vähemmän toimivan
  3958. ratkaisun ääniongelmiin. Enginen lisenssit kannattaa varmaan kuitenkin
  3959. tarkistaa hieman tavallista tarkemmin, kun joidenkin mukana tuppaa
  3960. olevan varsin kirjavia käyttöehtoja (suurin osa kieltää kaupallisen
  3961. käytön).
  3962.  
  3963. Jos grafiikka tai musiikki ei itseltä suju on tietenkin mahdollista
  3964. hankkia joku kaveri tai vaikka aivan tuntematonkin mukaan projektiin
  3965. tekemään grafiikkaa ja säveltämään musiikkia. Pelin ollessa sitten
  3966. muiden osien osalta kasassa alkaakin kannattaa miettimään levitystä
  3967. ja dokumentointia, jotka ovat muun kokonaisuuden kanssa myös tärkeitä.
  3968.  
  3969. Normaalisti käytettyjä levitystyyppejä on kolme, PD, FW ja SW (ja
  3970. täysin kaupallinen levitys, mutta tätä tutoriaalia ei kyllä sellaisen
  3971. tekijöille ole tarkoitettu). PD (Public Domain) tarkoittaa, että
  3972. luovut kaikista oikeuksistasi ohjelman suhteen, eli muut saavat
  3973. tehdä ohjelmallasi mitä ikinä keksivät, vaihtaa nimen ja levittää tai
  3974. myydä miten haluavat.
  3975.  
  3976. Hieman rajoitetumpi muoto on FW (FreeWare), jossa pidät
  3977. tekijänoikeutesi tuotokseesi ja saat itse sanella ehdot miten sitä
  3978. levitetään. FreeWare -tuotteista ei kuitenkaan saa periä mitään (sillä
  3979. se on termi jota käytetään ilmaisesta tuotteesta). SW (ShareWare) taas
  3980. on levitystyyppi, jossa käyttäjä saa kokeilla ohjelmaa tietyn ajan ja
  3981. sitten vasta päättää mitä tekee ohjelman kanssa.
  3982.  
  3983. Ensimmäinen tehtävä päättäessäsi minkätyyppinen ohjelmastasi tulee
  3984. on miettiä mihin ohjelmasi pystyy. Jos tuotos on ensimmäinen pelisi
  3985. ja harjoitustyö voi hyvinkin olla järkevää antaa koko ohjelma
  3986. lähdekoodeineen muiden levitykseen. Tällaiset julkistukset ovat aina
  3987. harvinaisia ja ohjelmasi ehkä leviää tällä tavoin paremmin. Jos
  3988. olet kuitenkin sitä mieltä, että et halua muiden käyttävän peliäsi
  3989. miten haluavat kannattanee levitysmuodoksi laittaa FW.
  3990.  
  3991. Sharewarena tuotetta kannattaa levittää vasta jos todella olet panos-
  3992. tanut siihen vain siinä mielessä, että saat siitä rahaa tai jos olet
  3993. sitä mieltä, että ohjelmasi on merkittävästi parempi kuin kilpailevat,
  3994. kaupalliset tai SW-tuotteet. Shareware-ohjelmaksi ei kuitenkaan
  3995. kannata laittaa sitä ensimmäistä matopeliä tai jotain bugista
  3996. viritelmää, jos haluaa säilyttää maineensa. =)
  3997.  
  3998. Sharewareakin on kolmea tyyppiä, nimittäin tiukka aikarajoitettu
  3999. shareware, aikarajoitettu shareware ja rajoittamaton shareware. Tiukka
  4000. aikarajoitettu SW on tyypillisesti kuten kaikki mahdolliset Windows-
  4001. viritellyt HTML-editorit, joissa 99% on jokin viritelmä, joka terminoi
  4002. ohjelman ennemmin tai myöhemmin (yleensä ennemmin). Löysemmästi
  4003. aikarajoitetut ohjelmat ovat siitä kiitollisia, että niiden toimivuus
  4004. säilyy aikarajan jälkeenkin. Rajoittamattomat ovat sitten tietenkin
  4005. ne kaikkein mukavimmat ja niiden toimintaperiaate ei enää yleensä
  4006. olekaan antaa käyttäjän kokeilla ohjelmaa, vaan rahaa pyydetään siitä,
  4007. että käyttäjä ottaa käyttöönsä kaikki ohjelman toiminnot.
  4008.  
  4009. Jos peli on aivan ehdottoman huippu niin voi yrittää levittää sitä
  4010. täysin kaupallisesti, mutta se vaatiikin sitten yleensä aika lailla
  4011. kokemusta ja tietenkin hieman hyvää tuuria.
  4012.  
  4013. Sama minkä tyypin levitykseen sitten päätyy, niin kannattaa varmaan
  4014. kirjoittaa hieman tekstiä, jossa kerrot miten haluat ohjelmaasi
  4015. levitettävän. PD-tyypillä et tarvitse ehkä kuin tekstitiedoston, jossa
  4016. ilmoitat luopuvasi kaikista oikeuksista ja kaikesta vastuusta ohjelman
  4017. suhteen. SW:n ja FW:n kanssa kannattaakin sitten panostaa
  4018. lainopilliseen puoleen hieman enemmän.
  4019.  
  4020. Tärkein on ilmoittaa selvästi pelissä, että tekijänoikeudet kuuluvat
  4021. sinulle tai useammalle henkilölle ja kertoa ehdot joiden rajoissa
  4022. ohjelmaa saa levittää. Tärkein rivi lienee tämänkin dokumentin alusta
  4023. löytyvä:
  4024.  
  4025. Copyright (C) Joonas Pihlajamaa 1997. All rights reserved.
  4026.  
  4027. Tekijänoikeuksien merkki, (C) ei toistu oikein tietokoneella, sillä
  4028. sen pitäisi itseasiassa olla ympyrän keskellä oleva C. Näinollen
  4029. Copyright-teksti alussa voi olla varsin hyödyllinen. Sen jälkeen tulee
  4030. tekijän nimi ja loppuun yleensä vuodet joiden aikana olet tuotteen
  4031. tekijänoikeuksia pitänyt hallussasi (eli käytännössä minä aikana olet
  4032. peliä tehnyt). Jos tekijöitä on useita kannattaa varmaan pelata varman
  4033. päälle ja selittää tarkemmin ketkä henkilöt ovat tehneet mitäkin.
  4034.  
  4035. Sitten vain perään kaikki ehdot, joita haluat peliäsi levitettäessä
  4036. noudatettavan. Suhteellisen kattava vastaava löytynee suomeksi
  4037. tämänkin dokumentin alusta (porsaanrei'istä saa kyllä vapaasti
  4038. ilmoittaa ;), sekä kotisivujeni levitysehdoista, joiden parissa vietin
  4039. runsaasti aikaa pyrkien saada siitä niin vaikean kuin mahdollista.
  4040.  
  4041. Jos noiden tekeminen tuntuu turhalta, niin kannattaa muistaa, että jos
  4042. joskus satut joutumaan kahnauksiin ohjelmasi väärinkäytösten tai sen
  4043. aiheuttamien ongelmien kanssa, niin tuo teksti saattaa olla ainoa
  4044. apusi. Ilman tekstiä on paljon hankalampaa sanoa oikeudessa, ettet ole
  4045. vastuussa ohjelman aiheuttamasta sydämentahdistimen pysähtymisestä,
  4046. toisin kuin jos olisit kirjoittanut ehtoihin, että et ole vastuussa
  4047. moisista vahingoista.
  4048.  
  4049. Mitä sinun tulisi ilmoituksessa mainita olisivat seuraavat:
  4050.  
  4051. 0. Mihin kaikkeen ehdot ilmoituksessa ulottuvat
  4052. 1. Miten ohjelmaa ja sen tiedostoja saa käyttää
  4053. 2. Mistä olet vastuussa
  4054. 3. Mitä tehdä jos ei suostu ehtoihin ja milloin katsotaan käyttäjän
  4055. suostuneen niihin
  4056. 4. Miten ohjelmaa saa levittää
  4057. 5. Missä ohjelmaa saa levittää
  4058.  
  4059. Kun lainopillinen puoli ja itse peli on kunnossa lienee jäljellä vain
  4060. levityspuoli. Se onkin suhteellisen helppoa. Lähetys pariin suosittuun
  4061. purkkiin (MBnettiin =) ja kenties Internetiinkin lisää varmasti
  4062. leviämistä aivan toisin kuin kavereille antaminen. Mainostustakin voi
  4063. harrastaa, mutta kannattanee pitää se kohtuullisissa rajoissa, ettei
  4064. ohjelmasi saa negatiivista julkisuutta häiritsevästä mainonnasta. :)
  4065.  
  4066. Jos ohjelmasi on SW-tuote, niin lienee vielä yksi kohta, nimittäin
  4067. rekisteröinnit ja päivitykset, sekä mahdollisien lisäominaisuuksien
  4068. "vapautus" (enabling, tätä se on kun lukee liikaa englanninkielistä
  4069. materiaalia). Rekisteröimätön versio kannattaa pitää niin paljon
  4070. ominaisuuksia sisältävänä, että siitä todella on jotain iloa, mutta
  4071. pitää niin paljon hyviä ominaisuuksia rekisteröidyssä versiossa, että
  4072. rekisteröimätön käyttäjä näkee saavansa rekisteröintirahoilleen
  4073. vastinetta rekatessaan pelin. Myös mahdolliset ilmaiset/alennetut
  4074. päivitykset tai muut vastaavat etuisuudet tulevaisuudessa voivat
  4075. jonkin verran avittaa, mutta muista, että suurin osa käyttäjistä etsii
  4076. välitöntä hyötyä, eikä paljoa välitä tulevien pelien
  4077. rekisteröintihintojen alentumisista.
  4078.  
  4079. Kannattaa myös harkita millä tavalla hoidat rekisteröinnit. Maksaminen
  4080. pitää tehdä helpoksi (ja mielellään halvaksi), sillä suurin osa
  4081. rekisteröijistä on kuitenkin laiskaa porukkaa ja mahdollisuus
  4082. rekisteröityä tuoliltaan nousematta voi olla hyvinkin suuri
  4083. etu. Maksutapoina kannattaa ainakin huomioida suoran käteisen lisäksi
  4084. pankkisiirrot, jotka ovat viitteiden kanssa varsin näppärä tapa
  4085. rekisteröidä. Myös postiennakko on hyvä tapa, vaikka sillä on
  4086. suhteellisen korkeat kustannukset se on kuitenkin näppärä keino
  4087. varsinkin vähän tyyriimmille ohjelmille (20 markan
  4088. rekisteröintihintaan saman verran lisää voi pelottaa ostajia).
  4089.  
  4090. Rekisteröidyn version lähetykseenkin on useita mahdollisuuksia. Itse
  4091. olen miettinyt näitä ja tässä on muutama, mistä valita, osa helppoja
  4092. toteuttaa ja osa vaikeita:
  4093.  
  4094. 1. Rekisteröintiavain
  4095. + Pieni, näppärä lähettää vaikka sähköpostilla
  4096. - Todella helppo kopioida
  4097. - Helppo murtaa
  4098. 2. Rekisteröity EXE
  4099. + Suhteellisen pieni, mennee suurempiin sähköpostilaatikkoihin
  4100. + Varma, vaikea murtaa
  4101. - Lähes yhtä helppo kopioida
  4102. 3. Rekisteröity versio
  4103. + Helppo toteuttaa
  4104. + Ihan pikkuisen vaikeampi kopioida
  4105. + Varma, vaikea murtaa
  4106. 4. Rekisteröity versio ja avain
  4107. + Varmin menetelmistä, vaikea kopioida, helppo toteuttaa
  4108.  
  4109. Toisaalta kannattaa muistaa, että jos sinä jaksat laittaa sen
  4110. disketeillä postissa ei se ole kenellekään ongelma laittaa
  4111. viidellekymmenelle koneelle ja vaikka valmistaa diskcopyllä rekatusta
  4112. versioista piraattiversioita jatkolevitykseen, eli kopiointi on aina
  4113. aika helppoa. Toisaalta kopiointia haittaa ainakin hieman erillisenä
  4114. annettava avain tai installointiohjelmassa rekisterijöijän nimen ja
  4115. tunnuksen pyytäminen jne.
  4116.  
  4117. Myös voi pitää mielessä, että mitä enemmän turvatoimia sitä hankalampi
  4118. se on rekisteröijälle. Kohtuus kaikessa niin pysyvät rekkaajatkin
  4119. tyytyväisinä.
  4120.  
  4121. Siinä lienevät ne tärkeimmät asiat, joita kannattaa pitää mielessä
  4122. peliä tehdessä. Lisäksi tietenkin löytyy kokonaisia kirjoja pelinteon
  4123. taiteesta ja niiden suunnittelusta, mutta tämän luvun päätarkoitus on
  4124. ollut valaista pelinteon käytännöllisempiä puolia. Nyt tämä
  4125. tutorialisti lähtee lukemaan ruotsin kokeisiin!
  4126.  
  4127.  
  4128. 7.5 Interpolointi ja viivoja
  4129. ----------------------------
  4130.  
  4131. Ilja Bräysy taisi tässä kuukausi sitten patistaa minua neuvomaan miten
  4132. DJGPP:llä piirretään viivoja. No, pääsin pälkähästä lupaamalla
  4133. kirjoittaa siitä jutun sitten Laamatuttiin. No minkä taakseen jättää
  4134. sen edestään löytää, eikä tämäkään kerta näytä olevan mikään poikkeus.
  4135. Kuitenkin tällä kertaa selitän muutakin kuin sen viivanpiirron, nimittäin
  4136. selitän mitä tarkoittaa interpolointi, sekä miten ja mihin sitä voi
  4137. tietokoneella käyttää.
  4138.  
  4139. Eli termimme on interpolointi. Inter voisi latinassa tai jossain muussa
  4140. kielessä hyvinkin tarkoittaa välissä, ainakin interpolointi tarkoittaa
  4141. jotain tähän hyvin liittyvää. Interpolointi on nimittäin sitä, että kun
  4142. tiedossamme on kaksi pistettä, niin voimme "arvata" sinne keskelle ääret-
  4143. tömästi uusia pisteitä, jotka kaikki kuuluvat samalle suoralle. Tämä
  4144. on ns. lineaarista interpolointia, eli interpoloidaan pisteitä samalle
  4145. suoralle. Tällaisesta toimenpiteestä hyvä esimerkki voisi hyvinkin
  4146. olla viivanpiirto, sillä siinähän meillä on kaksi pistettä, ja meidän
  4147. täytyy saada niiden välillä tarpeellinen määrä pisteitä viivan
  4148. esittämiseksi.
  4149.  
  4150. No niin, tiedämme siis mitä on interpolointi. Se on siis pisteiden
  4151. lisäämistä kahden tunnetun pisteen välille. Vaan miten noiden pisteiden
  4152. sijainti sitten pitäisi laskea? No, miettikäämme tilannetta, jossa meillä
  4153. on kaksi pistettä, a ja b, joiden koordinaatit ovat vastaavasti (ax,ay)
  4154. ja (bx,by). Nyt me laskemme näiden välillä yhden pisteen. Ensimmäinen
  4155. tehtävä lienee laskea, kuinka pitkästi meillä on matkaa x- ja y-suunnassa.
  4156. Näitä lukuja nimitetään yleisesti delta-arvoiksi. Ne lasketaan
  4157. seuraavasti:
  4158.  
  4159. delta_x = | bx - ax |
  4160. delta_y = | by - ay |
  4161.  
  4162. Missä merkit "|" tarkoittavat itseisarvoa, siis "| a |" luetaan
  4163. "a:n itseisarvo". C:llä funktio on abs, tai fabs, jos käytämme
  4164. floatteja.
  4165.  
  4166. No niin, tiedämme kuinka kaukana pisteet ovat toisistaan, mutta mitä
  4167. ihmettä sitten oikein teemme tällä uudella, kiinnostavalla tiedolla?
  4168. No jatketaanpas hieman viivanpiirron kehittelyä. Jos haluamme
  4169. katkeamattoman viivan, niin meillä pitää olla yhtä monta pikseliä kuin
  4170. viivan pidemmän akselin pituus on. Eli jos delta_x on suurempi kuin
  4171. delta_y, niin tarvitsemme delta_x:n verran pikseleitä. Tilaanteen
  4172. ollessa päinvastainen on tarvittavien pikselien määrä tietenkin
  4173. vastaavasti delta_y.
  4174.  
  4175. Sitten pidemmälle toteutukseen. Kun nyt tiedämme montako pikseliä
  4176. tarvitsemme ja kummassa suunnassa, niin voimmekin suunnitella
  4177. seuraavanlaisen piirtorakenteen:
  4178.  
  4179. jos delta_x >= delta_y niin
  4180.  
  4181. y = ay
  4182.  
  4183. y_korotus = delta_y / delta_x
  4184.  
  4185. looppaa x välillä ax...bx
  4186.  
  4187. piste ( x, y, väri )
  4188.  
  4189. y = y + y_korotus
  4190.  
  4191. end looppi
  4192.  
  4193. muutoin
  4194.  
  4195. x = ax
  4196.  
  4197. x_korotus = delta_x / delta_y
  4198.  
  4199. looppaa y välillä ay...by
  4200.  
  4201. piste ( x, y, väri )
  4202.  
  4203. x = x + x_korotus
  4204.  
  4205. end looppi
  4206.  
  4207. end jos
  4208.  
  4209. Nyt te tietenkin kysytte: "Mitä tuo tekee?" No, olen ilkeä ja kerron
  4210. teille. Koska meidän täytyy piirtää pidemmän akselin verran
  4211. pikseleitä, niin se tarkoittaa, että piirtosilmukan täytyy korottaa
  4212. pidemmän akselin koordinaattia yhdellä ja lyhemmän jollain pienemmällä
  4213. kuin yhdellä. Jos alkaisimme piirtelemään lyhyemmän akselin mukaan,
  4214. niin viivan toinen akseli harppoisi yli 1 pikselin askelia ja viivaan
  4215. jäisi reikiä.
  4216.  
  4217. Eli jos...muutoin -rakenne valitsee pidemmän akselin. Sitten
  4218. alustetaan lyhyemmän akselin aloituskoordinaatti ja korotus jo
  4219. valmiiksi. Koska tiedämme, että looppi korottuessaan yhdellä tulee
  4220. toistamaan sen sisällä olevan koodin yhtä monta kertaa kuin pidemmälle
  4221. akselille tulee pikseleitä (jos delta_x on pidempi akseli, niin
  4222. delta_x kertaa) niin voimme helposti laskea paljonko lyhyemmällä
  4223. akselilla täytyy liikkua yhden kierroksen aikana. Tämä korotus
  4224. saadaan siis jakamalla lyhyen akselin pituus pidemmän akselin
  4225. pituudella.
  4226.  
  4227. Ette varmaan ymmärtäneet mitään, joten parasta ottaa esimerkki. Meillä
  4228. on viiva pisteestä (10, 10) (eli siis ax=10 ja ay=10) pisteeseen (30,
  4229. 20) (eli taas bx=30 ja by=20).
  4230.  
  4231. delta_x = | bx - ax | = | 30 - 10 | = | 20 | = 20
  4232. delta_y = | by - ay | = | 20 - 10 | = | 10 | = 10
  4233.  
  4234. Huomaamme, että delta_x on pidempi ja meidän täytyy piirtää delta_x
  4235. kappaletta pikseleitä saadaksemme yhtenäisen viivan. Valitsemme siis
  4236. pseudo-koodistamme jos-osaa seuraavan pätkän, sillä lause
  4237. 'delta_x >= delta_y' on tosi.
  4238.  
  4239. y = ay = 10
  4240. y_korotus = delta_y / delta_x = 10 / 20 = 0.5
  4241.  
  4242. Nyt kun siis looppaamme x:n välillä 20...30, niin joka x:n korotusta
  4243. yhdellä seuraa y:n korotus 0.5:llä. Näin siis x ja y menevät:
  4244.  
  4245. x | y
  4246. ---------
  4247. 10 | 10
  4248. 11 | 10.5
  4249. 12 | 11
  4250. .. | ..
  4251. 29 | 19.5
  4252. 30 | 20
  4253.  
  4254. Huomaa, että koska piirrossa pitää käyttää kokonaislukuja, niin nuo
  4255. desimaaliosan sisältävät y-koordinaatit pyöristyvät aina alaspäin,
  4256. jolloin piirtokoordinaatit ovat:
  4257.  
  4258. x | y
  4259. --------- -- (10, 10)
  4260. 10 | 10 --
  4261. 11 | 10 --
  4262. 12 | 11 --
  4263. 13 | 11 --
  4264. 14 | 12 --
  4265. 15 | 12 --
  4266. .. | .. --
  4267. 27 | 18 - (30, 20)
  4268. 28 | 19
  4269. 29 | 19
  4270. 30 | 20
  4271.  
  4272. Vasemmalla siis taulukko loopissa kiertäessä x- ja y-arvoista ja
  4273. sitten oikealla viiva, jonka näköinen tuosta suurinpiistein tulee.
  4274. Mutta, hienoa muuten, mutta pari ongelmaa on
  4275. ratkaisematta. Selvitettyämme pidemmän akselin ja laskettuamme
  4276. lyhyemmälle akselille tarvittavan koordinaatin korotuksen voimme kyllä
  4277. piirtää viivan noin, mutta ongelmia seuraa heti, jos ensimmäinen piste
  4278. on toisen pisteen oikealla- tai alapuolella. Sillä korotus on aina
  4279. positiivinen, kun sekä jaettava että jakaja ovat
  4280. positiivisia. Ongelmia aiheuttaa myös se, että jos pidemmän akselin
  4281. ensimmäinen koordinaatti on suurempi kuin jälkimmäinen, niin
  4282. korotuksen tilallahan pitäisi olla vähennys!
  4283.  
  4284. No, hieman lisälogiikkaa ja hyvin menee. Teemme nimittäin sillä
  4285. tavalla, että järjestämme pidemmän akselin ensimmäisen koordinaatin
  4286. aina pienemmäksi kuin toisen. Eli jos ensimmäinen piste onkin toisen
  4287. oikealla-/alapuolella, niin funktiomme vaihtaa pisteiden
  4288. paikkoja. Sama viiva se on silti, mutta loopissa ei tarvitse miettiä
  4289. onko se ensimmäinen pienempi tai suurempi, sillä se on aina pienempi.
  4290.  
  4291. Ja kun vielä poistamme pyöristykset alusta, niin jos lyhyemmän akselin
  4292. pituus on negatiivinen, niin sen jako pidemmän akselin pituudella
  4293. tuottaa negatiivisen korotuksen (y_korotus ja x_korotus). Ja jo
  4294. ala-asteellahan on opetettu, että negatiivisen luvun lisäys on sama
  4295. kuin vastaluvun vähennys. (eli suomeksi: 10 + (-10) = 10 - 10)
  4296.  
  4297. Eli upea pseudorutiinimme kokonaisuudessaan:
  4298.  
  4299. funktio viiva( int ax, int ay, int bx, int by, char väri )
  4300.  
  4301. float x, y, x_korotus, y_korotus, delta_x, delta_y
  4302.  
  4303. delta_x = bx-ax
  4304. delta_y = by-ay
  4305.  
  4306. jos |delta_x| >= |delta_y| niin
  4307.  
  4308. jos delta_x < 0 niin
  4309.  
  4310. vaihda( ax, bx )
  4311. vaihda( ay, by )
  4312.  
  4313. end jos
  4314.  
  4315. y = ay
  4316.  
  4317. jos delta_y == 0 niin
  4318.  
  4319. y_korotus = 0
  4320.  
  4321. muutoin
  4322.  
  4323. y_korotus = delta_y / delta_x
  4324.  
  4325. end jos
  4326.  
  4327. looppaa x välillä ax...bx
  4328.  
  4329. piste( (int)x, (int)y, väri )
  4330.  
  4331. y = y + y_korotus
  4332.  
  4333. end looppaa
  4334.  
  4335. muutoin
  4336.  
  4337. jos delta_y < 0 niin
  4338.  
  4339. vaihda( ax, bx )
  4340. vaihda( ay, by )
  4341.  
  4342. end jos
  4343.  
  4344. x = ax
  4345.  
  4346. jos delta_x == 0 niin
  4347.  
  4348. x_korotus = 0
  4349.  
  4350. muutoin
  4351.  
  4352. x_korotus = delta_x / delta_y
  4353.  
  4354. end jos
  4355.  
  4356. looppaa y välillä ay...by
  4357.  
  4358. piste( (int)x, (int)y, väri )
  4359.  
  4360. x = x + x_korotus
  4361.  
  4362. end looppaa
  4363.  
  4364. end jos
  4365.  
  4366. end funktio
  4367.  
  4368. Tuo tarkistus nollasta pidemmän akselin kohdalla ('jos delta_x == 0'
  4369. sekä 'jos delta_y == 0') siksi, että pystyviivan kanssa pitää korotus
  4370. olla 0, eikä jako nollalla tule kysymykseen muutenkaan, sillä se
  4371. kaataa ohjelman. Itseisarvot vertailussa 'jos |delta_x|>=|delta_y|'
  4372. pitää olla siksi, että emme käyttäneet niitä aiemmin lainkaan.
  4373. No juu, voin kyllä lyödä vetoa, ettei se toimi, mutta kirjoitetaanpas
  4374. silti kauniilla C-kielellä puhtaaksi:
  4375.  
  4376. void vaihda( int *a, int *b ) {
  4377. int temp;
  4378.  
  4379. temp=*a;
  4380. *a=*b;
  4381. *b=temp;
  4382. }
  4383.  
  4384. void viiva( int ax, int ay, int bx, int by, char vari ) {
  4385. float x, y, x_korotus, y_korotus, delta_x, delta_y;
  4386.  
  4387. delta_x = bx-ax;
  4388. delta_y = by-ay;
  4389.  
  4390. if( fabs(delta_x) >= fabs(delta_y) ) {
  4391.  
  4392. if( delta_x < 0 ) {
  4393. vaihda( &ax, &bx );
  4394. vaihda( &ay, &by );
  4395. }
  4396.  
  4397. y = ay;
  4398.  
  4399. if( delta_y == 0 ) {
  4400. y_korotus = 0;
  4401. } else {
  4402. y_korotus = delta_y / delta_x;
  4403. }
  4404.  
  4405. for( x=ax; x<=bx; x++ ) {
  4406. putpixel( (int)x, (int)y, vari );
  4407. y = y + y_korotus;
  4408. }
  4409.  
  4410. } else {
  4411.  
  4412. if( delta_y < 0 ) {
  4413. vaihda( &ax, &bx );
  4414. vaihda( &ay, &by );
  4415. }
  4416.  
  4417. x = ax;
  4418.  
  4419. if( delta_x == 0 ) {
  4420. x_korotus = 0;
  4421. } else {
  4422. x_korotus = delta_x / delta_y;
  4423. }
  4424.  
  4425. for( y=ay; y<=by; y++ ) {
  4426. putpixel( (int)x, (int)y, vari );
  4427. x = x + x_korotus;
  4428. }
  4429.  
  4430. }
  4431. }
  4432.  
  4433. Tuon testaamiseksi paras on omat silmät ja niinpä yhdistin
  4434. viivanpiirtorutiinin ja hiiriesimerkin pyynnön yhteen
  4435. tiedostoon. Hiirellä pyörivä viivanpiirtäjä löytyy EXAMPLE-hakemiston
  4436. alta tiedostosta LINE.C. Nyt varmaan olisi paras, että selvität
  4437. itsellesi miten interpolointi viivanpiirrossa toimii ja miten rutiini
  4438. yleensäkin toimii. Interpolointi on siis yksinkertaisesti pisteiden
  4439. laskemista kahden pisteen välille ja tietokoneella se tehdään yleensä
  4440. siten, että otetaan koordinaatit ja jaetaan niiden välillä oleva tila
  4441. n kappaleeseen jakolaskulla ja sitten vain loopataan n kertaa
  4442. korottaen koordinaatteja tällä luvulla. Viivanpiirrossa, kuten myös
  4443. monessa muussa hommassa järjestetään asiat siten, että toinen
  4444. korotettavista on 1 ja toinen sitten mitä tarve vaatii.
  4445.  
  4446. Kannattaa myös muistaa se, että esitelty viivanpiirtorutiini on, ellei
  4447. hitain, niin kuitenkin todella takkuinen. Ensimmäisenä voisi aloittaa
  4448. muuttamalla float-muuttujat fixed-pointiksi. Sitten myös omat rutiinit
  4449. pysty- ja vaakasuuntaisille sekä diagonaalisille (45 asteen kulma)
  4450. viivoille. Myös "pitkille" ja "leveille" rutiineille voisi tehdä
  4451. jotain optimointia. Erilliset rutiinit molemmille tai jokin kikka
  4452. millä yhdistää logiikkaa voisi hyvinkin nopeuttaa. Sitten todellisille
  4453. nopeuskiihkoilijoille assyoptimointi tai ehkä mieluummin Bresenhamin
  4454. viivanpiirron opettelu (löytyy PCGPE:stä) voisi olla
  4455. tarpeen. Bresenhamia en ala opettamaan, kun en itsekään rutiinin
  4456. toimintaa ymmärrä. Nopea se on joka tapauksessa.
  4457.  
  4458. Myös tutkiminen paperilla voi auttaa. Jos kuitenkin tuntuu, että jokin
  4459. jäi epäselväksi, niin sitten vain postia. En nimittäin tiedä kuinka
  4460. epätäydellinen selityksestä tuli, kun itse olen asian kanssa takunnut
  4461. niin kauan, että sen osaa etu- ja takaperin. Interpolointi on parasta
  4462. olla hanskassa, sillä sitä tarvitaan myös esim. kaikkeen polygonien
  4463. piirtoon liittyvässä. Mutta minä jatkan seuraavaan aiheeseen, nähdään
  4464. siellä!
  4465.  
  4466.  
  4467. 7.6 Vapaa skrollaus
  4468. -------------------
  4469.  
  4470. Tänään teemme sitä ylös ja alas, sivulle ja toiselle sekä useampia
  4471. yhtä aikaa. Se ei ole mitä luulet, vaan se on vapaasuuntaista
  4472. skrollausta, tarvittaessa vaikka osiin jaetulla ruudulla jokaisessa
  4473. omaan suuntaansa.
  4474.  
  4475. Aihe on helppo. Niin helppo, että minä tein sellaisen ilman mitään
  4476. ongelmia. Ja se on sitten aika helppoa. Mutta jotta ne jotka eivät
  4477. osaa/jaksa itse paneutua ongelmaan paria minuuttia enempää omien
  4478. aivojen voimin saavat tässä tyhjentävän selityksen. Skrollaushan on
  4479. pienen palasen näyttämistä suuremmasta kokonaisuudesta. Ruutu on
  4480. ikäänkuin ikkuna suurempaan ruutuun. Kuten alhaalla näkyy:
  4481.  
  4482. +---------------------------+ Kuvan pisteet vain hahmottavat
  4483. | . . . . . . . . . | näytettävää aluetta. Ne eivät
  4484. | (x,y) . . KOKO. . . .| merkitse mitään. :)
  4485. |. o-----+ . . . . . |
  4486. | . | |. NÄYTETTÄVÄ . |
  4487. | .|RUUTU| . . . . . .|
  4488. |. | | .ALUE . . . |
  4489. | . +-----+. . . . . . |
  4490. | . . . . . . . . .|
  4491. +---------------------------+
  4492.  
  4493. Ruutu on yleensä koko näyttöruudun kokoinen, tai sitten jos
  4494. näyttoruutu on jaettu useaan osaan niin sen osan kokoinen, johon
  4495. ikkuna piirretään. Kyse on siis vähän samantapaisesta toiminnasta,
  4496. kuin bittikarttojen kanssa. Bittikartoissa vain piirretään ruutua
  4497. pienempi kuva ruudulle, kun skrollauksessa luetaan ruutua suuremman
  4498. kuvan osa ruudulle. Piirrossa aloitamme näytettävän alueen kohdasta
  4499. (x,y) (merkitty kuvassa kirjaimella 'o' ruudun yläkulmaan). Sitten
  4500. vain kopioimme ruudun leveyden verran pikseleitä näytettävästä
  4501. alueesta ja siirrymme taas seuraavalle riville. Yksinkertaista, mutta
  4502. helppoa! Jatkamme tätä kunnes olemme saaneet ruudun täyteen. Helppoa!
  4503.  
  4504. Skrollaavassa pelissä täytyy ottaa nyt huomioon, että spritejä ja
  4505. muita ei enää piirretä kaksoispuskuriin, vaan tähän näytettävän alueen
  4506. puskuriin. Sen koko voi sitten olla mitä vain maan ja taivaan välillä
  4507. - ainakin lukualueen rajoissa, kuitenkin. Skrollauksessa näyttö onkin
  4508. nyt vain ikkuna liikkuvaan ja elävään pelimaailmaan. Fiksu kooderi
  4509. tietenkin piirtää vain näkyvissä olevat asiat, mutta sellaiset
  4510. hienoudet jäävät ohjelmoijan päätöksen varaan.
  4511.  
  4512. Aika heittää editorisi ruudulle hieman pseudoa:
  4513.  
  4514. char alue[640][400]
  4515. char ruutu[320][200]
  4516.  
  4517. funktio päivitä( int ylä_x, int ylä_y )
  4518.  
  4519. int rivi, sarake
  4520.  
  4521. looppaa rivi välillä 0...199
  4522.  
  4523. looppaa sarake välillä 0...319
  4524.  
  4525. ruutu[ rivi ][ sarake ] = alue[ ylä_y + rivi ][ ylä_x + sarake ]
  4526.  
  4527. end looppaa
  4528.  
  4529. end looppaa
  4530.  
  4531. end funktio
  4532.  
  4533.  
  4534. Näyttääkö vaikealta? Ei pitäisi, ei ainakaan minusta näytä. :)
  4535. Mutta pistetään vähän vaikeammaksi. C-toteutuksessa kun meillä
  4536. kuitenkin on vain yksiuloitteinen taulukko, niin sijoituksessa pitää
  4537. osoite laskea käsin:
  4538.  
  4539. ruutu[ rivi * 320 + sarake ] =
  4540. alue [ (ylä_y + rivi) * 640 + ylä_x + sarake ];
  4541.  
  4542. Toisena tuo on toivottoman hidasta. Kannattanee säästä sisempi looppi
  4543. ja kopioida memcpy:llä koko rivi kerralla:
  4544.  
  4545. memcpy( &ruutu[ rivi * 320 ],
  4546. &alue[ (ylä_y + rivi) * 640 + ylä_x ],
  4547. 320 );
  4548.  
  4549. Näin saamme seuraavanlaisen C-kielisen kyhäelmän:
  4550.  
  4551. char alue[640*400];
  4552. char ruutu[320*200];
  4553.  
  4554. void paivita( int yla_x, int yla_y ) {
  4555. int rivi;
  4556.  
  4557. for(rivi=0; rivi<200; rivi++)
  4558. memcpy( &ruutu[ rivi * 320 ],
  4559. &alue[ (yla_y + rivi) * 640 + yla_x ],
  4560. 320 );
  4561. }
  4562.  
  4563.  
  4564. Eri kokoisten näyttöalueiden/virtuaaliruutujen (tai miksi niitä nyt
  4565. haluatkin sitten kutsua) toteuttaminen ei paljoa vaadi. Puskurin koko
  4566. vain muokkaukseen ja offsetin ((yla_y + rivi) * 640 + yla_x) laskuun
  4567. pikku muutos ja se onkin siinä. Sitten vielä pitää hoitaa niin, että
  4568. piirrettävän alueen alakulma ei mene virtuaaliruudun ulkopuolelle, eli
  4569. tarkoitan tätä:
  4570.  
  4571. +-----------+
  4572. | |
  4573. | VIRTUAALI |
  4574. | RUUTU +--+--+
  4575. | | | |
  4576. +--------+--+ |
  4577. |RUUTU|
  4578. +-----+
  4579.  
  4580. Jos yla_x tai yla_y kasvaa niin suureksi, että yla_x+320 tai yla_y+200
  4581. menisi ruudun yli, niin silloin kaivetaan tavuja varatun muistialueen
  4582. ulkopuolelta aiheuttaen joko ihmeellistä käyttäytymistä tai koneen
  4583. kaatumisen. Joten pidetäänpäs koordinaatit kurissa!
  4584.  
  4585. Mitä tuo onkaan mitä kuulen? (olemattomia?) Totta, taisin luvata
  4586. muutakin kuin koko ruudun skrollausta. No, se ei ole vaikeaa. Kun
  4587. ylemmässä esimerkissä me piirsimme koko ruudulle, niin olisimme
  4588. tietenkin sen sijaan voineet aloittaa ruudultakin jostain muualta kuin
  4589. oikeasta yläkulmasta ja ikkunan koko olisi voinut olla vaikka 100x100.
  4590. Kun ikkunan koko on vähemmän kuin koko näyttöruudun koko se tarkoittaa
  4591. myös sitä, että ikkunoita mahtuu ruudulle tarvettaessa
  4592. useampia. Tällainen onnistuu funktiolla, joka ottaa parametreinään
  4593. virtuaaliruudun aloituspisteen lisäksi myös aloituspisteen
  4594. näyttöruudulla ja ikkunan korkeuden ja leveyden.
  4595.  
  4596. Tosi kooderi osaa tietenkin toteuttaa tuollaisen pienellä
  4597. miettimisellä. Ja koska minäkin olen sellainen, niin olen tehnyt
  4598. yhdistetyn näppäinesimerkin ja skrollausesimerkin joka löytyy myös
  4599. EXAMPLE-hakemiston alta tiedostosta, tällä kertaa nimen SCROLL.C alta.
  4600.  
  4601. Tässä vaiheessa täytyy vielä varoittaa siitä, että memcpy on syntisen
  4602. hidas tapa kopioida muistia. Optimointi assyllä tai jopa C:llä voi
  4603. nopeuttaa toimintaa, jos muistia heitellään 4 tavun palasissa. Mittaa
  4604. kuitenkin mahdollinen nopeushyöty, ettet vahingossa laita hitaampaa
  4605. korvaavaa rutiinia! Sitten vain sisäistämään luvun asiaa, olikos se
  4606. nyt niin vaikeaa? Viiden sekunnin sormienvenyttelytaun jälkeen onkin
  4607. sitten vuorossa sinit ja kosinitit, sekä plasmaa.
  4608.  
  4609.  
  4610. 7.7 Sinit ja kosinit sekä plasmaa
  4611. ---------------------------------
  4612.  
  4613. No nyt hieman kertausta yhdeksännen luokan matematiikasta. Sinit ja
  4614. kosinit. Mitä ne sitten ovat ja mitä niillä tehdään? Ennenkuin
  4615. vastaan, niin tutustukaamme Suorakulmaiseen Kolmioon:
  4616.  
  4617. o a = kateetti
  4618. |\
  4619. | \ b = kateetti
  4620. a | \ c
  4621. | \ c = hypotenuusa
  4622. | \
  4623. | \
  4624. | /\ * = tässä nurkassa on kulma alpha
  4625. o-------*
  4626. b
  4627.  
  4628. No kaikki varmaan osaavat jo pythagoraan lauseen c^2 = a^2 + b^2.
  4629. Mutta sinit ja kosinit ovatkin jotain ihan uutta, ainakin
  4630. 8-luokkalaisille ja ysinsä aloittaneille, kenties:
  4631.  
  4632. sin alpha = a/c
  4633. cos alpha = b/c
  4634. tan alpha = a/b
  4635.  
  4636. Vain kaksi ensimmäistä oikeastaan kiinnostavat meitä, sillä niitä
  4637. yleensä käytetään. Nyt tiedämme siis, että sini jostain kulmasta on
  4638. yhtä kuin kateetin a ja hypotenuusan osamäärä ja kosini vastaavasti
  4639. kateetin b ja hypotenuusan. Vaan mitä h**vettiä oikein teemme tällä
  4640. tiedolla? No KÄYTÄMME HYVÄKSEMME!
  4641.  
  4642. Nimittäin kiinnostavaa on, että jos tiedämme c:n, eli hypotenuusan
  4643. pituuden ja kulman, niin voimme laskea vastaavan suorakulmaisen
  4644. kolmion molempien kateettien pituudet. Pyörittelemällä hieman tavaraa
  4645. puolelta toiselle (kai yhtälön ratkaisu oli jo seiskalla?):
  4646.  
  4647. a = sin alpha * c ; sini
  4648. b = cos alpha * c ; kosini
  4649.  
  4650. Kiinnostavaa on myös, että jos kuviossa tähdellä merkitty kärki on
  4651. ympyrän keskipiste ja c ympyrän säde, niin 'sin alpha * c' antaa
  4652. ympyrältä kulman alpha kohdalla olevan pisteen y-koordinaatin ja
  4653. kosini sitten vastaavasti x-koordinaatin, kas näin:
  4654.  
  4655. --^-- Kuten kaaviosta näkee, niin ympyrän säteestä
  4656. -- | -- muodostuu hypotenuusa ja kun tiedämme kulman
  4657. - | X alpha voimme selvittää kateettien pituudet,
  4658. - | /|- jotka samalla ovat pisteen X koordinaatit
  4659. - | / |- kuviossa. Eli
  4660. - | / | -
  4661. - |/\ | - X = (cos alpha * radius, sin alpha * radius)
  4662. <------o----+->
  4663. - | - Viivanpiirron kehittely tästä ei olisi vaikeaa,
  4664. - | - tarvitaan vain looppaus vaikka 360 astetta
  4665. - | - ja joka kerralla lasketaan pisteen koordinaatit
  4666. - | - laskurin osoittamalle kulmalle (0...359), jolloin
  4667. - | - ruudulle piirtyy kaunis ympyrä säteeltään
  4668. -- | -- <radius>.
  4669. --v--
  4670.  
  4671. Olet myös varmaan pelannut autopeliä nimeltään Slicks, tai jotakin
  4672. luolalentelyä (esim. Auts, V-Wing, Kops, Rocket Chase, Kaboom,
  4673. Spruits, Wings, PP, Turboraketti, A-Wing, ...). Tällaisissa peleissä,
  4674. joissa täytyy pystyä liikkumaan muuallekin kuin ylös, alas, oikealle
  4675. ja vasemmalle täytyy myös pystyä liikkumaan muihin ilmansuuntiin.
  4676. Jos ajattelemme tietokoneen koordinaatistioa, niin aste 0 osoittaa
  4677. oikealle, 90 alas, 180 vasemmalle ja 270 ylös. Nyt jos haluamme tietää
  4678. paljonko pitää alusta siirtää x-suunnassa ja paljonko y-suunnassa
  4679. nopeuden ollessa vaikkapa 5 vasemmalle alaviistoon (siis 90+45=135
  4680. astetta) saamme seuraavan lausekkeen:
  4681.  
  4682. x_nopeus = (cos 135)*5
  4683. y_nopeus = (sin 135)*5
  4684.  
  4685. Kaikki näyttää helpolta. Osaamme piirtää ympyrän, laskea tarvittavan
  4686. x- ja y-nopeuden tiettyyn suuntaan kohdistuvalle liikkeelle ja vaikka
  4687. tehdä pyörivän tähden viivanpiirtorutiinien avulla. Vaan vielä hieman
  4688. pitää pinnistää päästäksemme tavoitteeseemme C:llä. Alla muutamia
  4689. totuuksia sinistä ja kosinista:
  4690.  
  4691. 1) Ne palauttavat 99.99% tilanteista arvon joka on yli -1 ja alle 1,
  4692. joten jos leikitään kokonaisluvuilla saadaan tulokseksi 0
  4693.  
  4694. 2) Koska arvoalue on niin pieni, täytyy aina käyttää joko liukulukuja
  4695. (float) taikka fixed point -lukuja. Fixedeinäkin sietää käyttää
  4696. monta bittiä desimaaliosalle, jottei tule karkeita muotoja.
  4697.  
  4698. 3) Kuten muitakin matikkatavaroita käyttävät funktiot, myös sinit ja
  4699. kosinit vaativat math.h:n ja libm.a:n (käännösoptio -lm) mukaansa
  4700. toimiakseen.
  4701.  
  4702. 4) Parametrina funktioille sin() ja cos() annetaan luku RADIAANEINA,
  4703. muunto tapahtuu seuraavasti:
  4704.  
  4705. radiaanit = 3.1415 * 2 * kulma / MAX_KULMA
  4706.  
  4707. MAX_KULMA on sama kuin suurin_mahdollinen_kulma+1. Eli
  4708. normaalistihan se on 360, mutta tietokoneella käytetään usein
  4709. 256-, 512- ja jopa 1024-asteisia kulmia, sillä ne ovat huomattavan
  4710. helppoja laskea. Etenkin 256-asteinen on näppärä, sillä kun
  4711. suurinta kulman arvoa 255:ttä korotetaan ja laskuri on tyyppiä
  4712. unsigned char, niin se pyörähtää automaattisesti ympäri, takaisin
  4713. nollaan. Huomaa myös, että 360-asteisillakin ympyröillä maksimi
  4714. kulma on 359!
  4715.  
  4716. Nyt kun tiedät kaiken tärkeän, niin olet valmis käyttämään taitojasi
  4717. käytännössä. Mutta vielä yksi asia: Taulukointi. Sini ja kosini ovat
  4718. molemmat luvattoman hitaita suorittaa, joten parasta on tehdä
  4719. lookup-taulukko niille. Eli teemme 256-alkioiset taulukot sekä sinille
  4720. ja kosinille ja laskemme niihin molempiin valmiiksi arvot sinistä ja
  4721. kosinista kulmille 0...255 (käytämme siis 256-asteista ympyrää):
  4722.  
  4723. int loop;
  4724. float sintab[256], costab[256];
  4725.  
  4726. for(loop=0; loop<256; loop++) {
  4727. sintab[loop] = sin(3.1415*2*(float)loop/256.0);
  4728. costab[loop] = cos(3.1415*2*(float)loop/256.0);
  4729. }
  4730.  
  4731. Nyt ei sitten tarvita turhia vääntelehtimisiä minkään
  4732. radiaanikonversion kanssa tai muutakaan yhtä epämiellyttävää, vaan
  4733. toiminta on suorasukaista. Laittakaamme aluksemme kohti kaakkoa:
  4734.  
  4735. x_suunta = costab[45];
  4736. y_suunta = sintab[45];
  4737.  
  4738. Jotain oli vielä... aijuu, se plasma! No jotkut ovat ehkä tämän
  4739. tehneet jo ja toiset ovat tehneet ainakin
  4740. paletinpyöritysplasman. Mutteivät vielä sitä aitoa ja oikeaa. Vaan nyt
  4741. tulee asiaan muutos. Liikkuva plasma on oikein mukava olla olemassa ja
  4742. tässä tulee idea lyhykäisesti:
  4743.  
  4744. 1) Tehdään 6 kappaletta eri "korkuisia" sinikäyriä (siis hypotenuusan
  4745. pituus / ympyrän säde / sinin kerroin) ja jotka mielellään alkavat
  4746. eri kohdista ja joissa aallon pituus on eri (eli toinen käyrä on
  4747. kuin 256-asteista ympyrää varten tehty ja toinen taas kuin
  4748. 128-asteista jne.). Myös kosinia kannattaa käyttää.
  4749.  
  4750. Idea on kuitenkin se, että jokainen käyrä tallennetaan omaan
  4751. taulukkoonsa ja että jokaista käyrää voidaan "monistaa" peräkkäin,
  4752. eli jos samaa käyrää piirretään kaksi peräkkäin ei käyrä katkea
  4753. kesken, siis:
  4754.  
  4755. VÄÄRIN:
  4756.  
  4757. \ \ \
  4758. \ \ \
  4759. \ / \ / \ /
  4760. \---/ \---/ \---/
  4761.  
  4762. OIKEIN:
  4763.  
  4764.  
  4765. \ /--- \ /--- \ /---
  4766. \ / \ / \ /
  4767. \---/ \---/ \---/
  4768.  
  4769. Eli koska käyrää joudutaan toistamaan peräkkäin niin jos se ei
  4770. palaa lähtöpaikkaansa loppuun mennessä tulee sahalaitaa. Plasman
  4771. tapauksessa tuloksena on epämiellyttävän näköisiä loikkauksia
  4772. muuten pehmeissä väriliu'uissa.
  4773.  
  4774. Alla esimerkki kuuden erilaisen käyrän alustuksesta ja
  4775. generoinnista:
  4776.  
  4777. float wave1[256], wave2[256], wave3[256],
  4778. wave4[256], wave5[256], wave6[256];
  4779. int loop;
  4780.  
  4781. for(loop=0; loop<256; loop++) {
  4782. wave1[loop]=cos(3.1415*2* (float) loop /256) * 25.0 + 25.0;
  4783. wave2[loop]=cos(3.1415*2* (float) (loop%128) /128) * 15.0 + 15.0;
  4784. wave3[loop]=cos(3.1415*2* (float) (255-loop) /256) * 17.5 + 17.5;
  4785. wave4[loop]=sin(3.1415*2* (float) (loop%64) /64) * 22.5 + 22.5;
  4786. wave5[loop]=cos(3.1415*2* (float) ((128-loop)%128) /128) * 20.0 + 20.0;
  4787. wave6[loop]=sin(3.1415*2* (float) loop /256) * 25.0 + 25.0;
  4788. }
  4789.  
  4790. Koska sin ja cos palauttavat myös negatiivisia arvoja, täytyy
  4791. niihin lisätä sama luku kuin kerroinkin, jotta ne olisivat aina
  4792. positiivisia. Näinollen kaikkien aaltojen summa on pahimmillaan
  4793. kerrointen summa * 2, suomeksi maksimissaan 250.
  4794.  
  4795. 2) Kun aallot ovat tallessa aletaan liikuttamaan niitä eri
  4796. suuntiin. Käytännössä tämä hoituu käyttämällä yhtä laskuria
  4797. jokaista aaltoa kohti, joka kertoo senhetkisen aallon alun
  4798. sijainnin.
  4799.  
  4800. Sitten vain aletaan piirtämään. Kolme ensimmäistä aaltoa ovat
  4801. vaaka-aaltoja ja kolme viimeistä pystyaaltoa. Tämä tarkoittaa
  4802. sitä, että kolmen ensimmäisen aallon alkion numero riippuu
  4803. väritettävän pikselin x-koordinaatista ja kolmen viimeisen indeksi
  4804. on riippuvainen y:stä. Kun indeksi vielä typistetään välille
  4805. 0...255 and-funktion maskilla 0xFF niin voimme laskea jokaiselle
  4806. aallolle oikein indeksin:
  4807.  
  4808. wave1[ ( ind1 + x ) & 0xFF ]
  4809. wave2[ ( ind2 + x ) & 0xFF ]
  4810. wave3[ ( ind3 + x ) & 0xFF ]
  4811. wave4[ ( ind4 + y ) & 0xFF ]
  4812. wave5[ ( ind5 + y ) & 0xFF ]
  4813. wave6[ ( ind6 + y ) & 0xFF ]
  4814.  
  4815. Sitten vain ynnätään jokaiselle pikselille aallon senhetkiset
  4816. alkiot yhteen ja saatu luku tungetaan ruudulle pikselin
  4817. väriarvona. Koska x- ja y-koordinaatit liikuttavat tasaisesti
  4818. aaltojen indeksiä eteenpäin syntyy ynnäämällä tasainen kumpuileva
  4819. värimaasto. Eli piirtolooppi:
  4820.  
  4821. for(x=0; x<320; x++) for(y=0; y<200; y++) {
  4822. dblbuf[y*320+x] =
  4823. wave1[ ( ind1 + x ) & 0xFF ]+
  4824. wave2[ ( ind2 + x ) & 0xFF ]+
  4825. wave3[ ( ind3 + x ) & 0xFF ]+
  4826. wave4[ ( ind4 + y ) & 0xFF ]+
  4827. wave5[ ( ind5 + y ) & 0xFF ]+
  4828. wave6[ ( ind6 + y ) & 0xFF ];
  4829. }
  4830.  
  4831. Jonka ulkopuolella korotetaan ja vähennetään aaltojen
  4832. aloituskohtia (ind1-ind6) ja tätä toimintaa toistetaan niin kauan
  4833. kunnes painetaan nappia. Täydet sorsat EXAMPLE-hakemistosta
  4834. tiedostosta PLASMA.C.
  4835.  
  4836. Siinä olikin tämän kappaleen asia. Nyt taidan katsoa läksyni ja tehdä
  4837. esimerkit loppuun. Vielä pitäisi paletin kvantisointi saada tehtyä
  4838. ennen joulua, katsotaan ehdinkö ajoissa, pakko kai kyllä varmaan on. :)
  4839. No niin, nyt vain piirtelemään ihme käyriä ja kuvioita paperille ja
  4840. ruudulle, jota sinin ja kosinin syvin olemus selviää täydellisesti.
  4841.  
  4842.  
  4843. 7.8 Paletin kvantisointi - Median cut
  4844. -------------------------------------
  4845.  
  4846. Nyt ollaankin sitten kyynärpäitä myöden mudassa. Paletin kvantisointi
  4847. lienee sellainen temppu, jota eivät kaikki kokeneemmatkaan osaa, ja
  4848. kaiken lisäksi se on suhteellisen vaikea homma. Joten kiinnittäkää
  4849. turvavyönne ja valmistautukaa yritykseeni selventää hieman asiaa
  4850. tuntemistani kvantisointitavoista helpomman, tai ainakin nopeamman,
  4851. osalta.
  4852.  
  4853. Eli mistä nyt sitten puhumme? Paletin kvantisointi on sitä, että kun
  4854. sinulla on vaikkapa 6 erilaista PCX-kuvaa, joissa on yhteensä 1321
  4855. erilaista väriä, niin esittääksesi nämä 1321 erilaista väriä ruudulla
  4856. täytyy sinun KVANTISOIDA palettisi. Kvantisointi on siis värimäärän
  4857. tiputusta. Ja nyt seuraa se, miten sen teemme.
  4858.  
  4859. Ensin hieman funktioiden ideasta. Kuvittelemme värit kuutiona, jossa
  4860. x-y-z -akseliston tilalla onkin r, g ja b. Meillä on siis
  4861. ns. rgb-avaruus, jossa värin sijainnin kuutiossa kertoo punaisen,
  4862. vihreän ja sinisen komponentin määrä. Jos jokainen koordinaatti on
  4863. välillä 0...63 (kuten normaaleissa PCX-kuvissa), niin meillä on
  4864. 64*64*64 pikseliä sisältävä kuutio, jonka särmän pituus siis on 64
  4865. pituusyksikköä.
  4866.  
  4867. Tämän monimutkaisen ajatusrakennelman pohjalle perustuu
  4868. algoritmimme. Kun teemme taulukon, joka sisältää kaikki erilaiset
  4869. värit on taulukon jokainen alkio (rgb-tripletti) piste
  4870. kuutiossa. Funktiomme etsii sen akselin (siis r, g tai b), joka on
  4871. "pisin", eli suomeksi katsotaan jokaisen värikomponentin pienin ja
  4872. suurin esiintyvä arvo. Sitten funktio jakaa värikuution kahtia
  4873. täsmälleen siten, että puolet pisteistä/väreistä jää toiselle ja
  4874. puolet toiselle puolelle. Ei siis suurimman ja pienimmän väriarvon
  4875. puolesta välistä!
  4876.  
  4877. Kun koko kuutio on jaettu, niin kutsumme vain samaa jakofunktiota
  4878. kummallekin pienemmälle kuutiolle, jossa molemmissa on nyt siis yhtä
  4879. monta väriä. Nämä funktiot etsivät oman palasensa pisimmän
  4880. värikomponenttien välin ja jakavat kuution kahtia kutsuen itseään
  4881. molemmille kuutioille.
  4882.  
  4883. Funktio, joka kutsuu itseään on ns. rekursiivinen funktio ja se on
  4884. näppärä monessa asiassa. Jos piirrät paperille yhden laatikon ja siitä
  4885. lähtemään kaksi laatikkoa, joista kummastakin lähtee kaksi laatikkoa,
  4886. joista jokaisesta lähtee kaksi laatikkoa jne., niin saat huomaat, että
  4887. joka rekursiotasolla funktioiden "määrä" kaksinkertaistuu. Jos siis
  4888. asetamme rekursiorajaksi 3, niin värit jakautuvat 2^3:n, eli
  4889. kahdeksaan pienempään kuutioon. Kvantisointi 256:een väriin vaatii
  4890. siis kahdeksan rekursiotasoa, jotta saataisiin kuutio 256:een osaan.
  4891.  
  4892. Nyt te, jotka saitte ahaa-elämyksen (jokaisen pitäisi saada ;) menette
  4893. tekemään oman funktionne. Tyhmemmille ja kenties niille, jotka
  4894. haluavat saada vielä hieman varmennusta tulee kuitenkin vielä
  4895. teknisempi selostus, jonka teossa apuna on käytetty skenelehti
  4896. Imphobian osan 10 sisältämää informaatiota. Kiitoksia Fakerille.
  4897.  
  4898. Ensimmäiseksi teemme iiison taulukon, jonka koko on kaikkien
  4899. mahdollisten värien yhteenlaskettu määrä. Jos valitsemme 64 sävyä
  4900. jokaiselle värikomponentille saamme siis kooltaan 64x64x64 kokoisen
  4901. värikuution. Varaamme tälle muistia:
  4902.  
  4903. unsigned char *PaletteArray=(unsigned char *)calloc(64*64*64, 1);
  4904.  
  4905. (huomaa kaikkien alkioiden nollaus alussa) Kun haluamme lisätä värin
  4906. kvantisoitavien joukkoon, merkkaamme yksinkertaisesti tämän kuution
  4907. vastaavan pikselin ykköseksi. Näin meillä on kvantisoinnin alkaessa
  4908. kuutiossa ykköstä käytettyjen värien kohdalla ja voimme koostaa niistä
  4909. näppärästi värilistan sisältäen kaikki kvantisoitavat
  4910. värit. Koordinaatin kuutiossahan voimme laskea vaikka kaavasta:
  4911.  
  4912. r*64*64 + g*64 + b
  4913.  
  4914. No niin, sitten itse kvantisointirutiiniin. Funktion tehtävä on siis
  4915. ottaa kaikki pikselit tietyltä värikuution osakuutiolta ja katsoa mikä
  4916. värikomponentti vaihtelee eniten (eli tummimman ja vaaleimman värisävyn
  4917. ero on suurin). Osakuution kuvailemiseksi tarvitsemme tietenkin rajat
  4918. kuutiolle, eli tummimman ja vaaleimman mukaan otettavan sävyn kustakin
  4919. värikomponentista, eli:
  4920.  
  4921. int RedSubspaceBottom;
  4922. int GreenSubspaceBottom;
  4923. int BlueSubspaceBottom;
  4924. int RedSubspaceTop;
  4925. int GreenSubspaceTop;
  4926. int BlueSubspaceTop;
  4927.  
  4928. Nämä funktiot ovat siis punaisen, vihreän ja sinisen alimmat sallitut
  4929. pitoisuudet ja vastaavasti kolme viimeistä korkeimmat sallitut. Hyvä
  4930. idea on laittaa tällaisen kuution tiedot yhteen rakenteeseen. Sekaan
  4931. laitamme vielä tilaa funktion laskemille kunkin värisävyn
  4932. optimiarvoille, joka siis lasketaan sitten, että jos kuutio
  4933. halkaistaan tämän sävyn kohdalta, jää molemmille kuution puolikkaille
  4934. yhtä monta väriä:
  4935.  
  4936. int OptimalRed;
  4937. int OptimalGreen;
  4938. int OptimalBlue;
  4939.  
  4940. Nämä kaikki on esimerkissä laitettu structiin BORDERS. Nyt kun meillä
  4941. on pätevä rakenne kuution määrittelemiseksi, niin voimmekin alkaa
  4942. pohtimaan käytännön toimia, mitä rekursiivisen kuutionjakajamme tulee
  4943. toteuttaa. Idea on seuraava:
  4944.  
  4945. 1) Tyhjennetään punaisten, vihreiden ja sinisten värikomponenttien
  4946. laskurit (RedCount[64], GreenCount[64] ja BlueCount[64]).
  4947.  
  4948. 2) Lasketaan kuution rajojen sisällä jokaisen värikomponentin sävyn
  4949. määrä looppaamalla kaikki kvantisoitavat värit läpi ja katsomalla,
  4950. ovatko värin rgb-arvot parametrina annetun Borders (tyyppiä
  4951. BORDERS) sisällä ja jos ovat, niin korotetaan vastaavia punaisen,
  4952. vihreän ja sinisen laskureita:
  4953.  
  4954. RedCount[red]++;
  4955. BlueCount[blue]++;
  4956. GreenCount[green]++;
  4957.  
  4958. Lisäksi täytyy pitää yllä tietoa pienimmästä ja suurimmasta mukaan
  4959. otetusta värikomponentin sävystä, eli tyyliin:
  4960.  
  4961. jos red < PieninPunainen
  4962. PieninPunainen = red
  4963. tai jos red > SuurinPunainen
  4964. SuurinPunainen = red
  4965.  
  4966. 3) Nyt kun sävyt on laskettu, seuraakin jännittävä vaihe. Muutamme
  4967. kunkin värisävyn määrät sisältävän taulukon juoksevaksi laskuriksi,
  4968. eli tässä näette muutoksen:
  4969.  
  4970. Indeksi 0 1 2 3 4 5 6 7 8
  4971. Aluksi 0 0 3 1 0 2 2 0 1
  4972. Nyt 0 0 3 4 4 6 8 8 9
  4973.  
  4974. Tämäntyyppinen rutiini toimii:
  4975.  
  4976. for(loop=1; loop<64; loop++)
  4977. RedCount[loop]+=RedCount[loop-1];
  4978.  
  4979. Nyt värisävytaulukossa on siis tietyn indeksin kohdalla, ei
  4980. suinkaan sen sävyn määrä, vaan siihen värisävyyn 'mennessä'
  4981. olleiden värien määrä. Nyt vielä etsitään se 'optimaalinen'
  4982. katkaisukohta kulkemalla kohti taulukon loppua, kunnes olemme
  4983. ohittaneet (noin) puolet pikseleistä, eli kun laskuri on suurempi
  4984. kuin RedCount[63]/2 (joka on siis kaikkien mukana olevien värien
  4985. määrä jaettuna kahdella). Onnistuu esim. seuraavasti:
  4986.  
  4987. for(loop=0; loop<63; loop++) {
  4988. if(RedCount[loop+1]>(RedCount[63]/2)) {
  4989. Borders.OptimalRed=loop;
  4990. break;
  4991. }
  4992. }
  4993.  
  4994. Älkää ihmeessä kysykö miksi se on tuollainen. Minulla oli aiemmin
  4995. jotain ongelmia toisenlaisen lähestymistavan kanssa ja tein
  4996. tuollaisen idioottivarman systeemin.
  4997.  
  4998. Tämä toistetaan tietenkin kaikille värisävyille.
  4999.  
  5000. 4) Nyt vasta kivaa tuleekin. Funktiolle parametrina annettu
  5001. rekursiotason laskuri tarkistetaan ja toimitaan sen mukaan. Jos
  5002. taso on 0, niin olemme siinä pisteessä, että kuutioita ei enää
  5003. jaeta. Voimmekin kirjoittaa Borders-rakenteen optimaaliset
  5004. värisävyt (OptimalRed, OptimalGreen, OptimalBlue) lopullista
  5005. paletinmuodostusta odottamaan.
  5006.  
  5007. Esimerkissä funktio saa parametrinaan osoittimen
  5008. BORDERS-taulukkoon, sekä laskurin, joka kertoo montako ollaan jo
  5009. täytetty. Niinpä tallennus onnistuu varsin vaivattomasti:
  5010.  
  5011. memcpy(&BorderTable[TablesUsed[0]], &Borders, sizeof(BORDERS));
  5012. TablesUsed[0]++;
  5013.  
  5014. Jos on kuitenkin niin onnettomasti, ettei vielä olla lopussa niin
  5015. tehtävämme on silti helppo. Etsimme pisimmän akselin vähentämällä
  5016. alussa keräämämme suurimman ja pienimmän väriarvon sisältävät
  5017. muuttujat toisistaan:
  5018.  
  5019. red=SuurinPunainen-PieninPunainen;
  5020. green=SuurinVihreä-PieninVihreä;
  5021. blue=SuurinSininen-PieninSininen;
  5022.  
  5023. Esimerkissä nämä muuttujat tottelevat lyhyempiä nimi sr, br, sg,
  5024. bg, sb ja bb.
  5025.  
  5026. Sitten vain katsotaan mikä on pisin akseli ja tehdään uudet
  5027. pikkukuutiot näppärästi kahteen pienempään ja jaetaan kuutioiden
  5028. väriavaruudet siten, että toisen ylärajaksi tulee optimiväri-1 ja
  5029. toisen alarajaksi optimiväri. Tämä ylä- ja alarajojen muuttaminen
  5030. siis _vain_ pisimmän väriakselin arvojen kohdalta. Esimerkistä
  5031. löydät koodin miten tämä on toteutettu. Sitten vain kutsumme
  5032. itseämme molemmille pienemmille kuutioille, yhtä matalemmalla
  5033. rekursiotasolla ja annamme logiikan hoita loput.
  5034.  
  5035. Tästä puuttuu vielä tarkistus, josko kuutioon kuuluu enää vain 1 väri,
  5036. jolloin tehdään siitä suoraan paletin väri ja palataan rekursiossa
  5037. ylöspäin (ks. esimerkkiohjelma).
  5038.  
  5039. Kun itse rekursiivinen funktio on valmis, täytyy vielä hieman laittaa
  5040. lihaa ympärille. Tarvitsemme ohjelman, joka muuttaa alussa neuvotulla
  5041. tavalla varatun värikuution värivaraukset (eli ykköset värin kohdalla)
  5042. normaaliksi rgb-triplettitaulukoksi, varaa muistia
  5043. BORDERS-rakenteille, joihin optimaaliset värit tallennetaan, laskee
  5044. tarvittavan rekursiotason ja lopuksi hoitaa alussa ykköstä ja nollaa
  5045. sisältäneen värikuution sisältämään vastaavan sijainnin kvantisoidun
  5046. värin.
  5047.  
  5048. Viimeksimainittuun voisimmekin perehtyä hieman tarkemmin. Kun oikein
  5049. kutsuttu rekursiivinen funktio loppuu ja palaamme takaisin, on meillä
  5050. siististi koko kuutiomme jaettu 256:een (yleensä) pienempään
  5051. värikuutioon. Emme kuitenkaan vielä tiedä mikä väri tarkoittaa
  5052. milläkin välillä olevia sävyjä, joten teemme vielä yhden
  5053. homman.
  5054.  
  5055. Looppaamme jokaisen BORDERS-rakenteen läpi ja laitamme looppimuuttujan
  5056. mukaisen arvon alussa varattuun PaletteArray-muuttujaan kaikkiin
  5057. rakenteen ilmoittamiin pikseleihin. Eli piirrämme kuution sisään
  5058. SubspaceBottom ja SubspaceTop -muuttujien rajoittamalle alueelle
  5059. pienemmän kuution värillä, jonka rakenteen indeksi taulukossa
  5060. ilmoittaa ja talletamme rakenteen Optimal-tripletin palettiin indeksin
  5061. kohdalle.
  5062.  
  5063. Kuten aiemmin mainittiin, joissakin tapauksissa paletti menee siten,
  5064. että ennen rekursiotason 0 saavuttamista on jäljellä vain 1
  5065. väri. Tässä tapauksessa BORDERS-rakenteisiin ei talletetakaan täyttä
  5066. 256:tta väriä optimisävyineen, joka taas täytyy ottaa huomioon
  5067. palettia tehtäessä. Eli ei mitään looppia välillä 0..256, vaan välillä
  5068. 0..N, jossa N on se laskuri, jota korotetaan aina kun rekursiivinen
  5069. funktio täyttää yhden BORDERS-rakenteen. Esimerkkiohjelmassa
  5070. 'TablesUsed'.
  5071.  
  5072. Pseudona se menisi jotenkin näin:
  5073.  
  5074. looppaa loop välillä 0...TablesUsed
  5075.  
  5076. looppaa r välillä border[loop].RedSubspaceBottom ..
  5077. border[loop].RedSubspaceTop
  5078.  
  5079. looppaa g välillä border[loop].GreenSubspaceBottom ..
  5080. border[loop].GreenSubspaceTop
  5081.  
  5082. looppaa b välillä border[loop].BlueSubspaceBottom ..
  5083. border[loop].BlueSubspaceTop
  5084.  
  5085. PaletteArray[r*64*64+g*64+b]=loop;
  5086.  
  5087. end looppaa
  5088.  
  5089. end looppaa
  5090.  
  5091. end looppaa
  5092.  
  5093. paletti[index].red=border[loop].OptimalRed;
  5094. paletti[index].green=border[loop].OptimalGreen;
  5095. paletti[index].blue=border[loop].OptimalBlue;
  5096.  
  5097. end looppaa
  5098.  
  5099. Sitten vain palauttamaan syntynyt paletti. Alustusfunktiomme on
  5100. muuttanut paletinvaraustaulukon taulukoksi, josta voidaan rgb-arvojen
  5101. avulla hakea oikea väri (colortorgb = PaletteArray[r*64*64+g*64+b]) ja
  5102. palauttanut tarvittavan paletin, jotta väri myös näyttää joltakin.
  5103.  
  5104. Paljon mainostettu Esimerkkiohjelma löytyy EXAMPLE-hakemistosta
  5105. nimellä QUANTIZ.C. Koodi on kieltämättä vähintään viisi kertaa
  5106. vaikeampaa kuin aiemmat esimerkit, mutta kyllä täytyy myöntää, että
  5107. kvantisointi asianakaan ei ole läheskään niin helppoa kuin
  5108. viivanpiirto.
  5109.  
  5110. Kvantisoinnin hyödyistä voidaan olla monta mieltä, mutta yksi asia on
  5111. varma. Jos ei kunnollista värimäärää omaavaa näyttötilaa ole
  5112. saatavilla, niin kyllä kvantisoitu paletti aina päihittää kotikutoisen
  5113. 2-3-2 -järjestelmän (2 bittiä punaiselle ja siniselle ja 3 vihreälle).
  5114. Lisäksi kvantisoinnin tuloksena syntyvän kuution avulla voi tehdä
  5115. monta kivaa asiaa, kuten esimerkiksi motion blurin (väri on uuden ja
  5116. vanhan pikselin rgb-arvojen sekoitus) tai jotain muuta yhtä
  5117. hyödyllistä.
  5118.  
  5119. Yritä sisäistää asia. Jos ei mene kaaliin sitten millään (= mieti
  5120. kauemmin kuin 15 minuuttia), niin ilmoittele hämäristä kohdista. Asia
  5121. ON vaikea, mutta mielestäni selitin sen melkein ymmärrettävästi. Ja
  5122. ne, jotka ymmärsivät idean ja tekivät oman rutiinin (vain hullut
  5123. käyttävät esimerkkiohjelman koodia ;) saavat vain kiristää niitä
  5124. turvavöitään, sillä ensi luvussa hieman vaikeampaa kvantisointia!
  5125. Silti jo tämä tapa, etenkin nopeutensa ja suhteellisen hyvän
  5126. tuloksensa ansiosta on varsin hyvä.
  5127.  
  5128.  
  5129. 7.9 Lisää paletin kvantisointia - Local K Mean
  5130. ----------------------------------------------
  5131.  
  5132. Niille, jotka nauroivat itsensä ulos edellisen luvun
  5133. esimerkkiohjelmasta lyödään nyt luu kerralla kurkkuun. Tätä lähemmäksi
  5134. täydellisyyttä ette pääse - ainakaan tässä luvussa. Tämä algoritmi on
  5135. niin hidas, että edellinen versio on tähän verrattuna kuin rasvaamaton
  5136. salama. Myös 3Dicassa on selostettu pääpiirteittäin tämä tekniikka ja
  5137. kumarrankin kohti Sampsa Lehtosta, sillä muokkailen hänen selostustaan
  5138. hieman.
  5139.  
  5140. Perusidea tämän takana, toisin kuin kuutioihin jakavassa
  5141. rekursiivisessa versiossa, on pallomainen
  5142. ajattelutapa. Värikuutiossamme onkin nyt Palloja, joiden sijainti on,
  5143. kuten edellisessäkin, värin rgb-arvo. Koko taasen määräytyy sen
  5144. mukaan, kuinka monta tämän väristä pikseliä löytyy kvantisoitavasta
  5145. kuvasta. Jos et käytä kuvia tai et jostain syystä halua laskea mukaan
  5146. pallojen vetovoimaa, johon niiden koko vaikuttaa, niin värin määrä
  5147. kuvassa on aina 1, jolloin asialla ei kaavoissa ole merkitystä.
  5148.  
  5149. Näiden väripallojen seassa liikkuu sitten paletin verran
  5150. palettipalloja, eli yleensä 256 kappaletta. Näillä palloilla ei ole
  5151. kokoa. Väripallot vetävät puoleensa näitä kelluvia palloja sen mukaan,
  5152. kuinka suuria ne ovat ja nämä paletin värejä esittävät pallukat
  5153. liikkuvat sitten näiden mukana.
  5154.  
  5155. Vitsinä on se, että värit ovat kuin palloja vetäviä kappaleita ja
  5156. palettipallot pyrkivät sijoittumaan optimaaliseen paikkaan
  5157. väripallojen väliin. Koska jokaisella kerralla pallot liikkuvat vain
  5158. hieman, tulee kertoja luultavasti aika useita, ennenkuin palettipallot
  5159. ovat saavuttaneet optimaalisen sijaintinsa, joista tulee sitten
  5160. kvantisoidun paletin rgb-arvot. Pallojen yhteenlaskettua liikettä
  5161. käytetäänkin laskemaan sitä, milloin pallot ovat tarpeeksi lähellä
  5162. parhaita sijaintipaikkojaan (=liike edelliseen pientä). Mitä pienempi
  5163. liikkeen pitää vuorolla olla loppumisen tapahtumiseksi, sitä kauemmin
  5164. homma kestää ja sitä parempi tulos tulee.
  5165.  
  5166. Koska väripallot vetävät vain lähintä palettipalloa, niin jokin pallo
  5167. voi jäädä ilman vetovoimaa. Tässä tapauksessa pallo heitetään jonkin
  5168. värin lähelle tai kohdalle, jotta tämäkin väärälle tielle eksynyt väri
  5169. saadaan käyttöön.
  5170.  
  5171. Ja kuten Ilkan editoima selostuskin tekee, menemme sitten teknisempään
  5172. puoleen. Niin tein minäkin tätä opetellessani, joten älkää hävetkö
  5173. lukea tätä ennenkuin yritätte tehdä oman versionne rutiinista.
  5174.  
  5175. Kvantisoinnin aluksi teemme histogrammin, eli käyrän, joka ilmoittaa
  5176. kunkin värin määrän kuvassa. Esimerkissä käytämme sanan kokoista
  5177. laskuria 15-bittisille pikseleille (5 bittiä jokaiselle
  5178. värikomponentille), jolloin taulukon koko on 2^15 * 2 = 65536 tavua.
  5179. Nollaamme sen aluksi ja sitten korotamme jokaista tietyn värin
  5180. esiintymää kohti histogrammin tätä kohtaa yhdellä. Tietyn
  5181. rgb-tripletin sijaintihan on taas r*32*32 + g*32 + b.
  5182.  
  5183. Seuraavana sitten teemme taulukon niistä väreistä, joita todella
  5184. kuvassa on. Tallentaa täytyy rgb-tripletin lisäksi jokaisen värin
  5185. määrän, jonka saamme nyt histogrammista, joka taasen on 0 jos ei
  5186. tiettyä väriä ole lainkaan. Lehtonen suosittelee seuraavanlaista
  5187. rakennetta:
  5188.  
  5189. typedef struct {
  5190. unsigned char R; /* väriarvo */
  5191. unsigned char G; /* väriarvo */
  5192. unsigned char B; /* väriarvo */
  5193. unsigned long count; /* Värimäärä kuvassa */
  5194. } colorListStruct;
  5195.  
  5196. colorListStruct colorList[32768];
  5197.  
  5198. Muistia säästää tietenkin myös jos laskee värit ja varaa sitten
  5199. staattisesti muistia systeemille:
  5200.  
  5201. colorListStruct colorList=
  5202. (colorListStruct *)malloc(sizeof(colorListStruct)*colors);
  5203.  
  5204. Lisäksi täytyy vielä tallettaa kaikkien eri värien määrä kuvassa,
  5205. vaikka muuttujaan colorListCount. Sitten seuraavana peruspaletti:
  5206.  
  5207. unsigned long palette[256][3]; /* 3 = R,G & B */
  5208.  
  5209. Ja muuttujien lisääminen vain lisääntyy... Teemme vielä
  5210. värilaskuritaulukon, johon summaamme palettipalloa kutsuneiden värien
  5211. rgb-arvot kerrottuna värin määrällä. Tarvitsemme siis suht' suuren
  5212. lukualueen. Ja sitten vielä laskuri värien yhteismäärälle.
  5213.  
  5214. unsigned long colorSum[256][3]; /* 256 väriä, 3 = R,G & B */
  5215. unsigned long colorCount[256]; /* Voidaan yhdistää kyllä
  5216. colorSummiinkin */
  5217.  
  5218. Ja lopuksi vielä pisteenä i:n päälle läiskäisemme laskurin, joka
  5219. laskee paletin muutoksen edelliseen.
  5220.  
  5221. unsigned long variance;
  5222.  
  5223. Sitten vain kvantisoimaan. Jälleen rankasti kopioituna Sampsalta
  5224. tarvittavat askeleet. Mitäs teki niin hyvän jutun tästä. :) Eli itse
  5225. kvantisointirutiini:
  5226.  
  5227. 1) colorSum ja colorCount -laskurien nollaus ja paletin täytto
  5228. colorList:in ensimmäisillä (256:lla) värillä.
  5229.  
  5230. 2) Läpikäydään colorList:in värit. Värien määrähän löytyi
  5231. muuttujasta colorListCount, kuten aiemmin kerrottiin.
  5232. Loopataan c välillä 0 .. colorListCount-1
  5233.  
  5234. a) Otetaan colorList:istä väri c
  5235.  
  5236. b) Etsitään lähin väri palette-muuttujasta. Tuloksena numero
  5237. välillä 0..256. Etäisyys avaruudessahan on r- g- ja
  5238. b-etäisyyksien neliöiden summan neliöjuuri. Eli
  5239.  
  5240. delta_r = abs( r2-r1 )
  5241. delta_g = abs( g2-g1 )
  5242. delta_b = abs( b2-b1 )
  5243.  
  5244. sqrt( delta_r^2 + delta_g^2 + delta_b^2 )
  5245.  
  5246. Meidän täytyy loopata joka väri ja laskea tämä etäisyys ja
  5247. verrata sitä siihen mennessä löytyneeseen lyhimpään
  5248. etäisyyteen ja jos uusi väri on lähempänä tallennamme tämän
  5249. numeron ja etäisyyden ja jatkamme.
  5250.  
  5251. Optimointikikkoina se, että koska toinen potenssi on aina
  5252. positiivinen, putoaa itseisarvo (abs) pois. Ja koska
  5253.  
  5254. a^2 < b^2 <=> a < b
  5255.  
  5256. Niin neliöjuuriakaan ei tarvita. Esimerkkiohjelman
  5257. Dist-funktion ydin on seuraava:
  5258.  
  5259. for(loop=0; loop<Wanted; loop++) {
  5260. dist=Dist(r, g, b,
  5261. Palette[loop][0], Palette[loop][1], Palette[loop][2]);
  5262. if(dist<shortest) {
  5263. shortest=dist;
  5264. sl=loop;
  5265. }
  5266. }
  5267.  
  5268. Jossa dist vain palauttaa tuon delta_r^2 + delta_g^2 + delta_b^2.
  5269.  
  5270. c) Lisätään lähimmän värin colorSum-taulukkoon väripallon
  5271. rgb-arvot kerrottuna väripallon pikseleiden määrällä (count
  5272. kappaletta tätä värisävyä). (x=lähin väri, c=looppi)
  5273.  
  5274. colorSum[x][0] += colorList[c].R * colorList[c].count;
  5275. colorSum[x][1] += colorList[c].G * colorList[c].count;
  5276. colorSum[x][2] += colorList[c].B * colorList[c].count;
  5277.  
  5278. d) Sitten täytyy vielä tallettaa montako kertaa niitä rgb-arvoja
  5279. sinne ynnättiinkään.
  5280.  
  5281. colorCount[x]+=colorList[c].count;
  5282.  
  5283. 3) Nollataan liikelaskurimuuttujamme variance.
  5284.  
  5285. 4) Käydään läpi kaikki värit peruspaletista (c = 0..255)
  5286.  
  5287. a) Jos värin värilaskuri colorCount on suurempi kuin nolla, niin
  5288. väri oli lähinnä ainakin yhtä väripalloa. Nyt vain laskemme
  5289. keskiarvon kaikista kutsuneista väreistä ottamalla keskiarvon
  5290. niistä. Koska colorSum sisältää aina n kappaletta värin c
  5291. rgb-arvoja ja colorCount sisältää tämän luvun n niin
  5292. keskiarvo saadaan yksinkertaisesti:
  5293.  
  5294. palette[c][0] = colorSum[c][0] / colorCount[c];
  5295. palette[c][1] = colorSum[c][1] / colorCount[c];
  5296. palette[c][2] = colorSum[c][2] / colorCount[c];
  5297.  
  5298. Jos et oikein ymmärtänyt ideaa, niin otetaan
  5299. esimerkki. Paletin väri 5 on lähinnä kahta väripalloa:
  5300. palloja A = (10, 20, 0) ja B = (10, 20, 5). Väriä A on
  5301. kvantisoitavassa kuvassa 100 ja väriä B 200.
  5302.  
  5303. Rutiinimmehän ensin lisää colorSum:iin värin A rgb-arvot
  5304. kerrottuna värin A määrällä kuvassa:
  5305.  
  5306. colorSum[5][0] = A.R * A.count = 10 * 100 = 1000
  5307. colorSum[5][1] = A.G * A.count = 20 * 100 = 2000
  5308. colorSum[5][2] = A.B * A.count = 0 * 100 = 0
  5309. colorCount[5] = 100
  5310.  
  5311. Nyt meillä on siis värin A rgb-arvot 100-kertaisena
  5312. tallessa. Koska väriä B on kaksinkertaisesti, saamme sen
  5313. 200-kertaisena summataulukkoomme:
  5314.  
  5315. colorSum[5][0] = 1000 + 10*200 = 3000
  5316. colorSum[5][1] = 2000 + 20*200 = 6000
  5317. colorSum[5][2] = 0 + 5*200 = 1000
  5318. colorCount[5] = 100 + 200 = 300
  5319.  
  5320. Nyt laskemme sitten lopullisen värin paletille:
  5321.  
  5322. palette[5][0] = 3000 / 300 = 10
  5323. palette[5][1] = 6000 / 300 = 20
  5324. palette[5][2] = 1000 / 300 = 3.33...
  5325.  
  5326. Huomaamme, että koska väriä B oli kaksi kertaa enemmän, on
  5327. rgb-arvokin lähempänä väriä B. Näin siis se väri, jota on
  5328. kaikkein eniten, vaikuttaa suurimpana uuden värin
  5329. rgb-arvoihin. Helppoa, eikö totta?
  5330.  
  5331. b) Mittaamme paletin muutoksen alkup. väriin:
  5332.  
  5333. temp = 0;
  5334. temp += abs( R - palette[c][0] );
  5335. temp += abs( G - palette[c][1] );
  5336. temp += abs( B - palette[c][2] );
  5337. variance += temp;
  5338.  
  5339. c) Kirjataan väri lopullisesti uuteen palettiin.
  5340.  
  5341. palette[c][0] = R;
  5342. palette[c][1] = G;
  5343. palette[c][2] = B;
  5344.  
  5345. 5) Nyt uusi paletti on taas generoitu ja muutos muuttujassa
  5346. variance. Nollaamme nyt colorSum- ja colorCount -taulukot.
  5347.  
  5348. 6) Jos variance-muuttujan kertoma paletin muutos on vielä yli
  5349. sallitun rajan, niin hyppäämme kohtaan 2. Jos taas olemme jo
  5350. sallitun rajan alla, niin lopetamme. Täten mitä pienempi
  5351. MAX_VARIANCE on, sitä useammin pyörimme looppia ja sitä kauemmin
  5352. tämä kestää.
  5353.  
  5354. Siinäpä se. Vaan ei kuitenkaan. Hitain osuus nimittäin alkoi
  5355. nyt. Kvantisoitu paletti on kiva, vaan missä onkaan toivottu
  5356. värikuutio? Tai edes oikeat indeksit kullekin histogrammin värille? Ei
  5357. missään. Paletti on optimaalinen, mutta ei kerro lainkaan, mitkä värit
  5358. sitä ovat lähinnä. Jos haluamme tietää jollekin rgb-arvolle lähimmän
  5359. värin täytyy meidän yksinkertaisesti loopata lävitse paletin kaikki
  5360. 256 väriä ja selvittää mihin etäisyys on lyhin. Ja koska haluamme
  5361. värikuution outoihin tarkoituksiimme, merkitsee se tässä tapauksessa
  5362. 256:n värin läpikäyntiä 32768 kertaa...
  5363.  
  5364. Siitä vain laskemaan pojat. :) Fiksu idea voisikin olla tallentaa 64
  5365. kilon tulos tiedostoon, ettei sitä tarvitse pelin käynnistyessä
  5366. laskea. Säästätte hermojanne kummasti. Tosin, itse käytin kuutta
  5367. bittiä värille, jolloin sain huimaavan 262144-tavuisen värikuution. =)
  5368.  
  5369. No nyt jokainen varmasti osaa tehdä tuosta toimivan
  5370. kvantisointisysteemin. Ja esimerkkisorsa löytyy sitten
  5371. tiedätte-kyllä-mistä hakemistosta nimellä QUANT2.C. Ja hyvää joulua ja
  5372. onnellista uutta vuotta vain kaikille, sillä tässä kohtaa saan
  5373. kakkosversion valmiiksi, juuri tapaninpäivänä!
  5374.  
  5375.  
  5376. 7.10 VESA 2.0, rakenteet
  5377. ------------------------
  5378.  
  5379. Ohhoh, vaikuttaa taas siltä että on aika alkaa puurtamaan kun
  5380. kesäloman viimein alettua minunkin osaltani (neljä ekaa viikkoa
  5381. kesätöissä), en enää keksi hyviä selityksiä myöhästymiselle. Eli
  5382. homman nimi on SVGA-ohjelmointi ja tavan nimi VESA 2.0. Lähdekoodiakin
  5383. tulee juuri sopivasti jotta kauan toivottu asia, 640x480 -kokoisten
  5384. PCX-kuvien lataus ja näyttäminen ruudulla onnistuu. Ja jotta homma
  5385. olisi hieman haastavampi, muutamme 256-värisen paletillisen PCX-kuvan
  5386. vielä 16-bittiseksi ennen näyttämistä.
  5387.  
  5388. Vaan ensin tongimme hieman menneisyyttä. Ihmisten alkaessa hiljalleen
  5389. kypsymään 640x480 16-väriseen tilaan alkoivat näytönohjainvalmistajat
  5390. tekemään näyttökortteja, jotka tukivat 640x480-tilaa 256
  5391. värillä. Sitten pikkuhiljaa jokaiselta valmistajalta alkoi tippumaan
  5392. uusia kortteja, jotka pystyivät yhä parempiin näyttötiloihin ja olivat
  5393. mahdollisimman toimimattomia toistensa kanssa. Samoin on asian laita
  5394. nykyäänkin, suoraan korttia ohjelmoimalla päästään hyviin nopeuksiin,
  5395. mutta valitettavasti ei ole kovin mukavaa jos ohjelmasi toimii esim 2%
  5396. maailman SVGA:n alla toimivasta konekannasta, toistaiseksi.
  5397.  
  5398. Tilanteen muuttuessa yhä sekavammaksi markkinoilla hätiin riensi
  5399. VESA-niminen standardointijärjestö (Video Electronics Standards
  5400. Assocation tjsp.) ja luotiin yhtenäinen rajapinta jota käyttäen
  5401. voitaisiin ohjelmoida kaikkia sitä tukevia kortteja. Ja kehityksen
  5402. kehittyessä VESA on muodostunut varsin suosituksi tavaksi käyttää
  5403. korkeampia näyttötiloja. Etenkin versio 2.0 tarjoaa lähes lyömättömän
  5404. tavan tehdä näyttäviä graafisia sovelluksia. Ja miten tämä meihin
  5405. liittyy? No me tietenkin opettelemme käyttämään tätä rajapintaa!
  5406.  
  5407. Heti alkuun totean, että nämä kappaleet käsittelevät VESA 2.0:llaa
  5408. erittäin puutteellisesti. Oletan että näytönohjaimesi on VESA
  5409. 2.0-yhteensopiva ja tukee juuri määrättyä tilaa, 640x480 16-bittisillä
  5410. väreillä. Jätän 90% standardin funktioista ja mahdollisuuksista
  5411. käyttämättä. Täydellisen, lähes 1400-rivisen englanninkielisen
  5412. dokumentaation löytää esim. MBnetistä nimellä VBE20.ZIP tai Scitech
  5413. softwaren kotisivuilta, joka dokumenttia tietääkseni levittääkin,
  5414. osoitteessa www.scitechsoft.com. Scitech Display Doctor, eli entinen
  5415. UniVBE on myös SE ohjelma jos näytönohjaimesi VESA 2.0-tuki on
  5416. vajaavainen (esim. oman korttini 20 VESA 2.0-näyttötilaa laajenevat
  5417. tuon myötä 58:aan).
  5418.  
  5419. Toisaalta kun VESA 2.0:llan info-rakenteen osaa ja ymmärtää
  5420. moodi-infon rakenteen ja tietää miten VESA-yhteensopiva tila
  5421. asetetaan, pystyy tekemään lähes millaisen ohjelman tahansa, uupumaan
  5422. jää vain mahdollinen hardware-tason tuki skrollaukselle ja muulle
  5423. vastaavalle (jota todellinen guru ei tietenkään tarvitse).
  5424.  
  5425. Tämä luku kattaa kahden olennaisimman VESA 2.0:llaan liittyvän
  5426. rakenteen määritelmän ja kaikkien kenttien selostuksen. Myöhemmissä
  5427. luvuissa tutkimme hieman käyttöä. Tässä ensimmäinen, ns. VESA
  5428. information struct, suoraan VESA-enginestäni revittynä:
  5429.  
  5430. /* DJGPP laajentaa esim. tavun kokoiset rakenteen kentät 4-tavuisiksi
  5431. nopeuden takia ilman tätä määrettä, jos tämä puuttuu
  5432. palautettavista rakenteista on tieto täysin päin seiniä. Voit
  5433. kokeilla. */
  5434. #define PACKED __attribute__ ((packed))
  5435.  
  5436. typedef unsigned long int dword; /* Kaksoissana 4 tavua */
  5437. typedef unsigned short int word; /* Sana 2 tavua */
  5438. typedef signed long int s_dword; /* Etumerkillinen kaksoissana 4 tavua */
  5439. typedef signed short int s_word; /* Etumerkillinen sana 2 tavua */
  5440. typedef unsigned char byte; /* Tavu */
  5441. typedef unsigned bit; /* Bitti */
  5442.  
  5443. typedef struct {
  5444. byte sign[4] PACKED;
  5445. word version PACKED;
  5446. dword OEMstring PACKED;
  5447.  
  5448. bit fixedDAC : 1 PACKED;
  5449. bit VGAcompatible : 1 PACKED;
  5450. bit RAMDACtype : 1 PACKED;
  5451. bit reserved1 : 29 PACKED;
  5452.  
  5453. dword videomodeptr PACKED;
  5454. word totalmemory PACKED;
  5455.  
  5456. word OEMsoftwarerev PACKED;
  5457. dword OEMvendornameptr PACKED;
  5458. dword OEMproductnameptr PACKED;
  5459. dword OEMproductrevptr PACKED;
  5460. byte reserved2[222] PACKED;
  5461. byte OEMdata[256] PACKED;
  5462. } VesaInformation;
  5463.  
  5464. sign täytetään merkkijonolla "VESA", jos näytönohjaimesi tukee
  5465. VESA-tiloja. Jos halutaan ns. laajennettu tietokenttä, tämä tulee
  5466. ennen kutsua asettaa olemaan "VBE2", jolloin VESA 2.0-toteutus
  5467. ymmärtää täyttää uudet VESA 2.0:llan mukanaan tuomat kentät.
  5468.  
  5469. version taas on BCD-muotoinen versionumero (käytetään vain heksoja
  5470. 0h-9h, eli versio voi olla 0000h - 9999h). Ylemmät kaksi heksaa ovat
  5471. suurempi versionumero (haluamme sen olevan väh. 02h) ja alempi
  5472. pienempi (edellisessä versiossa 1.2 olisi luku 0102h).
  5473.  
  5474. OEMstring on reaalitilan (seg:off, eli segmentti ja offsetti)
  5475. osoitin NULL-päätteiseen valmistajan määrittelemään merkkijonoon.
  5476. OEMsoftwarerev kertoo version-kenttää vastaavasti versiotietoa, ja
  5477. OEMvendornameptr, OEMproductnameptr ja OEMproductrevptr taasen
  5478. ovat reaalitilan osoittimia valmistajan ja tuotteen nimeen sekä
  5479. tuotteen versionumeroon. VESA 2.0-yhteensopivien toteutusten tulisi
  5480. laittaa merkkijonot OEMdata-alueelle (ei ROM-muistiin tai muuallekaan
  5481. infoblokin 512 tavun ulkopuolelle), jolloin kopioidessasi infoblokin
  5482. ohjelman omaan datasegmenttiin DOS-muistialueelta voidaan nämä
  5483. pointteritkin muuttaa toimiviksi suojatun tilan osoitteiksi näytölle
  5484. tulostamista varten.
  5485.  
  5486. fixedDAC on 0 jos DAC on aina kuusi bittiä per värikomponentti, ja 1
  5487. jos se on vaihdettavissa kahdeksaan bittiin komponenttia kohden. DAC
  5488. hoitaa palettia.
  5489.  
  5490. VGAcompatible on 0 jos kontrolleri on VGA-yhteensopiva ja 1 jos
  5491. ei. Eli VGA-portit toimivat ja normaalit videotilat myös. Varmaan
  5492. jokaisessa PC-näytönohjaimessa 0.
  5493.  
  5494. RAMDACtype on 0 jos RAMDAC on "normaali", 1 tarkoittaa että
  5495. VESA-keskeytyksessä 09h tulee asettaa blank bitti. Käytännössä tämä ja
  5496. fixedDAC koskevat sinua vain jos käytät paletti-tilaa (256 väriä),
  5497. johon en tässä luvussa ainakaan puutu. Blank-bitti palettia
  5498. asetettaessa estää "lumisateen" ruudulla ajoittamalla paletin
  5499. muutokset muualle kuin piirron ajalle.
  5500.  
  5501. videomodeptr on kaikkein tärkein info version-kentän jälkeen, sillä se
  5502. osoittaa tilojen numerot sisältävään listaan, joka lopetetaan -1:llä
  5503. (0xFFFF). Ohjelman tehtävä on tarkistaa onko jokin moodi todella
  5504. olemassa, esimerkiksi UniVBE asettaa minulla tuonne tiloja joita ei
  5505. ole olemassa. Lisää virheentarkastuksesta alempana.
  5506.  
  5507. totalmemory kertoo käytössä olevan muistin määrän 64kilon palasissa,
  5508. eli 1 mega esimerkiksi on 16.
  5509.  
  5510. No nyt vielä se toinen tärkeä rakenne, moodi-info, jota voi pyytää
  5511. halutulle tilalle jos vain tietää sen numeron. Ja ne numerothan
  5512. löytyivät jo info-blokista, joten nyt vain rakennetta tutkimaan. Tämä
  5513. rakenne on hirviö:
  5514.  
  5515. typedef struct {
  5516. bit modesupported : 1 PACKED;
  5517. bit reserved : 1 PACKED;
  5518. bit TTYsupported : 1 PACKED;
  5519. bit colormode : 1 PACKED;
  5520. bit graphicsmode : 1 PACKED;
  5521. bit notVGAcompatible : 1 PACKED;
  5522. bit notVGAwindowmemory : 1 PACKED;
  5523. bit linearmodeavailable : 1 PACKED;
  5524. bit reserved1 : 8 PACKED;
  5525.  
  5526. bit Arelocatablewindows : 1 PACKED;
  5527. bit Areadablewindow : 1 PACKED;
  5528. bit Awritablewindow : 1 PACKED;
  5529. bit reserved2 : 5 PACKED;
  5530.  
  5531. bit Brelocatablewindows : 1 PACKED;
  5532. bit Breadablewindow : 1 PACKED;
  5533. bit Bwritablewindow : 1 PACKED;
  5534. bit reserved3 : 5 PACKED;
  5535.  
  5536. word windowgranularity PACKED;
  5537. word windowsize PACKED;
  5538. word windowAsegment PACKED;
  5539. word windowBsegment PACKED;
  5540. dword windowfunctionptr PACKED;
  5541. word bytesperscanline PACKED;
  5542.  
  5543. word horizontalresolution PACKED;
  5544. word verticalresolution PACKED;
  5545. byte characterwidth PACKED;
  5546. byte characterheight PACKED;
  5547. byte planes PACKED;
  5548. byte bitsperpixel PACKED;
  5549. byte banks PACKED;
  5550. byte memorymodel PACKED;
  5551. byte banksize PACKED;
  5552. byte imagepages PACKED;
  5553. byte reserved4 PACKED;
  5554.  
  5555. byte redbits PACKED;
  5556. byte redshift PACKED;
  5557. byte greenbits PACKED;
  5558. byte greenshift PACKED;
  5559. byte bluebits PACKED;
  5560. byte blueshift PACKED;
  5561. byte reservedbits PACKED;
  5562. byte reservedshift PACKED;
  5563.  
  5564. bit programmablecolorramp : 1 PACKED;
  5565. bit reservedbitsusable : 1 PACKED;
  5566. bit reserved5 : 6 PACKED;
  5567.  
  5568. dword physicalbasepointer PACKED;
  5569. dword offscreenmemoryoffset PACKED;
  5570. word offscreenmemorysize PACKED;
  5571. byte reserved6[206] PACKED;
  5572. } VesaModeInformation PACKED;
  5573.  
  5574. modesupported kertoo onko tila edes tuettu. (1 = tosi, 0 = epätosi)
  5575.  
  5576. TTYsupported kertoo tukeeko toteutus tekstin tulostusfunktioita
  5577.  
  5578. colormode on 1 jos tila on väritila, 0 jos mustavalkoinen
  5579.  
  5580. graphicsmode on 1 jos tila on grafiikkatila, 0 jos tekstitila
  5581.  
  5582. notVGAcompatible on 1 jos tila ei tue VGA-rekistereitä ja IO-portteja
  5583.  
  5584. notVGAwindowmemory on 0 jos banked-tilat ovat mahdollisia, 1 jos eivät
  5585.  
  5586. linearmodeavailable on 1 jos LFB on, 0 jos ei. Lisää termeistä alempana
  5587.  
  5588. A- ja B-alkuiset kolme kenttää, relocatablewindows, readablewindow ja
  5589. writeable window kertovat, voiko ikkunaa A tai B (riippuen nimen
  5590. alkukirjaimesta) siirtää, voiko sitä lukea ja voiko siihen
  5591. kirjoittaa. Nämä tiedot ovat ns. banked-tiloille, jossa videomuistia
  5592. katsotaan yleensä reaalitilassa olevista "ikkunoista", joita voidaan
  5593. sitten siirrellä. windowgranularity kertoo kilotavuina, kuinka
  5594. pienissä askelissa ikkunaa voidaan siirtää näyttömuistissa ja
  5595. windowsize kuinka suuri ikkuna on. windowAsegment ja B-vastaava
  5596. kertovat CPU-osoiteavaruudessa ikkunoiden osoitteet, A000h:ta
  5597. näytti minulla, eli ihan normaali VGA-muistisegmenttihän tuo yleensä
  5598. on (muista vain että segmentti kerrotaan kuudellatoista jotta saadaan
  5599. suojatun tilan offsetti, ja muista että selektori on _dos_ds, eli
  5600. pitää käyttää movedata, _farpoke* tai dosmemput-komentoja).
  5601.  
  5602. En puutu banked-tiloihin tässä luvussa, vaan oikaisen ja kerron
  5603. linear-tiloista. Banked-tiloissa tarvitaan paljon logiikkaa jotta
  5604. voidaan selvittää millaisissa pätkissä pitää näyttömuistissa liikkua,
  5605. kumpaa ikkunaa A vai B voi liikuttaa vai voiko molempia, saako niihin
  5606. kirjoittaa jne. Banked-tilassa kaksoispuskurin kopiointi ruudulle
  5607. tehdään siten, että siirretään ikkuna muistin alkuun, kirjoitetaan
  5608. ikkunan pituuden verran kaksoispuskurista, siirretään ikkunaa
  5609. eteenpäin granularity-muuttujan sallimissa rajoissa pitkin
  5610. näyttömuistia (esim. 64 kiloa kerrallaan), kirjoitetaan seuraava pala
  5611. jne.
  5612.  
  5613. Tarvittavalla älykkyysosamäärällä ja ehkä hitusella englannin kielen
  5614. taitoa varustettu yksilö kyllä pystyy tekemään banked-tuen
  5615. halutessaan. windowfunctionptr on reaalitilan osoitin (seg:off)
  5616. rutiiniin, joka vaihtaa nopeasti ikkunan sijaintia. Valitettavasti
  5617. tällaisen funktion kutsumiseen tehtävät valmistelut ovat sen verran
  5618. massiivista luokkaa (lue: en jaksa alkaa perehtymään asiaan), että en
  5619. ala niitä tässä esittelemään.
  5620.  
  5621. bytesperscanline on hyödyllinen tieto myös LFB:tä käytettäessä. Se
  5622. kertoo montako tavua yksi rivi näyttötilassa vie. Yleensä on
  5623. turvallista olettaa että se on suoraan vaakaresoluutio kerrottuna
  5624. tavuilla per pikseli, mutta joillakin korteilla, esim. Matroxilla
  5625. kuulemma nuo joskus ovat jotain ihan muuta. Tämä poistaa sinulta
  5626. mahdollisuuden käyttää koko kaksoispuskurin kopioimisen kerrallaan
  5627. näyttöpuskuriin, mikä tietenkin hidastaa ohjelmaa. Kannattaa ehkä
  5628. tehdä kaksi piirtofunktiota, joista vain toinen ottaa huomioon tämän
  5629. seikan.
  5630.  
  5631. horizontalresolution ja verticalresolution kertovat tilan vaaka- ja
  5632. pystyresoluution. Tekstitilassa nämä arvot ovat riveinä,
  5633. characterwidth ja characterheight -muuttujien ilmoittaessa merkin
  5634. leveyden ja korkeyden pikseleissä.
  5635.  
  5636. planes kertoo muistitasojen määrän tässä tilassa. Käytännössä näillä
  5637. on väliä vain 16-värisissä tiloissa ja mode-x:ää vastaavissa
  5638. VESA-tiloissa. Normaalisti tämä on 1.
  5639.  
  5640. bitsperpixel kertoo montako bittiä pikselille tässä tilassa on
  5641. varattu. 16-värisessä tämä on 4, 256-värisessä 8, 16 tai 15
  5642. highcolor-tiloissa ja 24 tai 32 truecolor -tiloissa. Jos käytät
  5643. tasoja, on bittien määrä / taso yleensä suoraan bitit/tasojen_määrä,
  5644. eli 4-tasoinen 256-värinen tila olisi 2 bittiä per taso.
  5645.  
  5646. banks kertoo montako scanline-bankkia kussakin tilassa
  5647. on. Esim. CGA:ssa kaksi ja Herculeksella neljä. Yleensä tämä on 1
  5648.  
  5649. memorymodel kertoo minkä tyyppinen muistimalli tilassa
  5650. on. Muistimalleja on 8:
  5651.  
  5652. 00h Tekstitila
  5653. 01h CGA
  5654. 02h Hercules
  5655. 03h Plane-tyyppinen
  5656. 04h Pakattu pikseli (yleinen 256-värinen)
  5657. 05h Non-chain 4, 256-värinen
  5658. 06h Suora värimalli (jota me tulemme käyttämään)
  5659. 07h YUV (värimalli, tyyliin RGB)
  5660. 08h-0Fh = Varattuja VESA:n määriteltäviksi
  5661. 10h-FFh = Varattuja valmistajan määriteltäviksi
  5662.  
  5663. banksize kertoo aiemmin kuvattujen scanline-bankkien määrän. Jos
  5664. jollakulla on jotain aavistusta näistä scanline-bankeista, tasoista ja
  5665. plane- pakatun pikselin, non-chain ja YUV-muistimalleista, olen
  5666. kiinnostunut tietämään, tässä moodi-info -rakenteessa on minulle
  5667. ainakin lähes outoja kenttiä ihan tarpeeksi.
  5668.  
  5669. imagepages kertoo montako ylimääräistä ruutua näyttömuistiin mahtuu,
  5670. eli ohjelma voi ladata useampia ruudullisia muistiin ja vaihdella
  5671. näiden välillä.
  5672.  
  5673. Sitten tuleekin varmaan meitä eniten kiinnostava osa. Suoran
  5674. värimallin tiloissa (memorymodel = 06h) 15-, 16-, 24- ja 32-bittisissä
  5675. tiloissa käytetään aina tietty määrä bittejä ilmaisemaan kutakin
  5676. värikomponentteja. Esimerkiksi 16-bittisessä tilassa yleensä on 5
  5677. bittiä punaiselle, sitten 6 vihreälle ja vielä 5 siniselle. Näin
  5678. meillä on 2^5 = 32 sävyä punaiselle ja siniselle ja 2^6 vihreälle
  5679. (jonka eri sävyjä silmä parhaiten erottaa). Jotta voimme koota ne
  5680. meidän täytyy vielä shiftata bittejä oikeille paikoilleen:
  5681.  
  5682. #define RGB16(r,g,b) ((r<<11) + (g<<5) + b)
  5683.  
  5684. Shiftaukset ja bittien määrä värikomponenttia kohden vaihtelevat ja
  5685. moodi-infossa jokaiselle värikomponentille on määritetty bittien määrä
  5686. ja shiftaus (redbits, redshift, bluebits...). Jos ohjelma ottaisi
  5687. kaikki mahdolliset yhdistelmät huomioon, olisi homma luultavasti
  5688. tuskallisen hidasta. Onneksi kuitenkin on varsin turvallista olettaa
  5689. seuraavia asioita:
  5690.  
  5691. 15-bittinen värimalli on 5:5:5, eli kaava on (r<<10)+(g<<5)+b
  5692.  
  5693. 16-bittinen värimalli on 5:6:5, eli kaava on (r<<11)+(g<<5)+b
  5694.  
  5695. 24-bittinen värimalli on 8:8:8, eli kaava on (r<<16)+(g<<8)+b
  5696.  
  5697. 32-bittinen värimalli on sama kuin 24, mutta 8 ylintä bittiä jäävät
  5698. käyttämättä ja näin ollen kaikkien pikselien muistiosoitteet ovat
  5699. neljällä jaollisia, paljon helpompaa kuin 24-bittisten tilojen
  5700. kolmella jaolliset.
  5701.  
  5702. Joskus tietenkin poikkeuksia voi olla ja on parasta tarkistaa ennen
  5703. moodin asettamista, täsmäävätkö shiftaukset ja bitit oletuksiin ja
  5704. tulostaa vaikka virheilmoitus, jos näin ei käy. Jos haluaa että
  5705. ohjelma varmasti toimii kaikilla korteilla, voi tehdä yleisluontoisen,
  5706. mutta kylläkin älyttömän hitaan muuttujia käyttävän systeemin.
  5707.  
  5708. Tiivistelmänä shift- ja bits-kentistä, että tee päätös bitsperpixelin
  5709. mukaan ja tarkista ennen moodiin siirtymistä vielä että shiftaukset
  5710. ovat oikeita moodi-infossa ja jos ne eivät täsmää, älä asetakaan
  5711. tilaa.
  5712.  
  5713. programmablecolorramp kertoo voiko väriramppia ohjelmoida. Jos arvo on
  5714. 0 ei sitä voi muuttaa, mutta jos se on ohjelmoitava, voit säätää
  5715. punaiselle, vihreälle ja siniselle haluamasi muotoisen
  5716. värirampin. Voit esim. tehdä siten, että jos punaiselle on 64 sävyä
  5717. niin sen sijaan että 0 on ei yhtään ja 64 on maksimi, saavutetaan
  5718. maksimi jo 32:ssa ja loppu on tasaisen punaista. Tästä on hyötyä
  5719. käytännössä vain gamma-korjauksessa ja joissakin
  5720. erikoisefekteissä. Jos asia kiinnostaa kannattanee tutustua
  5721. VBE20.TXT:n keskeytykseen 09h.
  5722.  
  5723. reservedbitsusable on 1 jos yli jäävät (esim 32-bittisessä tilassa
  5724. yleensä 8 ylimmäistä) bitit ovat käytettävissä.
  5725.  
  5726. physicalbasepointer on LFB:n aloitusoffset fyysisessä
  5727. muistiavaruudessa (eli alkaen muistin alusta aina maksimiin 4
  5728. gigaan). Tämä on tärkeimpiä kenttiä jos et käytä banked-tiloja.
  5729. Jos LFB ei ollut tuettu tämä kenttä on 0.
  5730.  
  5731. offscreenmemoryoffset kertoo kuinka pitkällä näyttömuistin alusta on
  5732. ohjelman käytettävissä oleva ylimääräinen näyttömuisti ja
  5733. offscreenmemorysize kertoo montako kilotavua siinä on.
  5734.  
  5735. Huhhuh. Tämä käy työstä. Pahoittelen jos näissä kenttien selostuksissa
  5736. on jotain epäselvyyksiä, ilmoitathan asioista joita et ymmärrä
  5737. minulle, en tiedä kuinka selvää tämä on sellaiselle joka ei vielä
  5738. VESA-tiloja ole ohjelmoinut.
  5739.  
  5740.  
  5741. 7.11 VESA 2.0, ensimmäiset keskeytykset
  5742. ---------------------------------------
  5743.  
  5744. No niin, struktit ovat siis hallinnassa? Sitten hommiin. VESA 2.0:llan
  5745. käyttö on tiivistettynä seuraavanlaista:
  5746.  
  5747. 1) Otetaan talteen info-structi ja tarkistetaan että kortti on
  5748. VESA 2.0-yhteensopiva (versionumerosta)
  5749.  
  5750. 2) Luetaan structin lopusta tuettujen VESA-tilojen numeroiden lista
  5751.  
  5752. 3) Tutkitaan mikä tuetuista numeroista on se tila jonka haluamme (eli
  5753. mennään yksitellen lävitse kaikki, pyydetään moodi-info, tutkitaan
  5754. ja jos ei ole oikea, jatketaan eteenpäin).
  5755.  
  5756. 4) Löydettyämme oikean asetetaan tila.
  5757.  
  5758. Sitten hieman VESA-rajapinnan toiminnasta. VESA 2.0 on, kuten
  5759. edeltäjänsä, vanhan video-keskeytyksen 10h alle tehty joukko
  5760. keskeytyksiä. Tunnus VESA-keskeytykselle on arvo 4Fh
  5761. ah-rekisterissä. Al-rekisteriin asetetaan halutun toiminnon numero
  5762. (joista kolme ensimmäistä, 00h-02h selitetään tässä). Systeemi toimii
  5763. myös 16-bittisissä sovelluksissa, mikä tarkoittaa käytännössä sitä,
  5764. että kun keskeytykselle annetaan muistialueen osoite, jonne
  5765. informaatiorakenne tulee kirjoittaa, täytyy sen sijaita
  5766. perusmuistissa. Keskeytyksen kutsun jälkeen ax:ssä palautetaan
  5767. seuraavanlainen palautusarvo:
  5768.  
  5769. AL == 0x4F: Funktio on tuettu
  5770. AL != 0x4F: Funktio ei ole tuettu
  5771.  
  5772. AH == 0x00: Funktiokutsu onnistunut
  5773. AH == 0x01: Funktiokutsu epäonnistui
  5774. AH == 0x02: Softa tukee funktiota, mutta rauta ei
  5775. AH == 0x03: Funktiokutsu virheellinen nykyisessä näyttötilassa
  5776.  
  5777. Kaikki AH:n arvot muut kuin 0 täytyy tulkita yleisinä virhetiloina,
  5778. sillä myöhemmissä VESA:n versioissa virhemäärittelyjä saattaa tulla
  5779. lisää. Jotta voisimme käyttää normaalien rekisterien lisäksi
  5780. tarvittavia segmenttirekisterejä, käytämme __dpmi_int:iä ja rakennetta
  5781. __dpmi_regs. Alla esimerkkimalli VesaInt-komennosta, jolle annetaan
  5782. vain toiminnon numero.
  5783.  
  5784. int VesaInt(byte function, __dpmi_regs *regs) {
  5785. regs->h.ah=0x4F;
  5786. regs->h.al=function;
  5787. __dpmi_int(0x10, regs);
  5788.  
  5789. if(regs->h.al != 0x4F) {
  5790. puts("Funktio ei tuettu");
  5791. return -1;
  5792. }
  5793.  
  5794. switch(regs->h.ah) {
  5795. case 0x00:
  5796. break;
  5797. case 0x01:
  5798. puts("Funktiokutsu epäonnistui!");
  5799. return 1;
  5800. case 0x02:
  5801. puts("Softa tukee funktiota, mutta rauta ei!");
  5802. return 2;
  5803. case 0x03:
  5804. puts("Funktiokutsu virheellinen nykyisessä videotilassa!");
  5805. return 3;
  5806. default:
  5807. puts("Tuntematon virhe!");
  5808. return 4;
  5809. }
  5810.  
  5811. return 0;
  5812. }
  5813.  
  5814. __dpmi_regs-rakenteen h-kentän alta löytyy tavun kokoiset palat ja
  5815. x-osasta 16-bittiset rekisterit (ainakin). Muita emme tarvikaan.
  5816.  
  5817. Nyt olemme tarpeeksi evästettyjä kutsumaan funktiota 0h, joka
  5818. palauttaa VESA-infoblokin. Ah täytetään 4Fh:lla, al asetetaan nollaksi
  5819. ja es:di asetetaan osoittamaan puskuriin minne infoblokki sijoitetaan.
  5820.  
  5821. Jotta saisimme info-struktuurin talteen, täytyy meidän ensin varata
  5822. muistia megan alapuolelta tarvittavat 512 tavua (keskeytys jolla info
  5823. palautetaan haluaa reaalitilan osoitteen ja tämän takia meidän täytyy
  5824. varata DOS-muistia). Sekä ohjelman omalta muistialueelta saman
  5825. verran tilaa, emme halua käsitellä tietoja dos-muistissa jonkin
  5826. farpeekb:n avulla. Lisäksi sign täytyy asettaa VBE2:ksi, jotta
  5827. keskeytys tietää että haluamme version 2 mukaista tietoa.
  5828.  
  5829. Alla esimerkkikoodista pala, joka varaa dos-muistin, asettaa
  5830. tarvittavat asiat ja kutsuu keskeytystä:
  5831.  
  5832.  
  5833. int VesaInit() {
  5834. __dpmi_regs regs;
  5835.  
  5836. dosbuffer=(dword)__dpmi_allocate_dos_memory(64, (int *)&dosselector);
  5837.  
  5838. if(dosbuffer==-1) {
  5839. puts("Ei tarpeeksi perusmuistia VESA-infoblokille!");
  5840. return 1;
  5841. }
  5842.  
  5843. dosbuffer*=16; /* muutetaan lineaariseksi osoitteeksi (seg*16) */
  5844.  
  5845. vesainfo=(VesaInformation *)malloc(sizeof(VesaInformation));
  5846. memcpy(vesainfo->sign, "VBE2", 4);
  5847.  
  5848. dosmemput(vesainfo, sizeof(VesaInformation), dosbuffer);
  5849.  
  5850. regs.x.es=dosbuffer/16;
  5851. regs.x.di=0;
  5852. if(VesaInt(0x00, &regs)) {
  5853. puts("Virhe VESA-keskeytyksessä!");
  5854. return 1;
  5855. }
  5856.  
  5857. dosmemget(dosbuffer, sizeof(VesaInformation), vesainfo);
  5858.  
  5859. if(strnicmp(vesainfo->sign, "VESA", 4)!=0 || vesainfo->version<0x0200) {
  5860. puts("not found!");
  5861. return 1;
  5862. }
  5863.  
  5864. puts("found!");
  5865. return 0;
  5866. }
  5867.  
  5868. void VesaDeinit() {
  5869. __dpmi_free_dos_memory(dosselector);
  5870. }
  5871.  
  5872. Kuten selvästi näkyy, homma on varsin helppoa. Varataan muistit,
  5873. laitetaan "VBE2"-pala, kopioidaan DOS-muistiin, asetetaan rekisterit,
  5874. keskeytys, kopioidaan takaisin omaan muistiin ja se on siinä. Tutkimme
  5875. palautusarvon ja jos onnistuimme voimme jatkaa moodi-infojen
  5876. tiirailuun.
  5877.  
  5878. Moodi-infon lukemiseksi vain matkaamme lävitse halutun
  5879. alueen. Kaikkein varmin tapa tutkimiseen on hakea tieto
  5880. perusmuistista, jos jostain syystä lista ei olisikaan infoblokin
  5881. alueella, vaan jossain muualla. Käytämme vain dosmemget:iä niin
  5882. monesti että vastaan tulee -1 ja joka arvolle katsomme moodi-infon.
  5883. Allaoleva esimerkki etsii 640x480-tilan 16-bittisillä väreillä ja
  5884. asettaa tilan, varaa kaksoispuskurin ja palauttaa sen osoitteen tai
  5885. NULL jos ilmaantui virhe. Varsinainen monitoimityökalu, siis.
  5886.  
  5887. Ennen kuitenkin tutustumme käsitteeseen LFB, sillä se on se mitä
  5888. käytämme. Edellisissä versioissa käytettiin VGA-muistia, joka osoitti
  5889. aina haluttuun palaan videomuistia. Muistiin täytyi siis käydä käsiksi
  5890. 64 kilon palasissa, mikä oli varsin tuskallista touhua. Versio 2.0 toi
  5891. kuitenkin mukanaan suojatun tilan käyttäjille uuden asian,
  5892. LFB:n. Systeemi on sellainen, että videomuisti sijoitetaan jonnekin
  5893. osaan muistiavaruutta. Homma on siis sama kuin osoitteen 0xA0000
  5894. kanssa, mutta nyt paikka on yleensä jossain 300 megan paikkeilla tai
  5895. kauempana ja kokoa on 64 kilon sijasta näyttömuistin verran, omalla
  5896. koneellani 4 megaa.
  5897.  
  5898. Osoitteen saimmekin jo infoblokissa, mutta jotta voisimme käyttää tätä
  5899. osoitetta, täytyy muistisuojauksista päästä eroon. Tarvitsemme siis
  5900. selektorin joka osoittaa halutun muistiosoitteen alkuun ja joka on
  5901. asetettu toimivaksi tarvittavan pitkälle matkalle, jottemme saa
  5902. segmentation faultia muistialueen ohi kirjoittamisen takia
  5903. kopioidessamme kaksoispuskuria ruudulle. Alla suoraan jostain pöllitty
  5904. funktio (kiitoksia tekijälle) mappaamiseen ja mappauksen poistoon:
  5905.  
  5906. /* Funktio ottaa fyysisen osoitteen muistiavaruudessa (physaddr) sekä
  5907. koon tavuissa (size) ja palauttaa linear-muuttujassa varatun alueen
  5908. lineaarisen offsetin (linear), sekä selektorin jota käytetään kun
  5909. halutaan käsitellä muistialuetta (segment, tätä käytettäessä offset
  5910. aina 0). Funktio palauttaa 0 jos onnistui, 1 jos ei */
  5911. int VesaMapPhysical(dword *linear, s_dword *segment,
  5912. dword physaddr, dword size) {
  5913. __dpmi_meminfo meminfo;
  5914.  
  5915. meminfo.address = physaddr;
  5916. meminfo.size = size;
  5917.  
  5918. if(__dpmi_physical_address_mapping(&meminfo) != 0)
  5919. return 1;
  5920.  
  5921. linear[0]=meminfo.address;
  5922. __dpmi_lock_linear_region(&meminfo);
  5923. segment[0]=__dpmi_allocate_ldt_descriptors(1);
  5924.  
  5925. if(segment[0]<0) {
  5926. segment[0]=0;
  5927. __dpmi_free_physical_address_mapping(&meminfo);
  5928. return 1;
  5929. }
  5930.  
  5931. __dpmi_set_segment_base_address(segment[0], linear[0]);
  5932. __dpmi_set_segment_limit(segment[0], size-1);
  5933.  
  5934. return 0;
  5935. }
  5936.  
  5937. Eli käytännössä tarvitaan vain palautettua segmenttiä, offset segmentin
  5938. alla on suoraan (y*leveys+x)*tavuja_per_pikseli, eli mitään lukujen
  5939. lisäyksiä ei tule, kuten asian laita VGA-tilojen kanssa on (0xA0000).
  5940. Sitten tietenkin vapautus loppuun:
  5941.  
  5942. /* Tämä taasen vapauttaa muistin käsittelyyn varatut kahvat, kutsutaan
  5943. kun palataan VESA-tilasta. */
  5944. void VesaUnmapPhysical(dword *linear, s_dword *segment) {
  5945. __dpmi_meminfo meminfo;
  5946.  
  5947. if(segment[0]) {
  5948. __dpmi_free_ldt_descriptor(segment[0]);
  5949. segment[0]=0;
  5950. }
  5951.  
  5952. if(linear[0]) {
  5953. meminfo.address=linear[0];
  5954. __dpmi_free_physical_address_mapping(&meminfo);
  5955. linear[0]=0;
  5956. }
  5957. }
  5958.  
  5959. Hieno homma, vaan mitenkäs näitä käytetään? No näemme kohta senkin,
  5960. hieman vain kärsivällisyyttä. Ensin tutkimme funktiot 01h ja 02h.
  5961.  
  5962. 01h palauttaa cx-rekisterissä annettavan moodin tiedot, puskurin
  5963. ollessa jälleen es:di. Voimme käyttää mainiosti alustusfunktiossa
  5964. varattua muistialuetta dosbuffer. Käyttö on naurettavan helppoa:
  5965.  
  5966. /* Palauttaa 1 jos moodi ei ole olemassa */
  5967. int VesaGetModeInfo(word mode, VesaModeInformation *info) {
  5968. __dpmi_regs regs;
  5969.  
  5970. regs.x.cx=mode;
  5971. regs.x.es=dosbuffer>>4;
  5972. regs.x.di=0;
  5973. if(VesaInt(0x01, &regs))
  5974. return 1;
  5975.  
  5976. dosmemget(dosbuffer, sizeof(VesaModeInformation), info);
  5977.  
  5978. return 0;
  5979. }
  5980.  
  5981. Sitten vain tutkimme halutut arvot moodi-infosta ja jos oikea on
  5982. kohdalla, asetetaan tila. Keskeytyksen numero on 02h ja bx:ssä
  5983. annetaan tarvittava tieto moodista. Mukaan pakataan tieto haluammeko
  5984. lineaarisen tilan vain banked-tilan ja josko näyttömuisti tulee
  5985. tyhjentää ennen vaihtoa. Bitit on järjestelty näin:
  5986.  
  5987. 0-8 Moodin numero
  5988. 9-13 Nollaa (säästetty tulevaisuutta varten)
  5989. 14 0 jos käytetään banked-tilaa, 1 jos lineaarinen, eli LFB-tila
  5990. 15 0 jos tyhjennetään näyttömuisti, 1 jos ei
  5991.  
  5992. eli vaikkapa:
  5993.  
  5994. #define MODEFLAG_BANKED 0x0000
  5995. #define MODEFLAG_LINEAR 0x4000
  5996. #define MODEFLAG_CLEAR 0x0000
  5997. #define MODEFLAG_PRESERVE 0x8000
  5998.  
  5999. /* Jälleen ei-nolla arvo tarkoittaa virhettä */
  6000. int VesaSetMode(int mode) {
  6001. __dpmi_regs regs;
  6002.  
  6003. regs.x.bx = mode | MODEFLAG_LINEAR | MODEFLAG_CLEAR;
  6004. if(VesaInt(0x02, &regs))
  6005. return 1;
  6006.  
  6007. return VesaMapPhysical(&vesalfb_linear, &vesalfb_segment,
  6008. vesamodeinfo[modenum].physicalbasepointer,
  6009. vesamodeinfo[modenum].bytesperscanline*
  6010. vesamodeinfo[modenum].verticalresolution);
  6011. }
  6012.  
  6013. No niin, mitäs tässä enään on jäljellä. No ihan oikeassa olet, eipä
  6014. kai mitään. Vai häh? Ai mikä? Esimerkki?!? No kai se nyt vielä tähän
  6015. mahtuu. Täydelliset sorsat ja määrittelyt voit kaivaa tiedostosta (kai
  6016. nyt flipin osaa tehdä kuka tahansa kun tietää selektorin ja
  6017. näyttömuistin koon?) VESA20.C. Ja sitten miten homma todella hoidetaan
  6018. voit lukea seuraavasta luvusta. Mutta se moodin asetus:
  6019.  
  6020. /* Palauttaa 0 jos onnistui */
  6021. word * VesaSet640x480_16() {
  6022. VesaModeInformation modeinfo;
  6023.  
  6024. s_word mode=0;
  6025. dword addr=vesainfo->videomodeptr;
  6026.  
  6027. while(mode!=-1) {
  6028. dosmemget(addr, 2, &mode);
  6029. addr+=2;
  6030. if(mode!=-1) {
  6031. /* Jos virhe tulee jatketaan seuraavaan */
  6032. if(VesaGetModeInfo(mode, &modeinfo))
  6033. continue;
  6034.  
  6035. if(modeinfo.linearmodeavailable &&
  6036. modeinfo.horizontalresolution==640 &&
  6037. modeinfo.verticalresolution==480 &&
  6038. modeinfo.bitsperpixel==16) {
  6039. if(VesaSetMode(mode, &modeinfo))
  6040. return NULL;
  6041.  
  6042. vesascreen = (word *)malloc(640*480*sizeof(word));
  6043. return vesascreen;
  6044. }
  6045. }
  6046. }
  6047. return NULL;
  6048. }
  6049.  
  6050. Ja vielä se deinitti taitaapi puuttua.
  6051.  
  6052. void VesaReset() {
  6053. textmode(0x03);
  6054. VesaUnmapPhysical(vesalfb_linear, vesalfb_segment);
  6055. free(vesascreen);
  6056. }
  6057.  
  6058. Sitten vain niputetaan kaikki mitä on vastaan tullut, lisätään hieman
  6059. suolaa ja nautitaan PCX-kuvan kera. Hyvää ruokahalua!
  6060.  
  6061.  
  6062. 7.12 Miten se todella pitäisi tehdä
  6063. -----------------------------------
  6064.  
  6065. Aiemmat kaksi lukua vain raapaisivat pintaa VESA-ohjelmoinnin
  6066. saralla. Tärkeimmät funktiot kuitenkin on selostettu ja niiden
  6067. pohjalta on jo varsin helppoa tehdä oma engine. Esimerkkikoodia ei
  6068. kannata suoraan käyttää, sillä se on käytännössä vain omasta
  6069. enginestäni kokoon parsittu kevytversio, joka sisältää tarpeeksi
  6070. esimerkkejä eri asioiden teosta, jotta oman systeemin teko
  6071. helpottuisi. Tässä luvussa hieman siitä miten järjestelmän voisi
  6072. toteuttaa.
  6073.  
  6074. Ensimmäiseksi kannattaa erotella VESA-rutiinit järkeviin
  6075. palasiin. Itselläni esimerkiksi yhdessä tiedostossa on Vesa-rutiinit
  6076. inforakenteiden lukemiseksi muistiin ja olennaisimmat funktiot, kuten
  6077. VesaInt. Toinen osa sitten hoitaa graafisen puolen, eli asettaa
  6078. halutun näyttötilan, ja hoita näytönpäivityksen. Luonnollisesti
  6079. funktioiden määrittelyt ja rakenteet ovat omissa .h-tiedostoissaan ja
  6080. koodi ja muuttujat taas .c-osissa. Ei ole yhtään tyhmä idea tehdä
  6081. kirjastosta yhtä pakettia, esim libvesa.a, jonka DJGPP:n
  6082. lib-hakemistoon sijoittamisen jälkeen voi sisällyttää johonkin
  6083. ohjelmaan pelkästään parilla #include-lauseella ja -lvesa
  6084. -parametrilla.
  6085.  
  6086. Toiseksi erittäin tärkeä asia on tehdä systeemistä tarpeeksi joustava,
  6087. jotta siitä olisi todella jotain hyötyä. Nykyisellään näytönohjainten
  6088. kirjo ja resoluutioiden määrä on niin suuri, että jo tästä syystä
  6089. VESA-esimerkki lienee ensimmäisiä tutoriaalin ohjelmia, joka ei tule
  6090. koskaan toimimaan kaikilla koneilla. Hyvä järjestelmä hoitaa asiat
  6091. siten, että kutsuva ohjelma on tyystin tietämätön siitä mitä raudassa
  6092. on. Unelmasysteemi on sellainen, että initialisoit moottorin alussa ja
  6093. deinitialisoit lopussa. Käytön aikana sinulla on puskuri jonne voit
  6094. laittaa grafiikan ja käsky jolla tavara heitetään näytölle. Ja
  6095. systeemin tulisi toimia näin vaikka alla ei edes olisi todellista
  6096. VESA-yhteensopivaa rautaa.
  6097.  
  6098. Miten tämän pystyy sitten saavuttamaan? No initit ja deinitit on
  6099. helppo hoitaa, mutta että vielä universaali piirtotapa, vaikka alla
  6100. olisi ihan toinen resoluutio ja värimäärä kuin mitä ohjelma luulee,
  6101. onko tämä mahdollista? Vastaus on myöntävä. Eikä ratkaisu edes ole
  6102. kovin vaikea. Taikasana: funktio-osoittimet (C++:ssalla
  6103. virtuaalimetodit ja eri resoluutioiden periyttäminen perusluokasta).
  6104.  
  6105. Oma systeemini sisältää tällaisen muuttujan:
  6106.  
  6107. void VESAREFRESH (*VesaScreenRefresh)()=NULL;
  6108.  
  6109. Käytännössä VESAREFRESH on vain määritelty tyhjäksi (#define
  6110. VESAREFRESH), mutta yllä esitetty systeemi osoittautuu aika
  6111. käytännölliseksi kun se haluttu tila ei löydykään. Systeemi toimii
  6112. näin:
  6113.  
  6114. Oletetaan että pelini on tarkoitus toimia 320x200-resoluutiossa
  6115. 32-bittisillä väreillä. Systeemi on unelma, koska yksi pikseli on
  6116. dwordin kokoinen (=nopeaa) ja jokainen värikomponentti on tavun verran
  6117. ja ylimmäisen tavun jäädessä tyhjäksi. Vaan, ongelmana on, että vain
  6118. hyvin harvalla on 32-bittinen halutun resoluution näyttötila. No,
  6119. ongelma on helposti ratkaistu:
  6120.  
  6121. Yhden flipin sijasta tehdäänkin _useita_ päivitysrutiineja. Yksi
  6122. muuntaa värit 24-bittisiksi lennossa (käytännössä yhtä nopea kuin aito
  6123. 32-bittinen tilakin), toinen muuttaa ne 16-bittisiksi, yksi voi jopa
  6124. käyttää korkean resoluution 32-bittistä tilaa emuloimaan joistakin
  6125. korteista puuttuvia 320x200-kokoisia korkeavärisiä tiloja (oma
  6126. Matroxini esim. tukee normaalisti tiloja vain resoluutiosta 640x480
  6127. ylöspäin). Varalle voidaan vielä tehdä kvantisoitua tilaa tai
  6128. harmaasävyjä käyttävä, 100% VGA-yhteensopiva flippi, joka käyttää
  6129. 256-väristä tilaa. Initin aikana vain asetetaan VesaScreenRefresh
  6130. osoittamaan siihen päivitysfunktioon mitä asetettu näyttötila vastaa.
  6131.  
  6132. Ja kun flippi hoitaa kaksoispuskurin muuntamisen sellaiseen muotoon
  6133. että se on näytettävissä sillä hetkellä käytössä olevalla parhaiten
  6134. oikeaa vastaavalla näyttötilalla, ei ohjelman tarvitse kuin piirtää
  6135. tavara vesascreen-puskuriin, joka on aina saman suuruinen ja jossa on
  6136. aina sama värimäärä, sekä kutsua VesaScreenRefresh-funktiota. Näin
  6137. funktion ollessa oikea flippi tulee tavara ruudulle vaikka käyttäjällä
  6138. ei sattuisikaan olemaan 320x200 32bit -tilaa, vaan esim. 320x200
  6139. 24bit. Ihanaa. Ja tässä pätkä omasta koodistani:
  6140.  
  6141. int VesaLowresInit(int flags) {
  6142. int loop;
  6143.  
  6144. if(!(vesaflag & VESA_INITIALIZED))
  6145. VesaError("Vesa low-resolution mode init", "Engine not initialized!");
  6146.  
  6147. vesascreen=(pointer)Jmalloc(320*200*sizeof(dword));
  6148. memset(vesascreen, 0, 320*200*sizeof(dword));
  6149.  
  6150. for(loop=0; loop<vesamodes; loop++)
  6151. if(vesamodeinfo[loop].linearmodeavailable &&
  6152. vesamodeinfo[loop].horizontalresolution==320 &&
  6153. vesamodeinfo[loop].verticalresolution==200 &&
  6154. vesamodeinfo[loop].bitsperpixel==32) {
  6155. printf("Initializing mode 320x200 32-bit colors...\n");
  6156. vesacurmodeinfo=&vesamodeinfo[loop];
  6157. VesaScreenRefresh=VesaLowres_320x200x32;
  6158. VesaSetMode(loop);
  6159. vesamode=VESAMODE_320x200x32;
  6160. return vesamode;
  6161. }
  6162.  
  6163. for(loop=0; loop<vesamodes; loop++)
  6164. if(vesamodeinfo[loop].linearmodeavailable &&
  6165. vesamodeinfo[loop].horizontalresolution==640 &&
  6166. vesamodeinfo[loop].verticalresolution==400 &&
  6167. vesamodeinfo[loop].bitsperpixel==32) {
  6168. printf("Initializing 640x400 to 320x200 emulation"
  6169. " with 32-bit colors...\n");
  6170. vesacurmodeinfo=&vesamodeinfo[loop];
  6171. VesaScreenRefresh=VesaLowres_640x400x32;
  6172. VesaSetMode(loop);
  6173. vesamode=VESAMODE_640x400x32;
  6174. return vesamode;
  6175. }
  6176.  
  6177. for(loop=0; loop<vesamodes; loop++)
  6178. if(vesamodeinfo[loop].linearmodeavailable &&
  6179. vesamodeinfo[loop].horizontalresolution==640 &&
  6180. vesamodeinfo[loop].verticalresolution==480 &&
  6181. vesamodeinfo[loop].bitsperpixel==32) {
  6182. printf("Initializing 640x480 to 320x200 emulation"
  6183. " with 32-bit colors...\n");
  6184. vesacurmodeinfo=&vesamodeinfo[loop];
  6185. VesaScreenRefresh=VesaLowres_640x480x32;
  6186. VesaSetMode(loop);
  6187. vesamode=VESAMODE_640x480x32;
  6188. return vesamode;
  6189. }
  6190.  
  6191. for(loop=0; loop<vesamodes; loop++)
  6192. if(vesamodeinfo[loop].linearmodeavailable &&
  6193. vesamodeinfo[loop].horizontalresolution==320 &&
  6194. vesamodeinfo[loop].verticalresolution==200 &&
  6195. vesamodeinfo[loop].bitsperpixel==24) {
  6196. printf("Initializing mode 320x200 24-bit colors...\n");
  6197. vesacurmodeinfo=&vesamodeinfo[loop];
  6198. VesaScreenRefresh=VesaLowres_320x200x24;
  6199. VesaSetMode(loop);
  6200. vesamode=VESAMODE_320x200x24;
  6201. return vesamode;
  6202. }
  6203.  
  6204. printf("Initializing mode 320x200 with greyscale palette...\n");
  6205. VesaScreenRefresh=VesaLowres_320x200x8;
  6206. textmode(0x13);
  6207. VesaGreyscalePalette();
  6208. vesamode=VESAMODE_320x200x8;
  6209. return vesamode;
  6210. }
  6211.  
  6212. Ja jokainen voi arvata kuinka monelta harmaalta hiukselta systeemi on
  6213. minut säästänyt, kun toinen demokooderimme omistaa näytönohjaimen,
  6214. jossa on vain 24-bittisiä tiloja ja minulla on näyttis, joka taasen
  6215. tukee vain 32-bittisiä.
  6216.  
  6217. Vihjeenä nopeaan greyscale-flippiin on, että kun kerran komponentteja
  6218. on kolme ja jakaminen muilla kuin kahden potensseilla on tuhottoman
  6219. hidasta, käytä paletista vain ensimmäiset 192 väriä musta-valkoinen
  6220. liukuun (asteen muutos kolmen värin jälkeen), ja jaa komponenttien
  6221. summa kolmen sijasta neljällä. Voi ehkä olla hyödyllistä vääntää
  6222. nousukäyrää hieman siten, että vaaleampiin sävyihin päästään
  6223. nopeammin.
  6224.  
  6225. Sen lisäksi että moottori tukee useita eri näyttötiloja saman puskurin
  6226. esittämiseen, olisi virheensietokyvyn olla niin hyvä kuin se voi
  6227. olla. Esimerkkiohjelman sietokyky on jo aika korkea, mutta parempikin
  6228. se vielä voisi olla. Ongelma on myös se milloin ei enää voida
  6229. jatkaa. Jokin pelin alkulogon näyttäminen korkeammassa resoluutiossa
  6230. ei vielä exit:tiä vaadi, mutta jos koko peli vaatii paljon värejä ja
  6231. tarkkuutta, ei pelkkä virheilmoitus riitä.
  6232.  
  6233. VESA-enginen seuraksi voi olla hyvä idea kerätä myös mittava joukko
  6234. sekalaisia apufunktioita, kuten spritejen piirrot, motion blur ja
  6235. muuta sellaista pientä kivaa. Ehkä jopa kuvatiedostojen saumaton
  6236. integrointi voisi olla hyödyllistä, sillä sekalaisten PCX-laturien ja
  6237. piirtorutiinien kaapiminen kovalevyn uumenista alkaa viimeistään
  6238. silloin olla tuskastuttavaa, kun käytät 32-bittisiä tiloja joskus,
  6239. toisinaan 16-bittisiä ja välillä vain korkean resoluution 256-värisiä
  6240. tiloja. Hyvä grafiikkamoottori säästää vaivalta ja sinne muutokset on
  6241. helppo tehdä keskitetysti, jonkin nerokkaan optimoinnin lisääminen on
  6242. helpompi tehdä yhteen kirjastoon kuin jokaiseen optimoitua funktiota
  6243. käyttävään ohjelmaan.
  6244.  
  6245. En minä taida enempää porista, lähettäkääpäs ihmiset kommentteja tästä
  6246. SVGA-osiosta, sillä jälleen kirjoittelen tätä side silmillä, en minä
  6247. tiedä ymmärrättekö tästä mitään, minähän vain teen tätä. ;-D Nyt
  6248. taidankin siirtyä aloittelemaan "Asioiden taustaa"-osiota. Lykkyä tykö
  6249. grafiikkasysteemien tekoon.
  6250.  
  6251.  
  6252. 8.9 Polygoneista ja niiden fillauksesta
  6253. ---------------------------------------
  6254. 8.8 Lisää kivaa - zoomaus
  6255. -------------------------
  6256. 8.7 Prekalkattuja pintoja - ja tunneli
  6257. --------------------------------------
  6258. 8.6 Plasma tekee comebackin - wobblerit
  6259. ---------------------------------------
  6260. 8.5 Musiikkijärjestelmistä
  6261. --------------------------
  6262. 8.4 Vektorit pelimaailmassa
  6263. ---------------------------
  6264. 8.3 Motion blur - sumeeta menoa
  6265. -------------------------------
  6266. 8.2 Läpinäkyvyys ja sen vaihtoehto - shadebobit
  6267. -----------------------------------------------
  6268. 8.1 Datatiedostot - miten?
  6269. --------------------------
  6270.  
  6271. No niin, on aika siis viimein aloittaa se mitä pitkän aikaa jo olen
  6272. suunnitellut. Eli lukijoiden pakottaminen koodaamaan rutiininsa
  6273. varmasti itse. :) Tästä lähtien ei koodia heru yhtä tai kahta riviä
  6274. enempää, joudutte tekemään tuttavuutta libc:n dokumentaation kanssa
  6275. enemmänkin (komento 'info libc').
  6276.  
  6277. Eli homman nimi on datatiedostot. Ja helppoahan tämä. Idea on luoda
  6278. oma pieni "levyjärjestelmä" ja laittaa tiedostot sen
  6279. alaisuuteen. Käytännössä FAT:in tyyppistä varausyksikkö-systeemiä ei
  6280. kannattane luoda, ellei aio myös kirjoittaa paljon tiedostoja ja
  6281. lisätä datatiedostossa oleviin tiedostoihin uutta tavaraa. Yleensä
  6282. riittää pelkkä read-only järjestelmä, johon vain pakataan senhetkiset
  6283. erilliset tiedostot haluttaessa.
  6284.  
  6285. Kaikkein yksinkertaisin on tehdä headeri, joka sisältää tiedoston
  6286. nimen, sen koon ja sijainnin datatiedostossa. Datatiedoston rakenne on
  6287. vain sellainen, että ensin tulee tiedostojen määrä datatiedostossa, ja
  6288. sitä seuraa määrän verran mainitun kaltaisia headereita, jotka luetaan
  6289. muistiin. Sitten vain kun ohjelma haluaa lukea jonkinnimisen tiedoston
  6290. datatiedostosta, etsitään tiedostonimeä vastaava headeri ja mennään
  6291. fseekillä headerin kertomaan sijaintiin ja luetaan tiedot. Luku voi
  6292. olla puhdas "kaikki-tai-ei-mitään", eli että tiedosto luettaisiin vain
  6293. kokonaisuudessaan puskuriin joka palautetaan (tyyliin
  6294. bufferi=GetFile("dummy.fil")).
  6295.  
  6296. Toinen mahdollisuus on luoda normaaleja f*-funktioita vastaavat
  6297. funktiot datatiedostossa seikkailemiseen (minulla esim. on jfopen,
  6298. jfclose, jfread, jfgetc ja jfseek). Jos aiotaan tukea usean tiedoston
  6299. yhtäaikaista aukipitoa ja liikkumista yhden tiedoston sisällä
  6300. muihinkin suuntaan kuin eteenpäin täytyy myös toteuttaa
  6301. tiedostokahva-järjestelmä. Tämäkin on helppoa, kahvahan on käytännössä
  6302. vain rakenne, joka kertoo missä kohdassa tiedostoa ollaan ja kuinka
  6303. pitkä tiedosto on jne. Yksinkertaisimmillaan systeemissäsi kahva
  6304. sisältää tiedostoheaderin numeron, josta voidaan noutaa pituus ja
  6305. nimi, sekä senhetkisen sijainnin. Lukufunktiot ottavat sitten huomioon
  6306. sen mistä kohdasta nyt pitäisi lukea.
  6307.  
  6308. Jos koodaat kahvat siten, että käytät fseekiä joka lukukerralla
  6309. siirtyäksesi oikeaan paikkaan ja päivität sitten kahvan sijaintia
  6310. luetun palan koon verran eteenpäin, käy tavupohjaisille lukijoille
  6311. kalpaten. Normaalisti fgetc nimittäin lukee hieman eteenpäin ja
  6312. seuraavalla kutsukerralla luettava tavu on jo puskurissa tallessa ja
  6313. se tulee nopeasti muistista. Vaan fseekin jälkeen puskuri tyhjätään ja
  6314. päädyt todella lukemaan tiedostoa tavu kerrallaan, etkä esim. 256
  6315. tavua kerrallaan, kuten asian laita normaalisti puskuroituna
  6316. olisi. Vaikutus näkyy esim. demossamme Bleedissä - 20 kertaa normaalia
  6317. hitaampi PCX-laturi.
  6318.  
  6319. Nopeuden vuoksi kannattaa joko toteuttaa itse puskurointi, eli kahvaan
  6320. myös pieni, esim. 20-tavuinen puskuri, joka kertoo seuraavien tavujen
  6321. sisällön. Näin todellinen levyhaku tapahtuu 20 kertaa harvemmin. Tai
  6322. ainakin tarkistaa ettei fseekata ellei oikean datatiedostoon
  6323. osoittavan kahvan sijainti ole muuttunut.
  6324.  
  6325. Hyvä idea edellisten estämiseksi voi myös olla yksinkertainen
  6326. järjestelmä, jossa fopen korvataan funktiolla, joka avaa uuden
  6327. FILE-kahvan datatiedostoon ja siirtää sen osoittamaan tiedoston
  6328. aloituspaikkaan datatiedostossa. Nyt lukufunktioihin tarvitaan vain
  6329. tarkistus, ettei lueta tiedoston pituutta pidemmälle eteenpäin
  6330. (LueKirjain vain lisäisi sijaintilaskuria yhdellä, palauttaisi EOF:in
  6331. jos oltaisiin tiedoston lopussa ja muussa tapauksessa palauttaisi
  6332. suoraan fgetc(handle):n). Fseek-funktiokin vain muuttaisi sijainnin
  6333. tiedostossa sijainniksi datatiedostossa (lisäisi alkuoffsetin jne.).
  6334.  
  6335. EXE-tiedostoon tallettaminenkin on helppoa. Tunget vain datan EXE:n
  6336. loppuun, sitten headerit ja lopuksi tiedostojen määrän. Eli käännät
  6337. normaalin datatiedoston toisinpäin ja lätkäiset EXE:n perään. Lukiessa
  6338. sitten käytät aluksi fseek(handle, -4, SEEK_END), jolloin pääset
  6339. käsiksi neljään viimeiseen tavuun jotka sisältävät headereiden määrän,
  6340. sitten seekkaat taas taaksepäin päästäksesi headerien alkuun ja luet
  6341. itsesi loppuun (tai siis, neljä tavua vaille, headerit loppuvat ja
  6342. viimeisenä tulisi vielä niiden määrä).
  6343.  
  6344. Tiedostojen lisääminen fiksusti datatiedostoon on itseasiassa astetta
  6345. vaikeampi homma kuin lukeminen (jos siis teet sen helpolla tavalla, et
  6346. käytä puskurointeja tai monimutkaista logiikkaa). Järkevää voi olla
  6347. tehdä ohjelma, jolle annetaan esim. tiedoston nimi jossa on lista
  6348. mukaan otettavista tiedostoista. Sen jälkeen ohjelma lukee jokaisen
  6349. tiedoston nimen headeriin ja pituuden, sekä laskee offsetit.
  6350. Ensimmäisen tiedoston sijainti datatiedostossa on tiedostojen määrän
  6351. kertovan muuttujan ja headerien jälkeen, eli laskennallisesti:
  6352.  
  6353. sizeof(headerien_määrä)+sizeof(header)*headerien_määrä
  6354.  
  6355. Seuraava sijaitsee sitten edellisen koon verran edellisen jälkeen,
  6356. kolmas on toisen koon verran toisen jälkeen jne. Kun headeriin on näin
  6357. laskettu koon lisäksi sijainti, niin voidaan kirjoittaa tiedostoon
  6358. headereiden määrä, sitten headerit ja lopuksi lisätään tiedostot
  6359. samassa järjestyksessä kuin niiden headerit tiedoston
  6360. jatkoksi. EXE:een lisätessä tapa tietenkin olisi käänteinen, ensin
  6361. tiedostot loppuun, sitten headerit ja lopuksi tiedostojen määrä. Tai
  6362. toinen mahdollisuus EXE-pohjaisessa olisi vain lisätä datatiedosto
  6363. EXE:n loppuun ja viimeiseksi vielä offsetti siihen kohtaan missä EXE
  6364. aiemmin loppui ja josta data nyt alkaa headerin määrineen ja
  6365. headereineen.
  6366.  
  6367. No sepä oli siinä, ei tässä oikeastaan mitään vaikeaa pitäisi olla,
  6368. kun vain ei anna tämän luvun sekoittaa päätä.
  6369.  
  6370.  
  6371. 8.9 Polygoneista ja niiden fillauksesta
  6372. ---------------------------------------
  6373. 8.8 Lisää kivaa - zoomaus
  6374. -------------------------
  6375. 8.7 Prekalkattuja pintoja - ja tunneli
  6376. --------------------------------------
  6377. 8.6 Plasma tekee comebackin - wobblerit
  6378. ---------------------------------------
  6379. 8.5 Musiikkijärjestelmistä
  6380. --------------------------
  6381. 8.4 Vektorit pelimaailmassa
  6382. ---------------------------
  6383. 8.3 Motion blur - sumeeta menoa
  6384. -------------------------------
  6385. 8.2 Läpinäkyvyys ja sen vaihtoehto - shadebobit
  6386. -----------------------------------------------
  6387.  
  6388. Läpinäkyvyys on hieno efekti. Puoliksi läpi näkyvää esinettä
  6389. piirrettäessä piirretään puoliksi esine ja jätetään puolet taustasta
  6390. jäljelle. Todellinen läpinäkyvyys muodostetaan siten, että
  6391. valon läpäisemättömyys ilmoitetaan arvolla välillä 0..1 (jossa 0 on
  6392. näkymätön ja 1 täysin näkyvä), ja "pikselin" väri lasketaan kaavasta:
  6393.  
  6394. transparency * object_pixel + ( 1 - transparency ) * background_pixel
  6395.  
  6396. Valitettavasti täytyy todeta, että SVGA-tiloissa, joissa tätä
  6397. käytetään, täytyy jokainen värikomponentti ensinnäkin laskea erikseen
  6398. ja toisekseen että kertolasku ei ole kovin nopeaa hommaa. Vaikka
  6399. kuinka optimoit, jää käteen joka tapauksessa yksi kertolasku, yksi
  6400. addi ja shiftaus oikeaan (ja tämä vain jos trans*obj voidaan laskea
  6401. etukäteen, eli objekti ja läpinäkyvyys ei muutu).
  6402.  
  6403. Tosin jos muistia kulutetaan kaikille käytetyille läpinäkyvyyksille ja
  6404. prekalkataan taustatkin päästään yhdellä yhdeenlaskulla, joka on
  6405. nopeampaa kuin alhaalla esitettävä ratkaisu. Tämäkin hidastuu siinä
  6406. tapauksessa että objektin läpinäkyvyys voi vaihdella eri kohdissa,
  6407. jolloin joudutaan vaihtamaan lähdepikselin puskuria koko ajan. Ja
  6408. tietenkään päällekkäiset läpinäkyvät esineet eivät onnistu ellet laske
  6409. joka objektia kaikkiin mahdollisiin läpinäkyvyyksiin. Joka taas on
  6410. hidasta.
  6411.  
  6412. Mutta kuten nimeltä mainitsemattomassa pelissä Stan sanoo "But wait,
  6413. there is more!". Ja sen 'enemmän' nimi on shadebobit. Nämä 90-luvun
  6414. alkupuolen demoista vakioefektinä löytyvät vekkulit ovat varsin halpa
  6415. tapa toteuttaa maittavia läpinäkyvyysefektejä minimimäärällä
  6416. kertolaskuja, eli ilman ainoatakaan mullia. Ja mitenkä tämä onnistuu?
  6417.  
  6418. Shadebob eroaa normaalista spritestä vain siten, että sitä ei piirretä
  6419. päälle, vaan se lisätään. Jos sinulla on puhtaan sininen tausta ja
  6420. lennätät päällä punaista shadebobbia, niin bobin kohdalle syntyy
  6421. violettia, sillä (255,0,0) + (0,0,255) = (255,0,255)
  6422. (rgb-triplettejä). Esim. flaret ja muut toteutetaan usein
  6423. shadebobeilla.
  6424.  
  6425. Yksi ongelma kyllä on tässäkin tekniikassa. Ylivuodot. Joku teistä
  6426. varmaan on nähnyt efektin joka näyttää siltä että ruutu
  6427. "palaa". Käytännössä tämä on shadebobbi, joka on liian kauan samassa
  6428. paikassa, väriarvot kohoavat viimein 255:een ja kun ne menevät yli ne
  6429. pyörähtävät ympäri, ollaan takaisin nollassa ja jälki on
  6430. karua. Valkoisen kirkkaan täplän keskellä epämääräinen musta roso ei
  6431. oikein ole hienoa. Nopean shadebob-piirturin teko C:llä on jo taidetta
  6432. ja assylläkin hommaa on ihan liikaa. Ehtolause on periaatteessa
  6433. tällainen:
  6434.  
  6435. jos shadebob + tausta > yläraja
  6436. tausta = yläraja
  6437. muuten
  6438. tausta += shadebob
  6439.  
  6440. Assyllä voidaan käyttää hyväksi carry-flagia, joka menee päälle luvun
  6441. pyörähtäessä ympäri. Jnc hyppää jos carryä ei ole asetettu, jc taas
  6442. jos on. Myös adc ja sbc voivat olla mukavia, ne kun lisäävät
  6443. lisättävään/vähennettävään carry-flagin. Esimerkkinä varsin nopeasta
  6444. shadebob-rutiinista:
  6445.  
  6446. add al, bl
  6447. jnc .eiyli
  6448. mov al, 255
  6449. .eiyli:
  6450.  
  6451. Käytännössä tuo vie yhden kellon ja niissä tapauksissa kuin palamista
  6452. syntyy siltäkin vältytään toisen kellon menetyksellä. Ongelmallista
  6453. tosin on, että truecolor-tiloissa (24 ja 32) täytyy jokainen
  6454. komponentti lisätä erikseen, toisin kuin sopivaa palettia käyttävässä
  6455. 256-värisessä tilassa. Ja 15- ja 16-bittisissä tiloissa homma alkaa jo
  6456. muistuttamaan masokismiä.
  6457.  
  6458. Joka tapauksessa läpinäkyvyyden kaksi vaihtoehtoista menetelmää ovat
  6459. molemmat varsin hyödyllisiä oikein käytettynä. Shadebob-systeemissä on
  6460. vain se vika, että puoliksi läpinäkyvää valkoista ei ole nähtykään, ja
  6461. todellisuudessa vain punaista lävitseen päästävä lasi sinisen taustan
  6462. päällä on mustaa. Siksi shadebobit sopivat parhaiten valoefektien
  6463. tekoon. Tosin läpinäkyvyyskin onnistuu tekemällä alpha-kanava (joka on
  6464. kuten bittikartan maski, mutta kertookin kunkin pikselin
  6465. läpinäkyvyyden). Piirrossa sitten taustasta vähennetään alpha-kanava
  6466. (valkoisella tausta on aina musta) ja bittikartasta 255-alpha
  6467. (mustalla ei muutu, valkoisella muuttuu "läpinäkyväksi", eli nollaksi)
  6468. ja sitten vasta lisätään bittikarttaan. Ja tarkistuksia ei enää
  6469. tarvita kun alpha-laskut ovat varmistaneet, että taustan ja
  6470. bittikartan summa ei voi olla yli 255. Tässäkin bittikartan voi
  6471. etukäteen laskea oikealle läpinäkyvyydelle alpha-kanavan suhteen, jos
  6472. se ei muutu.
  6473.  
  6474. Jännittäviä hetkiä tämänkin parissa, shadebobbeja ja läpinäkyvyyttä
  6475. käyttämällä voi kehittää vaikka millaisia viritelmiä, jos ette usko
  6476. niin katsokaa vaikka Orangen Mr. Black, arvatkaa kahdesti onko pyörivä
  6477. lonkero-mömmö muuta kuin taustakuva joka lonkeroiden kohdalta näkyy
  6478. läpi, tummentuen lonkeron reunoja kohti. Shadebobbeja tai
  6479. läpinäkyvyyttä, luultavasti ensimmäisiä. Oikaiskaa jos olet väärässä.
  6480.  
  6481.  
  6482. 8.9 Polygoneista ja niiden fillauksesta
  6483. ---------------------------------------
  6484. 8.8 Lisää kivaa - zoomaus
  6485. -------------------------
  6486. 8.7 Prekalkattuja pintoja - ja tunneli
  6487. --------------------------------------
  6488. 8.6 Plasma tekee comebackin - wobblerit
  6489. ---------------------------------------
  6490. 8.5 Musiikkijärjestelmistä
  6491. --------------------------
  6492. 8.4 Vektorit pelimaailmassa
  6493. ---------------------------
  6494. 8.3 Motion blur - sumeeta menoa
  6495. -------------------------------
  6496.  
  6497. Motion blur ei ehkä peleissä kovin hyödylliseksi muodostu, mutta
  6498. demoissa se on varsin suosittu, ja kyllä sitä voi vaikka
  6499. autopelissäkin käyttää. Joka tapauksessa idea tulee nyt, joten turha
  6500. pyristellä vastaan. Tämä ei satu kuin hetken.
  6501.  
  6502. Motion blur eli liikesumennus kuten joku sen voisi suomentaa
  6503. tarkoittaa sitä, että liikkuvista tavaroista jää jäljet ruutuun ja ne
  6504. häviävät vasta pikkuhiljaa. Efekti on tuttu Dubiuksen ja muiden
  6505. demojen lisäksi vaikkapa Jyrkistä, joissa ainakin aikoinaan valoista
  6506. jäi hirmuiset raidat ruutuun.
  6507.  
  6508. Tekniikkakin on helppo, käytännössä motion blur on melkein sama kuin
  6509. läpinäkyvyys, sillä toteutus sattuu olemaan sellainen, että tietty osa
  6510. uudesta ruudusta muodostuu juuri piirretystä informaatiosta ja tietty
  6511. osa edellisestä ruudusta (jossa taas oli jonkin verran sitä edellistä
  6512. jne. Näin syntyy pikkuhiljaa häipyvä efekti). Siinä se. Sekoitat
  6513. vanhaa ja uutta halutussa suhteessa ja olet valmis. Shadebobit eivät
  6514. tähän käy kauhean hyvin (taino, jos vähennät edellisestä aina
  6515. esim. 100 ja piirrät uuden framen skaalalla 0-155 ja lisäät ne, niin
  6516. mikäs siinä, itseasiassa voisi tämäkin toimia).
  6517.  
  6518. Yleisemmin käytetään kuitenkin aitoa läpinäkyvyyttä ja yksinkertaisia
  6519. perusjakoja, jotka menevät ilman kertolaskuja. Käytännössä tämä on 1:1
  6520. ja pienellä kikkailulla vaikka 1:3 ja 1:7 suhteet onnistuvat varsin
  6521. helposti. Lupasin olla antamatta sorsaa, joten vihjeenä, että
  6522. truecolor-tiloissa shiftaamalla ja poistamalla sopivalla and-maskilla
  6523. toisten komponenttien alueelle mahdollisesti eksyneet bitit saa
  6524. 1:1-sekoituksen muutamassa kellossa. 1:3 onnistuu mukavasti
  6525. shiftaamalla neljällä molemmat ja kertomalla toisen lea-käskyä
  6526. käyttäen kolmella on homma vauhdikasta.
  6527.  
  6528. Jos käytät yhä jotain ankeaa kvantisoitua tilaa, tai jotain
  6529. muuta palettitilaa, niin voit olla onnellinen, voit laskea helposti
  6530. etukäteen 256x256-kokoisen taulukon, jonka jokainen alkio kertoo
  6531. pysty- ja vaakarivin mukaisten värien optimaalisen sekoituksen. Ja kun
  6532. taulukko prekalkataan voit valita sekoitussuhteen ihan
  6533. vapaasti. Rutiinikin on yksinkertainen, jokaista taulukon alkiota
  6534. kohden otat paletista pysty- ja vaakarivin mukaiset värit, sekoitat
  6535. halutussa suhteessa aidon läpinäkyvyyden mukaisesti ja etsit
  6536. tulokselle kvantisointikuutiosta (tai käyttäen lähimmän sopivan värin
  6537. etsintää, selostettu vektoreiden ohessa seuraavassa luvussa) lähimmän
  6538. sopivan värin.
  6539.  
  6540. Tämä oikeastaan tässä olikin. En tiedä kumpi on nopeampaa, blurraus
  6541. flipin sisällä (käytetään näyttöpuskuria toisena), vaiko nopeamman
  6542. keskusmuistin käyttö ja sitten vasta flippi ruudulle. Kokemuksia
  6543. otetaan vastaan.
  6544.  
  6545.  
  6546. 8.9 Polygoneista ja niiden fillauksesta
  6547. ---------------------------------------
  6548. 8.8 Lisää kivaa - zoomaus
  6549. -------------------------
  6550. 8.7 Prekalkattuja pintoja - ja tunneli
  6551. --------------------------------------
  6552. 8.6 Plasma tekee comebackin - wobblerit
  6553. ---------------------------------------
  6554. 8.5 Musiikkijärjestelmistä
  6555. --------------------------
  6556. 8.4 Vektorit pelimaailmassa
  6557. ---------------------------
  6558.  
  6559. Vektorit ovat sen verran vekkuleja juttuja, että käsittelen niitä
  6560. lyhyesti ja vähemmän teoreettisesti tässä. Kärsivälliset odottavat
  6561. lukion kursseja, ja kärsimättömät mutta tiedonhaluiset kaivavat
  6562. syvemmän teorian vaikka lukion matikankirjasta tai 3Dicasta, sieltä
  6563. löytyy pistetulo, ristitulo ja muutkin tärkeät tiedot. Me keskitymme
  6564. vain peruskäsitteeseen ja vektorien muodostamiseen, skaalaamiseen ja
  6565. yhteenlaskuun.
  6566.  
  6567. Olet varmaan jo käyttänyt vektoreita pariin otteeseen. Vektorit ovat
  6568. vain tapa ajatella muita kuin skalaarisia suureita
  6569. (suuruudellisia). Hyvä esimerkki on jonkin esineen sijainti
  6570. ruudulla. Aiemmin olet ajatellut että sijainti koostuu x- ja
  6571. y-koordinaateista. Mutta voit ajatella sijaintia myös vektorina, jonka
  6572. x-komponentti on x-koordinaatti ja y-komponentti ja y-koordinaatti.
  6573.  
  6574. Vektori on nuoli. Ja kuten nuoli, vektori voi osoittaa mihin suuntaan
  6575. tahansa ja olla minä pituinen tahansa. 2d-peleissä käytät varmaan
  6576. yleensä 2d-vektoreita, sillä paperillekaan ei voi piirtää nuolia kuin
  6577. tasossa. 3-ulotteisessa avaruudessa taas nuoli voi osoittaa pysty- ja
  6578. vaakasuunnan lisäksi myös sisään ja ulos näytöstä ja kaikkialle näiden
  6579. välillä. Vektorit ovat itseasiassa vain niputettuja koordinaatteja ja
  6580. yleensä riittää että keskitytään origokeskeisiin vektoreihin, jotka
  6581. alkavat koordinaatiston keskipisteestä. Ajattele ruutupaperia, jossa
  6582. on koordinaatisto. Origokeskeinen vektori on nuoli, joka lähtee
  6583. keskipisteestä ja jonka kärki on missä tahansa koordinaatistossa. Sama
  6584. millaisen vektorin piirrät koordinaatistoosi on helppoa huomata että
  6585. origokeskeinen vektori voidaan aina ilmoittaa kahdella luvulla, x- ja
  6586. y-koordinaatilla mihin nuolen kärki sitten osuukin. Myös
  6587. tietokonemaailmassa vektori ilmoitetaan kahdella luvulla, ihan kuten
  6588. ennen teit x- ja y-koordinaattiesi kanssa.
  6589.  
  6590. Määrittely onnistuu lukutaulukkona, tai rakenteena. Suosittelen
  6591. taulukkoa, eroa structiin ei kuitenkaan nopeudessa ole:
  6592.  
  6593. float v[3]; /* 3-ulotteinen vektori, v[0] on x-komponentti, v[1] on y
  6594. ja z tietenkin v[2] */
  6595.  
  6596. Vektoreita voi muodostaa kaikkien pisteiden välille, muodostat vain
  6597. molemmista pisteistä vektorin (käytännössä ajattelet koordinaatteja
  6598. vektorin komponentteina) ja vähennät ne toisistaan ja tuloksesta
  6599. tulee vektori, joka on yhtä pitkä kuin mitä pisteiden välillä lyhin
  6600. matka (tätä voi käyttää esimerkiksi kvantisoinnissa, kahden värin
  6601. etäisyys voidaan selvittää muodostamalla niiden välille vektori ja
  6602. laskemalla sen pituus). Vektorien yhteen- ja vähennyslasku on helppoa,
  6603. jokainen komponentti vain käsitellään erikseen. Eli jos vektorin a
  6604. (päällä pitäisi olla viiva, mutta...) x- ja y-komponentit ovat 5 ja 2
  6605. ja vektorin b vastaavasti 3 ja 4, ovat niiden summavektorin
  6606. komponentit vastaavasti 5+3=8 ja 2+4=6. Yleensä vektori ilmoitetaan
  6607. kuten koordinaatti, eli a + b = (8,6) ja a - b = (2,-2). Vektorin
  6608. pituus lasketaan kaavasta sqrt(x*x + y*y). Vektorin pituutta voidaan
  6609. skaalata jollain luvulla kertomalla jokainen komponentti erikseen
  6610. luvulla. Tällöin pituus kasvaa <luku>-kertaiseksi, mutta suunta pysyy
  6611. ennallaan.
  6612.  
  6613. Kolmiulotteisessa erona on vain se, että mukaan tulee
  6614. z-komponentti. Pituuden kaavaan lisätään + z*z ja laskuissa myös tämä
  6615. komponentti pitää muistaa käsitellä. Helppoa kun sen osaa.
  6616.  
  6617. No nyt tiedät mikä on vektori. Vaan mitä sillä tekee? Paras vastaus
  6618. on, että ihan mitä tahansa. Vaikka autopelin. Tai Quaken. Vektori on
  6619. vain kätevä tapa niputtaa n-ulotteisen avaruuden koordinaatit yhteen
  6620. pakettiin.
  6621.  
  6622. Joku kysyi minulta vähän aikaa sitten miten autopelissä tai
  6623. luolalentelyssä tehdään liikkuminen. Olisi ollut todella helppoa
  6624. selittää asia jos olisin ollut varma että kysyjä ymmärtää mikä on
  6625. vektori, mutta kun jouduin selittämään asian x- ja y-koordinaateilla,
  6626. hommassa oli paljon enemmän tekemistä. Selitänpä nyt miten
  6627. luolalentely voitaisiin toteuttaa vektoreilla:
  6628.  
  6629. Aluksen sijainti on normaali 2-ulotteinen vektori. Lisäksi aluksella
  6630. on nopeusvektori ja kiihtyvyysvektori. Joka framella sijaintivektoriin
  6631. lisätään nopeusvektori. Nopeusvektorin suunta vastaa aluksen
  6632. kulkusuuntaa (eli ihan kuten x-nopeus ja y-nopeus, vain niputettuna)
  6633. ja pituus nopeutta. Voit hahmottaa liikettä piirtämällä esimerkiksi
  6634. sijaintivektorin ja sen kärjestä lähtien nopeusvektorin. Joka
  6635. kierroksella nopeusvektori lisätään sijaintiin, ja tulos on juuri se
  6636. mitä saat kun piirrät nopeusvektorin suuntaisen ja pituisen jatkeen
  6637. sijaintivektorille. Kokeile vaikka alkusijaintia (5,7) ja nopeutta
  6638. (2,-1) ja lisää edelliseen jälkimmäinen ja piirrä tämä uusi vektori
  6639. (9,6). Huomaat että uuden ja vanhan sijaintivektorin väli on juuri
  6640. nopeusvektorin suuntainen ja pituinen.
  6641.  
  6642. Lisäksi meillä on vielä kiihtyvyysvektori, joka ilmoittaa mihin
  6643. suuntaan alus on kiihtymässä. Tämä vektori kertoo mihin moottori
  6644. milläkin hetkellä alusta työntää (suunta) ja kuinka nopeasti
  6645. (pituus). Rakettimoottoreilla kiihtyvyys on aina vakio, joten
  6646. kiihtyvyyden määrittäminen onnistuu luomalla sinillä ja kosinilla
  6647. yksikkövektori (nimitys jota käytetään vektoreista joiden pituus on 1,
  6648. joka pätee kaikkiin vektoreihin joiden x-komponentti on kosini ja
  6649. y-komponentti sini, se on näiden trigonometristen funktioiden
  6650. perusluonne) ja skaalaamalla se rakettimoottorin teholla, eli esim:
  6651.  
  6652. kiihtyvyys[0] = cos(kulma_rad);
  6653. kiihtyvyys[1] = sin(kulma_rad);
  6654.  
  6655. kiihtyvyys[0] *= TEHO;
  6656. kiihtyvyys[1] *= TEHO;
  6657.  
  6658. Ja kiihtyvyys lisätään tietenkin joka vuorolla nopeuteen, eli kun
  6659. moottori ponnistelee kulkusuunnan mukaisesti vauhti kiihtyy ja jos
  6660. käännät aluksen nokan vastakkaiseen suuntaan ja painat kaasun pohjaan
  6661. (onko napeissa muka muita asentoja ?-) vauhti alkaa
  6662. hidastumaan. Täydellistä.
  6663.  
  6664. Ja koska vektorit ovat suoraan fysiikkaa varten luotuja, on
  6665. painovoiman, aseen rekyylin, törmäysten ja muiden lisääminen lasten
  6666. leikkiä. Painovoima on vain vakiosuuntainen (alas) kiihtyvyys. Kitka
  6667. pinnasta (ei luolalentelyissä, autopeleissä kylläkin) on tietyn
  6668. prosenttimäärän (1-kitkakerroin) mukainen vauhdin hidastuminen,
  6669. rekyyli ja törmäykset perustuvat siihen, että jos ammut panoksen
  6670. tiettyyn suuntaan (vektori), niin aluksen suunnanmuutos on
  6671. vastakkainen ja suoraan suhteessa massojen eroon. Esim. jos panos
  6672. painaa 1/1000 aluksesta, lisätään nopeuteen ammuksen suuntavektori
  6673. käännettynä (miinusmerkki joka komponentin eteen ja nuoli osoittaa
  6674. vastakkaiseen suuntaan) ja skaalattuna yhteen tuhannesosaan, eli
  6675. kerrottuna 0.001:llä. Jos olet perfektionisti voit pitää lukua
  6676. panoksista ja vähentää ne massasta. :) Täydellinen törmäys (molemmat
  6677. kappaleet jatkavat samaan suuntaan, esim. luodit) on ihan sama, mutta
  6678. käänteisesti, eli ei tarvitse kääntää ammuksen suuntavektoria, vaan
  6679. lisätään se vain massojen suhteessa. Alusten ja seinän väliset
  6680. törmäykset ovat hankalampia, niissä kun pitäisi molempien kolahtaa eri
  6681. suuntiin. Tähän saat vapaasti kehitellä omasi, luolalentelyiden
  6682. tekijät ovat tyytyneet yleensä pysäyttämään aluksen seinään osuessa ja
  6683. antavat alusten läpäistä toisensa.
  6684.  
  6685. Siinäpä kaikki tärkein vektoreista. Jos ymmärrät mitä ne ovat, miten
  6686. ne jakautuvat x-, y- ja z-komponentteihin ja tajuat että skaalaamalla
  6687. muutetaan niiden pituutta, ja osaat kaiken lisäksi lisätä, vähentää ja
  6688. skaalata niitä, olet vahvoilla. Oppitunti on päättynyt.
  6689.  
  6690.  
  6691. 8.9 Polygoneista ja niiden fillauksesta
  6692. ---------------------------------------
  6693. 8.8 Lisää kivaa - zoomaus
  6694. -------------------------
  6695. 8.7 Prekalkattuja pintoja - ja tunneli
  6696. --------------------------------------
  6697. 8.6 Plasma tekee comebackin - wobblerit
  6698. ---------------------------------------
  6699. 8.5 Musiikkijärjestelmistä
  6700. --------------------------
  6701.  
  6702. Ne jotka haukkovat kotikatsomoissaan henkeä jo kuin kalat, saavat
  6703. aloittaa hengittämisen jälleen. Tiedossa ei vielä ole äänikorttien
  6704. saloja, eikä edes vaivaisia miksauksen perusteita, ne taidan laittaa
  6705. vasta 3.0-versioon, jos sellaista edes kannattaa siinä vaiheessa alkaa
  6706. tekemään. Nyt kuitenkin esittelen lyhyesti NE soittosysteemit, jotka
  6707. tällä hetkellä minun henk. koht. mielipiteeni mukaan ovat hyviä
  6708. vaihtoehtoja kun musiikkia pitää alkaa kuulumaan, muusikkojen tai
  6709. kohdeyleisön vaatimuksesta.
  6710.  
  6711. Alkusanoina totean, että kaikkein paras on tietenkin tehdä oma
  6712. systeemi. Mutta se suurin kompastuskivi on siinä, että levityksessä
  6713. olevien tasoisten (paraskaan ei soita IT- ja XM-kappaleita kaikkia
  6714. oikein) "playereiden" teko vie yhdestä viiteen vuotta. Tervetuloa
  6715. todellisuuteen. Vaatimukset nimittäin ovat hurjat, mitään yhtenäistä
  6716. standardia kun ei Windows Sound Systemiä lukuunottamatta DOS:in
  6717. puolella. Tai no, SB on vahva sana, Pro-mallia tukemalla tuet
  6718. luultavasti 99% tavoiteyleisösi äänikorteista.
  6719.  
  6720. Mutta edes yhden kortin koodaukseen vaadittava tieto- ja taitomäärä on
  6721. sen verran suuri, että jos viikossa saa sellaisen systeemin tehtyä,
  6722. että sillä voi soittaa looppaavia ja looppaamattomia sampleja,
  6723. katkottomasti, ilman naksahduksia ja vapaasti säädeltävällä vauhdilla
  6724. ja voluumilla, niin voi ajatella että kymmenesosa hommasta on jo
  6725. tehty. Sen jälkeen hyökätään FMODDOC:in kimppuun ja vietetään seuraava
  6726. viikko kyhäten jonkin formaatin moduuliloaderi (jos kyseessä on XM tai
  6727. IT suosittelen varaamaan pari viikkoa ja purkin Buranaa). Sen jälkeen
  6728. vielä pari kuukautta efektitukea väännellen (XM on dokumentaation
  6729. saatavuudessa suorastaan kuninkuusluokkaa, herrat FT2:n tekijät kun
  6730. ovat sitä mieltä että heidän trackerinsahan on melkein
  6731. itsedokumentoiva - tiedossa siis ainakin S3M- tai MOD-formaatin
  6732. dokumenttien luku ja hauskoja hetkiä niin heksaeditorin kuin FT2:nkin
  6733. parissa).
  6734.  
  6735. No nyt olette varmaan niin kauhuissanne että tämän luvun todellinen
  6736. asiasisältö menee kuin kuumille kiville. ;-D No ei, ei se
  6737. moduuliplayerin teko niin hirveää hommaa ole, täytyy vain omata
  6738. itsepäisyyttä, taito tehdä asiat tarpeeksi hyvin kerrasta (lukemalla
  6739. fmoddocin kerran läpi ennen aloitusta voi tähän syntyä kummasti
  6740. kiinnostusta) ja paljon paljon kärsivällisyyttä. Optimointitaitokaan
  6741. ei olisi pahitteeksi.
  6742.  
  6743. Jos kuitenkin lykkäät moduuliplayeriasi hieman kauemmaksi
  6744. tulevaisuuteen ja kokeilet ensin jotain muuta kuin kotikutoista
  6745. ratkaisua, kannattaa ensimmäiseksi kurkata Housemarquen sivuille
  6746. (www.housemarque.com/fi) ja imuroida Midaksen uusin versio. Midas on
  6747. moduuliplayereiden ehdoton Rolls Royce, jopa IT-tuki taisi löytyä, ja
  6748. XM:tkin soivat vain osaksi pieleen. Funktioita riittää vaikka muille
  6749. jakaa (tosin subrow-tarkkuista laskuria moduulin sijainnista ei saa
  6750. käyttöönsä :), timerista lähtien vga-tilojen asetukseen ja
  6751. näyttösynkronointeihin. DirectX-tuki löytyy niinikään.
  6752.  
  6753. Ainoa Midaksen ongelma on se, että sitä ei EHDOTTOMASTI saa käyttää
  6754. SW- tai muuhun kaupalliseen levitykseen. Ilmaisohjelmat ovat ok,
  6755. kunhan vain muistaa mainita käyttäneensä midasta, mutta jos otat siitä
  6756. rahaa, otat Midaksen myös pois ohjelmastasi. Kaupallisia lisenssejä
  6757. tosin on mahdollista hankkia, joten postia vain housemarquelle
  6758. sähköisessä muodossa. Aiemmin muistaakseni rekisteröintihinta oli
  6759. $500, mutta ehkä se on tippunut. :)
  6760.  
  6761. Jos SW kiinnostaa, tai haluat vaihtoehtoisen, DJGPP-optimoidun
  6762. systeemin peliisi, on Humppa (entinen Hubrmod), eli HUbris Module
  6763. Player PAckage tarkistamisen arvoinen, lähetät vaikka pelisi
  6764. lähdekoodit Kaikalle ja saat alkaa myymään peliä. :) Kuulemani mukaan
  6765. XM-tukikin on jo varsin hyvä ja sormeni syyhyävät päästä kokeilemaan
  6766. systeemiä, vaan en ole vielä ehtinyt.
  6767.  
  6768. Toinen hieman kalliimpi, mutta pitkät perinteet moduuliplayerien
  6769. saralla omaava vaihtoehto on MikMod, josta löytyy Midaksen tapaan tuki
  6770. melkein joka laitealustalle, mukaan lukien Linux. Rekisteröintihinta
  6771. oli varsin halpa, muistaakseni parikymmentä dollaria, ja sekin vain
  6772. siinä tapauksessa että haluat käyttää playeria kaupallisiin
  6773. tarkoituksiin, ilmaislevittäjät saavat käyttää softaakin
  6774. ilmaiseksi. Ja kuten Humpassa, myös MikModissa lähdekoodi tulee
  6775. mukana, joten mahdollisuudet omiin viritelmiin kohoavat huimasti.
  6776.  
  6777. Kaikkien kolmen mukana tulee varsin laadukas dokumentaatio, tai jos ei
  6778. sellaista löydy, niin esimerkkikoodia löytyy jokaisesta. Itse olen
  6779. kokeillut neljää tai viittä eri playeria ja jok'ikisen toimimaan
  6780. saanti ei ole vaatinut muuta kuin sopivan esimerkin räätälöimistä
  6781. omaan käyttöön sopivaksi. Uskaltakaa hyvät ihmiset kokeilla niitä, ja
  6782. jos ette kerta kaikkiaan ymmärrä niitä englanninkielisiä kommentteja
  6783. niin sanakirja tai taitava kaveri varmaan auttaa mielellään. :)
  6784.  
  6785. Tässä kaikki tältä erää, minulle saa postittaa ilmoituksia jos jokin
  6786. ehdottoman mahtava DJGPP-playeri puuttui (ei, se J. Hunterin DJGPP:lle
  6787. tehty SB Library tai mikä olikaan ei käy - edes modit eivät soi siinä
  6788. oikein).
  6789.  
  6790.  
  6791. 8.9 Polygoneista ja niiden fillauksesta
  6792. ---------------------------------------
  6793. 8.8 Lisää kivaa - zoomaus
  6794. -------------------------
  6795. 8.7 Prekalkattuja pintoja - ja tunneli
  6796. --------------------------------------
  6797. 8.6 Plasma tekee comebackin - wobblerit
  6798. ---------------------------------------
  6799.  
  6800. Yksi varsin hulvaton demoefekti ja peleissäkin kenties
  6801. hyödynnettävissä oleva vekkuli on nimeltään wobbler ja selostan
  6802. toiminnan lyhyesti tässä. Sen sijaan että ottaisin x:n mukaan parista
  6803. aallosta värin ja y:n mukaan parista aallosta, lisäätkin x-arvon
  6804. mukaisesti siniaallolla y-koordinaattia ja toisinpäin. Tuloksena
  6805. syntyy ihanasti vellova efekti, jota voi käyttää miten mieli sitten
  6806. tekeekään. Hyvää idea on käyttää 256x256-kokoista tekstuuria, jolloin
  6807. y- ja x-arvot on helppo saada menemään ympäri (y&255 ja assyllä
  6808. suoraan tavurekistereillä) ja oikeanlaisilla kartoilla ei reinoja
  6809. huomaa. (mikä olisi hyvä termi englanninkielessä käytetylle
  6810. tilingille? tiiliytyminen? tileytyminen?)
  6811.  
  6812. Voit myös kokeilla y:n lisäämistä y:n mukaan jolloin syntyy
  6813. venytystä. En myöskään tiedä millainen on tulos, jos et lisää näitä x-
  6814. ja y-arvoihin, vaan laitat ne sellaisenaan (eli ei x + ..., vaan vain
  6815. ...).
  6816.  
  6817.  
  6818. 8.9 Polygoneista ja niiden fillauksesta
  6819. ---------------------------------------
  6820. 8.8 Lisää kivaa - zoomaus
  6821. -------------------------
  6822. 8.7 Prekalkattuja pintoja - ja tunneli
  6823. --------------------------------------
  6824.  
  6825. Jälleen uutta pikkukivaa efektien saralla. Tunneli. Idea on sellainen,
  6826. että sinulla on kaksi puskuria, samaa kokoa kuin ruutukin, sekä
  6827. tekstuuri (esim 256x256). Puskurista 1 otetaan samasta kohdasta kuin
  6828. ruudulle laitettava pikselikin ensin y-arvo, ja sitten puskurista 2
  6829. x-arvo. Tunnelin tapauksessa puskuriin 1 piirretään kuvio, joka on
  6830. säteittäisiä viivoja keskipisteestä ja kuvio lasketaan siten, että
  6831. mennään joka pikseli läpi, muodostetaan vektori pikselin ja
  6832. keskipisteen välille (x-komponentti on x-160 ja y-komponentti y-100)
  6833. ja selvitetään välillä oleva kulma. Tämä hoituu joko tangentilla,
  6834. jolloin homma on nopeampaa, tai jos et sitä osaa, teet sen siten, että
  6835. piirrät keskipisteestä tarpeeksi tiheään ympyröitä tähän tyyliin:
  6836.  
  6837. for(radius = 0; radius < 140; radius ++) {
  6838. for(angle = 0; angle < 2048; angle++) {
  6839. x = (int)(cos(3.1415*(float)angle/180.0)*radius);
  6840. y = (int)(sin(3.1415*(float)angle/180.0)*radius);
  6841. buffer1[y*320+x] = angle/8; // 0..255
  6842. }
  6843. }
  6844.  
  6845. Ja toinen taas on sarja ympyröitä keskipisteestä, värin ollessa
  6846. etäisyys keskipisteestä. Tämä on helppo ratkaista neliöjuurella. Eli:
  6847.  
  6848. for(y=0; y<200; y++) {
  6849. for(x=0; x<320; x++) {
  6850. tx = x-160;
  6851. ty = y-100;
  6852. buffer2[y*320+x] = (int)sqrt(tx*tx+ty*ty);
  6853. }
  6854. }
  6855.  
  6856. Sitten vain piirretään tunneli:
  6857.  
  6858. for(loop=0; loop<64000; loop++) {
  6859. screen[loop] = texture[ buffer2[loop] * 256 + buffer1[loop] ];
  6860. }
  6861.  
  6862. Ainiin, perspektiivinkin voi lisätä vaihtamalla y-arvon (buffer2)
  6863. laskuun sqrt:n tilalle 256/sqrt:n. Tällöin pitää tosin varmistaa ettei
  6864. sqrt ole alle 1, sillä muuten käy todella huonosti. Ja jos etäisyys ei
  6865. tuollaisena tyydytä sen voi kertoa halutun suuruisella luvulla. Niin
  6866. ja tunnelin saa liikkeelle kun lisää piirron aikana x- ja y-arvoihin
  6867. jotain. Tässä pitää kuitenkin huolehtia ettei kumpikaan mene yli 255:n
  6868. (eli käytännössä ((buffer2[loop]+yoff) & 255) + ...).
  6869.  
  6870. Muitakin kuvioita joissa tekstuuri liikkuu "pintaa" pitkin, voi
  6871. helposti luoda. Wormhole on yksi tällainen, eikä edes hirvittävän
  6872. vaikea, mietippäs vain. Ja Trauman Mindtrapissa luultavasti käytettiin
  6873. samaa tekniikkaa siinä pyörivän pallon kohdassa jossa ympärillä
  6874. pyöritään toiseen suuntaan.
  6875.  
  6876.  
  6877. 8.9 Polygoneista ja niiden fillauksesta
  6878. ---------------------------------------
  6879. 8.8 Lisää kivaa - zoomaus
  6880. -------------------------
  6881.  
  6882. Jälleen sarjassa "helppo nakki kun käytät aivoja"-efektejä. Tällä
  6883. kertaa vuorossa vanha tuttumme reaaliaikainen suurennos. Ja mikäs sen
  6884. helpompaa.
  6885.  
  6886. Normaalissa bittikartan piirrossahan korotat ruudun offsettia yhdellä
  6887. ja bittikartan offsettia yhdellä. Entäs jos korottaisit bittikartan
  6888. offsettia kahdella? Bittikartta "loppuisi" puolet nopeammin, ja
  6889. tuloksena piirtäisit sen puoleen aiemmasta tilasta, sekä x- että
  6890. y-suunnassa. Pienensit juuri kuvaasi kahdella. Onnea. No entä
  6891. suurennos sitten? Helppoa, korotat ruudun offsettia kahdella? Totta,
  6892. mutta tuloksena syntyy hieman reikiä (aika kiva räjähdysefekti silti),
  6893. joten ehkä käytämme jotain muuta. No varmaan kaikki arvasivatkin jo -
  6894. korotetaan bittikartan offsettia puolella ja päästään samaan
  6895. tulokseen.
  6896.  
  6897. Vastaavalla tavalla pystyt suurentamaan ja pienentämään bittikarttoja
  6898. kaikilla kahden potensseilla. Mutta pystyt kyllä parempaankin ja
  6899. tiedät sen aivan varmasti. Nappaa käyttöön fixed-point luvut tai
  6900. vaikka floatit, niin yhtäkkiä voitkin tehdä sama minkä kokoisia
  6901. zoomauksia, portaattoman näköisiä vieläpä! (taino, siinä syntyy
  6902. sellaistä ärsyttävää pyöristysvirhekuviota, kokeile vaikka suurentaa
  6903. kuvaa pikkuhiljaa pienentämällä askelta mahdollisimman vähän framejen
  6904. välissä). Helppoa kun sen osaa.
  6905.  
  6906. Ainoa häiritsevä piirre tulee olemaan se, että karttasi lentelevät
  6907. ruudun ylitse tai loppuvat kesken. Niinpä piirrossa täytyy normaalin
  6908. for(... ; bitmap_x < x_size; bitmap_x++) -systeemin sijaan tarkistaa
  6909. pyörityksen aikana sekä ruudun että bittikartan x- ja
  6910. y-koordinaatit. Tai sitten klippaat ennen looppia, eli jos kartta
  6911. menee ruudun ylitse siirrät piirtoa alkamaan hieman myöhemmin kuin
  6912. ensimmäisen pikselin kohdalta. Jälleen tässä tulee tehdä sen verran
  6913. tarkka järjestelmä, ettei reunoilta jää satunnaisesti 2-5 kappaletta
  6914. pikseleitä tyhjäksi.
  6915.  
  6916.  
  6917. 8.9 Polygoneista ja niiden fillauksesta
  6918. ---------------------------------------
  6919.  
  6920. Minua on pitkän aikaa ruinattu tekemään 3D:stä juttua ja aina olen
  6921. käännyttänyt kysyjät 3Dican puoleen. Tai ainakin polygonijutuista. No
  6922. niin, samoin käy tällä kertaa. :) Tai melkein, tässä luvusta niin
  6923. lyhyesti ja ytimekkäästi polygonien fillaus ilman klippejä ja muita
  6924. kuin vain mahdollista. Ennen kuin taaperrat luvun läpi, hakkaa päähäsi
  6925. tieto miten piirretään muitakin kuin suoria viivoja. Eli lue se
  6926. interpolointi-juttu lävitse.
  6927.  
  6928. Polygonien täyttäminen on varsin helppoa. Ensin käymme lävitse
  6929. kolmioiden täyttämisen. Joka on itseasiassa _niin_ helppoa, että
  6930. keksin lineaarisen interpoloinnin ja kolmion täyttämisen idean ihan
  6931. itse, ilman kenenkään apua. Hienoa, lohduttiko?-) No joka tapauksessa,
  6932. asiaan, eli tarkemmin sanoen flat-polyfilleriin.
  6933.  
  6934. Tasaisella värillä täytetyn kolmion piirto on varsin helppoa. Kun
  6935. piirtelet vaikka 27 kappaletta kolmioita paperille, niin huomaat että
  6936. jos vedät kolmion pystytasossa katsottuna keskimmäisen pisteen kautta
  6937. kulkevalla vaakaviivalla halki, saat kaksi pienempää kolmiota (tai
  6938. erikoistapauksissa vain yhden, jos sinulla on jo tasapohjainen
  6939. kolmio), joista toisessa on tasainen pohja ja toisessa tasainen
  6940. "katto". Jos vielä ajattelet syntyneiden kolmioiden kylkiä viivoina ja
  6941. kuvittelet piirtäväsi ne ylhäältä alaspäin interpoloiden x:ää, huomaat
  6942. että olisi varsin helppoa aloittaa huipulta ja lisätä y:tä yhdellä,
  6943. interpoloida hieman alku- ja loppu- x-koordinaatteja ja piirtää
  6944. x-koordinaattien välille vaakaviiva. Itseasiassa jos mietit vielä
  6945. hetken huomaat, että se olisi enemmän kuin helppoa.
  6946.  
  6947. Siitä vain tekemään, ensimmäinen flat-fillerisi valmistui juuri. Eli
  6948. sorttaa pisteet y:n mukaiseen järjestykseen (menee kolmella
  6949. if-lauseella, bubblesorttia) siten että ensimmäisenä on ylin ruudulla,
  6950. sitten keskimmäinen ja lopuksi alin. Nyt lasket pisimmälle viivalle,
  6951. eli sille joka ulottuu ylimmästä alimpaan, x-askeleen
  6952. (kaava: (x3-x1)/(y3-y1)). Sitten tarkistat että y1 ja y2 eivät ole
  6953. samoja ja jos eivät, lasket vastaavan x-askeleen x1:n ja x2:n välille
  6954. ja menet loopilla välin y1-y2. Jos y1 oli sama kuin y2 niin et tee
  6955. tuota ja jatkat suoraan vastaavaan tarkistukseen y2:n ja y3:n kanssa,
  6956. ja jälleen jos ne ovat erisuuria jatkat pidemmän viivan piirtämistä
  6957. siitä mihin se jäi ja lasket x-askeleen vielä y2:n ja y3:n väliselle
  6958. matkalle. Koko ajan piirrät vaakaviivoja pisimmän viivan
  6959. x-koordinaatin ja ylemmän tai alemman kolmion viivan x-koordinaatin
  6960. välille, sille korkeudelle missä y-looppisi meneekään.
  6961.  
  6962. Vaakaviivassa voisi olla hyvä tarkistaa että x1 on pienempi kuin x2
  6963. (jos näin ei ole, vaihda pisteet keskenään) ja onko viiva edes
  6964. ruudulla (x2 >= 0 && x1 <= 319 && y>=0 && y<=199) ja ettet ala
  6965. piirtämään ruudun ulkopuolelta (siirrä x1 nollaan jos se on alle ja
  6966. laske x2 319:ään jos se on yli). Sitten vain helpolla for-loopilla
  6967. viiva täyteen väriä. Muista, että se se on for(x=x1; x<=x2; x++), eli
  6968. <=, eikä <!
  6969.  
  6970. Gouraud-kolmio on ihan samanlainen, mutta väriä täytyy interpoloida
  6971. samoin kuin x-koordinaattia. Tekstuurikolmiossa interpoloidaan värin
  6972. sijaan tekstuurin x- ja y-koordinaatteja. Huomioitavaa on, että jos
  6973. viivanpiirrossa siirrät x1-koordinaattia -5:stä 0:aan jottet piirtäisi
  6974. ruudun ohitse, täytyy myös tekstuurien/gouraud-väriarvojen
  6975. alkupisteitä siirtää viidellä askeleella eteenpäin. Ja texture ja
  6976. gouraud-fillereissä hlinessäkin pitää interpoloida samalla tavalla
  6977. viivaa piirrettäessä kuin tehdään kolmion sivujen kohdalla.
  6978.  
  6979. Sossiinä, mie meen saunaan.
  6980.  
  6981.  
  6982. 8.10 Pari kivaa feikkimoodia
  6983. ----------------------------
  6984.  
  6985. Ihmissilmää on helppo huijata, se on todistettu useampaan kertaan. Ja
  6986. huijauksen kohteeksi joutuu puolueellisen tutkimuksen mukaan varsin
  6987. useassa VESA-tiloja käyttämättömässä demossa. Ja mikäs siinä. Mode-X
  6988. -tiloja en käsittele, kun en jaksanut twiikata itsekään kuin 320x400
  6989. -tilan toimimaan, nykyään ne alkavat olla hieman turhia. Mutta, demoja
  6990. tehdessä etenkin arvostaa kunnon temppuja jolla saa ruudulle enemmän
  6991. värejä kuin mitä siellä oikeastaan voisi yhtä aikaa olla.
  6992.  
  6993. Ensimmäinen tapa onkin kaluttu jo lävitse, ja sen nimi on kvantisoidut
  6994. tilat. Niissä ongelmana tahtoo vain olla, että homma ei ole kovin
  6995. vauhdikasta. Demoihin asian kyllä voi hoitaa siten, että tekee
  6996. erikoisflipin, joka laskee kaikki käytetyt värit ja tekee niistä
  6997. värikuution ja paletin etukäteen, mutta erityisen värikkäillä
  6998. efekteillä alkaa homma maistua puulta, tai paletti näyttämään yhä
  6999. enemmän siltä perinteiseltä 3:3:2-ratkaisulta, eli käytetään
  7000. 256-värisen tilan pikselin kolmea ylimmäistä bittia punaiseen, kolmea
  7001. keskimmäistä vihreään ja kahta alimmaista siniseen. Voit hyvin
  7002. kuvitella miltä väriliu'ut näyttävät. Täytyy kuitenkin todeta, että
  7003. onhan se kuitenkin kahdeksan/neljä eri värikomponenttia, toisin kuin
  7004. 16-väristen tilojen värikomponenttien sekoitus, välivärit ja
  7005. tumma/vaalea -valinta. Argh. Perfektionistit sanovat ei väreille ja
  7006. tekevät 256-värisen tilansa tietenkin puhtailla harmaasävyillä, mutta
  7007. me (vai pitäisikö sanoa te? ;) muut ihmiset voimme käyttää erilaisia
  7008. pieniä kikkoja laadun parantamiseen.
  7009.  
  7010. Eräs hieno ja usein käytetty tapa on jakaa 256-värinen paletti
  7011. pehmeään sinisen, punaisen ja vihreän liukuun (á 64 sävyä) ja
  7012. muodostaa väri kolmesta vierekkäisestä pikselistä. Laadussa ei
  7013. todellakaan ole hurraamista, mutta välttämättä asiaa ei ihan ensi
  7014. silmäyksellä huomaa (minua ainakin on muutaman kerran vedetty
  7015. höplästä). Valkoinen väri on aika karu, mutta jääähän paletista 64
  7016. sävyä yli ja ne voi aina käyttää harmaasävyihin. Tehokasta jälkeä
  7017. tulee ainakin, jos twiikkaat käyttöösi jonkin 320x400-tyyppisen tilan,
  7018. jolla normaali tilan 13h pikselit tuplaantuvat ja päällekkäisiä
  7019. pikseleitä on suorastaan ihana käyttää tämäntyyppisiin
  7020. sekoituksiin. Jo pelkästään käyttämällä kahta 3:3:2-pikseliä
  7021. approksimoimaan värilänttiä saadaan teoriassa 4:4:3-tila, ja
  7022. kummallisemmilla värijaotteluilla (1 bitti ilmoittamaan onko ylä- vai
  7023. alapikselin paletti ja loput 7 bittiä molemmissa erikseen käyttöön,
  7024. jolloin saadaan 14-bittinen tila, teoriassa, ainakin) päästään jo
  7025. todella hyviin tuloksiin.
  7026.  
  7027. On myös olemassa todellisten virittelytilojen maineeseen päässyjä
  7028. moodeja, joissa käytetään kahta kuvaa joita välkytetään nopeasti ja
  7029. kaiken maailman muita virityksiä, mutta vauhti on yleensä huono
  7030. verrattuna lähes suoraan raudalla toteutettaviin illuusioihin ja
  7031. toimivuus joidenkin tilojen kohdalla satunnainen, riippuen koneen
  7032. näyttökortista ja Uranuksen kallistumiskulmasta (joka tietenkin on
  7033. vakio). Näihin en ole erityisemmin perehtynyt, mutta jos joku on, niin
  7034. minua voi vapaasti pommittaa informaatiolla. Joka tapauksessa hus vain
  7035. keksimään erikoisia tapoja saada värien määrää näennäisesti
  7036. korotettua!
  7037.  
  7038.  
  7039. 9.1 Saatteeksi
  7040. --------------
  7041.  
  7042. Yli-guruiksi itsensä tuntevat, skip it.
  7043.  
  7044. Nyt et ole enää laama, vaan hatarasti pääasiat osaava, toivottavasti
  7045. innokas peliohjelmoijan alku. Tai kenties jopa vielä enemmän, jos
  7046. kaikki dokumentin asiat ovat hanskassa. Tämän dokumentin tarkoituksena
  7047. on ollut saattaa sinut vain alkuun. Ensimmäinen neuvoni aloittelevalle
  7048. peliohjelmoijalle, eli sinulle on, että älä jätä lukemistasi
  7049. tähän. Mikään ei korvaa tuhansia tunteja tutoriaalien ja kokiksen
  7050. ääressä vietettyjä tunteja. Samaa asiaa voi opetella useasta eri
  7051. lähteestä, jolloin tajuaa asiat paremmin, selkeämmin ja syvemmin kuin
  7052. yhden tutoriaalin luvulla.
  7053.  
  7054. Toisena on se, että englannin kieli on pakko opetella. Sen oppii
  7055. parhaiten sanakirjan kanssa tutoriaaleja kahlailemalla. Jos aiot
  7056. pärjätä hyvin peliohjelmoinnissa täytyy englantia osata. Jos et vielä
  7057. sitä osaa, niin työskentele oppiaksesi.
  7058.  
  7059. Kolmanneksi kaikkein tärkeimpänä ovat oman järjen käyttö, rautainen
  7060. tahto ja sammumaton tiedonhalu, sekä ahkeruus. Maailma on täynnä
  7061. laamoja, jotka eivät osaa mitään siksi koska eivät ole tosissaan
  7062. yrittäneet. Minä aloitin C-ohjelmoinnin alle kolme vuotta sitten ja
  7063. pelkällä kovalla yrittämisellä ja innostuneisuudellani opettelin
  7064. koodaamaan. Olen lukenut tuhansia ja tuhansia rivejä ohjelmointiasiaa,
  7065. enkä ole vielä sitä joutunut katumaan.
  7066.  
  7067. MBnetistä löytyy valtava määrä lähdekoodia ja tutoriaaleja lähes
  7068. kaikkiin ohjelmoinnin haaroihin. Netistä puhumattakaan. Ennenkuin
  7069. menetät toivosi tai menet kysymään mistään kahlaile sieltä kaikki
  7070. tarpeelliset alueet ("C/C++" ja "muut") läpi. Melkein kaikkeen pitäisi
  7071. vastaus noista dokumenteista löytyä (mistä muualta henkilöt joilta
  7072. kysytään olisivat ne saaneet selville kuin dokumenteista).
  7073.  
  7074. Lisäksi löytyy kymmeniä ohjelmointikirjastoja, joissa tulee lähdekoodi
  7075. mukana. Ja muista, että itsekin voi tehdä päätelmiä ja kokeiluja. Kyllä
  7076. aina jostain tarvittu tieto löytyy!
  7077.  
  7078. 9.2 Hieman koodereiden jargonia
  7079. -------------------------------
  7080.  
  7081. Alta löytyy muutamia kivoja tietokoneslangin termejä, joiden merkitystä
  7082. on hieman valotettu lyhyellä selityksellä. Huomaa, että tässä on vain
  7083. tietokoneslangia, sanat optimaalinen, ideaalinen ja muu vastaava pitää
  7084. yhä hakea Suomen Sivistyssanakirjasta:
  7085.  
  7086. PIKSELI Yksi kuvaruudun piste. Piste voi olla mustavalkoinen,
  7087. harmaasävyinen tai värillinen riippuen näyttötilasta ja sen
  7088. muistinvienti vaihtelee 1 bitin ja 4 tavun välillä.
  7089.  
  7090. KAKSOISPUSKURI näyttöpuskurin (ks. NÄYTTÖPUSKURI) kokoinen puskuri, jonne
  7091. kaikki piirretään ennen ruudulle kopioimista, jolloin saadaan tavara
  7092. mahdollisimman "tiivissä" (ei hajanaisia kirjoituksia silloin tällöin)
  7093. paketissa hitaaseen näyttömuistiin.
  7094.  
  7095. SEGMENTTI reaalitilassa muisti on jaettu 64 kilon kokoisin segmentteihin.
  7096. Itseasiassa segmentti on 16 ylintä tavua 20-bittisessä (max. 1 mega
  7097. osoitettavaa muistiavaruutta) osoitteessa ja tähän lisätään sitten
  7098. vielä offsetti (ks. OFFSET). Muodostuskaava on seg*16+off.
  7099.  
  7100. Myös lähdekoodista käännettävissä objektitiedostoissa (ks. OBJEKTI)
  7101. on segmenttejä, mutta ne tarkoittavat tietojen säilömispalasia,
  7102. kuten koodille varattu segmentti, datalle varattu segmentti ja
  7103. numeromuuttujille varattu segmentti. Ks. myös SEGMENTTIREKISTERIT.
  7104.  
  7105. OFFSET reaalitilan 20-bittisen muistiosoitteen 16 alinta bittiä. Huomaa,
  7106. että segmenttirekisterin ollessa 16 ylintä on osa osoitteista
  7107. "päällekkäin" Näin ollen on useita tapoja osoittaa samaan kohtaan
  7108. muistia. Katso alhaalla olevaa esimerkkiä:
  7109.  
  7110. Huomaa, että segmentti on yhden heksan, eli 4 bittiä enemmän vasemmalla,
  7111. sillä 4 bitin bittisiirtohan vastaa kertomista 16:sta (2^4=16).
  7112.  
  7113. Offsetti: 1234 6784
  7114. Segmentti: + 5678 + 5123
  7115. ------- -------
  7116. 20-bittinen: 579B4 579B4
  7117.  
  7118. SELEKTORI Suojatussa tilassa prosessori jakaa todellisen fyysisen muistin
  7119. halutun kokoisiin loogisiin paloihin. Selektori kertoo prosessori mihin
  7120. loogiseen alueeseen halutaan osoittaa. Tähän liittyy hieman monimutkai-
  7121. sempaakin asiaa, mutta sen hyödyllisyys jää kyseenalaiseksi normaalissa
  7122. peliohjelmoinnissa. Loogisilla muistialueilla on myös koko tallessa ja
  7123. jos osoitellaan tätä pidemmälle muistialueella ohjelma kaatuu ja
  7124. tulostaa virheilmoituksen. (SIGSEGV muistaakseni) Selektoreja käytetään
  7125. usein kuten segmenttirekisterejäkin (ks. SEGMENTTIREKISTERIT), eli
  7126. ds:ssä on dataselektori, cs:ssä koodiselektori jne.
  7127.  
  7128. SEGMENTTIREKISTERIT Prosessorilla on muutama 16-bittinen segmenttirekisteri,
  7129. jota täytyy käyttää kun halutaan osoittaa tiettyyn segmenttiin tai
  7130. suojatussa tilassa selektoriin (ks. SELEKTORI). Näitä ovat cs, ds, es,
  7131. fs, gs ja ss. Rekisteri cs säilöö koodin segmenttiä, eli koodia
  7132. luettaessa prosessori katsoo aina segmentistä 'cs' tavaraa. Cs:n pari
  7133. on ip-rekisteri, joka on 16-bittinen ja osoittaa koodin offsetin (ks.
  7134. OFFSET). Lähes kaikilla segmenttirekistereillä on tällainen "pari",
  7135. joka hoitaa offset-puolen.
  7136.  
  7137. 386-prosessorista lähtien näissä on myös extended osa, joka on myös
  7138. 16-bittinen ja käytettäessä tätä extended-osaa lisätään e-kirjain
  7139. rekisterin eteen. Näitä käytetään etenkin suojatussa tilassa, jossa
  7140. 64 kilotavua on hieman liian vähän, kun taas 32-bittinen (16-bittinen
  7141. extended osa normaalin jatkoksi) osoite riittää varsin hyvin, ainakin
  7142. toistaiseksi. Offset-rekisterejä ip, si, di, sp ja bp vastaavat siis
  7143. eip, esi, edi, esp ja ebp.
  7144.  
  7145. Seuraavaksi tulee ds, eli datasegmentti, joka on ns. oletussegmentti,
  7146. jota käytetään jos et määrittele toista segmenttiä assembler-käskyssäsi
  7147. (esim. mov [eax], ebx on sama kuin mov ds:[eax], ebx). Tämän "pari"
  7148. taasen on si/esi. Sitten es, fs ja gs, jotka ovat taas yleissegmentti-
  7149. rekisterejä, joista kaksi viimeistä lisättiin 368-prosessorin mukaan.
  7150. Ainoastaan es:llä on pari, di/edi. Sitten löytyy ss, eli pinosegmentti,
  7151. joka osoittaa siis ohjelman pinon (ks. PINO) segmenttiin ja tämän
  7152. offset-pari, sp/esp. Ylimääräistä bp/ebp -offsetrekisteriä käytetään
  7153. useasti osoittamaan funktion parametreihin ja omaan muistiin.
  7154.  
  7155. PINO Ohjelmalle on varattu pino, josta funktiot voivat varata muistia
  7156. siirtämällä ss:esp -parin osoittamaa osoitetta. Pinossa välitetään
  7157. myös parametrit. Pino toimii LIFO-periaatteella (last in, first out),
  7158. joka tarkoittaa, että viimeiseksi sinne pantu tieto tulee ensimmäisenä
  7159. pois. Pinoon tallennetaan ja sieltä poistetaan tavaraa push ja pop
  7160. käskyillä. Lisää tietoa kannattaa katsoa ulkoista assembleria
  7161. käsittelevästä luvusta.
  7162.  
  7163. OBJEKTI Tällä on käyttöyhteydestä riippuen muutamakin merkitys, mutta tär-
  7164. keimmät lienevät alhaalla. Tietokonemaailmassa merkitys ei ole aivan
  7165. sama kuin tosielämässä, esimerkiksi seksiobjektista puhuttaessa. =)
  7166.  
  7167. Objekti-sanaa käytetään puhuttaessa pelien esineistä, sekä myöskin
  7168. spriteistä ja bittikartoista, eli käytännössä objekti on jokin
  7169. yksittäinen esine (luultavasti pelien käyttämä nimitys on juuri peräisin
  7170. tästä bittikartta-merkityksestä, muistattehan te seikkailupelit?).
  7171.  
  7172. Myös olioille on joskus joissain yhteyksissä käytetty nimitystä
  7173. objekti, vaikkei se nyt olekaan enää kovin yleistä, ainakaan minun
  7174. tietääkseni.
  7175.  
  7176. Objektitiedosto taas on yksittäinen, käännetty lähdekoodi (C, assembler
  7177. tai joku muu), jossa on tiettyjä segmenttejä, jotka sisältävät koodia
  7178. ja lukumuuttujia. Lisäksi tällainen objektitiedosto sisältää paljon
  7179. tietoa funktioiden nimistä ja funktioiden sijainnista tiedostossa,
  7180. joita tarvitaan linkkauksessa (ks. LINKKAUS).
  7181.  
  7182. LINKKAUS Tämä tapahtuma on viimeinen osa ohjelman käännösvaiheessa. Siinä
  7183. konekielelle käännetyt objektitiedostot "linkataan", eli liitetään
  7184. yhteen ajettavaksi tiedostoksi. Omien objektitiedostojesi lisäksi gcc
  7185. linkkaa mukaan standardim C-kirjaston ja muut määrittelemäsi kirjastot,
  7186. sekä ns. stubin (ks. STUB).
  7187.  
  7188. STUB Sijaitsee EXE:n alussa ja käynnistyy ohjelman käynnistyessä. Sen
  7189. tehtävä on raivata itse ohjelmalle sen koodin tarvitsema muistialue
  7190. ja toimittaa muistipalvelut sun muut toimintakuntoon, jotta ohjelman
  7191. ei tarvitse huolehtia näistä. Stub tavallaan hoitaa ohjelman
  7192. suojatun tilan autuuteen sen omalle muistialueelle, jotta itse
  7193. pääohjelmalla on helppoa. Tästä syystä perusmuistin kulutus on suojatun
  7194. tilan ohjelmissa niin pientä, sillä vain stub pitää saada perusmuistiin
  7195. ja sieltä käsin se sitten hinaa pääohjelman jatkettuun muistin, megan
  7196. yläpuolelle.
  7197.  
  7198. REAALITILA PC:n "alkuperäinen" tila, jossa on segmentit ja offsetit ja 1
  7199. megan yläraja muistinkäytöllä. Tätä ollaan kierretty ohjelmilla kuten
  7200. EMM386 ja HIMEM, jotka vastaavasti toimittavat suojatun tilan
  7201. jatkomuistia reaalitilan ohjelmille (ks. EMS ja XMS).
  7202.  
  7203. SUOJATTU TILA Jo 286-prosessorien mukana esitelty tila, mutta paremmin
  7204. toteutettuna vasta 386:ssa (siksi DJGPP ei toimi vasta kuin sillä).
  7205. Suojatussa tilassa on kaikki muisti käytettävissä ja prosessori
  7206. tarjoaa useita palveluja, kuten muistin suojaus ja monta muuta kivaa
  7207. ominaisuutta, jolla yksittäiset ohjelmat eivät pääse niin helposti
  7208. kaatamaan konetta.
  7209.  
  7210. EMS Expanded Memory Services on tapa antaa jatkettua muistia reaalitilan
  7211. ohjelmille. Muistinhallintaohjelma, yleensä EMM386.EXE toimii siten,
  7212. että se käyttää suojattua tilaa hyväkseen mapaten yli megan alueelta
  7213. muistipalasia alle megan alueelle muistin kohtaan, jota kutsutaan
  7214. nimellä PAGE FRAME (katso vaikka DOS:sin helpeistä). Ohjelma voi varata
  7215. EMS-sivuja, joiden koko on 16 kilotavua ja sitten asettaa niistä
  7216. maksimissaan 4 kerralla näkyviksi (siksi page framen koko on yleensä
  7217. 4*16=64kilotavua). Nopea tapa, muttei niin nopea kuin suora muistin
  7218. osoitus.
  7219.  
  7220. XMS Extended Memory Services taas on systeemi, joka perustuu siihen, että
  7221. järjestelmä tarjoaa joukon funktioita, joilla voit varata XMS-muistia
  7222. ja kopioida sitä perusmuisti<->jatkomuisti, ja
  7223. jatkomuisti<->jatkomuisti -alueilla. Ongelmana on se, että reaalitilan
  7224. ohjelma ei voi käsitellä jatkomuistia kuin kopioimalla sen ensin
  7225. perusmuistiin ja sitten takaisin jatkomuistiin, mikä tekee tästä usein
  7226. aika hitaan tavan.
  7227.  
  7228. PALETTI Tämä on näytönohjaimen muistissa oleva taulukko, jossa on
  7229. värinumeroiden (värin 0, värin 1, värin 2 jne.) väriarvot, eli se
  7230. paljonko mikäkin väri sisältää punaista, vihreää ja sinistä. Palettia
  7231. ei käytetä high-color ja true-color tiloissa (ks. HIGHCOLOR ja
  7232. TRUECOLOR), vaan ainoastaan 256- ja 16-värisissä tiloissa. Lisää
  7233. paletin asettamisesta ja lukemisesta palettia käsittelevästä luvusta.
  7234.  
  7235. HIGHCOLOR on väritila, jossa värejä on 65536 tai joissain tapauksissa
  7236. 32768 kappaletta, eli 16-bittinen tai 15-bittinen pikseli
  7237. (ks. PIKSELI). Tämän pikselin värinumero on jaettu yleensä siten,
  7238. että numerosta 5 bittiä on tarkoitettu punaiselle, 6 vihreälle ja
  7239. 5 siniselle (koska ihmisen silmä kai aistii tarkimmin vihreää),
  7240. joten erillistä palettia ei tarvita. 15-bittisessä tilassa
  7241. vastaavasti on vain 5 bittiä / väriarvo. Myös jotain virityksiä
  7242. 14-bittisistä tiloista taitaa olla. Ks. myös PALETTI ja TRUECOLOR.
  7243.  
  7244. TRUECOLOR on väritila, jossa on 16.7 miljoonaa väriä, tarkemmin 2^24
  7245. väriä. Jako on kuten high-color tiloissa (ks. HIGHCOLOR), mutta
  7246. jokaiselle väriarvolle on 8 bittiä, eli 1 tavu punaiselle, vihreälle
  7247. ja siniselle. Ei varmaan tarvitse erikseen mainita, että tällaiset
  7248. tilat ovat ohjelmoijan taivas. Uudempina on myös 32-bittiset tilat,
  7249. joissa yksi tavu käytetään tietääkseni hukkaan. Tämä sen takia, että
  7250. 32-bittinen pikseli (ks. PIKSELI) on paljon helpompi käsitellä, kun
  7251. rekisterit ovat 32-bittisiä, samoin kuin joidenkin assembler-käskyjen
  7252. käyttämät alkioiden koot. 24 tai 32 bittiä voidaan varmaan jakaa useilla
  7253. muillakin tavoilla (ks. CMYK ja RGB) ja tehokkaammin kuin kaksi edellä
  7254. esitettyä, mutta en tiedä kuinka paljon käytännössä käytetään
  7255. toisenlaisia bittien jakotapoja.
  7256.  
  7257. CMYK Cyan, Magenta, Yellow, black. Tämä on yksi tapa jakaa väriavaruus,
  7258. eli käytetään normaalin rgb-tripletin sijasta (ks. RGB) syaania,
  7259. magentaa, keltaista ja mustaa. Myös CMY-tyyppiä on näkynyt, josta siis
  7260. musta puuttuu. Tätä ei käytetä kovin paljoa pelimaailmassa, mutta
  7261. printtereiden ja skannereiden kanssa toiminut on varmaan tästä
  7262. kuullut. Muistaisin, että on vielä pari tapaa jakaa värit, jokin YMK
  7263. tai vastaava oli ainakin, mutta tiedän selittää vain CMYK-, CMY- ja
  7264. RGB-mallit.
  7265.  
  7266. RGB Red, Green, Blue. Tapa jakaa väriavaruus, eli jokainen väri sen puna-
  7267. viher- ja sinikomponentteihin. Vähän samaan tyyliin siis kun sekoitat
  7268. Punaisesta, sinisestä ja keltaisesta vesivärit ja muun vastaavan niin
  7269. tietokoneella ja televisioissa käytetään tätä tapaa. Tiedä sitten
  7270. miksi vihreä, luultavasti se soveltuu paljon helpommin sädeputkelle.
  7271.  
  7272. RGB-AVARUUS Väriavaruus ajatellaan kuutioksi, jossa XYZ-akseliston
  7273. korvaa RGB-akselisto. Kuutio on rajallinen ja rajat asettavat
  7274. värikomponenttien minimi- ja maksimiarvot. Esim. normaalin
  7275. VGA-paletin värit voidaan ajatella pisteiksi kuutiossa, jonka
  7276. alakulma on (0,0) ja vastakkainen kulma (63,63).
  7277.  
  7278. KVANTISOINTI Paletin kvantisointi on tapa optimoida käytössä olevaa
  7279. palettia. Useasti tarvittaisiin enemmän värejä käyttöön kuin mitä
  7280. niitä on käytettävissä ja tähän käytetään paletin värien "optimointia",
  7281. joissa yhdistellään toisiaan lähellä olevia värejä. Kvantisoinnin
  7282. tehtävä on siis lyhyesti etsiä optimaalinen n väriä sisältävä paletti
  7283. jolla voidaan näyttää mahdollisimman alkuperäistä vastaavasti
  7284. m-värinen kuva.
  7285.  
  7286. MOODI Yleisesti käytetty lyhenne näyttötilasta, screen mode.
  7287.  
  7288. HEKSA 16-kantainen, eli HEKSAdesimaalinen luku, käytetään monesti muisti-
  7289. osoitteissa ja porttien numeroissa. Lisää tietoa tiedostosta LUVUT.TXT
  7290.  
  7291. FYYSINEN OSOITE Ks. SELEKTORI
  7292.  
  7293. LOOGINEN OSOITE Ks. SELEKTORI
  7294.  
  7295. SIIRROSOSOITE Ks. OFFSET
  7296.  
  7297. POINTTERI Hiiren kursori tai yleensä ohjelmoinnissa tietoalkio, joka
  7298. sisältää muistiosoitteen. Pointteri näyttömuistiin on siis alkio,
  7299. jonka arvo on näyttömuistin osoite (ks. OFFSET, SEGMENTTI).
  7300. Hyödylliseksi pointterin tekee se, että sitä voidaan indeksoida,
  7301. eli sitä voidaan käyttää kantaosoitteena johonkin muistialueeseen.
  7302. Yhden indeksin osoittaman alkion pituus on pointterityypin pituus.
  7303. Jos pointteri on char-tyyppinen niin sen yksi alkio on yhtä pitkä
  7304. kuin yksi char-alkio, eli 1 tavu. Indeksi 10 olisi siis 10 tavua
  7305. pointterin osoittamasta muistista eteenpäin.
  7306.  
  7307. Tätä käytetään hyväksi esimerkiksi kaksoispuskurissa
  7308. (ks. KAKSOISPUSKURI), jossa pointteri osoittaa sen alkuun (samoin
  7309. kuin indeksi 0) ja sen 600. alkio kaksoispuskurin 600. tavuun,
  7310. tässä tapauksessa tulevan framen (ks. FRAME) 600. pikseliin (ks.
  7311. PIKSELI), olettaen että ollaan 256-värisessä tilassa.
  7312.  
  7313. Lyhesti: Pointteri on muistiosoite, indeksointi on tapa saada indeksin
  7314. määrämä alkio pointterin osoittamalta muistialueelta. Esim. 12. tavu
  7315. pointterin alusta saataisiin pointterin indeksillä 11 (indeksi 0 on
  7316. 1. tavu). Muista, että int-tyyppisen pointterin yhden indeksin
  7317. osoittaman alkion pituus on sizeof(int), eli 4 tavua, jolloin indeksissä
  7318. 0 on 1., 2., 3. ja 4. tavu ja indeksissä 1 vastaavasti 5., 6., 7. ja 8.
  7319.  
  7320. FRAME Tarkoittaa yhtä näyttöruudullista, yhtä näyttöruudun päivityskertaa.
  7321. Jos käytät kaksoispuskuria on frame se, minkä kopioit näyttömuistiin.
  7322. Lyhyesti frame siis on valmis, näytettäväksi tarkoitettu ruudullinen
  7323. kuva-informaatiota. Katso selvennykseksi myös kohta FRAMERATE.
  7324.  
  7325. FRAMERATE On se montako framea (ks. FRAME) jokin ohjelma voi tuottaa
  7326. tietyssä ajassa, yleensä sekunnissa (tätä framea/sekunti kutsutaan
  7327. myös nimellä FPS). Jos matopelisi esimerkiksi pystyisi päivittämään
  7328. madon sijainnin ruudulla, esteet ja muut objektit vaikka 10 kertaa
  7329. sekunnissa niin sen "FPS" olisi näinollen 10.
  7330.  
  7331. FPS, Frames per second. Ks. FRAMERATE.
  7332.  
  7333. VIRKISTYSTAAJUUS Luku ilmoitetaan yleensä hertseinä (herzeinä, hertzeinä?)
  7334. ja se kertoo montako kertaa sekunnissa (hertsi, hZ tarkoittaa
  7335. värähtelyä/sekunti) monitori ja näytönohjain (heikoin lenkki ratkaisee)
  7336. pystyvät päivittämään näyttöruutua. Normaalissa VGA-tilassa luku on 70,
  7337. minkä takia yleensä sanotaan, että hyvän toimintapelin tulisi pyöriä
  7338. 70 fps (ks. FRAMERATE). Tämä ei kuitenkaan estä sitä, että fps ei voisi
  7339. olla suurempi kuin virkistystaajuus, mutta jos ohjelmasi pyörittää
  7340. 300 kuvaa ruudulle sekunnissa (sopiva määrä 3D-enginelle jollain yksin-
  7341. kertaisella varjostuksella, kuten phongilla) niin vain 70 näkyy.
  7342.  
  7343. ANTIALIAS Tämä termi esiintyy yleensä englanninkielisessä materiaalissa
  7344. muodossa antialising, joka tarkoittaa kuvioiden reunojen pehmentämistä
  7345. väreillä. Esimerkiksi kun piirrät vinon viivan vihreällä mustalle poh-
  7346. jalle se näyttää aika rujolta, mutta kun lisäät jokaiseen kulmaan hie-
  7347. man tummanvihreätä näyttää viiva huomattavasti pehmeämmältä. Käytännössä
  7348. tämä hoidetaan laskemalla paljonko viivan "arvio" (se mitä piirretään
  7349. ruudulle) poikkeaa oikean viivan sijainnista ja mitä enemmän se poikkeaa
  7350. sen enemmän reunoille laitetaan samaa väriä (väri siis sekoitetaan siinä
  7351. suhteessa missä viiva sijaitsee minkäkin pikselin päällä.
  7352.  
  7353. CROSSFADE Suomeksi termi voisi olla ehkä ristiliu'utus. Ideana on, että
  7354. toinen kuva ilmestyy toisen takaa pikkuhiljaa, ensin vain haaleana,
  7355. mutta voimistuen hiljalleen toisen häipyessä ja lopuksi ensimmäisestä
  7356. kuvasta onkin tullut jälkimmäinen.
  7357.  
  7358. PALETTE ROTATION Eli kauniimmin paletinpyöritys rullaa palettia ympäri siten,
  7359. että aiemmin värinä 3 toiminut muuttuu väriksi 2, väri 2 muuttuu väriksi
  7360. 1, 1 muuttuu 0:ksi ja nolla menee väriksi 255, väri 255 väriksi 254 jne.
  7361. Eli siirretään koko palettia asken taaksepäin (tai eteenpäin) ja se joka
  7362. ei voi enää mennä edemmäs tai taaemmas laitetaan toiseen päähän
  7363. vapautuneelle paikalle. Myös osia paletista voidaan pyörittää. Tämä
  7364. aiheuttaa varsin kivan näköisiä efektejä, etenkin jos tausta sisältää
  7365. väriliu'utuksia. Tarkempaa tietoa palettia koskevasta kappaleesta ja
  7366. esimerkkiohjelmasta pal3.c.
  7367.  
  7368. RLE Taas uusi jännittävä lyhenne kokoelmaamme. Run Length Encoding
  7369. tarkoittaa käytännössä, että kun meillä on 5 kappaletta N-kirjaimia,
  7370. niin ilmoitamme ne tyyliin 5N. Näin säästämme 3 tavua tilaa jo
  7371. tuossakin. Idea on siis, että useat toistuvat merkit ilmoitetaan
  7372. numerona ja merkkinä. Toisaalta vaihteleva data on ongelma ja tähän
  7373. on useita kiertotapoja, kuten PCX:n lähestymistapa, jossa tavu, jonka
  7374. arvo on yli 192 tarkoittaa että seuraavaa tavua ilmestyy
  7375. <luettutavu>-192 kertaa, tai LBM-tyyli, jossa on yksi tavu, joka kertoo
  7376. joko kuinka monta pakkaamatonta pikseliä edessä on, tai kuinka monta
  7377. pakattua (muistaakseni jos n on alle 128 niin tarkoitetaan montako
  7378. pakkaamatonta edessä ja jos se on yli, niin sitten jotain tyyliin
  7379. n-127).
  7380.  
  7381. HANDLERI Tämä kummajainen on suomeksi sama kuin käsittelijä. Ohjelmassa
  7382. on usein monenlaisia handlereita, kuten näppishandleri, joka käsittelee
  7383. näppäinten painallukset tai esimerkiksi interrupt handleri, joka
  7384. käsittelee toiminnan painaessa CTRL-BREAK tai CTRL-C
  7385. -näppäinyhdistelmiä. Ks. myös KESKEYTYS.
  7386.  
  7387. KESKEYTYS PC-perusrakenteeseen kuuluvat keskeytykset, jotka osa ovat nk.
  7388. software-keskeytyksiä ja osa hardware-keskeytyksiä. Se kummantyyppinen
  7389. keskeytys on riippuu siitä aiheuttaako sen ohjelma itse (esimerkiksi
  7390. videokeskeytys 0x10 jolla voidaan vaihtaa vaikka näyttötilaa) vai
  7391. generoidaanko se laitteiston toimesta (kuten ajastinkeskeytys, joka
  7392. generoidaan halutuin välein). Keskeytyksen satuttua komento siirtyy
  7393. keskeytyskäsittelijään (ks. HANDLERI), joka hoitaa tarvittavat
  7394. toimenpiteet keskeytyksen satuttua. Käyttäjä voi itse koukuttaa
  7395. handlereita (ks. KOUKUTUS) ja näin tarjota keskeytyspalveluja tai
  7396. kutsua itse keskeytyksiä ja pyytää näiltä keskeytyskäsittelijöitä
  7397. palveluksia, kuten edellämainittu videotilan vaihto.
  7398.  
  7399. PC on aika keskeytyspohjainen tietokone ja esimerkiksi kovalevyn
  7400. lukeminen ja muu vastaava tehdään yleensä keskeytysten kautta. Monet
  7401. ajurit koukuttavat laitteen keskeytyksen ja kommunikoivat itse laitteen
  7402. kanssa, jolloin keskeytystä kutsuvan ohjelman ei tarvitse tietää
  7403. tarkasti miten laite toimii. Esimerkiksi hiirikeskeytyksen koukuttaa
  7404. hiiriajuri ja ajuri hoitaa suoran kommunikoinnin hiiren kanssa ohjelman
  7405. tarvitessa vain kutsua keskeytyskäsittelijää generoimalla
  7406. hiirikeskeytys.
  7407.  
  7408. KOUKUTUS (HOOKING) Keskeytyskäsittelijän muistiosoite sijaitsee taulukossa
  7409. aivan muistin alussa (luoja tietää onko se siellä suojatussa tilassa,
  7410. minä en ainakaan tiedä, mutta sillä ei onneksi ole väliä) ja koukutus
  7411. tarkoittaa sitä, että otat talteen alkup. keskeytyskäsittelijän
  7412. osoitteen ja sijoitat omasi sinne osoitteen tilalle, jolloin keskeytystä
  7413. kutsuttaessa käsky siirtyy omalle käsittelijällesi (ks. myös HANDLERI
  7414. ja KESKEYTYS). Voit myös kutsua vanhaa käsittelijää oman toimintasi
  7415. jälkeen.
  7416.  
  7417. LFB Tapaa osoittaa suoraan koko näyttömuistiin. Kuten VGA-segmentti
  7418. A000h, mutta sijaitsee kaukana 1 megan rajan yläpuolella, joten
  7419. kokorajoitus ei enää ole 64 kiloa.
  7420.  
  7421. BANKED-TILAT Toinen tapa päästä käsiksi yli 64 kilon näyttömuistiin on
  7422. tehdä pieni ikkuna (yleensä sama kuin VGA-segmentti ja koko 64
  7423. kiloa) jota liikutellaan pitkin näyttömuistia. Aika tuskainen
  7424. verrattuna LFB:hen (ks. LFB)
  7425.  
  7426. VESA eli Video Electronics Standards Assocation, jonka käsialaa ovat
  7427. mm. näytönohjaimien käsittelyyn yleisesti käytetty
  7428. VESA-standardi. Virallinen nimi standardille lienee kuitenkin VBE
  7429. (ks. VBE)
  7430.  
  7431. VBE eli VESA BIOS Extension on normaalin grafiikkakeskeytyksen 10h
  7432. rinnalle toteutettu joukko laajennuksia joka mahdollistaa
  7433. SVGA-tilojen näytönohjainriippumattoman käsittelyn. 1.2, 2.0 ovat
  7434. suosittuja ja 3.0 on ihan äskettäin saapunut.
  7435.  
  7436.  
  7437. 9.3 Lähteet
  7438. -----------
  7439.  
  7440. Muutamia erityismaininnan ansaitsevia dokumentteja sähköisessä ja
  7441. paperimuodossa sekalaisessa järjestyksessä, joiden sisältämää
  7442. informaatiota on käytetty tämän tutoriaalin tekoon.
  7443.  
  7444.  
  7445. Tiedostot:
  7446.  
  7447.  
  7448. PCGPE10.ZIP
  7449. Jokaisen ohjelmoijan pakkoimurointi. Sekalainen kokoelma valittuja
  7450. paloja. Sisältää 10 ensimmäistä Aphyxian traineria!
  7451.  
  7452. FMODDOC2.ZIP
  7453. Kaikille äänikorteista ja MOD-playereistä kiinnostuineille hieman
  7454. vaikea (äänikortin ohjelmointi ei nimittäin aina ole helppoa)
  7455. tutoriaali sisältäen kaiken tarvittavan tiedon. Löytyy jokaisen
  7456. itseään kunnioittavan TosiKooderin kovalevyltä.
  7457.  
  7458. HELPPC21.ZIP
  7459. Mainio asioiden tarkistamiseen soveltuva lähdeteos.
  7460.  
  7461. HPC21_P5.ZIP
  7462. Päivitys edelliseen sisältäen Pentium-käskyt.
  7463.  
  7464. TUT*.ZIP
  7465. Asphyxian VGA-trainerit. Etsi hakusanalla Asphyxia.
  7466.  
  7467. 3DICA*.ZIP
  7468. 3D ohjelmoija-wannaben sekä kokeneemmankin raamattu. Suomen
  7469. kielellä kaiken lisäksi!
  7470.  
  7471. DJTUT255.ZIP
  7472. Selittää DJGPP:n AT&T-syntaksin ja inline-asseblerin
  7473. englanniksi. Korvaamaton jos haluaa käyttää assembleria
  7474. DJGPP-ohjelmissaan!
  7475.  
  7476. ASSYT.ZIP
  7477. Assemblerin alkeet suomeksi.
  7478.  
  7479. NASM095B.ZIP
  7480. Tällä voit tehdä Intel-syntaksin assemblerilla DJGPP:n
  7481. COFF-muotoisia objektitiedostoja. Tiivistettynä TASM joka osaa
  7482. myöskin DJGPP:n objektiformaatin. Huomaa, että uusin versio voi
  7483. olla muutakin kuin 0.95 (095-osa tiedostonimessä).
  7484.  
  7485. ABEDEMO?.ZIP
  7486. Ruotsalainen demokoulu. Ei onneksi ruotsia, vaan
  7487. englantia. Ensimmäisiä lukemiani tutoriaaleja, joka auttoi minut
  7488. alkuun koodauksessa.
  7489.  
  7490. INTER*.ZIP
  7491. Ralph Brownin keskeytyslista. Sisältää hurjan määrän paketteja ja
  7492. kyllä tietoakin.
  7493.  
  7494.  
  7495. Kirjallisuus:
  7496.  
  7497.  
  7498. Opeta itsellesi C++ -ohjelmointi 21 päivässä
  7499. Jos et vielä osaa C++:ssaa tai C:tä, niin tämä voi olla
  7500. lainaamisen arvoinen teos. Kokeneemmalle ohjelmoijalle
  7501. suositeltavampi voi olla jokin muu, mutta monet on tämä kirja
  7502. auttanut alkuun.
  7503.  
  7504. 486-ohjelmointi
  7505. Aina kun joku on kysynyt assembler-ohjelmointia käsittelevää
  7506. kirjaa, niin tällä hänet on vaiennettu. Omasta mielestänikin kelpo
  7507. kirja.
  7508.  
  7509. Assembler-ohjelmointi
  7510. Vaan joku kuitenkin oli sitä mieltä, että 486-ohjelmointi ei ollut
  7511. paras, vaan että tämä kirja olisi selkeämpi. Itse en ole tätä
  7512. lukenut.
  7513.  
  7514. Computer Graphics: Principles and Practice
  7515. Grafiikkaohjelmoijan raamattu. Sisältää paljon erilaisista
  7516. algoritmeistä sun muusta. Tietääkseni.
  7517.  
  7518. Zen of graphics programming: Second edition
  7519. Grafiikkaohjelmoijan koraani. Tosin nykyään VESA:n ja
  7520. huippumodernien 3d-engineiden aikana osa tiedosta on
  7521. vanhentunutta. Sisältää kuitenkin todella tehokkaita
  7522. optimointikikkoja sun muuta mukavaa.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement