// 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.
//
// DESCRIPTION:
// Uniform non-rational bspline class.
//

#include "g_local.h"
#include "BSpline.h"

void BSpline::Set( Vector *control_points_,int num_control_points_,splinetype_t type )
{
	int i;

	SetType( type );

	has_orientation = false;

	if ( control_points )
	{
		delete [] control_points;
		control_points = NULL;
	}

	num_control_points = num_control_points_;
	if ( num_control_points )
	{
		control_points = new BSplineControlPoint[ num_control_points ];
		assert( control_points );

		for( i = 0; i < num_control_points; i++ )
		{
			control_points[ i ].Set( control_points_[ i ] );
		}
	}
}

void BSpline::Set( Vector *control_points_,Vector *control_orients_,float  *control_speeds_,int num_control_points_,splinetype_t type )
{
	int i;

	SetType( type );

	has_orientation = true;

	if ( control_points )
	{
		delete [] control_points;
		control_points = NULL;
	}

	num_control_points = num_control_points_;
	if ( num_control_points )
	{
		control_points = new BSplineControlPoint[ num_control_points ];
		assert( control_points );

		for( i = 0; i < num_control_points; i++ )
		{
			control_points[ i ].Set( control_points_[ i ], control_orients_[ i ], control_speeds_[ i ] );
		}
	}
}

void BSpline::Clear( void )
{
	if( control_points )
	{
		delete [] control_points;
		control_points = NULL;
	}
	num_control_points = 0;
	has_orientation = false;
}

inline float BSpline::EvalNormal( float u,Vector& pos,Vector& orient )
{
	int segment_id;
	float B[ 4 ];
	float tmp;
	float u_2;
	float u_3;
	Vector ang;
	float roll;
	float speed;

	segment_id = ( int )u;
	if ( segment_id < 0 )
	{
		segment_id = 0;
	}
	if ( segment_id > num_control_points - 4 )
	{
		segment_id = num_control_points - 4;
	}
	u -= ( float )segment_id;

	u_2 = u * u;
	u_3 = u * u_2;

	tmp = 1 - u;
	B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f );
	B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f );
	B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f );
	B[ 3 ] = u_3 * ( 1.0f / 6.0f );

	pos =
		*control_points[ 0 + segment_id ].GetPosition() * B[ 0 ] +
		*control_points[ 1 + segment_id ].GetPosition() * B[ 1 ] +
		*control_points[ 2 + segment_id ].GetPosition() * B[ 2 ] +
		*control_points[ 3 + segment_id ].GetPosition() * B[ 3 ];

	ang =
		*control_points[ 0 + segment_id ].GetOrientation() * B[ 0 ] +
		*control_points[ 1 + segment_id ].GetOrientation() * B[ 1 ] +
		*control_points[ 2 + segment_id ].GetOrientation() * B[ 2 ] +
		*control_points[ 3 + segment_id ].GetOrientation() * B[ 3 ];

	roll =
		*control_points[ 0 + segment_id ].GetRoll() * B[ 0 ] +
		*control_points[ 1 + segment_id ].GetRoll() * B[ 1 ] +
		*control_points[ 2 + segment_id ].GetRoll() * B[ 2 ] +
		*control_points[ 3 + segment_id ].GetRoll() * B[ 3 ];

	speed =
		*control_points[ 0 + segment_id ].GetSpeed() * B[ 0 ] +
		*control_points[ 1 + segment_id ].GetSpeed() * B[ 1 ] +
		*control_points[ 2 + segment_id ].GetSpeed() * B[ 2 ] +
		*control_points[ 3 + segment_id ].GetSpeed() * B[ 3 ];

	orient = ang.toAngles();
	orient[ ROLL ] = roll;

	return speed;
}

inline float BSpline::EvalLoop( float t,Vector& pos,Vector& orient )
{
	Vector retval;
	Vector ang;
	float speed;
	float roll;
	int segment_id;
	int next_id;
	float B[ 4 ];
	float tmp;
	float u;
	float u_2;
	float u_3;
	int i;
	int j;

	segment_id = ( int )floor( t );
	u = t - floor( t );

	segment_id %= num_control_points;
	if ( segment_id < 0 )
	{
		segment_id += num_control_points;
	}

	u_2 = u * u;
	u_3 = u * u_2;

	tmp = 1 - u;
	B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f );
	B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f );
	B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f );
	B[ 3 ] = u_3 * ( 1.0f / 6.0f );

	speed = 0;
	roll = 0;

	for( i = 0, j = segment_id; i < 4; i++, j++ )
	{
		if ( j >= num_control_points )
		{
			j -= ( num_control_points - loop_control_point );
		}

		retval += *control_points[ j ].GetPosition() * B[ i ];
		ang += *control_points[ j ].GetOrientation() * B[ i ];
		speed += *control_points[ j ].GetSpeed() * B[ i ];
		roll += *control_points[ j ].GetRoll() * B[ i ];
	}

	pos = retval;

	next_id = segment_id + 1;
	if ( next_id >= num_control_points )
	{
		next_id -= ( num_control_points - loop_control_point );
	}
	orient = ang.toAngles();
	orient[ ROLL ] = roll;

	return speed;
}

inline float BSpline::EvalClamp( float t,Vector& pos,Vector& orient )
{
	Vector retval;
	Vector ang;
	int segment_id;
	int next_id;
	float B[ 4 ];
	float tmp;
	float u;
	float u_2;
	float u_3;
	int i;
	int j;
	float speed;
	float roll;

	segment_id = ( int )floor( t );
	u = t - floor( t );

	u_2 = u * u;
	u_3 = u * u_2;

	tmp = 1 - u;
	B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f );
	B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f );
	B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f );
	B[ 3 ] = u_3 * ( 1.0f / 6.0f );

	speed = 0;
	roll = 0;
	for( i = 0; i < 4; i++, segment_id++ )
	{
		j = segment_id;
		if ( j < 0 )
		{
			j = 0;
		}
		else if ( j >= num_control_points )
		{
			j = num_control_points - 1;
		}

		retval += *control_points[ j ].GetPosition() * B[ i ];
		ang += *control_points[ j ].GetOrientation() * B[ i ];
		speed += *control_points[ j ].GetSpeed() * B[ i ];
		roll += *control_points[ j ].GetRoll() * B[ i ];
	}

	pos = retval;

	next_id = segment_id + 1;
	if ( segment_id < 0 )
	{
		segment_id = 0;
	}
	if ( segment_id >= num_control_points )
	{
		segment_id = num_control_points - 1;
	}
	if ( next_id < 0 )
	{
		next_id = 0;
	}
	if ( next_id >= num_control_points )
	{
		next_id = num_control_points - 1;
	}
	orient = ang.toAngles();
	orient[ ROLL ] = roll;

	return speed;
}


Vector BSpline::Eval( float u )
{
	Vector pos;
	Vector orient;

	switch( curvetype )
	{
	default:
	case SPLINE_NORMAL:
		EvalNormal( u, pos, orient );
		break;

	case SPLINE_CLAMP:
		EvalClamp( u, pos, orient );
		break;

	case SPLINE_LOOP:
		EvalLoop( u, pos, orient );
		break;
	}
	return pos;
}

float BSpline::Eval( float u,Vector &pos,Vector &orient )
{
	switch( curvetype )
	{
	default:
	case SPLINE_NORMAL:
		return EvalNormal( u, pos, orient );
		break;

	case SPLINE_CLAMP:
		return EvalClamp( u, pos, orient );
		break;

	case SPLINE_LOOP:
		return EvalLoop( u, pos, orient );
		break;
	}
}

void BSpline::DrawControlSegments( void )
{
	int i;

	G_BeginLine();
	for( i = 0; i < num_control_points; i++ )
	{
		G_Vertex( *control_points[ i ].GetPosition() );
	}
	G_EndLine();
}

void BSpline::DrawCurve( int num_subdivisions )
{
	float u;
	float du;

	du = 1.0f / ( float )num_subdivisions;

	G_BeginLine();
	for( u = 0.0f; u <= ( float )num_control_points; u += du )
	{
		G_Vertex( ( Vector )Eval( u ) );
	}
	G_EndLine();
}

void BSpline::DrawCurve( Vector offset,int num_subdivisions )
{
	float u;
	float du;

	du = 1.0f / ( float )num_subdivisions;

	G_BeginLine();
	for( u = 0.0f; u <= ( float )num_control_points; u += du )
	{
		G_Vertex( offset + ( Vector )Eval( u ) );
	}
	G_EndLine();
}

void BSpline::AppendControlPoint( const Vector& new_control_point )
{
	BSplineControlPoint *old_control_points;
	int i;

	old_control_points = control_points;
	num_control_points++;

	control_points = new BSplineControlPoint[num_control_points];
	assert( control_points );

	if ( old_control_points )
	{
		for( i = 0; i < num_control_points - 1; i++ )
		{
			control_points[ i ] = old_control_points[ i ];
		}
		delete [] old_control_points;
	}

	control_points[ num_control_points - 1 ].Set( new_control_point );
}

void BSpline::AppendControlPoint( const Vector& new_control_point,const float& speed )
{
	BSplineControlPoint *old_control_points;
	int i;

	old_control_points = control_points;
	num_control_points++;

	control_points = new BSplineControlPoint[num_control_points];
	assert( control_points );

	if ( old_control_points )
	{
		for( i = 0; i < num_control_points - 1; i++ )
		{
			control_points[ i ] = old_control_points[ i ];
		}
		delete [] old_control_points;
	}

	control_points[ num_control_points - 1 ].Set( new_control_point, speed );
}

void BSpline::AppendControlPoint( const Vector& new_control_point,const Vector& new_control_orient,const float& new_control_speed )
{
	BSplineControlPoint *old_control_points;
	int i;

	has_orientation = true;

	old_control_points = control_points;
	num_control_points++;

	control_points = new BSplineControlPoint[num_control_points];
	assert( control_points );

	if ( old_control_points )
	{
		for( i = 0; i < num_control_points - 1; i++ )
		{
			control_points[ i ] = old_control_points[ i ];
		}
		delete [] old_control_points;
	}

	control_points[ num_control_points - 1 ].Set( new_control_point, new_control_orient, new_control_speed );
}

void BSpline::SetLoopPoint( const Vector& pos )
{
	int i;

	for( i = 0; i < num_control_points; i++ )
	{
		if ( pos == *control_points[ i ].GetPosition() )
		{
			loop_control_point = i;
			break;
		}
	}
}

int BSpline::PickControlPoint( const Vector& window_point,float pick_size )
{
	int i;
	float closest_dist_2;
	int closest_index;
	float dist_2;
	Vector delta;

	closest_index = -1;
	closest_dist_2 = 1000000.0f;
	for( i = 0; i < num_control_points; i++ )
	{
		delta = window_point - *control_points[ i ].GetPosition();
		dist_2 = delta * delta;
		if ( dist_2 < closest_dist_2 )
		{
			closest_dist_2 = dist_2;
			closest_index = i;
		}
	}

	if ( pick_size * pick_size >= closest_dist_2 )
	{
		return closest_index;
	}
	else
	{
		return -1;
	}
}

CLASS_DECLARATION( Entity, SplinePath, "info_splinepath" );

Event EV_SplinePath_Create( "SplinePath_create" );

ResponseDef SplinePath::Responses[] =
{
	{ &EV_SplinePath_Create,     ( Response )SplinePath::CreatePath },
	{ NULL, NULL }
};

SplinePath::SplinePath()
{
	owner = this;
	next = NULL;
	loop = NULL;
	loop_name = G_GetStringArg( "loop" );
	angles = G_GetVectorArg( "angles" );
	speed = G_GetFloatArg( "speed", 1 );
	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );
	hideModel();

	if ( !LoadingSavegame )
	{
		PostEvent( EV_SplinePath_Create, 0 );
	}
}

void SplinePath::CreatePath( Event *ev )
{
	const char  *target;
	int num;

	// Make the path from the targetlist.
	target = Target();
	if ( target[ 0 ] )
	{
		if ( num = G_FindTarget( 0, target ) )
		{
			next = ( SplinePath * )G_GetEntity( num );
			next->owner = this;
		}
		else
		{
			gi.error( "SplinePath::CreatePath: target %s not found\n", target );
		}
	}
	if ( loop_name.length() )
	{
		if ( num = G_FindTarget( 0, loop_name.c_str() ) )
		{
			loop = ( SplinePath * )G_GetEntity( num );
		}
	}
}

SplinePath *SplinePath::GetNext( void )
{
	return next;
}

SplinePath *SplinePath::GetLoop( void )
{
	return loop;
}
