Jac*_*ack 6 java math graphics 2d-3d-conversion raycasting
我有一个java项目谁制作了"windows'迷宫"并使用光线投射算法.这是一个截图:
就像你可以看到所有的墙都有相同的高度.我想做同样的事,但身高不同
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我的R班级Plot (x, y, z)现在WALL_HEIGHT有一个颜色,距离和光线的法线.现在这个有用,但我想添加一个像castRayInZ这样的新函数,但我没有掌握所有的数学理论.我的迷宫是用这样的地图制作的:
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" }
Run Code Online (Sandbox Code Playgroud)
其中XYZ用于颜色(X表示红色,Y表示绿色,Z表示蓝色,仅测试我的灯光功能),我为地图的每个方块添加高度.我将所有长度设置SQUARE_LENGTH为现在可能稍后我将每个方块的大小减小为像素并通过生成它来放大我的地图.但我真的想知道如何改变每个方格的高度.我现在工作4天了,我没有任何线索......
编辑
我有一些消息,我改变了墙壁的大小,但我有一些奇怪的东西,这是一个截图:
就像你可以看到我在这里出现一些奇怪的东西.这是我的代码:
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);
Run Code Online (Sandbox Code Playgroud)
我的问题应该来自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;
}
}
Run Code Online (Sandbox Code Playgroud)
我应该做一个castRayInZ功能吗?或者我应该在z其他地方获得我的价值?
所以你显然知道了Wolfenstein光线投射技术的基础知识.要添加可变高度,您需要执行以下操作:
每个单元格添加高度信息
所以只需在地图表中为您的单元格信息添加另一个值即可map[][].你把东西编码为奇怪的字符串......
更新扫描线渲染
代码中的某处(检测到命中后),每条光线渲染一条垂直线.在那里你应该计算扫描线的大小(假设y = 0是屏幕的顶部):
y0 = center_of_view_y + projected_half_size
y1 = center_of_view_y - projected_half_size
Run Code Online (Sandbox Code Playgroud)
并应改为:
y0 = center_of_view_y + projected_size
y1 = y0 - 2*projected_half_size*wall_size
Run Code Online (Sandbox Code Playgroud)
projected_half_size现在,为恒定单元格高度计算的线条大小在哪里,wall_size=<0,1>是scale,并且center_of_view_y是视图中水平线的y坐标.这将把你的墙放在地上.
更新光线投射
now when you hit the first wall you stop. With variable wall height you can stop only when you hit wall of full size (wall_size=1) or run out of map. You got 2 option to implement this.
The first option is easy to implement but requires more memory and it is slower. The second one is fast and does not need any lists or stacks. But it involves a little bit more math for the scan line render (O(1) if coded right)
I played a bit with my demo from the link at the top. Now the result should look like this:
As you can see the highlighted cells on the map are pass through for rays above their height (so you can see bigger cells behind them).
Beware once you add movement altitude direction (jumps, stairs etc) then the end condition must be different (the rendered scan line hit the top of the view). Also projection part of y coordinate will be different and need to include the actual player altitude.
add topside
You need to add the rendering of top side. It is similar to rendering ceiling and floor. IIRC original Wolfenstein did not have this capability but the latter pseudo 3D games like DOOM did.
There are more possible approaches like Perspective Vision on Canvas but I think the easiest to implement (as we already got enough info) is to compute the top side part of vertical scanline coordinates in texture and just copy pixels. As we already know where the ray hit the cell and angle of player/camera is also known. For more info see: PCGPE 1.0 Doom techniques
So as a first step add hits for back faces too. That should look like this:
That is done by checking hits of the last hit cell first. Now if you remember the last rendered y coordinate from previous hit (of the same scanline) then if back face is hit instead of rendering the face render top side color from last y to actual y (or copy pixels from floor/ceiling texture instead). Here the green color used for this:
If it helps here is mine C++ (GDI/VCL based) code for this:
//---------------------------------------------------------------------------
//--- 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
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
Just ignore the performance.h time measurement tbeg,tend,tstr, OpenGLrep4d_double.h keyboard and mouse handler keytab and port VCL related stuff (Canvas,AnsiString,File access, JPEG...).
If you need help with understand the gfx stuff see
The usage of this class is simple declare an object of this class, and add the event to your window (mouse,keyboard,repaint ...). My VCL Window (single form with one timer on it) code looks like this:
//$$---- 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; }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
And here the main iteration variables explanation:
And here the texture file:
Here is how it looks like after some more code tweaking and perspective correct texture mapping: