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

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

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

// Base
base_point = new ObjParam_point_virtual( N_("Base point"), "BPOINT", NULL, app_ref, true );
base_point->set( 0, 0, 0 );
cap_point = new ObjParam_point_virtual( N_("Cap point"), "CPOINT", NULL, app_ref, true );
cap_point->set( 0, 0.3, 0 );
radius = new ObjParam_float( N_("Radius"), "RAD", NULL, app_ref, true, 0.15 );
radius->set_range( 1000, -1000, 0.1, 5 );
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( 1, 1, 1 );
rotation = new ObjParam_rotation( N_("Rotation"), "ROT", NULL, app_ref, true );
rotation->set( 0, 0, 0 );
open = new ObjParam_bool( N_("Open"), "OPEN", NULL, app_ref, true, false );
}

Cylinder::Cylinder( Cylinder & ref ) : Object3D_with_material( ref )
{
location = new ObjParam_point( *ref.location );
base_point = new ObjParam_point_virtual( *ref.base_point );
cap_point = new ObjParam_point_virtual( *ref.cap_point );
radius = new ObjParam_float( *ref.radius );
size = new ObjParam_scale( *ref.size );
rotation = new ObjParam_rotation( *ref.rotation );
open = new ObjParam_bool( *ref.open );
}

Cylinder::~Cylinder()
{
delete location;
delete base_point;
delete cap_point;
delete radius;
delete size;
delete rotation;
delete open;
}

//**************************************
// Display
// Dessin du cylindre
//**************************************
void Cylinder::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( location->changed() || radius->changed() || base_point->changed() || cap_point->changed() || size->changed() || rotation->changed() || open->changed() )
	list.invalidate();
	
Object3D::display( view );	
if ( set_col ) set_color();
if ( ! list.exec() )
	{
	// mise à zero des parametres
	location->unchange();
	base_point->unchange();
	cap_point->unchange();
	radius->unchange();
	size->unchange();
	rotation->unchange();
	open->unchange();

	// creation de la liste si necessaire
	list.begin();
	glPushMatrix();

	// Position et direction
	float x, y, z, a, b, c;
	location->get( x, y, z );
	glTranslatef( x, y, z );
	size->get( x, y, z );
	glScalef( x, y, z );
	rotation->gl_rotate();

    // Def of points
	base_point->get( x, y, z );
	cap_point->get( a, b, c );
	glTranslatef( x, y, z );	
	glRotatef( atan2( a-x, c-z )*180.0/M_PI, 0, 1, 0 );	
	glRotatef( -atan2( b-y, sqrt( (c-z)*(c-z) + (a-x)*(a-x) ) )*180.0/M_PI, 1, 0, 0 );

	PREF_DEF
	int quality = 6 + pref->preview_quality->value()*6;
	
	float dist = sqrt( (x-a)*(x-a) + (y-b)*(y-b) + (z-c)*(z-c) );
	float rad = radius->value();
	GLUquadricObj *quadobj = gluNewQuadric();
	if ( !quadobj ) { app_warning( N_("Out of memory" ) ); return; }	
	gluQuadricDrawStyle( quadobj, GLU_FILL );
	gluQuadricNormals( quadobj, GLU_SMOOTH );
	gluQuadricOrientation( quadobj, GLU_OUTSIDE );
   	gluCylinder(quadobj, rad, rad, dist, quality, quality );

    if ( ! open->value() )
    	{
    	//glRotatef( 90, 1, 0, 0 );
		GLUquadricObj *disk1 = gluNewQuadric();
		if ( !quadobj ) { app_warning( N_("Out of memory" ) ); return; }	
		gluQuadricDrawStyle( disk1, GLU_FILL );
		gluQuadricNormals( disk1, GLU_SMOOTH );
		gluQuadricOrientation( disk1, GLU_OUTSIDE );
	    gluDisk( disk1, 0, rad, quality, 6 );

    	glTranslatef( 0, 0, dist );
		GLUquadricObj *disk2 = gluNewQuadric();
		if ( !quadobj ) { app_warning( N_("Out of memory" ) ); return; }	
		gluQuadricDrawStyle( disk2, GLU_FILL );
		gluQuadricNormals( disk2, GLU_SMOOTH );
		gluQuadricOrientation( disk2, GLU_OUTSIDE );
	    gluDisk( disk2, 0, rad, quality, 6 );    	
    	}
	glPopMatrix();
	list.end();
	}
}


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

// Options de geometrie
new_table( edit_cont, _("General settings"), 7 );
	base_point->get_widget( table, tt, 1 );
	cap_point->get_widget( table, tt, 2 );
	radius->get_widget( table, tt, 3 );
	location->get_widget( table, tt, 4 );
	size->get_widget( table, tt, 5 );
	rotation->get_widget( table, tt, 6 );
	open->get_widget( table, tt, 7 );

get_texture_widgets( edit_cont, tt );
gtk_widget_show_all( wid );
}

//***********************************************
// Mouse drag
//***********************************************
void Cylinder::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 Cylinder::pref_changed()
{
Object3D::pref_changed();
location->pref_changed();
base_point->pref_changed();
cap_point->pref_changed();
radius->pref_changed();
size->pref_changed();
open->pref_changed();
rotation->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void Cylinder::destroy_editor()
{
Object3D::destroy_editor();
location->clear_widget();
base_point->clear_widget();
cap_point->clear_widget();
radius->clear_widget();
size->clear_widget();
texture->clear_widget();
rotation->clear_widget();
open->clear_widget();
}

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

file << "\ncylinder {\n\t";
float x,y,z;
base_point->get( x, y, z );
file << "<" << x << ',' << y << ',' << -z << ">, ";
cap_point->get( x, y, z );
file << "<" << x << ',' << y << ',' << -z << ">, " << radius->value();
if ( open->value() ) file << "open\n\t";
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}";
}


void Cylinder::save( ofstream & file )
{
file << "\nCYLINDER{\n";
save_basics( file );
size->save( file );
base_point->save( file );
cap_point->save( file );
radius->save( file );
rotation->save( file );
location->save( file );
texture->save( file );
open->save( file );
file << "\n}";
}

bool Cylinder::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "CYLINDER" ) ) 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 ( base_point->load( file, tag ) ) continue;
 	if ( cap_point->load( file, tag ) ) continue;
 	if ( radius->load( file, tag ) ) continue;
 	if ( size->load( file, tag ) ) continue;
 	if ( rotation->load( file, tag ) ) continue;
	if ( open->load( file, tag ) ) continue;
	
	tvio_skip_section(file );
	} while ( tag != NULL );

return true;
}



//**************************************
// Constructeur
//**************************************
BlobCylinder::BlobCylinder( app_objs *appref ) : Cylinder( appref )
{
type = TV_OBJ3D_BLOBCYLINDER;
category = TV_OBJ3D_OBJECTS;
set_name( "Blob Cylinder" );

// Base
strength = new ObjParam_float( N_("Strength"), "STRENGTH", NULL, app_ref, true, 1 );
strength->set_range( 100, -100, 0.1, 3 );
}

BlobCylinder::BlobCylinder( BlobCylinder & ref ) : Cylinder( ref )
{
strength = new ObjParam_float( *ref.strength );
}

BlobCylinder::~BlobCylinder()
{
delete strength;
}

//***********************************************
// Edit
//***********************************************
void BlobCylinder::edit_widget( GtkWidget *wid )
{
bool tt = true;
// Options communes
Object3D::edit_widget( wid );

// Options de geometrie
new_table( edit_cont, _("General settings"), 7 );
	strength->get_widget( table, tt, 1 );
	base_point->get_widget( table, tt, 1 );
	cap_point->get_widget( table, tt, 2 );
	radius->get_widget( table, tt, 3 );
	location->get_widget( table, tt, 4 );
	size->get_widget( table, tt, 5 );
	rotation->get_widget( table, tt, 6 );
	open->get_widget( table, tt, 7 );

gtk_widget_show_all( wid );
}



//***********************************************
// Pref_changed
//***********************************************
void BlobCylinder::pref_changed()
{
Cylinder::pref_changed();
strength->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void BlobCylinder::destroy_editor()
{
Cylinder::destroy_editor();
strength->clear_widget();
}

//***********************************************
// Output to povray
//***********************************************
void BlobCylinder::output_to_povray_pass1( ofstream & file )
{
if ( !render->value() ) return;
file << "\n\n//Cylinder " << name->value();
file << "\ncylinder {\n\t";
float x,y,z;
base_point->get( x, y, z );
file << "<" << x << ',' << y << ',' << -z << ">, ";
cap_point->get( x, y, z );
file << "<" << x << ',' << y << ',' << -z << ">, " << radius->value();
file << " , strength " << strength->value() << " ";
if ( open->value() ) file << "open\n\t";
Object3D::output_to_povray_pass1( file );

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

file << "\n}";
}


void BlobCylinder::save( ofstream & file )
{
file << "\nBLOBCYLINDER{\n";
save_basics( file );
size->save( file );
base_point->save( file );
cap_point->save( file );
radius->save( file );
rotation->save( file );
location->save( file );
radius->save( file );
strength->save( file );
open->save( file );
file << "\n}";
}

bool BlobCylinder::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "BLOBCYLINDER" ) ) 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 ( base_point->load( file, tag ) ) continue;
 	if ( cap_point->load( file, tag ) ) continue;
 	if ( radius->load( file, tag ) ) continue;
 	if ( size->load( file, tag ) ) continue;
 	if ( rotation->load( file, tag ) ) continue;
	if ( open->load( file, tag ) ) continue;
	if ( strength->load( file, tag ) ) continue;
	
	tvio_skip_section(file );
	} while ( tag != NULL );

return true;
}


/*float *Cylinder::get_location()
{
float x, y, z, a, b, c;
base_point->get( x, y, z );
cap_point->get( a, b, c );
float c1 = a-x; float c2 =b-y; float c3 = c-z;
location->get( a, b, c );
static float loc[3];
loc[0] = c1-a; loc[1] = c2-b; loc[2] = c3-c;
return loc;
}*/

float BlobCylinder::get_strength_radius()
{
float val = 0;

float x, y, z, a, b, c;
base_point->get( x, y, z );
cap_point->get( a, b, c );
float rad = ( sqrt( (a-x)*(a-x) + (b-y)*(b-y) + (c-z)*(c-z) ) + radius->value() );
float stren =  strength->value();
if  ( stren < 1 ) rad *= stren;
float *sc = size->get();

for ( int i = 0 ; i < 3 ; i++ ) 
	{
	float test = rad * sc[i];
	if ( test > val ) val = test;
	}
return val;
}


float BlobCylinder::get_strength( float x, float y, float z )
{
float density = 0;
float a, b, c;
location->get( a, b, c );
float ux = x-a; float uy = y-b; float uz = z-c;

float angles[3], cosa[3], sina[3];
rotation->get_as_radian( angles[0], angles[1], angles[2] );
for ( int i = 0 ; i < 3 ; i++ ) { cosa[i] = cos( angles[i] ); sina[i] = sin( angles[i] ); }
// X rotate
float tux = ux;
float tuy = cosa[0]*uy - sina[0]*uz;
float tuz = cosa[0]*uz + sina[0]*uy;
// Y rotate
ux = tuz*sina[1] + tux*cosa[1];
uy = tuy;
uz = tuz*cosa[1] - tux*sina[1];
// Z rotate
tux = cosa[2]*ux - sina[2]*uy;
tuy = cosa[2]*uy + sina[2]*ux;
tuz = uz;
	
size->get( a, b, c );
ux = tux/a; uy = tuy/b; uz = tuz/c;

float a1, a2, a3, b1, b2, b3;
cap_point->get( a1, a2, a3 );
base_point->get( b1, b2, b3 );
float coeff1 = (b1-a1)*a1 + (b2-a2)*a2 + (b3-a3)*a3 - b1*ux + a1*ux - b2*uy + a2*uy - b3*uz + a3*uz;
float coeff2 = (b1-a1)*(b1-a1) + (b2-a2)*(b2-a2)+ (b3-a3)*(b3-a3);
float prox = a1 - ( coeff1 * (b1-a1) / coeff2 );
float proy = a2 - ( coeff1 * (b2-a2) / coeff2 );
float proz = a3 - ( coeff1 * (b3-a3) / coeff2 );

float da = square( a1 - prox ) + square( a2 - proy ) + square( a3 - proz );
float db = square( b1 - prox ) + square( b2 - proy ) + square( b3 - proz );
float dab = square( a1 - b1 ) + square( a2 - b2 ) + square( a3 - b3 );
float distance;
if ( da < dab && db < dab ) distance = square( prox - ux ) + square( proy - uy ) + square( proz - uz );
else
	if ( da < db ) distance = square( a1 - ux ) + square( a2 - uy ) + square( a3 - uz );
		else distance = square( b1 - ux ) + square( b2 - uy ) + square( b3 - uz );

// Radius calculation
float rad = radius->value();
rad *= rad;
	
if ( distance > rad ) return 0;
density = strength->value() * square( 1 - (distance / rad)   );

return density;
}
