/*
Copyright (C) 2005 Lord Havoc & Reckless hehe

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.  

Its sole content is to keep better tracing of particles 
Atm. But the plan is to add more stuff from amf_quake,
like death animations / coronas / and some off the particle stuff.

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.

*/
// cl_collision.c -- clientside world query functions

#include "quakedef.h"

int cl_traceline_endcontents;

static int CL_RecursiveHullCheck (RecursiveHullCheckTraceInfo_t *t, int num, float p1f, float p2f, vec3_t p1, vec3_t p2)
{
	// status variables, these don't need to be saved on the stack when
	// recursing...  but are because this should be thread-safe
	// (note: tracing against a bbox is not thread-safe, yet)
	int			ret;
	mplane_t	*plane;
	float		t1, t2;

	// variables that need to be stored on the stack when recursing
	dclipnode_t *node;
	int			side;
	float		midf;
	vec3_t		mid;

	// LordHavoc: a goto!  everyone flee in terror... :)
loc0:
	// check for empty
	if (num < 0)
	{
		t->trace->endcontents = num;

		if (t->trace->startcontents)
		{
			if (num == t->trace->startcontents)
			{
				t->trace->allsolid = false;
			}
			else
			{
				// if the first leaf is solid, set startsolid
				if (t->trace->allsolid)
				{
					t->trace->startsolid = true;
				}
				return HULLCHECKSTATE_SOLID;
			}
			return HULLCHECKSTATE_EMPTY;
		}
		else
		{
			if (num != CONTENTS_SOLID)
			{
				t->trace->allsolid = false;

				if (num == CONTENTS_EMPTY)
				{
					t->trace->inopen = true;
				}
				else
				{
					t->trace->inwater = true;
				}
			}
			else
			{
				// if the first leaf is solid, set startsolid
				if (t->trace->allsolid)
				{
					t->trace->startsolid = true;
				}
				return HULLCHECKSTATE_SOLID;
			}
			return HULLCHECKSTATE_EMPTY;
		}
	}

	// find the point distances
	node = t->hull->clipnodes + num;

	plane = t->hull->planes + node->planenum;

	if (plane->type < 3)
	{
		t1 = p1[plane->type] - plane->dist;
		t2 = p2[plane->type] - plane->dist;
	}
	else
	{
		t1 = DotProduct (plane->normal, p1) - plane->dist;
		t2 = DotProduct (plane->normal, p2) - plane->dist;
	}

	if (t1 < 0)
	{
		if (t2 < 0)
		{
			num = node->children[1];
			goto loc0;
		}
		side = 1;
	}
	else
	{
		if (t2 >= 0)
		{
			num = node->children[0];
			goto loc0;
		}
		side = 0;
	}

	// the line intersects, find intersection point
	// LordHavoc: this uses the original trace for maximum accuracy
	if (plane->type < 3)
	{
		t1 = t->start[plane->type] - plane->dist;
		t2 = t->end[plane->type] - plane->dist;
	}
	else
	{
		t1 = DotProduct (plane->normal, t->start) - plane->dist;
		t2 = DotProduct (plane->normal, t->end) - plane->dist;
	}
	midf = t1 / (t1 - t2);
	midf = bound(p1f, midf, p2f);
	VectorMA(t->start, midf, t->dist, mid);

	// recurse both sides, front side first
	ret = CL_RecursiveHullCheck (t, node->children[side], p1f, midf, p1, mid);

	// if this side is not empty, return what it is (solid or done)
	if (ret != HULLCHECKSTATE_EMPTY) return ret;

	ret = CL_RecursiveHullCheck (t, node->children[side ^ 1], midf, p2f, mid, p2);

	// if other side is not solid, return what it is (empty or done)
	if (ret != HULLCHECKSTATE_SOLID) return ret;

	// front is air and back is solid, this is the impact point...
	if (side)
	{
		t->trace->plane.dist = -plane->dist;
		VectorNegate (plane->normal, t->trace->plane.normal);
	}
	else
	{
		t->trace->plane.dist = plane->dist;
		VectorCopy (plane->normal, t->trace->plane.normal);
	}

	// bias away from surface a bit
	t1 = DotProduct(t->trace->plane.normal, t->start) - (t->trace->plane.dist + DIST_EPSILON);
	t2 = DotProduct(t->trace->plane.normal, t->end) - (t->trace->plane.dist + DIST_EPSILON);

	midf = t1 / (t1 - t2);
	t->trace->fraction = bound(0.0f, midf, 1.0);

	VectorMA(t->start, t->trace->fraction, t->dist, t->trace->endpos);

	return HULLCHECKSTATE_DONE;
}

// used if start and end are the same
static void CL_RecursiveHullCheckPoint (RecursiveHullCheckTraceInfo_t *t, int num)
{
	// If you can read this, you understand BSP trees
	while (num >= 0)
	{
		num = t->hull->clipnodes[num].children[((t->hull->planes[t->hull->clipnodes[num].planenum].type < 3) ? (t->start[t->hull->planes[t->hull->clipnodes[num].planenum].type]) : (DotProduct(t->hull->planes[t->hull->clipnodes[num].planenum].normal, t->start))) < t->hull->planes[t->hull->clipnodes[num].planenum].dist];
	}

	// check for empty
	t->trace->endcontents = num;

	if (t->trace->startcontents)
	{
		if (num == t->trace->startcontents)
		{
			t->trace->allsolid = false;
		}
		else
		{
			// if the first leaf is solid, set startsolid
			if (t->trace->allsolid)
			{
				t->trace->startsolid = true;
			}
		}
	}
	else
	{
		if (num != CONTENTS_SOLID)
		{
			t->trace->allsolid = false;

			if (num == CONTENTS_EMPTY)
			{
				t->trace->inopen = true;
			}
			else
			{
				t->trace->inwater = true;
			}
		}
		else
		{
			// if the first leaf is solid, set startsolid
			if (t->trace->allsolid)
			{
				t->trace->startsolid = true;
			}
		}
	}
}

static hull_t		box_hull;
static dclipnode_t	box_clipnodes[6];
static mplane_t		box_planes[6];

/*
===================
CL_InitBoxHull

Set up the planes and clipnodes so that the six floats of a bounding box
can just be stored out and get a proper hull_t structure.
clientside this one "havoc"
===================
*/
void CL_InitBoxHull (void)
{
	int		i;
	int		side;

	//Set up the planes and clipnodes so that the six floats of a bounding box
	//can just be stored out and get a proper hull_t structure.
	box_hull.clipnodes = box_clipnodes;
	box_hull.planes = box_planes;
	box_hull.firstclipnode = 0;
	box_hull.lastclipnode = 5;

	for (i = 0;i < 6;i++)
	{
		box_clipnodes[i].planenum = i;

		side = i&1;

		box_clipnodes[i].children[side] = CONTENTS_EMPTY;

		if (i != 5)
		{
			box_clipnodes[i].children[side^1] = i + 1;
		}
		else
		{
			box_clipnodes[i].children[side^1] = CONTENTS_SOLID;
		}
		box_planes[i].type = i>>1;
		box_planes[i].normal[i>>1] = 1;
	}
}


static hull_t *CL_HullForBBoxEntity (const vec3_t corigin, const vec3_t cmins, const vec3_t cmaxs, const vec3_t mins, const vec3_t maxs, vec3_t offset)
{
	vec3_t hullmins, hullmaxs;

	// create a temp hull from bounding box sizes
	VectorCopy (corigin, offset);
	VectorSubtract (cmins, maxs, hullmins);
	VectorSubtract (cmaxs, mins, hullmaxs);

	//To keep everything totally uniform, bounding boxes are turned into small
	//BSP trees instead of being compared directly.
	box_planes[0].dist = hullmaxs[0];
	box_planes[1].dist = hullmins[0];
	box_planes[2].dist = hullmaxs[1];
	box_planes[3].dist = hullmins[1];
	box_planes[4].dist = hullmaxs[2];
	box_planes[5].dist = hullmins[2];
	return &box_hull;
}

static const hull_t *CL_HullForBrushModel (const model_t *cmodel, const vec3_t corigin, const vec3_t mins, const vec3_t maxs, vec3_t offset)
{
	vec3_t			size;
	const hull_t	*hull;

	// decide which clipping hull to use, based on the size
	// explicit hulls in the BSP model
	VectorSubtract (maxs, mins, size);

	// LordHavoc: FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	if (size[0] < 3)
		hull = &cmodel->hulls[0]; // 0x0x0
	else if (size[0] <= 32)
		hull = &cmodel->hulls[1]; // 32x32x56
	else
		hull = &cmodel->hulls[2]; // 64x64x88

	// calculate an offset value to center the origin
	VectorSubtract (hull->clip_mins, mins, offset);
	VectorAdd (offset, corigin, offset);

	return hull;
}

void CL_CollisionClipTrace (trace_t *trace, const void *cent, const model_t *cmodel, const vec3_t corigin, const vec3_t cangles, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end)
{
	RecursiveHullCheckTraceInfo_t	rhc;
	vec3_t							offset, forward, left, up;
	vec3_t							startd, endd, tempd;

	// fill in a default trace
	memset (&rhc, 0, sizeof(rhc));
	memset (trace, 0, sizeof(trace_t));

	rhc.trace = trace;

	rhc.trace->fraction = 1;
	rhc.trace->allsolid = true;

	// brush model
	if (cmodel && cmodel->type == mod_brush)
	{
		// get the clipping hull
		rhc.hull = CL_HullForBrushModel (cmodel, corigin, mins, maxs, offset);

		VectorSubtract(start, offset, startd);
		VectorSubtract(end, offset, endd);

		// rotate start and end into the model's frame of reference
		if (cangles[0] || cangles[1] || cangles[2])
		{
			AngleVectorsFLU (cangles, forward, left, up);
			VectorCopy(startd, tempd);
			startd[0] = DotProduct (tempd, forward);
			startd[1] = DotProduct (tempd, left);
			startd[2] = DotProduct (tempd, up);
			VectorCopy(endd, tempd);
			endd[0] = DotProduct (tempd, forward);
			endd[1] = DotProduct (tempd, left);
			endd[2] = DotProduct (tempd, up);
		}

		// trace a line through the appropriate clipping hull
		VectorCopy(startd, rhc.start);
		VectorCopy(endd, rhc.end);
		VectorCopy(rhc.end, rhc.trace->endpos);
		VectorSubtract(rhc.end, rhc.start, rhc.dist);
		if (rhc.dist[0] || rhc.dist[1] || rhc.dist[2])
		{
			CL_RecursiveHullCheck (&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
		}
		else
		{
			CL_RecursiveHullCheckPoint (&rhc, rhc.hull->firstclipnode);
		}

		if (rhc.trace->fraction < 0 || rhc.trace->fraction > 1)
		{
			Con_Printf("fraction out of bounds %f %s:%d\n", rhc.trace->fraction, __LINE__, __FILE__);
		}

		// if we hit, unrotate endpos and normal, and store the entity we hit
		if (rhc.trace->fraction != 1)
		{
			// rotate endpos back to world frame of reference
			if (cangles[0] || cangles[1] || cangles[2])
			{
				VectorNegate (cangles, offset);
				AngleVectorsFLU (offset, forward, left, up);

				VectorCopy (rhc.trace->endpos, tempd);
				rhc.trace->endpos[0] = DotProduct (tempd, forward);
				rhc.trace->endpos[1] = DotProduct (tempd, left);
				rhc.trace->endpos[2] = DotProduct (tempd, up);

				VectorCopy (rhc.trace->plane.normal, tempd);
				rhc.trace->plane.normal[0] = DotProduct (tempd, forward);
				rhc.trace->plane.normal[1] = DotProduct (tempd, left);
				rhc.trace->plane.normal[2] = DotProduct (tempd, up);
			}
			rhc.trace->ent = (void *) cent;
		}
		else if (rhc.trace->allsolid || rhc.trace->startsolid)
		{
			rhc.trace->ent = (void *) cent;
		}

		// fix offset
		VectorAdd (rhc.trace->endpos, offset, rhc.trace->endpos);
	}
	else
	{
		// bounding box
		rhc.hull = CL_HullForBBoxEntity (corigin, cmins, cmaxs, mins, maxs, offset);

		// trace a line through the generated clipping hull
		VectorSubtract(start, offset, rhc.start);
		VectorSubtract(end, offset, rhc.end);
		VectorCopy(rhc.end, rhc.trace->endpos);
		VectorSubtract(rhc.end, rhc.start, rhc.dist);

		if (rhc.dist[0] || rhc.dist[1] || rhc.dist[2])
		{
			CL_RecursiveHullCheck (&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
		}
		else
		{
			CL_RecursiveHullCheckPoint (&rhc, rhc.hull->firstclipnode);
		}

		if (rhc.trace->fraction < 0 || rhc.trace->fraction > 1) 
		{
			Con_Printf("fraction out of bounds %f %s:%d\n", rhc.trace->fraction, __LINE__, __FILE__);
		}
		// if we hit, store the entity we hit
		if (rhc.trace->fraction != 1)
		{
			// fix offset
			VectorAdd (rhc.trace->endpos, offset, rhc.trace->endpos);
			rhc.trace->ent = (void *) cent;
		}
		else if (rhc.trace->allsolid || rhc.trace->startsolid)
		{
			rhc.trace->ent = (void *) cent;
		}
	}
}

float CL_TraceLine (const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int hitbmodels, entity_t *hitent)
{
	int			n;
	entity_t	*ent;
	float		maxfrac;
	vec3_t		tracemins, tracemaxs;
	trace_t		trace;

	if (hitent)
	{
		hitent = &cl_entities[cl.viewentity];
	}
	CL_CollisionClipTrace(&trace, hitent, cl.worldmodel, vec3_origin, vec3_origin, vec3_origin, vec3_origin, start, vec3_origin, vec3_origin, end);

	if (impact)
	{
		VectorCopy (trace.endpos, impact);
	}

	if (normal)
	{
		VectorCopy (trace.plane.normal, normal);
	}
	cl_traceline_endcontents = trace.endcontents;
	maxfrac = trace.fraction;
	
	if (hitent && trace.fraction < 1)
	{
		for (n = 0 ; n < cl_numvisedicts ; n++)
		{
			hitent = cl_visedicts[n];
		}
	}

	if (hitbmodels)
	{
		tracemins[0] = min(start[0], end[0]);
		tracemaxs[0] = max(start[0], end[0]);
		tracemins[1] = min(start[1], end[1]);
		tracemaxs[1] = max(start[1], end[1]);
		tracemins[2] = min(start[2], end[2]);
		tracemaxs[2] = max(start[2], end[2]);

		// look for embedded bmodels
		for (n = 0 ; n < cl_numvisedicts ; n++)
		{
			ent = cl_visedicts[n];
			if (ent->model->type != mod_brush) continue;
			if (ent->model->mins[0] > tracemaxs[0] || ent->model->maxs[0] < tracemins[0]
			 || ent->model->mins[1] > tracemaxs[1] || ent->model->maxs[1] < tracemins[1]
			 || ent->model->mins[2] > tracemaxs[2] || ent->model->maxs[2] < tracemins[2]) continue;

			 CL_CollisionClipTrace(&trace, ent, ent->model, ent->origin, ent->angles, ent->model->mins, ent->model->maxs, start, vec3_origin, vec3_origin, end);

			if (trace.allsolid || trace.startsolid || trace.fraction < maxfrac)
			{
				cl_traceline_endcontents = trace.endcontents;
				maxfrac = trace.fraction;

				if (impact)
				{
					VectorCopy(trace.endpos, impact);
				}

				if (normal)
				{
					VectorCopy(trace.plane.normal, normal);
				}

				if (hitent)
				{
					hitent = ent;
				}
			}
		}
	}

	if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __LINE__, __FILE__);
	{
		return maxfrac;
	}
}

int CL_PointContents (vec3_t p)
{
	CL_TraceLine(p, p, NULL, NULL, 1, NULL);
	return cl_traceline_endcontents;
}
