/*
 * 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_sprite.c

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

static int    clip_current;
static vec5_t clip_verts[2][MAXWORKINGVERTS];
static int    sprite_width, sprite_height;

spritedesc_t  r_spritedesc;

/*
 * R_RotateSprite
 */
void R_RotateSprite (float beamlength) {
    vec3_t        vec;

    if (beamlength == 0.0)
        return;

    VectorScale (r_spritedesc.vpn, -beamlength, vec);
    VectorAdd (r_entorigin, vec, r_entorigin);
    VectorSubtract (modelorg, vec, modelorg);
}

/*
 * R_ClipSpriteFace
 *
 * Clips the winding at clip_verts[clip_current] and changes clip_current
 * Throws out the back side
 */
int R_ClipSpriteFace (int nump, clipplane_t * pclipplane) {
    int           i, outcount;
    float         dists[MAXWORKINGVERTS + 1];
    float         frac, clipdist, *pclipnormal, *in, *instep, *outstep, *vert2;

    clipdist = pclipplane->dist;
    pclipnormal = pclipplane->normal;

    // calc dists
    if (clip_current) {
        in = clip_verts[1][0];
        outstep = clip_verts[0][0];
        clip_current = 0;
    } else {
        in = clip_verts[0][0];
        outstep = clip_verts[1][0];
        clip_current = 1;
    }

    instep = in;
    for (i = 0; i < nump; i++, instep += sizeof (vec5_t) / sizeof (float)) {
        dists[i] = DotProduct (instep, pclipnormal) - clipdist;
    }

    // handle wraparound case
    dists[nump] = dists[0];
    memcpy (instep, in, sizeof (vec5_t));

    // clip the winding
    instep = in;
    outcount = 0;

    for (i = 0; i < nump; i++, instep += sizeof (vec5_t) / sizeof (float)) {
        if (dists[i] >= 0) {
            memcpy (outstep, instep, sizeof (vec5_t));
            outstep += sizeof (vec5_t) / sizeof (float);
            outcount++;
        }

        if (dists[i] == 0 || dists[i + 1] == 0)
            continue;

        if ((dists[i] > 0) == (dists[i + 1] > 0))
            continue;

        // split it into a new vertex
        frac = dists[i] / (dists[i] - dists[i + 1]);

        vert2 = instep + sizeof (vec5_t) / sizeof (float);

        outstep[0] = instep[0] + frac * (vert2[0] - instep[0]);
        outstep[1] = instep[1] + frac * (vert2[1] - instep[1]);
        outstep[2] = instep[2] + frac * (vert2[2] - instep[2]);
        outstep[3] = instep[3] + frac * (vert2[3] - instep[3]);
        outstep[4] = instep[4] + frac * (vert2[4] - instep[4]);

        outstep += sizeof (vec5_t) / sizeof (float);
        outcount++;
    }

    return outcount;
}

/*
 * R_SetupAndDrawSprite
 */
void R_SetupAndDrawSprite () {
    int           i, nump;
    float         dot, scale, *pv;
    vec5_t       *pverts;
    vec3_t        left, up, right, down, transformed, local;
    emitpoint_t   outverts[MAXWORKINGVERTS + 1], *pout;

    dot = DotProduct (r_spritedesc.vpn, modelorg);

    // backface cull
    if (dot >= 0)
        return;

    // build the sprite poster in worldspace
    VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->right, right);
    VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->up, up);
    VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->left, left);
    VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->down, down);

    pverts = clip_verts[0];

    pverts[0][0] = r_entorigin[0] + up[0] + left[0];
    pverts[0][1] = r_entorigin[1] + up[1] + left[1];
    pverts[0][2] = r_entorigin[2] + up[2] + left[2];
    pverts[0][3] = 0;
    pverts[0][4] = 0;

    pverts[1][0] = r_entorigin[0] + up[0] + right[0];
    pverts[1][1] = r_entorigin[1] + up[1] + right[1];
    pverts[1][2] = r_entorigin[2] + up[2] + right[2];
    pverts[1][3] = sprite_width;
    pverts[1][4] = 0;

    pverts[2][0] = r_entorigin[0] + down[0] + right[0];
    pverts[2][1] = r_entorigin[1] + down[1] + right[1];
    pverts[2][2] = r_entorigin[2] + down[2] + right[2];
    pverts[2][3] = sprite_width;
    pverts[2][4] = sprite_height;

    pverts[3][0] = r_entorigin[0] + down[0] + left[0];
    pverts[3][1] = r_entorigin[1] + down[1] + left[1];
    pverts[3][2] = r_entorigin[2] + down[2] + left[2];
    pverts[3][3] = 0;
    pverts[3][4] = sprite_height;

    // clip to the frustum in worldspace
    nump = 4;
    clip_current = 0;

    for (i = 0; i < 4; i++) {
        nump = R_ClipSpriteFace (nump, &view_clipplanes[i]);
        if (nump < 3)
            return;
        if (nump >= MAXWORKINGVERTS)
            Sys_Error ("R_SetupAndDrawSprite: too many points");
    }

    // transform vertices into viewspace and project
    pv = &clip_verts[clip_current][0][0];
    r_spritedesc.nearzi = -999999;

    for (i = 0; i < nump; i++) {
        VectorSubtract (pv, r_origin, local);
        TransformVector (local, transformed);

        if (transformed[2] < NEAR_CLIP)
            transformed[2] = NEAR_CLIP;

        pout = &outverts[i];
        pout->zi = 1.0 / transformed[2];
        if (pout->zi > r_spritedesc.nearzi)
            r_spritedesc.nearzi = pout->zi;

        pout->s = pv[3];
        pout->t = pv[4];

        scale = xscale * pout->zi;
        pout->u = (xcenter + scale * transformed[0]);

        scale = yscale * pout->zi;
        pout->v = (ycenter - scale * transformed[1]);

        pv += sizeof (vec5_t) / sizeof (*pv);
    }

    // draw it
    r_spritedesc.nump = nump;
    r_spritedesc.pverts = outverts;
    D_DrawSprite ();
}

/*
 * R_GetSpriteframe
 */
mspriteframe_t *R_GetSpriteframe (msprite_t * psprite) {
    mspritegroup_t *pspritegroup;
    mspriteframe_t *pspriteframe;
    int           i, numframes, frame;
    float        *pintervals, fullinterval, targettime, time;

    frame = currententity->frame;

    if ((frame >= psprite->numframes) || (frame < 0)) {
        Con_Printf ("R_DrawSprite: no such frame %d\n", frame);
        frame = 0;
    }

    if (psprite->frames[frame].type == SPR_SINGLE) {
        pspriteframe = psprite->frames[frame].frameptr;
    } else {
        pspritegroup = (mspritegroup_t *) psprite->frames[frame].frameptr;
        pintervals = pspritegroup->intervals;
        numframes = pspritegroup->numframes;
        fullinterval = pintervals[numframes - 1];

        time = cl.time + currententity->syncbase;

        // when loading in Mod_LoadSpriteGroup, we guaranteed all interval
        // values
        // are positive, so we don't have to worry about division by 0
        targettime = time - ((int) (time / fullinterval)) * fullinterval;

        for (i = 0; i < (numframes - 1); i++) {
            if (pintervals[i] > targettime)
                break;
        }

        pspriteframe = pspritegroup->frames[i];
    }

    return pspriteframe;
}

/*
 * R_DrawSprite
 */
void R_DrawSprite (void) {
    int           i;
    msprite_t    *psprite;
    vec3_t        tvec;
    float         dot, angle, sr, cr;

    psprite = currententity->model->cache.data;

    r_spritedesc.pspriteframe = R_GetSpriteframe (psprite);

    sprite_width = r_spritedesc.pspriteframe->width;
    sprite_height = r_spritedesc.pspriteframe->height;

    // TODO: make this caller-selectable
    if (psprite->type == SPR_FACING_UPRIGHT) {
        // generate the sprite's axes, with vup straight up in worldspace, and
        // r_spritedesc.vright perpendicular to modelorg.
        // This will not work if the view direction is very close to straight
        // up or
        // down, because the cross product will be between two nearly parallel
        // vectors and starts to approach an undefined state, so we don't draw
        // if
        // the two vectors are less than 1 degree apart
        tvec[0] = -modelorg[0];
        tvec[1] = -modelorg[1];
        tvec[2] = -modelorg[2];
        VectorNormalize (tvec);
        dot = tvec[2];                 // same as DotProduct (tvec,
        // r_spritedesc.vup) because
        // r_spritedesc.vup is 0, 0, 1
        if ((dot > 0.999848) || (dot < -0.999848))      // cos(1 degree) =
            // 0.999848
            return;
        r_spritedesc.vup[0] = 0;
        r_spritedesc.vup[1] = 0;
        r_spritedesc.vup[2] = 1;
        r_spritedesc.vright[0] = tvec[1];
        // CrossProduct(r_spritedesc.vup, -modelorg,
        r_spritedesc.vright[1] = -tvec[0];
        // r_spritedesc.vright)
        r_spritedesc.vright[2] = 0;
        VectorNormalize (r_spritedesc.vright);
        r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
        r_spritedesc.vpn[1] = r_spritedesc.vright[0];
        r_spritedesc.vpn[2] = 0;
        // CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
        // r_spritedesc.vpn)
    } else if (psprite->type == SPR_VP_PARALLEL) {
        // generate the sprite's axes, completely parallel to the viewplane.
        // There
        // are no problem situations, because the sprite is always in the same
        // position relative to the viewer
        for (i = 0; i < 3; i++) {
            r_spritedesc.vup[i] = vup[i];
            r_spritedesc.vright[i] = vright[i];
            r_spritedesc.vpn[i] = vpn[i];
        }
    } else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) {
        // generate the sprite's axes, with vup straight up in worldspace, and
        // r_spritedesc.vright parallel to the viewplane.
        // This will not work if the view direction is very close to straight
        // up or
        // down, because the cross product will be between two nearly parallel
        // vectors and starts to approach an undefined state, so we don't draw
        // if
        // the two vectors are less than 1 degree apart
        dot = vpn[2];                  // same as DotProduct (vpn,
        // r_spritedesc.vup) because
        // r_spritedesc.vup is 0, 0, 1
        if ((dot > 0.999848) || (dot < -0.999848))      // cos(1 degree) =
            // 0.999848
            return;
        r_spritedesc.vup[0] = 0;
        r_spritedesc.vup[1] = 0;
        r_spritedesc.vup[2] = 1;
        r_spritedesc.vright[0] = vpn[1];
        // CrossProduct (r_spritedesc.vup, vpn,
        r_spritedesc.vright[1] = -vpn[0];       // r_spritedesc.vright)
        r_spritedesc.vright[2] = 0;
        VectorNormalize (r_spritedesc.vright);
        r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
        r_spritedesc.vpn[1] = r_spritedesc.vright[0];
        r_spritedesc.vpn[2] = 0;
        // CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
        // r_spritedesc.vpn)
    } else if (psprite->type == SPR_ORIENTED) {
        // generate the sprite's axes, according to the sprite's world
        // orientation
        AngleVectors (currententity->angles, r_spritedesc.vpn, r_spritedesc.vright, r_spritedesc.vup);
    } else if (psprite->type == SPR_VP_PARALLEL_ORIENTED) {
        // generate the sprite's axes, parallel to the viewplane, but rotated
        // in
        // that plane around the center according to the sprite entity's roll
        // angle. So vpn stays the same, but vright and vup rotate
        angle = currententity->angles[ROLL] * (M_PI * 2 / 360);
        sr = sin (angle);
        cr = cos (angle);

        for (i = 0; i < 3; i++) {
            r_spritedesc.vpn[i] = vpn[i];
            r_spritedesc.vright[i] = vright[i] * cr + vup[i] * sr;
            r_spritedesc.vup[i] = vright[i] * -sr + vup[i] * cr;
        }
    } else {
        Sys_Error ("R_DrawSprite: Bad sprite type %d", psprite->type);
    }

    R_RotateSprite (psprite->beamlength);

    R_SetupAndDrawSprite ();
}
