//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// spline2d.cc
//
// Vincent LE PRINCE <vincentleprince@users.sourceforge.net>
// Copyright (C) 2000-2005 Vincent LE PRINCE
// This file is part of the TRUEVISION Package

//   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */ 
//*******************************************************************************************
#include "include/spline2d.h"
#include "include/tvio.h"


// Lathe preview subdivisions
const int LatheSubdivision = 12;
const float SplineSubdivision = 10;

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

//**************************************
// Spline points
//**************************************
Spline2DPoint::Spline2DPoint( float a, float b )
{
x = a; y = b;
control1[0] = -0.08; control1[1]=-0.08;
control2[0] = 0.08; control2[1]=0.08;	
}

Spline2DPoint::Spline2DPoint( float a, float b , float c1x, float c1y, float c2x, float c2y )
{
x = a; y = b;
control1[0] = c1x; control1[1]=c1y;
control2[0] = c2x; control2[1]=c2y;	
}

void Spline2DPoint::display_rotated( float angle, bool invert )
 {
float cosa = cos( angle );
float sina = sin( angle );	 
if ( ! invert ) glNormal3f( cosa, 0, sina ); else glNormal3f( -cosa, 0, -sina );
glVertex3f( cosa*x, y, sina*x ); 
}

void Spline2DPoint::save( ofstream & file )
{
file << "\n\tPOINT{";
file << " X=" <<  x << " Y=" <<  y ;
file << " C1X=" << control1[0] << " C1Y=" << control1[1] ;
file << " C2X=" << control2[0] << " C2Y=" << control2[1] ;
file << " }";
}

void Spline2DPoint::load(  ifstream & file, char *tag )
{
char * val = NULL;
do
	{
	val = tvio_get_next_val( file );
	if ( val == NULL ) return;
	if ( ! strcmp( val, "X" ) ) 	{ x = tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "Y" ) ) 	{ y = tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "C1X" ) ) 	{ control1[0] = tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "C1Y" ) ) 	{ control1[1]= tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "C2X" ) ) 	{ control2[0] = tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "C2Y" ) ) 	{ control2[1] = tvio_get_value_as_float( file ); continue; }
	}
while( val != NULL );
}

void Spline2DPoint::output_to_povray( ofstream & file , int spline_type, bool first, bool for_lathe )
{
if ( for_lathe )
{
if ( spline_type != TV_SPLINE2D_BEZIER )
	file << "<" << x << ", " << y << ">";
else
	{
	if ( ! first ) file << "<" << x+control1[0] << ", " << y+control1[1] << ">, ";
	file << "<" << x << ", " << y << ">";
	if ( first ) file << ", <" << x+control2[0] << ", " << y+control2[1] << "> ";
	}
}
else {
if ( spline_type != TV_SPLINE2D_BEZIER )
	file << "<" << x << ", " << -y << ">";
else
	{
	if ( ! first ) file << "<" << x+control1[0] << ", " << -y-control1[1] << ">, ";
	file << "<" << x << ", " << -y << ">";
	if ( first ) file << ", <" << x+control2[0] << ", " << -y-control2[1] << "> ";
	}
}

}


//*********************************************************
// Spline 2d
//*********************************************************

Spline2D::Spline2D( SplineType atype, int asubdivisions )
{
type = atype;
subdivisions = asubdivisions;
}

Spline2D::~Spline2D()
{
for( unsigned int i = 0 ; i < Points.size() ; i++ )
	delete Points[i];
}

Spline2D * Spline2D::auto_copy()
{
Spline2D *nspline = new Spline2D( type, subdivisions );
nspline->set_for_lathe( for_lathe );
for ( unsigned int i = 0 ; i < Points.size() ; i++ )
	nspline->add_point( Points[i]->auto_copy() );
return nspline;
}

void Spline2D::add_point( float x, float y )
{
Spline2DPoint *pt = new Spline2DPoint( x, y );
Points.push_back( pt );
}

void Spline2D::add_point( float x, float y, float c1x, float c1y, float c2x, float c2y )
{
Spline2DPoint *pt = new Spline2DPoint( x, y, c1x, c1y, c2x, c2y );
Points.push_back( pt );
}

void Spline2D::set_point( int i, float x, float y, float c1x, float c1y, float c2x, float c2y )
{
if ( for_lathe ) 
	{
	if ( x < 0 ) x = 0; 
	if ( x+c1x < 0 ) c1x = -x; 
	if ( x+c2x < 0 ) c2x = -x; 
	}
Points[i]->set( x, y, c1x, c1y, c2x, c2y ); 
}

void Spline2D::append( )
{
float x = Points.back()->getx() + 0.1;
float y = Points.back()->gety() + 0.1;
add_point( x, y );
}

void Spline2D::prepend()
{
float x = Points[0]->getx() - 0.1;
float y = Points[0]->gety() - 0.1;
if ( for_lathe && x < 0 ) x = 0;
Spline2DPoint *pt = new Spline2DPoint( x, y );
Points.insert( Points.begin(), pt );
}

void Spline2D::insert( int selected )
{
float x, y;
if ( selected > 0 )
	{
	x = ( Points[selected]->getx() + Points[selected-1]->getx() ) / 2.0;
	y = ( Points[selected]->gety() + Points[selected-1]->gety() ) / 2.0;
	if ( for_lathe && x < 0 ) x = 0;
	}
else
	{
	x = Points[selected]->getx() / 2.0;
	y = Points[selected]->gety() / 2.0;
	if ( for_lathe && x < 0 ) x = 0;
	}
Spline2DPoint *pt = new Spline2DPoint( x, y );
Points.insert( Points.begin() + selected, pt );
}

void Spline2D::delete_point( int selected  )
{
if ( Points.size() <= 4 ) return;
Points.erase(Points.begin()+selected);
}

// Save & Load
void Spline2D::save( ofstream & file )
{
file << "\nSPLINE2D{ ";
int psize = Points.size();
for ( int i =0 ; i < psize ; i++ )
	Points[i]->save( file );
file << " }";
}

bool Spline2D::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "SPLINE2D" ) ) return false;

Points.clear();
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( ! strcmp( tag, "POINT" ) ) 
		{
		Spline2DPoint *pt = new Spline2DPoint();
		pt->load( file, tag );
		Points.push_back( pt );
		continue;
		}
	
	tvio_skip_section(file );
	} while ( tag != NULL );

return true;
}

void Spline2D::output_to_povray( ofstream & file )
{
if ( ! for_lathe )
	{
	if ( type == TV_SPLINE2D_QUADRATIC || type == TV_SPLINE2D_CUBIC) add_point( Points[1]->getx(), Points[1]->gety() );
	if ( type == TV_SPLINE2D_LINEAR || type == TV_SPLINE2D_CUBIC) add_point( Points[0]->getx(), Points[0]->gety() );
	if ( type == TV_SPLINE2D_BEZIER ) 
		{
		float ctrl1[2]; float ctrl2[2];
		get_point_ctrl1( 0, ctrl1 ); get_point_ctrl2( 0, ctrl2 ); 
		add_point( Points[0]->getx(), Points[0]->gety(), ctrl1[0], ctrl1[1], ctrl2[0], ctrl2[1] );	
		}
	}
int psize = Points.size();
if ( type == TV_SPLINE2D_BEZIER )
	{	
	for ( int i = 0 ; i < psize-1 ; i++ )
		{
		Points[i]->output_to_povray( file, type,  true, for_lathe  );
		Points[i+1]->output_to_povray( file, type, false, for_lathe  );
		if ( i < psize - 2 ) file << ", ";
		}
	}
else
	{
	for ( int i = 0 ; i < psize ; i++ )
		{
		Points[i]->output_to_povray( file, type,  false, for_lathe  );
		if ( i < psize - 1 ) file << ", ";
		}
	}
	
if ( ! for_lathe )
	{
	Points.erase( Points.end()-1 );
	if ( type == TV_SPLINE2D_CUBIC ) Points.erase( Points.end()-1 );
	}	
}


void Spline2D::get_segments( int & ptnum, float * & xcoords, float * & ycoords )
{
if ( ! for_lathe )
	{
	if ( type == TV_SPLINE2D_QUADRATIC || type == TV_SPLINE2D_CUBIC) add_point( Points[1]->getx(), Points[1]->gety() );
	if ( type == TV_SPLINE2D_LINEAR || type == TV_SPLINE2D_CUBIC) add_point( Points[0]->getx(), Points[0]->gety() );
	if ( type == TV_SPLINE2D_BEZIER ) 
		{
		float ctrl1[2]; float ctrl2[2];
		get_point_ctrl1( 0, ctrl1 ); get_point_ctrl2( 0, ctrl2 ); 
		add_point( Points[0]->getx(), Points[0]->gety(), ctrl1[0], ctrl1[1], ctrl2[0], ctrl2[1] );	
		}
	}
	
int size = Points.size();
int segs = size;
if ( type == TV_SPLINE2D_QUADRATIC ) segs -= 1;
if ( type == TV_SPLINE2D_CUBIC ) segs -= 2;
if ( type != TV_SPLINE2D_LINEAR ) ptnum =  (segs-1) * (subdivisions+1)  +1;
else ptnum = segs;
xcoords = new float[ ptnum ];
ycoords = new float[ ptnum ];
float div = 1 / (float)subdivisions;

switch ( type )
	{
	case TV_SPLINE2D_LINEAR:
		{
		for ( int i = 0 ; i < ptnum ; i++ )
			{
			xcoords[i] = Points[i]->getx();
			ycoords[i] = Points[i]->gety();
			}
		}
		break;
		
	case TV_SPLINE2D_QUADRATIC:
		{
		int cseg = 0;
		for ( int i = 1 ; i < size-1 ; i++ )
			{
			float a[2], b[2], c[2];
			Points[i]->get( a );
			Points[i+1]->get( b );
			Points[i-1]->get( c );
									
			xcoords[cseg] = a[0];
			ycoords[cseg++] = a[1];
			for ( float t = 0 ; t < 1 ; t += div )
				{					
				float t2 = t*t;
				xcoords[cseg] = t2*( 0.5*c[0] - 1.0*a[0] + 0.5*b[0] ) + t*( -0.5*c[0] + 0.5*b[0] ) + a[0];
				ycoords[cseg++] = t2*( 0.5*c[1] - 1.0*a[1] + 0.5*b[1] ) + t*( -0.5*c[1] + 0.5*b[1] ) + a[1];
				}
			}
		xcoords[cseg] = Points.back()->getx();
		ycoords[cseg++] = Points.back()->gety();
		}
		break;
		
	case TV_SPLINE2D_CUBIC:
		{
		int cseg = 0;
		for ( int i = 1 ; i < size-2 ; i++ )
			{
			float a[2], b[2], c[2], d[2];
			Points[i]->get(a);
			Points[i+1]->get(b);
			Points[i-1]->get(c);
			Points[i+2]->get(d);
				
			xcoords[cseg] = a[0];
			ycoords[cseg++] = a[1];
			for ( float t = 0 ; t < 1 ; t += div )
				{
				float t2 = t*t; float t3 = t2*t;
				xcoords[cseg] = t3*( -0.5*c[0] + 1.5*a[0] - 1.5*b[0] + 0.5*d[0] ) + t2*( c[0] - 2.5*a[0] +2.0*b[0]- 0.5*d[0] ) + t*( -0.5*c[0] + 0.5*b[0] ) + a[0];
				ycoords[cseg++] = t3*( -0.5*c[1] + 1.5*a[1] - 1.5*b[1] + 0.5*d[1] ) + t2*( c[1] - 2.5*a[1] +2.0*b[1]- 0.5*d[1] ) + t*( -0.5*c[1] + 0.5*b[1] ) + a[1];
				}							
			}
		xcoords[cseg] = Points[size-2]->getx();
		ycoords[cseg++] = Points[size-2]->gety();
		}
		break;
		
	case TV_SPLINE2D_BEZIER:
		{
		int cseg = 0;
		for ( int i = 0 ; i < size-1 ; i++ )
			{
			float a[2], b[2], c[2], d[2];
			Points[i]->get(a);
			Points[i+1]->get(b);
			Points[i]->get_control2(c);
			c[0] += a[0]; c[1] += a[1]; 
			Points[i+1]->get_control1(d);
			d[0] += b[0];d[1] += b[1]; 
			
			xcoords[cseg] = a[0];
			ycoords[cseg++] = a[1];
			for ( float t = 0 ; t < 1 ; t += div )
				{
				float t2 = t*t; float t3 = t2*t;
				xcoords[cseg] = t3*( -a[0] + 3*c[0] - 3*d[0] + b[0] ) + t2*( 3*a[0] - 6*c[0] +3*d[0] ) + t*( -3*a[0] + 3*c[0] ) + a[0];
				ycoords[cseg++] = t3*( -a[1] + 3*c[1] - 3*d[1] + b[1] ) + t2*( 3*a[1] - 6*c[1] +3*d[1] ) + t*( -3*a[1] + 3*c[1] ) + a[1];			
				}				
			}
		xcoords[cseg] = Points[size-1]->getx();
		ycoords[cseg++] = Points[size-1]->gety();
		}
		break;
	}

if ( ! for_lathe )
	{
	Points.erase( Points.end()-1 );
	if ( type == TV_SPLINE2D_CUBIC ) Points.erase( Points.end()-1 );
	}
}



void Spline2D::gl_display_segments()
{
float *xcoords, *ycoords;
int ptnum;
get_segments( ptnum, xcoords, ycoords );
glBegin( GL_LINE_STRIP );
	if ( for_lathe )
		for ( int i = 0 ; i < ptnum ; i++ ) glVertex3f( xcoords[i], ycoords[i], 0 );
	else
		for ( int i = 0 ; i < ptnum ; i++ ) glVertex3f( xcoords[i], 0, ycoords[i] );
		
glEnd();
delete xcoords;
delete ycoords;
}
