java math graphics raycasting 2d-3d-conversion

java - Ray Casting con diferentes tamaños de altura



math graphics (1)

así que obviamente conoces los conceptos básicos de las técnicas de emisión de rayos Wolfenstein Para agregar altura variable necesita hacer esto:

  1. agregar información de altura por celda

    así que simplemente agregue otro valor a la información de su celda en su mapa de la tabla map[][] . Codifica las cosas como cadenas que es extraño ...

  2. actualizar la línea de escaneo render

    En algún lugar del código (después de que se detectó el impacto), se representa una línea vertical por cada rayo. Allí debe calcular el tamaño de la línea de exploración de forma similar (suponiendo que y = 0 está en la parte superior de la pantalla):

    y0 = center_of_view_y + projected_half_size y1 = center_of_view_y - projected_half_size

    Y debería cambiar a:

    y0 = center_of_view_y + projected_size y1 = y0 - 2*projected_half_size*wall_size

    Donde projected_half_size es el tamaño de línea calculado para la altura constante de la celda como lo obtuvo ahora, wall_size=<0,1> es la escala y center_of_view_y es la coordenada y de la línea del horizonte en su vista. Esto pondrá tu pared en el suelo.

  3. actualizar ray-casting

    ahora cuando golpeas la primera pared te detienes. Con una altura de pared variable, puede detenerse solo cuando golpea una pared de tamaño completo ( wall_size=1 ) o queda sin mapa. Tienes 2 opciones para implementar esto.

    1. recuerda todos los golpes y renderiza en orden inverso
    2. renderizar inmediatamente pero solo desde la última altura renderizada en lugar de desde el suelo.

    La primera opción es fácil de implementar pero requiere más memoria y es más lenta. El segundo es rápido y no necesita ninguna lista o pila. Pero implica un poco más de matemática para el renderizado de la línea de escaneo ( O(1) si está codificado correctamente)

    Jugué un poco con mi demo desde el enlace en la parte superior. Ahora el resultado debería verse así:

    Como puede ver, las celdas resaltadas en el mapa pasan a través de rayos por encima de su altura (para que pueda ver celdas más grandes detrás de ellas).

    Tenga cuidado una vez que agregue la dirección de altitud de movimiento (saltos, escaleras, etc.), la condición final debe ser diferente (la línea de escaneo renderizada toca la parte superior de la vista). Además, la parte de proyección de la coordenada y será diferente y deberá incluir la altitud real del jugador.

  4. agregar la parte superior

    Debe agregar la representación del lado superior. Es similar al renderizado de techo y piso. El Wolfenstein original del IIRC no tenía esta capacidad, pero los últimos pseudo juegos 3D como DOOM sí.

    Hay más enfoques posibles como Perspective Vision en Canvas, pero creo que lo más fácil de implementar (ya que tenemos suficiente información) es calcular la parte superior de las coordenadas de la línea de exploración vertical en textura y simplemente copiar píxeles. Como ya sabemos dónde el rayo golpeó la celda y también se conoce el ángulo del jugador / cámara. Para más información ver: PCGPE 1.0 Técnicas Doom

    Entonces, como primer paso, agregue golpes para caras posteriores también. Eso debería verse así:

    Eso se hace comprobando primero los hits de la última celda de hit. Ahora, si recuerda la última coordenada y renderizada del golpe anterior (de la misma línea de exploración), entonces, si se golpea la cara posterior en lugar de renderizar la cara, renderice el color del lado superior de la última y a la y real (o copie píxeles de la textura del piso / techo en su lugar) . Aquí el color verde utilizado para esto:

Si ayuda aquí, está el código mío C ++ (basado en GDI / VCL) para esto:

//--------------------------------------------------------------------------- //--- Doom 3D engine ver: 1.000 -------------------------------------- //--------------------------------------------------------------------------- #ifndef _Doom3D_h #define _Doom3D_h //--------------------------------------------------------------------------- #include <math.h> #include <jpeg.hpp> #include "performance.h" #include "OpenGLrep4d_double.h" //--------------------------------------------------------------------------- const DWORD _Doom3D_cell_size=10; // 2D map cell size const DWORD _Doom3D_wall_size=100; // full height of wall in map #define _Doom3D_filter_txr //--------------------------------------------------------------------------- class Doom3D { public: DWORD mxs,mys,**pmap; // 2D map // txr + height<<16 DWORD sxs,sys,**pscr; // pseudo 3D screen Graphics::TBitmap *scr; DWORD txs,tys,**ptxr,tn; // 2D textures Graphics::TBitmap *txr,*txr2; // textures, texture mipmaps resolution: /2 and /4 double plrx,plry,plrz,plra; // player position [x,y,z,angle] double view_ang; // [rad] view angle double focus; // [cells] view focal length struct _ray { double x,y,l; // hit or end of map position DWORD hit; // map cell of hit or 0xFFFFFFFF char typ; // H/V _ray() {}; _ray(_ray& a) { *this=a; } ~_ray() {}; _ray* operator = (const _ray *a) { *this=*a; return this; } //_ray* operator = (const _ray &a) { ..copy... return this; } }; _ray *ray; // ray[sxs] keytab keys; DWORD txr_sel; DWORD cell_h; Doom3D(); Doom3D(Doom3D& a) { *this=a; } ~Doom3D(); Doom3D* operator = (const Doom3D *a) { *this=*a; return this; } //Doom3D* operator = (const Doom3D &a) { ..copy... return this; } void map_resize(DWORD xs,DWORD ys); // change map resolution void map_height(DWORD height); // set height for whole map to convert maps from Wolfenstein3D demo void map_clear(); // clear whole map void map_save(AnsiString name); void map_load(AnsiString name); void scr_resize(DWORD xs,DWORD ys); void txr_load(AnsiString name); void draw(); void update(double dt); void mouse(double x,double y,TShiftState sh) { x=floor(x/_Doom3D_cell_size); if (x>=mxs) x=mxs-1; if (x<0) x=0; y=floor(y/_Doom3D_cell_size); if (y>=mys) y=mys-1; if (y<0) y=0; DWORD xx=x,yy=y; keys.setm(x,y,sh); if (keys.Shift.Contains(ssLeft )) pmap[yy][xx]=(txr_sel)|(cell_h<<16); if (keys.Shift.Contains(ssRight)) pmap[yy][xx]=0xFFFFFFFF; keys.rfsmouse(); } void wheel(int delta,TShiftState sh) { if (sh.Contains(ssShift)) { if (delta<0) { cell_h-=10; if (cell_h<10) cell_h=10; } if (delta>0) { cell_h+=10; if (cell_h>_Doom3D_wall_size) cell_h=_Doom3D_wall_size; } } else{ if (delta<0) { txr_sel--; if (txr_sel==0xFFFFFFFF) txr_sel=tn-1; } if (delta>0) { txr_sel++; if (txr_sel== tn) txr_sel= 0; } } } }; //--------------------------------------------------------------------------- Doom3D::Doom3D() { mxs=0; mys=0; pmap=NULL; sxs=0; sys=0; scr=new Graphics::TBitmap; pscr=NULL; ray=NULL; txs=0; tys=0; txr=new Graphics::TBitmap; ptxr=NULL; tn=0; txr2=new Graphics::TBitmap; plrx=0.0; plry=0.0; plrz=0.0; plra=0.0; view_ang=60.0*deg; focus=0.25; txr_sel=0; cell_h=_Doom3D_wall_size; txr_load("textures128x128.jpg"); map_resize(16,16); map_load("Doom3D_map.dat"); } //--------------------------------------------------------------------------- Doom3D::~Doom3D() { DWORD y; map_save("Doom3D_map.dat"); if (pmap) { for (y=0;y<mys;y++) delete[] pmap[y]; delete[] pmap; pmap=NULL; } if (ray) delete[] ray; ray=NULL; if (pscr) { delete[] pscr; pscr=NULL; } if (scr) delete scr; scr=NULL; if (ptxr) { delete[] ptxr; ptxr=NULL; } if (txr) delete txr; txr=NULL; if (txr2) delete txr2; txr2=NULL; } //--------------------------------------------------------------------------- void Doom3D::map_resize(DWORD xs,DWORD ys) { DWORD y; if (pmap) { for (y=0;y<mys;y++) delete[] pmap[y]; delete[] pmap; pmap=NULL; } mys=ys; mxs=xs; pmap=new DWORD*[mys]; for (y=0;y<mys;y++) pmap[y]=new DWORD[mxs]; map_clear(); plrx=(mxs-1)*0.5; plry=(mys-1)*0.5; plrz=0.0; plra=0.0*deg; } //--------------------------------------------------------------------------- void Doom3D::map_height(DWORD h) { DWORD x,y,c; for (y=0;y<mys;y++) for (x=0;x<mxs;x++) { c=pmap[y][x]; c&=0xFFFF; c|=h<<16; pmap[y][x]=c; } } //--------------------------------------------------------------------------- void Doom3D::map_clear() { DWORD x,y,c; for (y=0;y<mys;y++) for (x=0;x<mxs;x++) { c=0xFFFFFFFF; if ((x==0)||(x==mxs-1)) c=0; if ((y==0)||(y==mys-1)) c=0; pmap[y][x]=c; } } //--------------------------------------------------------------------------- void Doom3D::map_save(AnsiString name) { int hnd=FileCreate(name); if (hnd<0) return; DWORD y; y='' PAM''; FileWrite(hnd,&y ,4); // id FileWrite(hnd,&mxs,4); // x resolution FileWrite(hnd,&mys,4); // y resolution for (y=0;y<mys;y++) // map FileWrite(hnd,pmap[y],mxs<<2); y='' RLP''; FileWrite(hnd,&y ,4); // id FileWrite(hnd,&plrx,8); FileWrite(hnd,&plry,8); FileWrite(hnd,&plrz,8); FileWrite(hnd,&plra,8); FileClose(hnd); } //--------------------------------------------------------------------------- void Doom3D::map_load(AnsiString name) { int hnd=FileOpen(name,fmOpenRead); if (hnd<0) return; DWORD x,y; y='' PAM''; FileRead(hnd,&x ,4); // id if (x==y) { FileRead(hnd,&x,4); // x resolution FileRead(hnd,&y,4); // y resolution map_resize(x,y); for (y=0;y<mys;y++) // map FileRead(hnd,pmap[y],mxs<<2); } y='' RLP''; FileRead(hnd,&x ,4); // id if (x==y) { FileRead(hnd,&plrx,8); FileRead(hnd,&plry,8); FileRead(hnd,&plrz,8); FileRead(hnd,&plra,8); } FileClose(hnd); } //--------------------------------------------------------------------------- void Doom3D::scr_resize(DWORD xs,DWORD ys) { scr->HandleType=bmDIB; scr->PixelFormat=pf32bit; scr->SetSize(xs,ys); sxs=scr->Width; sys=scr->Height; delete[] pscr; pscr=new DWORD*[sys]; for (DWORD y=0;y<sys;y++) pscr[y]=(DWORD*)scr->ScanLine[y]; if (ray) delete[] ray; ray=new _ray[sxs]; } //--------------------------------------------------------------------------- void Doom3D::txr_load(AnsiString name) { AnsiString ext=ExtractFileExt(name).LowerCase(); for(;;) { if (ext==".bmp") { txr->LoadFromFile(name); break; } if (ext==".jpg") { TJPEGImage *jpg=new TJPEGImage; if (jpg==NULL) return; jpg->LoadFromFile(name); txr->Assign(jpg); delete jpg; break; } return; } DWORD y=tys; txr->HandleType=bmDIB; txr->PixelFormat=pf32bit; txs=txr->Width; tys=txr->Height; // mip map txr2->SetSize(txs>>1,(tys>>1)+(tys>>2)); txr2->Canvas->StretchDraw(TRect(0, 0,txs>>1,tys>>1),txr); txr2->Canvas->StretchDraw(TRect(0,tys>>1,txs>>2,(tys>>1)+(tys>>2)),txr); tn=txs/tys; txs=tys; delete[] ptxr; ptxr=new DWORD*[tys]; for (y=0;y<tys;y++) ptxr[y]=(DWORD*)txr->ScanLine[y]; } //--------------------------------------------------------------------------- void Doom3D::draw() { // total time measurement tbeg(); double tperf0=performance_tms; AnsiString tcls,tray,tmap,ttotal; double a,a0,da,dx,dy,l,mx,my; DWORD x,y,xs2,ys2,c,m; double xx0,yy0,dx0,dy0,ll0; DWORD c0,d0; double xx1,yy1,dx1,dy1,ll1; DWORD c1,d1; _ray *p; xs2=sxs>>1; ys2=sys>>1; // aspect ratio,view angle corrections a=90.0*deg-view_ang; double wall=double(sxs)*(1.25+(0.288*a)+(2.04*a*a)); // [px] // floor,ceilling/sky tbeg(); for (y=0;y<ys2;y++) for (x=0;x<sxs;x++) pscr[y][x]=0x000080FF; for ( ;y<sys;y++) for (x=0;x<sxs;x++) pscr[y][x]=0x00404040; tend(); tcls=tstr(1)+" cls"; // [cast rays] tbeg(); // diffuse + ambient lighting DWORD ch=155.0+fabs(100.0*sin(plra)); DWORD cv=155.0+fabs(100.0*cos(plra)); a0=plra-(0.5*view_ang); da=divide(view_ang,sxs-1); mx=mxs; my=mys; for (p=ray,a=a0,x=0;x<sxs;x++,a+=da,p++) { p->x=plrx; p->y=plry; p->hit=0xFFFFFFFF; p->typ='' ''; p->l=1.0e20; ll0=ll1=p->l; // grid V-line hits c0=0; dx0=cos(a); if (dx0<0.0) { c0=1; xx0=floor(plrx)-0.001; dx0=-1.0; } if (dx0>0.0) { c0=1; xx0=ceil (plrx)+0.001; dx0=+1.0; } if (c0) { dy0=tan(a); yy0=plry+((xx0-plrx)*dy0); dy0*=dx0; dx=xx0-plrx; dy=yy0-plry; ll0=(dx*dx)+(dy*dy); } // grid H-line hits c1=0; dy1=sin(a); if (dy1<0.0) { c1=1; yy1=floor(plry)-0.001; dy1=-1.0; } if (dy1>0.0) { c1=1; yy1=ceil (plry)+0.001; dy1=+1.0; } if (c1) { dx1=divide(1.0,tan(a)); xx1=plrx+((yy1-plry)*dx1); dx1*=dy1; dx=xx1-plrx; dy=yy1-plry; ll1=(dx*dx)+(dy*dy); } int height0=sys; // already rendered height [pixels] bool _hit,_back=false,_bck=true; if (!c0) ll0=1e20; if (!c1) ll1=1e20; for (;c0||c1;) { _hit=false; // grid V-line hits if (c0) { if (xx0<0.0) { c0=0; ll0=1e20; } if (xx0>=mx) { c0=0; ll0=1e20; } if (yy0<0.0) { c0=0; ll0=1e20; } if (yy0>=my) { c0=0; ll0=1e20; } } if ((c0)&&(ll0<ll1)) { m=DWORD(xx0-dx0); if ((m>=0.0)&&(m<mxs)&&(!_bck)){ c=pmap[DWORD(yy0)][ m ]; if ((c&0xFFFF)!=0xFFFF) { p->hit=c; p->typ=''V''; p->l=ll0; p->x=xx0; p->y=yy0; _hit=true; _back=true; _bck=true; }} if (!_hit) { c=pmap[DWORD(yy0)][DWORD(xx0)]; if ((c&0xFFFF)!=0xFFFF) { p->hit=c; p->typ=''V''; p->l=ll0; p->x=xx0; p->y=yy0; _hit=true; _back=false; _bck=false; } xx0+=dx0; dx=xx0-plrx; yy0+=dy0; dy=yy0-plry; ll0=(dx*dx)+(dy*dy); } } // grid H-line hits if (c1) { if (xx1<0.0) { c1=0; ll1=1e20; } if (xx1>=mx) { c1=0; ll1=1e20; } if (yy1<0.0) { c1=0; ll1=1e20; } if (yy1>=my) { c1=0; ll1=1e20; } } if ((c1)&&(ll0>ll1)&&(!_hit)) { m=DWORD(yy1-dy1); if ((m>=0.0)&&(m<mys)&&(!_bck)){ c=pmap[ m ][DWORD(xx1)]; if ((c&0xFFFF)!=0xFFFF) { p->hit=c; p->typ=''H''; p->l=ll1; p->x=xx1; p->y=yy1; _hit=true; _back=true; _bck=true; }} if (!_hit) { c=pmap[DWORD(yy1)][DWORD(xx1)]; if ((c&0xFFFF)!=0xFFFF) { p->hit=c; p->typ=''H''; p->l=ll1; p->x=xx1; p->y=yy1; _hit=true; _back=false; _bck=false; } xx1+=dx1; dx=xx1-plrx; yy1+=dy1; dy=yy1-plry; ll1=(dx*dx)+(dy*dy); } } // render scan line if (_hit) { union { DWORD dd; BYTE db[4]; } cc; int tx,ty,sy,sy0,sy1,cnt,dsy,dty; p->l=sqrt(p->l)*cos(a-plra);// anti fish eye m=divide(wall*focus,p->l); // projected wall half size c=0; if (p->typ==''H'') { c=ch; tx=double(double(txs)*(p->x-floor(p->x))); } if (p->typ==''V'') { c=cv; tx=double(double(txs)*(p->y-floor(p->y))); } tx+=txs*(p->hit&0xFFFF); // prepare interpolation sy1=ys2+m; // sy0=ys2-m; // constant wall height sy0=sy1-(((m+m)*(p->hit>>16))/_Doom3D_wall_size); // variable wall height dty=tys-1; dsy=sy1-sy0+1; // skip sy>=sys if (sy1>=sys) sy1=sys-1; // skip sy<0 for (cnt=dsy,sy=sy0,ty=0;sy<0;sy++) { cnt-=dty; while (cnt<=0) { cnt+=dsy; ty++; }} #ifdef _Doom3D_filter_txr DWORD r=0,g=0,b=0,n=0; #else cc.dd=ptxr[ty][tx]; cc.db[0]=DWORD((DWORD(cc.db[0])*c)>>8); cc.db[1]=DWORD((DWORD(cc.db[1])*c)>>8); cc.db[2]=DWORD((DWORD(cc.db[2])*c)>>8); #endif // continue sy>=0 y=height0; if (sy1>height0) sy1=height0; if (sy0<height0) height0=sy0; if (_back){ for (sy=sy0;sy<=y;sy++){ if ((sy>0)&&(sy<sys)) pscr[sy][x]=0x0000FF00; }} else for (;sy<=sy1;sy++) { #ifdef _Doom3D_filter_txr if (!n) { cc.dd=ptxr[ty][tx]; b+=DWORD(cc.db[0]); g+=DWORD(cc.db[1]); r+=DWORD(cc.db[2]); n+=256; } if ((sy>0)&&(sy<sys)) { cc.db[0]=DWORD(c*b/n); b=0; cc.db[1]=DWORD(c*g/n); g=0; cc.db[2]=DWORD(c*r/n); r=0; n=0; pscr[sy][x]=cc.dd; } cnt-=dty; while (cnt<=0) { cnt+=dsy; ty++; cc.dd=ptxr[ty][tx]; b+=DWORD(cc.db[0]); g+=DWORD(cc.db[1]); r+=DWORD(cc.db[2]); n+=256; } #else if ((sy>0)&&(sy<sys)) pscr[sy][x]=cc.dd; cnt-=dty; while (cnt<=0) { cnt+=dsy; ty++; cc.dd=ptxr[ty][tx]; cc.db[0]=DWORD((DWORD(cc.db[0])*c)>>8); cc.db[1]=DWORD((DWORD(cc.db[1])*c)>>8); cc.db[2]=DWORD((DWORD(cc.db[2])*c)>>8); } #endif } if (height0<0) break; } } } tend(); tray=tstr(1)+" ray"; // [2D map] tbeg(); m=_Doom3D_cell_size; mx=_Doom3D_cell_size; if ((sxs>=mxs*m)&&(sys>=mys*m)) { for (y=0;y<mys*m;y++) // pmap[][] for (x=0;x<mxs*m;x++) { if ((pmap[y/m][x/m]&0xFFFF)!=0xFFFF) c=0x00808080; else c=0x00000000; pscr[y][x]=c; } x=double(plrx*mx); // view rays y=double(plry*mx); scr->Canvas->Pen->Color=0x00005050; scr->Canvas->Pen->Mode=pmMerge; for (c=0;c<sxs;c++) { scr->Canvas->MoveTo(x,y); scr->Canvas->LineTo(DWORD(ray[c].x*mx),DWORD(ray[c].y*mx)); } scr->Canvas->Pen->Mode=pmCopy; c=focus*m; // player and view direction scr->Canvas->Pen->Color=0x000000FF; scr->Canvas->Brush->Color=0x000000FF; scr->Canvas->MoveTo(x,y); scr->Canvas->LineTo(DWORD(ray[xs2].x*mx),DWORD(ray[xs2].y*mx)); scr->Canvas->Ellipse(x-c,y-c,x+c,y+c); scr->Canvas->Pen->Color=0x00202020; for (y=0;y<=mys;y++) // map grid for (x=0;x<=mxs;x++) { scr->Canvas->MoveTo(0 ,y*m); scr->Canvas->LineTo(mxs*m,y*m); scr->Canvas->MoveTo(x*m, 0); scr->Canvas->LineTo(x*m,mys*m); } x=keys.mx*m; // selected cell y=keys.my*m; scr->Canvas->Pen->Color=0x0020FFFF; scr->Canvas->MoveTo(x ,y ); scr->Canvas->LineTo(x+m,y ); scr->Canvas->LineTo(x+m,y+m); scr->Canvas->LineTo(x ,y+m); scr->Canvas->LineTo(x ,y ); } tend(); tmap=tstr(1)+" map"; // [editor] if (txr_sel!=0xFFFFFFFF) { int x=sxs,y=5,s0,s1,s2,i,j; s0=txs>>1; s1=txs>>2; s2=(s0*cell_h)/_Doom3D_wall_size; for (i=-3;i<=3;i++) { j=txr_sel+i; while (j< 0) j+=tn; while (j>=tn) j-=tn; if (i) { scr->Canvas->CopyRect(TRect(x-s1,y+(s1>>1),x,s1+(s1>>1)),txr2->Canvas,TRect(s1*j,s0,s1*j+s1,s0+s1)); x-=s1+5; } else { scr->Canvas->CopyRect(TRect(x-s0,y+s0-s2 ,x,s0 ),txr2->Canvas,TRect(s0*j, 0,s0*j+s0,s2 )); x-=s0+5; } } } // total time measurement performance_tms=tperf0; tend(); ttotal=tstr(1)+" total"; x=m*mxs+m; c=16; y=-c; scr->Canvas->Font->Color=clYellow; scr->Canvas->Brush->Style=bsClear; scr->Canvas->TextOutA(x,y+=c,AnsiString().sprintf("player: %.2lf x %.2lf x %.2lf",plrx,plry,plrz)); scr->Canvas->TextOutA(x,y+=c,AnsiString().sprintf(" mouse: %.2lf x %.2lf",keys.mx,keys.my)); scr->Canvas->TextOutA(x,y+=c,tray); scr->Canvas->TextOutA(x,y+=c,tcls); scr->Canvas->TextOutA(x,y+=c,tmap); scr->Canvas->TextOutA(x,y+=c,ttotal); scr->Canvas->TextOutA(x,y+=c,AnsiString().sprintf(" key: %d",keys.Key)); // aspect ratio test /* c=ys2*7/10; scr->Canvas->Rectangle(xs2-c,ys2-c,xs2+c,ys2+c); */ // cross c=4,m=32; scr->Canvas->Pen->Color=clRed; scr->Canvas->MoveTo(xs2-c,ys2-m); scr->Canvas->LineTo(xs2-c,ys2-c); scr->Canvas->LineTo(xs2-m,ys2-c); scr->Canvas->MoveTo(xs2+c,ys2-m); scr->Canvas->LineTo(xs2+c,ys2-c); scr->Canvas->LineTo(xs2+m,ys2-c); scr->Canvas->MoveTo(xs2-c,ys2+m); scr->Canvas->LineTo(xs2-c,ys2+c); scr->Canvas->LineTo(xs2-m,ys2+c); scr->Canvas->MoveTo(xs2+c,ys2+m); scr->Canvas->LineTo(xs2+c,ys2+c); scr->Canvas->LineTo(xs2+m,ys2+c); scr->Canvas->Brush->Style=bsSolid; } //--------------------------------------------------------------------------- void Doom3D::update(double dt) { int move=0; double da=120.0*deg*dt; double dl= 5.0 *dt; double dx=0.0,dy=0.0,dz=0.0; if (keys.get(104)) { plra-=da; if (plra< 0.0) plra+=pi2; } // turn l/r if (keys.get(105)) { plra+=da; if (plra>=pi2) plra-=pi2; } if (keys.get(101)) { move=1; dx=+dl*cos(plra); dy=+dl*sin(plra); } // move f/b if (keys.get( 98)) { move=1; dx=-dl*cos(plra); dy=-dl*sin(plra); } if (keys.get(102)) { move=1; dx= dl*cos(plra-90*deg); dy=dl*sin(plra-90*deg); } // strafe l/r if (keys.get( 99)) { move=1; dx= dl*cos(plra+90*deg); dy=dl*sin(plra+90*deg); } if (keys.get(100)) { move=1; dz=+dl; } // strafe u/d if (keys.get( 97)) { move=1; dz=-dl; } if (move) // update/test plr position { double x,y,z,mx,my; x=plrx+dx; mx=mxs-focus; y=plry+dy; my=mys-focus; z=plrz+dz; if ((z>=0.0)&&(z<=_Doom3D_wall_size)) plrz=z;; if (x<focus) x=focus; if (x>mx) x=mx; if (y<focus) y=focus; if (y>my) y=my; dx*=divide(focus,dl); dy*=divide(focus,dl); if ((pmap[DWORD(y+dy)][DWORD(x+dx)]&0xFFFF)==0xFFFF) { plrx=x; plry=y; } else if ((pmap[DWORD(y+dy)][DWORD(x )]&0xFFFF)==0xFFFF) plry=y; else if ((pmap[DWORD(y )][DWORD(x+dx)]&0xFFFF)==0xFFFF) plrx=x; } keys.rfskey(); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- //---------------------------------------------------------------------------

Simplemente ignore la medición de tiempo de performance.h tbeg,tend,tstr , OpenGLrep4d_double.h teclado y mouse, teclado tbeg,tend,tstr OpenGLrep4d_double.h y cosas relacionadas con el puerto VCL ( Canvas,AnsiString , File access, JPEG ...).

Si necesita ayuda para comprender las cosas de gfx, vea

  • Mostrar una matriz de color en C

El uso de esta clase es simple, declara un objeto de esta clase y agrega el evento a tu ventana (mouse, teclado, repintado ...). El código de mi ventana VCL (formulario único con un temporizador) se ve así:

//$$---- Form CPP ---- //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "win_main.h" #include "Doom3D.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TMain *Main; Doom3D game; //--------------------------------------------------------------------------- void TMain::draw() { game.draw(); Canvas->Draw(0,0,game.scr); } //--------------------------------------------------------------------------- __fastcall TMain::TMain(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TMain::FormResize(TObject *Sender) { game.scr_resize(ClientWidth,ClientHeight); } //--------------------------------------------------------------------------- void __fastcall TMain::tim_redrawTimer(TObject *Sender) { game.update(tim_redraw->Interval*0.001); draw(); } //--------------------------------------------------------------------------- void __fastcall TMain::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift){ game.keys.set(Key,Shift); } void __fastcall TMain::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift) { game.keys.rst(Key,Shift); } void __fastcall TMain::FormActivate(TObject *Sender) { game.keys.reset_keys(); } //--------------------------------------------------------------------------- void __fastcall TMain::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { game.mouse(X,Y,Shift); } void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { game.mouse(X,Y,Shift); } void __fastcall TMain::FormMouseUp (TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { game.mouse(X,Y,Shift); } void __fastcall TMain::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled) { game.wheel(WheelDelta,Shift); Handled=true; } //---------------------------------------------------------------------------

Y aquí la explicación de las principales variables de iteración:

Y aquí el archivo de textura:

Así es como se ve después de algunos ajustes de código y mapeo de texturas correctas en perspectiva :

Tengo un proyecto de Java que hace el "laberinto de Windows" y uso el algoritmo de proyección de rayos. Aquí hay una captura de pantalla:

Como puede ver, todas las paredes tienen el mismo tamaño de altura. Me gustaría hacer lo mismo pero con diferentes tamaños de altura

private void castRay(int xOnScreen,double angle,double direction) { R rx = castRayInX(angle,direction); R ry = castRayInY(angle,direction); // In case of out-of-space rays if (rx.getDistance()==Double.MAX_VALUE && ry.getDistance()==Double.MAX_VALUE) { graphics.setColor(BACKGROUND); graphics.drawLine(xOnScreen,0,xOnScreen,this.image.getHeight()); return; } double distance = rx.getDistance(); double normal = rx.getNormal(); Color c = rx.getColor(); double coef = Math.cos((angle+direction+Math.PI)-normal); Plot collision = rx.getPlot(); if (ry.getDistance()<rx.getDistance()) { distance = ry.getDistance(); normal = ry.getNormal(); c = ry.getColor(); coef = Math.cos((angle+direction+Math.PI)-normal); collision = ry.getPlot(); } coef = Math.abs(coef); int factor = map.length*SQUARE_SIZE; double d = (double)(distance+factor)/factor; coef *= 1/(d*d); Color c2 = new Color((int)(c.getRed()*coef),(int)(c.getGreen()*coef),(int)(c.getBlue()*coef)); graphics.setColor(c2); // graphics.setColor(c); // no illumination distance *= Math.cos(angle); // lens correction int h = (int)(this.screenDistance/distance*WALL_HEIGHT); // perspective height int vh = this.image.getHeight(); graphics.drawLine(xOnScreen,(vh-h)/2,xOnScreen,(vh+h)/2); drawEye(direction,collision); } private R castRayInX(double angleRay,double direction) { double angle = angleRay+direction; double x1 = eye.getX()+SQUARE_SIZE*Math.cos(angle); double y1 = eye.getY()+SQUARE_SIZE*Math.sin(angle); double slope = (y1-eye.getY())/(x1-eye.getX()); if (Math.cos(angle)==0) { if (Math.sin(angle)>0) return new R(Double.MAX_VALUE,3*Math.PI/2,BACKGROUND,null); else return new R(Double.MAX_VALUE,Math.PI/2,BACKGROUND,null); } if (Math.cos(angle)>0) { int firstX = ((eye.getX()/SQUARE_SIZE)+1)*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int x = firstX; x<map[0].length*SQUARE_SIZE; x += SQUARE_SIZE) { int y = (int)(slope*(x-eye.getX())+eye.getY()); if (isOutside(x,y,Color.MAGENTA,this.showRayCastingX)) break; Color c = colorAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (c!=null) { int DX = x-eye.getX(); double DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),Math.PI,c,new Plot((int)x,(int)y, WALL_HEIGHT)); } } return r; } else { int firstX = ((eye.getX()/SQUARE_SIZE))*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int x = firstX; x>=0; x -= SQUARE_SIZE) { int y = (int)(slope*(x-eye.getX())+eye.getY()); if (isOutside(x,y,Color.MAGENTA,this.showRayCastingX)) break; Color c = colorAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (c!=null) { int DX = x-eye.getX(); double DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),0,c,new Plot((int)x,(int)y, WALL_HEIGHT)); } } return r; } } private R castRayInY(double angleRay,double direction) { // System.out.println("cast ray 2 Y "+angleRay+" "+direction); double angle = angleRay+direction; double x1 = eye.getX()+SQUARE_SIZE*Math.cos(angle); double y1 = eye.getY()+SQUARE_SIZE*Math.sin(angle); // System.out.println(eye+" "+x1+" "+y1); double slope = (y1-eye.getY())/(x1-eye.getX()); if (Math.sin(angle)==0) { if (Math.cos(angle)>0) return new R(Double.MAX_VALUE,Math.PI,BACKGROUND,null); else return new R(Double.MAX_VALUE,0,BACKGROUND,null); } if (Math.sin(angle)>0) { int firstY = ((eye.getY()/SQUARE_SIZE)+1)*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int y = firstY; y<map.length*SQUARE_SIZE; y += SQUARE_SIZE) { int x = (int)((y-eye.getY())/slope)+eye.getX(); if (isOutside(x,y,Color.CYAN,this.showRayCastingY)) break; Color c = colorAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (c!=null) { double DX = x-eye.getX(); int DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),3*Math.PI/2,c,new Plot((int)x,(int)y, WALL_HEIGHT)); } } return r; } else { int firstY = ((eye.getY()/SQUARE_SIZE))*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int y = firstY; y>=0; y -= SQUARE_SIZE) { int x = (int)((y-eye.getY())/slope)+eye.getX(); if (isOutside(x,y,Color.CYAN,this.showRayCastingY)) break; Color c = colorAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (c!=null) { double DX = x-eye.getX(); int DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),Math.PI/2,c,new Plot((int)x,(int)y, WALL_HEIGHT)); } } return r; } }

Mi clase R tiene un Plot (x, y, z) por ahora uso WALL_HEIGHT un color, una distancia y un normal para la luz. Por ahora esto funciona, pero me gustaría agregar una nueva función como castRayInZ, pero no tengo toda la teoría matemática detrás. Mi laberinto está hecho de un mapa como ese:

private String [][]map = { // each: SQUARE_SIZE x SQUARE_SIZE { "Y300", "Z500", "X230", "Y112", "Z321", "X120", "X354" }, { "X89", " ", " ", " ", "Y120", " ", "X232" }, { "Z124", " ", "X276", " ", "X123", " ", "X" }, { "Y290", " ", " ", " ", " ", " ", "X100" }, { "X32", "Z430", " ", "Y500", "X120", " ", "X123" }, { "X222", " ", " ", " ", " ", " ", "X210" }, { "X12", "Y98", "Y763", "X146", "Y111", "Y333", "X321" }

donde XYZ es para el color (X para Rojo, Y para Verde y Z para Azul solo probando mi función de luz) y agrego una altura para cada cuadrado de mi mapa. Configuré toda la longitud en SQUARE_LENGTH por ahora, tal vez más tarde SQUARE_LENGTH el tamaño de cada cuadrado a un píxel y ampliaré mi mapa al generarlo. Pero realmente quiero saber cómo puedo cambiar la altura de cada cuadrado. Estoy trabajando en eso desde hace 4 días y no tengo pistas ...

EDITAR

Tengo algunas noticias, cambié el tamaño de mis paredes pero tengo algunas cosas extrañas, aquí hay una captura de pantalla:

Como puedes ver, hay algunas cosas extrañas que aparecen aquí. Aquí está mi código:

private void castRay(int xOnScreen,double angle,double direction) { R rx = castRayInX(angle,direction); R ry = castRayInY(angle,direction); // In case of out-of-space rays if (rx.getDistance()==Double.MAX_VALUE && ry.getDistance()==Double.MAX_VALUE) { graphics.setColor(BACKGROUND); graphics.drawLine(xOnScreen,0,xOnScreen,this.image.getHeight()); return; } double distance = rx.getDistance(); double normal = rx.getNormal(); Color c = rx.getColor(); double coef = Math.cos((angle+direction+Math.PI)-normal); Plot collision = rx.getPlot(); if (ry.getDistance()<rx.getDistance()) { distance = ry.getDistance(); normal = ry.getNormal(); c = ry.getColor(); coef = Math.cos((angle+direction+Math.PI)-normal); collision = ry.getPlot(); } coef = Math.abs(coef); int factor = map.length*SQUARE_SIZE; double d = (double)(distance+factor)/factor; coef *= 1/(d*d); Color c2 = new Color((int)(c.getRed()*coef),(int)(c.getGreen()*coef),(int)(c.getBlue()*coef)); graphics.setColor(c); distance *= Math.cos(angle); // lens correction int h; int hw = (int)(this.screenDistance/distance*WALL_HEIGHT); //WALL_HEIGHT value is 300px at default if(rx.getPlot() != null) h = (int)(this.screenDistance/distance*rx.getPlot().getZ()); // perspective height else h = (int)(this.screenDistance/distance*WALL_HEIGHT); int vh = this.image.getHeight(); int y0 = (hw+vh)/2; int y1 = (vh-h)/2; graphics.drawLine(xOnScreen,y0,xOnScreen,y1); drawEye(direction,collision);

Mi problema debería ser de la función castRayInX :

private R castRayInX(double angleRay,double direction) { double angle = angleRay+direction; double x1 = eye.getX()+SQUARE_SIZE*Math.cos(angle); double y1 = eye.getY()+SQUARE_SIZE*Math.sin(angle); double slope = (y1-eye.getY())/(x1-eye.getX()); if (Math.cos(angle)==0) { if (Math.sin(angle)>0) return new R(Double.MAX_VALUE,3*Math.PI/2,BACKGROUND,null); else return new R(Double.MAX_VALUE,Math.PI/2,BACKGROUND,null); } if (Math.cos(angle)>0) { int firstX = ((eye.getX()/SQUARE_SIZE)+1)*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int x = firstX; x<map[0].length*SQUARE_SIZE; x += SQUARE_SIZE) { int y = (int)(slope*(x-eye.getX())+eye.getY()); if (isOutside(x,y,Color.MAGENTA,this.showRayCastingX)) break; Color c = colorAt(x,y); int z = heightAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (z == 0) z = heightAt(x,y-1); if (z == 0) z = heightAt(x-1,y); if (z == 0) z = heightAt(x-1,y-1); if (c!=null) { int DX = x-eye.getX(); double DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),Math.PI,c,new Plot((int)x,(int)y,(int)z)); } } return r; } else { int firstX = ((eye.getX()/SQUARE_SIZE))*SQUARE_SIZE; R r = new R(Double.MAX_VALUE,angle+Math.PI,BACKGROUND,null); for (int x = firstX; x>=0; x -= SQUARE_SIZE) { int y = (int)(slope*(x-eye.getX())+eye.getY()); if (isOutside(x,y,Color.MAGENTA,this.showRayCastingX)) break; Color c = colorAt(x,y); int z = heightAt(x,y); if (c==null) c = colorAt(x,y-1); if (c==null) c = colorAt(x-1,y); if (c==null) c = colorAt(x-1,y-1); if (z == 0) z = heightAt(x,y-1); if (z == 0) z = heightAt(x-1,y); if (z == 0) z = heightAt(x-1,y-1); if (c!=null) { int DX = x-eye.getX(); double DY = y-eye.getY(); return new R(Math.sqrt(DX*DX+DY*DY),0,c,new Plot((int)x,(int)y,(int)z)); } } return r; } }

¿Debo hacer una función castRayInZ ? ¿O debería obtener mi valor z en otro lugar?