//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// spline3d.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/spline3d.h"
#include "include/tvio.h"


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

//**************************************
// Spline points
//**************************************
Spline3DPoint::Spline3DPoint( float a, float b, float c )
{
x = a; y = b; z = c;
extra_param = 0.0;
}

Spline3DPoint::Spline3DPoint( float a, float b , float c, float extra )
{
x = a; y = b; z = c;
extra_param = extra;
}

void Spline3DPoint::save( ofstream & file )
{
file << "\n\tPOINT{";
file << " X=" <<  x << " Y=" <<  y << " Z=" << z;
file << " EXT=" << extra_param;
file << " }";
}

void Spline3DPoint::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, "Z" ) ) 	{ z = tvio_get_value_as_float( file ); continue; }
	if ( ! strcmp( val, "EXT" ) ) 	{ extra_param = tvio_get_value_as_float( file ); continue; }
	}
while( val != NULL );
}

void Spline3DPoint::output_to_povray( ofstream & file , int spline_type, bool first, bool for_lathe )
{
}


//*********************************************************
// Spline 3d
//*********************************************************

Spline3D::Spline3D( SplineType3D atype, int asubdivisions )
{
type = atype;
subdivisions = asubdivisions;
}

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

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

void Spline3D::add_point( float x, float y, float z )
{
Spline3DPoint *pt = new Spline3DPoint( x, y, z );
Points.push_back( pt );
}

void Spline3D::add_point( float x, float y, float z, float extra_param )
{
Spline3DPoint *pt = new Spline3DPoint( x, y, z, extra_param );
Points.push_back( pt );
}


void Spline3D::append( )
{
float x = Points.back()->getx() + 0.1;
float y = Points.back()->gety() + 0.1;
float z = Points.back()->getz() + 0.1;
float ext = Points.back()->get_extra_param();
add_point( x, y, z, ext );
}

void Spline3D::prepend()
{
float x = Points[0]->getx() - 0.1;
float y = Points[0]->gety() - 0.1;
float z = Points[0]->getz() - 0.1;
float ext = Points.back()->get_extra_param();
Spline3DPoint *pt = new Spline3DPoint( x, y, z, ext );
Points.insert( Points.begin(), pt );
}

void Spline3D::insert( int selected )
{
float x, y, z, ext;
if ( selected > 0 )
	{
	x = ( Points[selected]->getx() + Points[selected-1]->getx() ) / 2.0;
	y = ( Points[selected]->gety() + Points[selected-1]->gety() ) / 2.0;
	z = ( Points[selected]->getz() + Points[selected-1]->getz() ) / 2.0;
	ext = ( Points[selected]->get_extra_param() + Points[selected-1]->get_extra_param() ) / 2.0;
	}
else
	{
	x = Points[selected]->getx() / 2.0;
	y = Points[selected]->gety() / 2.0;
	z = Points[selected]->getz() / 2.0;
	ext = Points[selected]->get_extra_param() / 2.0;
	}
Spline3DPoint *pt = new Spline3DPoint( x, y, z, ext );
Points.insert( Points.begin() + selected, pt );
}

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

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

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

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

return true;
}

void Spline3D::output_to_povray( ofstream & file )
{
int spline_size = Points.size();
for ( int i = 0 ; i < spline_size ; i++ )
	{
	float p[3];
	float radius = Points[i]->get_extra_param();
	Points[i]->get( p );
	file << " <" << p[0] << "," << p[1] << "," << -p[2] << ">, "<< radius;
	}
}

const float cubicspline_matrix[4][4] = {
	-0.5,	1.5, 	-1.5, 	0.5,
	1, 		-2.5, 	2, 		-0.5,
	-0.5,	0, 		0.5, 	0,
	0, 		1, 		0, 		0 
};

const float bspline_matrix[4][4] = {
	-0.1666666,	.5, 	-.5, 	0.1666666,
	0.5, 		-1, 	0.5, 		0,
	-0.5,	0, 		0.5, 	0,
	0.1666666, 		0.6666666, 		0.1666666, 		0 
};

/*const float bspline_matrix[4][4] = {
	-1, 3, -3, 1, 
	3, -6, 3, 0, 
	-3, 3, 0, 0, 
	1, 0, 0, 0
};*/


void Spline3D::get_segments( int & ptnum, float * & xcoords, float * & ycoords, float * & zcoords )
{
	
int size = Points.size();
int segs = size;
ptnum = segs;
if ( type == TV_SPLINE3D_BSPLINE ) 
	{
	segs -= 2;
	ptnum =  (segs-1) * (subdivisions+1)+1;
	}
if ( type == TV_SPLINE3D_CUBIC ) 
	{
	segs -= 2;
	ptnum =  (segs-1) * (subdivisions)+1;
	}
	
xcoords = new float[ ptnum ];
ycoords = new float[ ptnum ];
zcoords = new float[ ptnum ];
float div = 1 / (float)subdivisions;
//cout << "\nptnum = " << ptnum; cout.flush();

switch ( type )
	{
	case TV_SPLINE3D_LINEAR:
		{
		for ( int i = 0 ; i < ptnum ; i++ )
			{
			xcoords[i] = Points[i]->getx();
			ycoords[i] = Points[i]->gety();
			zcoords[i] = Points[i]->getz();
			}
		}
		break;
		

	case TV_SPLINE3D_BSPLINE:
		{
		int cseg = 0;
		//cout << "\nsegs = "; cout.flush();
		for ( int i = 0 ; i < size-3 ; i++ )
			{
			float a[3], b[3], c[3], d[3];
			Points[i]->get(a);
			Points[i+1]->get(b);
			Points[i+2]->get(c);
			Points[i+3]->get(d);
				
			float t = 0;
			bool reloop = true;
			for ( int k = 0 ; k < subdivisions+1 ; k++ )
				{
				//float t = (float)k / (float)(subdivisions-1);
				float tt[4];
				float ttt[4];
				tt[0] = t*t*t;
				tt[1] = t*t;
				tt[2] = t;
				tt[3] = 1;
				for ( int j = 0 ; j < 4 ; j++ )
					{
					ttt[j] = 0;
					for ( int w = 0 ; w < 4 ; w++ )
						ttt[j] += tt[w] * bspline_matrix[w][j];
					}
				//cout << "\n\t seg ->" << cseg;
				xcoords[cseg] = ttt[0] * a[0] + ttt[1] * b[0] + ttt[2] * c[0] + ttt[3] * d[0];
				ycoords[cseg] = ttt[0] * a[1] + ttt[1] * b[1] + ttt[2] * c[1] + ttt[3] * d[1];
				zcoords[cseg] = ttt[0] * a[2] + ttt[1] * b[2] + ttt[2] * c[2] + ttt[3] * d[2];
				//cout << " : < " << xcoords[cseg] << " - " << ycoords[cseg] << " - " << zcoords[cseg] << " > ";
				cseg += 1;
				t += div;
				if ( i == size-4 && k == subdivisions && reloop == true ) { k--; reloop = false; }
				}										
			}
		//cout << "\ncseg = " << cseg; cout.flush();
		}
		break;
		
	case TV_SPLINE3D_CUBIC:
		{
		int cseg = 0;
		for ( int i = 0 ; i < size-3 ; i++ )
			{
			float a[3], b[3], c[3], d[3];
			Points[i]->get(a);
			Points[i+1]->get(b);
			Points[i+2]->get(c);
			Points[i+3]->get(d);
				
			for ( float t = 0 ; t < 1 ; t += div )
				{
				float tt[4];
				float ttt[4];
				tt[0] = t*t*t;
				tt[1] = t*t;
				tt[2] = t;
				tt[3] = 1;
				for ( int j = 0 ; j < 4 ; j++ )
					{
					ttt[j] = 0;
					for ( int k = 0 ; k < 4 ; k++ )
						ttt[j] += tt[k] * cubicspline_matrix[k][j];
					}
				xcoords[cseg] = ttt[0] * a[0] + ttt[1] * b[0] + ttt[2] * c[0] + ttt[3] * d[0];
				ycoords[cseg] = ttt[0] * a[1] + ttt[1] * b[1] + ttt[2] * c[1] + ttt[3] * d[1];
				zcoords[cseg++] = ttt[0] * a[2] + ttt[1] * b[2] + ttt[2] * c[2] + ttt[3] * d[2];
				}							
			}
		xcoords[cseg] = Points[size-2]->getx();
		ycoords[cseg] = Points[size-2]->gety();
		zcoords[cseg] = Points[size-2]->getz();
		}
		break;
		
	}
}



void Spline3D::interpolate_extra_param( int & ptnum, float * & extra_params )
{
	// Calculate segments
	int size = Points.size();
	int segs = size;
	ptnum = segs;
	if ( type == TV_SPLINE3D_BSPLINE ) {
		segs -= 2;
		ptnum =  (segs-1) * (subdivisions+1)+1;
	}
	if ( type == TV_SPLINE3D_CUBIC ) {
		segs -= 2;
		ptnum =  (segs-1) * (subdivisions)+1;
	}
	
	extra_params = new float[ ptnum ];
	float div = 1 / (float)subdivisions;
	//cout << "\nptnum = " << ptnum; cout.flush();

	switch ( type ) {
		
		// Linear spline, no interpolation
		case TV_SPLINE3D_LINEAR: {
			for ( int i = 0 ; i < ptnum ; i++ )
			extra_params[i] = Points[i]->get_extra_param();
		}
		break;
		
		// Cubic spline, cubic interpolation
		case TV_SPLINE3D_CUBIC: {
			int cseg = 0;
			for ( int i = 0 ; i < size-3 ; i++ ) {
				float a, b, c, d;
				a = Points[i]->get_extra_param();
				b = Points[i+1]->get_extra_param();
				c = Points[i+2]->get_extra_param();
				d = Points[i+3]->get_extra_param();
				
			for ( float t = 0 ; t < 1 ; t += div ) {
				float t2 = t*t;
				float t3 = t2*t;
				extra_params[cseg++] = t3*( -0.5*a + 1.5*b - 1.5*c + 0.5*d ) + t2*( a - 2.5*b +2.0*c- 0.5*d ) + t*( -0.5*a + 0.5*c ) + b;
				}							
			}
		extra_params[cseg] = Points[size-2]->get_extra_param();
		}
		break;

		// B Spline, bezier interpolation
		case TV_SPLINE3D_BSPLINE: {
			int cseg = 0;
			for ( int i = 0 ; i < size-3 ; i++ ) {
				float a, b, c, d;
				a = Points[i]->get_extra_param();
				b = Points[i+1]->get_extra_param();
				c = Points[i+2]->get_extra_param();
				d = Points[i+3]->get_extra_param();
				
				float t = 0;
				bool reloop = true;
				for ( int k = 0 ; k < subdivisions+1 ; k++ ) {
					float t2 = t*t;
					float t3 = t2*t;
					float u1 = 0.16666666666;
					float u2 = 0.6666666666;
					extra_params[cseg++] = t3*( -u1*a + 0.5*b - 0.5*c + u1*d ) + t2*( 0.5*a - b + 0.5*c ) + t*( -0.5*a + 0.5*c ) + u1*a + u2*b + u1*c;
					t += div;
					if ( i == size-4 && k == subdivisions && reloop == true ) { k--; reloop = false; }
				}
				
			}
		}
		break;
			
	}
}




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