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


// Lathe preview subdivisions
const int LatheSubdivision = 12;
const int LatheSplineSubdivision = 10;

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

// Spline type
const int spline_type_num = 4;
const gchar * spline_type_list[spline_type_num] = { N_("Linear"), N_("Quadratic"), N_("Cubic"), N_("Bezier") };
const char *spline_type_keys[spline_type_num] = { "linear_spline", "quadratic_spline", "cubic_spline", "bezier_spline" };
	

//**************************************
// Lathe - Constructeur
//**************************************
Lathe::Lathe( app_objs *appref ) : Object3D_with_material( appref )
{
type = TV_OBJ3D_LATHE;
category = TV_OBJ3D_OBJECTS;
set_name( "Lathe" );
spline_edit_box = NULL;
in_undo = false;
in_update = false;

// Spline	
spline = new Spline2D( TV_SPLINE2D_LINEAR, LatheSplineSubdivision );
spline->set_for_lathe();
spline->add_point( 0.2, 0 );
spline->add_point( 0.3, 0.1 );
spline->add_point( 0.4, 0.2 );
spline->add_point( 0.5, 0.3 );
	
// Base
location = new ObjParam_point( N_("Location"), "LOC", NULL, app_ref, true );
location->set( 0, 0, 0 );
size = new ObjParam_scale( N_("Size"), "SIZE", NULL, app_ref, true );
size->set( 1, 1, 1 );
rotation = new ObjParam_rotation( N_("Rotation"), "ROT", NULL, app_ref, true );
rotation->set( 0, 0, 0 );
edit = new ObjParam_bool_activator( N_("Edit"), "EDIT", N_("Edit Lathe"), app_ref, true, true );
sturm = new ObjParam_bool( N_("Sturm"), "STURM", N_("Use alternative root solver"), app_ref, true, false );
spline_type = new ObjParam_option_combo( N_("Spline mode"), "SPLINET", NULL, app_ref, true );
spline_type->set_list( spline_type_list, spline_type_num, 0 );
	
current_point_num = new ObjParam_int( N_("Point #"), "PNUM", NULL, app_ref, true, 1 );
current_point_num->set_range( spline->size(), 1, 1 );
current_point_num->set_send_undo( false );
current_point = new ObjParam_point_2d( N_("Position"), "POS", NULL, app_ref, true, true );
current_point->set_send_undo( false );
current_point_ctrl1 = new ObjParam_point_2d( N_("Control1"), "CTRL1", NULL, app_ref, true, true );
current_point_ctrl1->set_send_undo( false );
current_point_ctrl2 = new ObjParam_point_2d( N_("Control2"), "CTRL2", NULL, app_ref, true, true );
current_point_ctrl2->set_send_undo( false );
}

Lathe::Lathe( Lathe & ref ) : Object3D_with_material( ref )
{
in_undo = false;
spline_edit_box = NULL;
in_update = false;
location = new ObjParam_point( *ref.location );
size = new ObjParam_scale( *ref.size );
rotation = new ObjParam_rotation( *ref.rotation );
edit = new ObjParam_bool_activator( *ref.edit );
sturm = new ObjParam_bool( *ref.sturm );
spline_type = new ObjParam_option_combo( *ref.spline_type );
current_point_num = new ObjParam_int( *ref.current_point_num );
current_point = new ObjParam_point_2d( *ref.current_point );	
current_point_ctrl1 = new ObjParam_point_2d( *ref.current_point_ctrl1 );	
current_point_ctrl2 = new ObjParam_point_2d( *ref.current_point_ctrl2 );	

spline = ref.spline->auto_copy();
current_point_num->set_range( spline->size(), 1, 1 );
}

Lathe::~Lathe()
{
delete location;
delete size;
delete rotation;
delete edit;
delete sturm;
delete spline_type;
delete current_point_num;
delete current_point;
delete current_point_ctrl1;
delete current_point_ctrl2;
delete spline;
}

//**************************************
// Display
// Dessin de la boite
//**************************************
void Lathe::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( location->changed() || size->changed() || rotation->changed()  || edit->changed() || spline_type->changed() || current_point_num->changed() || current_point->changed() || current_point_ctrl1->changed() || current_point_ctrl1->changed())
	list.invalidate();

PREF_DEF
int quality = LatheSplineSubdivision + pref->preview_quality->value()*4;

// Mode Edition
// Sans liste et avec picking pour les points, mais pas pour les lignes
if (  edit->value() )
	{
	OBJLIST_DEF
	int selected_point = current_point_num->value() - 1;
	set_color();
	glPushMatrix();

	// Lines ( linear, quadradtic, cubic or bezier... )
	int mode;
	glGetIntegerv( GL_RENDER_MODE, &mode );
	spline->set_type( (SplineType)spline_type->value() );
	spline->set_subdivisions( quality );
	if ( mode != GL_SELECT ) // Draw all
		{
		spline->gl_display_segments();
		if ( spline_type->value() !=  (int)TV_SPLINE2D_LINEAR )
			{
			glEnable( GL_LINE_STIPPLE );
			glLineStipple( 1, 0xAAA );
			glBegin( GL_LINES );
			if ( spline_type->value() ==  (int)TV_SPLINE2D_QUADRATIC ) 
				{			
				spline->display_point( 0 );
				spline->display_point( 1 );
				}
			if ( spline_type->value() ==  (int)TV_SPLINE2D_CUBIC ) 
				{
				int size = spline->size();						
				spline->display_point( 0 );
				spline->display_point( 1 );
				spline->display_point( size-2 ); 
				spline->display_point( size-1 );	
				}
			if ( spline_type->value() ==  (int)TV_SPLINE2D_BEZIER ) 
				{
				spline->display_point( selected_point );
				spline->display_point_ctrl1( selected_point );
				spline->display_point( selected_point );
				spline->display_point_ctrl2( selected_point );
				}
			glEnd();
			glDisable( GL_LINE_STIPPLE );
			}		
		} // end non select mode

// Draw Points ( much, much easier :)
		pick_name = -1;		
		int size = spline->size();
		for ( int i = 0 ; i < size ; i++ ) 
			{
			int pick = objlist->get_pick_name();
			if ( pick_name == -1 ) pick_name = pick;
			glLoadName( pick );
			if ( i != selected_point ) 
				{
				glBegin( GL_POINTS );
				spline->display_point(i);
				glEnd();
				}
			else
				{
				if ( selected_ctrl == 0 ) glColor3f( 1.0, 0, 0 );
				glPointSize( 6 );
				glBegin( GL_POINTS );
				spline->display_point( selected_point );	
				glEnd();
				set_color();
				if ( spline_type->value() == TV_SPLINE2D_BEZIER )
					{
					int pick = objlist->get_pick_name();
					glLoadName( pick );
					if ( selected_ctrl == 1 ) glColor3f( 1.0, 0, 0 );
					glBegin( GL_POINTS );
					spline->display_point_ctrl1( selected_point );	
					glEnd();
					set_color();
					pick = objlist->get_pick_name();
					glLoadName( pick );
					if ( selected_ctrl == 2 ) glColor3f( 1.0, 0, 0 );
					glBegin( GL_POINTS );
					spline->display_point_ctrl2( selected_point );	
					glEnd();
					}
				glPointSize( 4 );
				set_color();
				}
			}
	glPopMatrix();
	}
else
	// Lathe opengl drawing
	// No point selection, we got a list
	{	
	Object3D::display( view );	
	if ( set_col ) set_color();
	if ( ! list.exec() )
		{
		// mise à zero des parametres
		location->unchange();
		size->unchange();
		rotation->unchange();
		edit->unchange();
		spline_type->unchange();
		current_point_num->unchange();
		current_point->unchange();
		current_point_ctrl1->unchange();
		current_point_ctrl2->unchange();
		spline->set_type( (SplineType)spline_type->value() );
		
		// creation de la liste si necessaire
		list.begin();
		glPushMatrix();
	
		// Position et direction
		gfloat x, y, z;
		gfloat a, b, c;
		location->get( x, y, z );
		glTranslatef( x, y, z );
		rotation->gl_rotate();
		size->get( a, b, c );
		glScalef( a, b, c );

		float *xcoords, *ycoords;
		int ptnum;
		spline->set_subdivisions( quality );
		spline->get_segments( ptnum, xcoords, ycoords );
		for ( int i = 0 ; i < ptnum-1 ; i++ )
			{
			glBegin( GL_QUAD_STRIP );
			float teta = 0;
			bool norm;
			if ( ycoords[i] <= ycoords[i+1] ) norm = false; else norm = true;
			for ( int slice = 0 ; slice < quality +1 ; slice++ )
				{
				teta += 2 * M_PI / quality; 
				float cosa = cos( teta );
				float sina = sin( teta );	 
				if ( ! norm ) glNormal3f( cosa, ycoords[i] - (ycoords[i+1]+ycoords[i-1])/2, sina ); else glNormal3f( -cosa, -ycoords[i] + (ycoords[i+1]+ycoords[i-1])/2, -sina );
				glVertex3f( cosa*xcoords[i+1], ycoords[i+1], sina*xcoords[i+1] ); 
				glVertex3f( cosa*xcoords[i], ycoords[i], sina*xcoords[i] ); 
				}
			glEnd();
			}
		delete xcoords;
		delete ycoords;
		glPopMatrix();
		list.end();
		}	
	}
}


//***********************************************
// Edit
//***********************************************

void Lathe::edit_widget( GtkWidget *wid )
{
PREF_DEF
bool tt = pref->tooltips->value();
// Options communes
Object3D::edit_widget( wid );

// Spline edition
new_frame( edit_cont, N_("Lathe edition") );
spline_type->get_widget( frame, tt );
edit->get_widget( frame, tt );
spline_edit_box = dlg_simple_box_frame( N_("Spline edition"), frame );
	current_point_num->get_widget( spline_edit_box, tt );
	current_point->get_widget( spline_edit_box, tt,  sign_lathe_current_point_changed, this );
	current_point_ctrl1->get_widget( spline_edit_box, tt,  sign_lathe_current_point_changed, this );
	current_point_ctrl2->get_widget( spline_edit_box, tt,  sign_lathe_current_point_changed, this );

// Bouttons
GtkWidget *hbox = gtk_hbox_new( TRUE, 3 );
gtk_box_pack_start( GTK_BOX(spline_edit_box), hbox, TRUE, TRUE, 1 );
	GtkWidget *button = gtk_button_new_with_label( N_("Append") );
	gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 1 );
	gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(sign_lathe_append_point), this );
	button = gtk_button_new_with_label( N_("Prepend") );
	gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 1 );
	gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(sign_lathe_prepend_point), this );
	button = gtk_button_new_with_label( N_("Insert") );
	gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 1 );
	gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(sign_lathe_insert_point), this );
	button = gtk_button_new_with_label( N_("Delete") );
	gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 1 );
	gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(sign_lathe_delete_point), this );
	
// Options de geometrie
new_table( edit_cont, N_("General settings"), 4 );
	location->get_widget( table, tt, 1 );
	size->get_widget( table, tt, 2 );
	rotation->get_widget( table, tt, 3 );
	sturm->get_widget( table, tt, 4 );

get_texture_widgets( edit_cont, tt );
gtk_widget_show_all( wid );
edit->set_target( spline_edit_box );
edit->toggle();
current_point_num->connect_signal( GTK_SIGNAL_FUNC(sign_lathe_current_point_num_changed), this );
current_point_num_changed();
}

//***********************************************
// Mouse drag
//***********************************************
void Lathe::mouse_drag( struct drag_info *drag )
{
VMAN_DEF
OBJLIST_DEF

switch( vmanager->get_pointer_mode() )
	{
	case TV_PMODE_SELECT:
	case TV_PMODE_TRANSLATE:
		if ( edit->value() ) 
			{
			if ( selected_ctrl == 0 ) current_point->mouse_drag( drag );
			if ( selected_ctrl == 1 ) current_point_ctrl1->mouse_drag( drag );
			if ( selected_ctrl == 2 ) current_point_ctrl2->mouse_drag( drag );
			}
		else location->mouse_drag( drag );
		break;

	case TV_PMODE_SCALE:
		{ size->mouse_drag( drag ); }
		break;

	case TV_PMODE_ROTATE:
		{ rotation->mouse_drag( drag ); break; }

	case TV_PMODE_CUSTOM:
		((ObjParam_point*)(objlist->get_current_param()))->mouse_drag( drag );
		break;

	default: 
		break;
	}
}

//***********************************************
// Pref_changed
//***********************************************
void Lathe::pref_changed()
{
Object3D::pref_changed();
location->pref_changed();
size->pref_changed();
rotation->pref_changed();
edit->pref_changed();
sturm->pref_changed();
spline_type->pref_changed();
current_point_num->pref_changed();
current_point->pref_changed();
current_point_ctrl1->pref_changed();
current_point_ctrl2->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void Lathe::destroy_editor()
{
Object3D::destroy_editor();
location->clear_widget();
size->clear_widget();
texture->clear_widget();
rotation->clear_widget();
edit->clear_widget();
sturm->clear_widget();
spline_type->clear_widget();
current_point_num->clear_widget();
current_point->clear_widget();
current_point_ctrl1->clear_widget();
current_point_ctrl2->clear_widget();
if ( spline_edit_box != NULL ) spline_edit_box = NULL;
}

//***********************************************
// Output to povray
//***********************************************
void Lathe::output_to_povray_pass1( ofstream & file )
{
file << "\n\n// Lathe : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 

int sptype = spline_type->value();
file << "\nlathe {\n\t" << spline_type_keys[ sptype ];
int psize = spline->size();
if ( spline_type->value() == TV_SPLINE2D_BEZIER )
	psize = ( psize - 1 )*4;
file << "\n\t" << psize << ",\n\t";

spline->set_type( (SplineType)spline_type->value() );
spline->output_to_povray( file );

float a, b, c, x, y, z;
Object3D_with_material::output_to_povray_pass1( file );
file << "\n\t";
size->get( a, b, c );
file << "scale <" << a << "," << b << "," << c << ">";
rotation->output_to_povray( file );
location->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << "> \n\t";
if ( sturm->value() ) file << "sturm\n\t";
file << "\n}";
}



//***********************************************
// Save & load
//***********************************************
void Lathe::save( ofstream & file )
{
file << "\nLATHE{\n";
save_basics( file );
location->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
edit->save( file );
sturm->save( file );
spline_type->save( file );
	
spline->save( file );
file << "\n}";
}

bool Lathe::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "LATHE" ) ) return false;
set_load_progress( file );

char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	
	if ( load_basics( file, tag ) ) continue;
 	if ( location->load( file, tag ) ) continue;
 	if ( size->load( file, tag ) ) continue;
 	if ( rotation->load( file, tag ) ) continue;
	if ( edit->load( file, tag ) ) continue;
	if ( sturm->load( file, tag ) ) continue;
	if ( spline_type->load( file, tag ) ) continue;
	if ( spline->load( file, tag ) ) continue;

	tvio_skip_section(file );
	} while ( tag != NULL );

current_point_num->set_range( spline->size(), 1, 1 );
return true;
}


//*******************************************************
//  Spline Edition
//*******************************************************
void Lathe::current_point_num_changed()
{
if ( spline_edit_box == NULL ) return;
if ( current_point_num->in_update ) return;
if ( in_update ) return;
in_update = true;
selected_ctrl = 0; 
int i = current_point_num->value() - 1;
current_point->set( spline->getx(i), spline->gety(i) );
current_point->update_widget();
current_point->unchange();
float a[2];
spline->get_point_ctrl1( i, a );
current_point_ctrl1->set( a[0], a[1] );
current_point_ctrl1->update_widget();
current_point_ctrl1->unchange();
spline->get_point_ctrl2( i, a );
current_point_ctrl2->set( a[0], a[1] );
current_point_ctrl2->update_widget();
current_point_ctrl2->unchange();
current_point_num->update_widget();
in_update = false;
}

void Lathe::current_point_changed()
{
if ( in_update ) return;
if ( spline_edit_box == NULL ) return;
if ( current_point->in_update ) return;
if ( current_point_ctrl1->in_update ) return;
if ( current_point_ctrl2->in_update ) return;
int i = current_point_num->value() - 1;
float x, y, z, c1x, c1y, c2x, c2y;
if ( current_point->change_is_reversible() ||  current_point_ctrl1->change_is_reversible() ||  current_point_ctrl2->change_is_reversible() ) push_undo_item();
if ( !current_point->is_in_drag() &&  !current_point_ctrl1->is_in_drag() &&   !current_point_ctrl2->is_in_drag()) push_undo_item();

current_point->flush();
current_point_ctrl1->flush();
current_point_ctrl2->flush();
current_point->get( x, y, z );
current_point_ctrl1->get( c1x, c1y, z );
current_point_ctrl2->get( c2x, c2y, z );
spline->set_point( i, x, y, c1x, c1y, c2x, c2y );
list.invalidate();
VMAN_DEF
vmanager->refresh();
}

void Lathe::delete_point()
{
int size = spline->size();
if ( size <= 4 ) return;
push_undo_item();
int selected_point = current_point_num->value() - 1;
spline->delete_point( selected_point );
current_point_num->reset_range( 1, size-1 );
current_point_num_changed();
list.invalidate();
VMAN_DEF
vmanager->refresh();
}

void Lathe::append_point()
{
push_undo_item();
spline->append();
int size = spline->size();
current_point_num->reset_range( 1, size );
current_point_num->set( size );
current_point_num->update_widget();
current_point_num_changed();
list.invalidate();
VMAN_DEF
vmanager->refresh();
}

void Lathe::prepend_point()
{
push_undo_item();
spline->prepend();
int size = spline->size();
current_point_num->reset_range( 1, size );
current_point_num->set( 1 );
current_point_num->update_widget();
current_point_num_changed();
list.invalidate();
VMAN_DEF
vmanager->refresh();
}

void Lathe::insert_point()
{
int selected_point = current_point_num->value() - 1;
if ( selected_point == 0 ) return;
push_undo_item();
spline->insert( selected_point );
current_point_num->reset_range( 1, spline->size() );
current_point_num_changed();
list.invalidate();
VMAN_DEF
vmanager->refresh();
}


bool Lathe::pick_test( int pname )
{
int selected_point = current_point_num->value() - 1;
if ( edit->value() )
	{
	int offset = ( spline_type->value() == TV_SPLINE2D_BEZIER ) ? 2 : 0;
	if ( ( pname >= pick_name ) && ( pname <= pick_name + spline->size() + offset ) )
		{
		if ( offset == 0 ) { current_point_num->set( pname - pick_name +1 );  }
		else
			{
			selected_ctrl = 0;
			int pointed = pname - pick_name;
			if ( pointed < selected_point ) { current_point_num->set( pointed + 1 );  }
			if ( pointed > selected_point + 2 ) { current_point_num->set( pointed-1 ); }
			if ( pointed > selected_point && pointed <= selected_point + 2 ) selected_ctrl = pointed - selected_point;
			}
		if ( selected_point != current_point_num->value() -1 ) { current_point_num_changed(); }
		list.invalidate();
		VMAN_DEF
		vmanager->refresh();
		return true;
		}
	else return false;
	}
else
	{
	if ( pname == pick_name ) return true;
	else return false;
	}
return false;
}

void Lathe::undo( Object3D *copy )
{
in_undo = true;
Lathe *changed = (Lathe*)copy;
Spline2D *sp = changed->spline;
changed->spline = spline;
spline = sp;
int size = spline->size();
current_point_num->reset_range( 1, size );
if ( current_point_num->value() > size  ) current_point_num->set( size );
current_point_num_changed();
VMAN_DEF
vmanager->refresh();
in_undo = false;
}
