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


float default_points[16][3] = {
	{0, 0, -.2}, {.1, 0, 0}, {.2, 0, 0}, {.3, 0,.2},
    {0, .1, 0}, {.1, .1, 0}, {.2, .1, 0}, {.3, .1, 0},
    {0, .2, 0}, {.1, .2, 0}, {.2, .2, 0}, {.3, .2, 0},
    {0, .3, -.2}, {.1, .3, 0}, {.2, .3, 0}, {.3, .3, .2}
};
/*
float default_points[16][3] = {
	{-0.3, -0.3, -.1}, {-.1, -.3, .1}, {.1, -.3, .2}, {.3, -.3,-.1},
    {-.3, -.1, -.3}, {-.1, -.1, .2}, {.1 -.1, -.1}, {.3, -.1, .1},
    {-.3, .1, -.1}, {-.1, .1, -.1}, {.1, .1, .1}, {.3, .1, -.1},
    {-.3, .3, -.2}, {-.1, .3, .1}, {.1, .3, .3}, {.3, .3, .1}
};*/

//**************************************
// Bicubic - Constructeur
//**************************************
Bicubic::Bicubic( app_objs *appref ) : Object3D_with_material( appref )
{
type = TV_OBJ3D_BICUBIC;
category = TV_OBJ3D_OBJECTS;
set_name( "Bicubic Patch" );
patch_edit_box = NULL;
in_undo = false;
in_update = false;
	
// Initialize control points
for ( int i = 0 ;  i < 16 ; i++ ) 
	{ 
	control_points[i][0] = default_points[i][0]; 
	control_points[i][1] = default_points[i][1]; 
	control_points[i][2] = default_points[i][2];
	}
	
// 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 Bicubic"), app_ref, true, true );
	
flatness = new ObjParam_float( N_("Flatness"), "FLATN", NULL, app_ref, false, 0 );
flatness->set_range( 1, 0, 0.01, 5 );
preprocess = new ObjParam_bool( N_("Preprocess"), "PREP", NULL, app_ref, false, true );
ustep = new ObjParam_int( N_("U Step"), "USTEP", NULL, app_ref, false, 4 );
ustep->set_range( 10, 1, 1 );
vstep = new ObjParam_int( N_("V Step"), "VSTEP", NULL, app_ref, false, 4 );
vstep->set_range( 10, 1, 1 );
	
current_point_num = new ObjParam_int( N_("Point #"), "PNUM", NULL, app_ref, true, 1 );
current_point_num->set_range( 16, 1, 1 );
current_point_num->set_send_undo( false );
current_point = new ObjParam_point( N_("Position"), "POS", NULL, app_ref, true );
current_point->set_send_undo( false );
}

Bicubic::Bicubic( Bicubic & ref ) : Object3D_with_material( ref )
{
in_undo = false;
patch_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 );
current_point_num = new ObjParam_int( *ref.current_point_num );
current_point = new ObjParam_point( *ref.current_point );	
flatness = new ObjParam_float( *ref.flatness );
preprocess = new ObjParam_bool( *ref.preprocess );
ustep = new ObjParam_int( *ref.ustep );
vstep = new ObjParam_int( *ref.vstep );
	
// Copy control points
for ( int i = 0 ;  i < 16 ; i++ ) 
	{ 
	control_points[i][0] = ref.control_points[i][0]; 
	control_points[i][1] = ref.control_points[i][1]; 
	control_points[i][2] = ref.control_points[i][2]; 
	}
}

Bicubic::~Bicubic()
{
delete location;
delete size;
delete rotation;
delete edit;
delete flatness;
delete preprocess;
delete ustep;
delete vstep;

delete current_point_num;
delete current_point;
}

//**************************************
// Display
// Dessin de la boite
//**************************************
/*tv_matrix Bmat = {
	{ -0.166666666,  0.5, -0.5, 0.1666666666 },
	{ 0.5, -1, 0.5, 0 },
	{ -0.5, 0, 0.5, 0 },
	{ 0.16666666, 0.6666666, 0.166666, 0 } };
*/
tv_matrix Bmat = {
	{ -1,  3, -3, 1 },
	{ 3, -6, 3, 0 },
	{ -3, 3, 0, 0 },
	{ 1, 0, 0, 0 } };

void Bicubic::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( location->changed() || size->changed() || rotation->changed()  || edit->changed() || current_point_num->changed() \
	|| current_point->changed() )
	list.invalidate();

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


// creation de la liste si necessaire
set_color();
list.begin();
glPushMatrix();
	
// Position et direction
gfloat x1, y1, z1;
gfloat x2, y2, z2;
location->get( x1, y1, z1 );
glTranslatef( x1, y1, z1 );
rotation->gl_rotate();
size->get( x2, y2, z2 );
glScalef( x2, y2, z2 );
int mode;
glGetIntegerv( GL_RENDER_MODE, &mode );

if (  edit->value() )
	{	
	// Draw control points
	pick_name = -1;		
	int selected_point = current_point_num->value() - 1;
	glPointSize( 4 );
	for ( int i = 0 ; i < 16 ; i++ )
			{
			int pick = objlist->get_pick_name();
			if ( pick_name == -1 ) pick_name = pick;
			glLoadName( pick );
			
			if ( i != selected_point ) 
				{ 
				glBegin( GL_POINTS ); 
				glVertex3f( control_points[i][0], control_points[i][1], control_points[i][2] ); 
				glEnd(); 
				}
			else
				{
				glPointSize( 6 );
				glBegin( GL_POINTS );
				glColor3f( 1.0, 0, 0 );
				glVertex3f( control_points[selected_point][0], control_points[selected_point][1], control_points[selected_point][2] );	
				set_color();					
				glEnd();
				glPointSize( 4 );
				}
			}

	if ( mode != GL_SELECT )
		{
		// Draw the hull
		for ( int i = 0 ; i < 3 ; i++ )
			{
			for ( int j = 0 ; j < 3 ; j++ )
				{
				glBegin( GL_LINE_LOOP );
					int ind = i*4+j;
					glVertex3f( control_points[ind][0], control_points[ind][1], control_points[ind][2] ); ind += 1;
					glVertex3f( control_points[ind][0], control_points[ind][1], control_points[ind][2] );	ind += 4;
					glVertex3f( control_points[ind][0], control_points[ind][1], control_points[ind][2] );	ind -= 1;
					glVertex3f( control_points[ind][0], control_points[ind][1], control_points[ind][2] );
					glEnd();			
				}
			}
		}
	}

if ( mode != GL_SELECT )
	{
	// Bicubic patch
	float points[quality][quality][3];
	float normals[quality][quality][3];
	tv_matrix a, aa, b, bb, c, cc, trans, tn[3];
	for ( int i = 0 ; i < 4 ; i++ )
		for ( int j = 0 ; j < 4 ; j++ )
			{
			int ind = i*4+j;
			a[i][j] = control_points[ind][0];
			b[i][j] = control_points[ind][1];
			c[i][j] = control_points[ind][2];
			}
	transpose_matrix( Bmat, trans );
	prod_matrix_matrix( Bmat, a, aa );
	prod_matrix_matrix( Bmat, b, bb );
	prod_matrix_matrix( Bmat, c, cc );
	prod_matrix_matrix( aa, trans, tn[0] );
	prod_matrix_matrix( bb, trans, tn[1] );
	prod_matrix_matrix( cc, trans, tn[2] );
	for ( int i = 0 ; i < quality ; i++ )
		{
		for ( int j = 0 ; j < quality ; j++ )
			{
			float s = (float)i / (float)(quality-1);
			float t = (float)j / (float)(quality-1);
			tv_vector S = { s*s*s, s*s, s, 1 };
			tv_vector T = { t*t*t, t*t, t, 1 };
			tv_vector dS = { 3*s*s, 2*s, 1, 0 };
			tv_vector dT = { 3*t*t, 2*t, 1, 0 };
			tv_point ds, dt;
			tv_vector d;
			
			for ( int k = 0 ; k < 3 ; k++ )
				{
				prod_vector_matrix( S, tn[k], d );
				points[i][j][k] = prod_vector_vector( d, T );
				prod_vector_matrix( dS, tn[k], d );
				ds[k] = prod_vector_vector( d, T );
				prod_vector_matrix( S, tn[k], d );
				dt[k] = prod_vector_vector( d, dT );
				}
			
			prod_vector( dt, ds, normals[i][j] );
			normalize( normals[i][j] );
			}
		}	
	for ( int i = 0 ; i < quality-1 ; i++ )
		{
		glBegin( GL_TRIANGLE_STRIP );
		for ( int j = 0 ; j < quality ; j++ )
			{
			glNormal3f( normals[i][j][0], normals[i][j][1], normals[i][j][2] );
			glVertex3f( points[i][j][0], points[i][j][1], points[i][j][2] );
			glNormal3f( normals[i+1][j][0], normals[i+1][j][1], normals[i+1][j][2] );
			glVertex3f( points[i+1][j][0], points[i+1][j][1], points[i+1][j][2] );
			}
		glEnd();
		}
	}
		
glPopMatrix();
list.end();
}


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

void Bicubic::edit_widget( GtkWidget *wid )
{
PREF_DEF
bool tt = pref->tooltips->value();

// Options communes
Object3D::edit_widget( wid );

// Spline edition
new_frame( edit_cont, N_("Patch edition") );
edit->get_widget( frame, tt );
patch_edit_box = dlg_simple_box_frame( N_("Points edition"), frame );
new_table_no_frame( patch_edit_box, 2 );
	current_point_num->get_widget( table, tt, 1 );
	current_point->get_widget( table, tt, 2, sign_bicubic_current_point_changed, this );
	
new_table_no_frame( frame, 4 );
	flatness->get_widget( table, tt, 1 );
	preprocess->get_widget( table, tt, 2 );
	ustep->get_widget( table, tt, 3 );
	vstep->get_widget( table, tt, 4 );
	
// Options de geometrie
new_table( edit_cont, N_("General settings"), 3 );
	location->get_widget( table, tt, 1 );
	size->get_widget( table, tt, 2 );
	rotation->get_widget( table, tt, 3 );
	
get_texture_widgets( edit_cont, tt );

gtk_widget_show_all( wid );
edit->set_target( patch_edit_box );
edit->toggle();
current_point_num->connect_signal( GTK_SIGNAL_FUNC(sign_bicubic_current_point_num_changed), this );
current_point_num_changed();
}

//***********************************************
// Mouse drag
//***********************************************
void Bicubic::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() ) 
			current_point->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 Bicubic::pref_changed()
{
Object3D::pref_changed();
location->pref_changed();
size->pref_changed();
rotation->pref_changed();
edit->pref_changed();
current_point_num->pref_changed();
current_point->pref_changed();
flatness->pref_changed();
preprocess->pref_changed();
ustep->pref_changed();
vstep->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void Bicubic::destroy_editor()
{
Object3D::destroy_editor();
location->clear_widget();
size->clear_widget();
texture->clear_widget();
rotation->clear_widget();
edit->clear_widget();
current_point_num->clear_widget();
current_point->clear_widget();
flatness->clear_widget();
preprocess->clear_widget();
ustep->clear_widget();
vstep->clear_widget();
if ( patch_edit_box != NULL ) patch_edit_box = NULL;
}

//***********************************************
// Output to povray
//***********************************************
void Bicubic::output_to_povray_pass1( ofstream & file )
{
file << "\n\n// Bicubic : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\nbicubic_patch {";
if ( preprocess->value() ) file << "\n\ttype 1"; else file << "\n\ttype 0";
file << "\n\tflatness " << flatness->value();
file << "\n\tu_steps " << ustep->value();
file << "\n\tv_steps " << vstep->value();
file << "\n\t";

for ( int i = 0 ; i < 4 ; i++ )
	{
	for ( int j = 0 ; j < 4 ; j++ )
		{	
		int ind = i*4+j;
		file << "<" << control_points[ind][0] << "," << control_points[ind][1] << "," << -control_points[ind][2];
		if ( ind != 15 ) file << ">,"; else file << ">";
		}
	file << "\n\t";
	}
	
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";
file << "\n}";
}



//***********************************************
// Save & load
//***********************************************
void Bicubic::save( ofstream & file )
{
file << "\nBICUBIC{\n";
save_basics( file );
location->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
edit->save( file );
flatness->save( file );
preprocess->save( file );
ustep->save( file );
vstep->save( file );
file << "\n\tCONTROLS{";
for ( int i = 0 ; i < 16 ; i++ )
	file << ' ' << control_points[i][0] << ' ' << control_points[i][1] << ' ' << control_points[i][2];

file << " }\n}";
}

bool Bicubic::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "BICUBIC" ) ) 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 ( flatness->load( file, tag ) ) continue;
	if ( preprocess->load( file, tag ) ) continue;
	if ( ustep->load( file, tag ) ) continue;
	if ( vstep->load( file, tag ) ) continue;
	
	if ( ! strcmp( "CONTROLS", tag ) ) 
		{
		char ch = 0;
		while ( ch == 0 || ch == '{' ) ch = file.get();
		char line[150];
		for ( int i = 0 ; i < 16 ; i++ )
			for ( int j = 0 ; j < 3 ; j++ )
				{
				char ch = 0;
				int ind = 0;
				while ( ch != ' ')
					ch = line[ind++] = file.get();
				line[ind]= '\0';
				sscanf( line, "%f", &control_points[i][j] );
				//cout << "\nscanning [" << i << "][" << j << "] ->" << line << " = " << control_points[i][j]; cout.flush();
				}
		//tvio_skip_section(file );	
		}

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

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


//*******************************************************
//  Spline Edition
//*******************************************************
void Bicubic::current_point_num_changed()
{
if ( patch_edit_box == NULL ) return;
if ( current_point_num->in_update ) return;
if ( in_update ) return;
in_update = true;
int i = current_point_num->value() - 1;
current_point->set( control_points[i][0],  control_points[i][1], control_points[i][2] );
current_point->update_widget();
current_point->unchange();
current_point_num->update_widget();
in_update = false;
}

void Bicubic::current_point_changed()
{
if ( in_update ) return;
if ( patch_edit_box == NULL ) return;
if ( current_point->in_update ) return;
int i = current_point_num->value() - 1;
float x, y, z;
if ( current_point->change_is_reversible() ) push_undo_item();
if ( !current_point->is_in_drag() ) push_undo_item();

current_point->flush();
current_point->get( x, y, z );
control_points[i][0] = x;
control_points[i][1] = y;
control_points[i][2] = z;
list.invalidate();
VMAN_DEF
vmanager->refresh();
}


bool Bicubic::pick_test( int pname )
{
int selected_point = current_point_num->value() - 1;
if ( edit->value() )
	{
	if ( ( pname >= pick_name ) && ( pname <= pick_name + 16  ) )
		{
		int pointed = pname - pick_name;
		current_point_num->set( pointed + 1 );
		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 Bicubic::undo( Object3D *copy )
{
in_undo = true;
Bicubic *changed = (Bicubic*)copy;
	
float temp[16][3];
for ( int i = 0 ;  i < 16 ; i++ ) 
	{ 
	temp[i][0] = control_points[i][0];
	temp[i][1] = control_points[i][1];
	temp[i][2] = control_points[i][2];
	control_points[i][0] = changed->control_points[i][0];
	control_points[i][1] = changed->control_points[i][1];
	control_points[i][2] = changed->control_points[i][2];
	changed->control_points[i][0] = 	temp[i][0];
	changed->control_points[i][1] = 	temp[i][1];
	changed->control_points[i][2] = 	temp[i][2];
	}

VMAN_DEF
vmanager->refresh();
in_undo = false;
}
