Tuossa eräänä päivänä seurasin sivusta kun serkkuni pelasi jotain PS2 peliä jossa hilluttiin ilmeisesti jollain ketulla ja kerättin omenoita? Noh pelistä jäi kuitenkin mieleen siinä ollut siirrosefekti joka oli kenttien välissä. Efektissä kuva pyöri,zoomasi,'suli' ja feidasi. Jostain syystä kuitenkin sain mieleeni että tuosta täytyy tehdä koodivinkki. Vaikka periaattessahan tämä efekti on aika wanha ja varmasti tuon saisi kasattua kasaan pienen googletus-session jälkeen.
Efektin saa toteutettua yksinkertaisesti siten että ensin pikselin sijainti (x,y) muunnetaan ympyräkoordinaateiksi (r,phi) jonka jälkeen pikselille lasketaan uusi sijainti muuttamalla kulmaa ja sädettä. Kun kulmaa pienennetään saadaan kuva pyörimään kellon suuntaa vastaan ja sädettä pienentämällä kuva lähenee, koska laskettaessa uuden pikselin paikkaa ei lasketa uutta loppusijaintia vaan mistä otetaan vanha pikseli. Kun uusi sijainti on laskettu otetaan sen hetkisestä sijainnista pikselin arvo ja vanha pikseli ja sekoitetaan ne jollain suhteella('sulaminen'). Vielä lopuksi uutta pikselin arvoa piennetään hieman jotta saadaan mukaan feidaus.
Onneksi nuo siirrokset voidaan laskea etukäteen taulukkoon jolloin säästytään paljolta laskemiselta per pikseli. Kuitenkin koodiesimerkissä on mukana MMX-asm-inline versio koska C-versio oli sen verran hidas että oli pakko tehdä sille jotain.
Tuosta MMX-versiosta täytyy mainita sen verran että siinä käytetään fixed-point-aritmetiikka jotta saadaan laskettua nuo 'sulamiset' ja feidaukset. Ongelmaksi muodostui se että käytettäessä 16-bit laskuja vie etumerkki yhden bitin jolloin desim.osalle ei jää kuin 7-bittiä ja tämä vuorostaan aiheuttaa sen että laskut menevät vähän hankaliksi, koska kertolaskun jälkeen desim.osa on 14-bittiä ja MMX-mul-käsky 'katkaisee' luvun 16-bitin kohdalta. Tällöin 2-bittiä jotka kuuluvat pikselille menee desim.osan mukana. Tästä syystä tuli vähän kikkailua siihen koodin, mutta eipä tuo MMX-osa ollut esimerkin aiheenakaan(ja Ext.MMX:ssä on etumerkitön kertolasku).
En kuitenkaan muista enää aivan tarkalleen millainen tuo efekti oikeastaan oli joten täytyy luottaa siihen että se näyttää siltä miltä tämä esimerkki näyttää. Lopuksi vielä muutama esim. kerroinyhdistelmä. Jotta nämä toimivat pitää MMX-versio ottaa pois käytöstä tai muuttaa MMX-versioon liittyviä kertoimia samallalailla. Ohjelma tekee efektin joko kuvalle(raaka-dataa pic.raw-tiedostosta 640*480*24bit) tai jos kuvaa ei ole ladattavissa niin laskee yksinkertaisen kuvan jolle efekti tehdään.
Pelkkä Rotaatio:
#define ROT_MUL 0.045
#define XOOM_MUL 1
#define PIC_MUL1 0
#define PIC_MUL2 1
#define FADE_MUL 1
Pelkkä Zoom:
#define ROT_MUL 0
#define XOOM_MUL 0.9525
#define PIC_MUL1 0
#define PIC_MUL2 1
#define FADE_MUL 1
Pelkkä Fade:
#define ROT_MUL 0
#define XOOM_MUL 1
#define PIC_MUL1 1
#define PIC_MUL2 0
#define FADE_MUL 0.9375
Valmis exe(copy-paste osoite kenttään):
http://www.geocities.com/prlnsop/rtxmfd.zip
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <stdlib.h> #include <stdio.h> #include <math.h> #define MY_PI 3.14159265358979323846264338327950288419716939937510582097 #define ROT_MUL 0.045 #define XOOM_MUL 0.9525 #define PIC_MUL1 0.75 #define PIC_MUL2 0.25 #define FADE_MUL 0.9375 #define CPUID_STD_MMX 0x00800000 #define FEATURE_CPUID 0x00000001 #define FEATURE_STD_FEATURES 0x00000002 #define FEATURE_MMX 0x00000020 HINSTANCE hInst; HWND hMainWindow; char clzName[]="RotoXoomFeider"; unsigned char *DrawBuffer; unsigned char *CopyBuffer; int *xbuff; int *ybuff; unsigned int shift1[]={1,0}; unsigned int shift2[]={7,0}; unsigned int shift3[]={2,0}; unsigned int shift4[]={14,0}; //0.75,0.25,0.75,0.25 unsigned short muller[]={0x0060,0x0020,0x0060,0x0020}; //0.9375,0.9375,0.9375,0.9375 unsigned short muller2[]={0x0078,0x0078,0x0078,0x0078}; void RotoXoom(void); void RotoXoom_MMX(void); void (*TheRotoXoom)(void); /*AMD Processor Recognition(AppNote:20734)*/ unsigned int get_feature_flags() { unsigned int result = 0; /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Step 1: Check if processor has CPUID support. The processor faults ;; with an illegal instruction exception if the instruction is not ;; supported. This step catches the exception and immediately returns ;; with feature string bits with all 0s, if the exception occurs. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ __try { __asm{ xor eax, eax xor ebx, ebx xor ecx, ecx xor edx, edx cpuid } } __except (EXCEPTION_EXECUTE_HANDLER) { return (0); } result |= FEATURE_CPUID; __asm { /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Step 2: Check if CPUID supports function 1 (signature/std features) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ xor eax, eax // CPUID function #0 cpuid // largest std func/vendor string test eax, eax // largest standard function==0? jz no_standard_features // yes, no standard features func or [result], FEATURE_STD_FEATURES // does have standard features /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Step 3: Get standard feature flags and signature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ mov eax, 1 // CPUID function #1 cpuid // get signature/std feature flgs /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Step 4: Extract desired features from standard feature flags ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ /*;; Check for MMX support*/ mov ecx, CPUID_STD_MMX // bit 23 indicates MMX support and ecx, edx // supports MMX ? CPUID_STD_MMX:0 neg ecx // supports MMX ? CY : NC sbb ecx, ecx // supports MMX ? 0xffffffff:0 and ecx, FEATURE_MMX // supports MMX ? FEATURE_MMX:0 or [result], ecx // merge into feature flags /* Extract features specific to non AMD CPUs */ no_standard_features: } return result; } int SetRXFunc() { if(get_feature_flags()&FEATURE_MMX){ TheRotoXoom=RotoXoom_MMX; } else { TheRotoXoom=RotoXoom; } return true; } void InitBuff() { int i,j; float x,y; int ax,ay; float ang,rad; FILE *data; static int start=1; if(start){ //turha tarkistaa samaa asiaa useaan kertaan SetRXFunc(); start=0; } data=fopen("pic.raw","rb"); if(data!=NULL){ for(i=0;i<480;i++) for(j=0;j<640;j++){ DrawBuffer[i*640*3+j*3+2]=fgetc(data); DrawBuffer[i*640*3+j*3+1]=fgetc(data); DrawBuffer[i*640*3+j*3+0]=fgetc(data); } fclose(data); } else { //lasketaan kuva kun ei ollut tiedostoa mistä ladata for(i=0;i<480;i++) for(j=0;j<640;j++){ DrawBuffer[i*640*3+j*3+2]=sqrt((j-320)*(j-320)+(i-240)*(i-240)); DrawBuffer[i*640*3+j*3+1]=i&j; DrawBuffer[i*640*3+j*3+0]=i^j; } } //käännetään kuva for(i=0;i<480;i++) for(j=0;j<640;j++){ CopyBuffer[i*640*3+j*3+0]=DrawBuffer[(479-i)*640*3+j*3+0]; CopyBuffer[i*640*3+j*3+1]=DrawBuffer[(479-i)*640*3+j*3+1]; CopyBuffer[i*640*3+j*3+2]=DrawBuffer[(479-i)*640*3+j*3+2]; } for(i=0;i<480;i++) for(j=0;j<640;j++){ DrawBuffer[i*640*3+j*3+0]=CopyBuffer[i*640*3+j*3+0]; DrawBuffer[i*640*3+j*3+1]=CopyBuffer[i*640*3+j*3+1]; DrawBuffer[i*640*3+j*3+2]=CopyBuffer[i*640*3+j*3+2]; } //lasketaan siirrokset etukäteen for(i=0;i<480;i++){ for(j=0;j<640;j++){ x=(float)j-320.0; y=(float)i-240.001; //muunnetaan (x,y)->(r,phi) //korjataan arkustangentti if(y<0) ang=MY_PI*1.5-atan(x/y); else ang=MY_PI*0.5-atan(x/y); rad=sqrt(x*x+y*y); //lasketaan mistä uusi pikseli otetaan //pyöritetään cw koska kuvan pitää pyöriä ccw ang-=ROT_MUL; //piennetään sädettä rad*=XOOM_MUL; //lasketaan pikselin uusi sijainti ax=(int)(320+cos(ang)*rad); ay=(int)(240+sin(ang)*rad); //ei yli kuvan if(ax<0)ax=0; if(ax>639)ax=639; if(ay<0)ay=0; if(ay>479)ay=479; xbuff[i*640+j]=ax; ybuff[i*640+j]=ay; } } } void RotoXoom() { int i,off; int ax,ay; for(i=0;i<307200;i++){ //haetaan siirros ax=xbuff[i]; ay=ybuff[i]; //lasketaan uusi sijainti off=ay*1920+ax*3; //sotketaan uutta ja vanhaa sopivasti yhteen ja samalla feidataan DrawBuffer[i*3]=(int)(((float)DrawBuffer[i*3]*PIC_MUL1+(float)CopyBuffer[off]*PIC_MUL2)*FADE_MUL); DrawBuffer[i*3+1]=(int)(((float)DrawBuffer[i*3+1]*PIC_MUL1+(float)CopyBuffer[off+1]*PIC_MUL2)*FADE_MUL); DrawBuffer[i*3+2]=(int)(((float)DrawBuffer[i*3+2]*PIC_MUL1+(float)CopyBuffer[off+2]*PIC_MUL2)*FADE_MUL); } memcpy(CopyBuffer,DrawBuffer,640*480*3); } void RotoXoom_MMX() { int tmp; __asm { emms push edi push esi xor ecx,ecx mov ebx,DrawBuffer mov edx,CopyBuffer mov edi,xbuff mov esi,ybuff daluup: mov eax,[esi+ecx*4] imul eax,eax,640 mov tmp,eax mov eax,[edi+ecx*4] add eax,tmp imul eax,eax,3 //eax=(ybuff[off]*640+xbuff[off])*3 //0=draw //1=copy //2=d*m1+c*m2 pxor mm0,mm0 pxor mm1,mm1 punpcklbw mm0,[ebx] //mm0=X0R0G0B0 movq mm2,mm0 punpcklbw mm1,[edx+eax] movq mm3,mm1 //mm1=X1R1G1B1 punpcklwd mm0,mm1 //mm0=G1G0B1B0 punpckhwd mm2,mm3 //mm2=X1X0R1R0 psrlw mm0,shift1 //1 psrlw mm2,shift1 //15=etum.,14-7 pixel,6-0 desim. movq mm1,muller pmaddwd mm0,mm1 //kerrotaan ja summataan ja saadaan pmaddwd mm2,mm1 //31=etum.,30-14=pixel,13-0=desim. psrld mm0,shift2 //7 psrld mm2,shift2 //säilytetään 7bit desim. packssdw mm0,mm2 //kaikki(R,G,B) samaan rekisteriin //mm0=X2R2G2B2 movq mm2,mm0 pmulhw mm0,muller2 //kikkaillaan jotta saadaan psllw mm0,shift3 //alemmista 16bitistä 2 ylintä pmullw mm2,muller2 //bittiä talteen(jotka kuuluvat pikselille) psrlw mm2,shift4 por mm0,mm2 packuswb mm0,mm0 movd eax,mm0 mov [ebx],ax shr eax,16 mov [ebx+2],al add ebx,3 inc ecx cmp ecx,307200 jnz daluup pop esi pop edi emms } memcpy(CopyBuffer,DrawBuffer,640*480*3); } LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { static UINT uTimer; static HDC hdcDIBSection,hdcScreen; static BITMAPINFO *pbmiDIB; static HBITMAP hDIBSection; HBITMAP holdbitmap; HDC hDC; static int count=0; switch(uMsg){ case WM_CREATE: DrawBuffer=new unsigned char[640*481*3]; CopyBuffer=new unsigned char[640*480*3]; xbuff=new int[640*480]; ybuff=new int[640*480]; if(DrawBuffer==NULL||CopyBuffer==NULL||xbuff==NULL||ybuff==NULL){ MessageBox(NULL,"ERROR","Memory alloc failed(2400 kBytes)?\n",MB_OK|MB_ICONWARNING); PostQuitMessage(0); } pbmiDIB = (BITMAPINFO* )new BITMAPINFO[1]; if (pbmiDIB == NULL){ MessageBox(NULL,"ERROR","Mem alloc failed!\n",MB_OK|MB_ICONWARNING); PostQuitMessage(0); } pbmiDIB->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmiDIB->bmiHeader.biWidth = 640; //MMX menee viimeisellä pikselillä yhden //tavun yli joten sitä varten pitää //olla yksi rivi 'liikaa' pbmiDIB->bmiHeader.biHeight = 481; pbmiDIB->bmiHeader.biPlanes = 1; pbmiDIB->bmiHeader.biBitCount = 24; pbmiDIB->bmiHeader.biCompression = BI_RGB; pbmiDIB->bmiHeader.biSizeImage = 0; pbmiDIB->bmiHeader.biXPelsPerMeter = 0; pbmiDIB->bmiHeader.biYPelsPerMeter = 0; pbmiDIB->bmiHeader.biClrUsed = 0; pbmiDIB->bmiHeader.biClrImportant = 0; hDC=GetDC(hWnd); hDIBSection = CreateDIBSection (hDC, pbmiDIB, DIB_RGB_COLORS,(void**)&DrawBuffer, NULL, 0); if(!hDIBSection) { delete DrawBuffer; delete CopyBuffer; delete xbuff; delete ybuff; delete pbmiDIB; MessageBox(NULL,"ERROR","Call to CreateDIBSection failed!\n",MB_OK|MB_ICONWARNING); PostQuitMessage(0); } hdcScreen = GetDC(hWnd); hdcDIBSection = CreateCompatibleDC(hdcScreen); holdbitmap = (HBITMAP)SelectObject(hdcDIBSection, hDIBSection); InitBuff(); //n. 25 kertaa sekunnissa uTimer = SetTimer(hWnd, 1, 40, NULL); break; case WM_TIMER: //aloitetaan alusta melkein 2 sekunnin välein if(count++>40){InitBuff();count=0;} TheRotoXoom(); BitBlt(hdcScreen, 0, 0,640,480, hdcDIBSection,0, 0, SRCCOPY); break; case WM_DESTROY: delete DrawBuffer; delete CopyBuffer; delete xbuff; delete ybuff; delete pbmiDIB; DeleteObject(hDIBSection); KillTimer(hWnd,uTimer); break; case WM_SIZE: //ei anneta käyttäjän säätää ikkunaa SetWindowPos(hWnd,HWND_TOPMOST,0,0,648,508,SWP_NOMOVE); break; case WM_CLOSE: PostQuitMessage(0); break; default: return DefWindowProc(hWnd,uMsg,wParam,lParam); break; } return 1; } ATOM myRegClass() { WNDCLASS wc; memset(&wc,0,sizeof(WNDCLASS)); wc.style=CS_HREDRAW|CS_VREDRAW; wc.lpfnWndProc=(WNDPROC)WindowProc; wc.hInstance=hInst; wc.hIcon=LoadIcon(NULL,IDI_WINLOGO); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hbrBackground=(HBRUSH)(COLOR_GRAYTEXT+1); wc.lpszMenuName=NULL; wc.lpszClassName=clzName; wc.lpszMenuName=NULL; return (RegisterClass(&wc)); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) { bool Running=true;; MSG Msg; hInst=hInstance; if(!myRegClass()){ MessageBox(NULL,"Call to RegisterClass failed","ERROR",MB_OK|MB_ICONWARNING); return -1; } hMainWindow=CreateWindow(clzName,"RotoXoomerFeideri",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,648,508,NULL,NULL,hInstance,NULL); if(!hMainWindow){ MessageBox(NULL,"Call to CreateWindow failed","ERROR",MB_OK|MB_ICONWARNING); return -1; } ShowWindow(hMainWindow,SW_NORMAL); UpdateWindow(hMainWindow); while(Running){ if(PeekMessage(&Msg,NULL,0,0,PM_REMOVE)){ if(Msg.message==WM_QUIT){ Running=false; } else { TranslateMessage(&Msg); DispatchMessage(&Msg); } } } return 0; }
Spirits on kova jätkä;)
Taitaapi olla Crash Bandicoot :)
P.S: se on koira ;D
Itse asiassa bandicoot tarkoittaa muistaakseni pussimäyrää.
yhteen kohtaan Spirits kirjoitti:
PS2 peliä jossa hilluttiin ilmeisesti jollain ketulla ja kerättin omenoita?
Apua(!) ;D ;)
Luulin, että kaikki tietää tuon pelin. >_<
Siis no kyllähän minä periaattessa tuon pelin tiesin. En vain sillä hetkellä tunnistanut kun sitä ohimennen seurasin.
Se elukka on pussieläimiin(Marsupialia) kuuluva Eastern Barred Bandicoot(Perameles gunni). Piti tämäkin sitten selvittää.
Hienosti tehty efekti!
Anteeks mitäh? Vähän sä oot pro!
Hyvä jatka samaan malliin.
Aihe on jo aika vanha, joten et voi enää vastata siihen.