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

//**************************************
// Constructeur
//**************************************
Parametric::Parametric( app_objs *appref ) : Object3D_with_material( appref )
{
type = TV_OBJ3D_PARAMETRIC;
category = TV_OBJ3D_OBJECTS;
set_name( "Parametric" );

// Transformation
location = new ObjParam_point( N_("Translation"), "LOC", NULL, app_ref, true );
location->set( 0, 0, 0 );
size = new ObjParam_scale( N_("Scale"), "SIZE", NULL, app_ref, true );
size->set( 0.2, 0.2, 0.2 );
rotation = new ObjParam_rotation( N_("Rotation"), "ROT", NULL, app_ref, true );
rotation->set( 0, 0, 0 );

// Bounding box	
corner1 = new ObjParam_point( N_("Corner 1"), "CORN1", NULL, app_ref, true );
corner1->set( 1, 1, 1 );
corner2 = new ObjParam_point( N_("Corner 2"), "CORN2", NULL, app_ref, true );
corner2->set( -1, -1, -1 );
show_bb = new ObjParam_bool( N_("Show"), "BBSHOW", NULL, appref, true, true );

// Functions
functionx = new ObjParam_function( N_("X Function"), "FUNCX", NULL, appref, true );
functiony = new ObjParam_function( N_("Y Function"), "FUNCY", NULL, appref, true );
functionz = new ObjParam_function( N_("Z Function"), "FUNCZ", NULL, appref, true );

umin = new ObjParam_float( N_("U min"), "UMIN", NULL, app_ref, true );
umin->set_range( 100, -100, 0.1, 5 );
umax = new ObjParam_float( N_("U max"), "UMAX", NULL, app_ref, true );
umax->set_range( 100, -100, 0.1, 5 );
vmin = new ObjParam_float( N_("V min"), "VMIN", NULL, app_ref, true );
vmin->set_range( 100, -100, 0.1, 5 );
vmax = new ObjParam_float( N_("V max"), "VMAX", NULL, app_ref, true );
vmax->set_range( 100, -100, 0.1, 5 );

preview = new ObjParam_bool( N_("Preview"), "PREV", NULL, appref, true, true );
accuracy = new ObjParam_float( N_("Accuracy"), "ACC", NULL, appref, false, 0.001);
accuracy->set_range( 100, 0, 0.0001, 6 );
max_gradient =  new ObjParam_float( N_("Max gradient"), "MAXG", NULL, appref, false, 1.1 );
max_gradient->set_range( 10000, 0, 0.01, 5 );

// Precompute
precompute_depth = new ObjParam_int( N_("Depth"), "PREDE", NULL, appref, false, 10 );
precompute_depth->set_range( 20, 0, 1 );
precompute_x = new ObjParam_bool( N_("Precompute x"), "PRECX", NULL, appref, false, false );
precompute_y = new ObjParam_bool( N_("Precompute y"), "PRECY", NULL, appref, false, false );
precompute_z = new ObjParam_bool( N_("Precompute z"), "PRECZ", NULL, appref, false, false );
}

Parametric::Parametric( Parametric & ref ) : Object3D_with_material( ref )
{
location = new ObjParam_point( *ref.location );
size = new ObjParam_scale( *ref.size );
rotation = new ObjParam_rotation( *ref.rotation );
corner1 = new ObjParam_point( *ref.corner1 );
corner2 = new ObjParam_point( *ref.corner2 );
show_bb = new ObjParam_bool( *ref.show_bb );
functionx = new ObjParam_function( *ref.functionx );
functiony = new ObjParam_function( *ref.functiony );
functionz = new ObjParam_function( *ref.functionz );
preview = new ObjParam_bool( *ref.preview );
accuracy = new ObjParam_float( *ref.accuracy );
max_gradient = new ObjParam_float( *ref.max_gradient );
umin = new ObjParam_float( *ref.umin );
umax = new ObjParam_float( *ref.umax );
vmin = new ObjParam_float( *ref.vmin );
vmax = new ObjParam_float( *ref.vmax );
precompute_depth = new ObjParam_int( *ref.precompute_depth );
precompute_x = new ObjParam_bool( *ref.precompute_x );
precompute_y = new ObjParam_bool( *ref.precompute_y );
precompute_z = new ObjParam_bool( *ref.precompute_z );
}

Parametric::~Parametric()
{
delete location;
delete size;
delete rotation;
delete corner1;
delete corner2;
delete show_bb;
delete functionx;
delete functiony;
delete functionz;
delete preview;
delete accuracy;
delete max_gradient;
delete umin;
delete umax;
delete vmin;
delete vmax;
delete precompute_depth;
delete precompute_x;
delete precompute_y;
delete precompute_z;
}

//**************************************
// Display
// Dessin de la boite
//**************************************
void Parametric::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( location->changed() || size->changed() || rotation->changed() || corner1->changed() || corner2->changed() || show_bb->changed() \
	 || functionx->changed() || functiony->changed() || functionz->changed() || preview->changed()  || umin->changed()  || umax->changed() \
	|| vmin->changed()  || vmax->changed() )
	list.invalidate();

Object3D::display( view );	
if ( set_col ) set_color();
if ( ! list.exec() )
	{
	// mise à zero des parametres
	location->unchange();
	size->unchange();
	rotation->unchange();
	corner1->unchange();
	corner2->unchange();
	show_bb->unchange();
	functionx->unchange();
	functiony->unchange();
	functionz->unchange();
	preview->unchange();
	umin->unchange();
	umax->unchange();
	vmin->unchange();
	vmax->unchange();
	
	// 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 );
	size->get( a, b, c );
	glScalef( a, b, c );
	rotation->gl_rotate();

	corner1->get( a, b, c );
	corner2->get( x, y, z );

	// Bounding box display if requested	
	if ( show_bb->value() )
		{
		const float div = 4.0;
		float xrule = (x-a) / div ;
		float yrule = (y-b) / div ;
		float zrule = (z-c) / div ;
		glBegin( GL_LINES );
			glVertex3f( x, y, z ); glVertex3f( x-xrule, y, z ); 
			glVertex3f( x, y, z ); glVertex3f( x, y-yrule, z ); 
			glVertex3f( x, y, z ); glVertex3f( x, y, z-zrule ); 

			glVertex3f( x, y, c ); glVertex3f( x-xrule, y, c ); 
			glVertex3f( x, y, c ); glVertex3f( x, y-yrule, c ); 
			glVertex3f( x, y, c ); glVertex3f( x, y, c+zrule ); 

			glVertex3f( a, y, z ); glVertex3f( a+xrule, y, z ); 
			glVertex3f( a, y, z ); glVertex3f( a, y-yrule, z ); 
			glVertex3f( a, y, z ); glVertex3f( a, y, z-zrule ); 

			glVertex3f( a, y, c ); glVertex3f( a+xrule, y, c ); 
			glVertex3f( a, y, c ); glVertex3f( a, y-yrule, c ); 
			glVertex3f( a, y, c ); glVertex3f( a, y, c+zrule ); 

			glVertex3f( a, b, c  ); glVertex3f( a+xrule, b, c ); 
			glVertex3f( a, b, c  ); glVertex3f( a, b+yrule, c ); 
			glVertex3f( a, b, c  ); glVertex3f( a, b, c+zrule ); 

			glVertex3f( x, b, c  ); glVertex3f( x-xrule, b, c ); 
			glVertex3f( x, b, c  ); glVertex3f( x, b+yrule, c ); 
			glVertex3f( x, b, c  ); glVertex3f( x, b, c+zrule ); 

			glVertex3f( a, b, z  ); glVertex3f( a+xrule, b, z ); 
			glVertex3f( a, b, z  ); glVertex3f( a, b+yrule, z ); 
			glVertex3f( a, b, z  ); glVertex3f( a, b, z-zrule ); 

			glVertex3f( x, b, z  ); glVertex3f( x-xrule, b, z ); 
			glVertex3f( x, b, z  ); glVertex3f( x, b+yrule, z ); 
			glVertex3f( x, b, z  ); glVertex3f( x, b, z-zrule ); 
		glEnd();
		}
	
	// Add preview here
	if ( functionx->get_status() && functiony->get_status() && functionz->get_status() && preview->value() )
		{
		PREF_DEF
		float pref_factor = 1 + pref->preview_quality->value()*4;
		float udist = ( umax->value() - umin->value() );
		int usub = (int)( pref_factor * udist );
		float udiv = udist / (float)usub;

		float vdist = ( vmax->value() - vmin->value() );
		int vsub = (int)( pref_factor * vdist );			
		float vdiv = vdist / (float)vsub;

		float u = umin->value();
		float v = vmin->value();
		float xcoords[ usub+1 ][ vsub+1 ], ycoords[ usub+1 ][ vsub+1 ], zcoords[ usub+1 ][ vsub+1 ];
		for ( int i = 0 ; i < usub+1 ; i++ )
			{
			v = vmin->value();
			for ( int y = 0 ; y < vsub+1 ; y++ )
				{
				xcoords[i][y] = functionx->evaluate( u, v, 0 );
				ycoords[i][y] = functiony->evaluate( u, v, 0 );
				zcoords[i][y] = - functionz->evaluate( u, v, 0 );
				v += vdiv;
				}
			u += udiv;
			} 
			
		float min[3] = { a < x ? a : x,  b < y ? b : y, c < z ? c : z };
		float max[3] = { a > x ? a : x,  b > y ? b : y, c > z ? c : z };
		for ( int i = 0 ; i < vsub ; i++ )
			{
			glBegin( GL_TRIANGLE_STRIP );
			for ( int y = 0 ; y < usub+1 ; y++ )
				{
				if ( xcoords[y][i] > max[0] || xcoords[y][i] < min[0] ) continue;
				if ( ycoords[y][i] > max[1] || zcoords[y][i] < min[1] ) continue;
				if ( zcoords[y][i] > max[2] || zcoords[y][i] < min[2] ) continue;
				get_normal( xcoords[y][i], ycoords[y][i], zcoords[y][i], xcoords[y][i+1], ycoords[y][i+1], zcoords[y][i+1], xcoords[y+1][i], ycoords[y+1][i], zcoords[y+1][i] );
				glVertex3f( xcoords[y][i], ycoords[y][i], zcoords[y][i] );
				glVertex3f( xcoords[y][i+1], ycoords[y][i+1], zcoords[y][i+1] );
				}
			glEnd();
			}
		}
		
	glPopMatrix();
	list.end();
	}
}


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

// Options de geometrie
new_table( edit_cont, _("Bounding Box"), 3 );
	corner1->get_widget( table, tt, 1 );
	corner2->get_widget( table, tt, 2 );
	show_bb->get_widget( table, tt, 3 );

new_table( edit_cont, _("General settings"), 5 );
	preview->get_widget( table, tt, 1 );
	umin->get_widget( table, tt, 2 );
	umax->get_widget( table, tt, 3 );
	vmin->get_widget( table, tt, 4 );
	vmax->get_widget( table, tt, 5 );
	
	functionx->get_widget( table, tt, 6 );
	functiony->get_widget( table, tt, 7 );
	functionz->get_widget( table, tt, 8 );
	accuracy->get_widget( table, tt, 9 );
	max_gradient->get_widget( table, tt, 10 );

new_table( edit_cont, _("Precompute"), 4 );
	precompute_depth->get_widget( table, tt, 1 );
	precompute_x->get_widget( table, tt, 2 );
	precompute_y->get_widget( table, tt, 3 );
	precompute_z->get_widget( table, tt, 4 );
	
new_table( edit_cont, _("Transformation"), 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 );
}

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

switch( vmanager->get_pointer_mode() )
	{
	case TV_PMODE_SELECT:
	case TV_PMODE_TRANSLATE:
		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 Parametric::pref_changed()
{
Object3D::pref_changed();
location->pref_changed();
size->pref_changed();
rotation->pref_changed();
corner1->pref_changed();
corner2->pref_changed();
show_bb->pref_changed();
functionx->pref_changed();
functiony->pref_changed();
functionz->pref_changed();
preview->pref_changed();
accuracy->pref_changed();
max_gradient->pref_changed();
umin->pref_changed();
umax->pref_changed();
vmin->pref_changed();
vmax->pref_changed();
precompute_depth->pref_changed();
precompute_x->pref_changed();
precompute_y->pref_changed();
precompute_z->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void Parametric::destroy_editor()
{
Object3D::destroy_editor();
location->clear_widget();
size->clear_widget();
texture->clear_widget();
rotation->clear_widget();
corner1->clear_widget();
corner2->clear_widget();
show_bb->clear_widget();
functionx->clear_widget();
functiony->clear_widget();
functionz->clear_widget();
preview->clear_widget();
accuracy->clear_widget();
max_gradient->clear_widget();
umin->clear_widget();
umax->clear_widget();
vmin->clear_widget();
vmax->clear_widget();
precompute_depth->clear_widget();
precompute_x->clear_widget();
precompute_y->clear_widget();
precompute_z->clear_widget();
}

//***********************************************
// Output to povray
//***********************************************
void Parametric::output_to_povray_pass1( ofstream & file )
{
//if (  function->value() == NULL ) return;
file << "\n\n// Parametric : " << name->value();
functionx->define_internals( file );
functiony->define_internals( file );
functionz->define_internals( file );
file << "\n#declare "; get_underscore_name( file ); file << " ="; 

file << "\nparametric {";
file << "\n\tfunction{ "; file << functionx->value() <<  " }";
file << "\n\tfunction{ "; file << functiony->value() <<  " }";
file << "\n\tfunction{ "; file << functionz->value() <<  " }";
file << "\n\t< " << umin->value() << ", " << vmin->value() << " >, < " << umax->value() << ", " << vmax->value() << " >";	
	
file << "\n\tcontained_by{ box{ ";
float a, b, c, x, y, z;
corner1->get( a, b, c );
corner2->get( x, y, z );
file << '<' << a << ',' << b << ',' << -c << ">,\n\t<" << x << ',' << y << ',' << -z << "> } }\n\t";

file << "\n\taccuracy " << accuracy->value();
file << "\n\tmax_gradient " << max_gradient->value();	

if ( precompute_x->value() || precompute_y->value() || precompute_z->value() )
	{
	file << "\n\tprecompute " << precompute_depth->value();
	if ( precompute_x->value() ) file << " x";
	if ( precompute_y->value() ) file << " y";
	if ( precompute_z->value() ) file << " z";
	}

Object3D_with_material::output_to_povray_pass1( file );
file << "\t";
rotation->output_to_povray( file );

size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">";
location->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
file << "\n}  ";
}

//***********************************************
// Save & load
//***********************************************
void Parametric::save( ofstream & file )
{
file << "\nPARAMETRIC{\n";
save_basics( file );
corner1->save( file );
corner2->save( file );
show_bb->save( file );
functionx->save( file );
functiony->save( file );
functionz->save( file );
preview->save( file );
accuracy->save( file );
max_gradient->save( file );
umin->save( file );
umax->save( file );
vmin->save( file );
vmax->save( file );
precompute_depth->save( file );
precompute_x->save( file );
precompute_y->save( file );
precompute_z->save( file );
	
location->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
file << "\n}";
}

bool Parametric::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "PARAMETRIC" ) ) 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 ( corner1->load( file, tag ) ) continue;
 	if ( corner2->load( file, tag ) ) continue;
 	if ( show_bb->load( file, tag ) ) continue;
	if ( functionx->load( file, tag ) ) continue;
	if ( functiony->load( file, tag ) ) continue;
	if ( functionz->load( file, tag ) ) continue;
	if ( preview->load( file, tag ) ) continue;
	if ( accuracy->load( file, tag ) ) continue;
	if ( max_gradient->load( file, tag ) ) continue;
	if ( umin->load( file, tag ) ) continue;
	if ( umax->load( file, tag ) ) continue;
	if ( vmin->load( file, tag ) ) continue;
	if ( vmax->load( file, tag ) ) continue;
	if ( precompute_depth->load( file, tag ) ) continue;
	if ( precompute_x->load( file, tag ) ) continue;
	if ( precompute_y->load( file, tag ) ) continue;
	if ( precompute_z->load( file, tag ) ) continue;
	
	tvio_skip_section(file );
	} while ( tag != NULL );

return true;
}
