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

*/

#include <stdlib.h>
#include <string.h>
#include <math.h>

#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;
	
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);
}

//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], 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;
}

void R_SetupAndDrawSprite (void) {
	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)
			Host_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 ();
}

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) {
		Com_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;

		// 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;
}

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

	VectorCopy (currententity->origin, r_entorigin);
	VectorSubtract (r_origin, r_entorigin, modelorg);

	psprite = currententity->model->extradata;

	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
		VectorNegate(modelorg, tvec);
		VectorNormalizeFast (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;
		VectorSet(r_spritedesc.vup, 0, 0, 1);
		// CrossProduct(r_spritedesc.vup, -modelorg, r_spritedesc.vright)
		VectorSet(r_spritedesc.vright, tvec[1], -tvec[0], 0);
		VectorNormalizeFast (r_spritedesc.vright);
		// CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn)
		VectorSet(r_spritedesc.vpn, -r_spritedesc.vright[1], r_spritedesc.vright[0], 0);
	} 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;
		VectorSet(r_spritedesc.vup, 0, 0, 1);
		//CrossProduct (r_spritedesc.vup, vpn, r_spritedesc.vright);
		VectorSet(r_spritedesc.vright, vpn[1], -vpn[0], 0);
		VectorNormalizeFast (r_spritedesc.vright);
		// CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn)
		VectorSet(r_spritedesc.vpn, -r_spritedesc.vright[1], r_spritedesc.vright[0], 0);
	} 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 = DEG2RAD(currententity->angles[ROLL]);
		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 ();
}
