/*
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
// r_sprite.c

#include "console.h"
#include "d_local.h"
#include "model.h"
#include "protocol.h"
#include "quakedef.h"
#include "r_local.h"
#include "sys.h"

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

spritedesc_t r_spritedesc;

int
R_SpriteDataSize(int pixels)
{
    return pixels * r_pixbytes;
}

void
R_SpriteDataStore(const model_t *model, mspriteframe_t *frame, const char *modelname,
		  int framenum, byte *pixels)
{
    int i, size;

    size = frame->width * frame->height;
    if (r_pixbytes == 1) {
	memcpy(&frame->rdata[0], pixels, size);
    } else if (r_pixbytes == 2) {
	unsigned short *pixout = (unsigned short *)&frame->rdata[0];
	for (i = 0; i < size; i++)
	    pixout[i] = d_8to16table[pixels[i]];
    } else {
	Sys_Error("%s: driver set invalid r_pixbytes: %d", __func__,
		  r_pixbytes);
    }
}

/*
================
R_RotateSprite
================
*/
static 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
==============
*/
static int
R_ClipSpriteFace(int nump, clipplane_t *pclipplane)
{
    int i, outcount;
    float dists[MAXWORKINGVERTS + 1];
    float frac, clipdist, *pclipnormal;
    float *in, *instep, *outstep, *vert2;

    clipdist = pclipplane->plane.dist;
    pclipnormal = pclipplane->plane.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
================
*/
static 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("%s: too many points", __func__);
    }

// 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_DrawSprite
================
*/
void
R_DrawSprite(const entity_t *entity)
{
    int i;
    msprite_t *sprite;
    vec3_t tvec;
    float dot, angle, sr, cr;

    sprite = entity->model->cache.data;

    r_spritedesc.pspriteframe = Mod_GetSpriteFrame(entity, sprite, cl.time + entity->syncbase);

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

    // TODO: make this caller-selectable
    switch (sprite->type) {
        case 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;

            // CrossProduct(r_spritedesc.vup, -modelorg, r_spritedesc.vright)
            r_spritedesc.vright[0] = tvec[1];
            r_spritedesc.vright[1] = -tvec[0];
            r_spritedesc.vright[2] = 0;
            VectorNormalize(r_spritedesc.vright);

            // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn)
            r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
            r_spritedesc.vpn[1] = r_spritedesc.vright[0];
            r_spritedesc.vpn[2] = 0;
            break;
        case 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];
            }
            break;
        case 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;

            // CrossProduct (r_spritedesc.vup, vpn, r_spritedesc.vright)
            r_spritedesc.vright[0] = vpn[1];
            r_spritedesc.vright[1] = -vpn[0];
            r_spritedesc.vright[2] = 0;
            VectorNormalize(r_spritedesc.vright);

            // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn)
            r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
            r_spritedesc.vpn[1] = r_spritedesc.vright[0];
            r_spritedesc.vpn[2] = 0;
            break;
        case SPR_ORIENTED:
            // generate the sprite's axes, according to the sprite's world orientation
            AngleVectors(entity->angles, r_spritedesc.vpn, r_spritedesc.vright, r_spritedesc.vup);
            break;
        case 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 = entity->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;
            }
            break;
        default:
            Sys_Error("%s: Bad sprite type %d", __func__, sprite->type);
    }

    R_RotateSprite(sprite->beamlength);

    /* Setup alpha table if required */
    if (entity->alpha != ENTALPHA_DEFAULT && entity->alpha != ENTALPHA_ONE) {
        r_transtable = Alpha_Transtable(ENTALPHA_DECODE(entity->alpha));
    } else {
        r_transtable = NULL;
    }

    R_SetupAndDrawSprite();
}
