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

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

//**************************************
// Définitions
// parametres du dessin
//**************************************
//const float pi = 3.141592654;
const float hauteur = 0.02;
const float largeur = 0.02;
const float longeur = 0.05;
const float longueur_sky = 0.1;
const float cam_poly[4][2] = { {-largeur,hauteur},{largeur,hauteur},{largeur,-hauteur},{-largeur,-hauteur} };

// Type de camera
const int cam_mode_num = 11;
const gchar * cam_mode_list[cam_mode_num] = { N_("Perspective"), N_("Orthographic"), N_("Fisheye"),
	N_("Ultrawide projection"), N_("Omnimax"), N_("Panoramic"), N_("Cylindrical, vertical fixed"),
	N_("Cylindrical, horizontal fixed"),N_("Cylindrical, vertical unfixed"),N_("Cylindrical, horizontal unfixed"), N_("Spherical")};
const char * cam_modes_output[cam_mode_num] = { "perspective", "orthographic", "fisheye", "ultra_wide_angle",
	"omnimax", "panoramic", "cylinder 1", "cylinder 2", "cylinder 3", "cylinder 4", "spherical" };


//**************************************
// Constructeur
//**************************************
Camera::Camera( app_objs *appref ) : Object3D( appref )
{
type = TV_OBJ3D_CAMERA;
category = TV_OBJ3D_NOCAT;
set_name( "Camera" );

location = new ObjParam_point( N_("Location"), "LOC", _("Coordinates of the point where the camera is installed"), app_ref, true );
location->set( 1, 1, 1 );
look_at = new ObjParam_point( N_("Look at"), "LOOKAT", _("Coordinates of the point the camera is looking at"), app_ref, true );
look_at->set( 0, 0, 0 );
frustum = new ObjParam_bool( N_("Show frustum"), "FRUSTUM", _("Show camera angle of view representation"), app_ref, true, true );
roll = new ObjParam_float_angle( N_("Roll"), "ROLL", _("Rotate the camera around its axis"), app_ref, true, 0 );
roll->set_range( 360, 0, 1 );
angle = new ObjParam_float( N_("Angle"), "ANGLE", NULL, app_ref, true, 40 );
angle->set_range( 360, 0, 1 );
aratio = new ObjParam_float( N_("Aspect ratio"), "ARATIO", NULL, appref, true, 1.33 );
aratio->set_range( 10, 0.01, 0.1 );

cam_mode = new ObjParam_option_combo( N_("Mode"), "MODE", NULL, app_ref, false );
cam_mode->set_list( cam_mode_list, cam_mode_num, 0 );

// Blur
blur =  new ObjParam_bool_activator( N_("Use focal blur"), "BLUR_USED", NULL, app_ref, false, false );
blur_sample = new ObjParam_int( N_("Blur sample"), "BLUR_SAMP", NULL, app_ref, false, 7 );
blur_sample->set_range( 100, 1, 1 );
aperture = new ObjParam_float( N_("Aperture"), "BLUR_APERT", NULL, app_ref, false, 1 );
aperture->set_range( 3, 0, 0.1 );
focal_point = new ObjParam_point_virtual( N_("Focal point"), "BLUR_FPOINT", NULL, app_ref, true );
focal_point->set( 0, 0, 0 );
confidence = new ObjParam_float( N_("Confidence"), "BLUR_CONF", NULL, app_ref, false, 0.5 );
confidence->set_range( 1, 0, 0.01 );
variance = new ObjParam_float( N_("Variance"), "BLUR_VAR", NULL, app_ref, false, 0.02 );
variance->set_range( 1, 0, 0.01 );

normal = new ObjParam_texref( N_("Normal"), "NORM", NULL, app_ref, false );

h_angle = new ObjParam_float( N_("Horiz. angle"), "H_ANG", NULL, app_ref, false, 180.0 );
h_angle->set_range( 360, -360, 0.1, 6 );
v_angle = new ObjParam_float( N_("Vert. angle"), "V_ANG", NULL, app_ref, false, 180.0 );
v_angle->set_range( 360, -360, 0.1, 6 );
}

//**************************************
// Display
// Dessin de la camera
//**************************************
void Camera::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;

// Rappel de liste deja calculee
if ( frustum->changed() || location->changed() || look_at->changed() || roll->changed() || angle->changed() || aratio->changed() )
	list.invalidate();

Object3D::display( view );	
set_color();
if ( ! list.exec() )
	{
	// mise à zero des parametres
	frustum->unchange();
	location->unchange();
	look_at->unchange();
	roll->unchange();
	angle->unchange();
	aratio->unchange();

	// creation de la liste si necessaire
	list.begin();
	
	// Position et direction
	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;
	
	glPushMatrix();
	
	// Translation
	glTranslatef( x, y, z );
	// Rotation
	float gamma = atan2( xoffset, zoffset ) * 180.0 / M_PI;
	float beta =  - atan2( yoffset, sqrt( zoffset2 + xoffset2 ) ) * 180.0 / M_PI;
	glRotatef( gamma, 0, 1, 0 );		
	glRotatef( beta, 1, 0, 0 );
	glRotatef( roll->value(), 0, 0, 1 );
	

	// Boite de la camera
	for ( float offset = 0 ; offset < longeur*4 ; offset += 2*longeur )
		{
		glBegin( GL_LINE_LOOP );
		for ( int i = 0 ; i < 4 ; i++ )
			glVertex3f( cam_poly[i][0], cam_poly[i][1], offset );
		glEnd();
		}
	glBegin( GL_LINES );		
	for ( int i = 0 ; i < 4 ; i++ )
		{
		glVertex3f( cam_poly[i][0], cam_poly[i][1], 0 /*longeur*/ );
		glVertex3f( cam_poly[i][0], cam_poly[i][1], 2*longeur );		
		}
	glEnd();

	// Frustum
	if ( frustum->value() )
		{
		// Distance au point focal
		const float dist = - sqrt( xoffset2 + yoffset*yoffset + zoffset2 );

		// Limites du champs
		const float right = dist * tan( angle->value() / 360.0 * M_PI );
		const float up = right / aratio->value();
		const float champ[4][2] = { {-right,up}, {right,up}, {right,-up}, {-right,-up} };
		
		glBegin( GL_LINES );
		// Rayon principal
		glVertex3f( 0, 0, 0 );
		glVertex3f( 0, 0, dist );

		// Sky
		glVertex3f( 0, 0, 0 );
		glVertex3f( 0, longueur_sky, 0 );
		glVertex3f( 0, longueur_sky, 0 );
		glVertex3f( longueur_sky/4, 2*longueur_sky/3, 0 );		
		glVertex3f( 0, longueur_sky, 0 );
		glVertex3f( -longueur_sky/4, 2*longueur_sky/3, 0 );
		

		// Enveloppe
		for ( int i = 0 ; i < 4 ; i++ )
			{
			glVertex3f( 0, 0, 0 );	
			glVertex3f( champ[i][0], champ[i][1], dist );		
			}
		glEnd();
		glBegin( GL_LINE_LOOP );
		for ( int i = 0 ; i < 4 ; i++ )
			glVertex3f( champ[i][0], champ[i][1], dist );
		glEnd();
		}
	
	glPopMatrix();	
	list.end();
	}
}


//***********************************************
// Edit
//***********************************************
void Camera::edit_widget( GtkWidget *wid )
{
PREF_DEF
bool tt = pref->tooltips->value();
// Options communes
Object3D::edit_widget( wid, false );
//hollow->unget_widget();
frustum->get_widget( edit_cont, tt );


// Options de geometrie
new_table( edit_cont,_("General settings"), 6 );
cam_mode->get_widget( table, tt, 1 );	
location->get_widget( table, tt, 2 );
look_at->get_widget( table, tt, 3 );
roll->get_widget( table, tt, 4 );
angle->get_widget( table, tt, 5 );
aratio->get_widget( table, tt, 6 );
	

new_table( edit_cont, N_("Spherical settings"), 2 );
h_angle->get_widget( table, tt, 1 );
v_angle->get_widget( table, tt, 2 );
mode_changed();

// Focal blur
new_frame( edit_cont, N_("Focal blur") );
GtkWidget *box = gtk_vbox_new( FALSE, 0 );
blur->get_widget( frame, tt, box );
gtk_box_pack_start( GTK_BOX(frame), box, TRUE, TRUE, 3 );
new_table_no_frame( box, 5 );
	blur_sample->get_widget( table, tt, 1 );
	aperture->get_widget( table, tt, 2 );
	focal_point->get_widget( table, tt, 3 );
	confidence->get_widget( table, tt, 4 );
	variance->get_widget( table, tt, 5 );

// Camera ray perturbation
new_table( edit_cont, N_("Ray perturbation"), 1 );
	normal->get_widget( table, tt, 1 );

gtk_widget_show_all( wid );
cam_mode->connect_signal( GTK_SIGNAL_FUNC(sign_camera_mode), this );
}


void Camera::mode_changed()
{
//cam_mode->flush();
//cout << "\nCamera mode changed to : " << cam_mode->value();
//cout.flush();
if ( cam_mode->value() == 10 ) 
	{
	h_angle->set_widget_active();
	v_angle->set_widget_active();
	}
else
	{
	h_angle->set_widget_inactive();
	v_angle->set_widget_inactive();
	}	
}


//***********************************************
// Pref_changed
//***********************************************
void Camera::pref_changed()
{
Object3D::pref_changed();
cam_mode->pref_changed();
frustum->pref_changed();
location->pref_changed();
look_at->pref_changed();
roll->pref_changed();
angle->pref_changed();
aratio->pref_changed();

blur->pref_changed();
blur_sample->pref_changed();
focal_point->pref_changed();
aperture->pref_changed();
confidence->pref_changed();
variance->pref_changed();

normal->pref_changed();
h_angle->pref_changed();
v_angle->pref_changed();
}

//***********************************************
// Pref_changed
//***********************************************
void Camera::destroy_editor()
{
Object3D::destroy_editor();
cam_mode->clear_widget();
frustum->clear_widget();
location->clear_widget();
look_at->clear_widget();
roll->clear_widget();
angle->clear_widget();
aratio->clear_widget();

blur->clear_widget();
blur_sample->clear_widget();
focal_point->clear_widget();
aperture->clear_widget();
confidence->clear_widget();
variance->clear_widget();
normal->clear_widget();
v_angle->clear_widget();
h_angle->clear_widget();
}

//***********************************************
// Mouse drag
//***********************************************
void Camera::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_ROTATE:
		{ roll->mouse_drag( drag ); break; }

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

	default: 
		break;
	}
}


//***********************************************
// Output to povray
//***********************************************
void Camera::output_to_povray_pass2( ofstream & file )
{
file << "\n\n// Camera : " << name->value();

file << "\n\ncamera {\n\t";
file << cam_modes_output[cam_mode->value()];

file << "\n\tlocation ";
location->output_to_povray(file);
if ( cam_mode->value() == 10 )
	{
	file << "\n\tangle ";
	h_angle->output_to_povray(file);
	file << "\t";
	v_angle->output_to_povray(file);
	}
else
	{
	file << "\n\tangle ";
	angle->output_to_povray(file);
	}
file << "\n\tup <0,1,0>\n\tright <";
aratio->output_to_povray(file);

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 rolla = roll->value() /180.0 * M_PI;

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

file << ",0,0>\n\tsky < " << upx << ", " << upy << ", " << -upz  << " >";
/*file << cos( roll->value() / 180.0 * M_PI );
file << ',';
file << sin( roll->value() / 180.0 * M_PI );
file << '>';*/
file << "\n\tlook_at ";
look_at->output_to_povray(file);
	

if ( blur->value() )
	{
	file << "\n\taperture ";
	aperture->output_to_povray(file);
	file << "\n\tfocal_point ";
	focal_point->output_to_povray(file);
	file << "\n\tblur_samples ";
	blur_sample->output_to_povray(file);
	file << "\n\tconfidence ";
	confidence->output_to_povray(file);
	file << "\n\tvariance ";
	variance->output_to_povray(file);
	}
	
PovMaterial *mat = ((PovMaterial*)normal->get_current());
if ( mat != NULL )
	if ( mat->has_normal() ) mat->output_to_povray_normal( file );

file << "\n}";
}

void Camera::save( ofstream & file )
{
file << "\nCAMERA{\n";
save_basics( file );
cam_mode->save( file );
frustum->save( file );
location->save( file );
look_at->save( file );
roll->save( file );
angle->save( file );
aratio->save( file );

blur->save( file );
blur_sample->save( file );
focal_point->save( file );
aperture->save( file );
confidence->save( file );
variance->save( file );
h_angle->save( file );
v_angle->save( file );
file << "\n}";
}


bool Camera::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "CAMERA" ) ) 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 ( cam_mode->load( file, tag ) ) continue;
 	if ( frustum->load( file, tag ) ) continue;
 	if ( look_at->load( file, tag ) ) continue;
 	if ( roll->load( file, tag ) ) continue;
 	if ( angle->load( file, tag ) ) continue;
 	if ( aratio->load( file, tag ) ) continue;
 	if ( blur->load( file, tag ) ) continue;
 	if ( blur_sample->load( file, tag ) ) continue;
 	if ( focal_point->load( file, tag ) ) continue;
 	if ( aperture->load( file, tag ) ) continue;
 	if ( confidence->load( file, tag ) ) continue;
 	if ( variance->load( file, tag ) ) continue;
 	if ( h_angle->load( file, tag ) ) continue;
 	if ( v_angle->load( file, tag ) ) continue;
		
	tvio_skip_section(file );
	} while ( tag != NULL );

return true;
}
