/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2001-2013)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2001-2013)

	E-mail address:
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at COPYING.
*/
#include "surfs.h"

#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "opengl.h"
#include <visu_extset.h>
#include <visu_configFile.h>


/**
 * SECTION:surfs
 * @short_description: Defines methods to draw surfaces.
 *
 * <para>#VisuSurface object can be drawn with this class. Simply
 * create a new #VisuGlExtSurfaces object and add surfaces with
 * visu_gl_ext_surfaces_add(). It is better to add several surfaces to
 * a single #VisuGlExtSurfaces object since all vertices are reordered
 * when necessary to ensure proper transparency.</para>
 */

#define DESC_RESOURCE_INTRA "Choose if the interior is drawn in color inverse ;"\
  " a boolean (0 or 1)"
#define FLAG_RESOURCE_INTRA "isosurfaces_drawIntra"
static gboolean INTRA_DEFAULT = FALSE;

typedef struct _SurfaceHandleStruct
{
  VisuGlExtSurfaces *ext;

  VisuSurface *surface;
  gulong masked_signal, resource_signal, box_signal;
  VisuSurfaceResource *res;
  gulong notify_signal;
} _SurfaceHandle;

typedef struct _VisuSurfaceOrder VisuSurfaceOrder;
/**
 * VisuSurfaceOrder:
 *
 * Short name to adress #_VisuSurfaceOrder objects.
 */
struct _VisuSurfaceOrder
{
  /* Any_pointer[i][0:1] gives the id for surfaces and id for poly i
     in the z sorted from back to front. */
  GArray *any_pointer;
  /* Give for all poly the id for surfaces object and the id for poly
     in this object. any_pointer elements point to that array. */
  GArray *polygon_number;
};
struct _orderInfo
{
  VisuSurfaceIterPoly iter;
  double z;
};

/* static gint _cmpZ(gconstpointer a, gconstpointer b) */
/* { */
/*   struct _orderInfo *alpha = (struct _orderInfo*)a; */
/*   struct _orderInfo *beta = (struct _orderInfo*)b; */

/*   if (alpha->z > beta->z) */
/*     return +1; */
/*   else if (alpha->z < beta->z) */
/*     return -1; */
/*   else */
/*     return 0; */
/* } */
static void sort_by_z(struct _orderInfo *pointer[], int begin, int end) {
   int i;
   int middle;
   struct _orderInfo *temp;

   if( begin >= end ) return;
   temp = pointer[begin];
   pointer[begin] = pointer[(end+begin)/2];
   pointer[(end+begin)/2] = temp;
   middle = begin;
   for(i = begin +1; i <= end; i++) {
      if ( pointer[i]->z < pointer[begin]->z ) {
         temp = pointer[i];
         pointer[i] = pointer[++middle];
         pointer[middle] = temp;
      }
   }
   temp = pointer[begin];
   pointer[begin] = pointer[middle];
   pointer[middle] = temp;
   sort_by_z(pointer, begin, middle-1);
   sort_by_z(pointer, middle+1, end);
}

/**
 * visu_surface_order_new:
 *
 * Create an object to hold the order in which the surfaces must be
 * drawn. See visu_surface_order_polygons() to set this object.
 *
 * Returns: a newly created #VisuSurfaceOrder object without any values.
 */
VisuSurfaceOrder* visu_surface_order_new(void)
{
  VisuSurfaceOrder *order;

  order = g_malloc(sizeof(VisuSurfaceOrder));
  order->any_pointer    = g_array_new(FALSE, FALSE, sizeof(struct _orderInfo*));
  order->polygon_number = g_array_new(FALSE, FALSE, sizeof(struct _orderInfo));

  return order;
}
/**
 * visu_surface_order_free:
 * @order: the object to be freed.
 *
 * Free memory used by a #VisuSurfaceOrder object.
 */
void visu_surface_order_free(VisuSurfaceOrder *order)
{
  g_return_if_fail(order);

  if (order->any_pointer)
    g_array_unref(order->any_pointer);
  if (order->polygon_number)
    g_array_unref(order->polygon_number);

  g_free(order);
}
/**
 * visu_surface_order_polygons:
 * @order: the description of the polygons order ;
 * @surfs: an array of #VisuSurface object, must be NULL terminated.
 *
 * Re-orders the polygons in back to front order.
 * This function should be called everytime a redraw is needed.
 */
void visu_surface_order_polygons(VisuSurfaceOrder *order, GList *surfs)
{
  float mat[16];
  double z;
  VisuSurfaceIterPoly iter;
  struct _orderInfo data, *pdata;
  GList *surf;
  guint i;

  g_return_if_fail(order);

  DBG_fprintf(stderr, "Extension Surfaces: re-ordering polygons in back to front order.\n");
  
  glGetFloatv(GL_MODELVIEW_MATRIX, mat);
  
  /* For all polygons, we compute the z position of the isobarycentre. */
  g_array_set_size(order->polygon_number, 0);
  for (surf = surfs; surf; surf = g_list_next(surf))
    for (visu_surface_iter_poly_new(((_SurfaceHandle*)surf->data)->surface, &iter);
         iter.valid; visu_surface_iter_poly_next(&iter))
      if (visu_surface_iter_poly_getZ(&iter, &z, mat))
        {
          data.iter   = iter;
          data.z      = z;
          g_array_append_val(order->polygon_number, data);
        }
  g_array_set_size(order->any_pointer, 0);
  for (i = 0; i < order->polygon_number->len; i++)
    {
      pdata = &g_array_index(order->polygon_number, struct _orderInfo, i);
      g_array_append_val(order->any_pointer, pdata);
    }

  DBG_fprintf(stderr, "Extension Surfaces: sorting...");
  /* g_array_sort(order->any_pointer, _cmpZ); */
  sort_by_z(&g_array_index(order->any_pointer, struct _orderInfo*, 0),
            0, order->polygon_number->len - 1);
  DBG_fprintf(stderr, "OK\n");
}

/**
 * VisuGlExtSurfacesClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuGlExtSurfacesClass structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtSurfaces:
 *
 * An opaque structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtSurfacesPrivate:
 *
 * Private fields for #VisuGlExtSurfaces objects.
 *
 * Since: 3.7
 */
struct _VisuGlExtSurfacesPrivate
{
  gboolean dispose_has_run;

  /* Polygon ordering for alpha drawing. */
  VisuSurfaceOrder *order;
  gboolean reorderingNeeded;

  /* Rendering characteristics. */
  gboolean drawIntra;

  /* Signals for the attached objects. */
  GList *surfs;
  VisuGlView *view;
  gulong theta_signal, phi_signal, omega_signal;
  VisuInteractive *inter;
  gulong observe_signal;
  VisuPlaneSet *mask;
  VisuBox *box;
  gulong size_signal;
};

enum
  {
    ADD_SIGNAL,
    REMOVE_SIGNAL,
    NB_SIGNAL
  };
static guint _signals[NB_SIGNAL] = { 0 };
enum
  {
    PROP_0,
    DRAW_INTRA_PROP,
    BOX_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static VisuGlExtSurfaces* defaultSurfaces;

static void visu_gl_ext_surfaces_finalize(GObject* obj);
static void visu_gl_ext_surfaces_dispose(GObject* obj);
static void visu_gl_ext_surfaces_get_property(GObject* obj, guint property_id,
                                              GValue *value, GParamSpec *pspec);
static void visu_gl_ext_surfaces_set_property(GObject* obj, guint property_id,
                                              const GValue *value, GParamSpec *pspec);
static void visu_gl_ext_surfaces_rebuild(VisuGlExt *ext);
static void visu_gl_ext_surfaces_draw(VisuGlExt *surfs);
static gboolean _setGlView(VisuGlExt *ext, VisuGlView *view);

/* Callbacks. */
static void _markDirty(VisuGlExt *data);
static void _markDirtyAndReorder(VisuGlExtSurfaces *data);
static void onSurfaceResource(VisuSurface *surfaces, GParamSpec *param, _SurfaceHandle *data);
static void onResourceNotify(VisuSurfaceResource *res, GParamSpec *param, VisuGlExtSurfaces *data);
static void onCameraChange(VisuGlExtSurfaces *data);
static void onObserve(VisuInteractive *inter, gboolean start, VisuGlExtSurfaces *data);
static void onEntryIntra(VisuGlExtSurfaces *surfs, VisuConfigFileEntry *entry, VisuConfigFile *obj);

/* Local methods. */
static void isosurfaces_export_resources(GString *data,
                                         VisuData *dataObj);

/* Local routines. */
static void _freeSurfaceHandle(gpointer obj)
{
  _SurfaceHandle *shd;

  shd = (_SurfaceHandle*)obj;
  g_signal_handler_disconnect(G_OBJECT(shd->surface), shd->resource_signal);
  g_signal_handler_disconnect(G_OBJECT(shd->surface), shd->masked_signal);
  g_signal_handler_disconnect(G_OBJECT(shd->surface), shd->box_signal);
  g_object_unref(shd->surface);
  g_signal_handler_disconnect(G_OBJECT(shd->res), shd->notify_signal);
  g_object_unref(shd->res);
#if GLIB_MINOR_VERSION > 9
  g_slice_free1(sizeof(_SurfaceHandle), obj);
#else
  g_free(obj);
#endif
}
static gpointer _newSurfaceHandle(VisuGlExtSurfaces *surfaces, VisuSurface *surface)
{
  _SurfaceHandle *shd;

#if GLIB_MINOR_VERSION > 9
  shd = g_slice_alloc(sizeof(_SurfaceHandle));
#else
  shd = g_malloc(sizeof(_SurfaceHandle));
#endif
  DBG_fprintf(stderr, "Extension Surfaces: add listeners on surface %p.\n", (gpointer)surface);
  shd->ext = surfaces;
  shd->surface = surface;
  g_object_ref(surface);
  shd->masked_signal = g_signal_connect_swapped
    (G_OBJECT(surface), "masked", G_CALLBACK(_markDirtyAndReorder), surfaces);
  shd->resource_signal = g_signal_connect(G_OBJECT(surface), "notify::resource",
                                          G_CALLBACK(onSurfaceResource), (gpointer)shd);
  shd->box_signal = g_signal_connect_swapped
    (G_OBJECT(surface), "setBox", G_CALLBACK(_markDirty), surfaces);
  shd->res = visu_surface_getResource(surface);
  g_object_ref(shd->res);
  shd->notify_signal = g_signal_connect(G_OBJECT(shd->res), "notify",
                                        G_CALLBACK(onResourceNotify), (gpointer)surfaces);
  return (gpointer)shd;
}
static gint _cmpSurfaceHandle(gconstpointer a, gconstpointer b)
{
  return ((_SurfaceHandle*)a)->surface == b ? 0 : 1;
}
#define _getSurface(H) ((_SurfaceHandle*)H)->surface

G_DEFINE_TYPE_WITH_CODE(VisuGlExtSurfaces, visu_gl_ext_surfaces, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtSurfaces))

static void visu_gl_ext_surfaces_class_init(VisuGlExtSurfacesClass *klass)
{
  VisuConfigFileEntry *entry;

  DBG_fprintf(stderr, "Extension Surfaces: creating the class of the object.\n");
  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuGlExtSurfaces::added:
   * @set: the object emitting the signal.
   * @surface: the added #VisuSurface object.
   *
   * This signal is emitted each time a surface is added to
   * the set.
   *
   * Since: 3.8
   */
  _signals[ADD_SIGNAL] =
    g_signal_new("added", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE, 1, VISU_TYPE_SURFACE, NULL);
  /**
   * VisuGlExtSurfaces::removed:
   * @set: the object emitting the signal.
   * @surface: the removed #VisuSurface object.
   *
   * This signal is emitted each time a surface is removed from
   * the set.
   *
   * Since: 3.8
   */
  _signals[REMOVE_SIGNAL] =
    g_signal_new("removed", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE, 1, VISU_TYPE_SURFACE, NULL);

  DBG_fprintf(stderr, "                - adding new resources ;\n");
  entry = visu_config_file_addBooleanEntry(VISU_CONFIG_FILE_RESOURCE,
                                           FLAG_RESOURCE_INTRA,
                                           DESC_RESOURCE_INTRA,
                                           &INTRA_DEFAULT, FALSE);
  visu_config_file_entry_setVersion(entry, 3.4f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     isosurfaces_export_resources);

  defaultSurfaces = (VisuGlExtSurfaces*)0;

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_gl_ext_surfaces_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_gl_ext_surfaces_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_surfaces_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_surfaces_get_property;
  VISU_GL_EXT_CLASS(klass)->setGlView = _setGlView;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_surfaces_rebuild;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_surfaces_draw;

  /**
   * VisuGlExtSurfaces::draw-intra:
   *
   * Store if the inside of surfaces are drawn with inverse colour.
   *
   * Since: 3.8
   */
  _properties[DRAW_INTRA_PROP] = g_param_spec_boolean("draw-intra", "Draw intra",
                                                      "use inverse colour for inside",
                                                      FALSE, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), DRAW_INTRA_PROP,
				  _properties[DRAW_INTRA_PROP]);
  /**
   * VisuGlExtSurfaces::fitting-box:
   *
   * If set, all surfaces are scaled to fit this box.
   *
   * Since: 3.8
   */
  _properties[BOX_PROP] = g_param_spec_object("fitting-box", "Fitting box",
                                              "If set, all surfaces are scaled to fit this box.",
                                              VISU_TYPE_BOX, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), BOX_PROP,
				  _properties[BOX_PROP]);
}
static void visu_gl_ext_surfaces_init(VisuGlExtSurfaces *obj)
{
  DBG_fprintf(stderr, "Extension Surfaces: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_surfaces_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->drawIntra   = INTRA_DEFAULT;
  obj->priv->surfs       = (GList*)0;
  obj->priv->view           = (VisuGlView*)0;
  obj->priv->inter          = (VisuInteractive*)0;
  obj->priv->observe_signal = 0;
  obj->priv->mask           = (VisuPlaneSet*)0;
  obj->priv->reorderingNeeded = FALSE;
  obj->priv->order          = visu_surface_order_new();
  obj->priv->box            = (VisuBox*)0;

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_INTRA,
                          G_CALLBACK(onEntryIntra), (gpointer)obj, G_CONNECT_SWAPPED);

  if (!defaultSurfaces)
    defaultSurfaces = obj;
}
static void visu_gl_ext_surfaces_dispose(GObject* obj)
{
  VisuGlExtSurfaces *surfaces;
  GList *lst;

  DBG_fprintf(stderr, "Extension Surfaces: dispose object %p.\n", (gpointer)obj);

  surfaces = VISU_GL_EXT_SURFACES(obj);
  if (surfaces->priv->dispose_has_run)
    return;
  surfaces->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  if (surfaces->priv->mask)
    g_object_unref(surfaces->priv->mask);
  for (lst = surfaces->priv->surfs; lst; lst = g_list_next(lst))
    _freeSurfaceHandle((_SurfaceHandle*)lst->data);
  _setGlView(VISU_GL_EXT(surfaces), (VisuGlView*)0);
  visu_gl_ext_surfaces_setOnObserveOrdering(surfaces, (VisuInteractive*)0);
  visu_gl_ext_surfaces_setFittingBox(surfaces, (VisuBox*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_surfaces_parent_class)->dispose(obj);
}
static void visu_gl_ext_surfaces_finalize(GObject* obj)
{
  VisuGlExtSurfaces *surfaces;

  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Extension Surfaces: finalize object %p.\n", (gpointer)obj);

  surfaces = VISU_GL_EXT_SURFACES(obj);
  /* Free privs elements. */
  if (surfaces->priv)
    {
      DBG_fprintf(stderr, "Extension Surfaces: free private surfaces.\n");
      visu_surface_order_free(surfaces->priv->order);
      g_list_free(surfaces->priv->surfs);
    }

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Extension Surfaces: chain to parent.\n");
  G_OBJECT_CLASS(visu_gl_ext_surfaces_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Extension Surfaces: freeing ... OK.\n");
}
static void visu_gl_ext_surfaces_get_property(GObject* obj, guint property_id,
                                              GValue *value, GParamSpec *pspec)
{
  VisuGlExtSurfaces *self = VISU_GL_EXT_SURFACES(obj);

  DBG_fprintf(stderr, "Extension Surfaces: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DRAW_INTRA_PROP:
      g_value_set_boolean(value, self->priv->drawIntra);
      DBG_fprintf(stderr, "%d.\n", self->priv->drawIntra);
      break;
    case BOX_PROP:
      g_value_set_object(value, self->priv->box);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_surfaces_set_property(GObject* obj, guint property_id,
                                              const GValue *value, GParamSpec *pspec)
{
  VisuGlExtSurfaces *self = VISU_GL_EXT_SURFACES(obj);

  DBG_fprintf(stderr, "Extension Surfaces: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DRAW_INTRA_PROP:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_gl_ext_surfaces_setDrawIntra(self, g_value_get_boolean(value));
      break;
    case BOX_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_gl_ext_surfaces_setFittingBox(self, VISU_BOX(g_value_get_object(value)));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_surfaces_new:
 * @name: (allow-none): the name to give to the extension (default is #VISU_GL_EXT_SURFACES_ID).
 *
 * Creates a new #VisuGlExt to draw surfaces.
 *
 * Since: 3.7
 *
 * Returns: a pointer to the #VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtSurfaces* visu_gl_ext_surfaces_new(const gchar *name)
{
  char *name_ = VISU_GL_EXT_SURFACES_ID;
  char *description = _("Drawing iso-surfaces");

  DBG_fprintf(stderr,"Extension Surfaces: new object.\n");
  
  return g_object_new(VISU_TYPE_GL_EXT_SURFACES,
                      "name", (name) ? name : name_,
                      "label", (name) ? name : _(name_),
                      "description", description, "nGlObj", 2,
                      "priority", VISU_GL_EXT_PRIORITY_NORMAL + 2,
                      "saveState", TRUE, NULL);
}

/**
 * visu_gl_ext_surfaces_add:
 * @surfaces: a #VisuGlExtSurfaces object.
 * @surf: (transfer full): a #VisuSurface object.
 *
 * Add a new surface to the list of drawn surfaces.
 *
 * Since: 3.7
 *
 * Returns: FALSE if @surf was already registered.
 **/
gboolean visu_gl_ext_surfaces_add(VisuGlExtSurfaces *surfaces, VisuSurface *surf)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfaces), FALSE);

  lst = g_list_find_custom(surfaces->priv->surfs, surf, _cmpSurfaceHandle);
  if (lst)
    return FALSE;

  surfaces->priv->surfs = g_list_prepend(surfaces->priv->surfs,
                                         _newSurfaceHandle(surfaces, surf));
  visu_surface_setMask(surf, surfaces->priv->mask);

  if (surfaces->priv->box)
    {
      g_object_set(surf, "auto-adjust", TRUE, NULL);
      visu_boxed_setBox(VISU_BOXED(surf), VISU_BOXED(surfaces->priv->box));
    }

  surfaces->priv->reorderingNeeded = !visu_gl_getTrueTransparency(visu_gl_ext_getGlContext(VISU_GL_EXT(surfaces)));

  visu_gl_ext_setDirty(VISU_GL_EXT(surfaces), TRUE);

  g_signal_emit(G_OBJECT(surfaces), _signals[ADD_SIGNAL], 0, surf);

  return TRUE;
}
/**
 * visu_gl_ext_surfaces_remove:
 * @surfaces: a #VisuGlExtSurfaces object.
 * @surf: a #VisuSurface object.
 *
 * Removes @surf from the list of drawn surfaces.
 *
 * Since: 3.7
 *
 * Returns: TRUE if @surf was part of the drawn surfaces.
 **/
gboolean visu_gl_ext_surfaces_remove(VisuGlExtSurfaces *surfaces, VisuSurface *surf)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfaces), FALSE);
  
  lst = g_list_find_custom(surfaces->priv->surfs, surf, _cmpSurfaceHandle);
  if (!lst)
    return FALSE;

  g_object_ref(surf);
  surfaces->priv->surfs = g_list_remove_link(surfaces->priv->surfs, lst);
  _freeSurfaceHandle(lst->data);
  g_list_free(lst);

  surfaces->priv->reorderingNeeded = !visu_gl_getTrueTransparency(visu_gl_ext_getGlContext(VISU_GL_EXT(surfaces)));

  visu_gl_ext_setDirty(VISU_GL_EXT(surfaces), TRUE);

  g_signal_emit(G_OBJECT(surfaces), _signals[REMOVE_SIGNAL], 0, surf);
  g_object_unref(surf);

  return TRUE;
}
static gboolean _setGlView(VisuGlExt *ext, VisuGlView *view)
{
  VisuGlExtSurfaces *surfaces;
  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(ext), FALSE);
  surfaces = VISU_GL_EXT_SURFACES(ext);

  /* No change to be done. */
  if (view == surfaces->priv->view)
    return FALSE;

  if (surfaces->priv->view)
    {
      g_signal_handler_disconnect(G_OBJECT(surfaces->priv->view),
                                  surfaces->priv->theta_signal);
      g_signal_handler_disconnect(G_OBJECT(surfaces->priv->view),
                                  surfaces->priv->phi_signal);
      g_signal_handler_disconnect(G_OBJECT(surfaces->priv->view),
                                  surfaces->priv->omega_signal);
      g_clear_object(&surfaces->priv->view);
    }
  if (view)
    {
      surfaces->priv->view = g_object_ref(view);
      surfaces->priv->theta_signal =
        g_signal_connect_swapped(G_OBJECT(view), "notify::theta",
                                 G_CALLBACK(onCameraChange), (gpointer)surfaces);
      surfaces->priv->phi_signal =
        g_signal_connect_swapped(G_OBJECT(view), "notify::phi",
                                 G_CALLBACK(onCameraChange), (gpointer)surfaces);
      surfaces->priv->omega_signal =
        g_signal_connect_swapped(G_OBJECT(view), "notify::omega",
                                 G_CALLBACK(onCameraChange), (gpointer)surfaces);
    }

  return TRUE;
}
/**
 * visu_gl_ext_surfaces_setMask:
 * @surfaces: a #VisuGlExtSurfaces object.
 * @mask: (allow-none): a #VisuPlaneSet object.
 *
 * Attach @mask to every surface of the set.
 *
 * Since: 3.8
 *
 * Returns: TRUE if mask is changed.
 **/
gboolean visu_gl_ext_surfaces_setMask(VisuGlExtSurfaces *surfaces, VisuPlaneSet *mask)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfaces), FALSE);

  if (surfaces->priv->mask == mask)
    return FALSE;

  if (surfaces->priv->mask)
    g_object_unref(surfaces->priv->mask);
  surfaces->priv->mask = mask;
  if (surfaces->priv->mask)
    g_object_ref(surfaces->priv->mask);
  /* Apply mask on every surface. */
  for (lst = surfaces->priv->surfs; lst; lst = g_list_next(lst))
    visu_surface_setMask(((_SurfaceHandle*)lst->data)->surface, mask);

  return TRUE;
}
static void _fitToBox(VisuGlExtSurfaces *surfaces, gfloat extens _U_, VisuBox *box)
{
  GList *lst;
  gboolean res;

  res = FALSE;
  for (lst = surfaces->priv->surfs; lst; lst = g_list_next(lst))
    {
      g_object_set(G_OBJECT(((_SurfaceHandle*)lst->data)->surface), "auto-adjust", TRUE, NULL);
      res = visu_boxed_setBox(VISU_BOXED(((_SurfaceHandle*)lst->data)->surface),
                              VISU_BOXED(box)) || res;
    }
  if (res)
    visu_gl_ext_setDirty(VISU_GL_EXT(surfaces), TRUE);
}
/**
 * visu_gl_ext_surfaces_setFittingBox:
 * @surfaces: a #VisuGlExtSurfaces object.
 * @box: (allow-none): a #VisuBox object.
 *
 * Changes the box from which surfaces are scaled in.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_gl_ext_surfaces_setFittingBox(VisuGlExtSurfaces *surfaces, VisuBox *box)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfaces), FALSE);

  DBG_fprintf(stderr, "Extension Surfaces: set box %p.\n", (gpointer)box);
  
  if (surfaces->priv->box == box)
    return FALSE;

  if (surfaces->priv->box)
    {
      g_signal_handler_disconnect(surfaces->priv->box, surfaces->priv->size_signal);
      g_object_unref(surfaces->priv->box);
    }
  if (box)
    {
      g_object_ref(box);
      surfaces->priv->size_signal =
        g_signal_connect_swapped(box, "SizeChanged", G_CALLBACK(_fitToBox), surfaces);
      _fitToBox(surfaces, 0.f, box);
    }
  surfaces->priv->box = box;
  return TRUE;
}
/**
 * visu_gl_ext_surfaces_setOnObserveOrdering:
 * @surfaces: the #VisuGlExtSurfaces object to attached to rendering inter.
 * @inter: (transfer full) (allow-none): a #VisuInteractive object.
 *
 * Attach @surfaces to @inter, so it can be rendered there.
 *
 * Since: 3.7
 *
 * Returns: TRUE if the status actually changed.
 **/
gboolean visu_gl_ext_surfaces_setOnObserveOrdering(VisuGlExtSurfaces *surfaces,
                                                   VisuInteractive *inter)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfaces), FALSE);

  /* No change to be done. */
  if (inter == surfaces->priv->inter)
    return FALSE;

  if (surfaces->priv->inter)
    {
      g_signal_handler_disconnect(G_OBJECT(surfaces->priv->inter),
                                  surfaces->priv->observe_signal);
      DBG_fprintf(stderr, "Extension Surfaces: inter %p has %d ref counts.\n",
                  (gpointer)surfaces->priv->inter,
                  G_OBJECT(surfaces->priv->inter)->ref_count);
      g_object_unref(surfaces->priv->inter);
    }
  if (inter)
    {
      g_object_ref(inter);
      surfaces->priv->observe_signal =
        g_signal_connect(G_OBJECT(inter), "observe",
                         G_CALLBACK(onObserve), (gpointer)surfaces);
      DBG_fprintf(stderr, "Extension Surfaces: inter %p has %d ref counts.\n",
                  (gpointer)inter, G_OBJECT(inter)->ref_count);
    }
  else
    {
      surfaces->priv->observe_signal = 0;
    }
  surfaces->priv->inter = inter;

  return TRUE;
}

/**
 * visu_gl_ext_surfaces_getDrawIntra:
 * @surfs: a #VisuGlExtSurfaces object.
 *
 * Retrieve if the interiors of surfaces are drawn with a colour inverse or not.
 *
 * Returns: TRUE if the interior is painted in colour inverse.
 */
gboolean visu_gl_ext_surfaces_getDrawIntra(VisuGlExtSurfaces *surfs)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfs), FALSE);

  return surfs->priv->drawIntra;
}
/**
 * visu_gl_ext_surfaces_setDrawIntra:
 * @surfs: a #VisuGlExtSurfaces object.
 * @status: a boolean.
 *
 * Set if the interiors of surfaces are drawn with a colour inverse or not.
 *
 * Returns: TRUE if the status actually changed.
 */
gboolean visu_gl_ext_surfaces_setDrawIntra(VisuGlExtSurfaces *surfs, gboolean status)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_SURFACES(surfs), FALSE);

  if (surfs->priv->drawIntra == status)
    return FALSE;

  surfs->priv->drawIntra = status;
  g_object_notify_by_pspec(G_OBJECT(surfs), _properties[DRAW_INTRA_PROP]);

  visu_gl_ext_setDirty(VISU_GL_EXT(surfs), TRUE);
  return TRUE;
}

static void sort_block_by_z(int *order, float *z, int begin, int end) {
   int i;
   int middle;
   int temp;

   if( begin >= end ) return;

   /* We make sure end + begin / 2 has found its place. */
   temp = order[begin];
   order[begin] = order[(end + begin) / 2];
   order[(end + begin) / 2] = temp;

   middle = begin;
   for(i = begin + 1; i <= end; i++) {
      if ( z[order[i]] < z[order[begin]] ) {
         temp = order[i];
	 middle += 1;
         order[i] = order[middle];
         order[middle] = temp;
      }
   }
   temp = order[begin];
   order[begin] = order[middle];
   order[middle] = temp;
   sort_block_by_z(order, z, begin, middle-1);
   sort_block_by_z(order, z, middle+1, end);
}
/**
 * visu_surface_duplicate:
 * @totalList: an OpenGL identifier for the global list to create ;
 * @simpleBlockList: an OpenGL identifier for the list with the
 * surfaces in the primitive cell ;
 * @box: the definition of the #VisuBox for extension ;
 * @reorder: if TRUE the blocks are drawn from back to front.
 *
 * Duplicate the list @simpleBlockList using the extension of the
 * given @box.
 */
static void _duplicate(VisuGlExt *extension, int simpleBlockList,
                       VisuBox *box, gboolean reorder)
{
  float ext[3], *xyzTrans, boxTrans[3], *z;
  int i, j, k, n, *order;
  float mat[16];

  DBG_fprintf(stderr, "Isosurfaces: duplicate the primitive block.\n");

  if (box)
    /* Duplicate the surface according to the box extension. */
    visu_box_getExtension(box, ext);
  else
    {
      ext[0] = 0.f;
      ext[1] = 0.f;
      ext[2] = 0.f;
    }
  
  if (reorder)
    glGetFloatv(GL_MODELVIEW_MATRIX, mat);

  n = (1 + 2 * (int)ext[0]) * (1 + 2 * (int)ext[1]) * (1 + 2 * (int)ext[2]);
  xyzTrans = g_malloc(sizeof(int) * 3 * n);
  z = (float*)0;
  if (reorder)
    z = g_malloc(sizeof(float) * n);
  order = g_malloc(sizeof(int) * n);
  if (n > 1)
    {
      n = 0;
      for (i = -(int)ext[0]; i < 1 + (int)ext[0]; i++)
        for (j = -(int)ext[1]; j < 1 + (int)ext[1]; j++)
          for (k = -(int)ext[2]; k < 1 + (int)ext[2]; k++)
            {
              boxTrans[0] = (float)i;
              boxTrans[1] = (float)j;
              boxTrans[2] = (float)k;
              visu_box_convertBoxCoordinatestoXYZ(box, xyzTrans + 3 * n, boxTrans);
              if (reorder)
                z[n] = (mat[ 2] * xyzTrans[3 * n + 0] +
                        mat[ 6] * xyzTrans[3 * n + 1] +
                        mat[10] * xyzTrans[3 * n + 2] +
                        mat[14] * 1.) /
                  (mat[ 3] * xyzTrans[3 * n + 0] +
                   mat[ 7] * xyzTrans[3 * n + 1] +
                   mat[11] * xyzTrans[3 * n + 2] +
                   mat[15] * 1.);
              order[n] = n;
              n += 1;
            }
    }
  else
    {
      order[0] = 0;
      xyzTrans[0] = 0.f;
      xyzTrans[1] = 0.f;
      xyzTrans[2] = 0.f;
    }

  if (reorder)
    /* we sort xyzTrans following z values. */
    sort_block_by_z(order, z, 0, n - 1);

  visu_gl_ext_startDrawing(extension);
  for (i = 0; i < n; i++)
    {
/*       DBG_fprintf(stderr, "Isosurfaces: translate surfaces to box %d.\n", */
/* 		  order[i]); */
      glPushMatrix();
      glTranslated(xyzTrans[3 * order[i] + 0],
		   xyzTrans[3 * order[i] + 1],
		   xyzTrans[3 * order[i] + 2]);
      glCallList(simpleBlockList);
      glPopMatrix();
    }
  visu_gl_ext_completeDrawing(extension);

  g_free(order);
  g_free(xyzTrans);
  if (reorder)
    g_free(z);
}
static void visu_gl_ext_surfaces_draw(VisuGlExt *surfs)
{
  guint i, j;
  struct _orderInfo *data;
  VisuSurfaceResource *res, *res_old;
  float rgba[4];
  GArray *vertices;
  VisuSurfacePoint *at;
  VisuGlExtSurfacesPrivate *priv = VISU_GL_EXT_SURFACES(surfs)->priv;
  
  DBG_fprintf(stderr, "Isosurfaces: rebuilding surfaces list\n");
  visu_gl_ext_setDirty(surfs, FALSE);

  glDeleteLists(visu_gl_ext_getGlList(surfs), 1);
  if (!priv->surfs)
    return;

  /* If order is out of date, we update. */
  if (priv->reorderingNeeded || priv->order->polygon_number->len == 0)
    {
      visu_surface_order_polygons(priv->order, priv->surfs);
      priv->reorderingNeeded = FALSE;
    }

  vertices = g_array_sized_new(FALSE, FALSE, sizeof(VisuSurfacePoint), 5);

  glNewList(visu_gl_ext_getGlList(surfs) + 1, GL_COMPILE);  
  if (priv->drawIntra)
    glEnable(GL_CULL_FACE);
  else
    glDisable(GL_CULL_FACE);
   
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);   

  DBG_fprintf(stderr, " | draw polygons.\n");
  res = res_old = (VisuSurfaceResource*)0;
  for(i = 0; i < priv->order->any_pointer->len; i++)
    {
      data = g_array_index(priv->order->any_pointer, struct _orderInfo*, i);
      res = visu_surface_getResource(data->iter.surf);
      if (res != res_old || priv->drawIntra)
        {
          visu_gl_setColor((VisuGl*)0, visu_surface_resource_getMaterial(res),
                           visu_surface_resource_getColor(res)->rgba);
          res_old = res;
        }

      /* This is where to find the points and the normals. */
      visu_surface_iter_poly_getVertices(&data->iter, vertices);

      glBegin(GL_POLYGON);
      for (j = 0; j < vertices->len; j++ )
        {
          at = &g_array_index(vertices, VisuSurfacePoint, j);
          glNormal3dv(at->normal);
          glVertex3dv(at->at);
      }
      glEnd();

      if (priv->drawIntra)
        {
          glBegin(GL_POLYGON);
          tool_color_invertRGBA(rgba, visu_surface_resource_getColor(res)->rgba);
          visu_gl_setColor((VisuGl*)0, visu_surface_resource_getMaterial(res), rgba);
          for (j = vertices->len; j > 0; j-- )
            {
              at = &g_array_index(vertices, VisuSurfacePoint, j - 1);
              glNormal3d(-at->normal[0], -at->normal[1], -at->normal[2]);
              glVertex3dv(at->at);
            }
          glEnd();
        }
    }
   
  glEndList();

  g_array_unref(vertices);

  /* Duplicate here. */
  _duplicate(surfs, visu_gl_ext_getGlList(surfs) + 1,
             visu_boxed_getBox(VISU_BOXED(((_SurfaceHandle*)priv->surfs->data)->surface)), TRUE);
}

/**************/
/* Callbacks. */
/**************/
static void _markDirty(VisuGlExt *data)
{
  visu_gl_ext_setDirty(data, TRUE);
}
static void _markDirtyAndReorder(VisuGlExtSurfaces *data)
{
  data->priv->reorderingNeeded = TRUE;
  visu_gl_ext_setDirty(VISU_GL_EXT(data), TRUE);
}
static void visu_gl_ext_surfaces_rebuild(VisuGlExt *ext)
{
  _markDirtyAndReorder(VISU_GL_EXT_SURFACES(ext));
  visu_gl_ext_surfaces_draw(ext);
}
static void onSurfaceResource(VisuSurface *surfaces, GParamSpec *param, _SurfaceHandle *data)
{
  g_signal_handler_disconnect(G_OBJECT(data->res), data->notify_signal);
  g_object_unref(data->res);
  data->res = visu_surface_getResource(surfaces);
  g_object_ref(data->res);
  data->notify_signal = g_signal_connect(G_OBJECT(data->res), "notify",
                                         G_CALLBACK(onResourceNotify),
                                         (gpointer)data->ext);
  onResourceNotify(data->res, param, (gpointer)data->ext);
}
static void onResourceNotify(VisuSurfaceResource *res _U_, GParamSpec *param, VisuGlExtSurfaces *data)
{
  if (!strcmp(g_param_spec_get_name(param), "maskable") ||
      !strcmp(g_param_spec_get_name(param), "rendered"))
    data->priv->reorderingNeeded = TRUE;
  visu_gl_ext_setDirty(VISU_GL_EXT(data), TRUE);
}
static void onCameraChange(VisuGlExtSurfaces *data)
{
  data->priv->reorderingNeeded = !visu_gl_getTrueTransparency(visu_gl_ext_getGlContext(VISU_GL_EXT(data)));
  visu_gl_ext_setDirty(VISU_GL_EXT(data), TRUE);
}
static void onObserve(VisuInteractive *inter _U_, gboolean start, VisuGlExtSurfaces *data)
{
  if (!data->priv->view && !start)
    {
      data->priv->reorderingNeeded = !visu_gl_getTrueTransparency(visu_gl_ext_getGlContext(VISU_GL_EXT(data)));
      visu_gl_ext_setDirty(VISU_GL_EXT(data), TRUE);
    }
}
static void onEntryIntra(VisuGlExtSurfaces *surfs, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_ext_surfaces_setDrawIntra(surfs, INTRA_DEFAULT);
}

/***************/
/* Properties. */
/***************/
static void isosurfaces_export_resources(GString *data, VisuData *dataObj _U_)
{
  if (!defaultSurfaces)
    return;

  visu_config_file_exportComment(data, DESC_RESOURCE_INTRA);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_INTRA, NULL,
                               "%d", defaultSurfaces->priv->drawIntra);
  visu_config_file_exportComment(data, "");
}
