具有不同高度尺寸的光线投射

Ray Casting with different height size

我有一个 java 项目制作 "windows' maze" 并使用光线投射算法。这是屏幕截图:

你可以看到所有的墙都有相同的高度尺寸。我想做同样的事情,但高度不同

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

我的 Rclass 有一个 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 天了,但我没有任何线索...

编辑

我有一些消息,我改变了我的墙的大小,但我有一些奇怪的东西,这是一个截图:

就像你看到的一样,这里出现了一些奇怪的东西。这是我的代码:

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. 在每个单元格基础上添加高度信息

所以只需在您的地图 table 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)或运行时,您才能停下来。你有 2 个选项来实现这个。

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

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

我在顶部的 link 中播放了一些演示。现在结果应该是这样的:

如您所见,地图上突出显示的单元格在其高度以上的光线通过(因此您可以看到它们后面更大的单元格)。

当心 一旦你添加了运动高度方向(跳跃、楼梯等),那么结束条件必须不同(渲染的扫描线到达视图的顶部)。 y 坐标的投影部分也会不同,需要包括实际玩家高度。

  1. 添加顶部

您需要添加顶部渲染。它类似于渲染天花板和地板。 IIRC原版德军总部没有这个能力但是后来的伪3D游戏像DOOM

还有更多可能的方法,例如 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

因此,作为第一步,也为背面添加点击。那应该是这样的:

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

如果这有帮助,这是我的 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...).

如果您在理解 gfx 方面需要帮助,请参阅

  • Display an array of color in C

这个class的用法很简单,声明一个这个class的对象,然后将事件添加到你的window(鼠标、键盘、重绘...)。我的 VCL Window(带有一个计时器的单一表单)代码如下所示:

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

这里是主要的迭代变量解释:

这里是纹理文件:

这是经过更多代码调整和 perspective correct texture mapping:

后的样子

此处与此相关的质量检查:

以及更新的演示版本(带有地图编辑器):

我正在寻找一种向 Spektre 发送直接消息的方法,但没有找到...是否提供了完整的源代码?我正在做类似这样的事情,很想看看“步骤”是如何处理的……我下载了 win32 独立演示(虽然我没有看到地图编辑器),它看起来是一个更好的解决方案比我工作的方式