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

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

#include "quakedef.h"

#ifdef HLSERVER
#include "svhl_gcapi.h"

hull_t	*World_HullForBox (vec3_t mins, vec3_t maxs);
qboolean TransformedTrace (struct model_s *model, int hulloverride, int frame, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, struct trace_s *trace, vec3_t origin, vec3_t angles, unsigned int hitcontentsmask);
/*

entities never clip against themselves, or their owner

line of sight checks trace->crosscontent, but bullets don't

*/

extern cvar_t sv_compatiblehulls;

typedef struct
{
	vec3_t		boxmins, boxmaxs;// enclose the test object along entire move
	float		*mins, *maxs;	// size of the moving object
	vec3_t		mins2, maxs2;	// size when clipping against mosnters
	float		*start, *end;
	trace_t		trace;
	int			type;
	hledict_t		*passedict;
	int hullnum;
	unsigned int clipmask;
} hlmoveclip_t;

/*
===============================================================================

HULL BOXES

===============================================================================
*/


/*
===============================================================================

ENTITY AREA CHECKING

===============================================================================
*/

/*
===============
SV_UnlinkEdict

===============
*/
void SVHL_UnlinkEdict (hledict_t *ent)
{
	if (!ent->area.prev)
		return;		// not linked in anywhere
	RemoveLink (&ent->area);
	ent->area.prev = ent->area.next = NULL;
}


/*
====================
SV_TouchLinks
====================
*/
#define MAX_NODELINKS	256	//all this means is that any more than this will not touch.
hledict_t *nodelinks[MAX_NODELINKS];
void SVHL_TouchLinks ( hledict_t *ent, areanode_t *node )
{	//Spike: rewritten this function to cope with killtargets used on a few maps.
	link_t		*l, *next;
	hledict_t		*touch;

	int linkcount = 0, ln;

	//work out who they are first.
	for (l = node->edicts.next ; l != &node->edicts ; l = next)
	{
		if (linkcount == MAX_NODELINKS)
			break;
		next = l->next;
		touch = HLEDICT_FROM_AREA(l);
		if (touch == ent)
			continue;

		if (touch->v.solid != SOLID_TRIGGER)
			continue;

		if (ent->v.absmin[0] > touch->v.absmax[0]
		|| ent->v.absmin[1] > touch->v.absmax[1]
		|| ent->v.absmin[2] > touch->v.absmax[2]
		|| ent->v.absmax[0] < touch->v.absmin[0]
		|| ent->v.absmax[1] < touch->v.absmin[1]
		|| ent->v.absmax[2] < touch->v.absmin[2] )
			continue;

//		if (!((int)ent->xv.dimension_solid & (int)touch->xv.dimension_hit))
//			continue;

		nodelinks[linkcount++] = touch;
	}

	for (ln = 0; ln < linkcount; ln++)
	{
		touch = nodelinks[ln];

		//make sure nothing moved it away
		if (touch->isfree)
			continue;
		if (touch->v.solid != SOLID_TRIGGER)
			continue;
		if (ent->v.absmin[0] > touch->v.absmax[0]
		|| ent->v.absmin[1] > touch->v.absmax[1]
		|| ent->v.absmin[2] > touch->v.absmax[2]
		|| ent->v.absmax[0] < touch->v.absmin[0]
		|| ent->v.absmax[1] < touch->v.absmin[1]
		|| ent->v.absmax[2] < touch->v.absmin[2] )
			continue;

//		if (!((int)ent->xv->dimension_solid & (int)touch->xv->dimension_hit))	//didn't change did it?...
//			continue;

		SVHL_GameFuncs.DispatchTouch(touch, ent);

		if (ent->isfree)
			break;
	}


// recurse down both sides
	if (node->axis == -1 || ent->isfree)
		return;
	
	if ( ent->v.absmax[node->axis] > node->dist )
		SVHL_TouchLinks ( ent, node->children[0] );
	if ( ent->v.absmin[node->axis] < node->dist )
		SVHL_TouchLinks ( ent, node->children[1] );
}

/*
===============
SV_LinkEdict

===============
*/
void SVHL_LinkEdict (hledict_t *ent, qboolean touch_triggers)
{
	areanode_t	*node;
	
	if (ent->area.prev)
		SVHL_UnlinkEdict (ent);	// unlink from old position

	if (ent == &SVHL_Edict[0])
		return;		// don't add the world

	if (ent->isfree)
		return;

// set the abs box
	if (ent->v.solid == SOLID_BSP && 
	(ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) )
	{	// expand for rotation

#if 1
		int i;
		float v;
		float max;
		//q2 method
		max = 0;
		for (i=0 ; i<3 ; i++)
		{
			v =fabs( ent->v.mins[i]);
			if (v > max)
				max = v;
			v =fabs( ent->v.maxs[i]);
			if (v > max)
				max = v;
		}
		for (i=0 ; i<3 ; i++)
		{
			ent->v.absmin[i] = ent->v.origin[i] - max;
			ent->v.absmax[i] = ent->v.origin[i] + max;
		}
#else

		int			i;

		vec3_t f, r, u;
		vec3_t mn, mx;

		//we need to link to the correct leaves

		AngleVectors(ent->v.angles, f,r,u);

		mn[0] = DotProduct(ent->v.mins, f);
		mn[1] = -DotProduct(ent->v.mins, r);
		mn[2] = DotProduct(ent->v.mins, u);

		mx[0] = DotProduct(ent->v.maxs, f);
		mx[1] = -DotProduct(ent->v.maxs, r);
		mx[2] = DotProduct(ent->v.maxs, u);
		for (i = 0; i < 3; i++)
		{
			if (mn[i] < mx[i])
			{
				ent->v.absmin[i] = ent->v.origin[i]+mn[i]-0.1;
				ent->v.absmax[i] = ent->v.origin[i]+mx[i]+0.1;
			}
			else
			{	//box went inside out
				ent->v.absmin[i] = ent->v.origin[i]+mx[i]-0.1;
				ent->v.absmax[i] = ent->v.origin[i]+mn[i]+0.1;
			}
		}
#endif
	}
	else
	{
		VectorAdd (ent->v.origin, ent->v.mins, ent->v.absmin);	
		VectorAdd (ent->v.origin, ent->v.maxs, ent->v.absmax);
	}

//
// to make items easier to pick up and allow them to be grabbed off
// of shelves, the abs sizes are expanded
//
	if ((int)ent->v.flags & FL_ITEM)
	{
		ent->v.absmin[0] -= 15;
		ent->v.absmin[1] -= 15;
		ent->v.absmin[2] -= 1;
		ent->v.absmax[0] += 15;
		ent->v.absmax[1] += 15;
		ent->v.absmax[2] += 1;
	}
	else
	{	// because movement is clipped an epsilon away from an actual edge,
		// we must fully check even when bounding boxes don't quite touch
		ent->v.absmin[0] -= 1;
		ent->v.absmin[1] -= 1;
		ent->v.absmin[2] -= 1;
		ent->v.absmax[0] += 1;
		ent->v.absmax[1] += 1;
		ent->v.absmax[2] += 1;
	}
	
// link to PVS leafs
//	sv.worldmodel->funcs.FindTouchedLeafs_Q1(sv.worldmodel, ent, ent->v.absmin, ent->v.absmax);

// find the first node that the ent's box crosses
	node = sv.world.areanodes;
	while (1)
	{
		if (node->axis == -1)
			break;
		if (ent->v.absmin[node->axis] > node->dist)
			node = node->children[0];
		else if (ent->v.absmax[node->axis] < node->dist)
			node = node->children[1];
		else
			break;		// crosses the node
	}
	
// link it in	

	InsertLinkBefore (&ent->area, &node->edicts);
	
// if touch_triggers, touch all entities at this node and decend for more
	if (touch_triggers)
		SVHL_TouchLinks ( ent, sv.world.areanodes );
}


/*
===============================================================================

POINT TESTING IN HULLS

===============================================================================
*/

/*
==================
SV_PointContents

==================
*/
int SVHL_PointContents (vec3_t p)
{
	return sv.world.worldmodel->funcs.PointContents(sv.world.worldmodel, NULL, p);
}

//===========================================================================

/*
============
SV_TestEntityPosition

A small wrapper around SV_BoxInSolidEntity that never clips against the
supplied entity.
============
*/
hledict_t	*SVHL_TestEntityPosition (hledict_t *ent)
{
	trace_t	trace;

	trace = SVHL_Move (ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, 0, 0, ent);
	
	if (trace.startsolid)
		return &SVHL_Edict[0];
		
	return NULL;
}

/*
==================
SV_ClipMoveToEntity

Handles selection or creation of a clipping hull, and offseting (and
eventually rotation) of the end points
==================
*/
trace_t SVHL_ClipMoveToEntity (hledict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int hullnum, qboolean hitmodel, unsigned int clipmask)	//hullnum overrides min/max for q1 style bsps
{
	trace_t		trace;
	model_t		*model;

/*
#ifdef Q2BSPS
	if (ent->v->solid == SOLID_BSP)
		if (sv.models[(int)ent->v->modelindex] && (sv.models[(int)ent->v->modelindex]->fromgame == fg_quake2 || sv.models[(int)ent->v->modelindex]->fromgame == fg_quake3))
		{
			trace = CM_TransformedBoxTrace (start, end, mins, maxs, sv.models[(int)ent->v->modelindex]->hulls[0].firstclipnode, MASK_PLAYERSOLID, ent->v->origin, ent->v->angles);
			if (trace.fraction < 1 || trace.startsolid)
				trace.ent = ent;
			return trace;
		}
#endif
*/

// get the clipping hull
	if (ent->v.solid == SOLID_BSP)
	{
		model = sv.models[(int)ent->v.modelindex];
		if (!model || (model->type != mod_brush && model->type != mod_heightmap))
			SV_Error("SOLID_BSP with non bsp model (classname: %s)", SVHL_Globals.stringbase+ent->v.classname);
	}
	else
	{
		vec3_t boxmins, boxmaxs;
		VectorSubtract (ent->v.mins, maxs, boxmins);
		VectorSubtract (ent->v.maxs, mins, boxmaxs);
		World_HullForBox(boxmins, boxmaxs);
		model = NULL;
	}

// trace a line through the apropriate clipping hull
	if (ent->v.solid != SOLID_BSP)
	{
		ent->v.angles[0]*=-1;	//carmack made bsp models rotate wrongly.
		TransformedTrace(model, hullnum, ent->v.frame, start, end, mins, maxs, &trace, ent->v.origin, ent->v.angles, clipmask);
		ent->v.angles[0]*=-1;
	}
	else
	{
		TransformedTrace(model, hullnum, ent->v.frame, start, end, mins, maxs, &trace, ent->v.origin, ent->v.angles, clipmask);
	}

// fix trace up by the offset
	if (trace.fraction != 1)
	{
		if (!model && hitmodel && ent->v.solid != SOLID_BSP && ent->v.modelindex > 0)
		{
			//okay, we hit the bbox

			model_t *model;
			if (ent->v.modelindex < 1 || ent->v.modelindex >= MAX_MODELS)
				SV_Error("SV_ClipMoveToEntity: modelindex out of range\n");
			model = sv.models[ (int)ent->v.modelindex ];
			if (!model)
			{	//if the model isn't loaded, load it.
				//this saves on memory requirements with mods that don't ever use this.
				model = sv.models[(int)ent->v.modelindex] = Mod_ForName(sv.strings.model_precache[(int)ent->v.modelindex], false);
			}

			if (model && model->funcs.NativeTrace)
			{
				//do the second trace
				TransformedTrace(model, hullnum, ent->v.frame, start, end, mins, maxs, &trace, ent->v.origin, ent->v.angles, MASK_WORLDSOLID);
			}
		}

		if (trace.startsolid)
		{
			if (ent != &SVHL_Edict[0])
				Con_Printf("Trace started solid\n");
		}
	}

// did we clip the move?
	if (trace.fraction < 1 || trace.startsolid  )
		trace.ent = ent;

	return trace;
}

#ifdef Q2BSPS
float	*area_mins, *area_maxs;
hledict_t	**area_list;
int		area_count, area_maxcount;
void SVHL_AreaEdicts_r (areanode_t *node)
{
	link_t		*l, *next, *start;
	hledict_t		*check;
	int			count;

	count = 0;

	// touch linked edicts
	start = &node->edicts;

	for (l=start->next  ; l != start ; l = next)
	{
		next = l->next;
		check = HLEDICT_FROM_AREA(l);

//		if (check->v.solid == SOLID_NOT)
//			continue;		// deactivated
		if (check->v.absmin[0] > area_maxs[0]
		|| check->v.absmin[1] > area_maxs[1]
		|| check->v.absmin[2] > area_maxs[2]
		|| check->v.absmax[0] < area_mins[0]
		|| check->v.absmax[1] < area_mins[1]
		|| check->v.absmax[2] < area_mins[2])
			continue;		// not touching

		if (area_count == area_maxcount)
		{
			Con_Printf ("SV_AreaEdicts: MAXCOUNT\n");
			return;
		}

		area_list[area_count] = check;
		area_count++;
	}
	
	if (node->axis == -1)
		return;		// terminal node

	// recurse down both sides
	if ( area_maxs[node->axis] > node->dist )
		SVHL_AreaEdicts_r ( node->children[0] );
	if ( area_mins[node->axis] < node->dist )
		SVHL_AreaEdicts_r ( node->children[1] );
}

/*
================
SV_AreaEdicts
================
*/
int SVHL_AreaEdicts (vec3_t mins, vec3_t maxs, hledict_t **list, int maxcount)
{
	area_mins = mins;
	area_maxs = maxs;
	area_list = list;
	area_count = 0;
	area_maxcount = maxcount;

	SVHL_AreaEdicts_r (sv.world.areanodes);

	return area_count;
}

#endif
//===========================================================================


/*
====================
SV_ClipToEverything

like SV_ClipToLinks, but doesn't use the links part. This can be used for checking triggers, solid entities, not-solid entities.
Sounds pointless, I know.
====================
*/
void SVHL_ClipToEverything (hlmoveclip_t *clip)
{
	int e;
	trace_t		trace;
	hledict_t		*touch;
	for (e=1 ; e<sv.world.num_edicts ; e++)
	{
		touch = &SVHL_Edict[e];

		if (touch->isfree)
			continue;                 
		if (touch->v.solid == SOLID_NOT && !((int)touch->v.flags & FL_FINDABLE_NONSOLID))
			continue;
		if (touch->v.solid == SOLID_TRIGGER && !((int)touch->v.flags & FL_FINDABLE_NONSOLID))
			continue;

		if (touch == clip->passedict)
			continue;

		if (clip->type & MOVE_NOMONSTERS && touch->v.solid != SOLID_BSP)
			continue;

		if (clip->passedict)
		{
			// don't clip corpse against character
			if (clip->passedict->v.solid == SOLID_CORPSE && (touch->v.solid == SOLID_SLIDEBOX || touch->v.solid == SOLID_CORPSE))
				continue;
			// don't clip character against corpse
			if (clip->passedict->v.solid == SOLID_SLIDEBOX && touch->v.solid == SOLID_CORPSE)
				continue;

//			if (!((int)clip->passedict->v.dimension_hit & (int)touch->v.dimension_solid))
//				continue;
		}

		if (clip->boxmins[0] > touch->v.absmax[0]
				|| clip->boxmins[1] > touch->v.absmax[1]
				|| clip->boxmins[2] > touch->v.absmax[2]
				|| clip->boxmaxs[0] < touch->v.absmin[0]
				|| clip->boxmaxs[1] < touch->v.absmin[1]
				|| clip->boxmaxs[2] < touch->v.absmin[2] )
			continue;

		if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0])
			continue;	// points never interact

	// might intersect, so do an exact clip
		if (clip->trace.allsolid)
			return;
		if (clip->passedict)
		{
		 	if (touch->v.owner == clip->passedict)
				continue;	// don't clip against own missiles
			if (clip->passedict->v.owner == touch)
				continue;	// don't clip against owner
		}

		if ((int)touch->v.flags & FL_MONSTER)
			trace = SVHL_ClipMoveToEntity (touch, clip->start, clip->mins2, clip->maxs2, clip->end, clip->hullnum, clip->type & MOVE_HITMODEL, clip->clipmask);
		else
			trace = SVHL_ClipMoveToEntity (touch, clip->start, clip->mins, clip->maxs, clip->end, clip->hullnum, clip->type & MOVE_HITMODEL, clip->clipmask);
		if (trace.allsolid || trace.startsolid ||
				trace.fraction < clip->trace.fraction)
		{
			trace.ent = touch;
			clip->trace = trace;
		}
	}
}

/*
====================
SV_ClipToLinks

Mins and maxs enclose the entire area swept by the move
====================
*/
void SVHL_ClipToLinks ( areanode_t *node, hlmoveclip_t *clip )
{
	link_t		*l, *next;
	hledict_t		*touch;
	trace_t		trace;

// touch linked edicts
	for (l = node->edicts.next ; l != &node->edicts ; l = next)
	{
		next = l->next;
		touch = HLEDICT_FROM_AREA(l);
		if (touch->v.solid == SOLID_NOT)
			continue;
		if (touch == clip->passedict)
			continue;
		if (touch->v.solid == SOLID_TRIGGER)
			continue;

		if (clip->type & MOVE_NOMONSTERS && touch->v.solid != SOLID_BSP)
			continue;

		if (clip->passedict)
		{
			// don't clip corpse against character
			if (clip->passedict->v.solid == SOLID_CORPSE && (touch->v.solid == SOLID_SLIDEBOX || touch->v.solid == SOLID_CORPSE))
				continue;
			// don't clip character against corpse
			if (clip->passedict->v.solid == SOLID_SLIDEBOX && touch->v.solid == SOLID_CORPSE)
				continue;

//			if (!((int)clip->passedict->xv->dimension_hit & (int)touch->xv->dimension_solid))
//				continue;
		}

		if (clip->boxmins[0] > touch->v.absmax[0]
		|| clip->boxmins[1] > touch->v.absmax[1]
		|| clip->boxmins[2] > touch->v.absmax[2]
		|| clip->boxmaxs[0] < touch->v.absmin[0]
		|| clip->boxmaxs[1] < touch->v.absmin[1]
		|| clip->boxmaxs[2] < touch->v.absmin[2] )
			continue;

		if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0])
			continue;	// points never interact

	// might intersect, so do an exact clip
		if (clip->trace.allsolid)
			return;
		if (clip->passedict)
		{
		 	if (touch->v.owner == clip->passedict)
				continue;	// don't clip against own missiles
			if (clip->passedict->v.owner == touch)
				continue;	// don't clip against owner
		}

		if ((int)touch->v.flags & FL_MONSTER)
			trace = SVHL_ClipMoveToEntity (touch, clip->start, clip->mins2, clip->maxs2, clip->end, clip->hullnum, clip->type & MOVE_HITMODEL, clip->clipmask);
		else
			trace = SVHL_ClipMoveToEntity (touch, clip->start, clip->mins, clip->maxs, clip->end, clip->hullnum, clip->type & MOVE_HITMODEL, clip->clipmask);
		if (trace.allsolid || trace.startsolid ||
		trace.fraction < clip->trace.fraction)
		{
			trace.ent = touch;
			clip->trace = trace;
		}
	}
	
// recurse down both sides
	if (node->axis == -1)
		return;

	if ( clip->boxmaxs[node->axis] > node->dist )
		SVHL_ClipToLinks ( node->children[0], clip );
	if ( clip->boxmins[node->axis] < node->dist )
		SVHL_ClipToLinks ( node->children[1], clip );
}

static void SVHL_MoveBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs)
{
#if 0
// debug to test against everything
boxmins[0] = boxmins[1] = boxmins[2] = -9999;
boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999;
#else
	int		i;
	
	for (i=0 ; i<3 ; i++)
	{
		if (end[i] > start[i])
		{
			boxmins[i] = start[i] + mins[i] - 1;
			boxmaxs[i] = end[i] + maxs[i] + 1;
		}
		else
		{
			boxmins[i] = end[i] + mins[i] - 1;
			boxmaxs[i] = start[i] + maxs[i] + 1;
		}
	}
#endif
}

/*
==================
SV_Move
==================
*/
trace_t SVHL_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, int forcehull, hledict_t *passedict)
{
	hlmoveclip_t	clip;
	int			i;
	int hullnum;

	memset ( &clip, 0, sizeof ( clip ) );
	if (forcehull)
		hullnum = forcehull;
	else if (sv_compatiblehulls.value)
		hullnum = 0;
	else
	{
		int diff;
		int best;
		hullnum = 0;
		best = 8192;
		//x/y pos/neg are assumed to be the same magnitute.
		//z pos/height are assumed to be different from all the others.
		for (i = 0; i < MAX_MAP_HULLSM; i++)
		{
			if (!sv.world.worldmodel->hulls[i].available)
				continue;
#define sq(x) ((x)*(x))
			diff = sq(sv.world.worldmodel->hulls[i].clip_maxs[2] - maxs[2]) +
				sq(sv.world.worldmodel->hulls[i].clip_mins[2] - mins[2]) +
				sq(sv.world.worldmodel->hulls[i].clip_maxs[1] - maxs[1]) +
				sq(sv.world.worldmodel->hulls[i].clip_mins[0] - mins[0]);
			if (diff < best)
			{
				best = diff;
				hullnum=i;
			}
		}
		hullnum++;
	}

	if (type & MOVE_NOMONSTERS)
		clip.clipmask = MASK_WORLDSOLID; /*solid only to world*/
	else if (maxs[0] - mins[0])
		clip.clipmask = MASK_BOXSOLID;	/*impacts playerclip*/
	else
		clip.clipmask = MASK_POINTSOLID;		/*ignores playerclip but hits everything else*/

// clip to world
	clip.trace = SVHL_ClipMoveToEntity ( &SVHL_Edict[0], start, mins, maxs, end, hullnum, false, clip.clipmask);

	clip.start = start;
	clip.end = end;
	clip.mins = mins;
	clip.maxs = maxs;
	clip.type = type;
	clip.passedict = passedict;
	clip.hullnum = hullnum;

	if (type & MOVE_MISSILE)
	{
		for (i=0 ; i<3 ; i++)
		{
			clip.mins2[i] = -15;
			clip.maxs2[i] = 15;
		}
	}
	else
	{
		VectorCopy (mins, clip.mins2);
		VectorCopy (maxs, clip.maxs2);
	}
	
// create the bounding box of the entire move
	SVHL_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs );

// clip to entities
	if (clip.type & MOVE_EVERYTHING)
		SVHL_ClipToEverything (&clip);
	else
		SVHL_ClipToLinks ( sv.world.areanodes, &clip );

	if (clip.trace.startsolid)
		clip.trace.fraction = 0;

	if (!clip.trace.ent)
		return clip.trace;

	return clip.trace;
}

#endif
