使用不同高度尺寸进行光线投射

10

我有一个Java项目,用射线投射算法生成“迷宫”,以下是一张截图:

Maze made with ray casting

正如你所看到的,所有的墙壁高度都相同。我希望相同的射线投射算法可以产生不同高度的效果。

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;           
        }
    }

我的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" }

这里的X Y Z代表颜色(X代表红色,Y代表绿色,Z代表蓝色,只是测试我的光函数),我为地图上的每个方块添加了高度。目前我将所有长度都设置为SQUARE_LENGTH,也许以后我会将每个方块的大小减小到一个像素,并通过生成来放大我的地图。但我真的想知道如何改变每个方块的高度。我已经在这上面工作了4天了,但还没有任何线索...

编辑

我有一些消息,我改变了墙壁的大小,但出现了一些奇怪的东西,下面是截图:

Strange stuff image

正如您所看到的,这里出现了一些奇怪的东西。以下是我的代码:

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);

我的问题应该来自于 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;           
    }
}

我应该创建一个castRayInZ函数吗?还是我应该从其他地方获取我的z值?


1
你不是已经自己回答了这个问题吗?你需要每个补丁不同的高度值,而不是使用“WALL_HEIGHT”。你遇到了什么问题? - Nico Schertler
使用常量值很容易,但我不知道如何获取所有不同的高度。我遇到了一些奇怪的问题。 - Jack
2个回答

19

显然,您已经了解Wolfenstein射线投射技术的基础知识。 要添加可变高度,您需要执行以下操作:

  1. 按单元格基础添加高度信息

因此,在地图表map [][ ]中向每个单元格信息添加另一个值即可。您将编写字符串代码,这很奇怪...

  1. 更新扫描线渲染

在代码的某个位置(在命中被检测后),您会为每条射线呈现垂直线。 在那里,您应该计算扫描线大小,例如(假设y = 0位于屏幕顶部):

    y0 = center_of_view_y + projected_half_size
    y1 = center_of_view_y - projected_half_size

应该改为:

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

其中projected_half_size是为常量单元格高度计算的线大小,wall_size=<0,1>表示比例,center_of_view_y是视图中地平线的y坐标。这将把你的墙放在地上。

  1. 更新射线投射

现在当您撞到第一堵墙时就会停止。使用可变墙高,您只能在撞到全高的墙(wall_size=1)或地图用尽时停止。您有两个选项来实现此操作。

  1. 记住所有撞击,并以相反顺序呈现
  2. 立即渲染,但仅从最后呈现的高度开始而不是从地面开始。

第一个选项易于实现,但需要更多内存并且速度较慢。第二个选项快速且不需要任何列表或堆栈。但它涉及扫描线渲染的一点数学(如果编码正确,则为O(1)

我从上面的链接中玩了一下我的演示。现在结果应该像这样:

variable height

如您所见,映射上突出显示的单元格对于其高度之上的射线是可通过的(因此您可以看到它们后面更大的单元格)。

注意,一旦您添加移动高度方向(跳跃、楼梯等),然后结束条件必须不同(呈现的扫描线击中视图顶部)。还要包括实际玩家海拔的投影部分y坐标也将不同。

  1. 添加顶部

您需要添加顶部的渲染。它类似于渲染天花板和地板。IIRC原始Wolfenstein没有这种能力,但后来的假3D游戏如DOOM有。

还有更多可能的方法,例如Canvas上的透视视觉,但我认为最容易实现的方法(因为我们已经有足够的信息)是在纹理中计算垂直扫描线坐标的顶部部分,然后只需复制像素即可。由于我们已经知道射线撞击单元格的位置和播放器/相机的角度也已知。有关更多信息,请参见:PCGPE 1.0 Doom技术

因此,首先添加后面的面板撞击。应该看起来像这样:

背面

这是通过首先检查上一次击中单元格的命中次数来完成的。现在,如果您记得前一个命中(在同一扫描线上)的上一次渲染的y坐标,那么如果击中了背面,而不是渲染该面,将从上一个y到实际y渲染顶部颜色(或从地板/天花板纹理中复制像素)。这里使用绿色:

顶部

如果有帮助的话,这是我的C++(基于GDI/VCL)代码:

//---------------------------------------------------------------------------
//--- 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
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

忽略 performance.h 时间测量的 tbeg,tend,tstrOpenGLrep4d_double.h 键盘和鼠标处理程序的 keytab 以及与 VCL 相关的内容(Canvas,AnsiString,文件访问,JPEG...)。

如果您需要帮助理解图形相关内容,请参见:

使用此类很简单,只需声明该类的对象,并将事件添加到窗口上(如鼠标、键盘、重绘等事件)。我的 VCL 窗口(一个带有一个定时器的单个表单)代码如下:

//$$---- 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; }
//---------------------------------------------------------------------------

下面是主要的迭代变量解释:

variables

下面是纹理文件:

textures

在进一步调整代码和进行透视纠正纹理映射后,它看起来像这样:

jump

与此相关的QA如下:

更多最新演示版本(带地图编辑器):


当我添加了heightAt(double x, double y)函数以返回x,y坐标处墙壁的高度时,它是否有效?因为我这样做了,并在我的castRayInX函数中调用它,但有时会出现奇怪的东西,因为它有时会返回空的Plot对象,但是确实存在一堵墙。 - Jack
我已经截图了,我的WALL_HEIGHT值为300px。我将编辑我的帖子并提供更多信息。 - Jack
你的公式对我不起作用,因为现在 y = 0 不是我的屏幕顶部,所以我的计算是 int y0 = (hw+vh)/2int y1 = (vh-h)/2,其中 int vh = this.image.getHeight()h = (int)(this.screenDistance/distance*rx.getPlot().getZ()) 或者 h = (int)(this.screenDistance/distance*ry.getPlot().getZ()),而且 int hw = (int)(this.screenDistance/distance*WALL_HEIGHT) - Jack
@Jack在进行了一些代码调整并添加了透视纠正纹理映射和牛顿·德朗伯运动后,增加了带有纹理的动画预览。不幸的是,新代码约为25Kb,整个答案无法适应30KB的限制.... - Spektre
@Jack在答案末尾添加了2个新链接,我认为你会喜欢它们。 - Spektre
显示剩余5条评论

0

我想找一种方法直接向Spektre发送私信,但是没有找到...这个的完整源代码是否可用?我正在开发类似的东西,很想看看如何处理“步骤”...我下载了win32独立演示版(虽然我没有看到地图编辑器),它看起来比我原来的方法更好


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接