/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2011-2019)
  
	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 : Damien
	CALISTE, laboratoire L_Sim, (2011-2019)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

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

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

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include "node_vectors.h"

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

#include <opengl.h>
#include <openGLFunctions/text.h>
#include <coreTools/toolMatrix.h>
#include <coreTools/toolColor.h>
#include <renderingMethods/elementAtomic.h>
#include <extraFunctions/iface_sourceable.h>

#include <visu_configFile.h>



/**
 * SECTION:node_vectors
 * @short_description: Draw arrows at each node to represent forces,
 * displacements, vibrations...
 *
 * <para>A generic #VisuGlExt to represent vectors on nodes.</para>
 */

enum
  {
    TAIL_LENGTH,
    TAIL_RADIUS,
    TAIL_NLAT,
    HEAD_LENGTH,
    HEAD_RADIUS,
    HEAD_NLAT,
    N_ARROW_DEFS
  };

/**
 * VisuGlExtNodeVectorsClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuGlExtNodeVectorsClass structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtNodeVectors:
 *
 * An opaque structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtNodeVectorsPrivate:
 *
 * Private fields for #VisuGlExtNodeVectors objects.
 *
 * Since: 3.7
 */
struct _VisuGlExtNodeVectorsPrivate
{
  gboolean dispose_has_run;

  VisuSourceableData *source;

  /* Rendering definitions. */
  VisuGlExtNodeVectorsColorScheme colorScheme;
  float arrow[N_ARROW_DEFS];
  float normFactor, scale;
  VisuGlArrowCentering centering;
  float translation, addLength;
  float ratioMin, ratioMinLabel;

  VisuNodeArrayRenderer *renderer;
  gulong size_sig, col_sig, mat_sig, pop_sig, vis_sig, pos_sig, inc_sig;
};

enum
  {
    PROP_0,
    NORM_PROP,
    SCALE_PROP,
    N_PROP,
    SOURCE_PROP,
    MODEL_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_gl_ext_node_vectors_dispose(GObject* obj);
static void visu_gl_ext_node_vectors_rebuild(VisuGlExt *ext);
static void visu_gl_ext_node_vectors_draw(VisuGlExt *vect);
static void visu_gl_ext_node_vectors_get_property(GObject* obj, guint property_id,
                                                  GValue *value, GParamSpec *pspec);
static void visu_gl_ext_node_vectors_set_property(GObject* obj, guint property_id,
                                                  const GValue *value, GParamSpec *pspec);
static void visu_sourceable_interface_init(VisuSourceableInterface *iface);
static VisuSourceableData** _getSource(VisuSourceable *self);
static void _modelChanged(VisuSourceable *self);

/* Local callbacks. */
static void _setDirty(VisuGlExt *ext);
static void onElementSize(VisuElementRenderer *renderer, VisuElementRenderer *element,
                          gfloat extent, VisuGlExtNodeVectors *vect);

G_DEFINE_TYPE_WITH_CODE(VisuGlExtNodeVectors, visu_gl_ext_node_vectors, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtNodeVectors)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_SOURCEABLE,
                                              visu_sourceable_interface_init))

static void visu_gl_ext_node_vectors_class_init(VisuGlExtNodeVectorsClass *klass)
{
  DBG_fprintf(stderr, "Visu GlExt NodeVectors: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_gl_ext_node_vectors_dispose;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_node_vectors_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_node_vectors_get_property;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_node_vectors_rebuild;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_node_vectors_draw;

  /**
   * VisuGlExtNodeVectors::normalisation:
   *
   * The normalisation factor, zero to scale to the maximum value,
   * and positive for an absolute value.
   *
   * Since: 3.8
   */
  _properties[NORM_PROP] = g_param_spec_float("normalisation", "Normalisation",
                                              "normalisation factor", -1.f, G_MAXFLOAT, 0.f,
                                              G_PARAM_READWRITE);
  /**
   * VisuGlExtNodeVectors::rendering-size:
   *
   * When positive, this will be the rendering size for a normalised
   * value of 1, if negative, it is a factor used to scale element size.
   *
   * Since: 3.8
   */
  _properties[SCALE_PROP] = g_param_spec_float("rendering-size", "Rendering size",
                                               "rendering size", -G_MAXFLOAT, G_MAXFLOAT, -2.f,
                                               G_PARAM_READWRITE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);

  g_object_class_override_property(G_OBJECT_CLASS(klass), SOURCE_PROP, "source");
  g_object_class_override_property(G_OBJECT_CLASS(klass), MODEL_PROP, "model");
}
static void visu_sourceable_interface_init(VisuSourceableInterface *iface)
{
  iface->getSource = _getSource;
  iface->modelChanged = _modelChanged;
}

static void visu_gl_ext_node_vectors_init(VisuGlExtNodeVectors *obj)
{
  DBG_fprintf(stderr, "Visu GlExt NodeVectors: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_node_vectors_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  visu_sourceable_init(VISU_SOURCEABLE(obj));
  obj->priv->colorScheme        = VISU_COLOR_ELEMENT;
  obj->priv->arrow[TAIL_LENGTH] = 0.7f;
  obj->priv->arrow[TAIL_RADIUS] = 0.1f;
  obj->priv->arrow[TAIL_NLAT]   = 10.f;
  obj->priv->arrow[HEAD_LENGTH] = 0.3f;
  obj->priv->arrow[HEAD_RADIUS] = 0.15f;
  obj->priv->arrow[HEAD_NLAT]   = 10.f;
  obj->priv->normFactor         = -1.f;
  obj->priv->scale              = -2.f;
  obj->priv->centering          = VISU_GL_ARROW_BOTTOM_CENTERED;
  obj->priv->translation        = 0.f;
  obj->priv->addLength          = 0.f;
  obj->priv->ratioMin           = 0.f;
  obj->priv->ratioMinLabel      = G_MAXFLOAT;
  obj->priv->renderer           = (VisuNodeArrayRenderer*)0;
}

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

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

  vect = VISU_GL_EXT_NODE_VECTORS(obj);
  if (vect->priv->dispose_has_run)
    return;
  vect->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  visu_sourceable_dispose(VISU_SOURCEABLE(obj));
  visu_gl_ext_node_vectors_setNodeRenderer(vect, (VisuNodeArrayRenderer*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_node_vectors_parent_class)->dispose(obj);
}
static void visu_gl_ext_node_vectors_get_property(GObject* obj, guint property_id,
                                                  GValue *value, GParamSpec *pspec)
{
  VisuGlExtNodeVectors *self = VISU_GL_EXT_NODE_VECTORS(obj);

  DBG_fprintf(stderr, "Extension NodeVectors: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case SOURCE_PROP:
      g_value_set_string(value, visu_sourceable_getSource(VISU_SOURCEABLE(self)));
      DBG_fprintf(stderr, "%s.\n", g_value_get_string(value));
      break;
    case MODEL_PROP:
      g_value_set_object(value, visu_sourceable_getNodeModel(VISU_SOURCEABLE(self)));
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case NORM_PROP:
      g_value_set_float(value, self->priv->normFactor);
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      break;
    case SCALE_PROP:
      g_value_set_float(value, self->priv->scale);
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_node_vectors_set_property(GObject* obj, guint property_id,
                                                  const GValue *value, GParamSpec *pspec)
{
  VisuGlExtNodeVectors *self = VISU_GL_EXT_NODE_VECTORS(obj);

  DBG_fprintf(stderr, "Extension NodeVectors: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case SOURCE_PROP:
      DBG_fprintf(stderr, "%s.\n", g_value_get_string(value));
      visu_sourceable_setSource(VISU_SOURCEABLE(obj), g_value_get_string(value));
      break;
    case MODEL_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_sourceable_setNodeModel(VISU_SOURCEABLE(obj), g_value_get_object(value));
      break;
    case NORM_PROP:
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      visu_gl_ext_node_vectors_setNormalisation(self, g_value_get_float(value));
      break;
    case SCALE_PROP:
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      visu_gl_ext_node_vectors_setRenderedSize(self, g_value_get_float(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_node_vectors_new:
 * @name: (allow-none): the name to give to the extension.
 *
 * Creates a new #VisuGlExt to draw a box.
 *
 * Since: 3.7
 *
 * Returns: a pointer to the #VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtNodeVectors* visu_gl_ext_node_vectors_new(const gchar *name)
{
  char *name_ = "Node vectors";
  char *description = _("Draw vectors on each nodes.");

  DBG_fprintf(stderr,"Visu GlExt NodeVectors: new object.\n");
  
  return g_object_new(VISU_TYPE_GL_EXT_NODE_VECTORS,
                      "name", (name)?name:name_, "label", _(name),
                      "description", description, "nGlObj", 1,
                      "priority", VISU_GL_EXT_PRIORITY_NODE_DECORATIONS, NULL);
}

/**
 * visu_gl_ext_node_vectors_setNodeRenderer:
 * @vect: a #VisuGlExtNodeVectors object.
 * @renderer: a #VisuNodeArrayRenderer object.
 *
 * Use the #VisuElementRenderer properties from @renderer to display
 * vector properties.
 *
 * Since: 3.8
 *
 * Returns: TRUE if changed.
 **/
gboolean visu_gl_ext_node_vectors_setNodeRenderer(VisuGlExtNodeVectors *vect,
                                                  VisuNodeArrayRenderer *renderer)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->renderer == renderer)
    return FALSE;

  if (vect->priv->renderer)
    {
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->size_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->col_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->mat_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->pop_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->inc_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->vis_sig);
      g_signal_handler_disconnect(vect->priv->renderer, vect->priv->pos_sig);
      g_object_unref(vect->priv->renderer);
    }
  vect->priv->renderer = renderer;
  if (renderer)
    {
      g_object_ref(renderer);
      vect->priv->size_sig = g_signal_connect(renderer, "element-size-changed",
                                              G_CALLBACK(onElementSize), vect);
      vect->priv->col_sig = g_signal_connect_swapped(renderer, "element-notify::color",
                                                     G_CALLBACK(_setDirty), vect);
      vect->priv->mat_sig = g_signal_connect_swapped(renderer, "element-notify::material",
                                                     G_CALLBACK(_setDirty), vect);
      vect->priv->pop_sig = g_signal_connect_swapped(renderer, "nodes::population-decrease",
                                                     G_CALLBACK(_setDirty), vect);
      vect->priv->inc_sig = g_signal_connect_swapped(renderer, "nodes::population-increase",
                                                     G_CALLBACK(_setDirty), vect);
      vect->priv->vis_sig = g_signal_connect_swapped(renderer, "nodes::visibility",
                                                     G_CALLBACK(_setDirty), vect);
      vect->priv->pos_sig = g_signal_connect_swapped(renderer, "nodes::position",
                                                     G_CALLBACK(_setDirty), vect);
    }

  visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
static VisuSourceableData** _getSource(VisuSourceable *self)
{
  VisuGlExtNodeVectors *vect = VISU_GL_EXT_NODE_VECTORS(self);

  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(self), (VisuSourceableData**)0);

  return &vect->priv->source;
}
static void _modelChanged(VisuSourceable *self)
{
  visu_gl_ext_setDirty(VISU_GL_EXT(self), TRUE);
}

/**
 * visu_gl_ext_node_vectors_setRenderedSize:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @scale: a floating point value.
 *
 * @scale governs how large the node vectors are drawn. For a positive
 * value, a vector with a normalised size of 1 (see
 * visu_gl_ext_node_vectors_setNormalisation()) will be drawn with the size of
 * @scale. For a negative value, a vector of normalised size of 1 will
 * be drawn with a size of -@scale times the maximum element size (see
 * visu_node_array_renderer_getMaxElementSize()).
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setRenderedSize(VisuGlExtNodeVectors *vect, gfloat scale)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

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

  vect->priv->scale = scale;

  if (vect->priv->renderer && visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setNormalisation:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @norm: a floating point value.
 *
 * @norm governs how the input node vector field is normalised. With a
 * positive value, all node vectors will be normalised by @norm. With
 * a negative value, all node vectors are normalised with respect to
 * the biggest one.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setNormalisation(VisuGlExtNodeVectors *vect, float norm)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->normFactor == norm)
    return FALSE;

  vect->priv->normFactor = norm;

  g_object_notify_by_pspec(G_OBJECT(vect), _properties[NORM_PROP]);
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setTranslation:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @trans: a positive floating point value.
 *
 * Defines a translation with respect to the center of each node. The
 * vector is shifted outwards, following the vector direction by an
 * amount given by the product of @trans and the element size
 * currently drawn.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setTranslation(VisuGlExtNodeVectors *vect, float trans)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->translation == trans)
    return FALSE;

  vect->priv->translation = MAX(0.f, trans);

  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setColor:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @scheme: a value.
 *
 * If @scheme is %VISU_COLOR_ELEMENT, the vectors are drawn with the color of the
 * currently drawn #VisuElement. If @scheme is %VISU_COLOR_BRIGHT, they
 * are drawn with a highlighted color. If @scheme is
 * %VISU_COLOR_ORIENTATION, they are drawn with a hue depending onn orientation.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setColor(VisuGlExtNodeVectors *vect,
                                           VisuGlExtNodeVectorsColorScheme scheme)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->colorScheme == scheme)
    return FALSE;

  vect->priv->colorScheme = scheme;
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setCentering:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @centering: a #VisuGlArrowCentering id.
 *
 * Change how vectors are position with respect to to center of each node.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setCentering(VisuGlExtNodeVectors *vect,
                                               VisuGlArrowCentering centering)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->centering == centering)
    return FALSE;

  vect->priv->centering = centering;
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setAddLength:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @addLength: a positive value.
 *
 * Change the additional length that is used to draw the tail.
 *
 * Since: 3.8
 *
 * Returns: TRUE if settings has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setAddLength(VisuGlExtNodeVectors *vect, gfloat addLength)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->addLength == addLength)
    return FALSE;

  vect->priv->addLength = MAX(addLength, 0.f);
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setVectorThreshold:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @val: a value.
 *
 * Vectors are indeed drawn if a threshold value is reach. If @val is
 * strictly positive, the norm of each vector is compared to @val. If
 * @val is negative, the normalised [0;1] norm is compared to -@val.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setVectorThreshold(VisuGlExtNodeVectors *vect, float val)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->ratioMin == val)
    return FALSE;

  vect->priv->ratioMin = val;
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setLabelThreshold:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @val: a value.
 *
 * Vector norms can be drawn if a threshold value is reach. If @val is
 * strictly positive, the norm of each vector is compared to @val. If
 * @val is negative, the normalised [0;1] norm is compared to -@val.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setLabelThreshold(VisuGlExtNodeVectors *vect, float val)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  if (vect->priv->ratioMinLabel == val)
    return FALSE;

  vect->priv->ratioMinLabel = val;
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_node_vectors_setArrow:
 * @vect: the #VisuGlExtNodeVectors object to modify.
 * @tailLength: the length for the tail part of the vector.
 * @tailRadius: the radius for the tail part of the vector.
 * @tailN: the number of polygons to draw the tail part of the vector.
 * @headLength: the length for the head part of the vector.
 * @headRadius: the radius for the head part of the vector.
 * @headN: the number of polygons to draw the head part of the vector.
 *
 * Defines the profile of the arrows representing the vectors.
 *
 * Since: 3.7
 *
 * Returns: TRUE if setting has been changed.
 **/
gboolean visu_gl_ext_node_vectors_setArrow(VisuGlExtNodeVectors *vect, float tailLength,
                                           float tailRadius, guint tailN,
                                           float headLength, float headRadius, guint headN)
{
  gboolean diff;
  float fact;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), FALSE);

  diff = FALSE;
  diff = diff || (vect->priv->arrow[TAIL_LENGTH] != tailLength);
  diff = diff || (vect->priv->arrow[TAIL_RADIUS] != tailRadius);
  diff = diff || (vect->priv->arrow[TAIL_NLAT] != tailN);
  diff = diff || (vect->priv->arrow[HEAD_LENGTH] != headLength);
  diff = diff || (vect->priv->arrow[HEAD_RADIUS] != headRadius);
  diff = diff || (vect->priv->arrow[HEAD_NLAT] != headN);
  if (!diff)
    return FALSE;

  fact = 1.f / (tailLength + headLength);
  vect->priv->arrow[TAIL_LENGTH] = tailLength * fact;
  vect->priv->arrow[TAIL_RADIUS] = tailRadius * fact;
  vect->priv->arrow[TAIL_NLAT] = tailN;
  vect->priv->arrow[HEAD_LENGTH] = headLength * fact;
  vect->priv->arrow[HEAD_RADIUS] = headRadius * fact;
  vect->priv->arrow[HEAD_NLAT] = headN;
  
  if (vect->priv->renderer && visu_sourceable_getSource(VISU_SOURCEABLE(vect)))
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
  return TRUE;
}

/**
 * visu_gl_ext_node_vectors_getScale:
 *
 * The forces can be scaled manually or automatically, see
 * visu_gl_ext_forces_setScale() and visu_rendering_atomic_drawForces().
 *
 * Since: 3.7
 *
 * Returns: the scaling factor, -1 if automatic.
 **/
/* gfloat visu_gl_ext_forces_getScale() */
/* { */
/*   return scale; */
/* } */
/**
 * visu_gl_ext_node_vectors_getNormalisation:
 * @vect: the #VisuGlExtNodeVectors object to inquire.
 *
 * Gets the normalisation factor, see visu_gl_ext_node_vectors_setNormalisation().
 *
 * Since: 3.7
 *
 * Returns: the normalisation factor used by @vact.
 **/
float visu_gl_ext_node_vectors_getNormalisation(VisuGlExtNodeVectors *vect)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODE_VECTORS(vect), -1.f);
  
  return vect->priv->normFactor;
}

/********************/
/* Local callbacks. */
/********************/
static void _setDirty(VisuGlExt *ext)
{
  visu_gl_ext_setDirty(ext, TRUE);
}
static void onElementSize(VisuElementRenderer *renderer _U_, VisuElementRenderer *element _U_,
                          gfloat extent _U_, VisuGlExtNodeVectors *vect)
{
  if (vect->priv->scale < 0.f || vect->priv->translation > 0.f)
    visu_gl_ext_setDirty(VISU_GL_EXT(vect), TRUE);
}

/***********/
/* OpenGL. */
/***********/

static void visu_gl_ext_node_vectors_draw(VisuGlExt *vect)
{
  VisuNodeArrayRendererIter iter;
  GLUquadricObj *obj;
  float fact, scale, eleSize;
  float rMult, r, sMult, s, l, headLength, tailLength;
  float xyz[3], dxyz[3], hsl[3], rgba[4];
  const gfloat *sph;
  char distStr[10];
  gboolean valid;
  VisuGlExtNodeVectorsPrivate *priv = VISU_GL_EXT_NODE_VECTORS(vect)->priv;

  glDeleteLists(visu_gl_ext_getGlList(vect), 1);
  visu_gl_ext_setDirty(vect, FALSE);

  DBG_fprintf(stderr, "Extension Node Vectors: building for nodes %p and vectors %p.\n",
              (gpointer)priv->renderer,
              (gpointer)visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect)));
  if (!priv->renderer || !visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect)))
    return;

  obj = gluNewQuadric();

  /* Normalization factor. */
  fact = priv->normFactor;
  if (fact <= 0.f)
    fact = visu_node_values_farray_max(VISU_NODE_VALUES_FARRAY(visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect))));
  fact = 1.f / fact;

  /* Drawing rule for vectors. */
  rMult = 1.f;
  r     = 1.f;
  if (priv->ratioMin <= 0.f)
    {
      rMult = fact;
      r = -1.f;
    }
  /* Drawing rule for label. */
  sMult = 1.f;
  s     = 1.f;
  if (priv->ratioMinLabel <= 0.f)
    {
      sMult = fact;
      s = -1.f;
    }

  /* Maximum representation size. */
  scale = priv->scale;
  if (scale <= 0.f)
    scale = visu_node_array_renderer_getMaxElementSize(priv->renderer, NULL) * (-scale);

  visu_gl_ext_startDrawing(vect);

  eleSize = 0.f;
  for (valid = visu_node_array_renderer_iter_new(priv->renderer, &iter, TRUE);
       valid; valid = visu_node_array_renderer_iter_next(&iter))
    {
      if (!visu_element_getRendered(iter.element))
        continue;

      if (priv->colorScheme == VISU_COLOR_ELEMENT)
        visu_element_renderer_colorize(iter.renderer, VISU_ELEMENT_RENDERER_NO_EFFECT);
      else if (priv->colorScheme == VISU_COLOR_BRIGHT)
        visu_element_renderer_colorize(iter.renderer, VISU_ELEMENT_RENDERER_HIGHLIGHT);
      eleSize = visu_element_renderer_getExtent(iter.renderer);

      for(visu_node_array_iterRestartNode(iter.parent.array, &iter.parent); iter.parent.node;
          visu_node_array_iterNextNode(iter.parent.array, &iter.parent))
        {
          if (!iter.parent.node->rendered)
            continue;

          sph = visu_node_values_vector_getAtSpherical(VISU_NODE_VALUES_VECTOR(visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect))), iter.parent.node);
          if (!sph)
            continue;

          if (sph[TOOL_MATRIX_SPHERICAL_MODULUS] * rMult <= priv->ratioMin * r)
            continue;
          l = sph[TOOL_MATRIX_SPHERICAL_MODULUS] * fact;

          visu_data_getNodePosition(VISU_DATA(iter.parent.array), iter.parent.node, xyz);
          visu_node_values_vector_getShift(VISU_NODE_VALUES_VECTOR(visu_sourceable_getNodeModel(VISU_SOURCEABLE(vect))), iter.parent.node, dxyz);

          glPushMatrix();
          glTranslatef(xyz[0] - dxyz[0], xyz[1] - dxyz[1], xyz[2] - dxyz[2]);
          /* We draw the arrow. */
          if (priv->colorScheme == VISU_COLOR_ORIENTATION)
            {
              hsl[2] = 1.f - sph[TOOL_MATRIX_SPHERICAL_THETA] / 180.;
              hsl[1] = 1.f;
              hsl[0] = sph[TOOL_MATRIX_SPHERICAL_PHI] / 360.;

              tool_color_convertHSLtoRGB(rgba, hsl);

              rgba[3] = visu_element_renderer_getColor(iter.renderer)->rgba[3];
              visu_gl_setColor((VisuGl*)0, visu_element_renderer_getMaterial(iter.renderer), rgba);
            }
          glRotated(sph[TOOL_MATRIX_SPHERICAL_PHI], 0, 0, 1);
          glRotated(sph[TOOL_MATRIX_SPHERICAL_THETA], 0, 1, 0);
          glTranslated(0.f, 0.f, eleSize * priv->translation);
          switch (priv->centering)
            {
            case (VISU_GL_ARROW_CENTERED):
              glScalef(scale, scale, scale);
              tailLength = MAX(0.f, l - priv->arrow[HEAD_LENGTH]);
              headLength = MIN(l, priv->arrow[HEAD_LENGTH]);
              break;
            case (VISU_GL_ARROW_TAIL_CENTERED):
              glScalef(eleSize, eleSize, eleSize);
              tailLength = l * scale / eleSize + priv->addLength;
              headLength = priv->arrow[HEAD_LENGTH];
              break;
            default:
              glScalef(l * scale, l * scale, l * scale);
              tailLength = priv->arrow[TAIL_LENGTH];
              headLength = priv->arrow[HEAD_LENGTH];
            }
          visu_gl_drawSmoothArrow(obj, priv->centering,
                                  tailLength,
                                  priv->arrow[TAIL_RADIUS],
                                  (guint)priv->arrow[TAIL_NLAT], NULL,
                                  headLength,
                                  priv->arrow[HEAD_RADIUS],
                                  (guint)priv->arrow[HEAD_NLAT], NULL);
          /* We draw the value. */
          if (sph[TOOL_MATRIX_SPHERICAL_MODULUS] * sMult > priv->ratioMinLabel * s)
            {
              glRasterPos3f(0.f, 0.f, 0.f);
              sprintf(distStr, "%6.3f", sph[TOOL_MATRIX_SPHERICAL_MODULUS]);
              visu_gl_text_drawChars(distStr, VISU_GL_TEXT_NORMAL);
            }
          glPopMatrix();
        }
    }

  visu_gl_ext_completeDrawing(vect);

  gluDeleteQuadric(obj);
}
static void visu_gl_ext_node_vectors_rebuild(VisuGlExt *ext)
{
  DBG_fprintf(stderr, "Visu GlExt NodeVectors: rebuilding vectors %p.\n", (gpointer)ext);
  visu_gl_text_rebuildFontList();
  visu_gl_ext_setDirty(ext, TRUE);
  visu_gl_ext_node_vectors_draw(ext);
}
