/*
 * Copyright (C) 1996-1997 Id Software, Inc.
 *
 * 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.
 *
 */
// r_bsp.c

#include "quakedef.h"
#include "r_local.h"

// current entity info
qboolean      insubmodel;
entity_t     *currententity;
vec3_t        modelorg, base_modelorg; // modelorg is the viewpoint reletive to
                                                // the currently rendering
                                                // entity
vec3_t        r_entorigin;             // the currently rendering entity in
                                       // world
                                        // coordinates

float         entity_rotation[3][3];

int           r_currentbkey;

typedef enum {
    touchessolid, drawnode, nodrawnode
} solidstate_t;

#define MAX_BMODEL_VERTS	500    // 6K
#define MAX_BMODEL_EDGES	1000   // 12K

static mvertex_t *pbverts;
static bedge_t *pbedges;
static int    numbverts, numbedges;

static mvertex_t *pfrontenter, *pfrontexit;

static qboolean makeclippededge;

// 

/*
 * R_EntityRotate
 */
void R_EntityRotate (vec3_t vec) {
    vec3_t        tvec;

    VectorCopy (vec, tvec);
    vec[0] = DotProduct (entity_rotation[0], tvec);
    vec[1] = DotProduct (entity_rotation[1], tvec);
    vec[2] = DotProduct (entity_rotation[2], tvec);
}

/*
 * R_RotateBmodel
 */
void R_RotateBmodel (void) {
    float         angle, s, c, temp1[3][3], temp2[3][3], temp3[3][3];

    // TODO: should use a look-up table
    // TODO: should really be stored with the entity instead of being
    // reconstructed
    // TODO: could cache lazily, stored in the entity
    // TODO: share work with R_SetUpAliasTransform

    // yaw
    angle = currententity->angles[YAW];
    angle = DEG2RAD (angle);
    if (angle) {
        s = sin (angle);
        c = cos (angle);
    } else {
        s = 0;
        c = 1;
    }

    temp1[0][0] = c;
    temp1[0][1] = s;
    temp1[0][2] = 0;
    temp1[1][0] = -s;
    temp1[1][1] = c;
    temp1[1][2] = 0;
    temp1[2][0] = 0;
    temp1[2][1] = 0;
    temp1[2][2] = 1;

    // pitch
    angle = currententity->angles[PITCH];
    angle = DEG2RAD (angle);
    if (angle) {
        s = sin (angle);
        c = cos (angle);
    } else {
        s = 0;
        c = 1;
    }

    temp2[0][0] = c;
    temp2[0][1] = 0;
    temp2[0][2] = -s;
    temp2[1][0] = 0;
    temp2[1][1] = 1;
    temp2[1][2] = 0;
    temp2[2][0] = s;
    temp2[2][1] = 0;
    temp2[2][2] = c;

    R_ConcatRotations (temp2, temp1, temp3);

    // roll
    angle = currententity->angles[ROLL];
    angle = DEG2RAD (angle);
    if (angle) {
        s = sin (angle);
        c = cos (angle);
    } else {
        s = 0;
        c = 1;
    }

    temp1[0][0] = 1;
    temp1[0][1] = 0;
    temp1[0][2] = 0;
    temp1[1][0] = 0;
    temp1[1][1] = c;
    temp1[1][2] = s;
    temp1[2][0] = 0;
    temp1[2][1] = -s;
    temp1[2][2] = c;

    R_ConcatRotations (temp1, temp3, entity_rotation);

    // rotate modelorg and the transformation matrix
    R_EntityRotate (modelorg);
    R_EntityRotate (vpn);
    R_EntityRotate (vright);
    R_EntityRotate (vup);

    R_TransformFrustum ();
}

/*
 * R_RecursiveClipBPoly
 */
void R_RecursiveClipBPoly (bedge_t * pedges, mnode_t * pnode, msurface_t * psurf) {
    bedge_t      *psideedges[2], *pnextedge, *ptedge;
    int           i, side, lastside;
    float         dist, frac, lastdist;
    mplane_t     *splitplane, tplane;
    mvertex_t    *pvert, *plastvert, *ptvert;
    mnode_t      *pn;

    psideedges[0] = psideedges[1] = NULL;

    makeclippededge = false;

    // transform the BSP plane into model space
    // FIXME: cache these?
    splitplane = pnode->plane;
    tplane.dist = -PlaneDiff (r_entorigin, splitplane);
    tplane.normal[0] = PlaneDist (entity_rotation[0], splitplane);
    tplane.normal[1] = PlaneDist (entity_rotation[1], splitplane);
    tplane.normal[2] = PlaneDist (entity_rotation[2], splitplane);

    // clip edges to BSP plane
    for (; pedges; pedges = pnextedge) {
        pnextedge = pedges->pnext;

        // set the status for the last point as the previous point
        // FIXME: cache this stuff somehow?
        plastvert = pedges->v[0];

        lastdist = DotProduct (plastvert->position, tplane.normal) - tplane.dist;
        lastside = (lastdist <= 0);

        pvert = pedges->v[1];

        dist = DotProduct (pvert->position, tplane.normal) - tplane.dist;
        side = (dist <= 0);

        if (side != lastside) {
            // clipped
            if (numbverts >= MAX_BMODEL_VERTS)
                return;

            // generate the clipped vertex
            frac = lastdist / (lastdist - dist);
            ptvert = &pbverts[numbverts++];
            ptvert->position[0] = plastvert->position[0] + frac * (pvert->position[0] - plastvert->position[0]);
            ptvert->position[1] = plastvert->position[1] + frac * (pvert->position[1] - plastvert->position[1]);
            ptvert->position[2] = plastvert->position[2] + frac * (pvert->position[2] - plastvert->position[2]);

            // split into two edges, one on each side, and remember entering
            // and exiting points
            // FIXME: share the clip edge by having a winding direction flag?
            if (numbedges >= (MAX_BMODEL_EDGES - 1)) {
                Con_Printf ("Out of edges for bmodel\n");
                return;
            }

            ptedge = &pbedges[numbedges];
            ptedge->pnext = psideedges[lastside];
            psideedges[lastside] = ptedge;
            ptedge->v[0] = plastvert;
            ptedge->v[1] = ptvert;

            ptedge = &pbedges[numbedges + 1];
            ptedge->pnext = psideedges[side];
            psideedges[side] = ptedge;
            ptedge->v[0] = ptvert;
            ptedge->v[1] = pvert;

            numbedges += 2;

            if (side == 0) {
                // entering for front, exiting for back
                pfrontenter = ptvert;
                makeclippededge = true;
            } else {
                pfrontexit = ptvert;
                makeclippededge = true;
            }
        } else {
            // add the edge to the appropriate side
            pedges->pnext = psideedges[side];
            psideedges[side] = pedges;
        }
    }

    // if anything was clipped, reconstitute and add the edges along the clip
    // plane to both sides (but in opposite directions)
    if (makeclippededge) {
        if (numbedges >= (MAX_BMODEL_EDGES - 2)) {
            Con_Printf ("Out of edges for bmodel\n");
            return;
        }

        ptedge = &pbedges[numbedges];
        ptedge->pnext = psideedges[0];
        psideedges[0] = ptedge;
        ptedge->v[0] = pfrontexit;
        ptedge->v[1] = pfrontenter;

        ptedge = &pbedges[numbedges + 1];
        ptedge->pnext = psideedges[1];
        psideedges[1] = ptedge;
        ptedge->v[0] = pfrontenter;
        ptedge->v[1] = pfrontexit;

        numbedges += 2;
    }
    // draw or recurse further
    for (i = 0; i < 2; i++) {
        if (psideedges[i]) {
            // draw if we've reached a non-solid leaf, done if all that's left
            // is a
            // solid leaf, and continue down the tree if it's not a leaf
            pn = pnode->children[i];

            // we're done with this branch if the node or leaf isn't in the PVS
            if (pn->visframe == r_visframecount) {
                if (pn->contents < 0) {
                    if (pn->contents != CONTENTS_SOLID) {
                        r_currentbkey = ((mleaf_t *) pn)->key;
                        R_RenderBmodelFace (psideedges[i], psurf);
                    }
                } else {
                    R_RecursiveClipBPoly (psideedges[i], pnode->children[i], psurf);
                }
            }
        }
    }
}

/*
 * R_DrawSolidClippedSubmodelPolygons
 */
void R_DrawSolidClippedSubmodelPolygons (model_t * pmodel) {
    int           i, j, lindex, numsurfaces;
    float         dot;
    msurface_t   *psurf;
    mplane_t     *pplane;
    mvertex_t     bverts[MAX_BMODEL_VERTS];
    bedge_t       bedges[MAX_BMODEL_EDGES], *pbedge;
    medge_t      *pedge, *pedges;

    // FIXME: use bounding-box-based frustum clipping info?

    psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
    numsurfaces = pmodel->nummodelsurfaces;
    pedges = pmodel->edges;

    for (i = 0; i < numsurfaces; i++, psurf++) {
        // find which side of the node we are on
        pplane = psurf->plane;

        dot = PlaneDiff (modelorg, pplane);

        // draw the polygon
        if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON))
            || (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
            // FIXME: use bounding-box-based frustum clipping info?

            // copy the edges to bedges, flipping if necessary so always
            // clockwise winding
            // FIXME: if edges and vertices get caches, these assignments must
            // move
            // outside the loop, and overflow checking must be done here
            pbverts = bverts;
            pbedges = bedges;
            numbverts = numbedges = 0;

            if (psurf->numedges > 0) {
                pbedge = &bedges[numbedges];
                numbedges += psurf->numedges;

                for (j = 0; j < psurf->numedges; j++) {
                    lindex = pmodel->surfedges[psurf->firstedge + j];

                    if (lindex > 0) {
                        pedge = &pedges[lindex];
                        pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]];
                        pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[1]];
                    } else {
                        lindex = -lindex;
                        pedge = &pedges[lindex];
                        pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[1]];
                        pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]];
                    }

                    pbedge[j].pnext = &pbedge[j + 1];
                }

                pbedge[j - 1].pnext = NULL;     // mark end of edges

                R_RecursiveClipBPoly (pbedge, currententity->topnode, psurf);
            } else {
                Sys_Error ("no edges in bmodel");
            }
        }
    }
}

/*
 * R_DrawSubmodelPolygons
 */
void R_DrawSubmodelPolygons (model_t * pmodel, int clipflags) {
    int           i, numsurfaces;
    float         dot;
    msurface_t   *psurf;
    mplane_t     *pplane;

    // FIXME: use bounding-box-based frustum clipping info?

    psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
    numsurfaces = pmodel->nummodelsurfaces;

    for (i = 0; i < numsurfaces; i++, psurf++) {
        // find which side of the node we are on
        pplane = psurf->plane;

        dot = PlaneDiff (modelorg, pplane);

        // draw the polygon
        if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON))
            || (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
            r_currentkey = ((mleaf_t *) currententity->topnode)->key;

            // FIXME: use bounding-box-based frustum clipping info?
            R_RenderFace (psurf, clipflags);
        }
    }
}

/*
 * R_RecursiveWorldNode
 */
void R_RecursiveWorldNode (mnode_t * node, int clipflags) {
    int           i, c, side, *pindex;
    vec3_t        acceptpt, rejectpt;
    mplane_t     *plane;
    msurface_t   *surf, **mark;
    mleaf_t      *pleaf;
    double        d, dot;

    if (node->contents == CONTENTS_SOLID)
        return;                        // solid

    if (node->visframe != r_visframecount)
        return;

    // cull the clipping planes if not trivial accept
    // FIXME: the compiler is doing a lousy job of optimizing here; it could be
    // twice as fast in ASM
    if (clipflags) {
        for (i = 0; i < 4; i++) {
            if (!(clipflags & (1 << i)))
                continue;              // don't need to clip against it

            // generate accept and reject points
            // FIXME: do with fast look-ups or integer tests based on the sign
            // bit
            // of the floating point values

            pindex = pfrustum_indexes[i];

            rejectpt[0] = (float) node->minmaxs[pindex[0]];
            rejectpt[1] = (float) node->minmaxs[pindex[1]];
            rejectpt[2] = (float) node->minmaxs[pindex[2]];

            d = DotProduct (rejectpt, view_clipplanes[i].normal);
            d -= view_clipplanes[i].dist;

            if (d <= 0)
                return;

            acceptpt[0] = (float) node->minmaxs[pindex[3 + 0]];
            acceptpt[1] = (float) node->minmaxs[pindex[3 + 1]];
            acceptpt[2] = (float) node->minmaxs[pindex[3 + 2]];

            d = DotProduct (acceptpt, view_clipplanes[i].normal);
            d -= view_clipplanes[i].dist;

            if (d >= 0)
                clipflags &= ~(1 << i); // node is entirely on screen
        }
    }
    // if a leaf node, draw stuff
    if (node->contents < 0) {
        pleaf = (mleaf_t *) node;

        mark = pleaf->firstmarksurface;
        c = pleaf->nummarksurfaces;

        if (c) {
            do {
                (*mark)->visframe = r_framecount;
                mark++;
            }
            while (--c);
        }
        // deal with model fragments in this leaf
        if (pleaf->efrags)
            R_StoreEfrags (&pleaf->efrags);

        pleaf->key = r_currentkey;
        r_currentkey++;                // all bmodels in a leaf share the same
        // key
    } else {
        // node is just a decision point, so go down the apropriate sides

        // find which side of the node we are on
        plane = node->plane;
        dot = PlaneDiff (modelorg, plane);
        side = (dot < 0);

        // recurse down the children, front side first
        R_RecursiveWorldNode (node->children[side], clipflags);

        // draw stuff
        c = node->numsurfaces;

        if (c) {
            surf = cl.worldmodel->surfaces + node->firstsurface;
            if (dot < -BACKFACE_EPSILON) {
                do {
                    if ((surf->flags & SURF_PLANEBACK) && (surf->visframe == r_framecount))
                        R_RenderFace (surf, clipflags);

                    surf++;
                }
                while (--c);
            } else if (dot > BACKFACE_EPSILON) {
                do {
                    if (!(surf->flags & SURF_PLANEBACK) && (surf->visframe == r_framecount))
                        R_RenderFace (surf, clipflags);

                    surf++;
                }
                while (--c);
            }
            // all surfaces on the same node share the same sequence number
            r_currentkey++;
        }
        // recurse down the back side
        R_RecursiveWorldNode (node->children[!side], clipflags);
    }
}

/*
 * R_RenderWorld
 */
void R_RenderWorld (void) {
    model_t      *clmodel;

    currententity = &r_worldentity;
    VectorCopy (r_origin, modelorg);
    clmodel = currententity->model;
    r_pcurrentvertbase = clmodel->vertexes;

    R_RecursiveWorldNode (clmodel->nodes, 15);
}
