Лучевое литье с разной высотой

У меня есть проект Java, который создает "лабиринт окон" и использует алгоритм приведения лучей. Вот скриншот:

Лабиринт с лучевой отливкой

Как вы можете видеть, все стены имеют одинаковый размер по высоте. Я хотел бы сделать то же самое, но с разным размером высоты

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 дня, и у меня нет никаких подсказок...

РЕДАКТИРОВАТЬ

У меня есть некоторые новости, я изменил размер своих стен, но у меня есть некоторые странные вещи, вот скриншот:

Странное изображение вещи

Как вы видите, здесь появляются странные вещи. Вот мой код:

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 ценность где-то еще?

2 ответа

Решение

Таким образом, вы, очевидно, знаете основы техники лучевого вещания Wolfenstein. Чтобы добавить переменную высоту, вам нужно сделать это:

  1. добавить информацию о высоте для каждой ячейки

    так что просто добавьте другое значение к вашей ячейке в таблице карт map[][], Вы кодируете вещи как строки, что странно...

  2. обновить визуализацию строки сканирования

    Где-то в коде (после обнаружения попадания) вы визуализируете вертикальную линию для каждого луча. Там вы должны вычислить размер строки сканирования примерно так (при условии, что 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 линии горизонта в вашем представлении. Это положит вашу стену на землю.

  3. обновить лучевое литье

    теперь, когда вы ударяете первую стену, вы останавливаетесь. С переменной высотой стены вы можете остановиться только тогда, когда вы ударите стену в натуральную величину (wall_size=1) или не хватает карты. У вас есть 2 варианта для реализации этого.

    1. запоминать все хиты и рендерить в обратном порядке
    2. визуализировать сразу, но только с последней визуализированной высоты, а не с земли.

    Первый вариант прост в реализации, но требует больше памяти и медленнее. Второй - быстрый и не требует списков или стеков. Но для рендеринга строки сканирования требуется немного больше математики (O(1) если закодировано правильно)

    Я немного поиграл со своим демо по ссылке вверху. Теперь результат должен выглядеть так:

    переменная высота

    Как вы можете видеть, выделенные ячейки на карте пропускаются лучами выше их высоты (поэтому вы можете видеть более крупные ячейки за ними).

    Остерегайтесь, когда вы добавляете направление высоты движения (прыжки, лестница и т. Д.), Тогда конечное условие должно быть другим (визуализированная линия сканирования достигает верхней части вида). Также проекционная часть координаты y будет отличаться и должна включать фактическую высоту игрока.

  4. добавить верх

    Вам нужно добавить рендеринг верхней стороны. Это похоже на рендеринг потолка и пола. У оригинального IIRC Wolfenstein такой возможности не было, но у последних псевдо 3D- игр, таких как DOOM, была.

    Есть более возможные подходы, такие как Perspective Vision на 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,tstr, OpenGLrep4d_double.h обработчик клавиатуры и мыши keytab и порт VCL связанные вещи (Canvas,AnsiString Доступ к файлам, JPEG...).

Если вам нужна помощь с пониманием gfx, смотрите

Использование этого класса просто объявите объект этого класса и добавьте событие в ваше окно (мышь, клавиатура, перекрасить...). Мой код 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; }
//---------------------------------------------------------------------------

А вот основные пояснения переменных итерации:

переменные

И вот файл текстуры:

текстуры

Вот как это выглядит после некоторой дополнительной настройки кода и корректного отображения текстуры в перспективе:

Прыгать

Я искал способ отправить прямое сообщение в Spektre, но не нашел... доступен ли полный исходный код? Я работаю над чем-то вроде этого и хотел бы посмотреть, как обрабатываются «шаги» ... Я скачал автономную демонстрацию win32 (хотя я не видел редактора карт с ней), и это похоже на лучшее решение чем то, как я работал

Другие вопросы по тегам