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:
-
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 ... -
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 ycenter_of_view_y
es la coordenada y de la línea del horizonte en su vista. Esto pondrá tu pared en el suelo. -
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.- recuerda todos los golpes y renderiza en orden inverso
- 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.
-
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 últimay
a lay
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?