
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

#include "afx/arcaneFX.h"
#include "afx/util/afxCurveEval.h"
#include "afx/util/afxCurve3D.h"

afxCurve3D::afxCurve3D() : usable( false ), default_vector( 0, 0, 0 ), flip(false)
{
	evaluator = new afxHermiteEval();
}

afxCurve3D::~afxCurve3D()
{
	delete evaluator;
}

void afxCurve3D::addPoint( F32 param, Point3F &v )
{
	if( param < 0.0f || param > 1.0f )
		return;

	CurvePoint p;
	p.parameter = param;
	p.point.set( v );
	
	points.push_back( p );	

	usable = false;	
}	

void afxCurve3D::setPoint( int index, Point3F &v )
{
	if( ( index < 0 ) || ( index >= points.size() ) )
		return;

	CurvePoint &p = points[index];
	p.point = v;

	if( index == 0 )
		start_value = v;
	else if( index == points.size()-1 )
		final_value = v;
}

//bool afxCurve3D::compare_CurvePoint( const afxCurve3D::CurvePoint &a, const afxCurve3D::CurvePoint &b ) 
//{
//	return a.parameter < b.parameter;
//}

S32 QSORT_CALLBACK afxCurve3D::compare_CurvePoint( const void* a, const void* b )
{
	//CurvePoint *cp_a = *((CurvePoint **)a);
  //CurvePoint *cp_b = *((CurvePoint **)b);
  
	const CurvePoint *cp_a = (CurvePoint *)a;
	const CurvePoint *cp_b = (CurvePoint *)b;

  //Con::printf( "*** %f %f", cp_a->parameter, cp_b->parameter  );

	//return cp_a->parameter < cp_b->parameter;
	//return 1;

	if (cp_a->parameter > cp_b->parameter)
      return 1;
   else if (cp_a->parameter < cp_b->parameter)
      return -1;
   else
      return 0;
}

void afxCurve3D::sort( )
{
	if( points.size() == 0 )
		return;

	if( points.size() == 1 )
	{
		start_value   = points[0].point;
		final_value   = start_value;
		usable = true;
		return;
	}

	//Con::printf( "*** pre-sort" );
	//std::sort( points.begin(), points.end(), afxCurve3D::compare_CurvePoint );
	dQsort( points.address(), points.size(), sizeof(CurvePoint), afxCurve3D::compare_CurvePoint );
	//Con::printf( "*** post-sort" );

	start_value = points[0].point;
	final_value = points[points.size()-1].point;

	usable = true;

	start_tangent = evaluateTangent( 0.0f );
	final_tangent = evaluateTangent( 1.0f );
}	

int afxCurve3D::numPoints()
{
	return points.size();
}

F32 afxCurve3D::getParameter( int index )
{
	if( ( index < 0 ) || ( index >= points.size() ) )
		return 0.0f;

	return points[index].parameter;
}

Point3F afxCurve3D::getPoint( int index )
{
	if( ( index < 0 ) || ( index >= points.size() ) )
		return default_vector;

	return points[index].point;
}

Point3F afxCurve3D::evaluate( F32 param )
{
	if( !usable )
		return default_vector;

	if( param <= 0.0f )
		return start_value;
	
	if( param >= 1.0f )
		return final_value;

	if( points.size() == 1 )
		return start_value;

	int start_index = 0;
	for( ; start_index < points.size()-1; start_index++ )
	{
		if( param < points[start_index+1].parameter )
			break;
	}
	int end_index = start_index+1;

	CurvePoint p0 = points[start_index];
	CurvePoint p1 = points[end_index];

	// Compute tangents
	//Point3F tan0 = computeTangentP0( p0.point, p1.point, start_index );
	//Point3F tan1 = computeTangentP1( p0.point, p1.point, end_index );
	

	F32 local_param = ( param - p0.parameter ) / ( p1.parameter - p0.parameter );

	//Point3F vnew = evaluator->evaluateCurve( p0.point,
	//																				p1.point,
	//																				tan0,
	//																				tan1,
	//																				local_param );

   Point3F vnew = evaluator->evaluateCurve( p0.point,   p1.point,
														  p0.tangent, p1.tangent,
														  local_param );
	return vnew;
}

Point3F afxCurve3D::evaluateTangent( F32 param )
{
	if( !usable )
		return default_vector;

	if( param < 0.0f )
		return start_tangent;
	
	if( param > 1.0f )
		return final_tangent;

	if( points.size() == 1 )
		return start_tangent;

	int start_index = 0;
	for( ; start_index < points.size()-1; start_index++ )
	{
		if( param < points[start_index+1].parameter )
			break;
	}
	int end_index = start_index+1;

	if( param == 1.0f )
	{
		end_index = points.size()-1;
		start_index = end_index - 1;
	}

	CurvePoint p0 = points[start_index];
	CurvePoint p1 = points[end_index];
	
	// Compute tangents
	//Point3F tan0 = computeTangentP0( p0.point, p1.point, start_index );
	//Point3F tan1 = computeTangentP1( p0.point, p1.point, end_index );

	F32 local_param = ( param - p0.parameter ) / ( p1.parameter - p0.parameter );

	//Point3F vnew = evaluator->evaluateCurveTangent( p0.point,
	//																								p1.point,
	//																								tan0,
	//																								tan1,
	//																								local_param );
   Point3F vnew = evaluator->evaluateCurveTangent( p0.point,   p1.point,
														         p0.tangent, p1.tangent,
														         local_param );

	return vnew;
}

Point3F afxCurve3D::computeTangentP0( Point3F &p0, Point3F &p1, int start_index )
{
	Point3F tan0;
	
	Point3F p_prev;
	Point3F p_next;

		// tangent for p0
	if( start_index == 0 )
	{
		p_prev = p0;  	// Setting previous point to p0, creating a hidden point in
										//  the same spot
		p_next = p1;
	}
	else
	{
		CurvePoint &p = points[start_index-1];
		p_prev = p.point;
		p_next = p1;
	}
	tan0 = p_next-p_prev; //p_next.subtract( p_prev );
	tan0 *= .5f; //= tan0.scale( .5f );

	return tan0;
}

Point3F afxCurve3D::computeTangentP1( Point3F &p0, Point3F &p1, int end_index )
{
	Point3F tan1;
	
	Point3F p_prev;
	Point3F p_next;

		// tangent for p1
	if( end_index == points.size()-1 )
	{
		p_prev = p0;  
		p_next = p1;	// Setting next point to p1, creating a hidden point in
									//  the same spot
	}
	else
	{
		p_prev = p0;
		CurvePoint &p = points[end_index+1];
		p_next = p.point;
	}
	tan1 = p_next-p_prev; //p_next.subtract( p_prev );
	tan1 *= .5f; //= tan1.scale( .5f );

	//Con::printf("UPDATE");
	return tan1;
}

void afxCurve3D::computeTangents()
{
	CurvePoint *p_prev;
	CurvePoint *p_next;

   for( int i = 0; i < points.size(); i++ )
	{
		CurvePoint *p = &points[i];
		
      if( i == 0 )
      {
         p_prev = p;  	// Setting previous point to p0, creating a hidden point in
								//  the same spot
		   p_next = &points[i+1];
      }
      else if( i == points.size()-1 )
	   {
		   p_prev = &points[i-1];  
		   p_next = p;	   // Setting next point to p1, creating a hidden point in
	   						//  the same spot
	   }
      else
	   {		   
		   p_prev = &points[i-1];
		   p_next = &points[i+1];
	   }

      p->tangent = p_next->point - p_prev->point;
      //(p->tangent).normalize();
      p->tangent *= .5f;

      //Con::printf( "%d: %f %f %f", i, p->tangent.x, p->tangent.y, p->tangent.z );
   }
}

void afxCurve3D::print()
{
	Con::printf( "afxCurve3D -------------------------" );
	for( int i = 0; i < points.size(); i++ )
	{
		CurvePoint &p = points[i];
		Con::printf( "%f: %f %f %f", p.parameter, p.point.x, p.point.y, p.point.z );
	}
	Con::printf( "---------------------------------" );
}
