/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD, Damien
	CALISTE, Olivier D'Astier, laboratoire L_Sim, (2001-2005)
  
	Adresses mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.
	D'ASTIER, dastier AT iie P cnam 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-2005)

	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 "map.h"
#include "isoline.h"

#include <math.h>
#include <GL/gl.h>
#include <glib.h>
#include <string.h>


#include <visu_configFile.h>

#ifdef HAVE_CAIRO
#include <cairo.h>
#include <cairo-svg.h>
#include <cairo-pdf.h>
#endif

/**
 * SECTION:map
 * @short_description: Describe how to handle and draw coloured map on
 * a plane from a density field.
 *
 * <para>A map is a coloured representation of a scalar field on a
 * plane. To define a new map, use visu_map_new_fromPlane() and use
 * visu_map_setField() to associate a scalarfield to it.</para>
 * <para>The representation of the map is done by an adaptive mesh of
 * triangles. One can adjust the size of the smaller resolution by
 * using visu_map_setLevel(). Finally the level of adaptiveness is
 * chosen at rendering time by choosing a more or less crude precision
 * argument to visu_map_draw().</para>
 * <para>In adition to the colour representation, isolines can be
 * drawn at given iso-values, see visu_map_setLines().</para>
 * <para>An additionnal capability allows to export #VisuMap into SVG
 * or PDF vector files.</para>
 *
 * Since: 3.6
 */

/**
 * VisuMapClass:
 * @parent: private.
 * @computePool: unused.
 * 
 * Common name to refer to a #_VisuMapClass.
 */

/**
 * Triangle :
 * @triangle_ABC :a location to store coordinates of tree vertexs
 * @value_scalarfield: a location to store the value of scalarfideld
 * @min_node : the minimum value
 * @max_node : the maximum value
 * @rgba: a color,can be NULL;
 * @flag:return TRUE if the triangle has the least one vertex is out of bounds,return FALSE ifnot
 *
 * The structure is used to store informations about coordinates,color,position of each vertex and
 * the maximum and minimum value of scalarfield
 */
typedef struct _Triangle
{
  float vertices[3][3];
  float minmax[2];
  float value[3];

  guint level;
  struct _Triangle *children[4];
} Triangle;

struct _VisuMapPrivate
{
  gboolean dispose_has_run;
  GMutex access;

  GMutex mutex;
  guint dirty;
  gboolean computing;

  /* Pointers to building objects. */
  VisuPlane *plane;
  glong moved_signal, boxed_signal;
  VisuSurface *surface;
  VisuScalarField *field;
  gulong changed_sig;

  /* Transformation values. */
  ToolMatrixScalingFlag scale;
  tool_matrix_getScaledValue get_val;
  tool_matrix_getScaledValue get_inv;
  GArray *scaleminmax;
  gboolean scaleauto;
  float extension[3];

  /* Triangle structure. */
  guint level;
  GList *triangles;

  /* The min/max valuess in [0;1] after colour transformation. */
  float minmax[2];
  /* The min/max values in scalarfield unit. */
  float valMinMax[2];

  /* Desired lines description. */
  guint nLines;
  float lineminmax[2];
  GArray *lines; /* Actual line storage. */
};

enum
  {
    CHANGED_SIGNAL,
    NB_SIGNAL
  };
static guint _signals[NB_SIGNAL] = { 0 };

enum
  {
    PROP_0,
    FIELD_PROP,
    PLANE_PROP,
    SURFACE_PROP,
    SCALE_PROP,
    MANUAL_MM_PROP,
    N_PROP
  };
static GParamSpec *properties[N_PROP];

#define FLAG_RESOURCE_LEG_SCALE "map_legendScale"
#define DESC_RESOURCE_LEG_SCALE "Choose the scale to draw the legend for coloured maps ; float (positive)"
#define DEFT_RESOURCE_LEG_SCALE 1.f

#define FLAG_RESOURCE_LEG_POS "map_legendPosition"
#define DESC_RESOURCE_LEG_POS "Choose the legend position ; two floats (between 0 and 1)"
#define DEFT_RESOURCE_LEG_POS 0.f

static float legendScale, legendPosition[2];

static void exportResources(GString *data, VisuData *dataObj);

static Triangle* triangle_new(float ABC[3][3], guint level);
static void triangle_free(Triangle *T);
static void triangle_getDataAtLevel(Triangle *T, guint level, float **data, guint *pos);
static void triangle_setValues(Triangle *T, VisuScalarField *field,
                               tool_matrix_getScaledValue get_val, float minmax[2],
                               float extension[3]);
static void triangle_draw(Triangle *T, float thresh, const ToolShade *shade);
static void triangle_drawWithAlpha(Triangle *T, float thresh, const ToolShade *shade);
#ifdef HAVE_CAIRO
static void triangle_drawToCairo(Triangle *T, cairo_t *cr, float thresh,
                                 const ToolShade *shade, float basis[2][3], float center[3]);
#endif

static gboolean _compute(VisuMap *map);
static gboolean _computeLines(VisuMap *map, guint nIsoLines, float minMax[2]);

static void onPlaneMoved(VisuPlane *plane, gpointer data);
static void onPlaneBoxed(VisuPlane *plane, VisuBox *box, gpointer data);
static void onChanged(VisuScalarField *field, VisuMap *map);

#define ROOT_LEVEL 0
static Triangle* triangle_new(float ABC[3][3], guint level)
{
  Triangle *T;

  T = g_malloc(sizeof(Triangle));
  T->children[0] = (Triangle*)0;
  T->children[1] = (Triangle*)0;
  T->children[2] = (Triangle*)0;
  T->children[3] = (Triangle*)0;
  T->minmax[0] = G_MAXFLOAT;
  T->minmax[1] = -G_MAXFLOAT;
  T->level = level;
  memcpy(T->vertices, ABC, sizeof(float) * 9);
  /* DBG_fprintf(stderr, "VisuScalarField: create Triangle %p (%d).\n", (gpointer)T, level); */

  return T;
}
static Triangle* triangle_newFromVertices(GArray *vertices, guint level)
{
  Triangle *T;
  VisuSurfacePoint *pt;

  g_return_val_if_fail(vertices->len == 3, (Triangle*)0);

  T = g_malloc(sizeof(Triangle));
  T->children[0] = (Triangle*)0;
  T->children[1] = (Triangle*)0;
  T->children[2] = (Triangle*)0;
  T->children[3] = (Triangle*)0;
  T->minmax[0] = G_MAXFLOAT;
  T->minmax[1] = -G_MAXFLOAT;
  T->level = level;
  pt = &g_array_index(vertices, VisuSurfacePoint, 0);
  T->vertices[0][0] = pt->at[0];
  T->vertices[0][1] = pt->at[1];
  T->vertices[0][2] = pt->at[2];
  pt = &g_array_index(vertices, VisuSurfacePoint, 1);
  T->vertices[1][0] = pt->at[0];
  T->vertices[1][1] = pt->at[1];
  T->vertices[1][2] = pt->at[2];
  pt = &g_array_index(vertices, VisuSurfacePoint, 2);
  T->vertices[2][0] = pt->at[0];
  T->vertices[2][1] = pt->at[1];
  T->vertices[2][2] = pt->at[2];
  /* DBG_fprintf(stderr, "VisuScalarField: create Triangle %p (%d).\n", (gpointer)T, level); */

  return T;
}
static void triangle_free(Triangle *T)
{
  if (T->children[0])
    triangle_free(T->children[0]);
  if (T->children[1])
    triangle_free(T->children[1]);
  if (T->children[2])
    triangle_free(T->children[2]);
  if (T->children[3])
    triangle_free(T->children[3]);
  g_free(T);
}                              

static void triangle_getDataAtLevel(Triangle *T, guint level, float **data, guint *pos)
{
  if (T->level == level)
    {
      (data + (*pos))[0] = (float*)T->vertices;
      (data + (*pos))[1] = (float*)T->value;
      *pos += 2;
      return;
    }
  if (T->children[0])
    triangle_getDataAtLevel(T->children[0], level, data, pos);
  if (T->children[1])
    triangle_getDataAtLevel(T->children[1], level, data, pos);
  if (T->children[2])
    triangle_getDataAtLevel(T->children[2], level, data, pos);
  if (T->children[3])
    triangle_getDataAtLevel(T->children[3], level, data, pos);
}

static void _3DToVisuPlane(float uv[2], float xyz[3],
                       float basis[2][3], float origin[3])
{
  uv[0] = basis[0][0] * (xyz[0] - origin[0]) + basis[0][1] * (xyz[1] - origin[1]) +  basis[0][2] * (xyz[2] - origin[2]);
  uv[1] = basis[1][0] * (xyz[0] - origin[0]) + basis[1][1] * (xyz[1] - origin[1]) +  basis[1][2] * (xyz[2] - origin[2]);
}
static void triangle_setValues(Triangle *T, VisuScalarField *field,
                               tool_matrix_getScaledValue get_val, float minmax[2],
                               float extension[3])
{
  double value;
  int i;

  g_return_if_fail(T);

  T->minmax[0] =  G_MAXFLOAT;
  T->minmax[1] = -G_MAXFLOAT;
  if (field)
    for (i = 0; i < 3; i++)
      {
        if (!visu_scalar_field_getValue(field, T->vertices[i], &value, extension))
          if (!visu_scalar_field_getValue(field, T->vertices[(i + 1) % 3], &value, extension))
            visu_scalar_field_getValue(field, T->vertices[(i + 2) % 3], &value, extension);
        T->value[i]  = (float)get_val(value, minmax);
        T->minmax[0] = MIN(T->minmax[0], T->value[i]);
        T->minmax[1] = MAX(T->minmax[1], T->value[i]);
      }
}

static void triangle_draw(Triangle *T, float thresh, const ToolShade *shade)
{
  /* gchar str[32]; */
  float rgba[4];

  if ((T->minmax[1] - T->minmax[0]) <= thresh ||
      (!T->children[0] && !T->children[1] && !T->children[2] && !T->children[3]))
    {
      glBegin(GL_TRIANGLE_STRIP);
      tool_shade_valueToRGB(shade, rgba, T->value[0]);
      glColor3fv(rgba);
      glVertex3fv(T->vertices[0]);
      tool_shade_valueToRGB(shade, rgba, T->value[1]);
      glColor3fv(rgba);
      glVertex3fv(T->vertices[1]);
      tool_shade_valueToRGB(shade, rgba, T->value[2]);
      glColor3fv(rgba);
      glVertex3fv(T->vertices[2]);
      glEnd();
      /* sprintf(str, "%6.5f", T->value[0]); */
      /* glColor3fv(T->rgba[0]); */
      /* glRasterPos3fv(T->vertices[0]); visu_gl_text_drawChars(str, VISU_GL_TEXT_SMALL); */
      /* sprintf(str, "%6.5f", T->value[1]); */
      /* glColor3fv(T->rgba[1]); */
      /* glRasterPos3fv(T->vertices[1]); visu_gl_text_drawChars(str, VISU_GL_TEXT_SMALL); */
      /* sprintf(str, "%6.5f", T->value[2]); */
      /* glColor3fv(T->rgba[2]); */
      /* glRasterPos3fv(T->vertices[2]); visu_gl_text_drawChars(str, VISU_GL_TEXT_SMALL); */
      return;
    }

  if (T->children[0])
    triangle_draw(T->children[0], thresh, shade);
  if (T->children[1])
    triangle_draw(T->children[1], thresh, shade);
  if (T->children[2])
    triangle_draw(T->children[2], thresh, shade);
  if (T->children[3])
    triangle_draw(T->children[3], thresh, shade);
}
static void triangle_drawWithAlpha(Triangle *T, float thresh, const ToolShade *shade)
{
  /* gchar str[32]; */
  float rgba[4];

  if ((T->minmax[1] - T->minmax[0]) <= thresh ||
      (!T->children[0] && !T->children[1] && !T->children[2] && !T->children[3]))
    {
      glBegin(GL_TRIANGLE_STRIP);
      tool_shade_valueToRGB(shade, rgba, T->value[0]);
      rgba[3] = T->value[0];
      glColor4fv(rgba);
      glVertex3fv(T->vertices[0]);
      tool_shade_valueToRGB(shade, rgba, T->value[1]);
      rgba[3] = T->value[1];
      glColor4fv(rgba);
      glVertex3fv(T->vertices[1]);
      tool_shade_valueToRGB(shade, rgba, T->value[2]);
      rgba[3] = T->value[2];
      glColor4fv(rgba);
      glVertex3fv(T->vertices[2]);
      glEnd();
      return;
    }

  if (T->children[0])
    triangle_drawWithAlpha(T->children[0], thresh, shade);
  if (T->children[1])
    triangle_drawWithAlpha(T->children[1], thresh, shade);
  if (T->children[2])
    triangle_drawWithAlpha(T->children[2], thresh, shade);
  if (T->children[3])
    triangle_drawWithAlpha(T->children[3], thresh, shade);
}
#ifdef HAVE_CAIRO
static void triangle_drawToCairo(Triangle *T, cairo_t *cr, float thresh,
                                 const ToolShade *shade, float basis[2][3], float center[3])
{
  float uv[2];
  float v, rgba[4];

  if ((T->minmax[1] - T->minmax[0]) <= thresh ||
      (!T->children[0] && !T->children[1] && !T->children[2] && !T->children[3]))
    {
      v = (T->value[0] + T->value[1] + T->value[2]) / 3.f;
      tool_shade_valueToRGB(shade, rgba, v);
      cairo_set_source_rgba(cr, rgba[0], rgba[1], rgba[2], 1.f);
      _3DToVisuPlane(uv, T->vertices[0], basis, center);
      cairo_move_to(cr, uv[0], uv[1]);
      _3DToVisuPlane(uv, T->vertices[1], basis, center);
      cairo_line_to(cr, uv[0], uv[1]);
      _3DToVisuPlane(uv, T->vertices[2], basis, center);
      cairo_line_to(cr, uv[0], uv[1]);
      _3DToVisuPlane(uv, T->vertices[0], basis, center);
      cairo_line_to(cr, uv[0], uv[1]);
      cairo_fill_preserve(cr);
      /* cairo_set_source_rgb(cr, 0., 0., 0.); */
      cairo_stroke(cr);
      return;
    }

  if (T->children[0])
    triangle_drawToCairo(T->children[0], cr, thresh, shade, basis, center);
  if (T->children[1])
    triangle_drawToCairo(T->children[1], cr, thresh, shade, basis, center);
  if (T->children[2])
    triangle_drawToCairo(T->children[2], cr, thresh, shade, basis, center);
  if (T->children[3])
    triangle_drawToCairo(T->children[3], cr, thresh, shade, basis, center);
}
#endif


static void visu_map_dispose     (GObject* obj);
static void visu_map_finalize    (GObject* obj);
static void visu_map_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);
static void visu_map_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE(VisuMap, visu_map, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuMap))

/**************************/
/* The Map object itself. */
/**************************/
static void visu_map_class_init(VisuMapClass *klass)
{
  float rgWidth[2] = {0.01f, 10.f};
  float rgPos[2] = {0.f, 1.f};
  VisuConfigFileEntry *conf;

  DBG_fprintf(stderr, "Visu Map: creating the class of the object.\n");
  DBG_fprintf(stderr, "                - adding new signals ;\n");

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_map_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_map_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_map_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_map_get_property;
  klass->computePool = (GThreadPool*)0;

  /**
   * VisuMap::changed:
   * @map: the object emitting the signal.
   *
   * This signal is emitted each time the map has been changed and recomputed.
   *
   * Since: 3.8
   */
  _signals[CHANGED_SIGNAL] =
    g_signal_new("changed", 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);
  
  /**
   * VisuMap::field:
   *
   * Field with the values used to colour the map.
   *
   * Since: 3.8
   */
  properties[FIELD_PROP] = g_param_spec_object("field", "Field",
                                               "projection field",
                                               VISU_TYPE_SCALAR_FIELD,
                                               G_PARAM_READWRITE |
                                               G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), FIELD_PROP, properties[FIELD_PROP]);

  /**
   * VisuMap::plane:
   *
   * Projection plane for the coloured map.
   *
   * Since: 3.8
   */
  properties[PLANE_PROP] = g_param_spec_object("plane", "Plane",
                                               "projection plane",
                                               VISU_TYPE_PLANE, G_PARAM_READWRITE |
                                               G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), PLANE_PROP, properties[PLANE_PROP]);

  /**
   * VisuMap::surface:
   *
   * Projection surface for the coloured map.
   *
   * Since: 3.8
   */
  properties[SURFACE_PROP] = g_param_spec_object("surface", "Surface",
                                                 "projection surface",
                                                 VISU_TYPE_SURFACE, G_PARAM_READWRITE |
                                                 G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), SURFACE_PROP, properties[SURFACE_PROP]);

  /**
   * VisuMap::scale:
   *
   * Scaling method to transform the field values into [0;1].
   *
   * Since: 3.8
   */
  properties[SCALE_PROP] = g_param_spec_uint("scale", "Scale",
                                             "scaling scheme of input values",
                                             0, TOOL_MATRIX_SCALING_N_VALUES - 1,
                                             TOOL_MATRIX_SCALING_LINEAR,
                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), SCALE_PROP, properties[SCALE_PROP]);

  /**
   * VisuMap::range-min-max:
   *
   * Min / max range as used to normalise data. Default are the data
   * min / max themselves.
   *
   * Since: 3.8
   */
  properties[MANUAL_MM_PROP] = g_param_spec_boxed("range-min-max", "Range min/max",
                                                  "min / max range to normalise data",
                                                  G_TYPE_ARRAY,
                                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), MANUAL_MM_PROP, properties[MANUAL_MM_PROP]);

  DBG_fprintf(stderr, "                - set the conf entries.\n");
  conf = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                             FLAG_RESOURCE_LEG_SCALE,
                                             DESC_RESOURCE_LEG_SCALE,
                                             1, &legendScale, rgWidth, FALSE);
  visu_config_file_entry_setVersion(conf, 3.7f);
  conf = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                             FLAG_RESOURCE_LEG_POS,
                                             DESC_RESOURCE_LEG_POS,
                                             2, legendPosition, rgPos, FALSE);
  visu_config_file_entry_setVersion(conf, 3.7f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     exportResources);

  legendScale = DEFT_RESOURCE_LEG_SCALE;
  legendPosition[0] = DEFT_RESOURCE_LEG_POS;
  legendPosition[1] = DEFT_RESOURCE_LEG_POS;
}

static void visu_map_init(VisuMap *obj)
{
  DBG_fprintf(stderr, "Visu Map: initializing a new object (%p).\n",
	      (gpointer)obj);
  obj->priv = visu_map_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;
  g_mutex_init(&obj->priv->access);

  obj->priv->dirty           = 0;
  obj->priv->computing       = FALSE;
  g_mutex_init(&obj->priv->mutex);

  obj->priv->plane        = (VisuPlane*)0;
  obj->priv->moved_signal = 0;
  obj->priv->boxed_signal = 0;
  obj->priv->surface      = (VisuSurface*)0;
  obj->priv->field        = (VisuScalarField*)0;
  obj->priv->changed_sig  = 0;

  obj->priv->scale       = TOOL_MATRIX_SCALING_LINEAR;
  obj->priv->get_val     = tool_matrix_getScaledLinear;
  obj->priv->get_inv     = tool_matrix_getScaledLinearInv;
  obj->priv->scaleminmax = g_array_sized_new(FALSE, FALSE, sizeof(float), 2);
  obj->priv->scaleauto   = TRUE;

  obj->priv->level     = 0;
  obj->priv->minmax[0] =  G_MAXFLOAT;
  obj->priv->minmax[1] = -G_MAXFLOAT;
  obj->priv->triangles = (GList*)0;

  obj->priv->nLines    = 0;
  obj->priv->lines     = g_array_new(FALSE, FALSE, sizeof(VisuLine*));
  g_array_set_clear_func(obj->priv->lines, (GDestroyNotify)visu_line_unref);
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_map_dispose(GObject* obj)
{
  VisuMap *map;

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

  map = VISU_MAP(obj);
  if (map->priv->dispose_has_run)
    return;
  map->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  visu_map_setPlane(map, (VisuPlane*)0);
  visu_map_setField(map, (VisuScalarField*)0);
  
  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_map_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_map_finalize(GObject* obj)
{
  VisuMap *map;

  g_return_if_fail(obj);

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

  map = VISU_MAP(obj);
  g_array_free(map->priv->scaleminmax, TRUE);
  g_array_free(map->priv->lines, TRUE);

  g_mutex_lock(&map->priv->mutex);
  g_mutex_unlock(&map->priv->mutex);
  g_mutex_clear(&map->priv->mutex);

  g_mutex_lock(&map->priv->access);
  g_mutex_unlock(&map->priv->access);
  g_mutex_clear(&map->priv->access);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu Map: chain to parent.\n");
  G_OBJECT_CLASS(visu_map_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu Map: freeing ... OK.\n");
}
static void visu_map_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec)
{
  VisuMapPrivate *self = VISU_MAP(obj)->priv;

  DBG_fprintf(stderr, "Visu Map: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case FIELD_PROP:
      g_value_set_object(value, self->field);
      DBG_fprintf(stderr, "%p\n", (gpointer)self->field);
      break;
    case PLANE_PROP:
      g_value_set_object(value, self->plane);
      DBG_fprintf(stderr, "%p\n", (gpointer)self->plane);
      break;
    case SURFACE_PROP:
      g_value_set_object(value, self->surface);
      DBG_fprintf(stderr, "%p\n", (gpointer)self->surface);
      break;
    case SCALE_PROP:
      g_value_set_uint(value, self->scale);
      DBG_fprintf(stderr, "%d\n", self->scale);
      break;
    case MANUAL_MM_PROP:
      g_value_set_boxed(value, self->scaleminmax);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->scaleminmax);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_map_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec)
{
  GArray *arr;

  DBG_fprintf(stderr, "Visu Map: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case FIELD_PROP:
      DBG_fprintf(stderr, "%p\n", (gpointer)g_value_get_object(value));
      visu_map_setField(VISU_MAP(obj), VISU_SCALAR_FIELD(g_value_get_object(value)));
      break;
    case PLANE_PROP:
      DBG_fprintf(stderr, "%p\n", (gpointer)g_value_get_object(value));
      visu_map_setPlane(VISU_MAP(obj), VISU_PLANE(g_value_get_object(value)));
      break;
    case SCALE_PROP:
      DBG_fprintf(stderr, "%d\n", g_value_get_uint(value));
      visu_map_setScaling(VISU_MAP(obj), g_value_get_uint(value));
      break;
    case MANUAL_MM_PROP:
      arr = (GArray*)g_value_get_boxed(value);
      g_return_if_fail(arr && arr->len == 2);
      visu_map_setScalingRange(VISU_MAP(obj), (arr) ? (float*)arr->data : (float*)0);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
/**
 * visu_map_new:
 *
 * Creates a new #VisuMap object.
 *
 * Since: 3.6
 *
 * Returns: (transfer full): a newly created #VisuMap object.
 */
VisuMap* visu_map_new()
{
  VisuMap *map;

  map = VISU_MAP(g_object_new(VISU_TYPE_MAP, NULL));
  return map;
}
/**
 * visu_map_new_fromPlane:
 * @plane: a #VisuPlane object.
 *
 * Creates a new #VisuMap object, projected on @plane.
 *
 * Since: 3.6
 *
 * Returns: a newly created #VisuMap object.
 */
VisuMap* visu_map_new_fromPlane(VisuPlane *plane)
{
  VisuMap *map;

  map = VISU_MAP(g_object_new(VISU_TYPE_MAP, "plane", plane, NULL));
  return map;
}

static void _setDirty(VisuMap *map)
{
  DBG_fprintf(stderr, "Visu Map: set dirty (%d).\n", map->priv->dirty);
  if (map->priv->dirty)
    return;

  map->priv->dirty = g_idle_add((GSourceFunc)_compute, map);
  DBG_fprintf(stderr, "Visu Map (%p): create dirty idle %d.\n",
              (gpointer)g_thread_self(), map->priv->dirty);
}
static float** _getDataAtLevel(GList *triangles, guint level, guint *n)
{
  float **data;
  guint i, m;
  GList *lst;

  g_return_val_if_fail(level < 13, (float**)0);

  m = pow(4, level) * g_list_length(triangles);
  if (!m)
    return (float**)0;
  data = g_malloc(sizeof(float*) * m * 2);
  if (n)
    *n = m;

  DBG_fprintf(stderr, "Visu Map: generate the %d triangle data for %d triangles.\n",
              g_list_length(triangles), m);
  i = 0;
  for (lst = triangles; lst; lst = g_list_next(lst))
    triangle_getDataAtLevel((Triangle*)lst->data, level, data, &i);

  return data;
}
static void _freeLines(VisuMap *map)
{
  DBG_fprintf(stderr, "Map: free old lines (%d).\n", map->priv->lines->len);
  g_array_set_size(map->priv->lines, 0);
}
static void _freeTriangles(VisuMap *map)
{
  g_list_free_full(map->priv->triangles, (GDestroyNotify)triangle_free);
  map->priv->triangles = (GList*)0;
  _freeLines(map);
}
static void _setTriangles(VisuMap *map, VisuPlane *plane)
{
  GList *inter, *lst;
  float xyz[2][3], ABC[3][3];
  guint i;

  g_mutex_lock(&map->priv->access);
  /* Free currently allocated triangles. */
  _freeTriangles(map);
  
  inter = (plane && (visu_boxed_getBox(VISU_BOXED(plane)) != (VisuBox*)0)) ?
    visu_plane_getIntersection(plane) : (GList*)0;
  if (inter)
    {
      /* Reallocate everyone. */
      visu_plane_getBasis(plane, xyz, ABC[0]);
      i = 1;
      ABC[i][0] = ((float*)inter->data)[0];
      ABC[i][1] = ((float*)inter->data)[1];
      ABC[i][2] = ((float*)inter->data)[2];
      for (lst = g_list_next(inter); lst; lst = g_list_next(lst))
        {
          i = i % 2 + 1;
          ABC[i][0] = ((float*)lst->data)[0];
          ABC[i][1] = ((float*)lst->data)[1];
          ABC[i][2] = ((float*)lst->data)[2];
          map->priv->triangles = g_list_append(map->priv->triangles, triangle_new(ABC, ROOT_LEVEL));
        }
      i = i % 2 + 1;
      ABC[i][0] = ((float*)inter->data)[0];
      ABC[i][1] = ((float*)inter->data)[1];
      ABC[i][2] = ((float*)inter->data)[2];
      map->priv->triangles = g_list_append(map->priv->triangles, triangle_new(ABC, ROOT_LEVEL));
    }
  g_mutex_unlock(&map->priv->access);
}
/**
 * visu_map_setPlane:
 * @map: a #VisuMap object.
 * @plane: (transfer full) (allow-none): a #VisuPlane object.
 *
 * Set the internal #VisuPlane of @map to @plane. All changed to
 * @plane will be automatically propagated to @map. Use
 * visu_map_setField() to choose the field to take values from, set
 * the level precision with visu_map_setLevel().
 *
 * Since: 3.7
 *
 * Returns: TRUE if internal plane is changed.
 **/
gboolean visu_map_setPlane(VisuMap *map, VisuPlane *plane)
{
  
  g_return_val_if_fail(map, FALSE);

  if (map->priv->plane == plane)
    return FALSE;

  /* Free previous handle on plane. */
  if (map->priv->plane)
    {
      g_signal_handler_disconnect(G_OBJECT(map->priv->plane), map->priv->moved_signal);
      g_signal_handler_disconnect(G_OBJECT(map->priv->plane), map->priv->boxed_signal);
      g_object_unref(map->priv->plane);
    }
  
  map->priv->plane = plane;
  if (plane)
    {
      g_object_ref(plane);
      map->priv->moved_signal = g_signal_connect(G_OBJECT(plane), "moved",
                                           G_CALLBACK(onPlaneMoved), (gpointer)map);
      map->priv->boxed_signal = g_signal_connect(G_OBJECT(plane), "setBox",
                                           G_CALLBACK(onPlaneBoxed), (gpointer)map);
      if (visu_boxed_getBox(VISU_BOXED(plane)) != (VisuBox*)0)
        visu_box_getExtension(visu_boxed_getBox(VISU_BOXED(map->priv->plane)), map->priv->extension);
    }
  _setTriangles(map, plane);
  _setDirty(map);

  return TRUE;
}
static void _setTrianglesFromSurface(VisuMap *map, VisuSurface *surface)
{
  VisuSurfaceIterPoly iter;
  GArray *vertices;

  g_mutex_lock(&map->priv->access);
  /* Free currently allocated triangles. */
  _freeTriangles(map);

  vertices = g_array_sized_new(FALSE, FALSE, sizeof(VisuSurfacePoint), 3);
  for (visu_surface_iter_poly_new(surface, &iter); iter.valid;
       visu_surface_iter_poly_next(&iter))
    {
      visu_surface_iter_poly_getVertices(&iter, vertices);
      if (vertices->len == 3)
        map->priv->triangles =
          g_list_append(map->priv->triangles,
                        triangle_newFromVertices(vertices, ROOT_LEVEL));
    }
  g_mutex_unlock(&map->priv->access);
}
/**
 * visu_map_setSurface:
 * @map: a #VisuMap object.
 * @surface: (transfer full) (allow-none): a #VisuSurface object.
 *
 * Set the internal #VisuSurface of @map to @surface. All changed to
 * @surface will be automatically propagated to @map. Use
 * visu_map_setField() to choose the field to take values from, set
 * the level precision with visu_map_setLevel().
 *
 * Since: 3.8
 *
 * Returns: TRUE if internal surface is changed.
 **/
gboolean visu_map_setSurface(VisuMap *map, VisuSurface *surface)
{
  
  g_return_val_if_fail(VISU_IS_MAP(map), FALSE);

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

  /* Free previous handle on surface. */
  if (map->priv->surface)
    {
      /* g_signal_handler_disconnect(G_OBJECT(map->priv->surface), map->priv->moved_signal); */
      /* g_signal_handler_disconnect(G_OBJECT(map->priv->surface), map->priv->boxed_signal); */
      g_object_unref(map->priv->surface);
    }
  
  map->priv->surface = surface;
  if (surface)
    {
      g_object_ref(surface);
      /* map->priv->moved_signal = g_signal_connect(G_OBJECT(surface), "moved", */
      /*                                      G_CALLBACK(onSurfaceMoved), (gpointer)map); */
      /* map->priv->boxed_signal = g_signal_connect(G_OBJECT(surface), "setBox", */
      /*                                      G_CALLBACK(onSurfaceBoxed), (gpointer)map); */
      if (visu_boxed_getBox(VISU_BOXED(surface)) != (VisuBox*)0)
        visu_box_getExtension(visu_boxed_getBox(VISU_BOXED(map->priv->surface)), map->priv->extension);
    }
  _setTrianglesFromSurface(map, surface);
  _setDirty(map);

  return TRUE;
}
/**
 * visu_map_setField:
 * @map: a #VisuMap object.
 * @field: a #VisuScalarField object.
 *
 * It associates the values of @field to @map.
 *
 * Since: 3.6
 *
 * Returns: TRUE if @field is changed.
 */
gboolean visu_map_setField(VisuMap *map, VisuScalarField *field)
{
  double minMax_[2];

  g_return_val_if_fail(map, FALSE);

  if (map->priv->field)
    {
      g_signal_handler_disconnect(G_OBJECT(map->priv->field), map->priv->changed_sig);
      g_object_unref(G_OBJECT(map->priv->field));
    }

  map->priv->field = field;
  map->priv->minmax[0] =  G_MAXFLOAT;
  map->priv->minmax[1] = -G_MAXFLOAT;

  if (field)
    {
      g_object_ref(G_OBJECT(field));
      map->priv->changed_sig =
        g_signal_connect(G_OBJECT(field), "changed",
                         G_CALLBACK(onChanged), (gpointer)map);
      DBG_fprintf(stderr, "Visu Map: attach 'changed' signal from %p.\n",
                  (gpointer)field);

      if (map->priv->scaleauto)
        {
          visu_scalar_field_getMinMax(map->priv->field, minMax_);
          g_array_index(map->priv->scaleminmax, float, 0) = minMax_[0];
          g_array_index(map->priv->scaleminmax, float, 1) = minMax_[1];
          g_object_notify_by_pspec(G_OBJECT(map), properties[MANUAL_MM_PROP]);
        }
    }

  _freeTriangles(map);
  if (map->priv->plane)
    _setTriangles(map, map->priv->plane);
  if (map->priv->surface)
    _setTrianglesFromSurface(map, map->priv->surface);
  _setDirty(map);

  return TRUE;
}
/**
 * visu_map_setScaling:
 * @map: a #VisuMap object.
 * @scale: a flag.
 *
 * The scaling algorithm to transform input values into [0;1] is
 * defined by @scale.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @scale is changed.
 **/
gboolean visu_map_setScaling(VisuMap *map, ToolMatrixScalingFlag scale)
{
  g_return_val_if_fail(VISU_IS_MAP(map), FALSE);

  if (scale == map->priv->scale)
    return FALSE;

  /* In log scale, we need the second minimum value. */
  map->priv->scale = scale;
  switch (scale)
    {
    case TOOL_MATRIX_SCALING_LINEAR:
      map->priv->get_val = tool_matrix_getScaledLinear;
      map->priv->get_inv = tool_matrix_getScaledLinearInv;
      break;
    case TOOL_MATRIX_SCALING_LOG:
      map->priv->get_val = tool_matrix_getScaledLog;
      map->priv->get_inv = tool_matrix_getScaledLogInv;
      break;
    case TOOL_MATRIX_SCALING_ZERO_CENTRED_LOG:
      map->priv->get_val = tool_matrix_getScaledZeroCentredLog;
      map->priv->get_inv = tool_matrix_getScaledZeroCentredLogInv;
      break;
    default:
      map->priv->get_val = tool_matrix_getScaledLinear;
      map->priv->get_inv = tool_matrix_getScaledLinearInv;
      break;
    }
  g_object_notify_by_pspec(G_OBJECT(map), properties[SCALE_PROP]);

  _setDirty(map);

  return TRUE;
}
/**
 * visu_map_setScalingRange:
 * @map: a #VisuMap object.
 * @minMax: (allow-none) (array fixed-size=2): two floats defining a
 * range for input scaling.
 *
 * If @minMax is provided, these values are used to scale the values
 * of @field to [0;1], otherwise the minMax values of the field itself
 * are used.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @minMax is changed.
 **/
gboolean visu_map_setScalingRange(VisuMap *map, const float *minMax)
{
  double minMax_[2];

  g_return_val_if_fail(VISU_IS_MAP(map), FALSE);

  DBG_fprintf(stderr, "Visu Map: set map %p scaling range to %p (%d).\n",
              (gpointer)map, (gpointer)minMax, map->priv->scaleauto);
  
  if ((!minMax && map->priv->scaleauto) ||
      (minMax && !map->priv->scaleauto &&
       minMax[0] == g_array_index(map->priv->scaleminmax, float, 0) &&
       minMax[1] == g_array_index(map->priv->scaleminmax, float, 1)))
    return FALSE;

  map->priv->scaleauto = (minMax == (float*)0);
  if (minMax)
    {
      g_array_index(map->priv->scaleminmax, float, 0) = minMax[0];
      g_array_index(map->priv->scaleminmax, float, 1) = minMax[1];
    }
  else if (map->priv->field)
    {
      visu_scalar_field_getMinMax(map->priv->field, minMax_);
      DBG_fprintf(stderr, "Visu Map: set map %p min/max to %g / %g.\n",
                  (gpointer)map, minMax_[0], minMax_[1]);
      g_array_index(map->priv->scaleminmax, float, 0) = minMax_[0];
      g_array_index(map->priv->scaleminmax, float, 1) = minMax_[1];
    }
  g_object_notify_by_pspec(G_OBJECT(map), properties[MANUAL_MM_PROP]);

  _setDirty(map);

  return TRUE;
}
/**
 * visu_map_setLevel:
 * @map: a #VisuMap object.
 * @glPrec: the global OpenGL precision for drawing (default is 1.).
 * @gross: current zoom level.
 * @refLength: a reference length (see visu_gl_camera_getRefLength()).
 *
 * Setup the level of recursivity in triangle calculation, depending
 * on the current zoom level.
 *
 * Since: 3.6
 *
 * Returns: TRUE if the level is actually changed.
 */
gboolean visu_map_setLevel(VisuMap *map, float glPrec, float gross, float refLength)
{
  float length, mLength;
  GList *lst;
  guint level;
  Triangle *tri;

  g_return_val_if_fail(map, FALSE);

  /* Compute the longest triangle length. */
  mLength = 0.f;
  for (lst = map->priv->triangles; lst; lst = g_list_next(lst))
    {
      tri = (Triangle*)lst->data;
      mLength = MAX(mLength,
                    (tri->vertices[0][0] - tri->vertices[1][0]) *
                    (tri->vertices[0][0] - tri->vertices[1][0]) +
                    (tri->vertices[0][1] - tri->vertices[1][1]) *
                    (tri->vertices[0][1] - tri->vertices[1][1]) +
                    (tri->vertices[0][2] - tri->vertices[1][2]) *
                    (tri->vertices[0][2] - tri->vertices[1][2]));
      mLength = MAX(mLength,
                    (tri->vertices[1][0] - tri->vertices[2][0]) *
                    (tri->vertices[1][0] - tri->vertices[2][0]) +
                    (tri->vertices[1][1] - tri->vertices[2][1]) *
                    (tri->vertices[1][1] - tri->vertices[2][1]) +
                    (tri->vertices[1][2] - tri->vertices[2][2]) *
                    (tri->vertices[1][2] - tri->vertices[2][2]));
      mLength = MAX(mLength,
                    (tri->vertices[2][0] - tri->vertices[0][0]) *
                    (tri->vertices[2][0] - tri->vertices[0][0]) +
                    (tri->vertices[2][1] - tri->vertices[0][1]) *
                    (tri->vertices[2][1] - tri->vertices[0][1]) +
                    (tri->vertices[2][2] - tri->vertices[0][2]) *
                    (tri->vertices[2][2] - tri->vertices[0][2]));
    }

  /* We put by default (precision and gross = 1) 200 triangles along
     the longest line in the box. */
  length = refLength / (200. * glPrec * (gross * .5 + .5));
  level = MIN(12, MAX((guint)(log(sqrt(mLength) / length) / log(2)), 2) - 1);
  DBG_fprintf(stderr, "Map: number of division %d for map %p.\n", level, (gpointer)map);

  if (level == map->priv->level)
    return FALSE;

  map->priv->level = level;

  _setDirty(map);

  return TRUE;
}
/**
 * visu_map_setLines:
 * @map: a #VisuMap object.
 * @nIsoLines: number of required isolines.
 * @minmax: span for isoline values.
 *
 * Calculate @nIsoLines equally distributed in @minmax.
 *
 * Since: 3.6
 *
 * Returns: TRUE if lines are successfully calculated.
 */
gboolean visu_map_setLines(VisuMap *map, guint nIsoLines, float minmax[2])
{
  g_return_val_if_fail(map, FALSE);

  DBG_fprintf(stderr, "Visu Map: %p setting line min / max to %g / %g (%g / %g).\n",
              (gpointer)map, minmax[0], minmax[1],
              map->priv->lineminmax[0], map->priv->lineminmax[1]);
  if (minmax[1] <= minmax[0])
    return FALSE;
  if (map->priv->nLines == nIsoLines &&
      map->priv->lineminmax[0] == minmax[0] &&
      map->priv->lineminmax[1] == minmax[1])
    return FALSE;

  if (_computeLines(map, nIsoLines, minmax))
    g_signal_emit(G_OBJECT(map), _signals[CHANGED_SIGNAL], 0);

  return TRUE;
}
static void map_refine(VisuMap *map, Triangle *T)
{
  float ABC[3][3];

  g_return_if_fail(T && map && T->level < 100);

  /* We update the values of this triangle. */
  triangle_setValues(T, map->priv->field, map->priv->get_val, 
                     (float*)map->priv->scaleminmax->data, map->priv->extension);

  if (T->level >= map->priv->level)
    return;

  if (!T->children[0])
    {
      ABC[0][0] = T->vertices[0][0]; ABC[0][1] = T->vertices[0][1]; ABC[0][2] = T->vertices[0][2];
      ABC[1][0] = 0.5f * (ABC[0][0] + T->vertices[1][0]);
      ABC[1][1] = 0.5f * (ABC[0][1] + T->vertices[1][1]);
      ABC[1][2] = 0.5f * (ABC[0][2] + T->vertices[1][2]);
      ABC[2][0] = 0.5f * (ABC[0][0] + T->vertices[2][0]);
      ABC[2][1] = 0.5f * (ABC[0][1] + T->vertices[2][1]);
      ABC[2][2] = 0.5f * (ABC[0][2] + T->vertices[2][2]);
      T->children[0] = triangle_new(ABC, T->level + 1);
    }
  map_refine(map, T->children[0]);
  T->minmax[0] = MIN(T->minmax[0], T->children[0]->minmax[0]);
  T->minmax[1] = MAX(T->minmax[1], T->children[0]->minmax[1]);

  if (!T->children[1])
    {
      ABC[0][0] = 0.5f * (T->vertices[2][0] + T->vertices[1][0]);
      ABC[0][1] = 0.5f * (T->vertices[2][1] + T->vertices[1][1]);
      ABC[0][2] = 0.5f * (T->vertices[2][2] + T->vertices[1][2]);
      T->children[1] = triangle_new(ABC, T->level + 1);
    }
  map_refine(map, T->children[1]);
  T->minmax[0] = MIN(T->minmax[0], T->children[1]->minmax[0]);
  T->minmax[1] = MAX(T->minmax[1], T->children[1]->minmax[1]);

  if (!T->children[2])
    {
      ABC[2][0] = T->vertices[1][0];
      ABC[2][1] = T->vertices[1][1];
      ABC[2][2] = T->vertices[1][2];
      T->children[2] = triangle_new(ABC, T->level + 1);
    }
  map_refine(map, T->children[2]);
  T->minmax[0] = MIN(T->minmax[0], T->children[2]->minmax[0]);
  T->minmax[1] = MAX(T->minmax[1], T->children[2]->minmax[1]);

  if (!T->children[3])
    {
      ABC[1][0] = 0.5f * (T->vertices[0][0] + T->vertices[2][0]);
      ABC[1][1] = 0.5f * (T->vertices[0][1] + T->vertices[2][1]);
      ABC[1][2] = 0.5f * (T->vertices[0][2] + T->vertices[2][2]);
      ABC[2][0] = T->vertices[2][0];
      ABC[2][1] = T->vertices[2][1];
      ABC[2][2] = T->vertices[2][2];
      T->children[3] = triangle_new(ABC, T->level + 1);
    }
  map_refine(map, T->children[3]);
  T->minmax[0] = MIN(T->minmax[0], T->children[3]->minmax[0]);
  T->minmax[1] = MAX(T->minmax[1], T->children[3]->minmax[1]);
}

static GList* _lockTriangles(VisuMap *map)
{
  GList *root;
  
  /* Disconnect the triangles during the computation. */
  g_mutex_lock(&map->priv->access);
  root = map->priv->triangles;
  map->priv->triangles = (GList*)0;
  g_mutex_unlock(&map->priv->access);

  return root;
}
static void _unlockTriangles(VisuMap *map, GList *root)
{
  /* Reconnect the triangles after computation. */
  g_mutex_lock(&map->priv->access);
  if (map->priv->triangles)
    g_list_free_full(root, (GDestroyNotify)triangle_free);
  else
    map->priv->triangles = root;
  g_mutex_unlock(&map->priv->access);
}

static void _computeOne(Triangle* tri, VisuMap *map)
{
  map_refine(map, tri);
  DBG_fprintf(stderr, " | %p done.\n", (gpointer)tri);
}
static void _computeAll(VisuMap *map)
{
  GList *inter, *root;

  DBG_fprintf(stderr, "Map: refine all triangles of map %p (%d) for level %d.\n",
              (gpointer)map, g_list_length(map->priv->triangles), map->priv->level);
  /* if (!klass->computePool) */
  /*   { */
  /*     klass->computePool = g_thread_pool_new((GFunc)_computeOne, map, */
  /*                                            4, TRUE, NULL); */
  /*     g_thread_pool_set_max_idle_time(5000); */
  /*   } */
  /* for (inter = map->priv->triangles; inter; inter = g_list_next(inter)) */
  /*   g_thread_pool_push(klass->computePool, inter->data, NULL); */
  /* while(g_thread_pool_get_num_threads(klass->computePool) > 0) DBG_fprintf(stderr, "%d\n", g_thread_pool_get_num_threads(klass->computePool)); */

  root = _lockTriangles(map);
  map->priv->minmax[0] = G_MAXFLOAT;
  map->priv->minmax[1] = -G_MAXFLOAT;
  for (inter = root; inter; inter = g_list_next(inter))
    {
      _computeOne((Triangle*)inter->data, map);
      map->priv->minmax[0] = MIN(map->priv->minmax[0], ((Triangle*)inter->data)->minmax[0]);
      map->priv->minmax[1] = MAX(map->priv->minmax[1], ((Triangle*)inter->data)->minmax[1]);
    }
  _unlockTriangles(map, root);

  DBG_fprintf(stderr, " | minmax is %g %g.\n", map->priv->minmax[0], map->priv->minmax[1]);
  map->priv->valMinMax[0] = map->priv->get_inv(map->priv->minmax[0],
                                               (float*)map->priv->scaleminmax->data);
  map->priv->valMinMax[1] = map->priv->get_inv(map->priv->minmax[1],
                                               (float*)map->priv->scaleminmax->data);
  DBG_fprintf(stderr, " | memory usage is %gko\n",
              g_list_length(map->priv->triangles) * pow(4, map->priv->level) *
              sizeof(Triangle) / 1024.f);
  DBG_fprintf(stderr, " | %d lines.\n", map->priv->nLines);

  map->priv->computing = FALSE;

  _computeLines(map, map->priv->nLines, map->priv->lineminmax);

  g_signal_emit(G_OBJECT(map), _signals[CHANGED_SIGNAL], 0);
}
#if GLIB_MINOR_VERSION > 35 && 0
static void _computeThread(GTask *task, VisuMap *map, gpointer data _U_,
                           GCancellable *cancel _U_)
{
  g_mutex_lock(&map->priv->mutex);
  _computeAll(map);
  g_mutex_unlock(&map->priv->mutex);
  g_task_return_boolean(task, TRUE);
}
#endif
static gboolean _compute(VisuMap *map)
{
#if GLIB_MINOR_VERSION > 35 && 0
  GTask *task;
#endif
  
  g_return_val_if_fail(map, FALSE);

  if (map->priv->dirty)
    {
      DBG_fprintf(stderr, "Visu Map %p: removing dirty idle %d.\n",
                  (gpointer)g_thread_self(), map->priv->dirty);
      g_source_remove(map->priv->dirty);
      map->priv->dirty = 0;
    }

  DBG_fprintf(stderr, "Visu Map %p: computing (%d %d %d).\n",
              (gpointer)g_thread_self(),
              g_list_length(map->priv->triangles), (map->priv->field != NULL),
              map->priv->field ? visu_scalar_field_isEmpty(map->priv->field) : FALSE);
  if (!map->priv->triangles ||
      !map->priv->field || visu_scalar_field_isEmpty(map->priv->field))
    return G_SOURCE_REMOVE;

  map->priv->computing = TRUE;
#if GLIB_MINOR_VERSION > 35 && 0
  task = g_task_new(map, NULL, NULL, NULL);
  g_task_run_in_thread(task, (GTaskThreadFunc)_computeThread);
  g_object_unref(task);
#else
  _computeAll(map);
#endif

  return G_SOURCE_REMOVE;
}
/**
 * visu_map_compute_sync:
 * @map: a #VisuMap object.
 *
 * For later use. Currently computation of maps is always synchronous.
 *
 * Since: 3.8
 **/
void visu_map_compute_sync(VisuMap *map)
{
  g_return_if_fail(VISU_IS_MAP(map));
  
  if (map->priv->dirty)
    {
      g_source_remove(map->priv->dirty);
      map->priv->dirty = 0;
    }
  
  map->priv->computing = TRUE;
  _computeAll(map);
}
static gboolean _computeLines(VisuMap *map, guint nIsoLines, float minMax[2])
{
  float v, **data;
  guint i, j, n;
  VisuLine *line;

  DBG_fprintf(stderr, "Visu Map: create %d new lines for map %p.\n",
              nIsoLines, (gpointer)map);
  map->priv->nLines = nIsoLines;
  map->priv->lineminmax[0] = minMax[0];
  map->priv->lineminmax[1] = minMax[1];

  if (map->priv->dirty || map->priv->computing)
    return FALSE;

  g_mutex_lock(&map->priv->access);
  _freeLines(map);
  if (nIsoLines > 0)
    {
      n = 0;
      data = _getDataAtLevel(map->priv->triangles, map->priv->level, &n);
      if (data)
        {
          j = 0;
          for (i = 1; i <= nIsoLines; i++)
            {
              v = minMax[0] + (minMax[1] - minMax[0]) *
                (float)i / (float)(nIsoLines + 1);
              DBG_fprintf(stderr, "Map: compute line %d at value %f.\n", j, v);
              line = visu_line_newFromTriangles(data, n, v);
              if (line)
                g_array_insert_val(map->priv->lines, j++, line);
              DBG_fprintf(stderr, " | Ok %p.\n", (gpointer)line);
            }
          g_free(data);
        }
    }
  g_mutex_unlock(&map->priv->access);
  return TRUE;
}
static void onPlaneMoved(VisuPlane *plane, gpointer data)
{
  VisuMap *map = (VisuMap*)data;

  /* We reset the plane. */
  _setTriangles(map, plane);
  /* We recompute. */
  if (map->priv->field)
    _setDirty(map);
}
static void onPlaneBoxed(VisuPlane *plane, VisuBox *box, gpointer data)
{
  VisuMap *map = (VisuMap*)data;

  if (box != (VisuBox*)0)
    visu_box_getExtension(box, map->priv->extension);

  /* We reset the plane. */
  _setTriangles(map, plane);
  /* We recompute. */
  if (map->priv->field)
    _setDirty(map);
}
static void onChanged(VisuScalarField *field _U_, VisuMap *map)
{
  DBG_fprintf(stderr, "Visu Map %p: caught 'changed' signal from %p.\n",
              (gpointer)g_thread_self(), (gpointer)field);
  if (map->priv->scaleauto)
    {
      /* Trick to force a recalculation of min/max. */
      map->priv->scaleauto = FALSE;
      visu_map_setScalingRange(map, (const float*)0);
    }
  /* We recompute. */
  _setDirty(map);
}
/**
 * visu_map_draw:
 * @map: a #VisuMap object.
 * @prec: a pourcentage value.
 * @shade: a #ToolShade object.
 * @rgb: a colour or NULL.
 * @alpha: a boolean.
 *
 * It draws the @map with the given @shade. @prec give the level of
 * refinement used to draw the map, 100 means normal and 200 means
 * twice smaller level. If @rgb is present, this colour is used for
 * possible isolines. If @alpha is TRUE, an alpha channel is added as
 * a linear variation of the value of each vertex.
 * 
 * Since: 3.6
 */
void visu_map_draw(VisuMap *map, float prec, ToolShade *shade, float *rgb,
                   gboolean alpha)
{
  GList *inter;
  float thresh;
  guint i;
  float rgba[4];
  VisuLine *line;

  if (!map->priv->triangles || !map->priv->field)
    return;

  if (map->priv->dirty)
    _compute(map);

  if (visu_scalar_field_isEmpty(map->priv->field) ||
      map->priv->computing)
    return;
  
  glDisable(GL_CULL_FACE);
  glDisable(GL_LIGHTING);

  prec = (.06f - prec * 0.0003f);
  DBG_fprintf(stderr, "Visu Map: %p plot triangles.\n", (gpointer)map);
  thresh = (map->priv->minmax[1] - map->priv->minmax[0]) * prec;
  if (alpha)
    for (inter = map->priv->triangles; inter; inter = g_list_next(inter))
      triangle_drawWithAlpha((Triangle*)inter->data, thresh, shade);
  else
    for (inter = map->priv->triangles; inter; inter = g_list_next(inter))
      triangle_draw((Triangle*)inter->data, thresh, shade);

  DBG_fprintf(stderr, "Visu Map: %p plot %d lines.\n",
              (gpointer)map, map->priv->lines->len);
  for (i = 0; i < map->priv->lines->len; i++)
    {
      line = g_array_index(map->priv->lines, VisuLine*, i);
      if (!rgb)
        {
          tool_shade_valueToRGB(shade, rgba, visu_line_getValue(line));
          rgba[0] = 1.f - rgba[0];
          rgba[1] = 1.f - rgba[1];
          rgba[2] = 1.f - rgba[2];
          rgb = rgba;
        }
      visu_line_draw(line, rgb);
    }

  if (!alpha && map->priv->plane)
    {
      DBG_fprintf(stderr, "Visu Map: %p plot box.\n", (gpointer)map);
      glLineWidth(1.f);
      glColor3f(0.f, 0.f, 0.f);
      glBegin(GL_LINE_LOOP);
      for (inter = visu_plane_getIntersection(map->priv->plane); inter; inter = g_list_next(inter))
        glVertex3fv((float*)inter->data);
      glEnd();
    }

  glEnable(GL_CULL_FACE);
  glEnable(GL_LIGHTING);
}
/**
 * visu_map_getPlane:
 * @map: a #VisuMap object.
 *
 * Retrieves the plane @map is based on.
 *
 * Since: 3.7
 *
 * Returns: (transfer none) (allow-none): a #VisuPlane or %NULL.
 **/
VisuPlane* visu_map_getPlane(VisuMap *map)
{
  g_return_val_if_fail(VISU_IS_MAP(map), (VisuPlane*)0);

  return map->priv->plane;
}
/**
 * visu_map_getField:
 * @map:  a #VisuMap object.
 *
 * Retrieves the field @map is projected from.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (allow-none): a #VisuScalarField or %NULL.
 **/
VisuScalarField* visu_map_getField(VisuMap *map)
{
  g_return_val_if_fail(VISU_IS_MAP(map), (VisuScalarField*)0);

  return map->priv->field;
}
/**
 * visu_map_getScaledMinMax:
 * @map: a #VisuMap object.
 * @minMax: (out caller-allocates) (array fixed-size=2): two float location.
 *
 * After @map has been computed, one can access
 * the scaled min and max values represented in the map. For
 * field values, see visu_map_getFieldMinMax().
 *
 * Since: 3.6
 *
 * Returns: TRUE if the map has been computed and @minMax has valid values.
 */
gboolean visu_map_getScaledMinMax(const VisuMap *map, float minMax[2])
{
  g_return_val_if_fail(VISU_IS_MAP(map), FALSE);

  if (map->priv->dirty || map->priv->computing)
    return FALSE;
  
  minMax[0] = map->priv->minmax[0];
  minMax[1] = map->priv->minmax[1];
  return TRUE;
}
/**
 * visu_map_getFieldMinMax:
 * @map: a #VisuMap object.
 *
 * After @map has been computed, one can access
 * the min and max values of the field as represented in the map. For
 * scaled values, see visu_map_getScaledMinMax().
 *
 * Since: 3.6
 *
 * Returns: two floats being the min and the max.
 */
float* visu_map_getFieldMinMax(VisuMap *map)
{
  g_return_val_if_fail(VISU_IS_MAP(map), (float*)0);

  return map->priv->valMinMax;
}
/**
 * visu_map_getScalingRange:
 * @map: a #VisuMap object.
 *
 * Retrieves the min and max values used to scale the data in @map.
 *
 * Since: 3.8
 *
 * Returns: (array fixed-size=2): a min / max range.
 **/
const float* visu_map_getScalingRange(const VisuMap *map)
{
  g_return_val_if_fail(VISU_IS_MAP(map), (const float*)0);

  return (const float*)map->priv->scaleminmax->data;
}

/**
 * visu_map_export:
 * @map: a #VisuMap object.
 * @shade: a #ToolShade object.
 * @rgb: a colour (can be NULL).
 * @precision: a pourcentage for rendering quality.
 * @filename: the location to export to.
 * @format: the kind of format.
 * @error: a location to store an error.
 *
 * Export the given map to the @format, using the @shade color. If
 * @rgb is provided and @map has some isolines, they will be drawn
 * with this colour, otherwise an inverse colour is used.
 *
 * Since: 3.6
 *
 * Returns: TRUE if no error.
 */
gboolean visu_map_export(VisuMap *map, const ToolShade *shade,
                         const float *rgb, float precision,
                         const gchar *filename, VisuMapExportFormat format,
                         GError **error)
{
#ifdef HAVE_CAIRO
#define fact 25.f
  float viewport[4];
  guint i, j;
  float *uvs;
  guint nVals;
  cairo_surface_t *surface;
  cairo_t *cr;
  cairo_status_t status;
  cairo_matrix_t mat = {fact, 0., 0., fact, 0., 0.};
  float basis[2][3], center[3], uv[2], rgba[4];
  GList *lst;
  VisuLine *line;
  
  g_return_val_if_fail(error && *error == (GError*)0, FALSE);

  viewport[0] = viewport[2] =  G_MAXFLOAT;
  viewport[1] = viewport[3] = -G_MAXFLOAT;
  visu_plane_getBasis(map->priv->plane, basis, center);
  for (lst = visu_plane_getIntersection(map->priv->plane); lst; lst = g_list_next(lst))
    {
      _3DToVisuPlane(uv, (float*)lst->data, basis, center);
      viewport[0] = MIN(viewport[0], uv[0]);
      viewport[1] = MAX(viewport[1], uv[0]);
      viewport[2] = MIN(viewport[2], uv[1]);
      viewport[3] = MAX(viewport[3], uv[1]);
    }
  DBG_fprintf(stderr, "Map: begin export to PDF/SVG in (%f-%f x %f-%f).\n",
              viewport[0], viewport[1], viewport[2], viewport[3]);
  switch (format)
    {
    case VISU_MAP_EXPORT_SVG:
      surface = cairo_svg_surface_create(filename,
					 (double)((viewport[1] - viewport[0]) * fact),
					 (double)((viewport[3] - viewport[2]) * fact));
      break;
    case VISU_MAP_EXPORT_PDF:
      surface = cairo_pdf_surface_create(filename,
					 (double)((viewport[1] - viewport[0]) * fact),
					 (double)((viewport[3] - viewport[2]) * fact));
      break;
    default:
      surface = (cairo_surface_t*)0;
    }
  status = cairo_surface_status(surface);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      *error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
			   "%s", cairo_status_to_string(status));
      cairo_surface_destroy(surface);
      return FALSE;
    }

  cr = cairo_create(surface);
  status = cairo_status(cr);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      *error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
			   "%s", cairo_status_to_string(status));
      cairo_destroy(cr);
      cairo_surface_destroy(surface);
      return FALSE;
    }
  mat.x0 = -(double)viewport[0] * fact;
  mat.y0 = -(double)viewport[2] * fact;
  cairo_set_matrix(cr, &mat);

  cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
  cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
  cairo_set_line_width(cr, 0.01);

  if (map->priv->dirty)
    _compute(map);
  while (map->priv->computing)
    {
      g_mutex_lock(&map->priv->mutex);
      g_mutex_unlock(&map->priv->mutex);
    }

  for (lst = map->priv->triangles; lst; lst = g_list_next(lst))
    triangle_drawToCairo((Triangle*)lst->data, cr,
                         (map->priv->minmax[1] - map->priv->minmax[0]) * (.06f - precision * 0.0003f),
                         shade, basis, center);

  /* Add isolines. */
  DBG_fprintf(stderr, "Map: export isolines.\n");
  for (i = 0; i < map->priv->lines->len; i++)
    {
      line = g_array_index(map->priv->lines, VisuLine*, i);
      if (!rgb)
        {
          tool_shade_valueToRGB(shade, rgba, visu_line_getValue(line));
          rgba[0] = 1.f - rgba[0];
          rgba[1] = 1.f - rgba[1];
          rgba[2] = 1.f - rgba[2];
          rgba[3] = 1.f;
          rgb = rgba;
        }
      cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);

      DBG_fprintf(stderr, "VisuScalarFields: project line on plane %p.\n",
                  (gpointer)map->priv->plane);
      uvs = visu_line_project(line, map->priv->plane, &nVals);
      for (j = 0; j < nVals; j++)
        {
          cairo_move_to(cr, uvs[j * 4 + 0], uvs[j * 4 + 1]);
          cairo_line_to(cr, uvs[j * 4 + 2], uvs[j * 4 + 3]);
          cairo_stroke(cr);
        }
      g_free(uvs);
    }

  /* Add frame. */
  DBG_fprintf(stderr, "Map: export the frame.\n");
  uvs = visu_plane_getReducedIntersection(map->priv->plane, &nVals);
  if (uvs)
    {
      cairo_set_source_rgb(cr, 0., 0., 0.);

      cairo_move_to(cr, uvs[(nVals - 1) * 2 + 0], uvs[(nVals - 1) * 2 + 1]);
      for (j = 0; j < nVals; j++)
        cairo_line_to(cr, uvs[j * 2 + 0], uvs[j * 2 + 1]);
      cairo_stroke(cr);
      g_free(uvs);
    }

  /* Add legend. */
/*   toolShadeExport(shade, (double)(viewport[2] * fact), */
/* 		  (double)(viewport[3] * fact)); */

  /* Finalising */
  cairo_show_page(cr);
  cairo_destroy(cr);
  cairo_surface_destroy(surface);

  return TRUE;
#else
  g_error("Not compiled with CAIRO not able to export.");
  return FALSE;
#endif
}

/*************************/
/* Resources management. */
/*************************/
static void exportResources(GString *data, VisuData *dataObj _U_)
{
  visu_config_file_exportComment(data, DESC_RESOURCE_LEG_SCALE);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_LEG_SCALE, NULL,
                               "%f", legendScale);

  visu_config_file_exportComment(data, DESC_RESOURCE_LEG_POS);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_LEG_POS, NULL,
                               "%f %f", legendPosition[0],
                               legendPosition[1]);

  visu_config_file_exportComment(data, "");
}
/**
 * visu_map_getLegendScale:
 *
 * Retrieve information about the static legend attached to maps.
 *
 * Since: 3.7
 *
 * Returns: the scale factor of the static legend associated to maps.
 **/
float visu_map_getLegendScale()
{
  return legendScale;
}
/**
 * visu_map_getLegendPosition:
 * @dir: %TOOL_XYZ_X or %TOOL_XYZ_Y
 *
 * Retrieve information about the static legend attached to maps.
 *
 * Since: 3.7
 *
 * Returns: the static legend position on screen in reduced coordinates.
 **/
float visu_map_getLegendPosition(ToolXyzDir dir)
{
  return (dir == TOOL_XYZ_X || dir == TOOL_XYZ_Y)?legendPosition[dir]:0.f;
}
