/*
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 included (GNU.txt) 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 "quakedef.h"

#define NUMVERTEXNORMALS	162
float	r_avertexnormals[NUMVERTEXNORMALS][3] = {
#include "anorms.h"
};


void R_Rockettrail_Callback(struct cvar_s *var, char *oldvalue)
{
	int i;
	model_t *mod;
	extern model_t	mod_known[];
	extern int		mod_numknown;

	if (cls.state == ca_disconnected)
		return; // don't bother parsing while disconnected

	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		if (mod->flags & EF_ROCKET)
			P_DefaultTrail(mod);
	}
}

void R_Grenadetrail_Callback(struct cvar_s *var, char *oldvalue)
{
	int i;
	model_t *mod;
	extern model_t	mod_known[];
	extern int		mod_numknown;

	if (cls.state == ca_disconnected)
		return; // don't bother parsing while disconnected

	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		if (mod->flags & EF_GRENADE)
			P_DefaultTrail(mod);
	}
}

particleengine_t pe_null;
particleengine_t pe_classic;
particleengine_t pe_darkplaces;
particleengine_t pe_script;

particleengine_t *particlesystem[] =
{
	&pe_script,
	&pe_darkplaces,
	&pe_classic,
	&pe_null,
	NULL,
};

void R_ParticleSystem_Callback(struct cvar_s *var, char *oldvalue)
{
	int i;
	if (pe)
		pe->ShutdownParticles();

	pe = NULL;

	for (i = 0; particlesystem[i]; i++)
	{
		if (   (particlesystem[i]->name1 && !stricmp(var->string, particlesystem[i]->name1))
			|| (particlesystem[i]->name2 && !stricmp(var->string, particlesystem[i]->name2)))
		{
			pe = particlesystem[i];
			break;
		}
		if (!pe)
			if (particlesystem[i]->name1)
				pe = particlesystem[i];
	}

	if (!pe)
		Sys_Error("No particle system available. Please recompile.");

	pe->InitParticles();
	pe->ClearParticles();
	CL_RegisterParticles();
}

cvar_t r_rockettrail = SCVARFC("r_rockettrail", "1", CVAR_SEMICHEAT, R_Rockettrail_Callback);
cvar_t r_grenadetrail = SCVARFC("r_grenadetrail", "1", CVAR_SEMICHEAT, R_Grenadetrail_Callback);
#ifdef MINIMAL
//minimal builds get a different default.
cvar_t r_particlesystem = SCVARFC("r_particlesystem", "classic", CVAR_SEMICHEAT, R_ParticleSystem_Callback);
#else
cvar_t r_particlesystem = SCVARFC("r_particlesystem", "script", CVAR_SEMICHEAT, R_ParticleSystem_Callback);
#endif
cvar_t r_particlesdesc = SCVARF("r_particlesdesc", "spikeset tsshaft", CVAR_SEMICHEAT);
extern cvar_t r_bouncysparks;
extern cvar_t r_part_rain;
extern cvar_t r_bloodstains;
extern cvar_t gl_part_flame;
cvar_t r_part_rain_quantity = SCVAR("r_part_rain_quantity", "1");

cvar_t r_particle_tracelimit = SCVAR("r_particle_tracelimit", "250");
cvar_t r_part_sparks = SCVAR("r_part_sparks", "1");
cvar_t r_part_sparks_trifan = SCVAR("r_part_sparks_trifan", "1");
cvar_t r_part_sparks_textured = SCVAR("r_part_sparks_textured", "1");
cvar_t r_part_beams = SCVAR("r_part_beams", "1");
cvar_t r_part_contentswitch = SCVAR("r_part_contentswitch", "1");


particleengine_t *pe;

void P_InitParticleSystem(void)
{
	char *particlecvargroupname = "Particle effects";

	Cvar_Register(&r_particlesystem, "Particles");




	//particles
	Cvar_Register(&r_particlesdesc, particlecvargroupname);
	Cvar_Register(&r_bouncysparks, particlecvargroupname);
	Cvar_Register(&r_part_rain, particlecvargroupname);

	Cvar_Register(&r_part_rain_quantity, particlecvargroupname);

	Cvar_Register(&r_particle_tracelimit, particlecvargroupname);

	Cvar_Register(&r_part_sparks, particlecvargroupname);
	Cvar_Register(&r_part_sparks_trifan, particlecvargroupname);
	Cvar_Register(&r_part_sparks_textured, particlecvargroupname);
	Cvar_Register(&r_part_beams, particlecvargroupname);
	Cvar_Register(&r_part_contentswitch, particlecvargroupname);

	Cvar_Register (&gl_part_flame, particlecvargroupname);
}

#ifdef Q2BSPS
qboolean Q2TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	vec3_t nul = {0,0,0};
	trace_t trace = CM_BoxTrace(pmove.physents[0].model, start, end, nul, nul, MASK_SOLID);

	if (trace.fraction < 1)
	{
		VectorCopy (trace.plane.normal, normal);
		VectorCopy (trace.endpos, impact);
		return true;
	}
	return false;
}
#endif

qboolean TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	trace_t		trace;
	float len, bestlen;
	int i;
	vec3_t delta, ts, te;
	physent_t *pe;
	qboolean clipped=false;

	memset (&trace, 0, sizeof(trace));

	VectorSubtract(end, start, delta);
	bestlen = Length(delta);

	VectorCopy (end, impact);

	for (i=0 ; i< pmove.numphysent ; i++)
	{
		pe = &pmove.physents[i];
		if (pe->nonsolid)
			continue;
		if (pe->model)
		{
			VectorSubtract(start, pe->origin, ts);
			VectorSubtract(end, pe->origin, te);
			pe->model->funcs.Trace(pe->model, 0, 0, ts, te, vec3_origin, vec3_origin, &trace);
			if (trace.fraction<1)
			{
				VectorSubtract(trace.endpos, ts, delta);
				len = Length(delta);
				if (len < bestlen)
				{
					bestlen = len;
					VectorCopy (trace.plane.normal, normal);
					VectorAdd (pe->origin, trace.endpos, impact);
				}

				clipped=true;
			}
			if (trace.startsolid)
			{
				VectorNormalize(delta);
				normal[0] = -delta[0];
				normal[1] = -delta[1];
				normal[2] = -delta[2];
				VectorCopy (start, impact);
				return true;
			}

		}
	}

	if (clipped)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//handy utility...
void P_EmitEffect (vec3_t pos, int type, trailstate_t **tsk)
{
	if (cl.paused)
		return;

	pe->RunParticleEffectState(pos, NULL, ((host_frametime>0.1)?0.1:host_frametime), type, tsk);
}











// P_SelectableTrail: given default/opposite effects, model pointer, and a user selection cvar
// changes model to the appropriate trail effect and default trail index
void P_SelectableTrail(model_t *model, cvar_t *selection, int mdleffect, int mdlcidx, int oppeffect, int oppcidx)
{
	int select = (int)(selection->value);

	switch (select)
	{
	case 0: // check for string, otherwise no trail
		if (selection->string[0] == '0')
		{
			model->particletrail = P_INVALID;		
			break;
		}
		else
		{
			int effect = P_FindParticleType(selection->string);

			if (effect >= 0)
			{
				model->particletrail = effect;
				model->traildefaultindex = mdlcidx;
				break;
			}
		}
		// fall through to default (so semicheat will work properly)
	case 1: // default model effect
	default:
		model->particletrail = mdleffect;
		model->traildefaultindex = mdlcidx;
		break;
	case 2: // opposite effect
		model->particletrail = oppeffect;
		model->traildefaultindex= oppcidx;
		break;
	case 3: // alt rocket effect
		model->particletrail = P_ParticleTypeForName("TR_ALTROCKET");
		model->traildefaultindex = 107;
		break;
	case 4: // gib
		model->particletrail = P_ParticleTypeForName("TR_BLOOD");
		model->traildefaultindex = 70;
		break;
	case 5: // zombie gib
		model->particletrail = P_ParticleTypeForName("TR_SLIGHTBLOOD");
		model->traildefaultindex = 70;
		break;
	case 6: // Scrag tracer
		model->particletrail = P_ParticleTypeForName("TR_WIZSPIKE");
		model->traildefaultindex = 60;
		break;
	case 7: // Knight tracer
		model->particletrail = P_ParticleTypeForName("TR_KNIGHTSPIKE");
		model->traildefaultindex = 238;
		break;
	case 8: // Vore tracer
		model->particletrail = P_ParticleTypeForName("TR_VORESPIKE");
		model->traildefaultindex = 154;
		break;
	case 9: // rail trail
		model->particletrail = P_ParticleTypeForName("TR_RAILTRAIL");
		model->traildefaultindex = 15;
		break;
	}
}



//figure out which particle trail to use for the given model, filling in its values as required.
void P_DefaultTrail (model_t *model)
{
	// TODO: EF_BRIGHTFIELD should probably be handled in here somewhere
	// TODO: make trail default color into RGB values instead of indexes
	if (model->engineflags & MDLF_NODEFAULTTRAIL)
		return;

	if (model->flags & EF_ROCKET)
		P_SelectableTrail(model, &r_rockettrail, P_ParticleTypeForName("TR_ROCKET"), 109, P_ParticleTypeForName("TR_GRENADE"), 6);
	else if (model->flags & EF_GRENADE)
		P_SelectableTrail(model, &r_grenadetrail, P_ParticleTypeForName("TR_GRENADE"), 6, P_ParticleTypeForName("TR_ROCKET"), 109);
	else if (model->flags & EF_GIB)
	{
		model->particletrail = P_ParticleTypeForName("TR_BLOOD");
		model->traildefaultindex = 70;
	}
	else if (model->flags & EF_TRACER)
	{
		model->particletrail = P_ParticleTypeForName("TR_WIZSPIKE");
		model->traildefaultindex = 60;
	}
	else if (model->flags & EF_ZOMGIB)
	{
		model->particletrail = P_ParticleTypeForName("TR_SLIGHTBLOOD");
		model->traildefaultindex = 70;
	}
	else if (model->flags & EF_TRACER2)
	{
		model->particletrail = P_ParticleTypeForName("TR_KNIGHTSPIKE");
		model->traildefaultindex = 238;
	}
	else if (model->flags & EF_TRACER3)
	{
		model->particletrail = P_ParticleTypeForName("TR_VORESPIKE");
		model->traildefaultindex = 154;
	}
	else if (model->flags & EFH2_BLOODSHOT)	//these are the hexen2 ones.
	{
		model->particletrail = P_ParticleTypeForName("t_bloodshot");
		model->traildefaultindex = 136;
	}
	else if (model->flags & EFH2_FIREBALL)
	{
		model->particletrail = P_ParticleTypeForName("t_fireball");
		model->traildefaultindex = 424;
	}
	else if (model->flags & EFH2_ACIDBALL)
	{
		model->particletrail = P_ParticleTypeForName("t_acidball");
		model->traildefaultindex = 440;
	}
	else if (model->flags & EFH2_ICE)
	{
		model->particletrail = P_ParticleTypeForName("t_ice");
		model->traildefaultindex = 408;
	}
	else if (model->flags & EFH2_SPIT)
	{
		model->particletrail = P_ParticleTypeForName("t_spit");
		model->traildefaultindex = 260;
	}
	else if (model->flags & EFH2_SPELL)
	{
		model->particletrail = P_ParticleTypeForName("t_spell");
		model->traildefaultindex = 260;
	}
	else if (model->flags & EFH2_VORP_MISSILE)
	{
		model->particletrail = P_ParticleTypeForName("t_vorpmissile");
		model->traildefaultindex = 302;
	}
	else if (model->flags & EFH2_SET_STAFF)
	{
		model->particletrail = P_ParticleTypeForName("t_setstaff");
		model->traildefaultindex = 424;
	}
	else if (model->flags & EFH2_MAGICMISSILE)
	{
		model->particletrail = P_ParticleTypeForName("t_magicmissile");
		model->traildefaultindex = 149;
	}
	else if (model->flags & EFH2_BONESHARD)
	{
		model->particletrail = P_ParticleTypeForName("t_boneshard");
		model->traildefaultindex = 384;
	}
	else if (model->flags & EFH2_SCARAB)
	{
		model->particletrail = P_ParticleTypeForName("t_scarab");
		model->traildefaultindex = 254;
	}
	else
		model->particletrail = P_INVALID;
}
