view src/r_render.cc @ 107:20aa5a515896

Reformat one line /* */ comments to //
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 06 Oct 2014 12:42:55 +0300
parents a68786b9c74b
children 51d5549a1bdc
line wrap: on
line source

/*
 *        r_render.cc
 *        3D Rendering
 *        AJA 2002-04-21
 */


/*
This file is part of Yadex.

Yadex incorporates code from DEU 5.21 that was put in the public domain in
1994 by Raphaël Quinet and Brendon Wyber.

The rest of Yadex is Copyright © 1997-2000 André Majorel.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.
*/


#include "yadex.h"

#include <math.h>
#include <vector>
#include <map>
#include <algorithm>

#ifdef Y_X11
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
#include "levels.h"
#include "wstructs.h"
#include "gfx.h"
#include "img.h"
#include "sticker.h"
#include "gamesky.h"
#include "things.h"
#include "wadres.h"
#include "objid.h"
#include "objects.h"
#include "pic2img.h"
#include "rgb.h"
#include "gcolour2.h"

#include "r_render.h"
#include "r_images.h"


#define ML_UPPER_UNPEGGED  0x08
#define ML_LOWER_UNPEGGED  0x10


struct Y_View
{
  public:
    int p_type, px, py;
    // player type and position.

    float x, y;
    int z;
    // view position.

    static const int EYE_HEIGHT = 41;
    // standard height above the floor.

    float angle;
    float Sin, Cos;
    // view direction.

    int sw, sh;
    Img *screen;
    // screen image.

    bool texturing;
    bool sprites;
    bool walking;

    ImageCache *im_ch;

    int *thing_floors;

      Y_View()
    {
        memset(this, 0, sizeof *this);
    }

    void SetAngle(float new_ang)
    {
        angle = new_ang;

        if (angle >= TWOPI)
            angle -= TWOPI;
        else if (angle < 0)
            angle += TWOPI;

        Sin = sin(angle);
        Cos = cos(angle);
    }

    void CalcViewZ()
    {
        Objid o;
        GetCurObject(o, OBJ_SECTORS, int (x), int (y));
        int secnum = o.num;
        if (secnum >= 0)
            z = Sectors[secnum].floorh + EYE_HEIGHT;
    }

    void ClearScreen()
    {
        memset(screen->wbuf(), colour0, sw * sh);
    }

    void PutScreen(int x, int y)
    {
        DrawScreenBox3D(x, y, x + BOX_BORDER * 2 + sw,
                        y + BOX_BORDER * 2 + sh);

        Sticker sticker(*screen, true);

        sticker.draw(drw, 't', x + BOX_BORDER, y + BOX_BORDER);
    }

    void FindThingFloors()
    {
        thing_floors = new int[NumThings];

        for (int i = 0; i < NumThings; i++)
        {
            Objid o;
            GetCurObject(o, OBJ_SECTORS, Things[i].xpos, Things[i].ypos);
            int secnum = o.num;

            if (secnum < 0)
                thing_floors[i] = 0;
            else
                thing_floors[i] = Sectors[secnum].floorh;
        }
    }
};


static Y_View view;


struct DrawSurf
{
  public:
    enum
    {
        K_INVIS = 0,
        K_FLAT,
        K_TEXTURE
    };
    int kind;

    int h1, h2, tex_h;
    // heights for the surface (h1 is above h2).

    Img *img;
    img_pixel_t col;                // used if img is zero

    enum
    {
        SOLID_ABOVE = 1,
        SOLID_BELOW = 2
    };
    int y_clip;

    // CTor

      DrawSurf()
    {
        kind = K_INVIS;
        img = 0;
    }

    void FindFlat(const wad_flat_name_t & fname, Sector * sec)
    {
        if (view.texturing)
        {
            img = view.im_ch->GetFlat(fname);

            if (img != 0)
                return;
        }
        col = 0x70 + ((sec - Sectors) % 48);
    }

    void FindTex(const wad_tex_name_t & tname, LineDef * ld)
    {
        if (view.texturing)
        {
            img = view.im_ch->GetTex(tname);

            if (img != 0)
                return;
        }
        col = 0x30 + ((ld - LineDefs) % 64);

        if (col >= 0x60)
            col += 0x70;
    }
};


struct DrawWall
{
  public:
    typedef std::vector < struct DrawWall *>vec_t;

    Thing *th;
    // when `th' is non-zero, this is actually a sprite, and `ld' and
    // `sd' will be zero.  Sprites use the info in the `ceil' surface.

    LineDef *ld;
    SideDef *sd;
    Sector *sec;

    int side;
    // which side this wall faces (0 right, 1 left)

    float ang1, dang, cur_ang;
    float base_ang;
    // clipped angles

    float dist, t_dist;
    float normal;
    // line constants

    double iz1, diz, cur_iz;
    double mid_iz;
    // distance values (inverted, so they can be lerped)

    float spr_tx1;
    // translate coord, for sprite

    int sx1, sx2;
    // screen X coordinates

    int oy1, oy2;
    // for sprites, the remembered open space to clip to

    // surfaces

    DrawSurf ceil;
    DrawSurf upper;
    DrawSurf lower;
    DrawSurf floor;

    static const double IZ_EPSILON = 0.000001;

    // PREDICATES

    struct MidDistCmp
    {
        inline bool operator() (const DrawWall * A, const DrawWall * B) const
        {
            return A->mid_iz > B->mid_iz;
        }
    };

    struct DistCmp
    {
        inline bool operator() (const DrawWall * A, const DrawWall * B) const
        {
            if (fabs(A->cur_iz - B->cur_iz) < IZ_EPSILON)
                return A->diz > B->diz;

            return A->cur_iz > B->cur_iz;
        }
    };

    struct SX1Cmp
    {
        inline bool operator() (const DrawWall * A, const DrawWall * B) const
        {
            return A->sx1 < B->sx1;
        }

        inline bool operator() (const DrawWall * A, int x) const
        {
            return A->sx1 < x;
        }

        inline bool operator() (int x, const DrawWall * A) const
        {
            return x < A->sx1;
        }
    };

    struct SX2Less
    {
        int x;

          SX2Less(int _x):x(_x)
        {
        }

        inline bool operator() (const DrawWall * A) const
        {
            return A->sx2 < x;
        }
    };

    // methods

    void ComputeWallSurface()
    {
        Sector *front = sec;
        Sector *back = 0;

        if (is_obj(side ? ld->sidedef1 : ld->sidedef2))
        {
            SideDef *bsd = SideDefs + (side ? ld->sidedef1 : ld->sidedef2);

            if (is_obj(bsd->sector))
                back = Sectors + bsd->sector;
        }

        bool sky_upper = back && is_sky(front->ceilt) && is_sky(back->ceilt);

        if ((front->ceilh > view.z || is_sky(front->ceilt)) && !sky_upper)
        {
            ceil.kind = DrawSurf::K_FLAT;
            ceil.h1 = +99999;
            ceil.h2 = front->ceilh;
            ceil.tex_h = ceil.h2;
            ceil.y_clip = DrawSurf::SOLID_ABOVE;

            if (is_sky(front->ceilt))
                ceil.col = sky_colour;
            else
                ceil.FindFlat(front->ceilt, front);
        }

        if (front->floorh < view.z)
        {
            floor.kind = DrawSurf::K_FLAT;
            floor.h1 = front->floorh;
            floor.h2 = -99999;
            floor.tex_h = floor.h1;
            floor.y_clip = DrawSurf::SOLID_BELOW;

            if (is_sky(front->floort))
                floor.col = sky_colour;
            else
                floor.FindFlat(front->floort, front);
        }

        if (!back)
        {
            // ONE-sided line

            lower.kind = DrawSurf::K_TEXTURE;
            lower.h1 = front->ceilh;
            lower.h2 = front->floorh;
            lower.y_clip = DrawSurf::SOLID_ABOVE | DrawSurf::SOLID_BELOW;

            lower.FindTex(sd->tex3, ld);

            if (lower.img && (ld->flags & ML_LOWER_UNPEGGED))
                lower.tex_h = lower.h2 + lower.img->height();
            else
                lower.tex_h = lower.h1;
        }
        else
        {
            // TWO-sided line

            if (back->ceilh < front->ceilh && !sky_upper)
            {
                upper.kind = DrawSurf::K_TEXTURE;
                upper.h1 = front->ceilh;
                upper.h2 = back->ceilh;
                upper.tex_h = upper.h1;
                upper.y_clip = DrawSurf::SOLID_ABOVE;

                upper.FindTex(sd->tex1, ld);

                if (upper.img && !(ld->flags & ML_UPPER_UNPEGGED))
                    upper.tex_h = upper.h2 + upper.img->height();
                else
                    upper.tex_h = upper.h1;
            }

            if (back->floorh > front->floorh)
            {
                lower.kind = DrawSurf::K_TEXTURE;
                lower.h1 = back->floorh;
                lower.h2 = front->floorh;
                lower.y_clip = DrawSurf::SOLID_BELOW;

                lower.FindTex(sd->tex2, ld);

                if (ld->flags & ML_LOWER_UNPEGGED)
                    lower.tex_h = front->ceilh;
                else
                    lower.tex_h = lower.h1;
            }
        }
    }
};


struct RendInfo
{
  public:
    DrawWall::vec_t walls;
    // complete set of walls/sprites to draw.

    DrawWall::vec_t active;
    // the active list.  Pointers here are always duplicates of ones in
    // the walls list (no need to `delete' any of them).

    std::vector < double >depth_x;
    // inverse distances over X range, 0 when empty.

    int open_y1;
    int open_y2;

    static const double Y_SLOPE = 1.70;

    static void DeleteWall(DrawWall * P)
    {
        delete P;
    }

     ~RendInfo()
    {
        std::for_each(walls.begin(), walls.end(), DeleteWall);

        walls.clear();
        active.clear();
    }

    void InitDepthBuf(int width)
    {
        depth_x.resize(width);

        std::fill_n(depth_x.begin(), width, 0);
    }

    static inline float PointToAngle(float x, float y)
    {
        if (-0.01 < x && x < 0.01)
            return (y > 0) ? HALFPI : (3 * HALFPI);

        float angle = atan2(y, x);

        if (angle < 0)
            angle += TWOPI;

        return angle;
    }

    static inline int AngleToX(float ang)
    {
        float t = tan(HALFPI - ang);

        int x = int (view.sw * t);

        x = (view.sw + x) / 2;

        if (x < 0)
            x = 0;
        else if (x > view.sw)
            x = view.sw;

        return x;
    }

    static inline float XToAngle(int x)
    {
        x = x * 2 - view.sw;

        float ang = HALFPI + atan(x / float (view.sw));

        if (ang < 0)
            ang = 0;
        else if (ang > ONEPI)
            ang = ONEPI;

        return ang;
    }

    static inline int DeltaToX(double iz, float tx)
    {
        int x = int (view.sw * tx * iz);

        x = (x + view.sw) / 2;

        return x;
    }

    static inline float XToDelta(int x, double iz)
    {
        x = x * 2 - view.sw;

        float tx = x / iz / view.sw;

        return tx;
    }

    static inline int DistToY(double iz, int sec_h)
    {
        if (sec_h > 32770)
            return -9999;

        if (sec_h < -32770)
            return +9999;

        sec_h -= view.z;

        int y = int (view.sh * sec_h * iz * Y_SLOPE);

        y = (view.sh - y) / 2;

        return y;
    }

    static inline float YToDist(int y, int sec_h)
    {
        sec_h -= view.z;

        y = y * 2 - view.sh;

        if (y == 0)
            return 999999;

        return view.sh * sec_h * Y_SLOPE / y;
    }

    static inline float YToSecH(int y, double iz)
    {
        y = y * 2 - view.sh;

        return view.z - (float (y) / view.sh / iz / Y_SLOPE);
    }

    void AddLine(int linenum)
    {
        LineDef *ld = LineDefs + linenum;

        if (!is_obj(ld->start) || !is_obj(ld->end))
            return;

        float x1 = Vertices[ld->start].x - view.x;
        float y1 = Vertices[ld->start].y - view.y;
        float x2 = Vertices[ld->end].x - view.x;
        float y2 = Vertices[ld->end].y - view.y;

        float tx1 = x1 * view.Sin - y1 * view.Cos;
        float ty1 = x1 * view.Cos + y1 * view.Sin;
        float tx2 = x2 * view.Sin - y2 * view.Cos;
        float ty2 = x2 * view.Cos + y2 * view.Sin;

        // reject line if complete behind viewplane
        if (ty1 <= 0 && ty2 <= 0)
            return;

        float angle1 = PointToAngle(tx1, ty1);
        float angle2 = PointToAngle(tx2, ty2);
        float span = angle1 - angle2;

        if (span < 0)
            span += TWOPI;

        int side = 0;
        SideDef *sd;

        if (span >= ONEPI)
            side = 1;

        // ignore the line when there is no facing sidedef
        if (!is_obj(side ? ld->sidedef2 : ld->sidedef1))
            return;

        sd = SideDefs + (side ? ld->sidedef2 : ld->sidedef1);

        if (!is_obj(sd->sector))
            return;

        if (side == 1)
        {
            float tmp = angle1;
            angle1 = angle2;
            angle2 = tmp;
        }

        // clip angles to view volume

        float base_ang = angle1;

        float leftclip = (3 * ONEPI / 4);
        float rightclip = ONEPI / 4;

        float tspan1 = angle1 - rightclip;
        float tspan2 = leftclip - angle2;

        if (tspan1 < 0)
            tspan1 += TWOPI;
        if (tspan2 < 0)
            tspan2 += TWOPI;

        if (tspan1 > HALFPI)
        {
            // Totally off the left edge?
            if (tspan2 >= ONEPI)
                return;

            angle1 = leftclip;
        }

        if (tspan2 > HALFPI)
        {
            // Totally off the left edge?
            if (tspan1 >= ONEPI)
                return;

            angle2 = rightclip;
        }

        // convert angles to on-screen X positions
        int sx1 = AngleToX(angle1);
        int sx2 = AngleToX(angle2) - 1;

        if (sx1 > sx2)
            return;

        // compute distance from eye to wall
        float wdx = x2 - x1;
        float wdy = y2 - y1;

        float wlen = sqrt(wdx * wdx + wdy * wdy);
        float dist = fabs((y1 * wdx / wlen) - (x1 * wdy / wlen));

        if (dist < 0.01)
            return;

        // compute normal of wall (translated coords)
        float normal;

        if (side == 1)
            normal = PointToAngle(ty2 - ty1, tx1 - tx2);
        else
            normal = PointToAngle(ty1 - ty2, tx2 - tx1);

        // compute inverse distances
        double iz1 = cos(normal - angle1) / dist / cos(HALFPI - angle1);
        double iz2 = cos(normal - angle2) / dist / cos(HALFPI - angle2);

        double diz = (iz2 - iz1) / y_max(1, sx2 - sx1);

        // create drawwall structure

        DrawWall *dw = new DrawWall;

        dw->th = 0;
        dw->ld = ld;
        dw->sd = sd;
        dw->sec = Sectors + sd->sector;

        dw->side = side;

        dw->base_ang = base_ang;
        dw->ang1 = angle1;
        dw->dang = (angle2 - angle1) / y_max(1, sx2 - sx1);

        dw->dist = dist;
        dw->normal = normal;
        dw->t_dist = tan(base_ang - normal) * dist;

        dw->iz1 = iz1;
        dw->diz = diz;
        dw->mid_iz = iz1 + (sx2 - sx1 + 1) * diz / 2;

        dw->sx1 = sx1;
        dw->sx2 = sx2;

        walls.push_back(dw);
    }

    void AddThing(int thingnum)
    {
        Thing *th = Things + thingnum;

        float x = th->xpos - view.x;
        float y = th->ypos - view.y;

        float tx = x * view.Sin - y * view.Cos;
        float ty = x * view.Cos + y * view.Sin;

        // reject sprite if complete behind viewplane
        if (ty < 4)
            return;

        Img *sprite = view.im_ch->GetSprite(th->type);
        if (!sprite)
            return;

        float tx1 = tx - sprite->width() / 2.0;
        float tx2 = tx + sprite->width() / 2.0;

        double iz = 1 / ty;

        int sx1 = DeltaToX(iz, tx1);
        int sx2 = DeltaToX(iz, tx2) - 1;

        if (sx1 < 0)
            sx1 = 0;

        if (sx2 >= view.sw)
            sx2 = view.sw - 1;

        if (sx1 > sx2)
            return;

        int h2 = view.thing_floors[thingnum];
        int h1 = h2 + sprite->height();

        // create drawwall structure

        DrawWall *dw = new DrawWall;

        dw->th = th;
        dw->ld = 0;
        dw->sd = 0;
        dw->sec = 0;

        dw->spr_tx1 = tx1;

        dw->ang1 = dw->dang = 0;

        dw->iz1 = dw->mid_iz = iz;
        dw->diz = 0;

        dw->sx1 = sx1;
        dw->sx2 = sx2;

        dw->ceil.img = sprite;
        dw->ceil.h1 = h1;
        dw->ceil.h2 = h2;

        walls.push_back(dw);
    }

    void ComputeSurfaces()
    {
        DrawWall::vec_t::iterator S;

        for (S = walls.begin(); S != walls.end(); S++)
            if ((*S)->ld)
                (*S)->ComputeWallSurface();
    }

    void ClipSolids()
    {
        // perform a rough depth sort of the walls and sprites.

        std::sort(walls.begin(), walls.end(), DrawWall::MidDistCmp());

        // go forwards, from closest to furthest away

        DrawWall::vec_t::iterator S;

        for (S = walls.begin(); S != walls.end(); S++)
        {
            DrawWall *dw = (*S);

            if (!dw)
                continue;

            int one_sided = dw->ld && !is_obj(dw->ld->sidedef2);
            int vis_count = dw->sx2 - dw->sx1 + 1;

            for (int x = dw->sx1; x <= dw->sx2; x++)
            {
                double iz = dw->iz1 + (dw->diz * (x - dw->sx1));

                if (iz < depth_x[x])
                    vis_count--;
                else if (one_sided)
                    depth_x[x] = iz;
            }

            if (vis_count == 0)
            {
                delete dw;
                (*S) = 0;
            }
        }

        // remove null pointers

        S = std::remove(walls.begin(), walls.end(), (DrawWall *) 0);

        walls.erase(S, walls.end());
    }

    void RenderFlatColumn(DrawWall * dw, DrawSurf & surf,
                          int x, int y1, int y2)
    {
        img_pixel_t *buf = view.screen->wbuf();
        img_pixel_t *wbuf = surf.img->wbuf();

        int tw = surf.img->width();
        int th = surf.img->height();

        float ang = XToAngle(x);
        float modv = cos(ang - HALFPI);

        float t_cos = cos(ONEPI + -view.angle + ang) / modv;
        float t_sin = sin(ONEPI + -view.angle + ang) / modv;

        buf += x + y1 * view.sw;

        for (; y1 <= y2; y1++, buf += view.sw)
        {
            float dist = YToDist(y1, surf.tex_h);

            int tx = int (view.x + t_sin * dist) & (tw - 1);
            int ty = int (-view.y - t_cos * dist) & (th - 1);

            *buf = wbuf[ty * tw + tx];
        }
    }

    void RenderTexColumn(DrawWall * dw, DrawSurf & surf,
                         int x, int y1, int y2)
    {
        img_pixel_t *buf = view.screen->wbuf();
        img_pixel_t *wbuf = surf.img->wbuf();

        int tw = surf.img->width();
        int th = surf.img->height();

        // compute texture X coord

        int tx = int (dw->t_dist - tan(dw->cur_ang - dw->normal) * dw->dist);

        tx = (dw->sd->xoff + tx) & (tw - 1);

        // compute texture Y coords

        float base_h = surf.tex_h + dw->sd->yoff;

        float h1 = base_h - YToSecH(y1, dw->cur_iz);
        float dh = base_h - YToSecH(y2, dw->cur_iz);

        dh = (dh - h1) / y_max(1, y2 - y1);

        buf += x + y1 * view.sw;
        wbuf += tx;

        for (; y1 <= y2; y1++, h1 += dh, buf += view.sw)
        {
            int ty = int (h1) % th;

            // handle negative values (use % twice)
            ty = (ty + th) % th;

            *buf = wbuf[ty * tw];
        }
    }

    void RenderSolidColumn(DrawWall * w, DrawSurf & surf,
                           int x, int y1, int y2)
    {
        img_pixel_t *buf = view.screen->wbuf();

        buf += x + y1 * view.sw;

        for (; y1 <= y2; y1++, buf += view.sw)
        {
            *buf = surf.col;
        }
    }

    inline void RenderWallSurface(DrawWall * dw, DrawSurf & surf, int x)
    {
        if (surf.kind == DrawSurf::K_INVIS)
            return;

        int y1 = DistToY(dw->cur_iz, surf.h1);
        int y2 = DistToY(dw->cur_iz, surf.h2) - 1;

        if (y1 < open_y1)
            y1 = open_y1;

        if (y2 > open_y2)
            y2 = open_y2;

        if (y1 > y2)
            return;

        // clip the open region

        if (surf.y_clip & DrawSurf::SOLID_ABOVE)
            if (y2 > open_y1)
                open_y1 = y2;

        if (surf.y_clip & DrawSurf::SOLID_BELOW)
            if (y1 < open_y2)
                open_y2 = y1;

        // fill pixels

        if (!surf.img)
        {
            RenderSolidColumn(dw, surf, x, y1, y2);
        }
        else
            switch (surf.kind)
            {
            case DrawSurf::K_FLAT:
                RenderFlatColumn(dw, surf, x, y1, y2);
                break;

            case DrawSurf::K_TEXTURE:
                RenderTexColumn(dw, surf, x, y1, y2);
                break;
            }
    }

    inline void RenderSprite(DrawWall * dw, int x)
    {
        int y1 = DistToY(dw->cur_iz, dw->ceil.h1);
        int y2 = DistToY(dw->cur_iz, dw->ceil.h2) - 1;

        if (y1 < dw->oy1)
            y1 = dw->oy1;

        if (y2 > dw->oy2)
            y2 = dw->oy2;

        if (y1 > y2)
            return;

        // fill pixels

        img_pixel_t *buf = view.screen->wbuf();
        img_pixel_t *wbuf = dw->ceil.img->wbuf();

        int tw = dw->ceil.img->width();
        int th = dw->ceil.img->height();

        int tx = int (XToDelta(x, dw->cur_iz) - dw->spr_tx1);

        if (tx < 0 || tx >= tw)
            return;

        float h1 = dw->ceil.h1 - YToSecH(y1, dw->cur_iz);
        float dh = dw->ceil.h1 - YToSecH(y2, dw->cur_iz);

        dh = (dh - h1) / y_max(1, y2 - y1);

        buf += x + y1 * view.sw;
        wbuf += tx;

        for (; y1 <= y2; y1++, h1 += dh, buf += view.sw)
        {
            int ty = int (h1);

            if (ty < 0 || ty >= th)
                continue;

            img_pixel_t pix = wbuf[ty * tw];

            if (pix != IMG_TRANSP)
                *buf = pix;
        }
    }

    void UpdateActiveList(int x)
    {
        DrawWall::vec_t::iterator S, E, P;

        bool changes = false;

        // remove walls that have finished.

        S = active.begin();
        E = active.end();

        S = std::remove_if(S, E, DrawWall::SX2Less(x));

        if (S != E)
        {
            active.erase(S, E);
            changes = true;
        }

        // add new walls that start in this column.

        S = walls.begin();
        E = walls.end();

        S = std::lower_bound(S, E, x, DrawWall::SX1Cmp());
        E = std::upper_bound(S, E, x, DrawWall::SX1Cmp());

        if (S != E)
            changes = true;

        for (; S != E; S++)
        {
            active.push_back(*S);
        }

        // calculate new depth values

        S = active.begin();
        E = active.end();

        for (P = S; (P != E); P++)
        {
            DrawWall *dw = (*P);

            dw->cur_iz = dw->iz1 + dw->diz * (x - dw->sx1);

            if (P != S && (*(P - 1))->cur_iz < dw->cur_iz)
                changes = true;

            dw->cur_ang = dw->ang1 + dw->dang * (x - dw->sx1);
        }

        // if there are changes, re-sort the active list...

        if (changes)
        {
            std::sort(active.begin(), active.end(), DrawWall::DistCmp());
        }
    }

    void RenderWalls()
    {
        // sort walls by their starting column, to allow binary search.

        std::sort(walls.begin(), walls.end(), DrawWall::SX1Cmp());

        active.clear();

        for (int x = 0; x < view.sw; x++)
        {
            // clear vertical depth buffer

            open_y1 = 0;
            open_y2 = view.sh - 1;

            UpdateActiveList(x);

            // render, front to back

            DrawWall::vec_t::iterator S, E, P;

            S = active.begin();
            E = active.end();

            for (P = S; P != E; P++)
            {
                DrawWall *dw = (*P);

                // for things, just remember the open space
                if (dw->th)
                {
                    dw->oy1 = open_y1;
                    dw->oy2 = open_y2;
                    continue;
                }

                RenderWallSurface(dw, dw->ceil, x);
                RenderWallSurface(dw, dw->floor, x);
                RenderWallSurface(dw, dw->upper, x);
                RenderWallSurface(dw, dw->lower, x);

                if (open_y1 >= open_y2)
                    break;
            }

            // now render things, back to front

            if (P == E)
                P--;

            for (; P != (S - 1); P--)
            {
                DrawWall *dw = (*P);

                if (dw->th)
                    RenderSprite(dw, x);
            }
        }
    }

    void DoRender3D()
    {
        view.ClearScreen();

        InitDepthBuf(view.sw);

        for (int i = 0; i < NumLineDefs; i++)
            AddLine(i);

        if (view.sprites)
            for (int j = 0; j < NumThings; j++)
                AddThing(j);

        ClipSolids();
        ComputeSurfaces();
        RenderWalls();
    }
};


static Thing *FindPlayer(int typenum)
{
    for (int i = 0; i < NumThings; i++)
        if (Things[i].type == typenum)
            return Things + i;

    return 0;
}


/*
 *  Render a 3D view from the player's position. 
 */

void Render3D()
{
    if (!view.p_type)
    {
        view.p_type = THING_PLAYER1;
        view.px = 99999;
    }

    Thing *player = FindPlayer(view.p_type);

    if (!player)
    {
        if (view.p_type != THING_DEATHMATCH)
            view.p_type = THING_DEATHMATCH;

        player = FindPlayer(view.p_type);

        if (!player)
            return;
    }

    if (view.px != player->xpos || view.py != player->ypos)
    {
        // if player moved, re-create view parameters

        view.x = view.px = player->xpos;
        view.y = view.py = player->ypos;

        view.CalcViewZ();
        view.SetAngle(player->angle * ONEPI / 180.0);
    }

// create image

    view.sw = 640;
    view.sh = 400;

    view.screen =
        new Img((unsigned short int) view.sw, (unsigned short int) view.sh,
                false);
    view.im_ch = new ImageCache;

    view.FindThingFloors();

    bool Redraw = true;

// input loop

    for (;;)
    {
        // render image

        if (Redraw)
        {
            if (view.walking)
                view.CalcViewZ();

            RendInfo rend;

            rend.DoRender3D();

            view.PutScreen(40, 40);

            Redraw = false;
        }

        // handle keypress

        int key = get_key();

        if (key == YK_ESC || key == 'q')
            break;

        if ((key & ~YK_SHIFT) == YK_LEFT)
        {
            view.SetAngle(view.angle + ONEPI / ((key & YK_SHIFT) ? 4 : 8));
            Redraw = true;
        }
        else if ((key & ~YK_SHIFT) == YK_RIGHT)
        {
            view.SetAngle(view.angle - ONEPI / ((key & YK_SHIFT) ? 4 : 8));
            Redraw = true;
        }
        else if ((key & ~YK_SHIFT) == YK_UP)
        {
            view.x += view.Cos * ((key & YK_SHIFT) ? 192 : 32);
            view.y += view.Sin * ((key & YK_SHIFT) ? 192 : 32);
            Redraw = true;
        }
        else if ((key & ~YK_SHIFT) == YK_DOWN)
        {
            view.x -= view.Cos * ((key & YK_SHIFT) ? 192 : 32);
            view.y -= view.Sin * ((key & YK_SHIFT) ? 192 : 32);
            Redraw = true;
        }
        else if (key == 'n' || key == 'N')
        {
            view.x -= view.Sin * ((key == 'N') ? 192 : 32);
            view.y += view.Cos * ((key == 'N') ? 192 : 32);
            Redraw = true;
        }
        else if (key == 'm' || key == 'M')
        {
            view.x += view.Sin * ((key == 'M') ? 192 : 32);
            view.y -= view.Cos * ((key == 'M') ? 192 : 32);
            Redraw = true;
        }
        else if (key == 'd' || key == 'D')
        {
            view.z += (key == 'D') ? 128 : 32;
            Redraw = true;
        }
        else if (key == 'c' || key == 'C')
        {
            view.z -= (key == 'C') ? 128 : 32;
            Redraw = true;
        }
        else if (key == 't')
        {
            view.texturing = !view.texturing;
            Redraw = true;
        }
        else if (key == 's')
        {
            view.sprites = !view.sprites;
            Redraw = true;
        }
        else if (key == 'w')
        {
            view.walking = !view.walking;
            Redraw = true;
        }
        else if (key)
        {
            // key no good, get another one
            Beep();
        }
    }

// all done

    delete view.screen;
    view.screen = 0;

    delete view.im_ch;
    view.im_ch = 0;

    delete[]view.thing_floors;
    view.thing_floors = 0;
}