//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// glview3d.cc
//
// Vincent LE PRINCE <vincentleprince@users.sourceforge.net>
// Copyright (C) 2000-2001 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/glview3d.h"
#include "include/preferences.h"
#include "include/viewmanager.h"
#include "include/objectlist.h"
#include "include/camera.h"
#include "include/tvio.h"
#include <iostream>

// Définitions
const float labels_width = 2;

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


//*****************************************************
// Constructeur
//*****************************************************
glview3d::glview3d( app_objs *appr,  GtkWidget *box, GtkWidget *f1, GtkWidget *f2 ) : glview( appr, box, f1, f2 )
{
type = TV_VIEW_3D;
set_title( N_("Perspective") );
init_3dview();
}

void glview3d::init_3dview()
{
PREF_DEF
rotate.set( -0.1796346, 0.2291131, 0.0429528, 0.958205 );
zoom = new TvWidget_float( _("Zoom"), "ZOOM", NULL, app_ref, 20 );
zoom->set_range( 100, 1, 1 );
tv_widgets.push_back( zoom );
background = (TvWidget_color*)(pref->v3dbkg_color);
gl_solid->set( true );
gl_lighting->set( true );
	
// Grid
grids[0] = new TvWidget_bool( N_("GridXZ"), "GXZ", NULL, app_ref, pref->gridxz->value() );
grids[1] = new TvWidget_bool( N_("GridXY"), "GXY", NULL, app_ref, pref->gridxy->value() );
grids[2] = new TvWidget_bool( N_("GridYZ"), "GYZ", NULL, app_ref, pref->gridyz->value() );

// Label
axis->set( pref->axis3D->value() );
label->set( pref->labels3D->value() );

attach_popup();
}


glview3d::~glview3d()
{
delete zoom;
for ( int i = 0 ; i < 0 ; i ++ )
	delete grids[i];
}


//*****************************************************
// Pref Changed !
//*****************************************************
void glview3d::pref_changed()
{
glview::pref_changed();
label_list.invalidate();
refresh();
}


//*****************************************************
// Menus
//*****************************************************
const gchar *gridlabs[3] = { N_("Top (XZ)"), N_("Front (XY)"), N_("Right (YZ)") };

void glview3d::add_menu()
{
// Labels
menu_labels = gtk_check_menu_item_new_with_label( N_("Show labels") );
gtk_signal_connect( GTK_OBJECT(menu_labels), "activate", GTK_SIGNAL_FUNC(sign_toggle_3Dlabels), this );
gtk_check_menu_item_set_show_toggle( GTK_CHECK_MENU_ITEM(menu_labels), TRUE );
GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
gtk_menu_append( GTK_MENU(popup), menu_labels );

// Grids
GtkWidget *subm = gtk_menu_new();
for ( int i = 0 ; i < 3 ; i++ )
	{
	menu_grids[i] = gtk_check_menu_item_new_with_label( gridlabs[i] );
	gtk_menu_append( GTK_MENU(subm), menu_grids[i] );
	gtk_check_menu_item_set_show_toggle( GTK_CHECK_MENU_ITEM(menu_grids[i]), TRUE );
	GTK_CHECK_MENU_ITEM(menu_grids[i])->active =  grids[i]->value();
	gtk_signal_connect( GTK_OBJECT(menu_grids[i]), "activate", GTK_SIGNAL_FUNC(sign_toggle_grids), this );
	}
GtkWidget *item = gtk_menu_item_new_with_label( N_("Show grids") );
gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subm );
gtk_menu_append( GTK_MENU(popup), item );
}


//*******************************************************
// Refresh
//*******************************************************
void glview3d::refresh()
{
if ( !visible ) return;
glview::refresh();

// Zoom
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( zoom->value(),  (float)area->allocation.width / (float)area->allocation.height, 1, +100 );
glMatrixMode( GL_MODELVIEW );

// Transformations
glLoadIdentity();
glTranslatef( 0, 0, -5 );
rotate.gl_rotate();

// Center of Interest
glTranslatef( -lookat->get(0), -lookat->get(1), -lookat->get(2) );

display_objects();
gdk_window_process_all_updates ();
}


void glview3d::display_objects()
{
//glEnable( GL_LIGHTING );
//glEnable( GL_LIGHT0 );

OBJLIST_DEF
objlist->display( this );
objlist->display_current_param( this );
// Options
if ( axis->value() ) draw_axis();
if ( label->value() ) draw_label();
for ( int i = 0 ; i < 3 ; i++ ) if ( grids[i]->value() ) draw_grid(i);

glFlush();
gdk_gl_drawable_swap_buffers( get_drawable() );
}

//*******************************************************
// Mouse moved
//*******************************************************
void glview3d::mouse_moved( GdkEventMotion *ev )
{
GdkRectangle rect;
GdkModifierType state;
if ( drag.view != this ) return;
rect.x = 0; rect.y = 0;
rect.width = area->allocation.width;
rect.height = area->allocation.height;
VMAN_DEF
OBJLIST_DEF

if ( ev->is_hint ) gdk_window_get_pointer( ev->window, &drag.current_x, &drag.current_y, &state );
else {
	drag.current_x = (int)ev->x;
	drag.current_y = (int)ev->y;
	state = (GdkModifierType)ev->state;
	}


switch( vmanager->get_pointer_mode() )
	{
	// TRACKBALL MODE	
	case TV_PMODE_TRACKBALL:
		{	
		// SPINNING MODE
		if ( (state & GDK_BUTTON1_MASK) )
			{
			double w = rect.width;
			double h = rect.height;			
			//cout <<	"\n coords < " <<  drag.previous_x << ", " <<  drag.previous_y << ", " << drag.current_x << ", " <<  drag.current_y << ">" ; cout.flush();
			rotate.trackball(
					(2.0*(double)drag.previous_x - w ) / w ,
					(-2.0*(double)drag.previous_y + h  ) / h,
					(2.0*(double)drag.current_x - w  ) / w,
					(-2.0*(double)drag.current_y + h  ) / h
					);
			gtk_widget_draw( area, &rect );
			}

		// Zoom mode
		if ( state & GDK_BUTTON2_MASK )
			{
			zoom->set( zoom->value() + ( (float)(drag.current_y - drag.previous_y) / rect.height ) * 40 );
			gtk_widget_draw( area, &rect );
			}
			
		drag.previous_x = drag.current_x;
		drag.previous_y = drag.current_y;
		}
		break;

	// Default mode : sending datas to current object
	default:
		drag.gdkview = &rect;
		drag.shift = ( state & GDK_SHIFT_MASK ) ? true : false;
		drag.control = ( state & GDK_CONTROL_MASK ) ? true : false;
		if ( state & GDK_BUTTON1_MASK )	objlist->mouse_interact_drag( &drag );
		drag.previous_x = drag.current_x;
		drag.previous_y = drag.current_y;			
		break;
	}
}


void glview3d::mouse_scrolled( GdkEventScroll *ev )
{
VMAN_DEF
float shift = 0;
if (  ev->direction == GDK_SCROLL_UP ) shift = 1;
else shift = -1;
zoom->set( zoom->value() + shift );
//force_refresh();
vmanager->refresh();
}


//********************************************************
// Picking
//********************************************************
void glview3d::set_viewport()
{
gluPerspective( zoom->value(),  (float)area->allocation.width / (float)area->allocation.height, 1, +100 );
glMatrixMode( GL_MODELVIEW );

// Transformations
glLoadIdentity();
glTranslatef( 0, 0, -5 );
rotate.gl_rotate();

// Center of Interest
glTranslatef( -lookat->get(0), -lookat->get(1), -lookat->get(2) );
}


//*********************************************************
// Draw labels
//*********************************************************
void glview3d::draw_label()
{
PREF_DEF

if ( !label_list.exec() )
	{
	label_list.begin();

	glLineWidth( labels_width );
	pref->labels_color->gl_set_rgb();	

	glBegin( GL_LINES );
	// Lettres
	const float pos = 1.05;
	const float pos2 = 1.1;
	float offset = 0.05;
	glVertex3f( pos, offset, 0 );     // X
	glVertex3f( pos2, -offset, 0 );
	glVertex3f( pos, -offset, 0 );
	glVertex3f( pos2, offset, 0 );
	offset = 0.03;
	glVertex3f( offset, pos2, 0 );    // Y
	glVertex3f( -offset, pos, 0 );
	glVertex3f( -offset, pos2, 0 );
	glVertex3f( 0, pos+(pos2-pos)/2, 0 );
	glEnd();
	glBegin( GL_LINE_STRIP );
	glVertex3f( 0, offset, pos2 );     // Z
	glVertex3f( 0, offset, pos );
	glVertex3f( 0, -offset, pos2 );
	glVertex3f( 0, -offset, pos );
	glEnd();
	glLineWidth( LINE_WIDTH );
	label_list.end();
	}
}


//*********************************************************
// Toggle grids
//*********************************************************
void glview3d::toggle_grids()
{
for ( int i = 0 ; i < 3 ; i++ )
	grids[i]->set( GTK_CHECK_MENU_ITEM(menu_grids[i])->active );
refresh();
}

void glview3d::save( ofstream & file )
{
file << "\nGLVIEW3D{\n";
save_basics( file );
zoom->save( file );
for ( int i = 0 ; i < 3 ; i ++ ) grids[i]->save( file );
file << "\n}";
}


bool glview3d::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "GLVIEW3D" ) ) return false;
TvWidget_int wtype( N_("Type"), "TYPE", NULL, app_ref, type );
TvWidget_bool new_maximized( N_("Maximized"), "MAXI", NULL, app_ref, false );
TvWidget_bool new_rolled_up( N_("Rolled"), "ROLLU", NULL, app_ref, false );

char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
    if ( load_basics( file, tag ) ) continue;
	if ( new_maximized.load( file, tag ) ) continue;
	if ( new_rolled_up.load( file, tag ) ) continue;
	if ( wtype.load( file, tag ) ) continue;
	if ( grids[0]->load( file, tag ) ) continue;
	if ( grids[1]->load( file, tag ) ) continue;
	if ( grids[2]->load( file, tag ) ) continue;
	
	if ( zoom->load( file, tag ) ) continue;
	tvio_skip_section(file );
	} while ( tag != NULL );

if ( type != wtype.value() ) { change_type( (ViewType)(wtype.value()) ); return true; }	
if ( maximized->value() != new_maximized.value() )  MaximizeRestore();
if ( rolled_up->value() != new_rolled_up.value() ) RollUp();

GTK_CHECK_MENU_ITEM(menu_gl_solid)->active =  gl_solid->value();
GTK_CHECK_MENU_ITEM(menu_gl_lighting)->active =  gl_lighting->value();
GTK_CHECK_MENU_ITEM(menu_gl_smooth)->active =  gl_smooth->value();
GTK_CHECK_MENU_ITEM(menu_axis)->active =  axis->value();
GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
for ( int i = 0 ; i < 3 ; i ++ )
	GTK_CHECK_MENU_ITEM(menu_grids[i])->active =  grids[i]->value();

if ( !gdk_gl_drawable_gl_begin( get_drawable(), get_context() ) ) return true;
OBJLIST_DEF
objlist->light_disable_all();
gdk_gl_drawable_gl_end( get_drawable() );
return true;
}


void glview3d::clear( ViewType cl_type )
{
if ( cl_type != type )   { change_type( cl_type ); return; }
glview::clear( cl_type );
PREF_DEF
rotate.set( -0.1796346, 0.2291131, 0.0429528, 0.958205 );
zoom->set( 20 );
background = (TvWidget_color*)(pref->v3dbkg_color);

// Grid
grids[0]->set( pref->gridxz->value() );
grids[1]->set( pref->gridxy->value() );
grids[2]->set( pref->gridyz->value() );

// Label
axis->set( pref->axis3D->value() );
label->set( pref->labels3D->value() );
}


void glview3d::reset_home()
{
rotate.set( -0.1796346, 0.2291131, 0.0429528, 0.958205 );
zoom->set( 20 );
local_lookat->set( 0, 0, 0 );
common_lookat->set( 0, 0, 0 );
VMAN_DEF
vmanager->force_refresh();
}

void glview3d::key_press( GdkEventKey *ev )
{
if ( ev->length == 0 ) return;
if ( handle_base_key_press( ev ) ) return;

switch ( ev->keyval )
	{
	case GDK_B:	
	case GDK_b:
		label->toggle();
		GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
		refresh();
		break;
			
	default:
		//cout << "\nUnhandled key -> " << ev->keyval; cout.flush();
		break;
	}
}




//*****************************************************
// Constructeur
//*****************************************************
glcamview::glcamview( app_objs *appr, GtkWidget *box, GtkWidget *f1, GtkWidget *f2 ) : glview3d( appr, box, f1, f2 )
{
type = TV_VIEW_CAMERA;
set_title( N_("Camera") );
init_3dview();
}


//*******************************************************
// Refresh
//*******************************************************
void glcamview::refresh()
{
if ( !visible ) return;
glview::refresh();

glMatrixMode( GL_PROJECTION );
glLoadIdentity();

glcamview::set_viewport();
display_objects();
}


void glcamview::set_viewport()
{
OBJLIST_DEF
Camera *camera = (Camera*)(objlist->get_camera());
if ( camera == NULL ) { glview3d::set_viewport(); return; }
gluPerspective( camera->get_angle(),  /*camera->get_aspect() */(float)area->allocation.width / (float)area->allocation.height, 0.01, +100 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();

// Position et direction
TvWidget_point *location = camera->get_camera_location();
TvWidget_point *look_at = camera->get_lookat();
gfloat x, y, z, a, b, c;
location->get( x, y, z );
look_at->get( a, b, c );

gfloat xoffset = x-a, yoffset = y-b, zoffset = z-c;
gfloat xoffset2= xoffset*xoffset;
gfloat zoffset2=zoffset*zoffset;
float gamma = atan2( xoffset, zoffset );
float beta =  - atan2( yoffset, sqrt( zoffset2 + xoffset2 ) );
float roll = camera->get_roll() /180.0 * M_PI;

float upx = sin(beta)*sin(gamma)*cos(roll) - cos(gamma)*sin(roll);
float upy = cos(beta)*cos(roll);
float upz = cos(roll)*sin(beta)*cos(gamma) + sin(roll)*sin(gamma);

gluLookAt( x, y, z, a ,b, c, upx, upy, upz );
}


void glcamview::save( ofstream & file )
{
file << "\nGLCAMVIEW{\n";
save_basics( file );
//zoom->save( file );
for ( int i = 0 ; i < 3 ; i ++ ) grids[i]->save( file );
file << "\n}";
}


bool glcamview::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "GLCAMVIEW" ) ) return false;
TvWidget_int wtype( _("Type"), "TYPE", NULL, app_ref, type );
TvWidget_bool new_maximized( N_("Maximized"), "MAXI", NULL, app_ref, false );
TvWidget_bool new_rolled_up( N_("Rolled"), "ROLLU", NULL, app_ref, false );

char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
    if ( load_basics( file, tag ) ) continue;
	if ( new_maximized.load( file, tag ) ) continue;
	if ( new_rolled_up.load( file, tag ) ) continue;
	if ( wtype.load( file, tag ) ) continue;
	if ( grids[0]->load( file, tag ) ) continue;
	if ( grids[1]->load( file, tag ) ) continue;
	if ( grids[2]->load( file, tag ) ) continue;
	
//	if ( zoom->load( file, tag ) ) continue;
	tvio_skip_section(file );
	} while ( tag != NULL );

if ( type != wtype.value() ) { change_type( (ViewType)(wtype.value()) ); return true; }	
if ( maximized->value() != new_maximized.value() )  MaximizeRestore();
if ( rolled_up->value() != new_rolled_up.value() ) RollUp();

GTK_CHECK_MENU_ITEM(menu_gl_solid)->active =  gl_solid->value();
GTK_CHECK_MENU_ITEM(menu_gl_lighting)->active =  gl_lighting->value();
GTK_CHECK_MENU_ITEM(menu_gl_smooth)->active =  gl_smooth->value();
GTK_CHECK_MENU_ITEM(menu_axis)->active =  axis->value();
GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
for ( int i = 0 ; i < 3 ; i ++ )
	GTK_CHECK_MENU_ITEM(menu_grids[i])->active =  grids[i]->value();

if ( !gdk_gl_drawable_gl_begin( get_drawable(), get_context() ) ) return true;
OBJLIST_DEF
objlist->light_disable_all();
gdk_gl_drawable_gl_end( get_drawable() );
return true;
}
