Raycaster 中的高效地板/天花板渲染

2023-12-15

我在 Raycaster 引擎上工作了一段时间,我在较慢的机器上运行。 我遇到的最具挑战性的问题是高效的地板和天花板铸造。

我的问题是: 我还可以使用什么其他更快的方法? (我不确定末日地板和天花板是如何渲染的)

到目前为止我尝试了两种典型的解决方案:

  • 垂直和水平 - 铸造如众所周知的爱情教程中所述:https://lodev.org/cgtutor/raycasting2.html

水平方法当然要快得多,但我还使用定点变量对其进行了优化。

不幸的是,即使这种方法也是性能杀手——即使在更快的 cpu 上,fps 也会大幅下降,而较慢的 cpu 则是一个瓶颈。

我的其他想法:

  • 我找到了一种算法,可以将可见的地板/天花板地图图块转换为四边形,并将其分割为两个三角形 - 并像在常规扫描线光栅化器中一样对它们进行光栅化。它的速度要快得多 - 我还可以按纹理 ID 对图块进行排序,以更加缓存友好。不幸的是,在这种情况下我进入了“透视校正纹理映射” - 为了解决这个问题,我必须添加一些分区,这会降低性能..但也可以进行一些优化..

enter image description here

  • 每 2 条射线(在列、行或两者中)使用水平投射 - 我将用平均纹理坐标填充空白空间

  • 我还可以尝试将我的算法从 1 点开始与水平投射相结合 - 我可以按 ID 对纹理进行排序,例如,我认为不会有纹理扭曲

  • mode 7 ?

到目前为止我的进展:https://www.youtube.com/watch?v=u3zA2Wh0NB4

编辑(1):

地板和天花板渲染代码(基于爱情教程的水平方法) 但用定点进行了优化。上限计算镜像到下限。

https://lodev.org/cgtutor/raycasting2.html

这种方法比垂直方法更快,但大量计算是内部循环,并且随机访问纹理像素会影响性能。

inline void RC_Raycast_Floor_Ceiling()
{
    // get local copy of RC_level_textures
    sBM_Bitmap* level_textures = RC_level_textures;

    // ray direction for leftmost ray (x = 0) and rightmost ray (x = width)
    float32 r_dx0 = RC_pp_dx - RC_pp_nsize_x_d2;
    float32 r_dy0 = RC_pp_dy - RC_pp_nsize_y_d2;

    //float32 r_dx1 = RC_pp_dx + RC_pp_nsize_x_d2;
    //float32 r_dy1 = RC_pp_dy + RC_pp_nsize_y_d2;

    // precalculated helpers for performance
    float32 r_dx1_m_dx0_div_width = (RC_pp_nsize_x_d2 + RC_pp_nsize_x_d2) * RC_render_width__1div__f;
    float32 r_dy1_m_dy0_div_width = (RC_pp_nsize_y_d2 + RC_pp_nsize_y_d2) * RC_render_width__1div__f;

    int16 ray_y = -1;
    u_int16 ray_y_counter = RC_render_height__i;

    u_int8* walls_buffer__ptr = RC_walls_buffer;

    // casting floor and ceiling - horizontal line by line - from left to right
    while(ray_y_counter)
    {
        ray_y++;
        ray_y_counter--;

        // dont go further if the current floor/ceil scanline line won't be visible
        if (ray_y >= RC_walls_start)
        {
            break;
            if (ray_y < RC_walls_end)
            {
                ray_y = RC_walls_end;
                ray_y_counter = RC_walls_start - 1;
                walls_buffer__ptr += ((RC_walls_end - RC_walls_start) * RC_render_width__i);
                continue;
            }
        }
      
        // whether this section is floor or ceiling
        u_int8 is_floor = ray_y > RC_render_height__div2__i;

        // current ray y position compared to the center of the screen (the horizon)
        float32 ry_pos = (float32)(is_floor ? (ray_y - RC_render_height__div2__i) : (RC_render_height__div2__i - ray_y));

        // vertical position of projection plane, 0.5 is between floor and ceiling
        float32 pp_z = (float32)(is_floor ? (RC_render_height__div2__i) : (RC_render_height__div2__i));

        float32 straight_distance_to_point = pp_z / ry_pos;

        // calculate the real world step vector we have to add for each x (parallel to camera plane)
        // adding step by step avoids multiplications with a weight in the inner loop
        float32 floor_step_x = straight_distance_to_point * r_dx1_m_dx0_div_width;
        float32 floor_step_y = straight_distance_to_point * r_dy1_m_dy0_div_width;

        float32 floor_x = RC_player_x + straight_distance_to_point * r_dx0;
        float32 floor_y = RC_player_y + straight_distance_to_point * r_dy0;

        // convert that values to fixed point
        int32 floor_x__fp = (int32)(floor_x * 65536.0f);
        int32 floor_y__fp = (int32)(floor_y * 65536.0f);

        int32 floor_step_x__fp = (int32)(floor_step_x * 65536.0f);
        int32 floor_step_y__fp = (int32)(floor_step_y * 65536.0f);

        u_int32 ry_m_render_width = ray_y * RC_render_width__i;
        u_int32 ry_m_render_width_i_mirror = (RC_render_height__i- ray_y-1) * RC_render_width__i;

        int16 ray_x = -1;
        u_int16 ray_x_counter = RC_render_width__i;

        sLV_Cell* level_map = RC_level->map;

        // drawing floor and ceiling from left to right
        while(ray_x_counter)
        {
            ray_x++;
            ray_x_counter--;

            floor_x__fp += floor_step_x__fp;
            floor_y__fp += floor_step_y__fp;

            if (*walls_buffer__ptr != 0)
            {
                walls_buffer__ptr++;
                continue;
            }

            walls_buffer__ptr++;

            u_int32 output_pixel_index = ray_x + ry_m_render_width;
            u_int32 output_pixel_index_mirror = ray_x + ry_m_render_width_i_mirror;

            // the cell coord is simply got from the integer parts of floorX and floorY

            u_int32 curr_cell_x = (floor_x__fp & FP_INTEGER_MASK_16) >> 16;
            u_int32 curr_cell_y = (floor_y__fp & FP_INTEGER_MASK_16) >> 16;

            // prevent overflow
           // curr_cell_x &= LV_MAP_SIZE_m1;
           // curr_cell_y &= LV_MAP_SIZE_m1;

            u_int32 texture_pixel_x = (floor_x__fp & FP_FRACTION_MASK_16) >> 9;
            u_int32 texture_pixel_y = (floor_y__fp & FP_FRACTION_MASK_16) >> 9;

            u_int32 cell_index = curr_cell_x + (curr_cell_y << LV_MAP_SIZE_BITSHIFT);

            // get the texture index depending on the cell
            u_int32 texture_index;

        /*    if (is_floor)
            {
               
                texture_index = level_map[cell_index].floor_id;
            }
            else
            {
                texture_index = level_map[cell_index].ceil_id;
            }*/

            texture_index = level_map[cell_index].ceil_id;

            // get pixel coords in texture
            u_int32 tex_index = texture_pixel_x + (texture_pixel_y << 7);
            u_int32 texture_current_pixel = level_textures[0].pixels[tex_index];

            RC_output_buffer_32[output_pixel_index] = texture_current_pixel;

            texture_index = level_map[cell_index].floor_id;
            texture_current_pixel = level_textures[texture_index].pixels[tex_index];

            RC_output_buffer_32[output_pixel_index_mirror] = texture_current_pixel;
        }
    }
}

我将介绍我的光线投射引擎,这里有一些内容可以帮助您理解它。让我们从类声明开始:

const int   _Doom3D_mipmaps=8;          // number of mipmaps for texture
const DWORD _Doom3D_edit_cell_size=4;   // [pixel] 2D map cell size
const DWORD _Doom3D_cell_size=100;      // [units] cell cube size
//                                              heigh     ceil   floor  wall
const DWORD _Doom3D_cell_floor=(                0<<24)|( 0<<16)|(18<<8)|(39);
const DWORD _Doom3D_cell_wall =(_Doom3D_cell_size<<24)|( 0<<16)|( 0<<8)|(39);
class Texture2D // 2D textures
    {
public:
    Graphics::TBitmap *bmp;
    int xs,ys; DWORD **pyx;

    Texture2D() { bmp=new Graphics::TBitmap; xs=0; ys=0; pyx=NULL; resize(1,1); }
    Texture2D(Texture2D& a) { *this=a; }
    ~Texture2D() { free(); if (bmp) delete bmp; bmp=NULL; }
    Texture2D* operator = (const Texture2D *a) { *this=*a; return this; }
    Texture2D* operator = (const Texture2D &a) { bmp->Assign(a.bmp); resize(); return this; }

    void free() { if (pyx) delete[] pyx; xs=0; ys=0; pyx=NULL; }
    void resize(int _xs=-1,int _ys=-1) { free(); if ((_xs<0)||(_ys<0)) { _xs=bmp->Width; _ys=bmp->Height; } else bmp->SetSize(_xs,_ys); bmp->HandleType=bmDIB; bmp->PixelFormat=pf32bit; pyx=new DWORD* [_ys]; for (int y=0;y<_ys;y++) pyx[y]=(DWORD*)bmp->ScanLine[y]; xs=bmp->Width; ys=bmp->Height; }
    void load(AnsiString name) { AnsiString ext=ExtractFileExt(name).LowerCase(); for(;;) { if (ext==".bmp") { bmp->LoadFromFile(name); break; } if (ext==".jpg") { TJPEGImage *jpg=new TJPEGImage; if (jpg==NULL) return; jpg->LoadFromFile(name); bmp->Assign(jpg); delete jpg; break; } return; } resize(); }
    };
class Doom3D
    {
public:
    Texture2D map,map2,scr,sky;         // map, map preview, screen, sky texture
    Texture2D txr[_Doom3D_mipmaps],*ptxr;// texture atlas with mipmas, actual mipmap for rendering scan lines
    DWORD sxs2,sys2;                    // screen half resolution
    DWORD tn,tm;                        // number of textures in texture atlas, number of mipmaps used
    BYTE liH[256],liV[256],liF[256];    // shading LUT for H,V,floor/ceiling (light scaled shades of gray)

    int scale_x;
    bool _no_mipmap;

    struct _player
        {
        double  a;                      // player view direction [rad]
        double x0,y0,z0;                // old player position [cell]
        double  x, y, z;                // player position [cell]
        double vx,vy,vz;                // player speed [cell/s]
        double ax,ay,az;                // player acceleration [cell/s]
        void update(double dt)
            {
            x0=x; vx+=ax*dt; x+=vx*dt;
            y0=y; vy+=ay*dt; y+=vy*dt;
            z0=z; vz+=az*dt; z+=vz*dt;
            }
        _player() { a=x=y=z=vx=vy=vz=ax=ay=az=0.0; }
        _player(_player& a) { *this=a; }
        ~_player() {};
        _player* operator = (const _player *a) { *this=*a; return this; }
        //_player* operator = (const _player &a) { ..copy... return this; }
        } plr;
    double view_ang;                    // [rad] view angle
    double focus;                       // [cells] view focal length
    double wall;                        // [px] projected wall size ratio size = height*wall/distance
    struct _ray
        {
        int    x,y;                     // cell map position
        double ang;                     // ray angle
        double x0,y0,l0;                // cell first hit
        double x1,y1,l1;                // cell second hit
        int    sx;                      // screen x coordinate
        int    sy0,sy1;                 // screen y coordinates of V scanline to render
        char tp0,tp1;                   // H/V hit type
        DWORD map;                      // map cell of hit or 0xFFFFFFFF
        _ray() {};
        _ray(_ray& a)   { *this=a; }
        ~_ray() {};
        _ray* operator = (const _ray *a) { *this=*a; return this; }
        //_ray* operator = (const _ray &a) { ..copy... return this; }
        };

    keytab keys;                        // keyboard handler
    DWORD txr_wall;                     // map editor
    DWORD txr_floor;
    DWORD txr_ceil;
    DWORD cell_h;
    DWORD *txr_mode; AnsiString txr_mode_txt;

    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_clear();                   // clear whole map
    void map_save(AnsiString name);     // save map to file
    void map_load(AnsiString name);     // load map from file
    void scr_resize(DWORD xs,DWORD ys); // resize view
    void txr_mipmap();                  // generate mipmaps for txr

    bool cell2screen(int &sx,int &sy,double x,double y,double z);   // [pixel] <- [cell] return tru if in front of player
    void draw_scanline(int sx,int sy0,int sy1,                int symin,int tx0,int ty0,int tx1,int ty1,BYTE *li); // render screen y-scan line from texture (sub routine)
    void draw_scanline(int sx,int sy0,int sy1,int sz0,int sz1,int symin,int tx0,int ty0,int tx1,int ty1,BYTE *li); // render screen y-scan line from texture (sub routine)
    void draw_cell(_ray &p);            // render actual cell hit by ray p (sub routine)
    void draw();                        // render view
    void update(double dt);             // update game logic (call in timer with interval dt [s])
    void mouse(double x,double y,TShiftState sh)    // editor mouse handler
        {
        keys.setm(x,y,sh);  // mx,my mouse screen pos [pixels]
        x=floor(x/_Doom3D_edit_cell_size); //if (x>=map.xs) x=map.xs-1; if (x<0) x=0;
        y=floor(y/_Doom3D_edit_cell_size); //if (y>=map.ys) y=map.ys-1; if (y<0) y=0;
        DWORD xx=x,yy=y;
        if ((xx>=0)&&(xx<map.xs)&&(yy>=0)&&(yy<map.ys)) keys.setk(xx,yy,sh); // kx,ky mouse map pos [cells]
        xx=keys.kx; yy=keys.ky;
        if (keys.Shift.Contains(ssLeft  )) map.pyx[yy][xx]=(txr_wall)|(txr_floor<<8)|(txr_ceil<<16)|(cell_h<<24);
        if (keys.Shift.Contains(ssRight )) map.pyx[yy][xx]=0xFFFFFFFF;
        if (keys.Shift.Contains(ssMiddle))
            {
            DWORD c=map.pyx[yy][xx];
            txr_wall  =c     &0xFF;
            txr_floor=(c>> 8)&0xFF;
            txr_ceil =(c>>16)&0xFF;
            cell_h   =(c>>24)&0xFF;
            }
        keys.rfsmouse();
        }
    void wheel(int delta,TShiftState sh)    // editor mouse wheel handler
        {
        if (sh.Contains(ssShift))
            {
            if (delta<0) { cell_h-=10; if (cell_h>_Doom3D_cell_size) cell_h=0; }
            if (delta>0) { cell_h+=10; if (cell_h>_Doom3D_cell_size) cell_h=_Doom3D_cell_size; }
            }
        else{
            if (delta<0) { (*txr_mode)--; if (*txr_mode>=tn) *txr_mode=tn-1; }
            if (delta>0) { (*txr_mode)++; if (*txr_mode==tn) *txr_mode=   0; }
            }
        }
    };

完整代码(没有任何帮助文件)约为 27.7 KByte,因此它不适合答案。它的旧版本(没有地板/天花板/墙顶)在这里:

  • 不同高度尺寸的光线投射

它比你的更复杂,因为它允许墙壁具有不同的高度以及墙壁、地板和天花板的单独纹理(但是天花板尚未实现,但我使用了云纹理代替)并且还跳跃(z球员的位置)。

地图和投影的几何形状与上面的链接中的相同。这里有一个辅助函数,可以在 2.5D 地图位置和屏幕之间进行转换(这样你就知道使用了什么数学):

bool Doom3D::cell2screen(int &sx,int &sy,double x,double y,double z)
    {
    double a,l;
    // x,y relative to player
    x-=plr.x;
    y-=plr.y;
    // convert z from [cell] to units
    z*=_Doom3D_cell_size;
    // angle -> sx
    a=atanxy(x,y)-plr.a;
    if (a<-pi) a+=pi2;
    if (a>+pi) a-=pi2;
    sx=double(sxs2)*(1.0+(2.0*a/view_ang));
    // perpendicular distance -> sy
    l=sqrt((x*x)+(y*y))*cos(a);
    sy=sys2+divide((double((2.0*plr.z+1.0)*_Doom3D_cell_size)-z-z)*wall,l);
    // in front of player?
    return (fabs(a)<=0.5*pi);
    }

该功能本身仅用于编辑器的 HUD(在 2.5D 视图中突出显示选定的地图单元格)sxs2,sys2是屏幕的中心(分辨率的一半sxs,sys) and wall用于管理如下计算的透视投影:

void Doom3D::scr_resize(DWORD xs,DWORD ys)
    {
    scr.resize(xs,ys);
    sxs2=scr.xs>>1;
    sys2=scr.ys>>1;
    // aspect ratio,view angle corrections
    double a=90.0*deg-view_ang;
    wall=double(scr.xs)*(1.25+(0.288*a)+(2.04*a*a))*focus/double(_Doom3D_cell_size);
    }

在我的引擎中,光线以更像光线行进的方式进行测试,因为它不会在第一次击中时停止,而是在第一次击中时停止覆盖整个墙壁尺寸(因此光线可以穿过较小的墙壁,这些墙壁不会阻挡整个视图,这也会渲染地面沿途铺瓷砖)。

由于您的引擎不会执行此操作并且只有全尺寸的墙壁,因此您只会受到单次打击。

现在终于回到你的问题了。我看到有两种改进方法:

  1. 使用墙壁测试的射线结果代替投射地板/天花板射线的结果

    由于您希望在天花板和地板上使用单独的瓷砖,因此您应该使用光线行进(如光线投射)。意思是向屏幕的每一列投射一条光线,并通过所有地图单元格交叉点迭代它,直到击中墙壁。然而,您必须在每个单元格命中时进行渲染,而不是仅在墙壁命中时进行渲染。就像这张图片上的东西:

    ray march

    所以红线是投射光线(橙色只是一面镜子)。地图的每个渲染单元都被射线击中 2 个点。您应该知道每次点击的地图单元位置以及光线投射的屏幕坐标。因此,您只需添加与相机的垂直距离,并将线段渲染为地板和天花板的透视正确插值纹理线。墙壁始终只是垂直的非透视纹理线。纹理坐标取自命中的地图位置(坐标的小数部分)。代码有点乱,但这里是:

     void Doom3D::draw_scanline(int sx,int sy0,int sy1,int symin,int tx0,int ty0,int tx1,int ty1,BYTE *li)
         {
         // affine texture mapping (front side of walls) sy0>sy1
         union { DWORD dd; BYTE db[4]; } cc;
         int sy,tx,ty,ktx,kty,dtx,dty,ctx,cty,dsy;
                dsy=sy1-sy0; if (dsy<0)                        dsy=-dsy;
         ktx=0; dtx=tx1-tx0; if (dtx>0) ktx=+1; else { ktx=-1; dtx=-dtx; } tx=tx0; ctx=0;
         kty=0; dty=ty1-ty0; if (dty>0) kty=+1; else { kty=-1; dty=-dty; } ty=ty0; cty=0;
         if (dsy) for (sy=sy0;sy>=sy1;sy--)
             {
             if ((sy>=0)&&(sy<scr.ys)&&(sy<=symin))
              if ((tx>=0)&&(tx<ptxr->xs)&&(ty>=0)&&(ty<ptxr->ys))
                 {
                 cc.dd=ptxr->pyx[ty][tx];
                 cc.db[0]=li[cc.db[0]];
                 cc.db[1]=li[cc.db[1]];
                 cc.db[2]=li[cc.db[2]];
                 scr.pyx[sy][sx]=cc.dd;
                 }
             for (ctx+=dtx;ctx>=dsy;) { ctx-=dsy; tx+=ktx; }
             for (cty+=dty;cty>=dsy;) { cty-=dsy; ty+=kty; }
             }
         }
     void Doom3D::draw_scanline(int sx,int sy0,int sy1,int sz0,int sz1,int symin,int tx0,int ty0,int tx1,int ty1,BYTE *li)
         {
         // perspective correct mapping (floor, top side of walls, ceiling) sy0>sy1
         union { DWORD dd; BYTE db[4]; } cc;
         int sy,tx,ty,dsy,dtx,dty,n,dn;
         int a,_z0,_z1,_tx;
         const int acc0=16;
         const int acc1=8;
         _tx=tx0-(tx0%ptxr->ys);
         tx0-=_tx;
         tx1-=_tx;
         dsy=sy1-sy0; dn=abs(dsy);
         dtx=tx1-tx0;
         dty=ty1-ty0;
         if (sz0==0) return; _z0=(1<<acc0)/sz0;
         if (sz1==0) return; _z1=(1<<acc0)/sz1;
         if (dn) for (n=0;n<=dn;n++)
             {
             sy=sy0+((n*dsy)/dn);
             a=((n<<acc1)*_z1)/(((dn-n)*_z0)+(n*_z1)); // perspective correction a=<0,1<<acc1> (https://en.wikipedia.org/wiki/Texture_mapping)
             tx=tx0+((a*dtx)>>acc1)+_tx;
             ty=ty0+((a*dty)>>acc1);
             if ((sy>=0)&&(sy<scr.ys)&&(sy<=symin))
              if ((tx>=0)&&(tx<ptxr->xs)&&(ty>=0)&&(ty<ptxr->ys))
                 {
                 cc.dd=ptxr->pyx[ty][tx];
                 cc.db[0]=li[cc.db[0]];
                 cc.db[1]=li[cc.db[1]];
                 cc.db[2]=li[cc.db[2]];
                 scr.pyx[sy][sx]=cc.dd;
                 }
             }
         }
     void Doom3D::draw_cell(_ray &p)
         {
         BYTE *li;
         DWORD m;
         int tx0,tx1,ty0,ty1,sy,sy0,sy1,sy2,sy3,sz0,sz1,q;
         int sy4,sy5;
         //sy0>=sy1
         sy0=sys2+divide(double((1.0+2.0*plr.z)*_Doom3D_cell_size)*wall,p.l0);
         sy1=sy0 -divide(double((p.map>>24)<<1                   )*wall,p.l0);
         sy2=sys2+divide(double((1.0+2.0*plr.z)*_Doom3D_cell_size)*wall,p.l1);
         sy3=sy2 -divide(double((p.map>>24)<<1                   )*wall,p.l1);
         sy4=sys2-divide(double((1.0-2.0*plr.z)*_Doom3D_cell_size)*wall,p.l1);
         sy5=sys2-divide(double((1.0-2.0*plr.z)*_Doom3D_cell_size)*wall,p.l0);
         sz0=double(p.l0*_Doom3D_cell_size);
         sz1=double(p.l1*_Doom3D_cell_size);
         // select mipmap resolution
         ty0=divide(double(_Doom3D_cell_size<<1)*wall,p.l0);
         for (q=tm-1;q>=0;q--)
             {
             ptxr=txr+q;
             if (ty0<=ptxr->ys) break;
             }
         if (_no_mipmap) ptxr=txr;
         // mouse select
         if (p.sx==round(keys.mx))
          if (keys.my>=sy3)
           if (keys.my<=sy0)
            if ((keys.my>=map2.ys)||(keys.mx>=map2.xs))
             {
             keys.kx=p.x;
             keys.ky=p.y;
             }
         if ((p.map&0xFF)==0xFF) { sy1=sy0; sy3=sy2; }
         // wall
         if ((sy1<p.sy1)&&((p.map&0xFF)!=0xFF))
             {
             tx0=ptxr->ys*(p.map&0xFF);
             if (p.tp0=='H') { li=liH; tx0+=double(double(ptxr->ys-1)*(p.x0-floor(p.x0))); }
             if (p.tp0=='V') { li=liV; tx0+=double(double(ptxr->ys-1)*(p.y0-floor(p.y0))); }
             draw_scanline(p.sx,sy0,sy1,p.sy1,tx0,0,tx0,((p.map>>24)*(ptxr->ys-1))/_Doom3D_cell_size,li);
             p.sy1=sy1;
             }
         // ceiling
         if ((p.map&0xFF0000)!=0xFF0000)
             {
             q=ptxr->ys*((p.map>>16)&0xFF);
             tx0=double(double(ptxr->ys-1)*(p.x0-double(p.x)))+q;
             ty0=double(double(ptxr->ys-1)*(p.y0-double(p.y)));
             tx1=double(double(ptxr->ys-1)*(p.x1-double(p.x)))+q;
             ty1=double(double(ptxr->ys-1)*(p.y1-double(p.y)));
             draw_scanline(p.sx,sy5,sy4,sz0,sz1,p.sy1,tx0,ty0,tx1,ty1,liF);
             }
         // floor/top side
         if ((sy3<p.sy1)&&((p.map&0xFF00)!=0xFF00))
             {
             q=ptxr->ys*((p.map>>8)&0xFF);
             tx0=double(double(ptxr->ys-1)*(p.x0-double(p.x)))+q;
             ty0=double(double(ptxr->ys-1)*(p.y0-double(p.y)));
             tx1=double(double(ptxr->ys-1)*(p.x1-double(p.x)))+q;
             ty1=double(double(ptxr->ys-1)*(p.y1-double(p.y)));
             draw_scanline(p.sx,sy1,sy3,sz0,sz1,p.sy1,tx0,ty0,tx1,ty1,liF);
             p.sy1=sy3;
             }
         if (sy3<p.sy1) p.sy1=sy3;
         }
     void Doom3D::draw()
         {
         tbeg();
         _ray p;
         DWORD x,y,c,m;
         DWORD mx,mx0,mx1;
         DWORD my,my0,my1;
         double a,a0,da,dx,dy,l;
         double xx0,yy0,dx0,dy0,ll0,dl0;
         double xx1,yy1,dx1,dy1,ll1,dl1;
    
         // compute diffuse + ambient lighting LUT (light scaled shades of gray)
         c=155.0+fabs(100.0*sin(   plr.a)); for (x=0;x<256;x++) liH[x]=(x*c)>>8; // H wall
         c=155.0+fabs(100.0*cos(   plr.a)); for (x=0;x<256;x++) liV[x]=(x*c)>>8; // V wall
         c=155.0+fabs(100.0*cos(30.0*deg)); for (x=0;x<256;x++) liF[x]=(x*c)>>8; // floor, wall top side
    
         // [2D map]
         m=_Doom3D_edit_cell_size;
         for (my0=0,my1=m,y=0;y<map.ys;y++,my0=my1,my1+=m)   // map.pyx[][]
          for (mx0=0,mx1=m,x=0;x<map.xs;x++,mx0=mx1,mx1+=m)
             {
             c=0x00010101*((0x40+(0x40*(map.pyx[y][x]>>24)))/_Doom3D_cell_size);
             for (my=my0;my<my1;my++)
              for (mx=mx0;mx<mx1;mx++)
               map2.pyx[my][mx]=c;
             }
         c=0x00202020;                                       // map grid
         for (y=0;y<map2.ys;y+=m) for (x=0;x<map2.xs;x++) map2.pyx[y][x]=c;
         for (x=0;x<map2.xs;x+=m) for (y=0;y<map2.ys;y++) map2.pyx[y][x]=c;
         x=keys.kx*m;                                        // selected cell
         y=keys.ky*m;
         map2.bmp->Canvas->Pen->Color=0x0020FFFF;
         map2.bmp->Canvas->MoveTo(x  ,y  );
         map2.bmp->Canvas->LineTo(x+m,y  );
         map2.bmp->Canvas->LineTo(x+m,y+m);
         map2.bmp->Canvas->LineTo(x  ,y+m);
         map2.bmp->Canvas->LineTo(x  ,y  );
         map2.bmp->Canvas->Pen->Mode=pmMerge;
    
         // [cast rays]
         a0=plr.a-(0.5*view_ang);
         da=divide(view_ang,scr.xs-1);
         da*=scale_x;
         for (a=a0,x=0;x<scr.xs;x+=scale_x,a+=da)
             {
             // grid V-line hits
             ll0=1.0e20; dl0=0.0; dx0=cos(a); const char tp0='V';
             if (dx0<0.0) { xx0=floor(plr.x); dx0=-1.0; }
             if (dx0>0.0) { xx0=ceil (plr.x); dx0=+1.0; }
             if (fabs(dx0)>1e-6) { dy0=tan(a); yy0=plr.y+((xx0-plr.x)*dy0);             dy0*=dx0; dx=xx0-plr.x; dy=yy0-plr.y; ll0=sqrt((dx*dx)+(dy*dy)); dl0=sqrt((dx0*dx0)+(dy0*dy0)); }
             // grid H-line hits
             ll1=1.0e20; dl1=0.0; dy1=sin(a); const char tp1='H';
             if (dy1<0.0) { yy1=floor(plr.y); dy1=-1.0; }
             if (dy1>0.0) { yy1=ceil (plr.y); dy1=+1.0; }
             if (fabs(dy1)>1e-6) { dx1=divide(1.0,tan(a)); xx1=plr.x+((yy1-plr.y)*dx1); dx1*=dy1; dx=xx1-plr.x; dy=yy1-plr.y; ll1=sqrt((dx*dx)+(dy*dy)); dl1=sqrt((dx1*dx1)+(dy1*dy1)); }
             p.ang=a;
             p.sx =x;
             p.sy0=scr.ys;
             p.sy1=scr.ys;
             // first hit
             if (ll0<ll1){ p.tp0=tp0; p.x0=xx0; p.y0=yy0; p.l0=ll0; xx0+=dx0; yy0+=dy0; ll0+=dl0; }
              else       { p.tp0=tp1; p.x0=xx1; p.y0=yy1; p.l0=ll1; xx1+=dx1; yy1+=dy1; ll1+=dl1; }
             p.l0*=cos(p.ang-plr.a); // anti fish eye
             p.map=0xFFFFFFFF; p.x=p.x0; p.y=p.y0;
             for (;;)
                 {
                 // closest hit
                 if (ll0<ll1) { p.tp1=tp0; p.x1=xx0; p.y1=yy0; p.l1=ll0; xx0+=dx0; yy0+=dy0; ll0+=dl0; }
                  else        { p.tp1=tp1; p.x1=xx1; p.y1=yy1; p.l1=ll1; xx1+=dx1; yy1+=dy1; ll1+=dl1; }
                 p.x=floor(0.5*(p.x0+p.x1)); // actaul cell position
                 p.y=floor(0.5*(p.y0+p.y1));
                 p.l1*=cos(p.ang-plr.a);     // anti fish eye
                 // edge of map crossed?
                 if ((p.x>=0)&&(p.x<map.xs)&&(p.y>=0)&&(p.y<map.ys)) p.map=map.pyx[p.y][p.x]; else break;
                 // render
                 draw_cell(p);               // scan line
                 if (p.sy1<=0) break;        // scan line reached top of screen
                 // prepare next cell position
                 p.tp0=p.tp1; p.x0=p.x1; p.y0=p.y1; p.l0=p.l1;
                 }
             // copy skiped scan lines
             for (mx=1;mx<scale_x;mx++)
              if (x+mx<scr.xs)
               for (y=0;y<scr.ys;y++)
                scr.pyx[y][x+mx]=scr.pyx[y][x];
             // render map ray
             if (x==sxs2)                   map2.bmp->Canvas->Pen->Color=0x000000FF;
             if ((x==0)||(x==sxs2+scale_x)) map2.bmp->Canvas->Pen->Color=0x00002020;
             map2.bmp->Canvas->MoveTo(plr.x*m,plr.y*m);
             map2.bmp->Canvas->LineTo(p.x1*m,p.y1*m);
             }
         map2.bmp->Canvas->Pen->Mode=pmCopy;
         map2.bmp->Canvas->Pen->Color=0x000000FF;
         map2.bmp->Canvas->Brush->Color=0x000000FF;
         c=focus*m;
         map2.bmp->Canvas->Ellipse(plr.x*m-c,plr.y*m-c,plr.x*m+c,plr.y*m+c);
         scr.bmp->Canvas->Draw(0,0,map2.bmp);
         // ... here HUD and info texts continues I skipped it to keep this simple
         }
    
  2. 渲染地板和天花板,无需行/列光线投射的每个像素

    简单的光线投射器确实使用非纹理地板/天花板,这使得这个简单只需在渲染墙壁之前渲染一半屏幕的天空和另一半屏幕的底色(或者在渲染墙壁之后,如果记住了渲染墙壁的开始和结束):

    no texture floor/ceiling

    代码中是这样的:

     int x,y,sxs=sxs2<<1,sys=sys2<<1;
     // simple color sky/ceiling
     for (y=0;y<sys2;y++)
      for (x=0;x<sxs;x++)
       scr.pyx[y][x]=0x000080FF;
      for (y=sys2;y<sys;y++)
       for (x=0;x<sxs;x++)
        scr.pyx[y][x]=0x00404040;
    

    为了使其更加美观,通常会添加覆盖天花板的地图天空纹理的室外部分。它不随玩家移动而只是旋转。因此,您可以将天空纹理四边形映射到仅旋转的视图的上半部分-plr.a这里是几何形状的概述:

    sky single texture

    半径越大R是纹理分辨率的一半,我根据经验计算出的较小值r=R*sin(0.5*view_ang)因为它对我来说看起来最好(但是真正的值应该根据屏幕纵横比和透视焦距来计算)view_ang).

    这里有一些代码:

         const int x0=0,x1=sxs2<<1,y0=0,y1=sys2,y2=sys2<<1;
         int sx[4]={x0,x0,x1,x1},
             sy[4]={y0,y1,y1,y0},
             tx[4],ty[4],dx,dy;
         float a,r,R;
         R=sky.xs>>1;            // sky texture inscribed circle radius
         r=R*sin(0.5*view_ang);  // smaller radius (visible portion of sky)
         dx=sky.xs>>1;           // mid of sky texture
         dy=sky.ys>>1;
         a=plr.a-(0.5*view_ang);
         tx[0]=float(R*cos(a))+dx;
         ty[0]=float(R*sin(a))+dy;
         a=plr.a-(0.5*view_ang);
         tx[1]=float(r*cos(a))+dx;
         ty[1]=float(r*sin(a))+dy;
         a=plr.a+(0.5*view_ang);
         tx[2]=float(r*cos(a))+dx;
         ty[2]=float(r*sin(a))+dy;
         a=plr.a+(0.5*view_ang);
         tx[3]=float(R*cos(a))+dx;
         ty[3]=float(R*sin(a))+dy;
         polygon2D(scr,sky,sx,sy,tx,ty,4);
    

    地面(和室内天花板)可以类似地完成,但半径R必须是整个纹理的一部分。地图中的玩家位置必须缩放到纹理half size - R并添加到纹理坐标。然而,纹理分辨率必须足够大,否则看起来不会那么好(理想情况下,空白空间应该与地图尺寸的分辨率*墙壁纹理分辨率相匹配......所以如果R一半的空白空间也将是R然后它是这样完成的:

         const int x0=0,x1=sxs2<<1,y0=0,y1=sys2,y2=sys2<<1;
         int sx[4]={x0,x0,x1,x1},
             sy[4]={y1,y2,y2,y1},
             tx[4],ty[4],i,dx,dy,dr;
         float a,r,R;
         R=sky.xs>>2;            // sky texture inscribed circle radius /2 so empty space is also R
         r=R*sin(0.5*view_ang);  // smaller radius (visible portion of sky)
         dx=sky.xs>>1;           // mid of sky texture
         dy=sky.ys>>1;
         a=float(R)/float(map.xs);   // add player position skaled to empty space
         dx+=float(plr.x*a);
         dy+=float(plr.y*a);
         a=plr.a-(0.5*view_ang);
         tx[0]=float(R*cos(a))+dx;
         ty[0]=float(R*sin(a))+dy;
         a=plr.a-(0.5*view_ang);
         tx[1]=float(r*cos(a))+dx;
         ty[1]=float(r*sin(a))+dy;
         a=plr.a+(0.5*view_ang);
         tx[2]=float(r*cos(a))+dx;
         ty[2]=float(r*sin(a))+dy;
         a=plr.a+(0.5*view_ang);
         tx[3]=float(R*cos(a))+dx;
         ty[3]=float(R*sin(a))+dy;
         polygon2D(scr,sky,sx,sy,tx,ty,4);
    

    如果您想使用较小的纹理,那么您的多边形渲染必须能够处理纹理坐标,例如GL_REPEAT在OpenGL中。功能polygon2D(scr,sky,sx,sy,tx,ty,4)只是简单/丑陋/缓慢/未优化的 2D 纹理多边形渲染我昨天忙着测试这个(因为我不想弄乱 #1 方法的优化渲染例程,这些方法只支持扫描线而不是多边形)其中sx,sy是屏幕坐标数组,tx,ty是纹理坐标数组,4是顶点数并且scr,txr是目标纹理和源纹理。代码只是这个的一个端口fill_quad没有阴影和 SSD1306 相关的东西。这里是完整代码:

     const int ys_max=1024;
     int bufl_vx[ys_max],bufr_vx[ys_max];
     int bufl_tx[ys_max],bufr_tx[ys_max];
     int bufl_ty[ys_max],bufr_ty[ys_max];
     void _fill2D_line(Texture2D &scr,Texture2D &txr,int vx0,int vy0,int tx0,int ty0,int vx1,int vy1,int tx1,int ty1)
         {
         int *bvx,*btx,*bty;
         int i,n,cvx,cvy,ctx,cty,svx,svy,stx,sty;
         // target buffer depend on y direction (before point ordering)
         if (vy0<vy1){ bvx=bufl_vx; btx=bufl_tx; bty=bufl_ty; }
          else       { bvx=bufr_vx; btx=bufr_tx; bty=bufr_ty; }
         // order points so joined edges are interpolated the same way
         if (vx0>vx1)
             {
             i=vx0; vx0=vx1; vx1=i;
             i=vy0; vy0=vy1; vy1=i;
             i=tx0; tx0=tx1; tx1=i;
             i=ty0; ty0=ty1; ty1=i;
             }
         // line DDA parameters
         vx1-=vx0; svx=0; if (vx1>0) svx=+1; if (vx1<0) { svx=-1; vx1=-vx1; } if (vx1) vx1++;            n=vx1;
         vy1-=vy0; svy=0; if (vy1>0) svy=+1; if (vy1<0) { svy=-1; vy1=-vy1; } if (vy1) vy1++; if (n<vy1) n=vy1;
         tx1-=tx0; stx=0; if (tx1>0) stx=+1; if (tx1<0) { stx=-1; tx1=-tx1; } if (tx1) tx1++; if (n<tx1) n=tx1;
         ty1-=ty0; sty=0; if (ty1>0) sty=+1; if (ty1<0) { sty=-1; ty1=-ty1; } if (ty1) ty1++; if (n<ty1) n=ty1;
         // single pixel (not a line)
         if (!n)
             {
             if ((vy0>=0)&&(vy0<scr.ys))
                 {
                 bufl_vx[vy0]=vx0; bufl_tx[vy0]=tx0; bufl_ty[vy0]=ty0;
                 bufr_vx[vy0]=vx0; bufr_tx[vy0]=tx0; bufr_ty[vy0]=ty0;
                 }
             return;
             }
         // horizontal line
         if (svy==0) return;
         // ND DDA algo i is parameter
         for (cvx=cvy=ctx=cty=n,i=0;;)
             {
             if ((vy0>=0)&&(vy0<scr.ys)){ bvx[vy0]=vx0; btx[vy0]=tx0; bty[vy0]=ty0; }
             i++; if (i>=n) break;
             cvx-=vx1; if (cvx<=0){ cvx+=n; vx0+=svx; }
             cvy-=vy1; if (cvy<=0){ cvy+=n; vy0+=svy; }
             ctx-=tx1; if (ctx<=0){ ctx+=n; tx0+=stx; }
             cty-=ty1; if (cty<=0){ cty+=n; ty0+=sty; }
             }
         }
     void _fill2D(Texture2D &scr,Texture2D &txr,int Y0,int Y1)
         {
         int vx0,vx1,tx0,tx1,ty0,ty1;
         int vy,i,n,cvx,ctx,cty,svx,stx,sty;
         // fill horizontal lines
         for (vy=Y0;vy<=Y1;vy++)
             {
             // horizontal line to render
             vx0=bufl_vx[vy]; tx0=bufl_tx[vy]; ty0=bufl_ty[vy];
             vx1=bufr_vx[vy]; tx1=bufr_tx[vy]; ty1=bufr_ty[vy];
             if ((vx0<      0)||(vx1<      0)) continue;
             if ((vx0<      0)&&(vx1<      0)) continue;
             if ((vx0>=scr.xs)&&(vx1>=scr.xs)) continue;
             // line DDA parameters
             vx1-=vx0; svx=0; if (vx1>0) svx=+1; if (vx1<0) { svx=-1; vx1=-vx1; } if (vx1) vx1++;            n=vx1;
             tx1-=tx0; stx=0; if (tx1>0) stx=+1; if (tx1<0) { stx=-1; tx1=-tx1; } if (tx1) tx1++; if (n<tx1) n=tx1;
             ty1-=ty0; sty=0; if (ty1>0) sty=+1; if (ty1<0) { sty=-1; ty1=-ty1; } if (ty1) ty1++; if (n<ty1) n=ty1;
             // single pixel (not a line)
             if (!n)
                 {
                 if ((vx0>=0)&&(vx0<scr.xs)) scr.pyx[vy][vx0]=txr.pyx[ty0][tx0];
                 continue;
                 }
             // ND DDA algo i is parameter
             for (cvx=ctx=cty=n,i=0;;)
                 {
                 while (tx0<0) tx0+=txr.xs;
                 while (ty0<0) ty0+=txr.ys;
                 while (tx0>=txr.xs) tx0-=txr.xs;
                 while (ty0>=txr.ys) ty0-=txr.ys;
                 if ((vx0>=0)&&(vx0<scr.xs)) scr.pyx[vy][vx0]=txr.pyx[ty0][tx0];
                 i++; if (i>=n) break;
                 cvx-=vx1; if (cvx<=0){ cvx+=n; vx0+=svx; }
                 ctx-=tx1; if (ctx<=0){ ctx+=n; tx0+=stx; }
                 cty-=ty1; if (cty<=0){ cty+=n; ty0+=sty; }
                 }
             }
         }
     void polygon2D(Texture2D &scr,Texture2D &txr,int *vx,int *vy,int *tx,int *ty,int n)
         {
         int i,j,y,Y0,Y1;
         // y range to render
         Y0=Y1=vy[0];
         for (i=1;i<n;i++)
             {
             if (Y0>vy[i]) Y0=vy[i];
             if (Y1<vy[i]) Y1=vy[i];
             }
         // clip to screen in y axis
         if ((Y1<0)||(Y0>=scr.ys)) return;
         if (Y0<      0) Y0=       0;
         if (Y1>=scr.ys) Y1=scr.ys-1;
         // clear buffers
         for (y=Y0;y<=Y1;y++)
             {
             bufl_vx[y]=-1;
             bufr_vx[y]=-1;
             }
         // render circumference
         for (j=n-1,i=0;i<n;j=i,i++)
          _fill2D_line(scr,txr,vx[i],vy[i],tx[i],ty[i],vx[j],vy[j],tx[j],ty[j]);
         // fill horizontal lines
         _fill2D(scr,txr,Y0,Y1);
         }
    

    最后预览(天空和地面都使用天空纹理):

    single textured quad

    渲染没有透视正确插值,但对于单个大纹理来说这不是一个大问题。如果你还想跳跃那么你需要重新计算R,r与使用z玩家的位置或使用另一个选项来计算纹理坐标,只需投射 4 条光线(半屏幕矩形的每个角各一条)并检查它击中地图边缘的位置。

    背后的数学可以在这里找到:

    • 画布上的透视视觉
    • 如何在顶部 2D 小地图上显示用 3D 透视渲染的平面世界的可见部分?

    但请注意,您的视角必须与光线投射相匹配,否则可能会出现对齐伪影(或地面移动速度与墙壁略有不同)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Raycaster 中的高效地板/天花板渲染 的相关文章

  • C++ Irrlicht 程序未链接:“未定义对‘__imp_createDevice’的引用”

    我的 Irrlicht 程序无法链接 我使用的编译器是g Code include
  • 投影 3D 网格的 2D 轮廓算法

    给定 一个 3D 网格 由一组顶点和三角形定义 并用这些点构建网格 问题 找到任意平面上投影的任意旋转网格的二维轮廓 投影很容易 挑战在于找到平面中投影三角形边的 外壳 我需要一些有关研究该算法的输入 指针的帮助 为简单起见 我们可以假设
  • 在哪里指定图像尺寸以实现最快渲染:在 HTML 中还是在 CSS 中?

    我了解到 明确指定图像尺寸是最佳实践 然后 浏览器可以在仍然下载图像本身的同时布局页面 从而缩短 感知的 页面渲染时间 这是真的 如果是这样 在 HTML 或 CSS 中指定尺寸是否有区别 HTML img src width 200 he
  • .NET 图形重影

    我正在为我们正在开发的新应用程序制作一个示例 GUI 我已经决定了语言 但我可以使用任何第 3 方 DLL 或插件或任何我需要的东西 以使 GUI 尽可能无缝地工作 他们希望它非常像 mac ubuntu vista Windows 7 所
  • 预乘 Alpha 合成

    我正在尝试实现预乘阿尔法混合 在本页 什么是颜色混合 https learn microsoft com en us previous versions windows xna bb976070 v xnagamestudio 41 它们确
  • IE9-11 检测变换样式:preserve-3d

    我为一个项目制作了一个 3d 类型的菜单 自然 IE 会引起问题 因为 IE10 即使 3d 变换工作 也不支持变换样式 preserve 3d 我尝试了解决方法 通过对 3d 菜单容器的每个子元素应用变换 但至少可以说 动画看起来很糟糕
  • 在java中使用多个bufferedImage

    我正在 java 小程序中制作游戏 并且正在尝试优化我的代码以减少闪烁 我已经实现了双缓冲 因此我尝试使用另一个 BufferedImage 来存储不改变的游戏背景元素的图片 这是我的代码的相关部分 public class QuizApp
  • 将签名位图转换为签名字符串(很奇怪的一个)

    基本上我需要将位图图像转换为字符串 但这不是常见的 困境在于该字符串由两部分组成 1 积分 2 线路 我需要将图像转换为由 分隔的两个部分 我得到的一个例子是 221A 221A270A270A25032503200720071716171
  • 在 RGL 中将立方体绘制到 3D 散点图中

    我正在尝试向 3D 散点图添加较小的立方体 网格 具有指定边长 我希望立方体位于原点 我该怎么做呢 我已经玩过cube3d 但我似乎无法将立方体正确定位 也无法使其成为网格 因此我可以看到它包含的数据点 这是我所拥有的 library rg
  • 如何将平面上的 3D 点转换为 UV 坐标?

    我有一个 3d 点 定义为 x0 y0 z0 该点属于一个平面 定义为 a b c d normal a b c and ax by cz d 0 如何将 3d 点转换或映射为一对 u v 坐标 这一定是非常简单的事情 但我无法弄清楚 首先
  • Android 中的 OpenGL 缩小

    我正在使用 3D 对象并渲染它并通过扩展 GLSurfaceView 实现渲染器来显示它 问题是如何通过捏合和捏合进行缩小 下面是我的班级 package com example objLoader import java nio Byte
  • XNA中窗口系统的渲染策略(RenderTarget性能)

    我目前正在从头开始为 XNA 游戏创建一个窗口系统 我主要针对 Windows 进行开发 但谁知道我将来可能支持哪些平台 如果您知道本机 Direct3D 的这一点 请随意回答 因为性能语义应该类似 如果可能 请考虑如果目标平台是 X Bo
  • 如何在不使用 Kinect SDK 函数的情况下将深度空间中的点转换为 Kinect 中的颜色空间?

    我正在做一个增强现实应用程序 将 3D 对象叠加在用户的彩色视频之上 使用 Kinect 1 7 版本 虚拟对象的渲染在 OpenGL 中完成 我已经成功地在深度视频上叠加了 3D 对象 只需使用 NuiSensor h 标头中深度相机的固
  • 如何从横滚、俯仰和偏航获取相机向上矢量?

    我需要从滚动角 俯仰角和偏航角 以度为单位 获取相机的向上矢量 以获得正确的外观 我已经尝试了几个小时不同的事情 但没有运气 这里的任何帮助将不胜感激 横滚 俯仰和偏航定义 3 轴旋转 从这些角度 您可以构建一个 3x3 变换矩阵来表达该旋
  • 修改 GGplot2 对象

    然而 我很好奇 是否可以添加任何特定的图例或将哪个物种对应于观察到的预期绘图中 以分别知道它是哪个圆圈 我目前使用的是一个名为 finches 的假数据集 该包称为 cooccurr 它创建一个 ggplot 对象 我很好奇如何实际编辑它以
  • 为什么 OpenGL 有远裁剪平面,以及使用什么惯用法来处理这个问题?

    我一直在学习 OpenGL 持续困扰我的一个话题是远裁剪平面 虽然我可以理解近剪裁平面和侧剪裁平面 它们永远不会产生任何实际效果 因为它们之外的对象无论如何都不会被渲染 背后的推理 但远剪裁平面似乎只是一个烦恼 由于 OpenGL 背后的人
  • Internet Explorer 不渲染从 JQuery ajax 帖子返回的 html

    我有一个带有输入框的页面 其 onkeyup 根据输入的内容 搜索字段 触发 JQuery ajax 帖子 ajax 调用回发的 html 应该填充页面上的另一个 div 这是 jquery ajax 帖子 var o me results
  • java绕中心旋转矩形

    我想围绕其中心点旋转一个矩形 它应该保留在应该绘制的位置并在该空间中旋转 这是我的代码 AffineTransform transform new AffineTransform transform rotate Math toRadian
  • 带孔的多边形三角剖分

    我正在寻找一种算法或库 更好 将多边形分解为三角形 我将在 Direct3D 应用程序中使用这些三角形 最好的可用选项是什么 这是我到目前为止发现的 本 迪斯科的笔记 http www vterrain org Implementation
  • 3D 空间中两个盒子之间的交集

    我想为我的图形引擎实现一个碰撞检测系统 我不知道这是否是常见的方法 但我的想法是将任何实体对象 如网格或相机 绑定在 3D 盒子内 这会给我比球体更准确的结果 这个盒子由八个顶点定义 x0 min vertices x off parsin

随机推荐

  • Gitlab 上的 Kubernetes 执行程序 - 错误:作业失败(系统故障):Post *api/v1/namespaces/gitlab/pods: x509: 由未知机构签名的证书

    我正在尝试为 Gitlab 设置 Kubernetes 执行器 但收到此错误 错误 作业失败 系统故障 发布https api kubernetes de api v1 namespaces gitlab pods x509 未知权威机构签
  • Javascript - string.split(regex) 保留分隔符

    我想使用正则表达式分割字符串 并将分隔符 匹配信息包含在结果数组中 在java中我使用 theString split lt gt lt gt lt lt AND AND lt OR OR 但是 javascript不支持lookbehin
  • Sqoop导入:复合主键和文本主键

    堆栈 使用 Ambari 2 1 安装 HDP 2 3 2 0 2950 源数据库模式位于 sql server 上 它包含多个表 这些表的主键为 一个varchar 复合 两个 varchar 列或一个 varchar 一个 int 列或
  • 在 pm3d 地图中画一条线

    I have a and I want to overplot on it a 我将这条线定义为具有恒定高度的 3d 线 并且我认为通过这种方法我可以将它们相互重叠绘制 但不幸的是 我失败了 事实上 我意识到 gnuplot 中的密度图例程
  • UIKit Dynamics:识别圆形形状和边界

    我正在编写一个应用程序 我使用 UIKit Dynamics 来模拟不同圈子之间的交互 我使用以下代码创建我的圈子 self super initWithFrame CGRectMake location x radius 2 0 loca
  • SQLite 中嵌套内连接的问题

    下面的sql语句不会在SQLite中运行 select from A left join B inner join C on B fkC C pk on A optionalfkB B pk 我收到 sqlException 未知列 B p
  • 如何在Python中创建表?

    这就是我想在 Python 中复制的内容 这些是存储数据的变量的名称 name 1 Alex name 2 Zia age 1 13 age 2 12 game 1 1 game 2 2 favourite 1 chess favourit
  • 如何在 .Rmd 文件中添加要发布的功能或缩略图

    我目前正在尝试使用 blogdown 设置一个 Hugo 博客 但找不到从内部向帖子添加功能或缩略图的方法 Rmd文件 这会喜欢这样宁静峰主题 据我了解 只需添加一些如下语法即可在 md 文件中轻松完成 featuredImage img
  • Python Pyrebase 配置

    当我尝试运行我的代码时 import pyrebase firebaseConfig apiKey xxxxxx authDomain xxxxxx projectId xxxxxx storageBucket xxxxxxx servic
  • PREG_MATCH 检查所有单词和条件

    我编写了一个正则表达式 它在 OR 条件下搜索搜索词 这样就提供了字符串中的三个单词 无论它们的顺序如何 现在我只想放置一个 AND 条件 因为我想同时以不同的顺序在字符串中获取所有三个单词 这是我的preg match 正则表达式 myP
  • bash eval 未检测到 System.exit 返回代码

    挣扎了一个小时 java代码 ULogger info throwing out 666 System exit 666 bash 包装器 eval COMMAND TO RUN ret code printf error code d r
  • Python:无头模式支持旧版本的 Chrome

    我正在尝试使用 python 和 selenium 自动发送短信https voice google com about 当我运行下面的代码时 它会获取最新版本 谷歌浏览器实例并且工作正常 但是 当我以无头模式运行它时 它使用旧版本的谷歌浏
  • 使用python排序词频计数

    我必须使用 python 计算文本中的词频 我想到将单词保存在字典中并计算每个单词的数量 现在 如果我必须根据出现次数对单词进行排序 我可以使用相同的字典来完成此操作 而不是使用以键作为计数 以单词数组作为值的新字典吗 WARNING 此示
  • 在哪里可以更改“电子邮件已被占用”错误消息?

    我需要自定义消息错误 Email has already been taken对于电子邮件 我正在使用 Ruby 1 9 2 Rails 3 1 3 Devise 1 5 3 我尝试更改以下消息 config locales devise
  • 使用 NSDictionary 对象写入 plist 文件

    抱歉 我看到了类似的问题 但他们似乎没有给我一些完整的答案 我试着把它整理好 这样人们就不会讨厌我或我糟糕的英语 我正在使用带有故事板和 ARC 的 Xcode 4 2我可以从我的 plist 文件中读取 我的任务只是将更新后的值写回我的
  • Mathematica 输出格式

    Mathematica 如何决定何时对输出中的数字进行舍入 例如 给出输入 250000 5 给出输出 2500001 While 25000 5 确实打印为 25000 5 N 在这里也没有帮助 我需要使用 NumberForm 让它实际
  • 连接到 Oracle 中的拆分字符串列

    我的数据库中有一个列 其中包含 4 个字段作为 分隔字符串 我已经拆分了这些字段 因为我在报告中需要单独使用它们 我还需要单独使用这些字段作为针对另一个表的条件 我尝试过的事情 临时表 CREATE GLOBAL TEMPORARY TAB
  • 如何从 cordova-sqlite 同步获取数据?

    是否可以从cordova sqlite同步获取数据 我有一张桌子caseTable包含字段 ID 案例名称 日期 该表中的每一行对应于另一个以 caseName 字段命名的表 我需要循环遍历caseTable表并获取所引用表中的行数 fun
  • PHP 将“”添加到任何 xml 输出

    不确定发生了什么 但这是我的代码 template
  • Raycaster 中的高效地板/天花板渲染

    我在 Raycaster 引擎上工作了一段时间 我在较慢的机器上运行 我遇到的最具挑战性的问题是高效的地板和天花板铸造 我的问题是 我还可以使用什么其他更快的方法 我不确定末日地板和天花板是如何渲染的 到目前为止我尝试了两种典型的解决方案