//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// heightfield.cc
//
// Kent Gustavsson  <oden@gmx.net>
// 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/heightfield.h"
#include "include/viewmanager.h"
#include "include/preferences.h"
#include "include/objectlist.h"
#include "include/tvio.h"
#include "include/utils3d.h"
#include <gdk-pixbuf/gdk-pixbuf.h>




//**************************************
// Constructeur
//**************************************

const int hfiletype_num = 8;
const char *hfiletype_ext[hfiletype_num] = { "gif","tga","pot","png","pgm","ppm", "jpg", "tif" };
const char *hfiletype_def[hfiletype_num] = { "gif","tga","pot","png","pgm","ppm", "jpeg", "tiff" };

Heightfield::Heightfield( app_objs *appref ) : Object3D_with_material( appref )
{
type = TV_OBJ3D_HEIGHTFIELD;
category = TV_OBJ3D_OBJECTS;
set_name( "HeightField" );

// Base
location = new ObjParam_point( _("Location"), "LOC", NULL, app_ref, true );
location->set( 0, 0, 0 );
size = new ObjParam_scale( _("Size"), "SIZE", NULL, app_ref, true );
size->set( 1, 1, 1 );
rotation = new ObjParam_rotation( _("Rotation"), "ROT", NULL, app_ref, true );
rotation->set( 0, 0, 0 );
water_level = new ObjParam_float (_("Water level"), "W_LEVEL", NULL, app_ref, true, 0 );
water_level->set_range( 1, 0, 0.01, 5 );
smooth = new ObjParam_bool (_("Smooth"), "SMOOTH", NULL, app_ref, true, false );
hierarchy = new ObjParam_bool (_("Hierarchy"), "HIERARCHY", NULL, app_ref, true, true);
bitmap = new ObjParam_file( _("Image"), "BITMAP", NULL, app_ref, true );
bitmap->set_list( hfiletype_ext, hfiletype_def, hfiletype_num );
preview = new ObjParam_bool ( _("Preview"), "PREV", NULL, app_ref, true, false );
}

Heightfield::Heightfield( Heightfield & ref ) : Object3D_with_material( ref )
{
location = new ObjParam_point( *ref.location );
size = new ObjParam_scale( *ref.size );
rotation = new ObjParam_rotation( *ref.rotation );
smooth = new ObjParam_bool( *ref.smooth );
water_level = new ObjParam_float( *ref.water_level );
hierarchy = new ObjParam_bool( *ref.hierarchy );
bitmap = new ObjParam_file( *ref.bitmap );
preview = new ObjParam_bool( *ref.preview );
}

Heightfield::~Heightfield()
{
delete location;
delete size;
delete rotation;
delete smooth;
delete water_level;
delete hierarchy;
delete bitmap;
delete preview;
}

//**************************************
// Display
// Dessin de la boite
//**************************************
void Heightfield::display( glview *view, bool setcol )
{
if ( hidden->value() ) return;
if ( location->changed() || size->changed() || rotation->changed() || water_level->changed() || preview->changed() || bitmap->changed() )
	list.invalidate();

Object3D::display( view );
if ( setcol ) set_color();
if ( ! list.exec() )
 {
	 PREF_DEF
	// mise à zero des parametres
	location->unchange();
	size->unchange();
	rotation->unchange();
	water_level->unchange();
	preview->unchange();
	bitmap->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 );
	rotation->gl_rotate();
	glScalef( a, b, c );
	bool do_preview = preview->value();
	if ( bitmap->value() == NULL )  do_preview = false;
	GdkPixbuf *image = NULL;
	if ( do_preview ) image = gdk_pixbuf_new_from_file( bitmap->value(), NULL );
	if ( image == NULL ) do_preview = false;
	if ( do_preview ) {
		// Mesh preview
		float hf_prev_quality = pref->hf_preview_quality->value() / 100.0;
		int yadv = gdk_pixbuf_get_height( image );
		int ysteps = (int)(float(yadv)*hf_prev_quality);
		float ystep_width = ((float)yadv)/((float)(ysteps-1));
		int xadv = gdk_pixbuf_get_width( image );
		int xsteps = (int)(float(xadv)*hf_prev_quality);
		float xstep_width = ((float)xadv)/((float)(xsteps-1));
		int rowstride = gdk_pixbuf_get_rowstride( image );
		float waterlevel = water_level->value();
		guchar *pixels = gdk_pixbuf_get_pixels( image );
		float xpoints[xsteps], ypoints[ysteps];
		float adv = 0;
		int channels = gdk_pixbuf_get_n_channels( image );
		float offset = 1.0 / (float)(xsteps);
		for ( int i = 0 ; i < xsteps ; i++ ) { xpoints[i] = adv; adv += offset; }
		
		adv = -1;
		offset = 1.0 / (float)(ysteps);
		for ( int j = 0 ; j < ysteps ; j++ ) { ypoints[j] = adv; adv += offset; }
		float zpoints[xsteps][ysteps];
		for ( int j = 0 ; j < ysteps; j++)
		{
			for ( int i = 0 ; i < xsteps ; i++)  
			{				
				guchar *ptr = pixels + ((int)(j*ystep_width))*rowstride + ((int)(i*xstep_width))*channels;
				zpoints[j][i] = (float)( ptr[0]*256 + ptr[1] ) / 65536.0 ;
			}
		}
			
		for ( int j = 0 ; j < ysteps-1; j ++ )
			{
			glBegin( GL_TRIANGLES );
			for ( int i = 0 ; i < xsteps-1 ; i += 1 )
				{
				if ( zpoints[j][i] >= waterlevel || zpoints[j+1][i] >= waterlevel || zpoints[j][i+1] >= waterlevel )
					{
					get_normal( xpoints[i], zpoints[j][i], ypoints[j], xpoints[i], zpoints[j+1][i], ypoints[j+1], xpoints[i+1], zpoints[j][i+1], ypoints[j] );
					// need optimization : normal y is always the same, don't need to calculate :)
					glVertex3f( xpoints[i], zpoints[j][i], ypoints[j] );
					glVertex3f( xpoints[i], zpoints[j+1][i], ypoints[j+1] );
					glVertex3f( xpoints[i+1], zpoints[j][i+1], ypoints[j] );		
					}

				if ( zpoints[j+1][i] >= waterlevel || zpoints[j][i+1] >= waterlevel || zpoints[j+1][i+1] >= waterlevel )
					{
					get_normal( xpoints[i], zpoints[j+1][i], ypoints[j+1], xpoints[i+1], zpoints[j][i+1], ypoints[j], xpoints[i+1], zpoints[j+1][i+1], ypoints[j+1], true );
					glVertex3f( xpoints[i], zpoints[j+1][i], ypoints[j+1] );
					glVertex3f( xpoints[i+1], zpoints[j][i+1], ypoints[j] );			
					glVertex3f( xpoints[i+1], zpoints[j+1][i+1], ypoints[j+1] );		
					}
				}
			glEnd();
			}
		}
	else
		{		
		// Bounding box
		glBegin( GL_LINE_LOOP );
			glVertex3f( 0, 0, 0 );
			glVertex3f( 1, 0, 0 );
			glVertex3f( 1, 0, -1 );
			glVertex3f( 0, 0, -1 );
		glEnd();
		glBegin( GL_LINE_LOOP );
			glVertex3f( 0, 1, 0 );
			glVertex3f( 1, 1, 0 );
			glVertex3f( 1, 1, -1 );
			glVertex3f( 0, 1, -1 );
		glEnd();
		glBegin( GL_LINES );
			glVertex3f( 0, 0, 0 );
			glVertex3f( 0, 1, 0 );
			glVertex3f( 0, 0, -1 );
			glVertex3f( 0, 1, -1 );
			glVertex3f( 1, 0, -1 );
			glVertex3f( 1, 1, -1 );
			glVertex3f( 1, 0, 0 );
			glVertex3f( 1, 1, 0 );
		glEnd();
	
		}
	glPopMatrix();
	list.end();
	}
}


//***********************************************
// Edit
//***********************************************
void Heightfield::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"), 9 );
	bitmap->get_widget( table, tt, 1 );

	smooth->get_widget( table, tt, 3 );
	water_level->get_widget( table, tt, 4 );
	hierarchy->get_widget( table, tt, 5 );
	preview->get_widget( table, tt, 6 );

	location->get_widget( table, tt, 7 );
	size->get_widget( table, tt, 8 );
	rotation->get_widget( table, tt, 9 );

get_texture_widgets( edit_cont, tt );

gtk_widget_show_all( wid );
}

//***********************************************
// Mouse drag
//***********************************************
void Heightfield::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 Heightfield::pref_changed()
{
Object3D::pref_changed();
location->pref_changed();
size->pref_changed();
rotation->pref_changed();
smooth->pref_changed();
water_level->pref_changed();
hierarchy->pref_changed();
bitmap->pref_changed();
preview->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void Heightfield::destroy_editor()
{
Object3D::destroy_editor();
location->clear_widget();
size->clear_widget();
texture->clear_widget();
rotation->clear_widget();
smooth->clear_widget();
water_level->clear_widget();
hierarchy->clear_widget();
bitmap->clear_widget();
preview->clear_widget();
}

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

file << "\nheight_field {\n\t";
bitmap->output_to_povray( file );
 file << "\n\t";
if ( smooth->value() ) file << "smooth\n\t";
if ( hierarchy->value() == 0 ) file << "hierarchy off\n\t"; // Doesn't work
if ( water_level->value() != 0 ) file << "water_level " << water_level->value() << "\n\t";
Object3D_with_material::output_to_povray_pass1( file );
float a, b, c, x, y, z;
location->get( x, y, z );
size->get( a, b, c );
file << "scale <" << a << ',' << b << ',' << c << ">\n\t";
rotation->output_to_povray( file );
file << "\n\ttranslate <" << x << "," << y << "," << -z << "> \n\t";
file << "\n}";
}



void Heightfield::save( ofstream & file )
{
file << "\nHEIGHTFIELD{\n";
save_basics( file );
location->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
smooth->save( file );
water_level->save( file );
hierarchy->save( file );
bitmap->save( file );
preview->save( file );
file << "\n}";
}

bool Heightfield::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "HEIGHTFIELD" ) ) return false;

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 ( smooth->load( file, tag ) ) continue;
	if ( water_level->load( file, tag ) ) continue;
	if ( hierarchy->load( file, tag ) ) continue;
	if ( bitmap->load( file, tag ) ) continue;
	if ( preview->load( file, tag ) ) continue;

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

return true;
}
