/**************************************************************************
  Copyright (C) 1992 Guy Moreillon
  All rights reserved.

  This software may be freely copied, modified, and redistributed
  provided that this copyright notice is preserved on all copies.

  You may not distribute this software, in whole or in part, as part of
  any commercial product without the express consent of the authors.

  There is no warranty or other guarantee of fitness of this software
  for any purpose.  It is provided solely "as is".
**************************************************************************/
/**************************************************************************

			     PREPRAD
		     (Scene preparation program)
		    	    Version 1.0
			       1992		    	    
		    	    
		  
		    	  Guy Moreillon
		    	  
**************************************************************************/

/**************************************************************************
  Fichier	: preprad.c
  Description	: Programme principal
**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <gl.h>
#include <gl/device.h>

#include <types.h>
#include <macros.h>
#include <misc.h>
#include <parse.h>
#include <ioread.h>
#include <load_image.h>
#include <free.h>
#include "prtype.h"
#include "init.h"
#include "select.h"
#include "ray.h"
#include "display.h"
#include "write.h"
#include "object.h"
#include "popup.h"
#include "forms.h"
#include "panels.h"
#include "preprad.h"

#define DEG2RAD(A)	((A)*0.017453)
#define RAD2DEG(A)	((A)/0.017453)

#define OVYELLOW	1
#define WIN3D_W		800
#define WIN3D_H		800

#define CTRL_PAN_LABEL	"Control Panel"
#define VISU_WIN_LABEL	"Visualization"
#define HIER_WIN_LABEL	"Hierarchy"

extern FILE *yyin;

static float mat[] = {
    AMBIENT,	0.1, 0.1, 0.1,
    DIFFUSE,	0.9, 0.9, 0.9,
    SPECULAR,	0.0, 0.0, 0.0,
    LMNULL
};

static float lite[] = {
    LCOLOR,	0.8, 0.8, 0.8,
    POSITION,	0.0, 0.0, 1.0, 0.0,
    LMNULL
};

static float lm[] = {
    AMBIENT,	0.1, 0.1, 0.1,
    LMNULL
};

unsigned short pattern[16] = {
    0x2222, 0x8888, 0x2222, 0x8888,
    0x2222, 0x8888, 0x2222, 0x8888,
    0x2222, 0x8888, 0x2222, 0x8888,
    0x2222, 0x8888, 0x2222, 0x8888};

BOOLEAN	    tex_yes;
BOOLEAN	    win_redraw = FALSE;
BOOLEAN	    hier_redraw = FALSE;
BOOLEAN	    left_but_down = FALSE;
BOOLEAN	    cur_mode = FALSE;	    /* TRUE == on utilise le mode courant */
BOOLEAN	    sel_mode = FALSE;	    /* TRUE == on selectionne */
BOOLEAN	    d_bbox = FALSE, d_norm = FALSE;
BOOLEAN	    delta_transform = FALSE;
BOOLEAN	    scene_modified = FALSE;
char	    *scene_path, *off_path, *tex_path, cur_write_name[100];
int	    ok = TRUE;
int	    linecount = 0;
int	    tree_width, tree_height = 0;
int	    tab_i, nb_dad_pol = 0, nb_sel_pol = 0;
long	    visu, hierarchy;
long	    menu;
long	    visu_w, visu_h;
float	    center_x, center_y;
Matrix	    deltat_m;
BOX_OBJ	    tab_obj[1000];
OBJECT_OP   tab_dad_pol[1000];
OBJECT_OP   cur_sel_obj = NULL;
TRIANGLEP   cur_sel_pol = NULL;
SCENE_O	    the_scene;

void resize_hierarchy(SCENE_OP scene);			/* Forward */
void unselect_all(SCENE_OP scene, BOOLEAN polys);	/* Forward */
void remove_from_dad(OBJECT_OP object);			/* Forward */
void ctrl_pan_cb(FL_OBJECT *fl_obj);			/* Forward */
void event_cb(short dev, short val);			/* Forward */

void yyerror(char s)
/**************************************************************************
  But	: Affiche une erreur de syntaxe detectee par yacc
  Entree: s	: le texte de l'erreur
  Sortie: neant
**************************************************************************/
{
  fprintf(stderr, "Error while parsing: %s (%d)\n", s, linecount);
  exit(1);
}

void popup_create()
/**************************************************************************
  But	: Interface popup pour la creation d'un objet
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP new;
  MAT	    mat;

  if (nb_sel_pol < 1)
    return;

  new = create_object(&the_scene,
		      nb_sel_pol,
		      tab_dad_pol, nb_dad_pol);
  if (new == NULL)
    {
      fl_show_message("Can't create a new object !", "No memory.", "");
      fl_qreset();
      return;
    }

  mat.object		= new;
  mat.ro		= 0.6;
  mat.E			= 0.0;
  mat.name[0]		= '\0';
  mat.shadows		= TRUE;
  mat.tex.map.index	= -1;
  mat.tex.texture.image = NULL;
  V_init(mat.color, 1.0, 1.0, 1.0);

  if (popup_caract(&mat, &the_scene))
    {
      apply_modif(&the_scene);
      Mark(mat.object);
      unselect_all(&the_scene, TRUE);
      resize_hierarchy(&the_scene);
      scene_modified = TRUE;
      hier_redraw = TRUE;
      win_redraw = TRUE;
    }
}

void popup_modif()
/**************************************************************************
  But	: Interface popup pour la modification d'un objet
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  int	    i, nb = 0;
  OBJECT_OP obj;
  MAT	    mat;

  if (tab_i < 1) 
    return;

  for(i = 0; i < tab_i; i++)
    if (Marked(tab_obj[i].obj))
      {
	obj = tab_obj[i].obj;
	nb++;
      }

  if (nb < 1)
    return;

  if (nb > 1)
    {
      fl_show_message("", "More than one object selected !", "");
      fl_qreset();
      return;
    }

  if (obj->type != pure_type)
    return;

  mat.object		= obj;
  mat.ro		= obj->object.pure.ro;
  mat.E			= obj->object.pure.E;
  mat.shadows		= obj->object.pure.shadows;
  mat.tex.map		= obj->object.pure.texmap;
  strcpy(mat.name, obj->name);
  V_copy(mat.color, obj->object.pure.color);

  if (mat.tex.map.index > 0)
    mat.tex.texture = the_scene.textures[obj->object.pure.texmap.index];
  else
    mat.tex.texture.image = NULL;

  if (popup_caract(&mat, &the_scene))
    {
      apply_modif(&the_scene);
      scene_modified = TRUE;
      hier_redraw = TRUE;
      win_redraw = TRUE;
    }
}

void get_tree_w_h(OBJECT_OP object, int *width, int *height)
/**************************************************************************
  But	: Calcule la largeur et la profondeur de l'arbre de hierarchie
  Entree: object    : l'objet pere
	  width	    : la largeur retournee
	  height    : la hauteur retournee
  Sortie: neant
**************************************************************************/
{
  static int depth = 0;

  OBJECT_OP obj;
  int w;

  if (object == NULL)
    {
      *width  = 10;
      *height = 10;
      return;
    }

  depth++;

  if (depth > *height)
    *height = depth;

  (*width) = 0;

  switch(object->type) {
    case assembly_type :
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
	{
	  get_tree_w_h(obj, &w, height);
	  (*width) += w;
	}
      break;
    case composite_type :
      for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
	{
	  get_tree_w_h(obj, &w, height);
	  (*width) += w;
	}
      break;
    case pure_type :
      (*width)++;
      break;
    default:
      break;
  }

  depth--;
}

void resize_hierarchy(SCENE_OP scene)
/**************************************************************************
  But	: Met a jour la taille de la fenetre de hierarchie
  Entree: scene	    : la scene
  Sortie: neant
**************************************************************************/
{
  long hier_x, hier_y;
  long hier_w, hier_h;

  tree_height = 0;

  get_tree_w_h(scene->scene, &tree_width, &tree_height);
  hier_w = (long)(DBL_BOX*(tree_width+2));
  hier_h = (long)(DBL_BOX*(tree_height+1));
  winset(hierarchy);
  getorigin(&hier_x, &hier_y);
  winposition(hier_x, hier_x + hier_w, hier_y, hier_y + hier_h);
  prefsize(hier_w, hier_h);
  winconstraints();
  reshapeviewport();
  ortho2(-0.5, hier_w + 0.5, -0.5, hier_h + 0.5);
}

draw_hier_obj(OBJECT_OP object,
	      BOX_OBJP tab, int *index,
	      int width, float pos, int depth,
	      float at_pt_x, float at_pt_y)
/**************************************************************************
  But	: Affiche un objet et ses fils eventuels dans la fenetre de
	  hierarchie
  Entree: object    : l'objet
	  tab	    : le tableau des objets (rendu)
	  int	    : le nombre d'objet (rendu)
	  width	    : la largeur de l'arbre
	  pos	    : la position courante
	  depth	    : la profondeur courante
	  at_pt_x   : la position du pere (x) pour le trait de liaison
	  at_pt_y   : la position du pere (y) pour le trait de liaison
  Sortie: neant
**************************************************************************/
{
  int	p = 0;
  float x = (pos + (float)width/2.0)*DBL_BOX,
        y = depth*DBL_BOX;
  float ap[2];
  OBJECT_OP obj;

  if (object == NULL)
    return;

  if ((at_pt_x != 0.0) && (at_pt_y != 0.0))
    {
      cpack(0x77ffff);
      bgnline();
        ap[0] = at_pt_x;
        ap[1] = at_pt_y;
        v2f(ap);
        ap[0] = x;
        ap[1] = y-HALF_BOX;
        v2f(ap);
      endline();
    }

  if (!Hidden(object))
    if (Marked(object))
      cpack(0x0000ff);
    else
      cpack(0xff0000);
  else
    if (Marked(object))
      cpack(0x003080);
    else
      cpack(0x803000);

  sboxf(x-HALF_BOX, y-HALF_BOX, x+HALF_BOX, y+HALF_BOX);

  tab[*index].obj = object;
  tab[*index].x   = x;
  tab[*index].y   = y;
  (*index)++;

  switch(object->type) {
    case assembly_type :
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
	{
	  int	w, h;

	  get_tree_w_h(obj, &w, &h);
	  draw_hier_obj(obj, tab, index, w, pos+p, depth+1, x, y+HALF_BOX);
	  p += w;
	}
      break;
    case composite_type :
      for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
	{
	  int	w, h;

	  get_tree_w_h(obj, &w, &h);
	  draw_hier_obj(obj, tab, index, w, pos+p, depth+1, x, y+HALF_BOX);
	  p += w;
	}
      break;
    case pure_type :
      break;
    default:
      break;
  }
}

void redraw_hierarchy(SCENE_OP scene)
/**************************************************************************
  But	: Affiche la hierarchie
  Entree: scene	    : la scene
  Sortie: neant
**************************************************************************/
{
  winset(hierarchy);
  cpack(0x000000);
  clear();

  tab_i = 0;
  draw_hier_obj(scene->scene, tab_obj, &tab_i, tree_width, 1.0, 1, 0.0, 0.0);
  swapbuffers();
}

void compute_visu(SCENE_OP	scene,
		  Matrix	deltat)
/**************************************************************************
  But	: Calcule les matrices de visualisation de la delta transformation
	  generee par la spaceball
  Entree: scene	    : la scene, le bloc de visu est mis a jour
	  deltat    : delta transformation venant de la spaceball
  Sortie: neant
**************************************************************************/
{
  float	    h_f, cot, fpn, fmn, ftn;
  Matrix    m;
  Matrix invdeltat;

  M4D_invert(invdeltat, deltat);
  M4D_mul(scene->state.visu.viewing, scene->state.visu.viewing, invdeltat);
  M4D_invert(scene->state.visu.iviewing, scene->state.visu.viewing);

  h_f = DEG2RAD(scene->state.visu.fovy/20.0);
  cot = cos(h_f)/sin(h_f);
  fpn = scene->state.visu.far + scene->state.visu.near;
  fmn = scene->state.visu.far - scene->state.visu.near;
  ftn = scene->state.visu.far * scene->state.visu.near;

  M4D_init(scene->state.visu.persp,
	     cot, 0.0,	          0.0,  0.0,
	     0.0, cot,	          0.0,  0.0,
	     0.0, 0.0,	   -(fpn/fmn), -1.0,
	     0.0, 0.0, -2.0*(ftn/fmn),  0.0);

  M4D_invert(scene->state.visu.ipersp, scene->state.visu.persp);

  M4D_mul(scene->state.visu.unif2world,
	  scene->state.visu.ipersp,
	  scene->state.visu.iviewing);
  M4D_mul(scene->state.visu.world2unif,
	  scene->state.visu.viewing,
	  scene->state.visu.persp);

  M4D_identity(deltat);
}

void redraw_scene(SCENE_OP scene)
/**************************************************************************
  But	: Redessine la scene dans la fenetre de visualisation
  Entree: scene	: la scene
  Sortie: neant
**************************************************************************/
{
  Matrix	    viewing;
  static char	    string[30];
  static Matrix	    identity = { 1.0, 0.0, 0.0, 0.0,
				 0.0, 1.0, 0.0, 0.0,
				 0.0, 0.0, 1.0, 0.0,
				 0.0, 0.0, 0.0, 1.0 };

  winset(visu);
  reshapeviewport();
  czclear(0, getgdesc(GD_ZMAX));

  if (scene->scene != NULL)
    {
      getsize(&visu_w, &visu_h);
      center_x = visu_w*0.5;
      center_y = visu_h*0.5;
      compute_visu(scene, deltat_m);
      mmode(MPROJECTION);
      loadmatrix(scene->state.visu.persp);
      mmode(MVIEWING);
      loadmatrix(scene->state.visu.viewing);
      pushmatrix();
      loadmatrix(identity);
      lmbind(LIGHT0, 1);
      popmatrix();

      nb_tri_disp = 0;
      disp_obj(scene->scene, FALSE, FALSE, d_bbox, d_norm, tex_yes, scene);
      mmode(MPROJECTION);
      loadmatrix(identity);
      mmode(MVIEWING);
      loadmatrix(identity);
      cpack(0x00ffffff);
      cmov2(-1.0, -1.0);
      sprintf(string, "%d polygons", nb_tri_disp);
      charstr(string);
    };

  swapbuffers();
}

void init_draw_win()
/**************************************************************************
  But	: Initialise la fenetre de visualization
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  Matrix viewing;
  char	 str[255];

  prefposition(0, WIN3D_W, getgdesc(GD_YPMAX)-WIN3D_H, getgdesc(GD_YPMAX));
  visu = winopen(VISU_WIN_LABEL);
  keepaspect(1, 1);
  winconstraints();

  strcpy(str, "Control %t | Ctrl-Middle to hide an object| |");
  strcat(str, " Unselect All Polygons| Unselect All Objects|");
  strcat(str, " Flip Normals| Split Polygons");
  menu = defpup(str);

  zbuffer(TRUE);
  doublebuffer();
  backface(FALSE);
  subpixel(TRUE);
  RGBmode();
  lsetdepth(getgdesc(GD_ZMIN), getgdesc(GD_ZMAX));
  gconfig();
  mmode(MVIEWING);

  lmdef(DEFMATERIAL, 1, 0, mat);
  lmdef(DEFLIGHT, 1, 0, lite);
  lmdef(DEFLMODEL, 1, 0, lm);
  lmbind(MATERIAL, 1);
  lmbind(LMODEL, 1);
}

void init_hier_win()
/**************************************************************************
  But	: Initialise la fenetre de hierarchie
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  prefposition(Control_Panel->x, Control_Panel->x + 200,
	       Control_Panel->y - 250, Control_Panel->y - 50);
  hierarchy = winopen(HIER_WIN_LABEL);
  doublebuffer();
  RGBmode();
  overlay(2);
  drawmode(OVERDRAW);
  mapcolor(OVYELLOW, 255, 255, 0);
  drawmode(NORMALDRAW);
  gconfig();
  hier_redraw = TRUE;
}

void init_ctrl_pan()
/**************************************************************************
  But 	: Initialise la fenetre de controle
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  fl_set_slider_bounds(Aperture_Slider, 5.0, 90.0);
  fl_set_slider_value(Aperture_Slider, 50.0);
  fl_set_slider_return(Aperture_Slider, FALSE);
}

void init_tex_pan()
/**************************************************************************
  But 	: Initialise la fenetre d'edition de texture
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  if (tex_path != NULL)
    strcpy(tex_brow_path, tex_path);
  else
    strcpy(tex_brow_path, ".");

/* ****** Is to be added in the panels.c file in the definition of
   ****** the Texture_Panel (it can't be generated automatically).
   ****** Copied here for safety.

  Texture_Pict =
    fl_add_free(FL_SLEEPING_FREE,
		(float)Texture_Box->x, (float)Texture_Box->y,
		(float)Texture_Box->w, (float)Texture_Box->h,
		"",
		picture_handle);
*/

  set_picture(NULL, 0, 0);
}

void init_edit_pan()
/**************************************************************************
  But 	: Initialise la fenetre d'edition
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  fl_set_slider_bounds(Red_Slider, 0.0, 1.0);
  fl_set_slider_bounds(Green_Slider, 0.0, 1.0);
  fl_set_slider_bounds(Blue_Slider, 0.0, 1.0);
}

void init_the_forms()
/**************************************************************************
  But 	: Initialize les paneaux
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  init_ctrl_pan();
  init_edit_pan();
  init_tex_pan();

  fl_qdevice(INPUTCHANGE);
  fl_qdevice(LEFTMOUSE);
  fl_qdevice(MIDDLEMOUSE);
  fl_qdevice(RIGHTMOUSE);
  fl_qdevice(MOUSEX);
  fl_qdevice(MOUSEY);

  if (!sbInit())
    printf("No Spaceball detected !!\n");

  fl_set_form_call_back(Control_Panel, ctrl_pan_cb);
  fl_set_event_call_back(event_cb);
}

void init_display(SCENE_OP scene)
/**************************************************************************
  But 	: Initialise l'interface
  Entree: scene	    : la scene
  Sortie: neant
**************************************************************************/
{
  create_the_forms();

  init_the_forms();

  fl_set_form_position(Control_Panel,
		       getgdesc(GD_XPMAX) - Control_Panel->w,
		       getgdesc(GD_YPMAX) - Control_Panel->h);
  fl_show_form(Control_Panel, FL_PLACE_POSITION, TRUE, CTRL_PAN_LABEL);
  fl_set_form_position(Edit_Panel, 0, 0);
  fl_set_form_position(Texture_Panel, 0, 0);

  defpattern(PATTERN, 16, pattern);

  M4D_identity(scene->state.visu.mod);
  M4D_identity(scene->state.visu.viewing);

  init_draw_win(scene);
  init_hier_win(scene);

  win_redraw = TRUE;
}

void unselect(OBJECT_OP object, BOOLEAN polys)
/**************************************************************************
  But 	: Deselectionne un objet ou les polygons de cet objet et applique
	  la meme chose a ses fils
  Entree: object    : l'objet
	  polys	    : TRUE si les polygones doivent etre deselectionnes,
		      FALSE si l'objet doit etre deselectionne
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP obj;
  int	    i;

  if (!polys)
    unMark(object);

  switch(object->type) {
    case assembly_type:
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
	unselect(obj, polys);
      break;
    case composite_type:
      for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
	unselect(obj, polys);
      break;
    case pure_type:
      if (polys)
        for(i = 0; i < object->object.pure.nb_polys; i++)
	  object->object.pure.polys[i].tri.poly.samples = (SAMPLE_ARR *)FALSE;
      break;
    default:
      break;
  }
}

void unselect_all(SCENE_OP scene, BOOLEAN polys)
/**************************************************************************
  But 	: Deselectionne tous les objets ou tous les polygones de la scene
  Entree: scene	    : la scene
	  polys	    : TRUE si les polygones doivent etre deselectionnes,
		      FALSE si les objets doivent etre deselectionnes
  Sortie: neant
**************************************************************************/
{
  if (scene->scene != NULL)
    unselect(scene->scene, polys);

  if (polys)
    {
      nb_sel_pol = 0;
      nb_dad_pol = 0;
    }

  hier_redraw = TRUE;
}

OBJECT_OP find_object(SCENE_OP scene, long xpos, long ypos)
/**************************************************************************
  But 	: Selectionne un objet dans la fenetre de Visualisation
  Entree: scene	    : la scene
	  xpos	    : la position de la souris (x)
	  ypos	    : la position de la souris (y)
  Sortie: L'objet selectionne
**************************************************************************/
{
  VECTOR    p;
  SELECTION select;

  p[0] = (xpos - center_x)/(visu_w*0.5);
  p[1] = (ypos - center_y)/(visu_h*0.5);
  
  select_obj(scene, &select, p, FALSE);

  return select.pure;
}

void hide_object(SCENE_OP scene, long xpos, long ypos)
/**************************************************************************
  But 	: Change l'etat cache/pas cache d'un objet selectionne dans la
	  fenetre de Visualisation
  Entree: scene	    : la scene
	  xpos	    : la position de la souris (x)
	  ypos	    : la position de la souris (y)
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP selected;

  if ((selected = find_object(scene, xpos, ypos)) != NULL)
    Hideswitch(selected);

  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void select_object(SCENE_OP scene, long xpos, long ypos)
/**************************************************************************
  But 	: Change l'etat selectionne/pas selectionne d'un objet selectionne
	  dans la fenetre de Visualisation
  Entree: scene	    : la scene
	  xpos	    : la position de la souris (x)
	  ypos	    : la position de la souris (y)
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP selected;

  if ((selected = find_object(scene, xpos, ypos)) != NULL)
    Markswitch(selected);

  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void select_poly(SCENE_OP scene,
		 long xpos,
		 long ypos,
		 TRIANGLEP *last,
		 BOOLEAN cur_mode)
/**************************************************************************
  But	: Selectionne un polygone dans la fenetre de Visualisation
  Entree: scene	    : la scene
	  xpos	    : position de la souris en x
	  ypos	    : position de la souris en y
	  last	    : handle sur le dernier polygone selectionne
	  cur_mode  : TRUE s'il faut utiliser le mode de sel. courant
  Sortie: neant
**************************************************************************/
{
  static    BOOLEAN sel_mode = FALSE;

  VECTOR    p;
  SELECTION select;

  p[0] = (xpos - center_x)/(visu_w*0.5);
  p[1] = (ypos - center_y)/(visu_h*0.5);
  
  select_obj(scene, &select, p, TRUE);

  if ((select.tri != NULL) && (*last != select.tri))
    {
      int i;
      BOOLEAN	oldstate = (BOOLEAN)select.tri->tri.poly.samples;

      *last = select.tri;

      if (!cur_mode)
	sel_mode = !oldstate;
	
      select.tri->tri.poly.samples = (SAMPLE_ARR *)sel_mode;

      if (sel_mode)
	{
	  if (sel_mode != oldstate)
            nb_sel_pol++;
	}
      else
	{
	  if (sel_mode != oldstate)
            nb_sel_pol--;
	}

      for(i = 0; i < nb_dad_pol; i++)
	if (tab_dad_pol[i] == select.pure)
	  break;
      if (i >= nb_dad_pol)
	tab_dad_pol[nb_dad_pol++] = select.pure;

      win_redraw = TRUE;
    }
  else
    *last = select.tri;
}

void destroy_object()
/**************************************************************************
  But	: Detruit l'objet selectionne courant
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  int	    i, nb = 0;
  OBJECT_OP obj;

  if (tab_i < 1) 
    return;

  for(i = 0; i < tab_i; i++)
    if (Marked(tab_obj[i].obj))
      {
	obj = tab_obj[i].obj;
	nb++;
      }

  if (nb < 1)
    return;

  if (nb > 1)
    {
      fl_show_message("", "More than one object selected !", "");
      fl_qreset();
      return;
    }

  if (obj == the_scene.scene)
    return;

  if (fl_show_question("Are you sure you want to destroy",
		       obj->name,
		       "THERE IS NO WAY TO UNDO THIS !!!!"))
    {
      remove_from_dad(obj);
      unselect(obj, TRUE);
      free_obj(obj);
      fl_qreset();
    }

  resize_hierarchy(&the_scene);
  scene_modified = TRUE;
  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void join_objects()
/**************************************************************************
  But	: Groupe les objets selectionnes courants
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  int	    i, j;
  VECTOR    bboxi, bboxa;
  OBJECT_OP selected[1000];
  OBJECT_OP dad, new, *firstp;

  if (tab_i == 0)
    return;

  for(i = 0, j = 0; i < tab_i; i++)
    if (Marked(tab_obj[i].obj))
      selected[j++] = tab_obj[i].obj;

  if (j == 0)
    return;

  dad = selected[0]->dad;
  for(i = 1; i < j; i++)
    if (selected[i]->dad != dad)
      {
	ringbell();
	fprintf(stderr,	
	        "ERROR: the selected objects do not have the same father !\n");
	return;
      }

  new = (OBJECT_OP)malloc(sizeof(OBJECT_O));

  new->type = composite_type;
  new->object.composite.grid = NULL;
  new->object.composite.sons = NULL;
  if (selected[0]->type == pure_type)
    for(i = 1; i < j; i++)
      {
        if (selected[i]->type != pure_type)
	  {
	    new->type = assembly_type;
	    new->object.assembly.sons = NULL;
	    break;
	  }
      }
  else
    {
      new->type = assembly_type;
      new->object.assembly.sons = NULL;
    }

  new->dad = dad;
  if (new->dad->type == composite_type)
    {
      new->dad->type = assembly_type;
      new->dad->object.assembly.sons =
	new->dad->object.composite.sons;
    }

  if (dad->type == assembly_type)
    firstp = &(dad->object.assembly.sons);
  else
    firstp = &(dad->object.composite.sons);

  for(i = 0; i < j; i++)
    {
      OBJECT_OP *objp;

      for(objp = firstp;
	  (*objp != NULL) && (*objp != selected[i]);
	  objp = &((*objp)->next));

      *objp = ((*objp)->next);

      if (new->type == assembly_type)
	{
	  selected[i]->next = new->object.assembly.sons;
          new->object.assembly.sons = selected[i];	    
	}
      else
	{
	  selected[i]->next = new->object.composite.sons;
	  new->object.composite.sons = selected[i];
	}

      selected[i]->dad = new;
    }

  new->next = *firstp;
  *firstp = new;

  strcpy(new->name, "New Object");
  new->name[strlen(new->name)+1] = '\0';

  V_init(bboxi,  HUGE,  HUGE,  HUGE);
  V_init(bboxa, -HUGE, -HUGE, -HUGE);

  for(i = 0; i < j; i++)
    {
      V_min(bboxi, bboxi, selected[i]->bbox[0]);
      V_max(bboxa, bboxa, selected[i]->bbox[1]);
    }

  V_copy(new->bbox[0], bboxi);
  V_copy(new->bbox[1], bboxa);

  unselect(new, FALSE);
  Mark(new);

  resize_hierarchy(&the_scene);
  scene_modified = TRUE;
  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void split_object()
/**************************************************************************
  But	: Separe l'objet selectionne courant
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  int	    i, nb = 0;
  OBJECT_OP object, obj, objs, *dadsp;

  if (tab_i < 1)
    return;

  for(i = 0; i < tab_i; i++)
    if (Marked(tab_obj[i].obj))
      {
	object = tab_obj[i].obj;
	nb++;
      }

  if (nb < 1)
    return;

  if (nb > 1)
    {
      ringbell();
      fprintf(stderr, "ERROR: More than one object selected !\n");
      return;
    }

  if (object == the_scene.scene)
    return;

  if (object->dad->type == assembly_type)
    dadsp = &(object->dad->object.assembly.sons);
  else
    dadsp = &(object->dad->object.composite.sons);

  if (object->type == assembly_type)
    objs = object->object.assembly.sons;
  else
    objs = object->object.composite.sons;

  if (objs == NULL)
    return;

  for(obj = objs; obj->next != NULL; obj = obj->next)
    obj->dad = object->dad;
  obj->dad  = object->dad;

  obj->next = *dadsp;

  *dadsp = objs;

  for(obj = objs; obj->next != object; obj = obj->next);

  obj->next = obj->next->next;

  free(object);

  resize_hierarchy(&the_scene);
  scene_modified = TRUE;
  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void update_bbox(OBJECT_OP object)
/**************************************************************************
  But	: Met a jour la bounding boxe d'un objet et de ses fils
  Entree: object    : l'objet
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP obj;

  switch(object->type) {
    case assembly_type:
      V_init(object->bbox[0],  HUGE,  HUGE,  HUGE);
      V_init(object->bbox[1], -HUGE, -HUGE, -HUGE);
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
	{
	  update_bbox(obj);
	  V_min(object->bbox[0], object->bbox[0], obj->bbox[0]);
	  V_max(object->bbox[1], object->bbox[1], obj->bbox[1]);
	}
      break;
    case composite_type:
      V_init(object->bbox[0],  HUGE,  HUGE,  HUGE);
      V_init(object->bbox[1], -HUGE, -HUGE, -HUGE);
      for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
	{
	  V_min(object->bbox[0], object->bbox[0], obj->bbox[0]);
	  V_max(object->bbox[1], object->bbox[1], obj->bbox[1]);
	}
      break;
    case pure_type:
      break;
    default:
      break;
  }
}

void remove_from_dad(OBJECT_OP object)
/**************************************************************************
  But	: Enleve un objet de la liste des fils de son pere
  Entree: object    : l'objet
  Sortie: neant
**************************************************************************/
{
  OBJECT_OP *objp, *dadsp;

  if (object->dad->type == assembly_type)
    dadsp = &(object->dad->object.assembly.sons);
  else
    dadsp = &(object->dad->object.composite.sons);

  for(objp = dadsp;
      ((*objp) != object) && ((*objp) != NULL);
      objp = &((*objp)->next));

  (*objp) = object->next;
}

void attach_obj(OBJECT_OP object, OBJECT_OP newdad)
/**************************************************************************
  But	: Ajoute un objet a la liste des fils d'un autre objet
  Entree: object    : l'objet
	  newdad    : le nouveau pere
  Sortie: neant
**************************************************************************/
{
  int	    n;
  OBJECT_OP obj, olddad, first;

  if ((object == newdad) || (object->dad == newdad))
    return;

  remove_from_dad(object);

  olddad = object->dad;
  object->dad = newdad;
  if (newdad->type == assembly_type)
    {
      object->next = newdad->object.assembly.sons;
      newdad->object.assembly.sons = object;
    }
  else if (object->type != pure_type)
    {
      newdad->type = assembly_type;
      object->next = newdad->object.composite.sons;
      newdad->object.assembly.sons = object;
    }
  else
    {
      object->next = newdad->object.composite.sons;
      newdad->object.composite.sons = object;
    }

  update_bbox(newdad);

  first = (olddad->type == assembly_type) ?
	  olddad->object.assembly.sons :
	  olddad->object.composite.sons;

  for(n = 0, obj = first;
      obj != NULL;
      obj = obj->next, n++);

  if (n == 0)
    {
      remove_from_dad(olddad);
      free(olddad);
    }
  else if (n == 1)
    attach_obj(first, newdad);
  else
    update_bbox(olddad);
}

void merge_obj(OBJECT_OP dest, OBJECT_OP source)
/**************************************************************************
  But	: Fond un objet dans un autre
  Entree: dest	    : l'objet destination
	  source    : l'objet source
  Sortie: neant
**************************************************************************/
{
  STR	    str;
  int	    i, j, k;
  int	    old_nb_pol = dest->object.pure.nb_polys;
  TRIANGLEP old_polys = dest->object.pure.polys;
  MAT	    mat;

  if (dest == source)
    return;

  sprintf(str, "%s INTO", source->name);
  if (!fl_show_question("Are you sure you want to merge",
			str, dest->name))
    {
      fl_qreset();
      return;
    }

  fl_qreset();

  mat.object		= dest;
  mat.ro		= dest->object.pure.ro;
  mat.E			= dest->object.pure.E;
  mat.shadows		= dest->object.pure.shadows;
  mat.tex.map		= dest->object.pure.texmap;
  strcpy(mat.name, dest->name);
  V_copy(mat.color, dest->object.pure.color);

  if (mat.tex.map.index > 0)
    mat.tex.texture = the_scene.textures[dest->object.pure.texmap.index];
  else
    mat.tex.texture.image = NULL;

  if (popup_caract(&mat, &the_scene))
    apply_modif(&the_scene);
  else
    return;

  dest->object.pure.nb_polys += source->object.pure.nb_polys;
  dest->object.pure.polys =
    (TRIANGLEP)realloc(dest->object.pure.polys,
		       dest->object.pure.nb_polys*sizeof(TRIANGLE));
  if (dest->object.pure.polys == NULL)
    {
      fl_show_message("Internal Error !!",
		      "Can't merge the two objects !",
		      "No merging done.");
      dest->object.pure.nb_polys = old_nb_pol;
      dest->object.pure.polys  = old_polys;
      fl_qreset();
      return;
    }

  for(i = 0, j = old_nb_pol; i < source->object.pure.nb_polys; i++, j++)
    {
      dest->object.pure.polys[j] = source->object.pure.polys[i];
      for(k = 0; k < 3; k++)
	{
          V_min(dest->bbox[0],
		dest->bbox[0],
		source->object.pure.polys[i].tri.poly.vertex[k].point);
          V_max(dest->bbox[1],
		dest->bbox[1],
		source->object.pure.polys[i].tri.poly.vertex[k].point);
	}
    }

  free(source->object.pure.polys);
  remove_from_dad(source);
  unselect(source, TRUE);
  free_obj(source);

  resize_hierarchy(&the_scene);
  scene_modified = TRUE;
  hier_redraw = TRUE;
  win_redraw = TRUE;
}

void apply_to_sel_pol(OBJECT_OP object, Proc proc)
/**************************************************************************
  But	: Applique une fonction a tous les polygones selectionnes d'un
	  objet
  Entree: object    : l'objet
	  proc	    : la fonction
  Sortie: neant
**************************************************************************/
{
  int	    i, nb_polys;
  OBJECT_OP obj;

  switch(object->type) {
    case assembly_type:
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
	apply_to_sel_pol(obj, proc);
      break;
    case composite_type:
      for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
	apply_to_sel_pol(obj, proc);
      break;
    case pure_type:
      nb_polys = object->object.pure.nb_polys;
      for(i = 0; i < nb_polys; i++)
	if (object->object.pure.polys[i].tri.poly.samples)
	  proc(&(object->object.pure.polys[i]));
      break;
    default:
      break;
  }
}

void flip_poly_norm(TRIANGLEP tri)
/**************************************************************************
  But	: Inverse la normale d'un polygone
  Entree: tri	    : le polygone
  Sortie: neant
**************************************************************************/
{ 
  float  naxb, dum;
  VECTOR vdum, ea, eb, ab, axb, bxa;

  V_copy(vdum, tri->tri.poly.vertex[1].point);
  V_copy(tri->tri.poly.vertex[1].point,
	 tri->tri.poly.vertex[2].point);
  V_copy(tri->tri.poly.vertex[2].point,
	 vdum);

  V_sub(ea, tri->tri.poly.vertex[1].point, tri->tri.poly.vertex[0].point);
  V_sub(eb, tri->tri.poly.vertex[2].point, tri->tri.poly.vertex[0].point);
  V_sub(ab, tri->tri.poly.vertex[1].point, tri->tri.poly.vertex[2].point);
  
  V_cross(axb, ea, eb);
  naxb = V_norm(axb);
  dum = 1.0/(naxb * naxb);
  V_scale(tri->tri.poly.normal, (1.0/naxb), axb);
  tri->tri.poly.d = -V_dot(tri->tri.poly.normal, tri->tri.poly.vertex[0].point);

  V_cross(tri->tri.poly.sa, eb, axb);
  V_scale(tri->tri.poly.sa, dum, tri->tri.poly.sa);

  V_cross(bxa, eb, ea);
  V_cross(tri->tri.poly.sb, ea, bxa);
  V_scale(tri->tri.poly.sb, dum, tri->tri.poly.sb);
}

void flip_normals(SCENE_OP scene)
/**************************************************************************
  But	: Inverse la normale de tous les polygones selectionnes
  Entree: scene	    : la scene
  Sortie: neant
**************************************************************************/
{
  apply_to_sel_pol(scene->scene, flip_poly_norm);
}

void split_pol(TRIANGLEP tri)
/**************************************************************************
  But	: Subdivise un polygone
  Entree: tri	    : le polygone
  Sortie: neant
**************************************************************************/
{
  int	    i;
  VECTOR    a, b, c, ab, ac, bc;
  VERTEXP   old_array = tri->tri.poly.vertex;
  TRIANGLE  t[4];
  OBJECT_OP dad = tri->dad;

  for(i = 0; i < 4; i++)
    {
      t[i].dad = dad;
      t[i].tri.poly.samples = (SAMPLE_ARR *)TRUE;
      t[i].tri.poly.sons    = NULL;
      t[i].tri.poly.vertex  = NULL;
    }

  V_copy(a, tri->tri.poly.vertex[0].point);
  V_copy(b, tri->tri.poly.vertex[1].point);
  V_copy(c, tri->tri.poly.vertex[2].point);

  V_add(ab, a, b);
  V_scale(ab, 0.5, ab);
  V_add(ac, a, c);
  V_scale(ac, 0.5, ac);
  V_add(bc, b, c);
  V_scale(bc, 0.5, bc);

  init_poly(&(t[0]), a, ab, ac);
  init_poly(&(t[1]), ab, b, bc);
  init_poly(&(t[2]), bc, c, ac);
  init_poly(&(t[3]), ac, ab, bc);

  *tri = t[0];

  dad->object.pure.nb_polys += 3;
  dad->object.pure.polys =
    (TRIANGLEP)realloc(dad->object.pure.polys,
		       sizeof(TRIANGLE)*dad->object.pure.nb_polys);
  assert(dad->object.pure.polys);
  dad->object.pure.polys[dad->object.pure.nb_polys-3] = t[1];
  dad->object.pure.polys[dad->object.pure.nb_polys-2] = t[2];
  dad->object.pure.polys[dad->object.pure.nb_polys-1] = t[3];

  free(old_array);
}

void split_polygons(SCENE_OP scene)
/**************************************************************************
  But	: Subdivise tous les polygones selectionnes de la scene
  Entree: scene	    : la scene
  Sortie: neant
**************************************************************************/
{
  apply_to_sel_pol(scene->scene, split_pol);
}

void ctrl_pan_cb(FL_OBJECT *fl_obj)
/**************************************************************************
  But 	: Traite les evenements generes par le control panel
  Entree: fl_obj    : l'objet qui a emis l'evenement
  Sortie: neant
**************************************************************************/
{
  if (fl_obj == Load_Button)
    {
      static BOOLEAN	init = TRUE;
      static STR	dir, pat;

      char  *name;

      if (init)
	{
	  strcpy(dir, scene_path);
          strcpy(pat, "*.rd");
	  init = FALSE;
	}

      if (scene_modified)
	{
	  BOOLEAN   yes;

	  yes = fl_show_question("This scene hasn't been saved !",
				 "Are you sure you want to load a new scene ?",
				 "");
	  fl_qreset();

	  if (!yes)
	    return;
	}

      name = fl_show_file_selector("Select a file",
				   dir, pat, "");
      fl_qreset();

      if (name)
	{
	  FILE  *file;

          if ((file = fopen(name, "r")) == NULL)
	    {
	      fl_show_message("Can't open file", name, "");
	      fl_qreset();
	      return;
	    }

          unselect_all(&the_scene, TRUE);
          free_scene(&the_scene);

          yyin = file;
          if (yyparse())
	    {
	      fl_show_message("Can't read file", name, "");
	      the_scene.scene = NULL;
	      fl_qreset();
	      return;
	    }

          init_scene(&the_scene);
          unselect_all(&the_scene, TRUE);
          strcpy(cur_write_name, name);
          scene_modified = FALSE;
          hier_redraw = TRUE;
          win_redraw = TRUE;
	  M4D_identity(deltat_m);
          resize_hierarchy(&the_scene);
          fl_set_slider_value(Aperture_Slider,
			      (float)the_scene.state.visu.fovy/10.0);

          fclose(file);

	  strcpy(dir, fl_get_directory());
	}
    }
  else if (fl_obj == Write_Button)
    {
      char	*name;
      BOOLEAN	save_it;

      if (the_scene.scene == NULL)
	return;

      name = fl_show_input("Enter the file name", cur_write_name);
      fl_qreset();

      if (name)
	{
          FILE *file;

	  strcpy(cur_write_name, name);

          if ((file = fopen(cur_write_name, "r")) != NULL)
	    {
	      save_it = fl_show_question("This file already exists:",
				         cur_write_name,
				         "Do you want do replace it ?");
	      fclose(file);
	    }
	  else
	    save_it = TRUE;

	  if (save_it)
	    {
	      if (write_scene(&the_scene, cur_write_name))
	        scene_modified = FALSE;
	    }

	  fl_qreset();
	}
    }
  else if (fl_obj == Quit_Button)
    {
      BOOLEAN yes = TRUE;

      if (the_scene.scene == NULL)
	exit(0);

      if (scene_modified)
	{
          yes = fl_show_question("The current scene hasn't been saved !",
			         "Are you sure you want to quit ?",
			         "");
	  fl_qreset();
	}

      if (yes)
        exit(0);
    }
  else if (fl_obj == Aperture_Slider)
    {
      the_scene.state.visu.fovy =
	(Angle)(fl_get_slider_value(Aperture_Slider)*10.0);
      win_redraw     = TRUE;
      scene_modified = TRUE;
    }
  else if (fl_obj == Display_B_boxes_Button)
    {
      d_bbox = fl_get_button(Display_B_boxes_Button);
      win_redraw = TRUE;
    }
  else if (fl_obj == Display_Normals_Button)
    {
      d_norm = fl_get_button(Display_Normals_Button);
      win_redraw = TRUE;
    }
  else if (fl_obj == Create_Object_Button)
    popup_create();
  else if (fl_obj == Modify_Object_Button)
    popup_modif();
  else if (fl_obj == Destroy_Object_Button)
    destroy_object();
  else if (fl_obj == Join_Object_Button)
    join_objects();
  else if (fl_obj == Split_Object_Button)
    split_object();
}

void event_cb(short dev, short val)
/**************************************************************************
  But 	: Traite les evenements generes par la fenetre de visualisation
	  et les evenements n'appartenant pas au toolkit Forms en general
  Entree: dev	: la source de l'evenement
	  val	: la valeur de l'evenement
  Sortie: neant
**************************************************************************/
{
  static long	visuxpos, visuypos, hierxpos, hierypos, cur_win;

  short		xpos, ypos, dum;

  if (sbEvent(dev, val))
    return;

  switch(dev) {
    case INPUTCHANGE:
      if (val == cur_win)
	cur_win = NULL;
      else
	cur_win = val;
    case REDRAW:
      if (val == visu)
	{
          redraw_scene(&the_scene);
          getorigin(&visuxpos, &visuypos);
	}
      else if (val == hierarchy)
	{
	  redraw_hierarchy(&the_scene);
	  getorigin(&hierxpos, &hierypos);
	}
      break;
    case MOUSEX:
    case MOUSEY:
      if (cur_win == visu)
        {
          if (left_but_down)
	    select_poly(&the_scene,
			getvaluator(MOUSEX) - visuxpos,
			getvaluator(MOUSEY) - visuypos,
			&cur_sel_pol,
			TRUE);
        }
      else if (cur_win == hierarchy)
        if (left_but_down)
          {
	    float x = (float)(getvaluator(MOUSEX) - hierxpos),
		  y = (float)(getvaluator(MOUSEY) - hierypos);

	    winset(hierarchy);
	    drawmode(OVERDRAW);
	    color(BLACK);
	    clear();
	    color(OVYELLOW);
	    sbox(x-HALF_BOX, y-HALF_BOX, x+HALF_BOX, y+HALF_BOX);
	    drawmode(NORMALDRAW);
	}
      break;
    case LEFTMOUSE:
      if (val)
	{
          left_but_down = TRUE;
	  if (cur_win == visu)
	    select_poly(&the_scene,
			getvaluator(MOUSEX) - visuxpos,
			getvaluator(MOUSEY) - visuypos,
			&cur_sel_pol,
			FALSE);
	  else if (cur_win == hierarchy)
	    {
              OBJECT_OP	obj;

	      obj = select_box_obj(getvaluator(MOUSEX) - hierxpos,
				   getvaluator(MOUSEY) - hierypos,
				   tab_obj, tab_i);
	      if (obj != NULL)
		{
		  if (getbutton(CTRLKEY))
		    Hideswitch(obj);
		  else
		    Markswitch(obj);

	    	    cur_sel_obj = obj;

		    hier_redraw = TRUE;
		    win_redraw = TRUE;
		}
    	    }
	}
      else
	{
	  left_but_down = FALSE;
          cur_sel_pol = NULL;	    

	  if (cur_win == hierarchy)
	    {
	      OBJECT_OP   obj;

	      winset(hierarchy);
	      drawmode(OVERDRAW);
	      color(BLACK);
	      clear();
	      drawmode(NORMALDRAW);

	      obj = select_box_obj(getvaluator(MOUSEX) - hierxpos,
				   getvaluator(MOUSEY) - hierypos,
				   tab_obj, tab_i);
	      if (obj != NULL)
		{
		  if (obj->type != pure_type)
		    {
		      if ((cur_sel_obj != NULL) &&
			  (cur_sel_obj != the_scene.scene))
			attach_obj(cur_sel_obj, obj);
		    }
		  else if (cur_sel_obj != NULL)
		    if (cur_sel_obj->type == pure_type)
		      merge_obj(obj, cur_sel_obj);

		  scene_modified = TRUE;
		  hier_redraw = TRUE;
		  win_redraw = TRUE;
		}
	    }
	}
      break;
    case MIDDLEMOUSE:
      if (val)
	{
	  if (cur_win == visu)
	    if (getbutton(CTRLKEY))
	      hide_object(&the_scene,
			  getvaluator(MOUSEX) - visuxpos,
			  getvaluator(MOUSEY) - visuypos);
	    else
	      select_object(&the_scene,
			    getvaluator(MOUSEX) - visuxpos,
			    getvaluator(MOUSEY) - visuypos);
	}
      break;
    case RIGHTMOUSE:
      switch(dopup(menu)) {
	case 1:
        case 2:
          break;
	case 3:
	  unselect_all(&the_scene, TRUE);
	  hier_redraw = TRUE;
	  win_redraw = TRUE;
	  break;
	case 4:
	  unselect_all(&the_scene, FALSE);
	  hier_redraw = TRUE;
	  win_redraw = TRUE;
	  break;
	case 5:
	  flip_normals(&the_scene);
	  scene_modified = TRUE;
	  win_redraw = TRUE;
	  break;
	case 6:
	  split_polygons(&the_scene);
	  scene_modified = TRUE;
	  win_redraw = TRUE;
	  break;
	default:
	  break;
      }
      break;
    default:
      break;
  }
}

void init_glob()
/**************************************************************************
  But 	: Initialise certaines variables globales
  Entree: neant
  Sortie: neant
**************************************************************************/
{
  int i;

  scene_path =	getenv("RADSCENE");
  off_path   =	getenv("RADOFF");
  tex_path   =	getenv("RADTEX");

  tex_yes    = getgdesc(GD_TEXTURE);
#ifdef OLDSYS
  sbdataperiod(30, 1500);
#endif

  the_scene.scene = NULL;
  the_scene.textures = (TEXTUREP)malloc(sizeof(TEXTURE)*MAX_TEX);
  assert(the_scene.textures);
  for(i = 0; i < MAX_TEX; i++)
    the_scene.textures[i].image = NULL;
  the_scene.state.nb_tex = 1;

  M4D_identity(deltat_m);
}

main(int argc, char **argv)
/**************************************************************************
  But 	: Programme principal
  Entree: Parametres habituels
  Sortie: neant
**************************************************************************/
{
  fl_init();
  init_glob();
  init_display(&the_scene);

  while(TRUE)
    {
      if (fl_check_forms() == NULL)
	{
          if (win_redraw)
	    {
              redraw_scene(&the_scene);
	      win_redraw = FALSE;
	    }

	  if (hier_redraw)
	    {
	      redraw_hierarchy(&the_scene);
	      hier_redraw = FALSE;
	    }
	}
    }
}
