/***************************************************************************/
/*                                                                         */
/*                                                                         */
/* Raven 3D Engine                                                         */
/* Copyright (C) 1996 by Softdisk Publishing                               */
/*                                                                         */
/* Original Design:                                                        */
/*  John Carmack of id Software                                            */
/*                                                                         */
/* Enhancements by:                                                        */
/*  Robert Morgan of Channel 7............................Main Engine Code */
/*  Todd Lewis of Softdisk Publishing......Tools,Utilities,Special Effects */
/*  John Bianca of Softdisk Publishing..............Low-level Optimization */
/*  Carlos Hasan..........................................Music/Sound Code */
/*                                                                         */
/*                                                                         */
/***************************************************************************/

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "d_global.h"
#include "r_refdef.h"
#include "d_misc.h"
#include "d_ints.h"

/**** VARIABLES ****/

#define MAXENTRIES 1024

byte westwall[MAPROWS * MAPCOLS];
byte westflags[MAPROWS * MAPCOLS];
byte northwall[MAPROWS * MAPCOLS];
byte northflags[MAPROWS * MAPCOLS];
byte floorpic[MAPROWS * MAPCOLS];
byte floorflags[MAPROWS * MAPCOLS];
byte ceilingpic[MAPROWS * MAPCOLS];
byte ceilingflags[MAPROWS * MAPCOLS];
byte floorheight[MAPROWS * MAPCOLS];
byte ceilingheight[MAPROWS * MAPCOLS];
byte floordef[MAPROWS * MAPCOLS];
byte floordefflags[MAPROWS * MAPCOLS];
byte ceilingdef[MAPROWS * MAPCOLS];
byte ceilingdefflags[MAPROWS * MAPCOLS];
byte maplights[MAPROWS * MAPCOLS];
byte mapsprites[MAPROWS * MAPCOLS];
byte mapslopes[MAPROWS * MAPCOLS];
byte mapeffects[MAPROWS * MAPCOLS];
byte mapflags[MAPROWS * MAPCOLS];
int  reallight[MAPROWS * MAPCOLS];
int  actionflag;
int  wallglow, wallglowindex;
int  wallrotate;
int  maplight;
int  wallflicker1, wallflicker2, wallflicker3, wallflicker4, wallflags, wallcycle;

// each visible vertex is used up to four times, so to prevent recalculation
// the vertex info is reused if it has been calculated previously that f
// The calculated flag is also used to determine if a moving sprite is i
// is at least partially visable.
//
// frameon is incremented at the start of each frame, so it is 1 on the
// framevalid[][] holds the frameon number for which vertex[][] is valid
//      set to 0 at initialization, so no points are valid
// cornervertex[][] is a pointer into vertexlist[]
// vertexlist[] holds the currently valid transformed vertexes
// vertexlist_p is set to vertexlist[0] at the start of each frame, and
//      after transforming a new vertex

int        frameon;
int        framevalid[MAPROWS * MAPCOLS];
int        framech[MAPROWS * MAPCOLS];
int        framefl[MAPROWS * MAPCOLS];
vertex_t*  cornervertex[MAPROWS * MAPCOLS];
vertex_t   vertexlist[MAXVISVERTEXES], *vertexlist_p;
fixed_t    costable[ANGLES + 1];
fixed_t    sintable[ANGLES + 1];
pixel_t    viewbuffer[MAX_VIEW_WIDTH * MAX_VIEW_HEIGHT];
pixel_t*   viewylookup[MAX_VIEW_HEIGHT];
fixed_t    yslope[MAX_VIEW_HEIGHT + MAXSCROLL2], xslope[MAX_VIEW_WIDTH + 1];
byte**     wallposts;
byte*      colormaps;
int        numcolormaps;
byte*      zcolormap[(MAXZ >> FRACBITS) + 1];
fixed_t    viewx, viewy, viewz;
fixed_t    viewcos, viewsin;
fixed_t    xscale, yscale;  // SCALE/viewcos , SCALE/viewsin
int        viewangle, viewfineangle;
int        viewtilex, viewtiley;
vertex_t*  vertex[4];  // points to the for corner vertexes in vert
vertex_t * p1, *p2;
int        side;             // wall number 0-3
int        walltype;         // wall number (picture) of p1-p2 edge
int        wallshadow;       // degree of shadow for a tile
int        xclipl, xcliph;   // clip window for current tile
int        tilex, tiley;     // coordinates of the tile being rendered
int        mapspot;          // tiley*MAPSIZE+tilex
int*       flattranslation;  // global animation tables
int*       walltranslation;
int        spritelump, walllump, flatlump;
int        numsprites, numwalls, numflats;
boolean    doortile;  // true if the tile being renderd has a door
int        adjacentx[4] = { 0, 1, 0, -1 };
int        adjacenty[4] = { -1, 0, 1, 0 };
entry_t    entries[MAXENTRIES], *entry_p;
int        entrymap[MAPCOLS * MAPROWS], entrycount[MAPCOLS * MAPROWS];
int        entrycounter;
int        fxtimecount;
extern int rtimecount;

vertex_t* TransformVertex(int tilex, int tiley)
/* Returns a pointer to the vertex for a given coordinate
   tx,tz will be the transformed coordinates
   px, floorheight, ceilingheight will be valid if tz >= MINZ */
{
    fixed_t   trx, try, scale;
    vertex_t* point;
    int       mapspot2, fl, ch;

    mapspot2 = tiley * MAPROWS + tilex;
    if (mapspot != mapspot2)
    {
        if (mapflags[mapspot] & FL_FLOOR)
            fl = (floorheight[mapspot2] << FRACBITS) - viewz;
        else
            fl = ((floorheight[mapspot]) << FRACBITS) - viewz;
        if (mapflags[mapspot] & FL_CEILING)
            ch = (ceilingheight[mapspot2] << FRACBITS) - viewz;
        else
            ch = ((ceilingheight[mapspot]) << FRACBITS) - viewz;
    }
    else
    {
        fl = ((floorheight[mapspot2]) << FRACBITS) - viewz;
        ch = ((ceilingheight[mapspot2]) << FRACBITS) - viewz;
    }
    if (framevalid[mapspot2] == frameon && framefl[mapspot2] == fl && framech[mapspot2] == ch)
        return cornervertex[mapspot2];
    point = vertexlist_p++;
#ifdef VALIDATE
    if (point == &vertexlist[MAXVISVERTEXES])
        MS_Error("Vertexlist overflow (%i>=%i)", vertexlist_p - vertexlist, MAXVISVERTEXES);
#endif
    point->floorheight   = fl;
    point->ceilingheight = ch;
    trx                  = (tilex << (FRACBITS + TILESHIFT)) - viewx;
    try                  = (tiley << (FRACBITS + TILESHIFT)) - viewy;
    point->tx            = FIXEDMUL(trx, viewsin) + FIXEDMUL(try, viewcos);
    point->tz            = FIXEDMUL(trx, viewcos) - FIXEDMUL(try, viewsin);
    if (point->tz >= MINZ)
    {
        scale           = FIXEDDIV(SCALE, point->tz);
        point->px       = CENTERX + (FIXEDMUL(point->tx, scale) >> FRACBITS);
        point->floory   = CENTERY - (FIXEDMUL(point->floorheight, scale) >> FRACBITS);
        point->ceilingy = CENTERY - (FIXEDMUL(point->ceilingheight, scale) >> FRACBITS);
    }
    framevalid[mapspot2]   = frameon;
    cornervertex[mapspot2] = point;
    framefl[mapspot2]      = fl;
    framech[mapspot2]      = ch;
    return point;
}


boolean ClipEdge(void)
/* Sets p1->px and p2->px correctly for Z values < MINZ
   Returns false if entire edge is too close or far away */
{
    fixed_t leftfrac = 0, rightfrac, clipz, dx, dz;

    if (p1->tz > MAXZ && p2->tz > MAXZ)
        return false;  // entire face is too far away
    if (p1->tz <= 0 && p2->tz <= 0)
        return false;  // totally behind the projection plane
    if (p1->tz < MINZ || p2->tz < MINZ)
    {
        dx = p2->tx - p1->tx;
        dz = p2->tz - p1->tz;
        if (p1->tz < MINZ)
        {
            if (labs(dx + dz) < 1024)
                return false;
            leftfrac = FIXEDDIV(-p1->tx - p1->tz, dx + dz);
        }
        if (p2->tz < MINZ)
        {
            if (labs(dz - dx) < 1024)
                return false;
            rightfrac = FIXEDDIV(p1->tx - p1->tz, dz - dx);
            if (p1->tz < MINZ && rightfrac < leftfrac)
                return false;  // back face
            clipz = p1->tz + FIXEDMUL(dz, rightfrac);
            if (clipz < 0)
                return false;
            p2->px = windowWidth;
        }
    }
    if (p1->tz < MINZ)
    {
        clipz = p1->tz + FIXEDMUL(dz, leftfrac);
        if (clipz < 0)
            return false;
        p1->px = 0;
    }
    if (p1->px == p2->px)
        return false;
    return true;
}


void RenderTileWalls(entry_t* e)
{
    int xl, xh, tx, ty, x1, x2;

    tilex  = e->tilex;
    tiley  = e->tiley;
    xclipl = e->xmin;
    xcliph = e->xmax;
    // #ifdef VALIDATE
    //  if ((tilex<0)||(tilex>=MAPCOLS)||(tiley<0)||(tiley>=MAPROWS)||(xclipl<0)||
    //   (xclipl>=windowWidth)||(xcliph<0)||(xcliph>=windowWidth)||(xclipl>xcliph))
    //   MS_Error("Invalid RenderTile (%i, %i, %i, %i)\n", e->tilex, e->tiley,
    //   e->xmin, e->xmax);
    // #endif
    mapspot    = tiley * MAPCOLS + tilex;
    maplight   = ((int) maplights[mapspot] << 3) + reallight[mapspot];
    wallshadow = mapeffects[mapspot];
    // validate or transform the four corner vertexes
    vertex[0] = TransformVertex(tilex, tiley);
    vertex[1] = TransformVertex(tilex + 1, tiley);
    vertex[2] = TransformVertex(tilex + 1, tiley + 1);
    vertex[3] = TransformVertex(tilex, tiley + 1);
    // handle a door if present
    if (mapflags[mapspot] & FL_DOOR)
    {
        doortile = true;
        RenderDoor();  // sets doorxl / doorxh
    }
    else
        doortile = false;
    // draw or flow through the walls
    for (side = 0; side < 4; side++)
    {
        p1 = vertex[side];
        p2 = vertex[(side + 1) & 3];
        if (!ClipEdge())
            continue;
        if (p1->px >= p2->px)
            continue;
        switch (side)
        {
            case 0:  // north
                walltype  = northwall[mapspot];
                wallflags = northflags[mapspot];
                break;
            case 1:  // east
                walltype  = westwall[mapspot + 1];
                wallflags = westflags[mapspot + 1];
                break;
            case 2:  // south
                walltype  = northwall[mapspot + MAPCOLS];
                wallflags = northflags[mapspot + MAPCOLS];
                break;
            case 3:  // west
                walltype  = westwall[mapspot];
                wallflags = westflags[mapspot];
        }
        x1 = p1->px < xclipl ? xclipl : p1->px;
        x2 = p2->px - 1 > xcliph ? xcliph : p2->px - 1;
        if (x1 <= x2)
        {  // totally clipped off side
            if (walltype)
                DrawWall(x1, x2);
            DrawSteps(x1, x2);
        }
        if (walltype == 0 || (wallflags & F_TRANSPARENT))
        {
            // restrict outward flow by the door, if present
            xl = p1->px;
            xh = p2->px - 1;
            // restrict by clipping window
            if (xl < xclipl)
                xl = xclipl;
            if (xh > xcliph)
                xh = xcliph;
            // flow into the adjacent tile if there is at least a one pix
            if (xh >= xl)
            {
                tx = tilex + adjacentx[side];
                ty = tiley + adjacenty[side];
                if (tx < 0 || tx >= MAPCOLS - 1 || ty < 0 || ty >= MAPROWS - 1)
                    continue;
                entry_p->tilex   = tx;
                entry_p->tiley   = ty;
                entry_p->xmin    = xl;
                entry_p->xmax    = xh;
                entry_p->mapspot = (ty << 6) + tx;
                ++entrycounter;
                entry_p->counter             = entrycounter;
                entrycount[entry_p->mapspot] = entrycounter;
                ++entry_p;
#ifdef VALIDATE
                if (entry_p >= &entries[MAXENTRIES])
                    MS_Error("Entry Array OverFlow (%i>=%i)", entry_p - entries, MAXENTRIES);
#endif
            }
        }
    }
}


void SetupFrame(void)
{
    int i;

    memset(viewbuffer, 0, windowSize);

    /* Clears the wallz array, so posts that fade out into the distance won't block sprites */
    for (i = 0; i < windowWidth; i++)
        wallz[i] = MAXZ + 1;

    // reset span counters
    numspans         = 0;
    transparentposts = 0;
    ++frameon;
    vertexlist_p = vertexlist;  // put the first transformed vertex

    // special effects
    if (rtimecount > fxtimecount)
    {
        if (++wallglowindex == 32)
            wallglowindex = 0;
        if (wallglowindex < 16)
            wallglow = wallglowindex << 1;
        else
            wallglow = (32 - wallglowindex) << 1;
        if (wallrotate == 63)
            wallrotate = 0;
        else
            wallrotate++;
        wallflicker1 = (MS_RndT() & 63);
        wallflicker2 = (MS_RndT() & 63);
        wallflicker3 = (MS_RndT() & 63);
        if (frameon & 1)
            wallflicker4 = (MS_RndT() % 63) - 32;
        wallcycle++;
        wallcycle &= 3;
        fxtimecount = timecount + 5;
    }

    viewtilex     = viewx >> TILEFRACSHIFT;
    viewtiley     = viewy >> TILEFRACSHIFT;
    viewfineangle = viewangle << FINESHIFT;
    viewcos       = costable[viewangle];
    viewsin       = sintable[viewangle];
    xscale        = FIXEDDIV(viewsin, SCALE);
    yscale        = FIXEDDIV(viewcos, SCALE);
}


void FlowView()
{
    entry_t *process_p, *nextprocess_p;

    process_p          = entries;
    process_p->tilex   = viewtilex;
    process_p->tiley   = viewtiley;
    process_p->mapspot = (viewtiley << 6) + viewtilex;
    process_p->xmin    = 0;
    process_p->xmax    = windowWidth - 1;
    entry_p            = process_p + 1;
    memset(entrycount, 0, MAPCOLS * MAPROWS * 4);
    entrycounter = 1;
    while (process_p < entry_p)
    {
        if (process_p->mapspot == -1)  // entry has been merged
        {
            process_p++;
            continue;
        }

        /* check for mergeable entries */
        if (entrycount[process_p->mapspot] > process_p->counter)  // mergeable tile
            for (nextprocess_p = process_p + 1; nextprocess_p < entry_p;
                 nextprocess_p++)  // scan for mergeable entries
                if (nextprocess_p->mapspot == process_p->mapspot)
                {
                    if (nextprocess_p->xmin == process_p->xmax + 1)
                        process_p->xmax = nextprocess_p->xmax;
                    else if (nextprocess_p->xmax == process_p->xmin - 1)
                        process_p->xmin = nextprocess_p->xmin;
                    else  // bad merge!
                        MS_Error("Bad tile event combination:\n"
                                 " nextprocess_p=%p process_p=%p\n" //pvmk - p format
                                 " nextprocess_p->xmin=%d  nextprocess_p->xmax=%d\n"
                                 " process_p->xmin=%d  process_p->xmax=%d\n",
                                 (intptr_t) nextprocess_p, //pvmk - use intptr_t
                                 (intptr_t) process_p, //pvmk - use intptr_t
                                 nextprocess_p->xmin,
                                 nextprocess_p->xmax,
                                 process_p->xmin,
                                 process_p->xmax);
                    entrycount[nextprocess_p->mapspot] = 0;
                    nextprocess_p->mapspot             = -1;
                }

        /* check for a dublicate entry */
        if (entrymap[process_p->mapspot] == frameon)
            goto end;

        entrymap[process_p->mapspot] = frameon;
        RenderTileWalls(process_p);
        RenderTileEnds();
    end:
        process_p++;
    }
}
