/*   EXTRAITS DE LA LICENCE
     Copyright CEA, contributeurs : Luc BILLARD, Damien
     CALISTE, Olivier D'Astier, laboratoire L_Sim, (2001-2016)
  
     Adresse mèl :
     BILLARD, non joignable par 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 : Luc BILLARD and Damien
     CALISTE and Olivier D'Astier, laboratoire L_Sim, (2001-2016)

     E-mail addresses :
     BILLARD, not reachable any more ;
     CALISTE, damien P caliste AT cea P fr.
     D'ASTIER, dastier AT iie P cnam 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 Documentation/licence.en.txt.
*/
#include "surfaces.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <visu_tools.h>
#include <coreTools/toolMatrix.h>

/**
 * SECTION:surfaces
 * @short_description: Supports loading of .surf files and drawing of
 * surfaces through OpenGL.
 *
 * <para>Originally written by Luc Billard for his Visualize program. This
 * module allows loading of .surf files to draw scalar fields on top of
 * the current display scene. .surf files are text files which specs are
 * the following :
 * <itemizedlist>
 * <listitem><para>
 * 1st line is arbitrary
 * </para></listitem>
 * <listitem><para>
 * 2nd line must contain 3 real (float) values: dxx dyx dyy
 * </para></listitem>
 * <listitem><para>
 * 3rd line must contain 3 real (float) values: dzx dzy dzz
 * </para></listitem>
 * <listitem><para>
 * 4th line must contain 3 positive integers which
 * represents respectively the number of surfaces, the total number of
 * polys, and the total number of points
 * </para></listitem>
 * <listitem><para>
 * Then, for each of these surfaces :      
 *     
 * <itemizedlist> 
 * <listitem><para>
 * next line must contain the name of the surface : it is a
 * string which should match the pattern surface_*
 * </para></listitem>
 * <listitem><para>
 * next line must contain 2 positive integer values: the number of polys
 * (num_polys) and
 * the number of points (num_points) used by the surface
 * </para></listitem>
 * <listitem><para>
 * each of the following num_polys lines must match the pattern 
 * [n i_1 i_2 i_3 ... i_n] where n is the number of vertices in the poly (n >= 3)
 * and [i_1 i_2 i_3 ... i_n] are the numbering of these vertices (vertices numbered from 1 to num_points)
 * </para></listitem>
 * <listitem><para>
 * each of the following num_points lines must contain 6 real values for the 
 * successive (1 to num_points) points : [x y z nx ny nz], where x y z are the coordinates of the point
 * and nx ny nz are the coordinates of the unit normal at the point
 * </para></listitem>
 * </itemizedlist>
 * 
 * </para></listitem>
 * </itemizedlist>
 * 
 * It is the responsibility of the user to guarantee that
 * dxx, dyx, dyy, dzx, dzy, dzz match the one currently loaded in V_Sim's
 * current context. Though if you use <link
 * linkend="v-sim-panelSurfaces">panelSurfaces</link> you can ask
 * to resize the surfaces so that they fit in the current loaded box.
 * </para>
 */


#define ISOSURFACES_FLAG_POTENTIAL "# potentialValue"

static GQuark quark;

typedef struct VisuSurfaceProperties_struct
{
  gchar *name;
  /* The element (G_TYPE_INT...). */
  guint type;
  /* A pointer to the surf it belongs to. */
  VisuSurface *surf;
  /* The data. */
  gpointer data;
} VisuSurfaceProperties;
static void freeVisuSurfaceProperties(gpointer data);

enum
  {
    SURFACE_MASKED_SIGNAL,
    NB_SIGNAL
  };
enum
  {
    PROP_0,
    RESOURCE_PROP,
    N_PROP,
    ADJUST_PROP,
    BOX_PROP
  };

/* Private area. */
struct _VisuSurfacePrivate
{
  gboolean dispose_has_run;

  VisuSurfacePoints basePoints;
  VisuSurfacePoints volatilePlanes;

  /* The description of the box where surfaces are drawn. */
  VisuBox *box;
  gboolean adjust;

  /* Resources for the surface. */
  VisuSurfaceResource *resource;
  gulong maskable_signal;

  /* Related objects. */
  VisuPlaneSet *mask;
  gulong masking_signal;

  /* Table to add properties to surfaces. */
  GHashTable *properties;
};

/* Internal variables. */
static guint surfaces_signals[NB_SIGNAL] = { 0 };
static GParamSpec* properties[N_PROP];

/* Object gestion methods. */
static void visu_surface_dispose   (GObject* obj);
static void visu_surface_finalize  (GObject* obj);
static void visu_surface_get_property(GObject* obj, guint property_id,
                                       GValue *value, GParamSpec *pspec);
static void visu_surface_set_property(GObject* obj, guint property_id,
                                       const GValue *value, GParamSpec *pspec);
static void visu_boxed_interface_init(VisuBoxedInterface *iface);

/* Local functions. */
static VisuBox* visu_surface_getBox(VisuBoxed *self);
static gboolean visu_surface_setBox(VisuBoxed *self, VisuBox *box);
static void onMasking(VisuPlaneSet *mask, VisuSurface *data);
static gboolean _setResource(VisuSurface *surf, VisuSurfaceResource *res);
static void onMaskable(VisuSurfaceResource *res, GParamSpec *param, VisuSurface *data);

G_DEFINE_TYPE_WITH_CODE(VisuSurface, visu_surface, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuSurface)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_BOXED,
                                              visu_boxed_interface_init))

static void visu_surface_class_init(VisuSurfaceClass *klass)
{
  DBG_fprintf(stderr, "VisuSurface: creating the class of the object.\n");

  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuSurface::masked:
   * @surf: the object which received the signal.
   *
   * Gets emitted when a surface is shown or hidden by a plane.
   *
   * Since: 3.7
   */
  surfaces_signals[SURFACE_MASKED_SIGNAL] =
    g_signal_new("masked", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);

  /* Connect freeing methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_surface_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_surface_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_surface_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_surface_get_property;

  g_object_class_override_property(G_OBJECT_CLASS(klass), ADJUST_PROP, "auto-adjust");
  g_object_class_override_property(G_OBJECT_CLASS(klass), BOX_PROP, "box");

  /**
   * VisuSurface::resource:
   *
   * Resource used to draw the suface (color, properties...).
   *
   * Since: 3.8
   */
  properties[RESOURCE_PROP] = g_param_spec_object("resource", "Resource", "resource",
                                                  VISU_TYPE_SURFACE_RESOURCE, G_PARAM_READWRITE);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), RESOURCE_PROP, properties[RESOURCE_PROP]);

  quark = g_quark_from_static_string("visu_isosurfaces");
}
static void visu_boxed_interface_init(VisuBoxedInterface *iface)
{
  iface->get_box = visu_surface_getBox;
  iface->set_box = visu_surface_setBox;
}

static void visu_surface_init(VisuSurface *obj)
{
  DBG_fprintf(stderr, "VisuSurface: creating a new surfaces (%p).\n", (gpointer)obj);

  obj->priv = visu_surface_get_instance_private(obj);

  obj->priv->dispose_has_run = FALSE;

  obj->priv->adjust     = FALSE;
  obj->priv->box        = (VisuBox*)0;
  obj->priv->resource   = (VisuSurfaceResource*)0;
  obj->priv->maskable_signal = 0;
  obj->priv->mask       = (VisuPlaneSet*)0;
  obj->priv->masking_signal = 0;
  obj->priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                NULL, freeVisuSurfaceProperties);
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_surface_dispose(GObject* obj)
{
  DBG_fprintf(stderr, "VisuSurface: dispose object %p.\n", (gpointer)obj);

  if (VISU_SURFACE(obj)->priv->dispose_has_run)
    return;

  visu_surface_setBox(VISU_BOXED(obj), (VisuBox*)0);
  visu_surface_setMask(VISU_SURFACE(obj), (VisuPlaneSet*)0);
  _setResource(VISU_SURFACE(obj), (VisuSurfaceResource*)0);

  VISU_SURFACE(obj)->priv->dispose_has_run = TRUE;
  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_surface_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_surface_finalize(GObject* obj)
{
  VisuSurfacePrivate *priv = VISU_SURFACE(obj)->priv;

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

  visu_surface_points_free(&priv->basePoints);
  visu_surface_points_free(&priv->volatilePlanes);
  if (priv->properties)
    g_hash_table_destroy(priv->properties);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_surface_parent_class)->finalize(obj);
}
static void visu_surface_get_property(GObject* obj, guint property_id,
                                       GValue *value, GParamSpec *pspec)
{
  VisuSurface *self = VISU_SURFACE(obj);

  DBG_fprintf(stderr, "Visu GlView: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      g_value_set_object(value, self->priv->box);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->box);
      break;
    case ADJUST_PROP:
      g_value_set_boolean(value, self->priv->adjust);
      DBG_fprintf(stderr, "%d.\n", self->priv->adjust);
      break;
    case RESOURCE_PROP:
      g_value_set_object(value, self->priv->resource);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->resource);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_surface_set_property(GObject* obj, guint property_id,
                                       const GValue *value, GParamSpec *pspec)
{
  VisuSurface *self = VISU_SURFACE(obj);

  DBG_fprintf(stderr, "Visu GlView: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_surface_setBox(VISU_BOXED(obj), VISU_BOX(g_value_get_object(value)));
      break;
    case ADJUST_PROP:
      self->priv->adjust = g_value_get_boolean(value);
      DBG_fprintf(stderr, "%d.\n", self->priv->adjust);
      break;
    case RESOURCE_PROP:
      visu_surface_setResource(self, g_value_get_object(value));
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->resource);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_surface_getErrorQuark:
 *
 * Internal routine for error handling.
 *
 * Returns: the #GQuark associated to errors related to surface
 * files.
 */
GQuark visu_surface_getErrorQuark(void)
{
  return quark;
}

/**
 * VisuSurfaceDefinition:
 * @points: (element-type VisuSurfacePoint): some coordinates.
 * @polys: (element-type VisuSurfacePoly): some polygons.
 *
 * Define a surface by giving some node coordinates and normals and
 * how to connect these nodes to form polygons.
 */
static gpointer definition_copy(gpointer boxed)
{
  VisuSurfaceDefinition *out;

  out = (VisuSurfaceDefinition*)g_memdup(boxed, sizeof(VisuSurfaceDefinition));
  g_array_ref(out->points);
  g_array_ref(out->polys);

  return (gpointer)out;
}
static void definition_free(gpointer boxed)
{
  VisuSurfaceDefinition *def = (VisuSurfaceDefinition*)boxed;

  g_array_unref(def->points);
  g_array_unref(def->polys);
  g_free(boxed);
}
/**
 * visu_surface_definition_get_type:
 *
 * Create and retrieve a #GType for a #VisuSurfaceDefinition object.
 *
 * Since: 3.8
 *
 * Returns: a new type for #VisuSurfaceDefinition structures.
 */
GType visu_surface_definition_get_type(void)
{
  static GType g_define_type_id = 0;

  if (g_define_type_id == 0)
    g_define_type_id = g_boxed_type_register_static("VisuSurfaceDefinition",
                                                    definition_copy, definition_free);
  return g_define_type_id;
}

/**
 * visu_surface_checkConsistency:
 * @surf: a #VisuSurface object.
 *
 * Check if all arrays in the structures are consistent (without
 * overflow).
 */
void visu_surface_checkConsistency(VisuSurface* surf)
{
  DBG_fprintf(stderr, "Isosurfaces: Check consistency.\n");

  DBG_fprintf(stderr, " | Base points\n");
  visu_surface_points_check(&surf->priv->basePoints);
  DBG_fprintf(stderr, " | Volatile planes\n");
  visu_surface_points_check(&surf->priv->volatilePlanes);
}
/**
 * visu_surface_new:
 * @label: a string.
 * @points: (element-type VisuSurfacePoint): a set of point coordinates.
 * @polys: (element-type VisuSurfacePoly): a set of polygons.
 *
 * Create a new #VisuSurface object as defined by @points and @polys.
 *
 * Returns: a newly allocated #VisuSurface structure.
 */
VisuSurface* visu_surface_new(const gchar *label, const GArray *points,
                              const GArray *polys)
{
  VisuSurface *surf;
  VisuSurfaceResource *res;
  gboolean new;

  surf = VISU_SURFACE(g_object_new(VISU_TYPE_SURFACE, NULL));
  DBG_fprintf(stderr, "Visu Surface: new surfaces %p.\n", (gpointer)surf);
  visu_surface_points_init(&surf->priv->basePoints, 0);
  visu_surface_points_init(&surf->priv->volatilePlanes, 0);

  DBG_fprintf(stderr, "Visu Surface: add surface %d %d.\n",
	      polys->len, points->len);

  visu_surface_points_addPoly(&surf->priv->basePoints, points, polys);
  visu_surface_points_addPoly(&surf->priv->volatilePlanes, NULL, NULL);

  res = visu_surface_resource_new_fromName(label, &new);
  visu_surface_setResource(surf, res);
  if (new)
    g_object_set(G_OBJECT(surf->priv->resource), "rendered", TRUE, NULL);
  g_object_unref(res);

#if DEBUG == 1
  visu_surface_checkConsistency(surf);
#endif

  return surf;
}
/**
 * visu_surface_new_fromDefinition:
 * @label: a string.
 * @definition: Some points and polys.
 *
 * Same as visu_surface_new(), mainly for language bindings.
 *
 * Since: 3.8
 *
 * Returns: a newly allocated #VisuSurface structure.
 **/
VisuSurface* visu_surface_new_fromDefinition(const gchar *label,
                                             const VisuSurfaceDefinition *definition)
{
  g_return_val_if_fail(definition, (VisuSurface*)0);

  return visu_surface_new(label, definition->points, definition->polys);
}

static gboolean visu_surface_setBox(VisuBoxed *self, VisuBox *box)
{
  VisuSurface *surf;
  float trans[3][3], boxToXYZ[3][3], XYZToOldBox[3][3];
  double boxToXYZd[3][3], XYZToOldBoxd[3][3];

  g_return_val_if_fail(VISU_IS_SURFACE(self), FALSE);
  surf = VISU_SURFACE(self);

  DBG_fprintf(stderr, "Isosurfaces: change the current box to fit to %p.\n",
	      (gpointer)box);
  if (surf->priv->box == box)
    return FALSE;

  if (surf->priv->adjust && surf->priv->box && box)
    {
      visu_box_getInvMatrix(surf->priv->box, XYZToOldBoxd);
      tool_matrix_dtof(XYZToOldBox, XYZToOldBoxd);
      visu_box_getCellMatrix(box, boxToXYZd);
      tool_matrix_dtof(boxToXYZ, boxToXYZd);

      tool_matrix_productMatrix(trans, boxToXYZ, XYZToOldBox);

      visu_surface_points_transform(&surf->priv->basePoints, trans);
      visu_surface_points_transform(&surf->priv->volatilePlanes, trans);
    }

  if (surf->priv->box)
    g_object_unref(surf->priv->box);
  surf->priv->box = box;
  if (box)
    g_object_ref(box);

  return TRUE;
}
static VisuBox* visu_surface_getBox(VisuBoxed *self)
{
  g_return_val_if_fail(VISU_IS_SURFACE(self), (VisuBox*)0);

  DBG_fprintf(stderr, "VisuSurface: get box.\n");
  return VISU_SURFACE(self)->priv->box;
}
static void freeVisuSurfaceProperties(gpointer data)
{
  VisuSurfaceProperties *prop;

  prop = (VisuSurfaceProperties*)data;
  g_free(prop->name);
  g_free(prop->data);
  g_free(prop);
}
/* Dealing with properties as floats. */
/**
 * visu_surface_addPropertyFloat:
 * @surf: a #VisuSurface object ;
 * @name: the name of the property to add.
 *
 * Some properties can be associated to the surfaces stored in @surf.
 * This method is add a new property.
 *
 * Returns: a newly allocated array that can be populated.
 */
float* visu_surface_addPropertyFloat(VisuSurface *surf, const gchar* name)
{
  VisuSurfaceProperties *prop;

  g_return_val_if_fail(surf,            (float*)0);
  g_return_val_if_fail(name && name[0], (float*)0);

  DBG_fprintf(stderr, "Visu Surfaces: add a new float prop '%s'.\n", name);
  prop = g_malloc(sizeof(VisuSurfaceProperties));
  prop->name = g_strdup(name);
  prop->type = G_TYPE_FLOAT;
  prop->surf = surf;
  prop->data = g_malloc(sizeof(float));
  g_hash_table_insert(surf->priv->properties, (gpointer)prop->name, (gpointer)prop);
  return (float*)prop->data;
}
gboolean visu_surface_addPropertyFloatValue(VisuSurface *surf,
                                             const gchar* name, float value)
{
  float* data;
  VisuSurfaceProperties *prop;

  g_return_val_if_fail(surf, FALSE);
  
  prop = (VisuSurfaceProperties*)g_hash_table_lookup(surf->priv->properties, name);
  if (!prop)
    return FALSE;
  g_return_val_if_fail(prop->surf != surf, FALSE);

  data = (float*)prop->data;
  *data = value;
  return TRUE;
}
/**
 * visu_surface_getPropertyValueFloat:
 * @surf: a #VisuSurface object ;
 * @name: the name of the property to get the value from ;
 * @value: a location to store the value.
 *
 * This method retrieves a float value stored as a property called @name for
 * the surface defined by its number @idSurf.
 *
 * Returns: TRUE if a value is indeed found.
 */
gboolean visu_surface_getPropertyValueFloat(VisuSurface *surf,
                                             const gchar *name, float *value)
{
  float* data;
  VisuSurfaceProperties *prop;

  g_return_val_if_fail(surf && value, FALSE);
  
  prop = (VisuSurfaceProperties*)g_hash_table_lookup(surf->priv->properties, name);
  if (!prop)
    return FALSE;
  g_return_val_if_fail(prop->surf != surf, FALSE);

  data = (float*)prop->data;
  *value = *data;
  return TRUE;
}
/**
 * visu_surface_getPropertyFloat:
 * @surf: a #VisuSurface object ;
 * @name: the name of the property to look for.
 *
 * Some properties can be associated to the surfaces stored in @surf.
 * This method is used to retrieve floating point values properties.
 *
 * Returns: a table with the values if the property is found, NULL
 *            otherwise.
 */
float* visu_surface_getPropertyFloat(VisuSurface *surf, const gchar *name)
{
  VisuSurfaceProperties *prop;

  g_return_val_if_fail(surf, (float*)0);

  prop = (VisuSurfaceProperties*)g_hash_table_lookup(surf->priv->properties, name);
  if (prop)
    return (float*)prop->data;
  else
    return (float*)0;
}

/**
 * visu_surface_loadFile:
 * @file: target file to load ;
 * @surf: (out) (transfer full) (element-type VisuSurface*): a set of
 * surfaces (location) ;
 * @error: a location to store errors.
 *
 * This loads a surface file and set default material properties for it.
 * See surf file specifications.
 *
 * Returns: TRUE in case of success, FALSE otherwise. Even in case of success
 *          @error may have been set.
 */
gboolean visu_surface_loadFile(const char *file, GList **surf, GError **error)
{
  GIOChannel* surf_file;
  int line_number = 0;
  double dxx=0, dyx=0, dyy=0;
  double dzx=0, dzy=0, dzz=0;
  double geometry[VISU_BOX_N_VECTORS];
  guint file_nsurfs=0, file_npolys=0, file_npoints=0;
  guint npolys_loaded=0, npoints_loaded=0;
  guint i;
  int j,k, res;
  guint sum_polys=0, sum_points=0;
  GIOStatus io_status;
  GString *current_line;
  float *densityData, densityValue;
  gchar *label;
  GArray *points, *polys;
  VisuBox *box;
  VisuSurface *surface;

  g_return_val_if_fail(surf, FALSE);
  g_return_val_if_fail(error, FALSE);

  DBG_fprintf(stderr, "Isosurfaces: trying to load %s file.\n", file);

  *surf = (GList*)0;

  current_line = g_string_new("");

  *error = (GError*)0;
  surf_file = g_io_channel_new_file(file, "r", error);
  if(!surf_file)
    return FALSE;

  /*   DBG_fprintf(stderr, "File opened\n"); */
  *error = (GError*)0;
  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
  if(io_status != G_IO_STATUS_NORMAL)
    {
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
  line_number++;  
  /*   DBG_fprintf(stderr, "Line %d read successfully\n", line_number); */

  *error = (GError*)0;
  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
  if(io_status != G_IO_STATUS_NORMAL)
    {
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
  line_number++;
  /*   DBG_fprintf(stderr, "Line %d read successfully\n", line_number); */
  if(sscanf(current_line->str, "%lf %lf %lf", &dxx, &dyx, &dyy) != 3)
    {
      *error = g_error_new(VISU_ERROR_ISOSURFACES,
			   VISU_ERROR_FORMAT,
			   _("Line %d doesn't match the [float, float, float]"
			     " pattern."), line_number);
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }

  *error = (GError*)0;
  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
  if(io_status != G_IO_STATUS_NORMAL)
    {
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
  line_number++;
  /*   DBG_fprintf(stderr, "Line %d read successfully\n", line_number); */
  if(sscanf(current_line->str, "%lf %lf %lf", &dzx, &dzy, &dzz) != 3)
    {
      *error = g_error_new(VISU_ERROR_ISOSURFACES,
			   VISU_ERROR_FORMAT,
			   _("Line %d doesn't match the [float, float, float]"
			     " pattern."), line_number);
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
      
  *error = (GError*)0;
  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
  if(io_status != G_IO_STATUS_NORMAL)
    {
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
  line_number++;
  /*   DBG_fprintf(stderr, "Line %d read successfully\n", line_number); */
  res = sscanf(current_line->str, "%u %u %u",
               &file_nsurfs, &file_npolys, &file_npoints);
  if(res != 3 || file_nsurfs <= 0 || file_npolys <= 0 || file_npoints <= 0)
    {
      *error = g_error_new(VISU_ERROR_ISOSURFACES,
			   VISU_ERROR_FORMAT,
			   _("Line %d doesn't match the [int > 0, int > 0, int > 0]"
			     " pattern."), line_number);
      g_string_free(current_line, TRUE);
      g_io_channel_unref(surf_file);
      return FALSE;
    }
  /* From now on, the file is supposed to be a valid surface file. */
  geometry[VISU_BOX_DXX] = dxx;
  geometry[VISU_BOX_DYX] = dyx;
  geometry[VISU_BOX_DYY] = dyy;
  geometry[VISU_BOX_DZX] = dzx;
  geometry[VISU_BOX_DZY] = dzy;
  geometry[VISU_BOX_DZZ] = dzz;
  box = visu_box_new(geometry, VISU_BOX_PERIODIC);
  visu_box_setMargin(box, 0, FALSE);

  /* For each surf */
  for(i = 0; i < file_nsurfs; i++) 
    {
      int surf_npolys=0, surf_npoints=0;
      densityValue = G_MAXFLOAT;
      
      /* Allow some commentaries here begining with a '#' character. */
      do
	{
	  /* Get the first line that should contains current surf' name */
	  *error = (GError*)0;
	  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
	  if(io_status != G_IO_STATUS_NORMAL)
	    {
	      g_string_free(current_line, TRUE);
	      g_io_channel_unref(surf_file);
	      g_object_unref(box);
	      return TRUE;
	    }
	  line_number++;

	  /* If the line begins with a '#', then we try to find a density value. */
	  if (current_line->str[0] == '#')
            sscanf(current_line->str, ISOSURFACES_FLAG_POTENTIAL" %f", &densityValue);
	}
      while (current_line->str[0] == '#');
      g_strdelimit(current_line->str, "\n", ' ');
      g_strstrip(current_line->str);
      label = g_strdup(current_line->str);
      DBG_fprintf(stderr, "Visu Surfaces: line %d ('%s') read successfully %f\n",
		  line_number, label, densityValue);

      *error = (GError*)0;
      io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
      if(io_status != G_IO_STATUS_NORMAL)
	{
	  g_string_free(current_line, TRUE);
	  g_io_channel_unref(surf_file);
	  g_object_unref(box);
          g_free(label);
	  return TRUE;
	}
      line_number++;
      /*        DBG_fprintf(stderr, "Line %d read successfully\n", line_number); */
      res = sscanf(current_line->str, "%d %d", &surf_npolys, &surf_npoints);
      if(res != 2 || surf_npolys <= 0 || surf_npoints <= 0)
	{
	  *error = g_error_new(VISU_ERROR_ISOSURFACES,
			       VISU_ERROR_FORMAT,
			       _("Line %d doesn't match the [int > 0, int > 0]"
				 " pattern."), line_number);
	  g_string_free(current_line, TRUE);
	  g_io_channel_unref(surf_file);
	  g_object_unref(box);
          g_free(label);
	  return TRUE;
	}

      sum_polys += surf_npolys;
      if(sum_polys > file_npolys)
	{
	  *error = g_error_new(VISU_ERROR_ISOSURFACES,
			       VISU_ERROR_CHECKSUM,
			       _("Error on line %d. Declared number of polygons"
				 " reached."), line_number);
	  g_string_free(current_line, TRUE);
	  g_io_channel_unref(surf_file);
	  g_object_unref(box);
          g_free(label);
	  return TRUE;
	}

      sum_points += surf_npoints;
      if(sum_points > file_npoints)
	{
	  *error = g_error_new(VISU_ERROR_ISOSURFACES,
			       VISU_ERROR_CHECKSUM,
			       _("Error on line %d. Declared number of points"
				 " reached."), line_number);
	  g_string_free(current_line, TRUE);
	  g_io_channel_unref(surf_file);
	  g_object_unref(box);
          g_free(label);
	  return TRUE;
	}

      polys = g_array_sized_new(FALSE, FALSE, sizeof(VisuSurfacePoly), surf_npolys);
      for(j = 0; j < surf_npolys; j++)
	{
          VisuSurfacePoly poly;
	  guint nvertex_i=0;
	  gchar **split_line;

          poly.nvertices = 0;
	   
	  *error = (GError*)0;
	  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
	  if(io_status != G_IO_STATUS_NORMAL)
	    {
	      g_string_free(current_line, TRUE);
	      g_io_channel_unref(surf_file);
	      g_object_unref(box);
              g_free(label);
	      return TRUE;
	    }
	  line_number++;

	  split_line = g_strsplit_set(current_line->str, " ", -1);

	  for(k = 0; split_line[k] != NULL; k++)
	    {
	      if(g_ascii_strcasecmp(split_line[k], "") == 0) 
		continue;	       
	      if (poly.nvertices == 0)
		{
		  if(sscanf(split_line[k], "%u", &poly.nvertices) != 1 ||
                     poly.nvertices < 3)
		    {
		      *error = g_error_new(VISU_ERROR_ISOSURFACES,
					   VISU_ERROR_FORMAT,
					   _("Line %dmust begin by an int."),
					   line_number);
		      g_string_free(current_line, TRUE);
		      g_io_channel_unref(surf_file);
		      g_object_unref(box);
                      g_free(label);
                      g_array_unref(polys);
		      return TRUE;
		    }
		  npolys_loaded++;
		  continue;
		}
	      res = sscanf(split_line[k], "%u", poly.indices + nvertex_i);
	      if(res != 1)
		{
		  *error = g_error_new(VISU_ERROR_ISOSURFACES,
				       VISU_ERROR_FORMAT,
				       _("Line %d doesn't match the [int > 3, ...]"
					 " required pattern."), line_number);
		  g_string_free(current_line, TRUE);
		  g_io_channel_unref(surf_file);
		  g_object_unref(box);
                  g_free(label);
                  g_array_unref(polys);
                  return TRUE;
		}
              poly.indices[nvertex_i] -= 1;
	      nvertex_i += 1;
	      if (nvertex_i >= poly.nvertices)
		break;
	    }
	  g_strfreev(split_line);

          g_array_append_val(polys, poly);
	}
 
      points = g_array_sized_new(FALSE, FALSE, sizeof(VisuSurfacePoint), surf_npoints);
      for(j = 0; j < surf_npoints; j++) 
	{
          VisuSurfacePoint point;

	  *error = (GError*)0;
	  io_status = g_io_channel_read_line_string(surf_file, current_line, NULL, error);
	  if(io_status != G_IO_STATUS_NORMAL)
	    {
	      g_string_free(current_line, TRUE);
	      g_io_channel_unref(surf_file);
	      g_object_unref(box);
              g_free(label);
              g_array_unref(polys);
              g_array_unref(points);
	      return TRUE;
	    }
	  line_number++;

	  if(sscanf(current_line->str, "%lf %lf %lf %lf %lf %lf", 
		    point.at, point.at + 1, point.at + 2,
		    point.normal, point.normal + 1, point.normal + 2) != 6) 
	    {
	      *error = g_error_new(VISU_ERROR_ISOSURFACES,
				   VISU_ERROR_FORMAT,
				   _("Line %d doesn't match the [float x 6]"
				     " required pattern."), line_number);
	      g_string_free(current_line, TRUE);
	      g_io_channel_unref(surf_file);
	      g_object_unref(box);
              g_free(label);
              g_array_unref(polys);
              g_array_unref(points);
	      return TRUE;
	    }
	  npoints_loaded++;
          g_array_append_val(points, point);
	}

      surface = visu_surface_new(label, points, polys);
      visu_surface_setBox(VISU_BOXED(surface), box);
      if (densityValue != G_MAXFLOAT)
        {
          /* Create a table to store the density values. */
          densityData = visu_surface_addPropertyFloat(surface, VISU_SURFACE_PROPERTY_POTENTIAL);
          densityData[0] = densityValue;
        }
      g_free(label);
      g_array_unref(points);
      g_array_unref(polys);

      *surf = g_list_append(*surf, surface);
    }
  g_object_unref(box);

  g_string_free(current_line, TRUE);
  g_io_channel_unref(surf_file);

  return TRUE;
}  

static void onMasking(VisuPlaneSet *mask, VisuSurface *data)
{
  gboolean redraw;

  DBG_fprintf(stderr, "Isosurfaces: compute masked polygons"
	      " for surface %p.\n", (gpointer)data);
  if (mask && !visu_plane_set_getHiddingStatus(mask))
    return;

  /* Free previous volatile points. */
  visu_surface_points_free(&data->priv->volatilePlanes);
  redraw = visu_surface_points_hide(&data->priv->basePoints,
                                    (const VisuSurfaceResource*)data->priv->resource,
                                    &data->priv->volatilePlanes, mask);
  
#if DEBUG == 1
  visu_surface_checkConsistency(data);
#endif

  if (redraw)
    g_signal_emit(G_OBJECT(data), surfaces_signals[SURFACE_MASKED_SIGNAL], 0, NULL);
}
static void onMaskable(VisuSurfaceResource *res _U_, GParamSpec *param _U_,
                       VisuSurface *data)
{
  if (data->priv->mask)
    onMasking(data->priv->mask, data);
}

/**
 * visu_surface_getResource:
 * @surf: the surface object ;
 * 
 * This returns the resource of the @surf.
 *
 * Since: 3.7
 *
 * Returns: (transfer none): the resource of the surface or NULL, if
 * @i is invalid.
 */
VisuSurfaceResource* visu_surface_getResource(VisuSurface *surf)
{
  g_return_val_if_fail(VISU_IS_SURFACE(surf), (VisuSurfaceResource*)0);

  return surf->priv->resource;
}
static gboolean _setResource(VisuSurface *surf, VisuSurfaceResource *res)
{
  g_return_val_if_fail(VISU_IS_SURFACE(surf), FALSE);

  if (surf->priv->resource == res)
    return FALSE;

  if (surf->priv->resource)
    {
      g_signal_handler_disconnect(G_OBJECT(surf->priv->resource),
                                  surf->priv->maskable_signal);
      g_object_unref(G_OBJECT(surf->priv->resource));
    }
  DBG_fprintf(stderr, "VisuSurface: set resource %p (%s) to surface.\n",
	      (gpointer)res, (res) ? visu_surface_resource_getLabel(res) : NULL);
  surf->priv->resource = res;
  if (surf->priv->resource)
    {
      g_object_ref(surf->priv->resource);
      surf->priv->maskable_signal =
        g_signal_connect(G_OBJECT(surf->priv->resource), "notify::maskable",
                         G_CALLBACK(onMaskable), (gpointer)surf);
    }
  return TRUE;
}
/**
 * visu_surface_setResource:
 * @surf: the surface object ;
 * @res: the new resource.
 * 
 * This method is used to change the resource of a surface.
 *
 * Returns: TRUE if the resource is changed.
 */
gboolean visu_surface_setResource(VisuSurface *surf, VisuSurfaceResource *res)
{

  g_return_val_if_fail(res, FALSE);

  if (_setResource(surf, res))
    {
      g_object_notify_by_pspec(G_OBJECT(surf), properties[RESOURCE_PROP]);
      return TRUE;
    }

  return FALSE;
}
/**
 * visu_surface_setMask:
 * @surface: a #VisuSurface object.
 * @mask: (allow-none): a #VisuPlaneSet object.
 *
 * Use @mask to hide portion of @surface (when maskable, depending on
 * their resources). Set @mask to %NULL to remove the mask.
 *
 * Since: 3.8
 *
 * Returns: TRUE if mask is changed.
 **/
gboolean visu_surface_setMask(VisuSurface *surface, VisuPlaneSet *mask)
{
  g_return_val_if_fail(VISU_IS_SURFACE(surface), FALSE);

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

  if (surface->priv->mask)
    {
      g_signal_handler_disconnect(G_OBJECT(surface->priv->mask),
                                  surface->priv->masking_signal);
      g_object_unref(surface->priv->mask);
    }
  surface->priv->mask = mask;
  if (surface->priv->mask)
    {
      g_object_ref(surface->priv->mask);
      surface->priv->masking_signal =
        g_signal_connect(G_OBJECT(surface->priv->mask), "masking-dirty",
                         G_CALLBACK(onMasking), (gpointer)surface);
    }
  /* Apply (or remove) masking immediately. */
  onMasking(surface->priv->mask, surface);

  return TRUE;
}

static double z_eye(const float mat[16], const float points[3])
{
  return
    (mat[ 2]*points[0]+
     mat[ 6]*points[1]+
     mat[10]*points[2]+
     mat[14]*1.)/
    (mat[ 3]*points[0]+
     mat[ 7]*points[1]+
     mat[11]*points[2]+
     mat[15]*1.);
}

static gboolean _nextPoints(VisuSurfaceIterPoly *iter)
{
  g_return_val_if_fail(iter && iter->surf, FALSE);

  if (!iter->valid && iter->points == &iter->surf->priv->basePoints)
    {
      iter->points = &iter->surf->priv->volatilePlanes;
      iter->i = 0;
      iter->valid = (iter->i < iter->points->num_polys);
      return !iter->valid;
    }
  return FALSE;
}
/**
 * visu_surface_iter_poly_new:
 * @surf: a #VisuSurface object.
 * @iter: (out caller-allocates): a #VisuSurfaceIterPoly structure.
 *
 * Setup a new @iter to iterate on drawn polygons of @surf.
 *
 * Since: 3.8
 **/
void visu_surface_iter_poly_new(VisuSurface *surf,
                               VisuSurfaceIterPoly *iter)
{
  g_return_if_fail(iter);

  iter->valid = FALSE;
  iter->surf = (VisuSurface*)0;
  iter->points = (VisuSurfacePoints*)0;

  g_return_if_fail(VISU_IS_SURFACE(surf));
  iter->surf = surf;
  iter->points = &surf->priv->basePoints;
  iter->i = 0;
  iter->valid = (iter->i < iter->points->num_polys);
  while (_nextPoints(iter));
}
/**
 * visu_surface_iter_poly_next:
 * @iter: a #VisuSurfaceIterPoly structure.
 *
 * Iterate to the next drawn polygon.
 *
 * Since: 3.8
 **/
void visu_surface_iter_poly_next(VisuSurfaceIterPoly *iter)
{
  g_return_if_fail(iter && iter->points);

  iter->i += 1;
  iter->valid = (iter->i < iter->points->num_polys);
  while (_nextPoints(iter));
}

/**
 * visu_surface_iter_poly_getZ:
 * @iter: a #VisuSurfaceIterPoly structure.
 * @z: (out): a location to store double.
 * @mat: a transformation matrix to go into viewport basis.
 *
 * Retrieve the @z value in the viewport basis of the drawn polygon
 * defined by @iter. If the polygon is not drawn, @z is not computed.
 *
 * Return: TRUE if polygon defined by @iter is drawn and z is computed
 *
 * Since: 3.8
 **/
gboolean visu_surface_iter_poly_getZ(const VisuSurfaceIterPoly *iter,
                                      double *z, const float mat[16])
{
  guint j;

  g_return_val_if_fail(iter && iter->points && z, FALSE);
  g_return_val_if_fail(iter->valid, FALSE);

  g_return_val_if_fail(iter->points->poly_surf_index[iter->i], FALSE);

  if (iter->points->poly_surf_index[iter->i] <= 0 ||
      !visu_surface_resource_getRendered(iter->surf->priv->resource))
    return FALSE;

  *z = 0.;
  for (j = 0; j < iter->points->poly_num_vertices[iter->i]; j++)
    *z += z_eye(mat, iter->points->poly_points_data[iter->points->poly_vertices[iter->i][j]]);
  *z /= (double)iter->points->poly_num_vertices[iter->i];

  return TRUE;
}
/**
 * visu_surface_iter_poly_getVertices:
 * @iter: a #VisuSurfaceIterPoly structure.
 * @vertices: (element-type VisuSurfacePoint): an array to store the vertex
 * coordinates and normals.
 *
 * Retrieve the position and normal of the point refered by @iter.
 *
 * Since: 3.8
 **/
void visu_surface_iter_poly_getVertices(const VisuSurfaceIterPoly *iter,
                                         GArray *vertices)
{
  guint j;
  guint *pvertices;
  VisuSurfacePoint vert;

  g_return_if_fail(iter && iter->points);
  g_return_if_fail(iter->valid);
  g_return_if_fail(iter->i < iter->points->num_polys);
  g_return_if_fail(vertices);

  pvertices = iter->points->poly_vertices[iter->i];

  g_array_set_size(vertices, 0);
  for (j = 0; j < iter->points->poly_num_vertices[iter->i]; j++ )
    {
      vert.at[0] = iter->points->poly_points_data[pvertices[j]][0];
      vert.at[1] = iter->points->poly_points_data[pvertices[j]][1];
      vert.at[2] = iter->points->poly_points_data[pvertices[j]][2];
      vert.normal[0] = iter->points->poly_points_data[pvertices[j]][0 + VISU_SURFACE_POINTS_OFFSET_NORMAL];
      vert.normal[1] = iter->points->poly_points_data[pvertices[j]][1 + VISU_SURFACE_POINTS_OFFSET_NORMAL];
      vert.normal[2] = iter->points->poly_points_data[pvertices[j]][2 + VISU_SURFACE_POINTS_OFFSET_NORMAL];
      g_array_append_val(vertices, vert);
    }
}
