Joo, tää on ehkä huonosti kuvattu, mutta ehkä joku käsittää:
Siis, kun on vaikka neliön muotoisessa huoneessa, niin katsoessa eteenpäin seinä viistoutuu, siis syvyysefekti. Eli tarvitsisin apua siinä, miten kuva muutetaan
SDL:ssä näin: ennen:|--------| jälkeen: /| | | / | | | | | | | \ | |________| \|
Osaisiko joku auttaa? Tarkoitus olisi että sitä voisi eri määrissä väänellä, eli ei juuri pelkästään tuo absoluuttinen 90 astetta vääntämistä.
Mod. edit: https://www.ohjelmointiputka.net/ohje.php?tunnus=kohjeet
Jos nyt ymmärsin oikein, mitä tässä haetaan, niin tätä vinkkiä muokkailemalla luulisi hoituvan.
Muok: Tai enpä tiiä...
Hyvin lyhyesti sanottuna se menee näin:
Ohjelman maailmassa olevilla esineillä on tietty sijainti siellä. Se ilmaistaan ns. maailmakoordinaateissa.
Kun maailmasta halutaan muodostaa kuva, valitaan kameralle jokin paikka ja suunta. Sitten voidaan laskea, mitä kamera näkee. Maailmasta pitää siis valita kaikki ne esineet, joiden koordinaatit ovat kameran näkökentässä (ja jotka eivät ole piilossa muuten). Näkyvien esineiden koordinaateista lasketaan suhteelliset koordinaatit, jotka kertovat missä osassa näkökenttää ne ovat.
Tämän jälkeen suhteellisista koordinaateista tehdään projektio kaksiulotteiselle näyttöpinnalle, jotta saadaan tietoon ne piirtokomennot, joilla varsinainen näkymä rakennetaan. Loppu onkin vain näiden komentojen käyttöä.
Lukiossa (syventävllä kurssilla kai) opitaan sellaista kivaa matikkaa nimeltä lineaarialgebra, jonka ansiosta tämä koko prosessi on helppo ymmärtää. Jos sinulla ei ole vielä ollut mahdollisuutta tehdä tätä kurssia, homma onkin vaikeampi.
Myös valmiita ratkaisuja (koodia) on saatavilla aina oikeisiin 3D-moottoreihin asti. Kannattaa nyt harkita, millä tavalla lähdet opettelemaan näitä asioita ja kuinka monimutkaisen ratkaisun tarvitset omaan ohjelmaasi.
Tässä artikkelissa on kuva tästä kameran "näkökentästä". Jotenkin kaihertaa mieltä, että suomeksi oli parempikin sana, mutta se ei tule mieleen.
http://www.suomipelit.com/index.php?c=naytaartikkeli&id=58&s=1
Siellä on muutakin asiaa. Löysin sen juuri pikaisella haulla.
Heippa taas!
Macromedia Director on aika veikeä juttu...
Jos kuvaa käännetään vain pystyakselin ympäri, (kuten Wolfernsteinissa ja ymmärtääkseni myös tässä tapauksessa) helpottuu laskenta huomattavasti pahimpia mutkia tarvittaessa oikoen. Kuvan yksinkertaisen kiertämisen voi ratkaista yläasteen kuvaamataidontunneilta tuttujen pakopisteiden ja yksinkertaisen optiikan sekä toivottavasti jostain muualta tutun trigonometrian avulla:
Kuva kierra_kuvaa(Kuva VanhaKuva, int xkoko, int ykoko, double kulma) { Kuva UusiKuva(xkoko,ykoko); double pako, // pakopisteen paikka horisontissa r0, // (edellisen arvon käänteisluku) m, // sarakkeen etäisyys katsojaan / venytyskerroin r, // etäisyys pakopisteeseen x1,y1, // kuvapisteen koordinaatit kuvapinnalla x0; // piirrettävän sarakkeen paikka if(kulma==0.0) return UusiKuva; // kuva katoaa viivaksi r0 = fabs(cos(kulma)/sin(kulma)); if(r0==0.0) return VanhaKuva; // kuva suorassa kulmassa pako = sin(kulma)/cos(kulma); for(int x=0; x<xkoko; x++) { x0 = (float)x/xkoko-0.5; r = x0-pako; if(r<0.0==pako<0.0) continue; // poistetaan valekuvat if(pako<0) m = 1.0/r; else m = -1.0/r; x1 = sqrt(r0*r0 + m*m - 2.0*m*r0/sqrt(1.0+x0*x0)) // kosinilauseesta *xkoko*fabs(pako); if(x0<0) x1 = xkoko/2-x1; else x1 = x1+xkoko/2; m = pako/r; // sarakkeen venytyskerroin for(int y=0; y<ykoko; y++) { y1 = xsize/2 - (y-ykoko/2)*m; UusiKuva(x,y) = VanhaKuva((int)x1,(int)y1); } return UusiKuva; }
Tässä yhdelle kuvapinnalle tehdään tuollainen syvyysvenytysefekti. Kuva piirretään sarake kerrallaan ja hitaat laskutoimitukset (jako / neliöjuuri) tehdään vain kerran jokaista saraketta kohti. Useammassa suunnassa kieputtaminen vaatii taas syvällisempää uppoutumista avaruusgeometrian (tai 3D-rajapintojen) ihmeelliseen maailmaan...
Hyvin kummallisen lopputuloksen tuo algoritmi ainakin tuotti. Kokeilin kuvalla, jossa oli kirjavia pystyviivoja ja välissä aina mustaa, sekä yhtenäisellä väriliu'ulla. Itse funktiota muutin vain sen verran, että lisäsin puuttuvan aaltosulun ja muutin virheellisen muuttujan xsize oikeaksi xkoko-muuttujaksi; implementoin luokan Kuva, jotta kaikki toimi aivan suoraan. Algoritmi toimii eri leveyksillä ja korkeuksilla (suhde kai ratkaisee) eri tavalla, eikä oikein millään tullut tyydyttävää tulosta. Kuvan lähemmästä päästä jää reunalta osa puuttumaan ja matalilla kuvilla lopputulos onkin yllättäen aivan tyhjä tai vähintäänkin hyvin pieni kulmasta riippumatta.
Huomioikos tuo periaatteessa sen, että yksi pikseli näyttääkin kauempaa vain puolelta pikseliltä? Siis että A_B_C_D
kääntyisi kuvaksi A__B_CD
? En oikein tuosta koodista päässyt kärryille, mikä mikäkin pituus oli (kun en jaksa paperille ajatella).
Köyhän miehen helppo ratkaisu, joka ei tosin huomioi tuota x-pienenemistä kauempana, toimisi jokseenkin näin (ei ainakaan minulla segfaultannut):
// Parametrina kuva, vaakataso [0, 1] ja uudet vasemman ja oikean reunan korkeudet. kuva funktio(const kuva& vanhakuva, float hkfloat, int h1, int h2) { int i, j; int korkeus = vanhakuva.h(); int leveys = vanhakuva.w(); int uusikorkeus, laskukorkeus, alkukorkeus, loppukorkeus; uusikorkeus = max(h1, h2); // tai näin: h1 > h2 ? h1 : h2; kuva uusikuva(leveys, uusikorkeus); // Keskilinja, joka siis on suorassa uudessakin kuvassa, annetaan väliltä [0.0, 1.0] int hk = hkfloat * korkeus; if (hk < 0) hk = 0; else if (hk > korkeus) hk = korkeus; for (i = 0; i < leveys; ++i) { laskukorkeus = h1 + ((h2 - h1) * i + (leveys-1) / 2) / (leveys-1); if (laskukorkeus <= 0) break; alkukorkeus = (uusikorkeus - laskukorkeus) * hk / korkeus; loppukorkeus = alkukorkeus + laskukorkeus; for (j = alkukorkeus; j < loppukorkeus; ++j) { uusikuva(i, j) = vanhakuva(i, (j - alkukorkeus) * korkeus / laskukorkeus); } } return uusikuva; } uusikuva = funktio(vanhakuva, 0.5, 30, 70);
Metabolix kirjoitti:
Kuvan lähemmästä päästä jää reunalta osa puuttumaan ja matalilla kuvilla lopputulos onkin yllättäen aivan tyhjä tai vähintäänkin hyvin pieni kulmasta riippumatta.
Öh, näininhän tässä taas kävi... Koodissa oli tosiaankin pikku virhe: xsize
n paikalle kuuluukin tietenkin ykoko
, eikä xkoko
, kun y-koordinaatteja lasketaan. Neliön muotoisilla kuvilla virhettä ei tietenkään huomaa.
Metabolix kirjoitti:
Huomioikos tuo periaatteessa sen, että yksi pikseli näyttääkin kauempaa vain puolelta pikseliltä? Siis että
A_B_C_D
kääntyisi kuvaksiA__B_CD
?
Juu. Eli kauempanahan pystysuorat viivat näyttävät tihenevän. Tästä kryptinen neliöjuurilauseke. Tuossa lasketaan siis:
sqrt(r02 + r12 - 2|r0||r1|cos(KULMA(r0,r1)))
missä r0 (r0) on näkökentän/kuvan keskelle osoittava vektori ja r1 (m) piirrettävän sarakkeen keskelle osoittava vektori. lisäksi cos(arctan(x)) = 1 / sqrt(1+x2)
Tämän pitäisi toimia:
(Oma ohjelmani näytti tältä: http://olento.dyndns.org/docs/imgfold.zip)
Kuva kierra_kuvaa(Kuva VanhaKuva, int xkoko, int ykoko, double kulma) { Kuva UusiKuva(xkoko,ykoko); double pako, // pakopisteen paikka horisontissa r0, // (edellisen arvon käänteisluku) m, // sarakkeen etäisyys katsojaan / venytyskerroin r, // etäisyys pakopisteeseen x1,y1, // kuvapisteen koordinaatit kuvapinnalla x0; // piirrettävän sarakkeen paikka if(kulma==0.0) return UusiKuva; // kuva katoaa viivaksi r0 = fabs(cos(kulma)/sin(kulma)); if(r0==0.0) return VanhaKuva; // kuva suorassa kulmassa pako = sin(kulma)/cos(kulma); for(int x=0; x<xkoko; x++) { x0 = (float)x/xkoko-0.5; r = x0-pako; if(r<0.0==pako<0.0) continue; // poistetaan valekuvat if(pako<0) m = 1.0/r; else m = -1.0/r; x1 = sqrt(r0*r0 + m*m - 2.0*m*r0/sqrt(1.0+x0*x0)) // kosinilauseesta *xkoko*fabs(pako); if(x0<0) x1 = xkoko/2-x1; else x1 = x1+xkoko/2; m = pako/r; // sarakkeen venytyskerroin for(int y=0; y<ykoko; y++) { y1 = ykoko/2 - (y-ykoko/2)*m; UusiKuva(x,y) = VanhaKuva((int)x1,(int)y1); } return UusiKuva; } }
Ongelmana tässä menetelmässä ovat lähes kohtisuorassa olevat kuvat, joiden tapauksessa ko. funktion suorittamia laskutoimituksia ei ole määritelty. Tämä näkyy ikävänä hyppäyksenä pyöritettäessä kuvaa kohtisuoran aseman ohi. Kuvaa ei myöskään koskaan peilata.
Jaa, toi Metabolixin järjestelmä valmiiksi käännettynä toimii, just sellasta mä yritin. Ja juuri se tarkoitus mihin tuota käyttäisin on ymmärretty hyvin. Itse en tosin saanut tuota Metabolixin ratkaisua käännettyä. :(
ja koodissa on tietysti taas virhe...
return UusiKuva;
kuuluu tietysti for(int x ...
-silmukan ulkopuolelle:
// ... } // lauseke oli virheellisesti tässä } return UusiKuva; }
Kumpikaan ratkaisuista ei (tietenkään) käänny sellaisenaan, vaan joudut itse implementoimaan kuvitteelliset/abstraktit Kuva
-luokat. Voit tietysti myös esimerkiksi korvata oliot SDL_Surface
illa tai kuvadatataulukoilla.
Tämän pitäisi toimia sellaisenaan.
(kiertokulman yksikkönä radiaani, palautettu pinta vapautettava jälkeenpäin SDL_FreeSurfacella
)
#include <SDL/SDL.h> #include <cstring> SDL_Surface *kierra_kuvaa(SDL_Surface *vanha_kuva, double kulma) { int xkoko = vanha_kuva->w, ykoko = vanha_kuva->h, bpp = vanha_kuva->format->BytesPerPixel; SDL_Surface *uusi_kuva = SDL_CreateRGBSurface(SDL_SWSURFACE, xkoko,ykoko,bpp*8,0,0,0,0); double pako, // pakopisteen paikka horisontissa r0, // (edellisen arvon käänteisluku) m, // piirrettävän sarakkeen etäisyys katsojaan r, // ... ja pakopisteeseen x1,y1, // kuvapisteen koordinaatit kuvapinnalla x0; // piirrettävän sarakkeen paikka r0 = cos(kulma)/sin(kulma); if(kulma==0.0) return uusi_kuva; // kuva katoaa viivaksi pako = sin(kulma)/cos(kulma); r0 = fabs(r0); if(r0==0.0) { // kuva suorassa kulmassa SDL_BlitSurface(vanha_kuva,NULL,uusi_kuva,NULL); return uusi_kuva; } for(int x=0; x<xkoko; x++) { x0 = (float)x/xkoko-0.5; r = x0-pako; if(r<0.0==pako<0.0) continue; // poistetaan valekuvat if(pako<0) m = 1.0/r; else m = -1.0/r; x1 = sqrt(r0*r0 + m*m - 2.0*m*r0/sqrt(1.0+x0*x0)) // kosinilauseesta * xkoko*fabs(pako); if(x0<0) x1 = xkoko/2-x1; else x1 = x1+xkoko/2; m = pako/r; for(int y=0; y<ykoko; y++) { y1 = ykoko/2 - (y-ykoko/2)*m; if(x1>=0 && y1>=0 && x1<xkoko && y1<ykoko) memcpy((Uint8*)uusi_kuva->pixels +uusi_kuva->pitch*y +x*bpp, (Uint8*)vanha_kuva->pixels +vanha_kuva->pitch*(int)y1 +(int)x1*bpp, bpp); } } return uusi_kuva; }
kray kirjoitti:
Jaa, toi Metabolixin järjestelmä valmiiksi käännettynä toimii
P.S. kumpaa ratkaisua tarkoitit "Metabolixin järjestelmällä"? :)
Aihe on jo aika vanha, joten et voi enää vastata siihen.