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


 //**************************************
// Constructeur
//**************************************
ObjCSG::ObjCSG( app_objs *appref ) : Object3D_with_material( appref )
{
category = TV_OBJ3D_OBJECTS;

// Base
translation = new ObjParam_point( N_("Location"), "LOC", NULL, app_ref, true );
translation->set( 0, 0, 0 );
size = new ObjParam_scale( N_("Size"), "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 );
evaluate = new ObjParam_bool( N_("Evaluate"), "EVAL", NULL, app_ref, true, false );
node_expanded = new TvWidget_bool( NULL, "EXP", NULL, app_ref, true );
}

ObjCSG::ObjCSG( ObjCSG & ref ) : Object3D_with_material( ref )
{
type = ref.type;
translation = new ObjParam_point( *ref.translation );
size = new ObjParam_scale( *ref.size );
rotation = new ObjParam_rotation( *ref.rotation );
evaluate = new ObjParam_bool( *ref.evaluate );
node_expanded = new TvWidget_bool( *ref.node_expanded );
OBJLIST_DEF
for ( unsigned int i = 0 ; i < (ref).children.size() ; i++ )
	{
	Object3D *no =  ref.children[i]->duplicate_yourself();
	children.push_back( no );
	objlist->add_object( no );
	no->set_parent( this );
	}
}

ObjCSG::~ObjCSG()
{
delete translation;
delete size;
delete rotation;
delete node_expanded;
delete evaluate;
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	delete children[i];
}



void ObjCSG::set_layer( ObjectLayer *lay )
{
Object3D::set_layer( lay );
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->set_layer( lay );
}




//**************************************
// Display
//**************************************
void ObjCSG::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;

glPushMatrix();
	
// Position et direction
gfloat x, y, z;
translation->get( x, y, z );
glTranslatef( x, y, z );
rotation->gl_rotate();
size->get( x, y, z );
glScalef( x, y, z );

for ( unsigned int i = 0 ; i < children.size() ; i++ )
	{
	if ( set_col ) set_color();
	if ( selected )   children[i]->select();
	children[i]->display( view );
	if ( selected )   children[i]->unselect();	
	}

glPopMatrix();
}


//***********************************************
// Edit
//***********************************************
void ObjCSG::edit_widget( GtkWidget *wid )
{
bool tt = true;
// Options communes
Object3D_with_material::edit_widget( wid );
evaluate->get_widget( edit_cont, tt );

// Options de geometrie
new_frame( edit_cont, _("General settings") );
translation->get_widget( frame, tt );
size->get_widget( frame, tt );
rotation->get_widget( frame, tt );
get_texture_widgets( frame, tt );

gtk_widget_show_all( wid );
}

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

switch( vmanager->get_pointer_mode() )
	{
	case TV_PMODE_TRANSLATE:
		translation->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 ObjCSG::pref_changed()
{
Object3D_with_material::pref_changed();
translation->pref_changed();
size->pref_changed();
rotation->pref_changed();
evaluate->pref_changed();
}

//***********************************************
// Destroy editor
//***********************************************
void ObjCSG::destroy_editor()
{
Object3D_with_material::destroy_editor();
translation->clear_widget();
size->clear_widget();
rotation->clear_widget();
evaluate->clear_widget();
}

//***********************************************
// Add to tree
//***********************************************
void ObjCSG::add_to_tree( GtkWidget *view, GtkTreeStore *store, GtkTreeSelection *sel, GtkTreeIter *parent, GtkTreeIter *sibling, const gchar *pixmap )
{
Object3D::add_to_tree( view, store, sel, parent, sibling, pixmap );
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->add_to_tree( view, store, sel, &node_iter, NULL );
GtkTreePath *path = gtk_tree_model_get_path( GTK_TREE_MODEL(tree_store), &node_iter );
bool test = false;
if ( node_expanded->value() == true ) test = gtk_tree_view_expand_row( GTK_TREE_VIEW(tree_view), path, FALSE );
else gtk_tree_view_collapse_row( GTK_TREE_VIEW(tree_view), path );
gtk_tree_path_free( path );}

void ObjCSG::save_tree()
{
gboolean isexp;
if ( !GTK_IS_TREE_VIEW(tree_view) ) return;
GtkTreePath *path = gtk_tree_model_get_path( GTK_TREE_MODEL(tree_store), &node_iter );
isexp = gtk_tree_view_row_expanded( GTK_TREE_VIEW(tree_view), path );
g_free( path );
node_expanded->set( isexp );
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->save_tree();
}

//***********************************************
// Child manipulation
//***********************************************
void ObjCSG::add_object( Object3D *obj )
{
OBJLIST_DEF
objlist->add_object( obj );
children.push_back( obj );
obj->set_parent( this );
}


void ObjCSG::paste_object( Object3D *obj )
{
add_object( obj );
obj->add_to_tree( tree_view, tree_store, tree_selection, &node_iter , NULL );
obj->tree_node_select();
}

void ObjCSG::insert_child( Object3D *obj, int position )
{
OBJLIST_DEF
objlist->add_object( obj );
obj->set_parent( this );
vector<Object3D*>::iterator it;
it = children.begin() + position;
children.insert( it, obj );
//obj->add_to_tree( GTK_WIDGET(ctree), ctree_node ,   ( (unsigned int)position == children.size() - 1 ) ? NULL : children[position+1]->get_node() );
obj->add_to_tree( tree_view, tree_store, tree_selection, &node_iter, 	( (unsigned int)position == children.size() - 1 ) ? NULL : children[position+1]->get_node() );	
obj->tree_node_select();
}


bool ObjCSG::has_as_ancester( Object3D *obj )
{
Object3D *mum = this;
while ( mum != NULL )
	{
	mum = mum->get_parent();
	if ( mum == obj ) return true;
	}
return false;
}

void ObjCSG::remove_object( Object3D *obj )
{
vector<Object3D*>::iterator object;
unsigned int i = 0;
for ( object = children.begin() ; object != children.end() ; object++ )
	{
	i++;
	if ( *object != obj ) continue;

	if ( i < 0 ) i = 0;
	if ( i > children.size() - 1 ) i = children.size() - 2;
	obj->remove_from_tree();	
	if ( children.size() > 1 ) children[i]->tree_node_select();				
	else tree_node_select();
	OBJLIST_DEF
	objlist->remove_object( obj );
	children.erase( object );
	break;
	}
	
/*if ( obj->get_parent() != NULL && parent  )
	{
	 obj->get_parent()->remove_object( obj );
	 return;
	 }*/
}


void ObjCSG::duplicate_object( Object3D *obj )
{ 	
vector<Object3D*>::iterator object;
for ( object = children.begin() ; object != children.end() ; object++ )
	{
	if ( *object != obj ) continue;
	Object3D *target = *object;
	
	OBJLIST_DEF
	Object3D *newobj = target->duplicate_yourself();
    objlist->add_object( newobj );
    target = *(++object);
    newobj->add_to_tree( tree_view, tree_store, tree_selection , &node_iter, ( object != children.end() ) ? target->get_node() : NULL );
	children.insert( object, newobj );
	newobj->tree_node_select();				
	break;
	}
}


void ObjCSG::move_child_down( Object3D *obj )
{	
vector<Object3D*>::iterator object;
unsigned int i = 0;
for ( object = children.begin() ; object != children.end() ; object++ )
	{
	i++;
	if ( *object != obj ) continue;
	if ( i ==  children.size() ) return;
	Object3D *target = *object;
	vector<Object3D*>::iterator object2 = object;
	object2 += 1;
	//gtk_ctree_move(  ctree, (*object2)->get_node(), ctree_node, target->get_node() );
	gtk_tree_store_move_after( tree_store, target->get_node(), (*object2)->get_node() );
	
	children.erase( object );
	children.insert( object2, target );
	break;
	}
}

void ObjCSG::move_child_up( Object3D *obj )
{
vector<Object3D*>::iterator object;
int i = 0;
for ( object = children.begin() ; object != children.end() ; object++ )
	{
	i++;
	if ( *object != obj ) continue;
	if ( i ==  1 ) return;
	Object3D *target = *object;
	vector<Object3D*>::iterator object2 = object;
	object2--;
	//gtk_ctree_move(  ctree, obj->get_node(), ctree_node, (*object2)->get_node() );
	gtk_tree_store_move_before( tree_store, target->get_node(), (*object2)->get_node() );
	children.erase( object );
	children.insert( object2, target );
	break;
	}
}


//***********************************************
// save
//***********************************************
void ObjCSG::save_children( ofstream & file )
{
save_basics( file );
translation->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
node_expanded->save( file );
evaluate->save( file );

for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->save( file );
}

void ObjCSG::load_children( ifstream & file )
{
OBJLIST_DEF
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( load_basics( file, tag ) ) continue;
	if ( translation->load( file, tag ) ) continue;
	if ( rotation->load( file, tag ) ) continue;
	if ( size->load( file, tag ) ) continue;
	if ( node_expanded->load( file, tag ) ) continue;
	if ( evaluate->load( file, tag ) ) continue;
	
	Object3D *obj = objlist->create_object( tag );
	if ( obj != NULL )
		{
		if ( obj->load( file, tag ) )
			{
			add_object( obj );
			continue;
			}
		else delete obj;
		}
		
	tvio_skip_section(file );
	} while ( tag != NULL );
}


//***********************************************
// Cutaway for Intersection / Difference
//***********************************************
void CsgCutAway::edit_widget( GtkWidget *wid )
{
bool tt = true;
// Options communes
Object3D_with_material::edit_widget( wid );
evaluate->get_widget( edit_cont, tt );

// Options de geometrie
new_frame( edit_cont, _("General settings") );
translation->get_widget( frame, tt );
size->get_widget( frame, tt );
rotation->get_widget( frame, tt );

cutaway_textures->get_widget( frame, tt );
get_texture_widgets( frame, tt );
gtk_widget_show_all( wid );
}

void CsgCutAway::load_children( ifstream & file )
{
OBJLIST_DEF
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( load_basics( file, tag ) ) continue;
	if ( translation->load( file, tag ) ) continue;
	if ( rotation->load( file, tag ) ) continue;
	if ( size->load( file, tag ) ) continue;
	if ( node_expanded->load( file, tag ) ) continue;
	if ( evaluate->load( file, tag ) ) continue;
	if ( cutaway_textures->load( file, tag ) ) continue;
	
	Object3D *obj = objlist->create_object( tag );
	if ( obj != NULL )
		{
		if ( obj->load( file, tag ) )
			{
			add_object( obj );
			continue;
			}
		else delete obj;
		}
		
	tvio_skip_section(file );
	} while ( tag != NULL );
}

//***********************************************
// UNION
//***********************************************
CsgUnion::CsgUnion( app_objs *appref ) : ObjCSG( appref )
{
type = TV_OBJ3D_CSGUNION;
set_name( "Union" );
split_union = new ObjParam_bool( N_("Split union"), "SPLIT", NULL, app_ref, false, true );
}

void CsgUnion::edit_widget( GtkWidget *wid )
{
bool tt = true;
// Options communes
Object3D_with_material::edit_widget( wid );
evaluate->get_widget( edit_cont, tt );

// Options de geometrie
new_frame( edit_cont, _("General settings") );
translation->get_widget( frame, tt );
size->get_widget( frame, tt );
rotation->get_widget( frame, tt );
split_union->get_widget( frame, tt );
get_texture_widgets( frame, tt );

gtk_widget_show_all( wid );
}

// Output to povray
void CsgUnion::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );

if ( !render->value() ) return;
file << "\n\n// Union : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\nunion {";
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );
Object3D_with_material::output_to_povray_pass1( file );

float x, y, z;
size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">\n\t";
rotation->output_to_povray( file );
translation->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
if ( split_union->value() == false ) file << "\n\tsplit_union off";
file << "\n}";
}

void CsgUnion::save( ofstream & file )
{
file << "\nCSGUNION{\n";
split_union->save( file );
save_children( file );
file << "\n}";
}

bool CsgUnion::load( ifstream & file, char *ltag )
{
if ( strcmp( "CSGUNION", ltag ) ) return false;
OBJLIST_DEF
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( load_basics( file, tag ) ) continue;
	if ( translation->load( file, tag ) ) continue;
	if ( rotation->load( file, tag ) ) continue;
	if ( size->load( file, tag ) ) continue;
	if ( node_expanded->load( file, tag ) ) continue;
	if ( evaluate->load( file, tag ) ) continue;
	if ( split_union->load( file, tag ) ) continue;

	Object3D *obj = objlist->create_object( tag );
	if ( obj != NULL )
		{
		if ( obj->load( file, tag ) )
			{
			add_object( obj );
			continue;
			}
		else delete obj;
		}
		
	tvio_skip_section(file );
	} while ( tag != NULL );
return true;
}


//***********************************************
// DIFFERENCE
//***********************************************
CsgDifference::CsgDifference( app_objs *appref ) : CsgCutAway( appref )
{
type = TV_OBJ3D_CSGDIFFERENCE;
set_name( "Difference" );
}

// Output to povray
void CsgDifference::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );

if ( !render->value() ) return;
file << "\n\n// Difference : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\ndifference {";
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );

if ( cutaway_textures->value() ) file << "\n\tcutaway_textures";
Object3D_with_material::output_to_povray_pass1( file );
	
float x, y, z;
size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">\n\t";
rotation->output_to_povray( file );
translation->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
file << "\n}";
}


void CsgDifference::save( ofstream & file )
{
file << "\nCSGDIFF{\n";
save_children(file);
file << "\n}";
}

bool CsgDifference::load( ifstream & file, char *ltag )
{
if ( strcmp( "CSGDIFF", ltag ) ) return false;
load_children(file );
return true;
}

void CsgDifference::draw_b( glview *view )
{
if ( selected )   children[0]->select();
children[0]->display( view );
if ( selected )   children[0]->unselect();	
}

void CsgDifference::draw_a( glview *view )
{
for ( unsigned int i = 1 ; i < children.size() ; i++ )
	{
	if ( selected )   children[i]->select();
	children[i]->display( view );
	if ( selected )   children[i]->unselect();	
	}
}

void CsgDifference::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( children.size() == 0 ) return;

glPushMatrix();
	
// Position et direction
gfloat x, y, z;
translation->get( x, y, z );
glTranslatef( x, y, z );
rotation->gl_rotate();
size->get( x, y, z );
glScalef( x, y, z );

if ( ! evaluate->value() )
	{
	for ( unsigned int i = 0 ; i < children.size() ; i++ )
		{
		if ( set_col ) set_color();
		if ( selected )   children[i]->select();
		children[i]->display( view );
		if ( selected )   children[i]->unselect();	
		}	
	}
else
	{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glCullFace( GL_FRONT );
	glClear( GL_STENCIL_BUFFER_BIT );
	glDepthMask(GL_TRUE);
	draw_a( view );

	glDepthMask(GL_FALSE);
	glEnable(GL_STENCIL_TEST);
	glStencilMask( GL_TRUE );
	glStencilFunc(GL_ALWAYS, 0, 0);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	glCullFace(GL_BACK);
	draw_b( view );
	
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	glCullFace(GL_FRONT);
	draw_b( view );
	
	glDepthMask(GL_TRUE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glStencilFunc(GL_NOTEQUAL, 0, 1);
	glDisable(GL_DEPTH_TEST);
	glCullFace(GL_FRONT);
	draw_a( view );
	
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_STENCIL_TEST);
	glDepthFunc(GL_ALWAYS);
	draw_b( view );
	glDepthFunc(GL_LESS);
	
	glEnable(GL_DEPTH_TEST);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glCullFace( GL_BACK );
	glClear( GL_STENCIL_BUFFER_BIT );
	draw_b( view );
	
	glDepthMask(GL_FALSE);
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_ALWAYS, 0, 0);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	glCullFace(GL_BACK);
	draw_a( view );
	
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	glCullFace(GL_FRONT);
	draw_a( view );
	
	glDepthMask(GL_TRUE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glStencilFunc(GL_EQUAL, 0, 1);
	glDisable(GL_DEPTH_TEST);
	glCullFace(GL_BACK);
	draw_b( view );
	
	glDisable(GL_STENCIL_TEST);
	glEnable(GL_DEPTH_TEST);
	glClear( GL_STENCIL_BUFFER_BIT );
	}
	
glPopMatrix();
}

//***********************************************
// MERGE
//***********************************************
CsgMerge::CsgMerge( app_objs *appref ) : ObjCSG( appref )
{
type = TV_OBJ3D_CSGMERGE;
set_name( "Merge" );
}

// Output to povray
void CsgMerge::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );

if ( !render->value() ) return;
file << "\n\n// Merge : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\nmerge {";
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );
Object3D_with_material::output_to_povray_pass1( file );
	
float x, y, z;
size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">\n\t";
rotation->output_to_povray( file );
translation->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
file << "\n}";
}


void CsgMerge::save( ofstream & file )
{
file << "\nCSGMERGE{\n";
save_children( file );
file << "\n}";
}

bool CsgMerge::load( ifstream & file, char *ltag )
{
if ( strcmp( "CSGMERGE", ltag ) ) return false;
load_children( file );
return true;
}


//***********************************************
// INTERSECTION
//***********************************************
CsgIntersection::CsgIntersection( app_objs *appref ) : CsgCutAway( appref )
{
type = TV_OBJ3D_CSGINTERSECTION;
set_name( "Intersection" );
}

// Output to povray
void CsgIntersection::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );
	
if ( !render->value() ) return;
file << "\n\n// Intersection : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\nintersection {";
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );

if ( cutaway_textures->value() ) file << "\n\tcutaway_textures";
Object3D_with_material::output_to_povray_pass1( file );
	
float x, y, z;
size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">\n\t";
rotation->output_to_povray( file );
translation->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
file << "\n}";
}

void CsgIntersection::save( ofstream & file )
{
file << "\nCSGINTER{\n";
save_children( file );
file << "\n}";
}

bool CsgIntersection::load( ifstream & file, char *ltag )
{
if ( strcmp( "CSGINTER", ltag ) ) return false;
load_children( file );
return true;
}

void CsgIntersection::draw_b( glview *view )
{
if ( selected )   children[0]->select();
children[0]->display( view );
if ( selected )   children[0]->unselect();	
}

void CsgIntersection::draw_a( glview *view )
{
for ( unsigned int i = 1 ; i < children.size() ; i++ )
	{
	if ( selected )   children[i]->select();
	children[i]->display( view );
	if ( selected )   children[i]->unselect();	
	}
}

void CsgIntersection::display( glview *view, bool set_col )
{
if ( hidden->value() ) return;
if ( children.size() == 0 ) return;

glPushMatrix();
	
// Position et direction
gfloat x, y, z;
translation->get( x, y, z );
glTranslatef( x, y, z );
rotation->gl_rotate();
size->get( x, y, z );
glScalef( x, y, z );

if ( ! evaluate->value() )
	{
	for ( unsigned int i = 0 ; i < children.size() ; i++ )
		{
		if ( set_col ) set_color();
		if ( selected )   children[i]->select();
		children[i]->display( view );
		if ( selected )   children[i]->unselect();	
		}	
	}
else
	{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glCullFace( GL_BACK );
	glClear( GL_STENCIL_BUFFER_BIT );
	glDepthMask(GL_TRUE);
	draw_a( view );

	glDepthMask(GL_FALSE);
	glEnable(GL_STENCIL_TEST);
	glStencilMask( GL_TRUE );
	glStencilFunc(GL_ALWAYS, 0, 0);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	glCullFace(GL_BACK);
	draw_b( view );
	
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	glCullFace(GL_FRONT);
	draw_b( view );
	
	glDepthMask(GL_TRUE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glStencilFunc(GL_NOTEQUAL, 0, 1);
	glDisable(GL_DEPTH_TEST);
	glCullFace(GL_BACK);
	draw_a( view );
	
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_STENCIL_TEST);
	glDepthFunc(GL_ALWAYS);
	draw_b( view );
	glDepthFunc(GL_LESS);
	
	glEnable(GL_DEPTH_TEST);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glCullFace( GL_BACK );
	glClear( GL_STENCIL_BUFFER_BIT );
	draw_b( view );
	
	glDepthMask(GL_FALSE);
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_ALWAYS, 0, 0);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	glCullFace(GL_BACK);
	draw_a( view );
	
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	glCullFace(GL_FRONT);
	draw_a( view );
	
	glDepthMask(GL_TRUE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glStencilFunc(GL_NOTEQUAL, 0, 1);
	glDisable(GL_DEPTH_TEST);
	glCullFace(GL_BACK);
	draw_b( view );
	
	glDisable(GL_STENCIL_TEST);
	glEnable(GL_DEPTH_TEST);
	glClear( GL_STENCIL_BUFFER_BIT );
	}
	
glPopMatrix();
}


//***********************************************
// Light Group
//***********************************************
LightGroup::LightGroup( app_objs *appref ) : ObjCSG( appref )
{
type = TV_OBJ3D_LIGHTGROUP;
set_name( "Light Group" );
global_lights = new ObjParam_bool( N_("Global lights"), "GLOBLIGHTS", NULL, app_ref, true, false );
}

void LightGroup::edit_widget( GtkWidget *wid )
{
bool tt = true;
// Options communes
Object3D::edit_widget( wid, false );

// Options de geometrie
new_frame( edit_cont, _("General settings") );
global_lights->get_widget( frame, tt );
translation->get_widget( frame, tt );
size->get_widget( frame, tt );
rotation->get_widget( frame, tt );

gtk_widget_show_all( wid );
}

void LightGroup::destroy_editor()
{
Object3D::destroy_editor();
translation->clear_widget();
size->clear_widget();
rotation->clear_widget();
global_lights->clear_widget();
}

void LightGroup::pref_changed()
{
Object3D::pref_changed();
translation->pref_changed();
size->pref_changed();
rotation->pref_changed();
global_lights->pref_changed();
}

// Output to povray
void LightGroup::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );

if ( !render->value() ) return;
file << "\n\n// Light group : " << name->value();
file << "\n#declare "; get_underscore_name( file ); file << " ="; 
file << "\nlight_group {";
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );

file << "\n\tglobal_lights " << ( global_lights->value() ? "on" : "off" );
float x, y, z;
size->get( x, y, z );
file << "\n\tscale <" << x << ',' << y << ',' << z << ">\n\t";
rotation->output_to_povray( file );
translation->get( x, y, z );
file << "\n\ttranslate <" << x << "," << y << "," << -z << ">";
file << "\n}";
}


void LightGroup::save( ofstream & file )
{
file << "\nLIGHTGROUP{\n";
save_basics( file );
translation->save( file );
size->save( file );
rotation->save( file );
texture->save( file );
node_expanded->save( file );
global_lights->save( file );

for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->save( file );
file << "\n}";
}

bool LightGroup::load( ifstream & file, char *ltag )
{
if ( strcmp( "LIGHTGROUP", ltag ) ) return false;
OBJLIST_DEF
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( load_basics( file, tag ) ) continue;
	if ( translation->load( file, tag ) ) continue;
	if ( rotation->load( file, tag ) ) continue;
	if ( size->load( file, tag ) ) continue;
	if ( node_expanded->load( file, tag ) ) continue;
	if ( global_lights->load( file, tag ) ) continue;
	
	Object3D *obj = objlist->create_object( tag );
	if ( obj != NULL )
		{
		if ( obj->load( file, tag ) )
			{
			add_object( obj );
			continue;
			}
		else delete obj;
		}
		
	tvio_skip_section(file );
	} while ( tag != NULL );
return true;
}


//***********************************************
// Group
//***********************************************
CsgGroup::CsgGroup( app_objs *appref ) : ObjCSG( appref )
{
type = TV_OBJ3D_GROUP;
set_name( "Group" );
}

void CsgGroup::edit_widget( GtkWidget *wid )
{
// Options communes
Object3D_with_material::edit_widget_base( wid );
gtk_widget_show_all( wid );
}

// Output to povray
void CsgGroup::output_to_povray_pass1( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass1( file );
}

void CsgGroup::output_to_povray_pass2( ofstream & file )
{
for ( unsigned int i = 0 ; i < children.size() ; i++ )
	children[i]->output_to_povray_pass2( file );
}

void CsgGroup::save( ofstream & file )
{
file << "\nGROUP{\n";
save_children( file );
file << "\n}";
}

bool CsgGroup::load( ifstream & file, char *ltag )
{
if ( strcmp( "GROUP", ltag ) ) return false;
OBJLIST_DEF
char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
	if ( load_basics( file, tag ) ) continue;
	if ( node_expanded->load( file, tag ) ) continue;

	Object3D *obj = objlist->create_object( tag );
	if ( obj != NULL )
		{
		if ( obj->load( file, tag ) )
			{
			add_object( obj );
			continue;
			}
		else delete obj;
		}
		
	tvio_skip_section(file );
	} while ( tag != NULL );
return true;
}
