//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/gravpath.cpp                     $
// $Revision:: 18                                                             $
//   $Author:: Markd                                                          $
//     $Date:: 10/24/98 12:42a                                                $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/gravpath.cpp                          $
// 
// 18    10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 17    10/21/98 2:24a Jimdose
// Made GravPath not add to the gravPathManager while loading savegames
// 
// 16    10/09/98 5:22p Aldie
// Changed commands to activate and deactivate
// 
// 15    10/07/98 11:52p Jimdose
// Added destructor for GravPathManager
// 
// 14    9/30/98 4:34p Aldie
// Free gravpath when level changes
// 
// 13    9/30/98 1:17p Markd
// Fixed gravpath null pointer checks
// 
// 12    9/29/98 5:33p Aldie
// Added force gravity to the path
// 
// 11    8/29/98 9:43p Jimdose
// Added call info to G_Trace
// 
// 10    8/27/98 2:55p Aldie
// Added a headnode spawnflag
// 
// 9     8/27/98 2:33p Aldie
// Added functionality so gravpaths work out of water
// 
// 8     8/26/98 8:46p Aldie
// Added activate and deactivate commands
// 
// 7     5/26/98 4:45p Aldie
// Fixed an error printf
// 
// 6     5/25/98 2:28p Aldie
// Fixed issues with not loading game dll
// 
// 5     5/23/98 10:21p Aldie
// Removed drawing of gravpath.
// 
// 4     5/23/98 5:15p Aldie
// Removed traces checking for obstacles when finding the closest grav point.
// 
// 3     5/22/98 12:24p Aldie
// Updated defaults
// 
// 2     5/22/98 12:19p Aldie
// First version of gravity path
//
// DESCRIPTION:
// Gravity path - Used for underwater currents and wells.

#include "g_local.h"
#include "entity.h"
#include "gravpath.h"
#include "container.h"
#include "navigate.h"
#include "misc.h"
#include "player.h"

GravPathManager gravPathManager;

CLASS_DECLARATION(Class, GravPathManager, NULL);

ResponseDef GravPathManager::Responses[] = 
   {
      { NULL,NULL }
   };

GravPathManager::~GravPathManager()
   {
   Reset();
   }

void GravPathManager::Reset( void )
   {
   while( pathList.NumObjects() > 0 )
		{
		delete ( GravPath * )pathList.ObjectAt( 1 );
      }

   pathList.FreeObjectList();
   }

void GravPathManager::AddPath(GravPath *p)
	{
   int num;
	num = pathList.AddObject( p );
   pathList.Resize( pathList.NumObjects() );
	}

void GravPathManager::RemovePath(GravPath *p)
	{
	pathList.RemoveObject( p );
   pathList.Resize( pathList.NumObjects() );
	}

Vector GravPathManager::CalculateGravityPull(Entity &ent, Vector pos, qboolean *force)
   {
   int            i,num;
   GravPath       *p;
   GravPathNode   *node;
   Vector         point;
   Vector         newpoint;
   Vector         dir;
   float          bestdist = 99999;
   float          dist;
   float          speed;
   float          radius;
   Vector         velocity;
   int            bestpath = 0;
   int            entity_contents, grav_contents;

 	num = pathList.NumObjects();

   entity_contents = gi.pointcontents( ent.worldorigin.vec3() );

   for( i = 1; i <= num; i++ )
		{
		p = ( GravPath * )pathList.ObjectAt( i );

      if ( !p )
         continue;
      
      // Check to see if path is active
      node = p->GetNode( 1 );
      if ( !node || !node->active )
         continue;

      // Check to see if the contents are the same
      grav_contents = gi.pointcontents( node->worldorigin.vec3() );

      // If grav node is in water, make sure ent is too.
      if ( ( grav_contents & CONTENTS_WATER ) && !( entity_contents & CONTENTS_WATER ) )
         continue;

      // Test to see if we are in this path's bounding box
      if ( (pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) && 
           (pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z) )
         {
         point = p->ClosestPointOnPath(pos, ent, &dist, &speed, &radius);      

         // If the closest distance on the path is greater than the radius, then 
         // do not consider this path.

         if (dist > radius)
            {
            continue;
            }
         else if (dist < bestdist)
            {
            bestpath = i;
            bestdist  = dist;
            }
         }
      }
   
   if (!bestpath)
      {
      return vec_zero;
      }

   p = ( GravPath * )pathList.ObjectAt( bestpath );
   if ( !p )
      return velocity;
   *force    = p->force;
   dist     = p->DistanceAlongPath(pos, &speed);
   newpoint = p->PointAtDistance( dist + speed);
   dir      = newpoint-pos;
   dir.normalize();
   velocity = dir * speed;
   return velocity;
   }

/*****************************************************************************/
/*SINED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE
 "radius" Radius of the effect of the pull (Default is 256)
 "speed"  Speed of the pull (Use negative for a repulsion) (Default is 100)

  Set HEADNODE to signify the head of the path.
  Set FORCE if you want un-fightable gravity ( i.e. can't go backwards )
/*****************************************************************************/

CLASS_DECLARATION( Entity, GravPathNode, "info_grav_pathnode" );

Event EV_GravPath_Create( "gravpath_create" );
Event EV_GravPath_Activate( "activate" );
Event EV_GravPath_Deactivate( "deactivate" );

ResponseDef GravPathNode::Responses[] =
	{
      { &EV_GravPath_Create,           ( Response )GravPathNode::CreatePath },
      { &EV_GravPath_Activate,         ( Response )GravPathNode::Activate },
      { &EV_GravPath_Deactivate,       ( Response )GravPathNode::Deactivate },
      { NULL, NULL }
   };

GravPathNode::GravPathNode()
	{
	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );
   hideModel();

   speed    = G_GetFloatArg( "speed",100.0f );
   radius   = G_GetFloatArg( "radius",256.0f );
   headnode = spawnflags & 1;
   active   = true;

   // This is the head of a new path, post an event to create the path
   if ( headnode )
      {
      PostEvent( EV_GravPath_Create, 0 );
      }
	}

float GravPathNode::Speed( void )
   {
   if ( active )
      return speed;
   else
      return 0;
   };

void GravPathNode::Activate(Event *ev)
   {
   GravPathNode   *node;
   int            num;
   const char     *target;
   
   active = true;
   node = this;
   // Go through the entire path and activate it
   target = node->Target();
   while (target[0])
      {
      if (num = G_FindTarget(0, target))
         {
         node = (GravPathNode *)G_GetEntity( num );
         assert( node );
         node->active = true;
         }
      else
         {
         gi.error("GravPathNode::CreatePath: target %s not found\n",target);
         }
      target = node->Target();
      }
   }

void GravPathNode::Deactivate(Event *ev)
   {
   GravPathNode   *node;
   int            num;
   const char     *target;
   
   active = false;
   node = this;
   // Go through the entire path and activate it
   target = node->Target();
   while (target[0])
      {
      if (num = G_FindTarget(0, target))
         {
         node = (GravPathNode *)G_GetEntity( num );
         assert( node );
         node->active = false;
         }
      else
         {
         gi.error("GravPathNode::CreatePath: target %s not found\n",target);
         }
      target = node->Target();
      }
   }

void GravPathNode::CreatePath(Event *ev)
   {
   const char     *target;
   GravPath       *path = new GravPath;
   GravPathNode   *node;
   int            num;

   ClearBounds(path->mins.vec3(),path->maxs.vec3());

   // This node is the head of a path, create a new path in the path manager.
   // and add it in, then add all of it's children in the path.
   node = this;
   path->AddNode(node);
   path->force = spawnflags & 2;

   // Make the path from the targetlist.
   target = node->Target();
   while (target[0])
      {
      if (num = G_FindTarget(0, target))
         {
         node = (GravPathNode *)G_GetEntity( num );
         assert( node );
         path->AddNode(node);
         }
      else
         {
         gi.error("GravPathNode::CreatePath: target %s not found\n",target);
         }
      target = node->Target();
      }

   // Set the origin.
   path->origin = path->mins + path->maxs;
   path->origin *= 0.5f;
   }

CLASS_DECLARATION( Listener, GravPath, NULL );

Event EV_DrawGravPath( "drawpath" );

ResponseDef GravPath::Responses[] =
	{
      { &EV_DrawGravPath, (Response)GravPath::DrawPath },
		{ NULL, NULL }
	};

GravPath::GravPath()
	{
	// Event *event;

   pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;

   if ( !LoadingSavegame )
      {
      gravPathManager.AddPath(this);
      }

   // event = new Event(EV_DrawGravPath);
   // event->AddFloat(1);
   // event->AddFloat(0);
   // event->AddFloat(0);
   // PostEvent(event,0.1f);
	}

GravPath::~GravPath()
	{
	pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;
   gravPathManager.RemovePath(this);
	}

void GravPath::Clear
	(
	void
	)

	{
	nextnode = 1;
	pathlength = 0;
	from = NULL;
	to = NULL;
	pathlist.FreeObjectList();
	}

void GravPath::Reset
	(
	void
	)

	{
	nextnode = 1;
	}

GravPathNode *GravPath::Start
	(
	void
	)

	{
	return from;
	}

GravPathNode *GravPath::End
	(
	void
	)

	{
	return to;
	}

void GravPath::AddNode
	(
	GravPathNode *node
	)

	{
	int num;
   Vector r,addp;

	if ( !from )
		{
		from = node;
		}

	to = node;
	pathlist.AddObject( GravPathNodePtr( node ) );

	num = NumNodes();
	if ( num > 1 )
		{
		pathlength += ( node->worldorigin - GetNode( num )->worldorigin ).length();
		}

   r.setXYZ(node->Radius(),node->Radius(),node->Radius());
   addp = node->worldorigin + r;
   AddPointToBounds(addp.vec3(),mins.vec3(),maxs.vec3());
   addp = node->worldorigin - r;
   AddPointToBounds(addp.vec3(),mins.vec3(),maxs.vec3());
	}

GravPathNode *GravPath::GetNode
	(
	int num
	)

	{
	return pathlist.ObjectAt( num );
	}

GravPathNode *GravPath::NextNode
	(
	void
	)

	{
	if ( nextnode <= NumNodes() )
		{
		return pathlist.ObjectAt( nextnode++ );
		}
	return NULL;
	}

Vector GravPath::ClosestPointOnPath
	(
	Vector pos,
	Entity &ent,
   float  *ret_dist,
   float  *speed,
   float  *radius
	)

	{
	GravPathNode	*s;
	GravPathNode	*e;
	int	      	num;
	int		      i;
	float		      bestdist;
	Vector	      bestpoint;
	float		      dist;
	float		      segmentlength;
	Vector	      delta;
	Vector	      p1;   
	Vector      	p2;
	Vector	      p3;
	float		      t;
	trace_t	      trace;

	num = NumNodes();
	s = GetNode( 1 );
	trace = G_Trace( pos, ent.mins, ent.maxs, s->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 1" );
	bestpoint = s->worldorigin;
	delta = bestpoint - pos;
	bestdist = delta.length();
   *speed = s->Speed();
   *radius = s->Radius();

	for( i = 2; i <= num; i++ )
		{
		e = GetNode( i );

		// check if we're closest to the endpoint
		delta = e->worldorigin - pos;
		dist = delta.length();

		if ( dist < bestdist )
			{
			trace = G_Trace( pos, ent.mins, ent.maxs, e->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 2" );
			bestdist = dist;
			bestpoint = e->worldorigin;
         *speed = e->Speed();
         *radius = e->Radius();
			}

		// check if we're closest to the segment
		p1 = e->worldorigin - s->worldorigin;
		segmentlength = p1.length();
		p1	 *= 1 / segmentlength;
		p2 = pos - s->worldorigin;

		t = p1 * p2;
		if ( ( t > 0 ) && ( t < segmentlength ) )
			{
			p3 = ( p1 * t ) + s->worldorigin;

			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
				{
				trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 3" );
				bestdist = dist;
				bestpoint = p3;
            *speed  = (e->Speed() * t) + (s->Speed() * (1.0f - t));
            *radius = (e->Radius() * t) + (s->Radius() * (1.0f - t));
				}
			}

		s = e;
		}
   *ret_dist = bestdist;
	return bestpoint;
	}

float GravPath::DistanceAlongPath
	(
	Vector pos,
   float *speed
	)

	{
	GravPathNode	*s;
	GravPathNode	*e;
	int	      	num;
	int		      i;
	float		      bestdist;
	float		      dist;
	float		      segmentlength;
	Vector	      delta;
	Vector	      segment;
	Vector      	p1;
	Vector	      p2;
	Vector	      p3;
	float		      t;
	float		      pathdist;
	float		      bestdistalongpath;
   float          oosl;
	pathdist = 0;

	num = NumNodes();
	s = GetNode( 1 );
	delta = s->worldorigin - pos;
	bestdist = delta.length();
	bestdistalongpath = 0;
   *speed = s->Speed();

	for( i = 2; i <= num; i++ )
		{
		e = GetNode( i );

		segment = e->worldorigin - s->worldorigin;
		segmentlength = segment.length();

		// check if we're closest to the endpoint
		delta = e->worldorigin - pos;
		dist = delta.length();

		if ( dist < bestdist )
			{
			bestdist = dist;
			bestdistalongpath = pathdist + segmentlength;
         *speed = e->Speed();
			}

		// check if we're closest to the segment
      oosl = ( 1 / segmentlength );
		p1 = segment * oosl;
		p1.normalize();
		p2 = pos - s->worldorigin;

		t = p1 * p2;
		if ( ( t > 0 ) && ( t < segmentlength ) )
			{
			p3 = ( p1 * t ) + s->worldorigin;

			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
				{
				bestdist = dist;
				bestdistalongpath = pathdist + t;

            t *= oosl;
            *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t));
            }
			}

		s = e;
		pathdist += segmentlength;
		}

	return bestdistalongpath;
	}

Vector GravPath::PointAtDistance
	(
	float dist
	)

	{
	GravPathNode	*s;
	GravPathNode	*e;
	int      		num;
	int		      i;
	Vector	      delta;
	Vector	      p1;
	float	      	t;
	float		      pathdist;
	float		      segmentlength;

	num = NumNodes();
	s = GetNode( 1 );
	pathdist = 0;

	for( i = 2; i <= num; i++ )
		{
		e = GetNode( i );

		delta = e->worldorigin - s->worldorigin;
		segmentlength = delta.length();

		if ( ( pathdist + segmentlength ) > dist )
			{
			t = dist - pathdist;

			p1 = delta * ( t / segmentlength );
			return p1 + s->worldorigin;
			}

		s = e;
		pathdist += segmentlength;
		}

	// cap it off at start or end of path
	return s->worldorigin;
	}

void GravPath::DrawPath
	(
   Event *ev
   )

	{
	Vector	      s;
	Vector	      e;
	Vector	      offset;
	GravPathNode	*node;
	int		      num;
	int		      i;
   float          r = ev->GetFloat(1);
   float          g = ev->GetFloat(2);
   float          b = ev->GetFloat(3);
   Event          *event;

	num = NumNodes();
	node = GetNode( 1 );
	s = node->worldorigin;
	offset = Vector( r, g, b ) * 4 + Vector( 0, 0, 0 );
   offset = Vector(0, 0, 0);

	for( i = 2; i <= num; i++ )
		{
		node = GetNode( i );
		e = node->worldorigin;

		G_DebugLine( s + offset, e + offset, r, g, b, 1 );
		s = e;
		}

   G_DebugBBox(origin,mins-origin,maxs-origin,1,0,0,1);

   event = new Event(EV_DrawGravPath);
   event->AddFloat(r);
   event->AddFloat(g);
   event->AddFloat(b);
   PostEvent(event,0.1f);
	}

int GravPath::NumNodes
	(
	void
	)

	{
	return pathlist.NumObjects();
	}

float GravPath::Length
	(
	void
	)

	{
	return pathlength;
	}
